Logo Search packages:      
Sourcecode: davfs2 version File versions  Download package

webdav.c

/* 
** Send requests to the WebDAV server.
 */

/*
 *  This file is part of davfs2.
 *
 *  davfs2 is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  davfs2 is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with davfs2; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */


#include "config.h"

#include <argz.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>

#include <ne_alloc.h>
#include <ne_auth.h>
#include <ne_basic.h>
#include <ne_dates.h>
#include <ne_locks.h>
#include <ne_props.h>
#include <ne_request.h>
#include <ne_session.h>
#include <ne_socket.h>
#include <ne_string.h>
#include <ne_uri.h>
#include <ne_utils.h>

#include <sys/stat.h>

#include "dav_debug.h"
#include "defaults.h"
#include "webdav.h"


/* Data Types */
/*============*/

/* Data structures used as userdata in neon callback functions. */

typedef struct {
    const char *path;           /* The *not* url-encoded path. */
    dav_props *results;         /* Start of the linked list of dav_props. */
} propfind_context;

typedef struct {
    int error;                  /* An error occured while reading/writing. */
    const char *file;           /* cache_file to store the data in. */
    int fd;                     /* file descriptor of the open cache file. */
} get_context;


/* Private constants */
/*===================*/

/* Properties to be retrieved from the server. This constants
   are used by dav_get_collection(). */
enum {
    NAME = 0,
    ETAG,
    LENGTH,
    CREATION,
    MODIFIED,
    TYPE,
    EXECUTE,
    END
};

static const ne_propname prop_names[] = {
    [NAME] = {"DAV:", "displayname"},
    [ETAG] = {"DAV:", "getetag"},
    [LENGTH] = {"DAV:", "getcontentlength"},
    [CREATION] ={"DAV:", "creationdate"},
    [MODIFIED] = {"DAV:", "getlastmodified"},
    [TYPE] = {"DAV:", "resourcetype"},
    [EXECUTE] = {"http://apache.org/dav/props/", "executable"},
    [END] = {NULL, NULL}
};


/* Private global variables */
/*==========================*/

/* The neon session.
   Will be set by dav_init_webdav(). */
static ne_session *session;

/* Lock store, lock owner and lock timeout for session.
   Will be set by dav_init_webdav(). */
static ne_lock_store *locks;
static char *owner;
static time_t lock_timeout;

/* Credentials for this session. Will be set by dav_init_webdav(). */
static char *username;
static char *password;
static char *p_username;
static char *p_password;

/* Whether to use the displayname property for file and directory names. */
int use_displayname;

/* The url for this session. Contains anything needed except path.
   Will be initialized by dav_init_webdav() and used by various functions.
   These will add path, use suri in calls to neon functions and finally
   free path. path must allways be initialized before use, as it may
   point to anything. */
static ne_uri *suri;
   
/* Whether a terminal is available to communicate with the user.
   Should be reset with set_no_terminal() when forking into daemon mode.
   Needed by  ssl_verify() which may be called at any time. */
static int have_terminal = 1;


/* Private function prototypes and inline functions */
/*==================================================*/

static int get_error(int ret, const char *method);

static int get_ne_error(const char *method);

static int lock_discover(const char *spath, time_t *expire);

static void lock_refresh(struct ne_lock *lock, time_t *expire);

static int put_file(const char *spath, int fd, time_t *expire,
                    time_t *mtime);

/* Call-back functions for neon. */

static int auth(void *userdata, const char *realm, int attempt, char *user,
                char *pwd);

#if NE_VERSION_MINOR == 24
static void block_writer(void *userdata, const char *block, size_t length);
#else
static int block_writer(void *userdata, const char *block, size_t length);
#endif

#if NE_VERSION_MINOR < 26
static void lock_result(void *userdata, const struct ne_lock *lock, 
                                    const char *uri, const ne_status *status);

static void prop_result(void *userdata, const char *href,
                         const ne_prop_result_set *set);
#else
static void lock_result(void *userdata, const struct ne_lock *lock, 
                                    const ne_uri *uri, const ne_status *status);

static void prop_result(void *userdata, const ne_uri *uri,
                         const ne_prop_result_set *set);
#endif

static int ssl_verify(void *userdata, int failures,
                      const ne_ssl_certificate *cert);


/* Public functions */
/*==================*/

