/*  webdav.c: send requests to the WebDAV server.
    Copyright (C) 2006, 2007, 2008, 2009 Werner Baumann

    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 3 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 <sys/syscall.h>  // gettid()

#include <errno.h>
#include <error.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_ICONV
#include <iconv.h>
#endif
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
#ifdef HAVE_LIBINTL_H
#include <libintl.h>
#endif
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <string.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif

#include <pthread.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 <ne_xml.h>
#include <ne_compress.h>

#include "defaults.h"
#include "mount_davfs.h"
#include "webdav.h"
#include "provide.h"
#include "cache.h"

#ifdef ENABLE_NLS
#define _(String) gettext(String)
#else
#define _(String) String
#endif


/* 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. */
    const char *server_path;           /* Path of the file on the server. */
} get_context;

typedef struct {
    int         error;
    char*       buf;
    size_t      buf_size;
    size_t      written_size;
    const char* server_path;
} get_buf_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}
};

static const ne_propname prop_names_simple[] = {
    [NAME] = {NULL, "displayname"},
    [ETAG] = {NULL, "getetag"},
    [LENGTH] = {NULL, "getcontentlength"},
    [CREATION] ={NULL, "creationdate"},
    [MODIFIED] = {NULL, "getlastmodified"},
    [TYPE] = {NULL, "resourcetype"},
    [EXECUTE] = {NULL, "executable"},
    [END] = {NULL, NULL}
};

/* Properties to be retrieved from the server. This constants
   are used by dav_quota(). */
enum {
    AVAILABLE = 0,
    USED
};

static const ne_propname quota_names[] = {
    [AVAILABLE] = {"DAV:", "quota-available-bytes"},
    [USED] = {"DAV:", "quota-used-bytes"},
    {NULL, NULL}
};

/* Some Provider-Names, to evaluate special infos from these servers. */
enum {
    PROVIDER_OTHER = 0,
    PROVIDER_1UND1,
    PROVIDER_GMX,
    PROVIDER_WEBDE,
    PROVIDER_TONLINE,
    PROVIDER_BOXNET,
    PROVIDER_IONOS
};

static size_t log_bufsize = 512;

static char *none_match_header = "If-None-Match: *\n";

volatile int *webdav_keep_on_running = 0;

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

//Cached quota values for a faster response of dav_quota().
static quota_context quota_cache = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//PROPFIND/USERINFO will not invoked several times with 20 seconds.
//Within this interval, only cached quota values will be used.
#define QUOTA_CACHE_TIMEOUT 20

/* 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;

/* Whether to create debug messages. */
static int debug;

//Infos for all webdav-connections, every thread uses an own connection.
struct server_session{
    //The neon session
    ne_session *neon_session;
    //Will be set to 1 when dav_init_connection() succeeded.
    int initialized;
    //TID of the thread, which uses this connection.
    pid_t tid;
    struct server_session *next;
};
struct server_session* server_session_list = 0;

//Infos of files, which up- or download is currently in progress.
struct transfering_file{
    //Complete path to the file.
    char* path;
    int type;
    struct transfering_file *next;
} *transfering_file_list;

//Read- and write-lock for all important operations.
extern pthread_mutex_t* mutex_allops;

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

static char *scheme = 0;
static char *host = 0;
static int port = 0;
static int provider = PROVIDER_OTHER;
static time_t connect_timeout = 0;
static time_t read_timeout = 0;
static char *p_host = 0;
static int p_port = 0;
static char *p_user = 0;
static char *p_passwd = 0;
static int useproxy = 0;
static char *servercert = 0;
static char *clicert = 0;
static char *clicert_pw = 0;
static int askauth = 0;
static int use_locks = 0;
static char *lock_owner = 0;
static char *header = 0;
static int user = 0;
static char *root_path = 0;

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

/* Whether to send expect 100-continue header in PUT requests. */
static int use_expect100;

/* Whether to use HEAD for prechecking instead of If-Match and If-None-Match
   on PTU and LOCK. */
static int has_if_match_bug;

/* Some servers sends a weak invalid etag that turns into a valid strong etag
   after one second. With this flag set, the weakness indicator is removed,
   otherwise the etag is not used at all. */
static int drop_weak_etags;

/* Check with HEAD for existence or modification of a file, before locking or
   uploading a new file. */
static int precheck;

/* Ignore the information in the DAV-header because it is wrong. */
static int ignore_dav_header;

/* 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 = 0; //AVM

#ifdef HAVE_ICONV
/* Handle to convert character encoding from utf-8 to LC_CTYPE.
   If NULL no conversion is done. */
static iconv_t from_utf_8;

/* Handle to convert character encoding from LC_CTYPE to utf-8.
   If NULL no conversion is done. */
static iconv_t to_utf_8;

/* Handle to convert character encing of path names to LC_CTYPE. If NULL
   no conversion is done. */
static iconv_t from_server_enc;

/* Handle to convert from LC_CTYPE to character encoding of path names.
   If NULL no conversion is done. */
static iconv_t to_server_enc;
#endif

/* A GNU custom stream, used to redirect neon debug messages to syslog. */
static FILE *log_stream;

#if NE_VERSION_MINOR > 25
/* Session cookie. */
static int n_cookies = 0;
static char **cookie_list = NULL;
#endif

static char *custom_header = 0;


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

#ifdef HAVE_ICONV
static void
convert(char **s, iconv_t conv);
#endif

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

static int
get_ne_error(const char *method);

static struct ne_lock *
lock_by_path(const char *path);

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

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

static ssize_t
log_writer(void *cookie, const char *buffer, size_t size);

static char *
normalize_etag(const char *etag);

static void
replace_slashes(char **name);

/* Call-back functions for neon. */

static void
add_header(ne_request *req, void *userdata, ne_buffer *header);

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

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

static int
block_writer_buf(void *userdata, const char *block, size_t length);

#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);

static void
quota_result(void *userdata, const char *href, const ne_prop_result_set *set);

#else /* NE_VERSION_MINOR >= 26 */

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);

static void
quota_result(void *userdata, const ne_uri *uri, const ne_prop_result_set *set);

#endif /* NE_VERSION_MINOR >= 26 */

static void start_session(struct server_session* sess);
static struct server_session* add_new_session(void);

int dav_init_connection(struct server_session* sess);

/* Thread ID */
pid_t gettid(void);



/*-------------------------------------------------------------------------------------*\
\*-------------------------------------------------------------------------------------*/
/* Thread ID */
pid_t gettid(void) {
    return syscall(SYS_gettid);
}


/*  Start and add a new webdav-session for the current thread
    return value: The created webdav-session, which is prepended to the session-list. */
static struct server_session* add_new_session(void){
    struct server_session** tmp;

    for(tmp = &server_session_list; *tmp; tmp = &((*tmp)->next));
    (*tmp)=(struct server_session*)calloc(1, sizeof(struct server_session));
    if(!*tmp)
        return 0;
    (*tmp)->neon_session=0;
    (*tmp)->tid = gettid();
    (*tmp)->initialized=0;
    (*tmp)->next=0;

    start_session(*tmp);
    return *tmp;
}

/*  Get the webdav-session of the current process or start a new one if it is needed.
    with_start: If >0 start a new webdav-connection if necessary.
    return value: The matching webdav-session or 0. */
static struct server_session* get_session(unsigned with_start){
    struct server_session* tmp;
    pid_t tid = gettid();
    for(tmp=server_session_list;tmp;tmp=tmp->next){
        if(tmp->tid == tid){
            return tmp;
        }
    }

    if(with_start)
        //Start a new connection.
        return add_new_session();
    else
        return 0;
}

/*  Delete the webdav-connection of the current process. */
static void del_session(void){
    struct server_session *p;
    struct server_session **pp;
    pid_t tid = gettid();

    for(pp = &server_session_list; *pp; pp = &(*pp)->next){
        if((*pp)->tid == tid){
            p = *pp;
            *pp = (*pp)->next;
            p->tid = 0;
            free(p);
            p = NULL;
            break;
        }
    }
}

static void get_oldest_uploads(char** file1, char** file2){
    struct transfering_file *p;

    for(p = transfering_file_list; p; p = p->next){
        if(p->type == TRANSFER_UPLOAD){
            if(!*file1)
                *file1 = p->path;
            else{
                *file2 = p->path;
                break;
            }
        }
    }
}

static void get_oldest_downloads(char** file1, char** file2){
    struct transfering_file *p;

    for(p = transfering_file_list; p; p = p->next){
        if(p->type == TRANSFER_DOWNLOAD){
            if(!*file1)
                *file1 = p->path;
            else{
                *file2 = p->path;
                break;
            }
        }
    }
}

/*  Add a new file to the list of currently transfered files.
    path: Path to the file, which must be added to the list.*/
static void add_transfering_file(const char* path, int transfer_type){
    struct transfering_file** tmp;

    if(!path)
        return;

    for(tmp = &transfering_file_list; *tmp; tmp = &((*tmp)->next));
    (*tmp)=(struct transfering_file*)calloc(1, sizeof(struct transfering_file));
    if(!*tmp)
        return;
    (*tmp)->path=strdup(path);
    (*tmp)->type=transfer_type;
    (*tmp)->next=0;

    if(transfer_type == TRANSFER_UPLOAD){
        provide_set_uint(RUNNING_UPLOADS, 1, '+');
        char* up_file1 = 0;
        char* up_file2 = 0;
        get_oldest_uploads(&up_file1, &up_file2);
        provide_set_files(UPLOAD_FILES, up_file1, up_file2);
    }
    else if(transfer_type == TRANSFER_DOWNLOAD){
        provide_set_uint(RUNNING_DOWNLOADS, 1, '+');
        char* down_file1 = 0;
        char* down_file2 = 0;
        get_oldest_downloads(&down_file1, &down_file2);
        provide_set_files(DOWNLOAD_FILES, down_file1, down_file2);
    }
}