void dav_init_webdav(const char *scheme, const char *host, int port,
                     time_t read_timeout, int use_locks, time_t l_timeout,
                     const char *user, const char *passwd, int useproxy,
                     const char *p_host, int p_port, const char *p_user,
                     const char *p_passwd, const char *lock_owner,
                     int displayname) {

    if (ne_sock_init() != 0)
        error(EXIT_FAILURE, 0, "Socket library initalization failed");

    session = ne_session_create(scheme, host, port);

#if NE_VERSION_MINOR == 24
    ne_set_expect100(session, 1);
#endif

    ne_set_read_timeout(session, read_timeout);

    char *useragent = ne_concat(PACKAGE_TARNAME, "/", PACKAGE_VERSION, NULL);
    ne_set_useragent(session, useragent);
    free(useragent);
    
    if (use_locks) {
        locks = ne_lockstore_create();
        ne_lockstore_register(locks, session);
        if (lock_owner == NULL) {
            if (user == NULL) {
                owner = ne_concat("unknown/", PACKAGE_STRING, NULL);
            } else {
                owner = ne_concat(user, "/", PACKAGE_STRING, NULL);
            }
        } else {
            owner = ne_strdup(lock_owner);
        }
        lock_timeout = l_timeout;
    }

    if (user != NULL)
        username = ne_strdup(user);
    if (passwd != NULL)
        password = ne_strdup(passwd);
    ne_set_server_auth(session, auth, "server");
    
    if (useproxy && p_host != NULL) {
        ne_session_proxy(session, p_host, p_port);
        if (p_user != NULL)
            p_username = ne_strdup(p_user);
        if (p_passwd != NULL)
            p_password = ne_strdup(p_passwd);
        ne_set_proxy_auth(session, auth, "proxy");
    }
    
    if (strcmp(scheme, "https") == 0) {
#if NE_VERSION_MINOR == 24
        if (!ne_supports_ssl())
#else
        if (!ne_has_support(NE_FEATURE_SSL))
#endif
            error(EXIT_FAILURE, 0, "Neon library does not support TLS/SSL.");
        ne_ssl_set_verify(session, ssl_verify, NULL);
        ne_ssl_trust_default_ca(session);
    }

    suri = (ne_uri *) ne_calloc(sizeof(ne_uri));
    ne_fill_server_uri(session, suri);
    
    use_displayname = displayname;
}


void dav_close_webdav(void) {

    if(session == NULL)
        return;

    DBG1("Closing connection to  %s", ne_get_server_hostport(session));

    if (locks != NULL) {
        struct ne_lock *lock = ne_lockstore_first(locks);
        while (lock != NULL) {
            DBG1("  UNLOCK %s", lock->uri.path);
            int ret = ne_unlock(session, lock);
            get_error(ret, "UNLOCK");
            lock = ne_lockstore_next(locks);
        }
    }

    ne_session_destroy(session);
    ne_sock_exit();
}


int dav_delete(const char *path, time_t *expire) {

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();

    DBG1("  DELETE %s", suri->path);
    int ret = ne_delete(session, suri->path);
    ret = get_error(ret, "DELETE");
    if (ret == EACCES && lock_discover(suri->path, expire) == 0) {
        ret = ne_delete(session, suri->path);
        ret = get_error(ret, "DELETE");
    }

    if (ret == 0 && locks != NULL && *expire != 0) {
        struct ne_lock *lock = ne_lockstore_findbyuri(locks, suri);
        if (lock != NULL)
            ne_lockstore_remove(locks, lock);
    }

    free(suri->path);
    return ret;
}


int dav_delete_dir(const char *path) {

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();

    DBG1("  DELETE %s", suri->path);
    int ret = ne_delete(session, suri->path);
    ret = get_error(ret, "DELETE");

    free(suri->path);
    return ret;
}


void dav_delete_props(dav_props *props) {

    if (props->path != NULL)
        free(props->path);
    if (props->name != NULL)
        free(props->name);
    if (props->etag != NULL) 
        free(props->etag);
    free(props);
}


int dav_get_collection(const char *path, dav_props **props) {

    propfind_context ctx;
    ctx.path = path;
    ctx.results = NULL;

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    ne_propfind_handler *pfh = ne_propfind_create(session, suri->path,
                                                  NE_DEPTH_ONE);
    DBG1("  PROPFIND %s", suri->path);
    int ret = ne_propfind_named(pfh, prop_names, prop_result, &ctx);
    ret = get_error(ret, "PROPFIND");
    ne_propfind_destroy(pfh);
    free(suri->path);

    if (ret != 0) {
        while(ctx.results != NULL) {
            dav_props *tofree = ctx.results;
            ctx.results = ctx.results->next;
            dav_delete_props(tofree);
        }    
    }

    *props = ctx.results;
    return ret;
}


int dav_get_file(const char *path, const char *cache_path, off_t *size,
                 char **etag, time_t *mtime) {

    get_context ctx;
    ctx.error = 0;
    ctx.file = cache_path;
    ctx.fd = 0;

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    ne_request *req = ne_request_create(session, "GET", suri->path);

    if (etag != NULL && *etag != NULL)
        ne_add_request_header(req, "If-Non-Match", *etag);
    char *mod_time = NULL;
    if (mtime != NULL && *mtime != 0) {
        mod_time = ne_rfc1123_date(*mtime);
        ne_add_request_header(req, "If-Modified-Since", mod_time);
    }

#if NE_VERSION_MINOR == 24

    char *r_mod_time = NULL;
    ne_add_response_header_handler(req, "Last-Modified", ne_duplicate_header,
                                   &r_mod_time);
    char *r_etag = NULL;
    ne_add_response_header_handler(req, "ETag", ne_duplicate_header, &r_etag);
    off_t total = 0;
    ne_add_response_header_handler(req, "Content-Length",
                                   ne_handle_numeric_header, &total);

#endif    /* NE_VERSION_MINOR */

    ne_add_response_body_reader(req, ne_accept_2xx, block_writer, &ctx);

    DBG1("  GET %s", suri->path);
    int ret = ne_request_dispatch(req);
    ret = get_error(ret, "GET");
    if (ctx.error)
        ret = ctx.error;

    const ne_status *status = ne_get_status(req);

#if NE_VERSION_MINOR == 24

    if (ret == 0 && status->code == 200) {
        *size = total;
        if (mtime != NULL && r_mod_time != NULL)
            *mtime = ne_httpdate_parse(r_mod_time);
        if (etag != NULL) {
            if (*etag != NULL)
                free(*etag);
            if (r_etag != NULL) {
                char *s = strchr(r_etag, '\"');
                if (s != NULL) {
                    *etag = ne_strdup(s);
                } else {
                    *etag = NULL;
                }
            }
        }
    }
    if (r_etag != NULL)
        free(r_etag);
    if (r_mod_time != NULL)
        free(r_mod_time);

#else     /* NE_VERSION_MINOR */

    if (ret == 0 && status->code == 200) {
        const char *value = ne_get_response_header(req, "Content-Length");
        if (value != NULL)
#if _FILE_OFFSET_BITS == 64
            *size = strtoll(value, NULL, 10);
#else
            *size = strtol(value, NULL, 10);
#endif
        value = ne_get_response_header(req, "Last-Modified");
        if (mtime != NULL && value != NULL)
            *mtime = ne_httpdate_parse(value);
        if (etag != NULL) {
            if (*etag != NULL)
                free(*etag);
            value = ne_get_response_header(req, "ETag");
            if (value != NULL)
                value = strchr(value, '\"');
            if (value != NULL) {
                *etag = ne_strdup(value);
            } else {
                *etag = NULL;
            }
        }
    }

#endif    /* NE_VERSION_MINOR */

    ne_request_destroy(req);
    if (ctx.fd > 0)
        close(ctx.fd);
    if (mod_time != NULL)
        free(mod_time);
    free(suri->path);
    return ret;
}


int dav_head(const char *path, char **etag, time_t *mtime) {

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    ne_request *req = ne_request_create(session, "HEAD", suri->path);

#if NE_VERSION_MINOR == 24

    char *r_mod_time = NULL;
    ne_add_response_header_handler(req, "Last-Modified",
                                   ne_duplicate_header, &r_mod_time);
    char *r_etag = NULL;
    ne_add_response_header_handler(req, "ETag", ne_duplicate_header, &r_etag);

#endif    /* NE_VERSION_MINOR */

    DBG1("  HEAD %s", suri->path);
    int ret = ne_request_dispatch(req);
    ret = get_error(ret, "HEAD");

#if NE_VERSION_MINOR == 24

    if (ret == 0 && etag != NULL && r_etag != NULL) {
        if (*etag != NULL)
            free(*etag);
        char *s = strchr(r_etag, '\"');
        if (s != NULL) {
            *etag = ne_strdup(s);
        } else {
            *etag = NULL;
        }
    }
    if (ret == 0 && mtime != 0 && r_mod_time != NULL)
        *mtime = ne_httpdate_parse(r_mod_time);
    if (r_etag != NULL)
        free(r_etag);
    if (r_mod_time != NULL)
        free(r_mod_time);

#else     /* NE_VERSION_MINOR */

    const char *value = ne_get_response_header(req, "ETag");
    if (ret == 0 && etag != 0 && value != NULL) {
        if (*etag != NULL)
            free(*etag);
        value = strchr(value, '\"');
        if (value != NULL) {
            *etag = ne_strdup(value);
        } else {
            *etag = NULL;
        }
    }
    value = ne_get_response_header(req, "Last-Modified");
    if (ret == 0 && mtime != 0 && value != NULL)
        *mtime = ne_httpdate_parse(value);

#endif    /* NE_VERSION_MINOR */

    ne_request_destroy(req);
    free(suri->path);
    return ret;
}


int dav_lock(const char *path, time_t *expire, int *exists) {

    if (locks == NULL) {
        *expire = 0;
        return 0;
    }

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();

    struct ne_lock *lock = ne_lockstore_findbyuri(locks, suri);
    if (lock != NULL) {
        free(suri->path);
        *expire = -1;
        lock_refresh(lock, expire);
        return 0;
    }

    lock = ne_lock_create();
    lock->uri.scheme = ne_strdup(suri->scheme);
    lock->uri.host = ne_strdup(suri->host);
    lock->uri.port = suri->port;
    lock->uri.path = suri->path;
    suri->path = NULL;
    lock->owner = ne_strdup(owner);
    lock->timeout = lock_timeout;

    DBG1("  LOCK %s", lock->uri.path);
    int ret = ne_lock(session, lock);
    ret = get_error(ret, "LOCK");

    if (ret == 0) {
        ne_lockstore_add(locks, lock);
        if (strtol(ne_get_error(session), NULL, 10) == 201)
            *exists = 1;
#if NE_VERSION_MINOR > 24
        if (lock->timeout <= 0) {
            *expire = LONG_MAX;
        } else {
            *expire = lock->timeout + time(NULL);
        }
#else
        *expire = LONG_MAX;
#endif
    } else {
        if (ret == EACCES && lock_discover(lock->uri.path, expire) == 0)
            ret = 0;
        ne_lock_destroy(lock);
    }

    return ret;
}


void dav_lock_refresh(const char *path, time_t *expire) {

    if (locks == NULL) {
        *expire = 0;
        return;
    }

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    struct ne_lock *lock = ne_lockstore_findbyuri(locks, suri);

    if (lock == NULL) {
        lock_discover(suri->path, expire);
    } else {
        lock_refresh(lock, expire);
    }

    free(suri->path);
}