/*  Delete file from the list.
    path: Path to the file, which must be removed from the list. */
static void del_transfering_file(const char* path){
    struct transfering_file *p;
    struct transfering_file **pp;

    if(!path)
        return;

    for(pp = &transfering_file_list; *pp; pp = &(*pp)->next){
        if(strcmp((*pp)->path, path)==0){
            p = *pp;
            int trans_type = p->type;
            *pp = (*pp)->next;
            if(p->path) free(p->path);
            free(p);
            if(trans_type == TRANSFER_UPLOAD){
                provide_set_uint(RUNNING_UPLOADS, 1, '-');
                //char* up_file1 = 0;
                //char* up_file2 = 0;
                //get_oldest_uploads(&up_file1, &up_file2);
                //provide_set_files(UPLOAD_FILES, up_file1, up_file2);
            }
            else if(trans_type== TRANSFER_DOWNLOAD){
                provide_set_uint(RUNNING_DOWNLOADS, 1, '-');
                //char* down_file1 = 0;
                //char* down_file2 = 0;
                //get_oldest_downloads(&down_file1, &down_file2);
                //provide_set_files(DOWNLOAD_FILES, down_file1, down_file2);
            }
            return;
        }
    }
    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "transfered file not deleted: %s", path);
    for(pp = &transfering_file_list; *pp; pp = &(*pp)->next)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "list-entry: %s", (*pp)->path);
}

/*  Check if the given path is part of a file which up- or download
    is still in progress.
    path: Filepath for searching in the list.
    return value: Returns 1, if the given file is now in transfer between
    the webdav-server and this client; 2 if the given path
    is only part of a transferring file; 0 if there is no matching item in the
    filelist. */
int is_filetranfer_inprogress(const char* path){
    struct transfering_file* tmp;

    if(!path)
        return 0;

    for(tmp=transfering_file_list;tmp;tmp=tmp->next){
        if(strcmp(tmp->path, path)==0){
            //The exact filepath is stored in the list.
            return 1;
        }
    }

    for(tmp=transfering_file_list;tmp;tmp=tmp->next){
        if(strlen(path)<strlen(tmp->path)){
            if(strncmp(tmp->path, path, strlen(path))==0){
                //The given filepath matches partly with a stored one and
                //is a parent directory of the transferring file.
                return 2;
            }
        }
    }
    return 0;
}

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

#if NE_VERSION_MINOR > 25
static void
get_cookies(ne_request *req, void *userdata, const ne_status *status);
#endif /* NE_VERSION_MINOR > 25 */


/* Update current up- or download speed. */
static int neon_notify(void *userdata, ne_session_status status, const ne_session_status_info *info)
{

    if(!*webdav_keep_on_running){
        if(debug)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "neon notify: abort sending");
        return 1;
    }
    if(info && info->sr.total != -1 && info->sr.duration > 100 && info->sr.current > 100){
        if(status == ne_status_sending)
            provide_set_transfer_speed(TRANSFER_UPLOAD, info->sr.current, info->sr.duration);
        else if(status == ne_status_recving)
            provide_set_transfer_speed(TRANSFER_DOWNLOAD, info->sr.current, info->sr.duration);
    }
    return 0;
}

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

void
dav_init_webdav(const dav_args *args, volatile int *keep_on_running)
{
    if (args->neon_debug & ~NE_DBG_HTTPPLAIN)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Initializing webdav");

    debug = args->debug;

    webdav_keep_on_running = keep_on_running;

    if (ne_sock_init() != 0)
        error(EXIT_FAILURE, errno, _("socket library initialization failed"));

    if (args->neon_debug & ~NE_DBG_HTTPPLAIN) {
        char *buf = malloc(log_bufsize);
        cookie_io_functions_t *log_func = malloc(sizeof(cookie_io_functions_t));
        if (!log_func) abort();
        log_func->read = NULL;
        log_func->write = log_writer;
        log_func->seek = NULL;
        log_func->close = NULL;
        log_stream = fopencookie(buf, "w", *log_func);
        openlog("davfs2", LOG_NDELAY|LOG_CONS|LOG_PID, LOG_USER); //AVM
        if (!log_stream)
            error(EXIT_FAILURE, errno,
                  _("can't open stream to log neon-messages"));
        ne_debug_init(log_stream, args->neon_debug);
    }

#ifdef HAVE_ICONV
    //AVM: nl_langinfo() returns ASCII, that is not the correct charset
#ifdef FULL_UTF8
    // AVM: massstorage uses UTF-8 locally
    char *lc_charset = "UTF-8";
#else
    char *lc_charset = "ISO-8859-1"; //nl_langinfo(CODESET);
#endif
    if (lc_charset && strcasecmp(lc_charset, "UTF-8") != 0) {
        from_utf_8 = iconv_open(lc_charset, "UTF-8");
        if (from_utf_8 == (iconv_t) -1)
            from_utf_8 = 0;
        to_utf_8 = iconv_open("UTF-8", lc_charset);
        if (to_utf_8 == (iconv_t) -1)
            to_utf_8 = 0;
    }

    if (lc_charset && args->s_charset
            && strcasecmp(args->s_charset, lc_charset) != 0) {
        if (strcasecmp(args->s_charset, "UTF-8") == 0) {
            from_server_enc = from_utf_8;
            to_server_enc = to_utf_8;
        } else {
            from_server_enc = iconv_open(lc_charset, args->s_charset);
            if (from_server_enc == (iconv_t) -1)
                from_server_enc = 0;
            to_server_enc = iconv_open(args->s_charset, lc_charset);
            if (to_server_enc == (iconv_t) -1)
                to_server_enc = 0;
        }
    }
#endif /* HAVE_ICONV */

    //AVM Old User-Agent: davfs2... //useragent = ne_concat(PACKAGE_TARNAME, "/", PACKAGE_VERSION, NULL);
    useragent = ne_concat("FRITZ!OS/", getenv("CONFIG_VERSION_MAJOR"), ".", getenv("CONFIG_VERSION"), getenv("CONFIG_SUBVERSION"), "\r\n", NULL);

    if (args->username)
        username = ne_strdup(args->username);
    if (args->password)
        password = ne_strdup(args->password);
    use_displayname = args->displayname;
    use_expect100 = args->expect100;
    has_if_match_bug = args->if_match_bug;
    drop_weak_etags = args->drop_weak_etags;
    precheck = args->precheck;
    ignore_dav_header = args->ignore_dav_header;
    if (args->scheme)
        scheme = ne_strdup(args->scheme);
    if (args->host)
        host = ne_strdup(args->host);
    port = args->port;
    connect_timeout = args->connect_timeout;
    read_timeout = args->read_timeout;
    if (args->p_host)
         p_host = ne_strdup(args->p_host);
    p_port = args->p_port;
    if (args->p_user)
        p_user = ne_strdup(args->p_user);
    if (args->p_passwd)
        p_passwd = ne_strdup(args->p_passwd);
    useproxy = args->useproxy;
    if(args->servercert)
        servercert = ne_strdup(args->servercert);
    if(args->clicert)
        clicert = ne_strdup(args->clicert);
    if(args->clicert_pw)
        clicert_pw = ne_strdup(args->clicert_pw);
    askauth = args->askauth;
    use_locks = args->locks;
    if(args->lock_owner)
        lock_owner  = ne_strdup(args->lock_owner);
    lock_timeout = args->lock_timeout;
    n_cookies = 0; // AVM - no cookie support
    if(args->header)
        header = ne_strdup(args->header);
    user = args->user;

    root_path = ne_strdup(args->path);

    if(strlen(host) > 8){
        //set provider name
        char* tmp_server_webde = host + (strlen(host) - 6);
        char* tmp_server_gmxnet = host + (strlen(host) - 7);
        char* tmp_server_1und1de = host + (strlen(host) - 8);
        char* tmp_server_iononscom = host + (strlen(host) - 9);
        if(!strcasecmp(tmp_server_webde, "web.de"))
            provider = PROVIDER_WEBDE;
        else if(!strcasecmp(tmp_server_gmxnet, "gmx.net"))
            provider = PROVIDER_GMX;
        else if(!strcasecmp(tmp_server_1und1de, "1und1.de"))
            provider = PROVIDER_1UND1;
        else if (!strcasecmp(tmp_server_iononscom, "ionos.com"))
            provider = PROVIDER_IONOS;
        else if(strlen(host) > 11){
            char* tmp_server_ton = host + (strlen(host) - 11);
            if(!strcasecmp(tmp_server_ton, "t-online.de"))
                provider = PROVIDER_TONLINE;
        }
    }
    else if(!strcasecmp(host, "box.net"))
        provider = PROVIDER_BOXNET;

    get_session(1);
}