int dav_make_collection(const char *path) {

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    DBG1("  MKCOL %s", suri->path);
    int ret = ne_mkcol(session, suri->path);
    ret = get_error(ret, "MKCOL");
    free(suri->path);
    return ret;
}


int dav_move(const char *src, const char *dst) {

    suri->path = ne_path_escape(src);
    char *dst_path = ne_path_escape(dst);
    if (suri->path == NULL || dst_path == NULL)
        abort();

    DBG1("  MOVE %s", suri->path);
    DBG1("       %s", dst_path);
    int ret = ne_move(session, 1, suri->path, dst_path); 
    ret = get_error(ret, "MOVE");

    free(suri->path);
    free(dst_path);
    return ret;
}


int dav_put(const char *path, const char *cache_path, int *exists,
            time_t *expire, char **etag, time_t *mtime) {

    int ret = 0;

    if (*exists == 0 || (etag != NULL && *etag != NULL)
            || (mtime != NULL && *mtime != 0)) {
        char *r_etag = NULL;
        time_t r_mtime = 0;
        ret = dav_head(path, &r_etag, &r_mtime);
        if (ret == 0 && *exists == 0) {
            ret = EEXIST;
        } else if (ret == ENOENT && *exists == 0) {
            ret = 0;
        } else if (ret == ENOENT) {
            if (dav_unlock(path, expire) == 0)
                ret = 0;
        } else if (ret == 0 && etag != NULL && *etag != NULL && r_etag != NULL
                   && strcmp(*etag, r_etag) != 0) {
            ret = EINVAL;
        } else if (ret == 0 && mtime != NULL && *mtime != 0 && r_mtime != 0
                   && *mtime < r_mtime) {
            ret = EINVAL;
        }
        if (r_etag != NULL)
            free(r_etag);
        if (ret != 0)
            return ret;
    }

    int fd = open(cache_path, O_RDONLY);
    if (fd <= 0)
        return EIO;

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();

    ret = put_file(suri->path, fd, expire, mtime);
    free(suri->path);
    close(fd);

    if (ret == 0) {
        *exists = 1;
        dav_head(path, etag, mtime);
    }

    return ret;
}


int dav_set_execute(const char *path, int set) {

    ne_proppatch_operation op[2];
    op[0].name = &prop_names[EXECUTE];
    op[0].type = ne_propset;
    if (set) {
        op[0].value = "T";
    } else {
        op[0].value = "F";
    }
    op[1].name = NULL;

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();
    DBG1("  PROPPATCH %s", suri->path);
    int ret = ne_proppatch(session, suri->path, &op[0]);
    ret = get_error(ret, "PROPPATCH");
    free(suri->path);

    return ret;
}


void dav_set_no_terminal(void) {
    have_terminal = 0;
}


int dav_unlock(const char *path, time_t *expire) {

    if (locks == NULL) {
        *expire = 0;
        return 0;
    }

    suri->path = ne_path_escape(path);
    if (suri->path == NULL)
        abort();

    struct ne_lock *lock = ne_lockstore_findbyuri(locks, suri);
    free(suri->path);
    if (lock == NULL) {
        *expire = 0;
        return 0;
    }

    DBG1("  UNLOCK %s", lock->uri.path);
    int ret = ne_unlock(session, lock);
    ret = get_error(ret, "UNLOCK");
    if (ret == 0 || ret == ENOENT || ret == EINVAL) {
        ne_lockstore_remove(locks, lock);
        ne_lock_destroy(lock);
        *expire = 0;
        return 0;
    }

    return ret;
}



/* Private functions */
/*===================*/

/* Returns a file error code according to NE_ERROR ret from the last WebDAV
   method call. If ret has value NE_ERROR the error code from the session is
   fetched and translated.
   ret    : the error code returned from NEON.
   method : name of the WebDAV method, used for debug messages.
   return value : a file error code according to errno.h. */
static int get_error(int ret, const char *method) {

    int err;
    switch (ret) {
    case NE_OK:
    case NE_ERROR:
        err = get_ne_error(method);
        break;
    case NE_LOOKUP:
        DBG1("  %s neon: NE_LOOKUP failure", method);
        err = EAGAIN;
        break;
    case NE_AUTH:
        DBG1("  %s neon: NE_AUTH failure", method);
        err = EPERM;
        break;
    case NE_PROXYAUTH:
        DBG1("  %s: NE_PROXYAUTH failure", method);
        err = EPERM;
        break;
    case NE_CONNECT:
        DBG1("  %s: NE_CONNECT failure", method);
        err = EAGAIN;
        break;
    case NE_TIMEOUT:
        DBG1("  %s: NE_TIMEOUT failure", method);
        err = EAGAIN;
        break;
    case NE_FAILED:
        DBG1("  %s neon: NE_FAILED failure", method);
        err = EINVAL;
        break;
    case NE_RETRY:
        DBG1("  %s: NE_RETRY failure", method);
        err = EAGAIN;
        break;
    case NE_REDIRECT:
        DBG1("  %s: NE_REDIRECT failure", method);
        err = ENOENT;
        break;
    default:
        DBG1("  %s: Unknown error", method);
        err = EIO;
        break;
    }

    return err;
}