/*  Start a new webdav-connection.
    sess: webdav-session to store the new created neon-session.
*/
static void start_session(struct server_session* sess){
    sess->neon_session = ne_session_create(scheme, host, port);


    ne_set_notifier(sess->neon_session, neon_notify, NULL);

    ne_set_read_timeout(sess->neon_session, read_timeout);

#if NE_VERSION_MINOR > 26
    ne_set_connect_timeout(sess->neon_session, connect_timeout);
#endif /* NE_VERSION_MINOR > 26 */

    ne_set_useragent(sess->neon_session, useragent);

    ne_set_server_auth(sess->neon_session, auth, "server");

    if (useproxy && p_host) {
        ne_session_proxy(sess->neon_session, p_host, p_port);
        if (p_user && !p_username)
            p_username = ne_strdup(p_user);
        if (p_passwd && !p_password)
            p_password = ne_strdup(p_passwd);
        ne_set_proxy_auth(sess->neon_session, auth, "proxy");
    }

    if (strcmp(scheme, "https") == 0) {
        if (!ne_has_support(NE_FEATURE_SSL))
            error(EXIT_FAILURE, 0, _("neon library does not support TLS/SSL"));
        ne_ssl_set_verify(sess->neon_session, ssl_verify, NULL);
        ne_ssl_trust_default_ca(sess->neon_session);

        if (servercert) {
            ne_ssl_certificate *server_cert
                    = ne_ssl_cert_read(servercert);
            if (!server_cert)
                error(EXIT_FAILURE, 0, _("can't read server certificate %s"),
                      servercert);
            ne_ssl_trust_cert(sess->neon_session, server_cert);
            ne_ssl_cert_free(server_cert);
        }

        if (clicert) {
            uid_t orig = geteuid();
            seteuid(0);
            ne_ssl_client_cert *client_cert
                    = ne_ssl_clicert_read(clicert);
            seteuid(orig);
            if (!client_cert)
                error(EXIT_FAILURE, 0, _("can't read client certificate %s"),
                      clicert);
            if (client_cert && ne_ssl_clicert_encrypted(client_cert)) {
                char *pw = NULL;
                if (!clicert_pw && askauth) {
                    printf(_("Please enter the password to decrypt client\n"
                             "certificate %s.\n"), clicert);
                    pw = dav_user_input_hidden(_("Password: "));
                } else {
                    pw = ne_strdup(clicert_pw);
                }
                int ret = 1;
                if (pw) {
                    ret = ne_ssl_clicert_decrypt(client_cert, pw);
                    memset(pw, '\0', strlen(pw));
                    free(pw);
                }
                if (ret)
                    error(EXIT_FAILURE, 0,
                          _("can't decrypt client certificate %s"),
                          clicert);
            }
            ne_ssl_set_clicert(sess->neon_session, client_cert);
            ne_ssl_clicert_free(client_cert);
        }
    }

    if (use_locks && !locks) {
        locks = ne_lockstore_create();
        if (!lock_owner) {
            if (!username) {
                owner = ne_strdup(PACKAGE_STRING);
            } else {
                owner = ne_strdup(username);
            }
        } else {
            owner = ne_strdup(lock_owner);
        }
    }

    if (header) {
        if(!custom_header)
            custom_header = ne_strdup(header);
    }

#if NE_VERSION_MINOR > 25
    if (n_cookies > 0) {
        cookie_list = (char **) ne_calloc(n_cookies * sizeof(char *));
        ne_hook_post_headers(sess->neon_session, get_cookies, NULL);
    }
#endif /* NE_VERSION_MINOR > 25 */
    if (custom_header || cookie_list) {
        ne_hook_pre_send(sess->neon_session, add_header, custom_header);
    }
}

/* Does an OPTIONS request to check the server capabilities. In case of
   success it will set the global variable initialized. If the server
   does not support locks, it will remove the lockstore and set locks
   to NULL.
   return value : 0 on success or an apropriate error code. */
int
dav_init_connection(struct server_session* sess)
{

    char *spath = ne_path_escape(root_path);
    ne_server_capabilities caps = {0, 0, 0};
    int ret = ne_options(sess->neon_session, spath, &caps);

    if (!ret) {
        sess->initialized = 1;
        if(debug)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), _("webdav connection initialized"));
        if (!caps.dav_class1 && !ignore_dav_header) {
            if (have_terminal) {
                error(EXIT_FAILURE, 0,
                      _("mounting failed; the server does not support WebDAV"));
            } else {
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR),
                       _("mounting failed; the server does not support WebDAV"));
                ret = EINVAL;
            }
        }
        if ((caps.dav_class2 || ignore_dav_header) && locks) {
            ne_lockstore_register(locks, sess->neon_session);
        } else if (locks) {
            if (have_terminal)
                error(0, 0, _("warning: the server does not support locks"));
            ne_lockstore_destroy(locks);
            locks = NULL;
        }
    } else {
        ret = get_error(ret, "OPTIONS");
    }

    free(spath);
    return ret;
}


void
dav_close_webdav(unsigned int clean_locks)
{
    mutex_lock(mutex_allops);

    struct server_session* sess = 0;

    if(clean_locks){
        //Close all sessions and webdav locks
        for(sess=server_session_list;sess;){
            struct server_session* tmp = sess->next;

            if(!sess->neon_session || sess->tid < 1)
            {
                sess = tmp;
                continue;
            }
            if(log_stream && debug)
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Closing connection of tid %d to %s",
                       sess->tid, ne_get_server_hostport(sess->neon_session));
            if (locks) {
                struct ne_lock *lock = ne_lockstore_first(locks);
                while (lock) {
                    if (sess->neon_session) {
                        int ret = ne_unlock(sess->neon_session, lock);
                        get_error(ret, "UNLOCK");
                    }
                    lock = ne_lockstore_next(locks);
                }
            }
            ne_session_destroy(sess->neon_session);
            sess->neon_session = NULL;
            ne_sock_exit();
            sess->tid = 0;
            free(sess);
            sess = tmp;
        }
        server_session_list = NULL;
    }
    else{
        //Close only session of this process
        sess = get_session(0);
        if(sess){
            if(sess->neon_session && log_stream && debug)
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "Closing connection to %s",
                       ne_get_server_hostport(sess->neon_session));
            if (sess->neon_session){
                ne_session_destroy(sess->neon_session);
                sess->neon_session = NULL;
            }
            ne_sock_exit();
        }
        del_session();
    }
    mutex_unlock(mutex_allops);
}


void
dav_check_webdav(void)
{
    struct server_session* sess = get_session(1);
    if (!sess->initialized){
       int ret = dav_init_connection(sess);
       if (ret) {
           error(EXIT_FAILURE, 0, _("%s"),
                  dav_get_webdav_error());
       }
    }
}

int
dav_test_webdav_connection(void)
{
    struct server_session* sess = get_session(1);
    if (!sess->initialized)
       return dav_init_connection(sess);
    else
       return 0;
}

// AVM Dummy-callback function for ne_propfind_named
static void
quota_result_dummy(void *userdata, const ne_uri *uri, const ne_prop_result_set *set)
{
    return;
}

// AVM Testrequest with PROPFIND + quata to check a auth-connection for T-Online
int
dav_test_webdav_propfind_quota(char * path)
{
    if (!path) return 0;
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    char *spath = ne_path_escape(path);
    ne_propfind_handler *ph = ne_propfind_create(sess->neon_session, spath, NE_DEPTH_ZERO);
    ret = ne_propfind_named(ph, quota_names, quota_result_dummy, NULL);
    ret = get_error(ret, "PROPFIND");
    ne_propfind_destroy(ph);

    if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_test_webdav_propfind_quota:   PROPFIND request return=%d  (path=%s)", ret, spath);

    if (!ret ) {
        ; // ok
    } else {
        ; // failed
    }
    free(spath);

    return ret;
}


void
dav_clean(void)
{
    if(mutex_allops){
        pthread_mutex_destroy(mutex_allops);
        free(mutex_allops);
    }
    if (cookie_list) {
        for (int i = 0; i < n_cookies; i++){
            free(cookie_list[i]);
        }
        free(cookie_list);
        cookie_list = NULL;
    }
    provide_destroy();
}


char *
dav_conv_from_utf_8(const char *s)
{
    char *new = ne_strdup(s);
#ifdef HAVE_ICONV
    if (from_utf_8)
        convert(&new, from_utf_8);
#endif
    return new;
}


char *
dav_conv_to_utf_8(const char *s)
{
    char *new = ne_strdup(s);
#ifdef HAVE_ICONV
    if (to_utf_8)
        convert(&new, to_utf_8);
#endif
    return new;
}


char *
dav_conv_from_server_enc(const char *s)
{
    char *new = ne_strdup(s);
#ifdef HAVE_ICONV
    if (from_server_enc)
        convert(&new, from_server_enc);
#endif
    return new;
}


char *
dav_conv_to_server_enc(const char *s)
{
    char *new = ne_strdup(s);
#ifdef HAVE_ICONV
    if (to_server_enc)
        convert(&new, to_server_enc);
#endif
    return new;
}


int
dav_delete(const char *path, time_t *expire)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
       if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }
    struct ne_lock *lock = NULL;

    char *spath = ne_path_escape(path);
    ret = ne_delete(sess->neon_session, spath);
    ret = get_error(ret, "DELETE");


    if ((ret == EACCES || ret == ENOENT) && locks) {
        lock_discover(spath, expire);
        lock = lock_by_path(spath);
        if (lock && ret == EACCES) {
            ret = ne_delete(sess->neon_session, spath);
            ret = get_error(ret, "DELETE");
        } else if (lock) {
            ne_unlock(sess->neon_session, lock);
            ret = 0;
        }
    }

    if (!ret && locks) {
        lock = lock_by_path(spath);
        if (lock) {
            ne_lockstore_remove(locks, lock);
            ne_lock_destroy(lock);
        }
    }

    if(!ret)
        dav_quota(root_path, NULL, NULL);

    free(spath);
    return ret;
}


int
dav_delete_dir(const char *path)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    char *spath = ne_path_escape(path);
    ret = ne_delete(sess->neon_session, spath);
    ret = get_error(ret, "DELETE");

    free(spath);
    return ret;
}


void
dav_delete_props(dav_props *props)
{
    if (props->path)
        free(props->path);
    if (props->name)
        free(props->name);
    if (props->etag)
        free(props->etag);
    free(props);
}


int
dav_get_collection(const char *path, dav_props **props)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

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

    char *spath = ne_path_escape(path);
    ne_propfind_handler *ph = ne_propfind_create(sess->neon_session, spath, NE_DEPTH_ONE);
    ret = ne_propfind_named(ph, prop_names, prop_result, &ctx);
    ret = get_error(ret, "PROPFIND");
    ne_propfind_destroy(ph);
    free(spath);

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

    *props = ctx.results;

    return ret;
}


const char *
dav_get_webdav_error()
{
    struct server_session* sess = get_session(0);
    if (!sess)
            return "unknown error";
    else
        return ne_get_error(sess->neon_session);
}

int
dav_get_partial_file (char *path, char* buf, size_t bufsize, off_t offset, ssize_t *size)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }
    get_buf_context ctx;
    ctx.error        = 0;
    ctx.buf          = buf;
    ctx.buf_size     = bufsize;
    ctx.written_size = 0;
    ctx.server_path  = path;

    if(debug)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "start downloading part of file: %s (from: %llu, size: %u)", path, offset, bufsize);

    char *spath = ne_path_escape(path);
    ne_request *req = ne_request_create(sess->neon_session, "GET", spath);

    char brange[64];
    snprintf(brange, sizeof brange, "bytes=%llu-%llu", offset, offset+bufsize-1);

    ne_add_request_header(req, "Range", brange);
    ne_add_request_header(req, "Accept-Ranges", "bytes");

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

    mutex_unlock(mutex_allops);
    ret = ne_request_dispatch(req);
    mutex_lock(mutex_allops);

    if (ctx.error)
        ret = ctx.error;
    else
        ret = get_error(ret, "GET");

    const ne_status *status = ne_get_status(req);

    if (!ret && (status->code == 200 || status->code == 206)) {
        *size = ctx.written_size;
    }
    else {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "downloading part of file failed (ret: %d, status-code: %i): %s", ret, status->code, path);
    }

    ne_request_destroy(req);
    free(spath);

    return ret;
}

int
dav_get_file (char *path, char *cache_path, off_t *size,
             char **etag, time_t *mtime, char **mime, int *modified)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }
    char* path_copy = 0;
    get_context ctx;
    ctx.error = 0;
    ctx.file = cache_path;
    ctx.server_path = path;
    ctx.fd = 0;
    ne_decompress *dc;

    char *spath = ne_path_escape(path);
    time_t start_time = time(NULL);
    if(debug)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "start downloading file: %s", path);
    add_transfering_file(path, TRANSFER_DOWNLOAD);
    path_copy=strdup(path);
    ne_request *req = ne_request_create(sess->neon_session, "GET", spath);

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

    dc = ne_decompress_reader(req, ne_accept_2xx, block_writer, &ctx);

    mutex_unlock(mutex_allops);
    ret = ne_request_dispatch(req);
    mutex_lock(mutex_allops);
    ret = get_error(ret, "GET");

    if(!path || !*path || strcmp(path, path_copy)){
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "downloaded file is lost");
        ret = ENODELOST;
    }
    else{
        if (ctx.error)
            ret = ctx.error;
        const ne_status *status = ne_get_status(req);

        if (!ret && status->code == 200) {

            if (modified)
                *modified = 1;

            const char *value = ne_get_response_header(req, "Content-Length");
            if (value)
#if _FILE_OFFSET_BITS == 64
                *size = strtoll(value, NULL, 10);
#else /* _FILE_OFFSET_BITS != 64 */
                *size = strtol(value, NULL, 10);
#endif /* _FILE_OFFSET_BITS != 64 */

            value = ne_get_response_header(req, "Last-Modified");
            if (mtime && value)
                *mtime = ne_httpdate_parse(value);

            if (etag) {
                if (*etag) free(*etag);
                *etag = normalize_etag(ne_get_response_header(req, "ETag"));
            }

            value = ne_get_response_header(req, "Content-Type");
            if (mime && value) {
                if (*mime)
                    free(*mime);
                *mime = ne_strdup(value);
            }
            struct stat st;
            if (stat(cache_path, &st)){
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "downloaded file is lost");
                ret = ENODELOST;
            }
            else if(st.st_size != *size){
                if(debug)
                    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "warning: content-length (%llu) != st_size (%llu)", *size, st.st_size);
                *size = st.st_size;
            }
            time_t dur_time = time(NULL)-start_time;
            if(debug){
                if(dur_time > 0 && *size > 0)
                    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "finished downloading file (%d sec, %.2f kbytes/sec): %s", (int)dur_time, (*size / dur_time) / 1000.0, path_copy);
                else
                    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "finished downloading file (%d sec, %llu bytes): %s", (int)dur_time, *size, path_copy);
            }
            provide_set_uint64(SIZE_DOWNLOADS, *size, '+');
            provide_set_uint64(FINISHED_DOWNLOADS, 1, '+');
            dav_quota(root_path, NULL, NULL);
        }
        else if (!ret && status->code == 304) {
            //"304 Not Modified"
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "local file up-to-date, got 304 Not Modified: %s", path_copy);
        }
        else{
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "downloading file failed - ret: %d, code: %d, path: %s", ret, status->code, path_copy);
            provide_set_uint64(FAILED_DOWNLOADS, 1, '+');
        }
    }

    //File-download is finished, modifying this file or a parent-directory is now allowed.
    del_transfering_file(path_copy);
    if(path_copy) free(path_copy);
    ne_decompress_destroy(dc);
    ne_request_destroy(req);
    if (ctx.fd > 0)
        close(ctx.fd);
    if (mod_time)
        free(mod_time);
    free(spath);

    return ret;
}


int
dav_head(const char *path, char **etag, time_t *mtime, off_t *length,
         char **mime)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    char *spath = ne_path_escape(path);
    ne_request *req = ne_request_create(sess->neon_session, "HEAD", spath);
    ret = ne_request_dispatch(req);
    ret = get_error(ret, "HEAD");

    const char *value = ne_get_response_header(req, "ETag");
    if (!ret && etag && value) {
        if (*etag) free(*etag);
        *etag = normalize_etag(value);
    }

    value = ne_get_response_header(req, "Last-Modified");
    if (!ret && mtime && value)
        *mtime = ne_httpdate_parse(value);

    value = ne_get_response_header(req, "Content-Length");
    if (!ret && length && value)
        *length = strtol(value, NULL, 10);

    value = ne_get_response_header(req, "Content-Type");
    if (!ret && mime && value) {
        if (*mime)
            free(*mime);
        *mime = ne_strdup(value);
    }

    ne_request_destroy(req);
    free(spath);
    return ret;
}


int
dav_lock(const char *path, time_t *expire, int *exists)
{
    int ret;
    struct server_session* sess = get_session(0);

    if (!sess || !locks) {
        *expire = 0;
        return 0;
    }

#if NE_VERSION_MINOR > 25
    if (precheck && has_if_match_bug && !*exists) {
#else /* NE_VERSION_MINOR == 25 */
    if (precheck && !*exists) {
#endif /* NE_VERSION_MINOR == 25 */
        if (dav_head(path, NULL, NULL, NULL, NULL) == 0) {
            return EEXIST;
        }
    }

    char *spath = ne_path_escape(path);

    struct ne_lock *lock = lock_by_path(spath);
    if (lock) {
        free(spath);
        *expire = -1;
        lock_refresh(lock, expire);
        return 0;
    }

    lock = ne_lock_create();
    ne_fill_server_uri(sess->neon_session, &lock->uri);
    lock->uri.path = spath;
    lock->owner = ne_strdup(owner);
    lock->timeout = lock_timeout;

#if NE_VERSION_MINOR > 25
    if (!has_if_match_bug && !*exists)
        ne_hook_pre_send(sess->neon_session, add_header, none_match_header);
#endif /* NE_VERSION_MINOR > 25 */
    ret = ne_lock(sess->neon_session, lock);
    ret = get_error(ret, "LOCK");
#if NE_VERSION_MINOR > 25
    if (!has_if_match_bug && !*exists) {
        ne_unhook_pre_send(sess->neon_session, add_header, none_match_header);
        if (ret && strtol(ne_get_error(sess->neon_session), NULL, 10) == 412)
            ret = EEXIST;
    }
#endif /* NE_VERSION_MINOR > 25 */

    if (!ret) {
        ne_lockstore_add(locks, lock);
        if (strtol(ne_get_error(sess->neon_session), NULL, 10) == 201)
            *exists = 1;
        if (lock->timeout <= 0) {
            *expire = LONG_MAX;
        } else {
            *expire = lock->timeout + time(NULL);
        }
    } else {
        if (ret == EACCES && lock_discover(spath, expire) == 0)
            ret = 0;
        ne_lock_destroy(lock);
    }

    return ret;
}


void
dav_lock_refresh(const char *path, time_t *expire)
{
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if (dav_init_connection(sess)!= 0)
            return;
    }

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

    char *spath = ne_path_escape(path);
    struct ne_lock *lock = lock_by_path(spath);

    if (!lock) {
        lock_discover(spath, expire);
    } else {
        lock_refresh(lock, expire);
    }

    free(spath);
}


int
dav_make_collection(const char *path)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    char *spath = ne_path_escape(path);
    ret = ne_mkcol(sess->neon_session, spath);
    ret = get_error(ret, "MKCOL");

    free(spath);
    return ret;
}