/* Get the error from the session and translates it into a file error code.
   method : name of the WebDAV method, used for debug messages.
   return value : a file error code according to errno.h. */
static int get_ne_error(const char *method) {

    const char *text = ne_get_error(session);
    DBG2("  %s: %s", method, text);

    char *tail;
    int err = strtol(text, &tail, 10);
    if (tail == text)
        return EIO;

    switch (err) {
        case 200:           /* OK */
        case 201:           /* Created */
        case 202:           /* Accepted */
        case 203:           /* Non-Authoritative Information */
        case 204:           /* No Content */
        case 205:           /* Reset Content */
        case 207:           /* Multi-Status */
        case 304:           /* Not Modified */
            return 0;
        case 401:           /* Unauthorized */
        case 402:           /* Payment Required */
        case 407:           /* Proxy Authentication Required */
            return EPERM;
        case 301:           /* Moved Permanently */
        case 303:           /* See Other */
        case 404:           /* Not Found */
        case 410:           /* Gone */
            return ENOENT;
        case 408:           /* Request Timeout */
        case 504:           /* Gateway Timeout */
            return EAGAIN;
        case 423:           /* Locked */
            return EACCES;
        case 400:           /* Bad Request */
        case 403:           /* Forbidden */
        case 405:           /* Method Not Allowed */
        case 409:           /* Conflict */
        case 411:           /* Length Required */
        case 412:           /* Precondition Failed */
        case 414:           /* Request-URI Too Long */
        case 415:           /* Unsupported Media Type */
        case 424:           /* Failed Dependency */
        case 501:           /* Not Implemented */
            return EINVAL;
        case 413:           /* Request Entity Too Large */
        case 507:           /* Insufficient Storage */
            return ENOSPC;
        case 206:           /* Partial Content */
        case 300:           /* Multiple Choices */
        case 302:           /* Found */
        case 305:           /* Use Proxy */
        case 306:           /* (Unused) */
        case 307:           /* Temporary Redirect */
        case 406:           /* Not Acceptable */
        case 416:           /* Requested Range Not Satisfiable */
        case 417:           /* Expectation Failed */
        case 422:           /* Unprocessable Entity */
        case 500:           /* Internal Server Error */
        case 502:           /* Bad Gateway */
        case 503:           /* Service Unavailable */
        case 505:           /* HTTP Version Not Supported */
            return EIO;
        default:
            return EIO;
    }
}


/* Checks if there is already a lock for file path that is owned by owner. If
   successful it stores the lock in the global lock store, refreshes the lock
   and updates expire.
   If no matching lock is found or the session is initialized with the nolocks
   option, it  sets expire to 0 and returns -1.
   If a matching lock is found, but it can not be refreshed, expire is set
   to -1 (= locked, expire time unknown).
   If an error occurs it leaves expire unchanged and returns -1:
   spath  : URL-escaped path of the file on the server.
   expire : The time when the lock expires, will be updated.
   return value : 0 if a matching lock is found, -1 otherwise. */
static int lock_discover(const char *spath, time_t *expire) {

    if (locks == NULL) {
        *expire = 0;
        return -1;
    }

    DBG1("  LOCKDISCOVER %s", spath);
    struct ne_lock *lock = NULL;
    int ret = ne_lock_discover(session, spath, lock_result, &lock);
    ret = get_error(ret, "LOCKDISCOVER");

    if (ret == 0 && lock != NULL)  {
        *expire = -1;
        lock_refresh(lock, expire);
        return 0;
    } else {
        if (ret == 0)
            *expire = 0;
        return -1;
    }
}


/* Refreshes lock and updates expire.
   If an error occurs it does nothing.
   lock   : The lock, will be updated on success.
   expire : The time when the lock expires, updated on success. */
static void lock_refresh(struct ne_lock *lock, time_t *expire) {

#if NE_VERSION_MINOR == 24

    *expire = LONG_MAX;

#else     /* NE_VERSION_MINOR */

    int ret = ne_lock_refresh(session, lock);
    ret = get_error(ret, "LOCKREFRESH");

    if (ret == 0) {
        if (lock->timeout <= 0) {
            *expire = LONG_MAX;
        } else {
            *expire = lock->timeout + time(NULL);
        }
    }

#endif    /* NE_VERSION_MINOR */
}


/* Stores the contents of file cache_path on the server location path and
   updates the value of exists, etag and mtime.
   Sometimes a lock may be discovered during put_file(). In this case expire
   will be updated.
   spath  : URL-escaped path of the file on the server.
   fd     : File descriptor of the local file to be stored on the server.
   mtime  : The Last_Modified value. Updated on success. May be NULL.
   return value : 0 on success; an appropriate file error code otherwise. */

#if NE_VERSION_MINOR == 24