int
dav_move(const char *src, const char *dst)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    char *spath = ne_path_escape(src);
    char *dst_path = ne_path_escape(dst);
    ret = ne_move(sess->neon_session, 1, spath, dst_path);
    ret = get_error(ret, "MOVE");

    if (!ret && locks) {
        struct ne_lock *lock = lock_by_path(spath);
        if (lock) {
            ne_lockstore_remove(locks, lock);
            ne_lock_destroy(lock);
        }
        if (use_displayname) {
            ne_proppatch_operation op[2];
            op[0].name = &prop_names[NAME];
            op[0].type = ne_propremove;
            op[1].name = NULL;
            ne_proppatch(sess->neon_session, dst_path, &op[0]);
        }
    }

    free(spath);
    free(dst_path);
    return ret;
}


int
mutex_lock(pthread_mutex_t* mutex)
{
    int ret = 0;
    if(mutex){
        if((ret = pthread_mutex_lock(mutex)))
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "mutex_lock failed: %d", ret);
    }
    return ret;
}

int
mutex_unlock(pthread_mutex_t* mutex)
{
    int ret = 0;
    if(mutex){
        if((ret = pthread_mutex_unlock(mutex)))
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "mutex_unlock failed: %d", ret);
    }

    return ret;
}

int
dav_put(char *path, char *cache_path, int *exists, time_t *expire,
        char **etag, time_t *mtime, char **mime, int execute, off_t length /*AVM*/)
{
    int ret = 0;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }
    char* path_copy = 0;

    if (precheck && (has_if_match_bug || (*exists && (!etag || !*etag)))
            && (!*exists || (etag && *etag) || (mtime && *mtime))) {
        char *r_etag = NULL;
        time_t r_mtime = 0;
        off_t r_length = 0;
        ret = dav_head(path, &r_etag, &r_mtime, &r_length, NULL);
        if (!ret) {
            if (!*exists && r_length) {
                ret = EEXIST;
            } else if (etag && *etag && r_etag) {
                if (strcmp(*etag, r_etag) != 0) {
                    if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_put  failed:  *etag=%s  !=  r_etag=%s", (etag && *etag)?*etag:"", r_etag);
                    ret = EINVAL;
                }
            } else if (mtime && *mtime && r_mtime) {
                if (*mtime < r_mtime) {
                    if (length && length > r_length) {
                        // maybe upload failed (interrupted) before => try again AVM
                        if (debug) {
                            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_put  etags different: *etag=%s != r_etag=%s", (etag && *etag)? *etag :"", r_etag);
                            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_put  ignore  *mtime= %s <  r_mtime= %s  length => force upload ", ne_rfc1123_date(*mtime),  ne_rfc1123_date(r_mtime));
                        }
                    } else {
                        if (debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "dav_put  failed:  *mtime= %s <  r_mtime= %s", ne_rfc1123_date(*mtime), ne_rfc1123_date(r_mtime));
                        ret = EINVAL;
                    }
                }
            }
        } else if (ret == ENOENT) {
            if (!*exists) {
                ret = 0;
            } else {
                ret = dav_unlock(path, expire);
            }
        }
        if (r_etag)
            free(r_etag);
        if (ret)
            return ret;
    }

    int fd = open(cache_path, O_RDONLY);
    if (fd <= 0)
        return EIO;
    struct stat st;
    if (fstat(fd, &st) != 0)
        return EIO;

    char *spath = ne_path_escape(path);
    time_t start_time = time(NULL);
    if(debug)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "start uploading file: %s", path);
    //Note that this file is currently in transfer. Moving, renaming or deleting one of
    //the parent-directories is not allowed until the upload is finished.
    add_transfering_file(path, TRANSFER_UPLOAD);
    path_copy = strdup(path);

    ne_request *req = ne_request_create(sess->neon_session, "PUT", spath);

    if (!has_if_match_bug) {
        if (!*exists) {
            ne_add_request_header(req, "If-None-Match", "*");
        } else {
            if (etag && *etag)
                ne_add_request_header(req, "If-Match", *etag);
        }
    }

    if (use_expect100)
#if NE_VERSION_MINOR == 25
        ne_set_request_expect100(req, 1);
#else /* NE_VERSION_MINOR > 25 */
        ne_set_request_flag(req, NE_REQFLAG_EXPECT100, 1);
#endif /* NE_VERSION_MINOR > 25 */

    ne_lock_using_resource(req, spath, 0);
#if _FILE_OFFSET_BITS == 64 && NE_VERSION_MINOR < 27
    ne_set_request_body_fd64(req, fd, 0, st.st_size, cache_path);
#else /* _FILE_OFFSET_BITS != 64 || NE_VERSION_MINOR >= 27 */
    ne_set_request_body_fd(req, fd, 0, st.st_size, cache_path);
#endif /* _FILE_OFFSET_BITS != 64 || NE_VERSION_MINOR >= 27 */

    mutex_unlock(mutex_allops);
    ret = ne_request_dispatch(req);
    mutex_lock(mutex_allops);
    ret = get_error(ret, "PUT");


    if(!path || !*path || strcmp(path, path_copy)){
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "uploaded file is lost");
        ret = ENODELOST;
    }

    //if (ret == EACCES && lock_discover(spath, expire) == 0) { //AVM
    if(ret && ret != ENODELOST && *webdav_keep_on_running) {
        //try uploading again
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "uploading file failed - try again: errno: %d - http: %s - file: %s", ret, dav_get_webdav_error(), path_copy);

        ne_request_destroy(req);
        req = ne_request_create(sess->neon_session, "PUT", spath);

        if (!has_if_match_bug) {
            if (!*exists) {
                ne_add_request_header(req, "If-None-Match", "*");
            } else {
                if (etag && *etag)
                    ne_add_request_header(req, "If-Match", *etag);
            }
        }

        if (use_expect100)
#if NE_VERSION_MINOR == 25
            ne_set_request_expect100(req, 1);
#else /* NE_VERSION_MINOR > 25 */
            ne_set_request_flag(req, NE_REQFLAG_EXPECT100, 1);
#endif /* NE_VERSION_MINOR > 25 */

        ne_lock_using_resource(req, spath, 0);
#if _FILE_OFFSET_BITS == 64  && NE_VERSION_MINOR < 27
        ne_set_request_body_fd64(req, fd, 0, st.st_size, cache_path);
#else /* _FILE_OFFSET_BITS != 64 || NE_VERSION_MINOR >= 27 */
        ne_set_request_body_fd(req, fd, 0, st.st_size, cache_path);
#endif /* _FILE_OFFSET_BITS != 64 || NE_VERSION_MINOR >= 27 */

        mutex_unlock(mutex_allops);
        ret = ne_request_dispatch(req);
        mutex_lock(mutex_allops);
        ret = get_error(ret, "PUT");

        if(!path || !*path || strcmp(path, path_copy)){
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "uploaded file is lost");
            ret = ENODELOST;
        }
    }

    if (!ret) {
        int need_head = 0;
        const char *value;
        *exists = 1;

        if (etag) {
            if (*etag) free(*etag);
            *etag = normalize_etag(ne_get_response_header(req, "ETag"));
        }

        if (mtime) {
            value = ne_get_response_header(req, "Last-Modified");
            if (!value)
                value = ne_get_response_header(req, "Date");
            if (value) {
                *mtime = ne_httpdate_parse(value);
            }
        }

        need_head = 1;

        if (mime) {
            value = ne_get_response_header(req, "Content-Type");
            if (value) {
                if (*mime)
                    free(*mime);
                *mime = ne_strdup(value);
            }
        }
        if (execute == 1)
            dav_set_execute(path, execute);
        if (need_head)
            dav_head(path, etag, mtime, NULL, mime);
        time_t dur_time = time(NULL)-start_time;
        if(debug){
            if(dur_time > 0 && st.st_size > 0)
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "finished uploading file (%d sec, %.2f kbytes/sec): %s",
                        (int)dur_time, (st.st_size / dur_time) / 1000.0, path_copy);
            else
                syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "finished uploading file (%d sec, %llu bytes): %s",
                        (int)dur_time, st.st_size, path_copy);
        }
        provide_set_uint64(SIZE_UPLOADS, st.st_size, '+');
        provide_set_uint64(FINISHED_UPLOADS, 1, '+');
        dav_quota(root_path, NULL, NULL);
    }
    if(ret && ret != ENODELOST && *webdav_keep_on_running){
        //generate failure-event
        if(ret == ENOSPC){
            /* 413: Request Entity Too Large */
            /* 507: Insufficient Storage */
            avm_event_log(AVM_EVENT_SERVER_NO_SPACE, path_copy);
        }
        else if(ret == EPERM){
            /* 401: Unauthorized */
            /* 402: Payment Required */
            /* 407: Proxy Authentication Required */
            avm_event_log(AVM_EVENT_SERVER_NO_WRITE_ACCESS, path_copy);
        }
        else if(ret == EAGAIN){
            /* 408: Request Timeout */
            /* 504: Gateway Timeout */
            avm_event_log(AVM_EVENT_SERVER_CONNECTION_LOST, path_copy);
        }
        else if(ret == EACCES){
            /* 423: Locked */
            avm_event_log(AVM_EVENT_SERVER_ACCESS_FAILED, path_copy);
        }
        else{
            /* unknown error */
            avm_event_log2(AVM_EVENT_SERVER_GENERIC_FAIL, dav_get_webdav_error(), path_copy);
        }

        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "uploading file failed: errno: %d - http: %s - file: %s", ret, dav_get_webdav_error(), path_copy);
        provide_set_uint64(FAILED_UPLOADS, 1, '+');
        provide_set_uint64(SUM_FAILED_UPLOADS, 1, '+');
    }

    ne_request_destroy(req);
    free(spath);
    close(fd);
    //File-upload is finished, modifying this file or a parent-directory is now allowed.
    del_transfering_file(path_copy);
    if(path_copy) free(path_copy);

    return ret;
}

#if 0 // newformat wird aktuell nicht von den UI WebDAV Servern genutzt
static void
userinfo_writer_newformat(void *userdata, const char* block, size_t length)
{
    char sep[] = "\n";
    char* quota_info = strdup(block); if(!quota_info) return;
    char* tmp = 0;
    int count = 0;
    for(tmp = strtok(quota_info, sep); tmp && count<16; tmp = strtok(0, sep)) {
        if (0==strncmp(tmp, "StorageQuota=", 13)) {
            quota_cache.storage_available = (uint64_t) atoll(tmp+13);
        } else if (0==strncmp(tmp, "StorageSmartDrive=", 18)) {
            quota_cache.storage_used = (uint64_t) atoll(tmp+18);
        } else if (0==strncmp(tmp, "StorageFileCount=", 17)) {
            quota_cache.storage_filecount = (uint32_t) atol(tmp+17);
        } else if (0==strncmp(tmp, "TrafficOwnerQuota=", 18)) {
            quota_cache.download_avail = (uint64_t) atoll(tmp+18);
        } else if (0==strncmp(tmp, "TrafficOwnerUsed=", 17)) {
            quota_cache.download_used = (uint64_t) atoll(tmp+17);
        } else if (0==strncmp(tmp, "TrafficUploadQuota=", 19)) {
            quota_cache.upload_avail = (uint64_t) atoll(tmp+19);
        } else if (0==strncmp(tmp, "TrafficUpload=", 14)) {
            quota_cache.upload_used = (uint64_t) atoll(tmp+14);
        } else if (0==strncmp(tmp, "TrafficUploadQuota=", 19)) {
            quota_cache.upload_avail = (uint64_t) atoll(tmp+19);
        } else if(0==strncmp(tmp, "TrafficUpload=", 14)) {
            quota_cache.upload_used = (uint64_t) atoll(tmp+14);
        } else if(0==strncmp(tmp, "MaxFilesPerFolder=", 18)) {
            quota_cache.max_filesperfolder = (uint32_t) atoll(tmp+18);
        } else if(0==strncmp(tmp, "MaxFileSize=", 12)) {
            quota_cache.max_filesize = (uint64_t) atoll(tmp+12);
        } else if(0==strncmp(tmp, "MaxFileCount=", 13)) {
            quota_cache.max_filecount = (uint32_t) atoll(tmp+13);
        } else if(0==strncmp(tmp, "MaxFileNameLength=", 18)) {
            quota_cache.max_filenamelength = (uint32_t) atoll(tmp+18);
        }
        count++;
    }

    /* WORKAROUND: exchange upload_avail and upload_used, if necessary. */
    if( (quota_cache.upload_used > 0) && (quota_cache.upload_avail > 0) &&
        (quota_cache.upload_avail < (quota_cache.upload_used + 200000))) {
        uint64_t tmp2 = quota_cache.upload_avail;
        quota_cache.upload_avail = quota_cache.upload_used;
        quota_cache.upload_used  = tmp2;
    }

    if(debug)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "parsed user info: st_av: %llu, st_us: %llu, "
            "up_av: %llu, up_us: %llu, dw_av: %llu, dw_us: %llu, tr_av: %llu, tr_us: %llu, st_files: %u, st_maxfiles: %u, "
            "st_maxfilesperfold: %u, st_maxfilesize: %llu, st_maxfilenamelen: %u",
            quota_cache.storage_available, quota_cache.storage_used, quota_cache.upload_avail,
            quota_cache.upload_used, quota_cache.download_avail, quota_cache.download_used,
            quota_cache.traffic_avail, quota_cache.traffic_used, quota_cache.storage_filecount, quota_cache.max_filecount,
            quota_cache.max_filesperfolder, quota_cache.max_filesize, quota_cache.max_filenamelength);

    free(quota_info);
}
#endif // 0

static void
userinfo_writer_oldformat(void *userdata, const char* block, size_t length)
{
    char sep[] = ",";
    char* quota_info = strdup(block); if(!quota_info) return;
    char* tmp = 0;
    int count = 0;
    for(tmp = strtok(quota_info, sep); tmp && count<4; tmp = strtok(0, sep)) {
        switch(count){
            case 0:
                quota_cache.storage_available = (uint64_t) atoll(tmp);
                break;
            case 1:
                quota_cache.storage_used = (uint64_t) atoll(tmp);
                break;
            case 2:
                if(provider == PROVIDER_WEBDE)
                    quota_cache.download_avail = (uint64_t) atoll(tmp);
                else if(provider == PROVIDER_GMX)
                    quota_cache.traffic_avail = (uint64_t) atoll(tmp);
                break;
            case 3:
                if(provider == PROVIDER_WEBDE)
                    quota_cache.download_used = (uint64_t) atoll(tmp);
                else if(provider == PROVIDER_GMX)
                    quota_cache.traffic_used = (uint64_t) atoll(tmp);
                break;
            default:
                break;
        }
        count++;
    }
    free(quota_info);
}

/*
 * Store quota infos of function USERINFO
 * USERINFO is a special function, provided by servers of UI (1und1, gmx, web.de and ionos)
 * It returns the following values in bytes, separated by commas:
 * 1und1:
 *  StorageQuota: entire storage
 *  StorageSmartDrive: used storage in SmartDrive
 *  TrafficOwnerQuota: max. download-trafficquota
 *  TrafficOwnerUsed: used download-traffic in the current month
 *  TrafficUpload: used upload-traffic in the current month
 *  TrafficUploadQuota: max. upload-trafficquota
 *  example:  16106127360,80254394,32212254720,573918485,392903139,32212254720
 * gmx:
 *  StorageQuota: entire storage
 *  StorageSmartDrive: used storage in SmartDrive
 *  TrafficOwnerQuota: up- and download quota
 *  TrafficOwnerUsed: used up- and download traffic in the current month
 *  example: 1073741824,1134076,2097152000,0
 * web.de
 *  StorageQuota: entire storage (+ fotoalbum and mail)
 *  StorageSmartDrive: used storage in SmartDrive
 *  TrafficOwnerQuota: max. download traffic quota in the current month
 *  TrafficOwnerUsed: used download-traffic in the current month
 *  example: 4244635648,35840,4294967296,1762.
 */
static int
userinfo_writer(void *userdata, const char *block, size_t length)
{
    if(length < 1 || !block)
        return 0;

    if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "USERINFO result: %s", block);
    //if(provider == PROVIDER_1UND1 || provider == PROVIDER_WEBDE)
    //    userinfo_writer_newformat(userdata, block, length);
    //else

    // alle United Internet WebDAV-Server schicken die Daten im "oldformat" Komma separiert
    // z.B. gmx.de "2147483648,82591607,4294967296,0,4294967296,0"
    userinfo_writer_oldformat(userdata, block, length);

    //if(provider == PROVIDER_1UND1 || provider == PROVIDER_WEBDE){
    //    quota_cache.traffic_avail = quota_cache.upload_avail + quota_cache.download_avail;
    //    quota_cache.traffic_used = quota_cache.upload_used + quota_cache.download_used;
    //}
    return 0;
}

int
dav_quota(const char *path, uint64_t *available, uint64_t *used)
{
    int ret = 0;


    if(time(NULL) <= quota_cache.update_time + QUOTA_CACHE_TIMEOUT){
        if(!quota_cache.update_ret){
            //Use cached values for a faster response.
            //Reason: Samba is calling statfs() frequently while writing
            //to a file. Avoid communication with the webdav-server
            //while writing to a file for increasing performance.
            if(available && used){
                *available  = quota_cache.storage_available;
                *used       = quota_cache.storage_used;
            }
            return quota_cache.update_ret;
        }
        else{
            //Do not invoke PROPFIND/USERINFO again, if it
            //failed within the last seconds. Probably the server does
            //not support the actions.
            return quota_cache.update_ret;
        }
    }
    else {
        //Fetch new quota infos.
        memset(&quota_cache, 0, sizeof(quota_context));
        quota_cache.storage_available = (uint64_t) -1;
        quota_cache.storage_used = (uint64_t) -1;

        struct server_session* sess = get_session(1);
        if (!sess->initialized) {
            if ((ret = dav_init_connection(sess))!= 0)
                return ret;
        }

        char *spath = ne_path_escape(path);
        ne_propfind_handler *ph = ne_propfind_create(sess->neon_session, spath, NE_DEPTH_ZERO);
        ret = ne_propfind_named(ph, quota_names, quota_result, NULL);
        //PROPFIND is supported by: apple, t-online, ..
        ret = get_error(ret, "PROPFIND");
        ne_propfind_destroy(ph);

        if (!ret && quota_cache.storage_available != (uint64_t) -1 && quota_cache.storage_used != (uint64_t) -1) {
            //got quota infos with PROPFIND
            ; //if(debug) syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "PROPFIND result: %llu / %llu", quota_cache.storage_used, quota_cache.storage_available);
        }

        if(provider == PROVIDER_1UND1 || provider == PROVIDER_GMX || provider == PROVIDER_WEBDE || provider == PROVIDER_IONOS){
            //no / not all quota infos available with PROPFIND, try USERINFO
            //USERINFO is a special function of GMX, 1und1, web.de and ionos
            //and returns the following infos:
            //StorageQuota,StorageSmartDrive,TrafficOwnerQuota,TrafficOwnerUsed,TrafficUpload,TrafficUploadQuota
            ne_decompress *dc;
            ne_request *req = ne_request_create(sess->neon_session, "USERINFO", spath);
            dc = ne_decompress_reader(req, ne_accept_2xx, userinfo_writer, NULL);
            ret = ne_request_dispatch(req);
            ret = get_error(ret, "USERINFO");
            ne_decompress_destroy(dc);
            ne_request_destroy(req);
        }
        free(spath);
    }

    if (ret || quota_cache.storage_available == (uint64_t) -1 || quota_cache.storage_used == (uint64_t) -1) {
        memset(&quota_cache, 0, sizeof(quota_context));
        quota_cache.update_ret = EIO;
    }
    else{
        if(available && used){
            *available  = quota_cache.storage_available;
            *used       = quota_cache.storage_used;
        }
        provide_set_quota(quota_cache);
        quota_cache.update_ret = 0;
    }
    quota_cache.update_time = time(NULL);
    return quota_cache.update_ret;
}