static int put_file(const char *spath, int fd, time_t *expire,
                    time_t *mtime) {

    ne_request *req = ne_request_create(session, "PUT", spath);
    ne_lock_using_resource(req, spath, 0);
    if (ne_set_request_body_fd(req, fd) != 0) {
        ne_request_destroy(req);
        return EIO;
    }
    char *date = NULL;
    ne_add_response_header_handler(req, "Date", ne_duplicate_header, &date);

    DBG1("  PUT %s", spath);
    int ret = ne_request_dispatch(req);
    ret = get_error(ret, "PUT");
    ne_request_destroy(req);

    if (ret == EACCES && lock_discover(spath, expire) == 0) {
        if (date != NULL)
            free(date);
        ne_request *req = ne_request_create(session, "PUT", spath);
        ne_lock_using_resource(req, spath, 0);
        if (ne_set_request_body_fd(req, fd) != 0) {
            ne_request_destroy(req);
            return EIO;
        }
        ne_add_response_header_handler(req, "Date", ne_duplicate_header, &date);
        DBG1("  PUT %s", spath);
        ret = ne_request_dispatch(req);
        ret = get_error(ret, "PUT");
        ne_request_destroy(req);
    }

    if (ret == 0 && mtime != NULL && date != NULL)
        *mtime = ne_httpdate_parse(date);
    if (date != NULL)
        free(date);

    return ret;
}

#else     /* NE_VERSION_MINOR */

static int put_file(const char *spath, int fd, time_t *expire,
                    time_t *mtime) {

    struct stat st;
    if (fstat(fd, &st) != 0)
        return EIO;

    ne_request *req = ne_request_create(session, "PUT", spath);
#if NE_VERSION_MINOR == 25
    ne_set_request_expect100(req, 1);
#else
    ne_set_request_flag(req, NE_REQFLAG_EXPECT100, 1);
#endif
    ne_lock_using_resource(req, spath, 0);
#if _FILE_OFFSET_BITS == 64
    ne_set_request_body_fd64(req, fd, 0, st.st_size);
#else
    ne_set_request_body_fd(req, fd, 0, st.st_size);
#endif

    DBG1("  PUT %s", spath);
    int ret = ne_request_dispatch(req);
    ret = get_error(ret, "PUT");

    if (ret == EACCES && lock_discover(spath, expire) == 0) {
        ne_request_destroy(req);
        ne_request *req = ne_request_create(session, "PUT", spath);
#if NE_VERSION_MINOR == 25
        ne_set_request_expect100(req, 1);
#else
        ne_set_request_flag(req, NE_REQFLAG_EXPECT100, 1);
#endif
        ne_lock_using_resource(req, spath, 0);
#if _FILE_OFFSET_BITS == 64
        ne_set_request_body_fd64(req, fd, 0, st.st_size);
#else
        ne_set_request_body_fd(req, fd, 0, st.st_size);
#endif
        DBG1("  PUT %s", spath);
        ret = ne_request_dispatch(req);
        ret = get_error(ret, "PUT");
    }

    if (ret == 0 && mtime != NULL) {
        const char *value = ne_get_response_header(req, "Date");
        if (value != NULL)
            *mtime = ne_httpdate_parse(value);
    }
    ne_request_destroy(req);

    return ret;
}

#endif    /* NE_VERSION_MINOR */


/* Call-back functions for neon. */

/* Copies credentials from global variables into user and pwd.
   userdata must be a string with value "server" or "proxy", to decide what
   the creditentials are needed for.
   The creditentials are taken form global variables username/passord or
   p_username/p_password.
   If attempt > 0, this is logged as an error and the value of attempt is
   returned, so neon will not try again.
   userdata : What the credentials are needed for ("server" or "proxy").
   realm    : Used for error log.
   attempt  : Number of attempts to get credentials. If not 0 an error occured.
   user     : A buffer of size NE_ABUFSIZ to return the username.
   pwd      : A buffer of size NE_ABUFSIZ to return the password.
   return value : value if attempt. neon will not call this function again if
                  it is greater than 0. */
static int auth(void *userdata, const char *realm, int attempt, char *user,
                char *pwd) {

    DBG1("  Neon wants creditentials for %s.", realm);
    if (attempt != 0) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "Authentication failure:");
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "  %s", realm);
        return attempt;
    }

    if (strcmp((char *) userdata, "server") == 0) {
        if (username != NULL)
            strncpy(user, username, NE_ABUFSIZ - 1);
        if (password != NULL)
            strncpy(pwd, password, NE_ABUFSIZ - 1);
    } else if (strcmp((char *) userdata, "proxy") == 0) {
        if (p_username != NULL)
            strncpy(user, p_username, NE_ABUFSIZ - 1);
        if (p_password != NULL)
            strncpy(pwd, p_password, NE_ABUFSIZ - 1);
    }
    return 0;
}


/* Writes data from block to a local file.
   userdata must be a get_context structure that holds at least the name of
   the local file. If it does not contain a file descriptor, the file is
   opened for writing and the file descriptor is stored in the get_context
   structure. In case of an error a error flag is set. 
   userdata : A get_context structure, containing the name of the local file,
              the file descriptor (if the file is open), and an error flag.
   block    : Buffer containing the data.
   length   : Number of bytes in the buffer.
if NE_VERSION_MINOR > 24
   return value : 0 on success, EIO otherwise. */

#if NE_VERSION_MINOR == 24