int
dav_set_execute(const char *path, int set)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    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;

    char *spath = ne_path_escape(path);
    ret = ne_proppatch(sess->neon_session, spath, &op[0]);
    ret = get_error(ret, "PROPPATCH");
    free(spath);

    return ret;
}


void
dav_set_no_terminal(void)
{
    have_terminal = 0;
}


int
dav_unlock(const char *path, time_t *expire)
{
    int ret;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

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

    char *spath = ne_path_escape(path);
    struct ne_lock *lock = lock_by_path(spath);
    free(spath);

    if (!lock) {
        *expire = 0;
        return 0;
    }

    ret = ne_unlock(sess->neon_session, lock);
    ret = get_error(ret, "UNLOCK");

    if (!ret || ret == ENOENT || ret == EINVAL) {
        ne_lockstore_remove(locks, lock);
        ne_lock_destroy(lock);
        *expire = 0;
        return 0;
    }

    return ret;
}



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

#ifdef HAVE_ICONV
static void
convert(char **s, iconv_t conv)
{
    size_t insize = strlen(*s);
    char *in = *s;
    size_t outsize = MB_LEN_MAX * (insize + 1);
    char *buf = calloc(outsize, 1);
    if (!buf) abort();
    char *out = buf;

    iconv(conv, NULL, NULL, &out, &outsize);
    if (iconv(conv, &in, &insize, &out, &outsize) != (size_t)-1
            && insize == 0 && outsize >= MB_LEN_MAX) {
        memset(out, 0, MB_LEN_MAX);
        free(*s);
        *s = ne_strndup(buf, out - buf + MB_LEN_MAX);
    }

    free(buf);
}
#endif /* HAVE_ICONV */


/* Returns a file error code according to 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:
        err = EIO;
        break;
    case NE_AUTH:
        err = EPERM;
        break;
    case NE_PROXYAUTH:
        err = EPERM;
        break;
    case NE_CONNECT:
        err = EAGAIN;
        break;
    case NE_TIMEOUT:
        err = EAGAIN;
        break;
    case NE_FAILED:
        err = EINVAL;
        break;
    case NE_RETRY:
        err = EAGAIN;
        break;
    case NE_REDIRECT:
        err = ENOENT;
        break;
    default:
        err = EIO;
        break;
    }
    if(ret)
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "%s failed:  errno (%d) - neon (%d) - http (%s)", method, err, ret, dav_get_webdav_error());

    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)
{
    struct server_session* sess = get_session(0);
    if (!sess)
        return EIO;

    const char *text = ne_get_error(sess->neon_session);

    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 206:           /* Partial 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 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;
    }
}


/* Searches for a lock for resource at path and returns
   this lock if successfull, NULL otherwise. */
static struct ne_lock *
lock_by_path(const char *path)
{
    struct ne_lock *lock = ne_lockstore_first(locks);
    while (lock) {
        if (strcmp(path, lock->uri.path) == 0)
            return lock;
        lock = ne_lockstore_next(locks);
    }

    return NULL;
}


/* 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:
   path   : 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 *path, time_t *expire)
{
    if (!locks) {
        *expire = 0;
        return -1;
    }

    int ret = 0;
    struct server_session* sess = get_session(1);
    if (!sess->initialized) {
        if ((ret = dav_init_connection(sess))!= 0)
            return ret;
    }

    struct ne_lock *lock = NULL;
    ret = ne_lock_discover(sess->neon_session, path, lock_result, &lock);
    ret = get_error(ret, "LOCKDISCOVER");

    if (!ret && lock)  {
        *expire = -1;
        lock_refresh(lock, expire);
        return 0;
    } else {
        if (!ret)
            *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)
{
    struct server_session* sess = get_session(0);
    if (!sess->initialized)
            return;
    int ret = ne_lock_refresh(sess->neon_session, lock);
    ret = get_error(ret, "LOCKREFRESH");

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


static ssize_t
log_writer(void *cookie_, const char *buffer, size_t size)
{
    if (size <= 0)
        return 0;

    size_t written = 0;
    const char *bpos = buffer;
    while (written < size) {
        size_t n = 2;
        char *cpos = (char *) cookie_;
        while (n < log_bufsize && written < size) {
            if (*bpos == '%') {
                *cpos++ = '%';
                n++;
            }
            *cpos++ = *bpos++;
            n++;
            written++;
        }
        *cpos = '\0';
        if(debug)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "%s", (char *) cookie_);
    }
    return written;
}


/* Checks etag for weakness indicator and quotation marks.
   The reurn value is either a strong etag with quotation marks or NULL.
   Depending on global variable drop_weak_etags weak etags are either
   dropped or convertet into strong ones. */
static char *
normalize_etag(const char *etag)
{
    if (!etag) return NULL;

    const char * e = etag;
    if (*e == 'W') {
        if (drop_weak_etags) {
            return NULL;
        } else {
            e++;
            if (*e == '/') {
                e++;
            } else {
                return NULL;
            }
        }
    }
    if (!*e) return NULL;

    char *ne = NULL;
    if (*e == '\"') {
        ne = strdup(e);
    } else {
        if (asprintf(&ne, "\"%s\"", e) < 0)
            ne = NULL;;
    }

    return ne;
}
/* Replaces slashes in name by "slash-", "-slash-" or "-slash", depending
   on the position of the slash within name. */
static void
replace_slashes(char **name)
{
    char *slash = strchr(*name, '/');
    while (slash) {
        char *end = *name + strlen(*name) - 1;
        char *nn;
        *slash = '\0';
        if (slash == *name) {
            nn = ne_concat("slash-", slash + 1, NULL);
        } else if (slash == end) {
            nn = ne_concat(*name, "-slash", NULL);
        } else {
            nn = ne_concat(*name, "-slash-", slash + 1, NULL);
        }
        free(*name);
        *name = nn;
        slash = strchr(*name, '/');
    }
}


/* Call-back functions for neon. */

static void
add_header(ne_request *req, void *userdata, ne_buffer *header_)
{
    if (userdata)
        ne_buffer_zappend(header_, (char *) userdata);

    if (cookie_list && cookie_list[0]) {
        ne_buffer_zappend(header_, "Cookie: ");
        ne_buffer_zappend(header_, cookie_list[0]);
        int i = 1;
         while (i < n_cookies && cookie_list[i]) {
            ne_buffer_zappend(header_, ", ");
            ne_buffer_zappend(header_, cookie_list[i]);
            i++;
        }
        ne_buffer_zappend(header_, "\r\n");
    }
}


/* 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/password 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)
{
    if (attempt) {
        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)
            strncpy(user_, username, NE_ABUFSIZ - 1);
        if (password)
            strncpy(pwd, password, NE_ABUFSIZ - 1);
    } else if (strcmp((char *) userdata, "proxy") == 0) {
        if (p_username)
            strncpy(user_, p_username, NE_ABUFSIZ - 1);
        if (p_password)
            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.
   return value : 0 on success, EIO otherwise. */
static int
block_writer(void *userdata, const char *block, size_t length)
{
    get_context *ctx = (get_context *) userdata;
    struct server_session* sess = get_session(0);
    char write_failed = 0;
    if (!sess->initialized) {
        ctx->error = EIO;
        return ctx->error;
    }

    mutex_lock(mutex_allops);

    if (!ctx->fd)
        ctx->fd = open(ctx->file, O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR);
    if (ctx->fd <= 0) {
        ne_set_error(sess->neon_session, _("%i can't open cache file"), 0);
        ctx->error = EIO;
    }

    while (!ctx->error && length > 0) {
        ssize_t ret = write(ctx->fd, block, length);
        if (ret < 0) {
            int err_code = errno;
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "writing cache file failed: %s", strerror(err_code));
            if(err_code == ENOSPC && resize_cache()){
                //Write failed, because no space is left.
                //Free space may be now available, because the cache was resized.
                //Try the write call a 2nd time.
                ret = write(ctx->fd, block, length);
                if(ret < 0){
                    syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "2nd attempt to write cache file failed: %s", strerror(errno));
                    ctx->error = EIO;
                    ne_set_error(sess->neon_session, _("%i error writing to cache file"), 0);
                    write_failed = 1;
                    break;
                }
                else{
                    if(debug)
                        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "2nd attempt to write cache file succeeded");
                }
            }
            else{
                ctx->error = EIO;
                ne_set_error(sess->neon_session, _("%i error writing to cache file"), 0);
                write_failed = 1;
                break;
            }
        }
        length -= ret;
        block += ret;
    }

    if(write_failed) {
        avm_event_log(AVM_EVENT_TRANSFER_FAILED, ctx->server_path);
    }
    /*else if(debug){
        struct stat st;
        if(!stat(ctx->file, &st))
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), "current size of downloading file: %llu", st.st_size);
    }*/
    mutex_unlock(mutex_allops);

    return ctx->error;
}

/* The block_writer variant for partial get requests. */
static int
block_writer_buf(void *userdata, const char *block, size_t length)
{
    get_buf_context *ctx = (get_buf_context *) userdata;
    struct server_session* sess = get_session(0);
    if (!sess->initialized) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "session not initialized (%i)", __LINE__);
        ctx->error = EIO;
        return ctx->error;
    }
    mutex_lock(mutex_allops);

    if(ctx->written_size + length > ctx->buf_size) {
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR), "too much bytes for buffer - written: %d, new: %d, bufsize: %d", ctx->written_size, length, ctx->buf_size);
        length = ctx->buf_size - ctx->written_size;
    }
    memcpy(ctx->buf + ctx->written_size, block, length);
    ctx->written_size += length;

    mutex_unlock(mutex_allops);
    return ctx->error;
}

/* 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 >= 26 */

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

#endif    /* NE_VERSION_MINOR >= 26 */

    struct server_session* sess = get_session(0);
    if (!sess->initialized)
           return;

    if (!locks || !owner || !userdata || !lock || !lock->owner)
        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 || !href || !set)
        return;

    ne_uri uri;
    if (ne_uri_parse(href, &uri) != 0 || !uri.path) {
        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 >= 26 */

static void
prop_result(void *userdata, const ne_uri *uri, const ne_prop_result_set *set)
{
    propfind_context *ctx = (propfind_context *) userdata;
    if (!ctx || !uri || !uri->path || !set)
        return;

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

#endif /* NE_VERSION_MINOR >= 26 */

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

    const char *data;

    data = ne_propset_value(set, &prop_names[TYPE]);
    if(!data)
        data =  ne_propset_value(set, &prop_names_simple[TYPE]);
    if (data && strstr(data, "collection"))
            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]);
            if(!data)
                data = ne_propset_value(set, &prop_names_simple[NAME]);
            if (data) {
                result->name = ne_strdup(data);
                replace_slashes(&result->name);
#ifdef HAVE_ICONV
                if (from_utf_8){
                    convert(&result->name, from_utf_8);
                }
#endif
            }
        }
        if (!result->name) {
            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);
            replace_slashes(&result->name);
#ifdef HAVE_ICONV
            if (from_server_enc){
                convert(&result->name, from_server_enc);
            }
#endif
        }
    }

    data = ne_propset_value(set, &prop_names[ETAG]);
    if(!data)
        data = ne_propset_value(set, &prop_names_simple[ETAG]);
    result->etag = normalize_etag(data);

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

    data = ne_propset_value(set, &prop_names[CREATION]);
    if(!data)
        data = ne_propset_value(set, &prop_names_simple[CREATION]);
    if (data) {
        result->ctime = ne_iso8601_parse(data);
        if (result->ctime == (time_t) -1)
            result->ctime = ne_httpdate_parse(data);
        if (result->ctime == (time_t) -1)
            result->ctime = 0;
    }

    data = ne_propset_value(set, &prop_names[MODIFIED]);
    if(!data)
        data = ne_propset_value(set, &prop_names_simple[MODIFIED]);
    if (data) {
        result->mtime = ne_httpdate_parse(data);
        if (result->mtime == (time_t) -1)
            result->mtime = ne_iso8601_parse(data);
        if (result->mtime == (time_t) -1)
            result->mtime = 0;
    }

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

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

    if (!dbl) {
        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)
                || ((dbl->size > 0 || !result->size)
                    && ((dbl->etag && !result->etag)
                || ((dbl->etag || !result->etag)
                    && ((dbl->mtime > 0 && !result->mtime)
                || ((dbl->mtime > 0 || !result->mtime)
                    && (dbl->ctime > 0 || !result->ctime))))))))) {
            dav_delete_props(result);
        } else {
            free(dbl->path);
            dbl->path = result->path;
            free(dbl->name);
            dbl->name = result->name;
            if (dbl->etag)
                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);
        }
    }
}


/* Reads available and used bytes from set and stores them in
   userdata. */
#if NE_VERSION_MINOR < 26

static void
quota_result(void *userdata, const char *href, const ne_prop_result_set *set)
{
    if (!href || !set)
        return;

#else /* NE_VERSION_MINOR >= 26 */

static void
quota_result(void *userdata, const ne_uri *uri, const ne_prop_result_set *set)
{
    if (!uri || !uri->path || !set)
        return;

#endif /* NE_VERSION_MINOR >= 26 */

    const char *data_avail = ne_propset_value(set, &quota_names[AVAILABLE]);
    if (data_avail)
        quota_cache.storage_available = strtoull(data_avail, NULL, 10);

    const char *data_used = ne_propset_value(set, &quota_names[USED]);
    if (data_used)
        quota_cache.storage_used = strtoull(data_used, NULL, 10);
    else if (quota_cache.storage_available != (uint64_t) -1) {
        //used-value not given, set to 0
        quota_cache.storage_used = 0;
    }
    if (data_avail)
        quota_cache.storage_available += quota_cache.storage_used;
}


/* 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 || !subject || ne_ssl_cert_digest(cert, digest) != 0) {
        if (have_terminal) {
            error(0, 0, _("error processing server certificate"));
        } else {
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_ERR),
                   _("error processing server certificate"));
        }
        ret = -1;
    }

    if (have_terminal) {
        if (failures & NE_SSL_NOTYETVALID)
            error(0, 0, _("the server certificate is not yet valid"));
        if (failures & NE_SSL_EXPIRED)
            error(0, 0, _("the server certificate has expired"));
        if (failures & NE_SSL_IDMISMATCH)
            error(0, 0, _("the server certificate does not match the server name"));
        if (failures & NE_SSL_UNTRUSTED)
            error(0, 0, _("the server certificate is not trusted"));
        if (failures & ~NE_SSL_FAILMASK)
            error(0, 0, _("unknown certificate error"));
        printf(_("  issuer:      %s"), issuer);
        printf("\n");
        printf(_("  subject:     %s"), subject);
        printf("\n");
        printf(_("  identity:    %s"), ne_ssl_cert_identity(cert));
        printf("\n");
        printf(_("  fingerprint: %s"), digest);
        printf("\n");
        if (!ret) {
            printf(_("You only should accept this certificate, if you can\n"
                     "verify the fingerprint! The server might be faked\n"
                     "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();
#ifdef HAVE_RPMATCH
            if (rpmatch(s) < 1)
#else
            if(!s || (*s == 'n') || (*s == 'N'))
#endif
                ret = -1;
            free(s);
        }
    }

    if (debug) {
        if (failures & NE_SSL_NOTYETVALID)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
                   _("the server certificate is not yet valid"));
        if (failures & NE_SSL_EXPIRED)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
                   _("the server certificate has expired"));
        if (failures & NE_SSL_IDMISMATCH)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
                   _("the server certificate does not match the server name"));
        if (failures & NE_SSL_UNTRUSTED)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
                   _("the server certificate is not trusted"));
        if (failures & ~NE_SSL_FAILMASK)
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG),
                   _("unknown certificate error"));
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), _("  issuer: %s"), issuer);
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), _("  subject: %s"), subject);
        syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), _("  identity: %s"),
                           ne_ssl_cert_identity(cert));
        if (!ret) {
            syslog(LOG_MAKEPRI(LOG_DAEMON, LOG_DEBUG), _("  accepted by user"));
        }
    }

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


#if NE_VERSION_MINOR > 25
/* A ne_post_header_fn to read cookies from the Set-Cookie header and
   store them in the global arrray of strings cookie_list. The cookies
   will be send in the Cookie header of subsequent requests.
   Only the "name=value" part of the cookie is stored. All attributes
   are completely ignored.
   When a cookie with the same name as an already stored cookie, but with
   a different value is received, it's value is updated if necessary.
   Only n_cookies cookies will be stored. If the server sends more
   different cookies these will be ignored. */
static void
get_cookies(ne_request *req, void *userdata, const ne_status *status)
{
    const char *cookie_hdr = ne_get_response_header(req, "Set-Cookie");
    if (!cookie_hdr)
        return;

    const char *next = cookie_hdr;
    while (next) {
        const char *start = next;
        next = strchr(start, ',');
        const char *end = strchr(start, ';');
        if (next) {
            if (!end || end > next)
                end = next;
            next++;
        } else if (!end) {
            end = start + strlen(start);
        }

        while (start < end && *start == ' ')
            start++;
        while (end > start && *(end - 1) == ' ')
            end--;

        char *es = strchr(start, '=');
        if (!es)
            continue;
        size_t nl = es - start;
        size_t vl = end - es - 1;
        if (nl == 0 || vl == 0)
            continue;

        int i = 0;
        for (i = 0; i < n_cookies; i++) {
            if (!cookie_list[i]) {
                cookie_list[i] = ne_strndup(start, end - start);
                break;
            }
            if (strncmp(cookie_list[i], start, nl) == 0) {
                if (strncmp(cookie_list[i] + nl + 1, es + 1, vl) != 0) {
                    free(cookie_list[i]);
                    cookie_list[i] = ne_strndup(start, end - start);
                }
                break;
            }
        }
    }
}

#endif /* NE_VERSION_MINOR > 25 */