static void block_writer(void *userdata, const char *block, size_t length) {

#else     /* NE_VERSION_MINOR */

static int block_writer(void *userdata, const char *block, size_t length) {

#endif    /* NE_VERSION_MINOR */

    get_context *ctx = (get_context *) userdata;
    if (ctx->fd == 0)
        ctx->fd = open(ctx->file, O_WRONLY | O_CREAT | O_TRUNC);
    if (ctx->fd <= 0) {
        ne_set_error(session, "0 Could not open cache file.");
        ctx->error = EIO;
    }

    while (!ctx->error && length > 0) {
        ssize_t ret = write(ctx->fd, block, length);
        if (ret < 0) {
            ctx->error = EIO;
            ne_set_error(session, "0 Error writing to cache file.");
        } else {
            length -= ret;
            block += ret;
        }
    }

#if NE_VERSION_MINOR > 24
    return ctx->error;
#endif    /* NE_VERSION_MINOR */
}


/* If the owner of this lock is the same as global variable owner, lock is
   stored in the global lock store locks and a pointer to the lock is
   returned in userdata.
   Otherwise it does nothing.
   userdata : *userdata will be set to lock, if lock is ownded ba owner.
   lock     : a lock found by ne_lock_discover() on the server.
   uri      : not used.
   status   : not used. */
 
#if NE_VERSION_MINOR < 26

static void lock_result(void *userdata, const struct ne_lock *lock, 
                                    const char *uri, const ne_status *status) {

#else     /* NE_VERSION_MINOR */

static void lock_result(void *userdata, const struct ne_lock *lock, 
                                    const ne_uri *uri, const ne_status *status) {

#endif    /* NE_VERSION_MINOR */

    if (locks == NULL || owner == NULL || userdata == NULL || lock == NULL
            || lock->owner == NULL)
        return;

    if (strcmp(lock->owner, owner) == 0) {
        struct ne_lock *l = ne_lock_copy(lock);
        ne_lockstore_add(locks, l);
        l->timeout = lock_timeout;
        *((struct ne_lock **) userdata) = l;
    }
}


/* Called by ne_propfind_named(). Evaluates a dav_props structure from
   href/uri and set and stores it in userdata.
   userdata must be a pointer to a propfind_context structure. Its member
   path holds the unescaped path of the collection. Its member results is
   a linked list of the dav_props structures.
   The unescaped version of href/uri->path must must be equal to the path of
   the collection or a descendent of it. It is stored as member path of the
   dav_props structure. It will be normalized (collections have a trailing
   slash, non-collections do not have one).
   If set does not contain the displayname property or global variable
   use_displayname is set, the name is derived from path. The name of the
   collection itself will be the empty string. If name contains
   a '/'-character it is replaced by the ugly string "-slash-".
   There must not be two dav_props structure with the same path or the same
   name. in this case one of them is removed from the list, preferable the one
   that is not a directory or that is less specific.
   userdata : A pointer to a propfind_context structure containing the path of
              the collection and the linked list of properties.
if NE_VERSION_MINOR < 26
   href     : Value of the href propertiy returned by the server. It may be
              the complete URL of the collection or the path only.
else
   uri      : ne_uri of the resource as returned from the server.
endif
   set      : Points to the set of properties returned from the server.*/

#if NE_VERSION_MINOR < 26

static void prop_result(void *userdata, const char *href,
                        const ne_prop_result_set *set) {

    propfind_context *ctx = (propfind_context *) userdata;
    if (ctx == NULL || href == NULL || set == NULL)
        return;

    ne_uri uri;
    if (ne_uri_parse(href, &uri) != 0 || uri.path == NULL) {
        ne_uri_free(&uri);
        return;
    }

    dav_props *result = ne_calloc(sizeof(dav_props));
    result->path = ne_path_unescape(uri.path);
    ne_uri_free(&uri);

#else /* NE_VERSION_MINOR */

static void prop_result(void *userdata, const ne_uri *uri,
                        const ne_prop_result_set *set) {

    propfind_context *ctx = (propfind_context *) userdata;
    if (ctx == NULL || uri == NULL || uri->path == NULL || set == NULL)
        return;

    dav_props *result = ne_calloc(sizeof(dav_props));
    result->path = ne_path_unescape(uri->path);

#endif /* NE_VERSION_MINOR */

    if (result->path == NULL || strlen(result->path) < 1) {
        dav_delete_props(result);
        return;
    }

    const char *data;

    data = ne_propset_value(set, &prop_names[TYPE]);
    if (data != NULL && strstr(data, "collection") != NULL)
            result->is_dir = 1;

    if (*(result->path + strlen(result->path) - 1) == '/') {
        if (!result->is_dir)
            *(result->path + strlen(result->path) - 1) = '\0';
    } else {
        if (result->is_dir) {
            char *tmp = ne_concat(result->path, "/", NULL);
            free(result->path);
            result->path = tmp;
        }
    }

    if (strstr(result->path, ctx->path) != result->path) {
        dav_delete_props(result);
        return;
    }

    if (strcmp(result->path, ctx->path) == 0) {
        result->name = ne_strdup("");
    } else {
        if (use_displayname) {
            data = ne_propset_value(set, &prop_names[NAME]);
        } else {
            data = NULL;
        }
        if (data != NULL) {
            result->name = ne_strdup(data);
        } else {
            if (strlen(result->path)
                    < (strlen(ctx->path) + result->is_dir + 1)) {
                dav_delete_props(result);
                return;
            }
            result->name = ne_strndup(result->path + strlen(ctx->path),
                                      strlen(result->path) - strlen(ctx->path)
                                      - result->is_dir);
        }
    }

    char *slash = strchr(result->name, '/');
    while (slash != NULL) {
        *slash = '\0';
        char *name = ne_concat(result->name, "-slash-", slash + 1, NULL);
        free(result->name);
        result->name = name;
        slash = strchr(result->name, '/');
    }

    data = ne_propset_value(set, &prop_names[ETAG]);
    if (data != NULL) {
        data = strchr(data, '\"');
        if (data != NULL)
            result->etag = ne_strdup(data);
    }

    data = ne_propset_value(set, &prop_names[LENGTH]);
    if (data != NULL)
#if _FILE_OFFSET_BITS == 64
         result->size = strtoll(data, NULL, 10);
#else
         result->size = strtol(data, NULL, 10);
#endif

    data = ne_propset_value(set, &prop_names[CREATION]);
    if (data != NULL)
        result->ctime = ne_httpdate_parse(data);

    data = ne_propset_value(set, &prop_names[MODIFIED]);
    if (data != NULL)
        result->mtime = ne_httpdate_parse(data);

    data = ne_propset_value(set, &prop_names[EXECUTE]);
    if (data == NULL) {
        result->is_exec = -1;
    } else if (*data == 'T') {
        result->is_exec = 1;
    }

    dav_props *dbl = NULL;
    dav_props *r = ctx->results;
    while (r != NULL && dbl == NULL) {
        if (strcmp(r->name, result->name) == 0
                || strcmp(r->path, result->path) == 0)
            dbl = r;
        r = r->next;
    }

    if (dbl == NULL) {
        result->next = ctx->results;
        ctx->results = result;
    } else {
        if (dbl->is_dir > result->is_dir
                || (dbl->is_dir == result->is_dir
                    && ((dbl->size > 0 && result->size == 0)
                || ((dbl->size > 0 || result->size == 0)
                    && ((dbl->etag != NULL && result->etag == NULL)
                || ((dbl->etag != NULL || result->etag == NULL)
                    && ((dbl->mtime > 0 && result->mtime == 0)
                || ((dbl->mtime > 0 || result->mtime == 0)
                    && (dbl->ctime > 0 || result->ctime == 0))))))))) {
            dav_delete_props(result);
        } else {
            free(dbl->path);
            dbl->path = result->path;
            free(dbl->name);
            dbl->name = result->name;
            if (dbl->etag != NULL)
                free(dbl->etag);
            dbl->etag = result->etag;
            dbl->size = result->size;
            dbl->ctime = result->ctime;
            dbl->mtime = result->mtime;
            dbl->is_dir = result->is_dir;
            dbl->is_exec = result->is_exec;
            free(result);
        }
    }
}


/* Displays information about cert and asks the user whether to accept
   the certificate or not.
   If no terminal is available (according to global variable Have_terminal)
   it returns an error. Else it displays an error message and certificate
   date and ask whether to accept the certificate. If the user accepts
   it returns 0, otherwise an error.
   In any case the event is logged.
   userdata : not used.
   failures : a constant indicating the kind of error.
   cert     : the server certificate that could not be verified by neon.
   return value : 0 accept the certificate for this session.
                  -1 don't accept the certificate. */
static int ssl_verify(void *userdata, int failures,
                      const ne_ssl_certificate *cert) {

    char *issuer = ne_ssl_readable_dname(ne_ssl_cert_issuer(cert));
    char *subject = ne_ssl_readable_dname(ne_ssl_cert_subject(cert));
    char *digest = ne_calloc(NE_SSL_DIGESTLEN);
    int ret = 0;
    if (issuer == NULL || subject == NULL
            || ne_ssl_cert_digest(cert, digest) != 0) {
        ret = -1;
    }

    char *error_msg;
    if (ret == -1) {
        error_msg = "Error processing certificate.";
    } else if (failures & NE_SSL_NOTYETVALID) {
        error_msg = "The server certificate is not yet valid.";
    } else if (failures & NE_SSL_EXPIRED) {
        error_msg = "The server certificate has expired.";
    } else if (failures & NE_SSL_IDMISMATCH) {
        error_msg = "The server certificate does not match the server name.";
    } else if (failures & NE_SSL_UNTRUSTED) {
        error_msg = "The server certificate is not trusted.";
    } else {
        error_msg = "Unrecognized certificate error.";
    }

    if (have_terminal) {
        printf("%s\n", error_msg);
        printf("  Server identity: %s\n", ne_ssl_cert_identity(cert));
        printf("  Issuer:  %s\n", issuer);
        printf("  Subject: %s\n", subject);
        printf("  Fingerprint: %s\n", digest);
        if (ret == 0) {
            printf("\nYou only should accept this certificate, if you can\n");
            printf("verify the fingerprint! The server might be faked\n");
            printf("or there might be a man-in-the-middle-attack.\n");
            printf("Accept certificate for this session? [y,N] ");
            char *s = NULL;
            size_t n = 0;
            ssize_t len = 0;
            len = getline(&s, &n, stdin);
            if (len < 0)
                abort();
            if (!(*s == 'y' || *s == 'Y'))
                ret = -1;
            free(s);
        }
    }

    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), error_msg);
    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "  identity:  %s",
                       ne_ssl_cert_identity(cert));
    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "  issuer:   %s", issuer);
    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "  subject:  %s", subject);
    if (ret == 0) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "  Accepted by user.");
    }

    free(issuer);
    free(subject);
    free(digest);
    return ret;
}

Generated by  Doxygen 1.6.0   Back to index