dav/config.c

changeset 734
b2cd82149116
parent 732
b0eb645cd26e
child 735
74a6e2d4fb1f
--- a/dav/config.c	Sun Aug 08 14:59:02 2021 +0200
+++ b/dav/config.c	Sun Aug 08 16:49:47 2021 +0200
@@ -37,6 +37,7 @@
 #include "pwd.h"
 #include "config.h"
 #include "main.h"
+#include "pwd.h"
 #include <libidav/utils.h>
 
 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b)
@@ -817,3 +818,286 @@
     free(pwfile);
     return ret; 
 }
+
+
+
+
+Repository* url2repo_s(sstr_t url, char **path) {
+    *path = NULL;
+    
+    int s;
+    if(sstrprefix(url, SC("http://"))) {
+        s = 7;
+    } else if(sstrprefix(url, SC("https://"))) {
+        s = 8;
+    } else {
+        s = 1;
+    }
+
+    // split URL into repository and path
+    sstr_t r = sstrsubs(url, s);
+    sstr_t p = sstrchr(r, '/');
+    r = sstrsubsl(url, 0, url.length-p.length);
+    if(p.length == 0) {
+        p = sstrn("/", 1);
+    }
+    
+    Repository *repo = get_repository(r);
+    if(repo) {
+        *path = sstrdup(p).ptr;
+    } else {
+        // TODO: who is responsible for freeing this repository?
+        // how can the callee know, if he has to call free()?
+        repo = calloc(1, sizeof(Repository));
+        repo->name = strdup("");
+        repo->decrypt_content = true;
+        repo->verification = true;
+        repo->authmethods = CURLAUTH_BASIC;
+        if(url.ptr[url.length-1] == '/') {
+            repo->url = sstrdup(url).ptr;
+            *path = strdup("/");
+        } else if (sstrchr(url, '/').length > 0) {
+            // TODO: fix the following workaround after
+            //       fixing the inconsistent behavior of util_url_*()
+            repo->url = util_url_base_s(url);
+            sstr_t truncated = sstrdup(url);
+            *path = strdup(util_url_path(truncated.ptr));
+            free(truncated.ptr);
+        } else {
+            repo->url = sstrdup(url).ptr;
+            *path = strdup("/");
+        }
+    }
+    
+    return repo;
+}
+
+Repository* url2repo(char *url, char **path) {
+    return url2repo_s(sstr(url), path);
+}
+
+static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) {
+    if(cmd_getoption(a, "noinput")) {
+        return 1;
+    }
+    
+    char *ps_password = NULL;
+    if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) {
+        UcxBuffer *cmd_out = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND);
+        if(!util_exec_command(secrets->unlock_cmd, cmd_out)) {
+            // command successful, get first line from output without newline
+            // and use that as password for the secretstore
+            size_t len = 0;
+            for(size_t i=0;i<=cmd_out->size;i++) {
+                if(i == cmd_out->size || cmd_out->space[i] == '\n') {
+                    len = i;
+                    break;
+                }
+            }
+            if(len > 0) {
+                ps_password = malloc(len + 1);
+                memcpy(ps_password, cmd_out->space, len);
+                ps_password[len] = 0;
+            }
+        }
+        ucx_buffer_free(cmd_out);
+    }
+    
+    if(!ps_password) {
+        ps_password = util_password_input("Master password: ");
+        if(!ps_password) {
+            return 1;
+        }
+    }
+    
+    if(pwdstore_setpassword(secrets, ps_password)) {
+        fprintf(stderr, "Error: cannot create key from password\n");
+        return 1;
+    }
+    if(pwdstore_decrypt(secrets)) {
+        fprintf(stderr, "Error: cannot decrypt secrets store\n");
+        return 1;
+    }
+    return 0;
+}
+
+typedef struct CredLocation {
+    char *id;
+    char *location;
+} CredLocation;
+
+static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) {
+    return strcmp(e2->location, e1->location);
+}
+
+static void free_cred_location(CredLocation *c) {
+    // c->id is not a copy, therefore we don't have to free it
+    free(c->location);
+    free(c);
+}
+
+static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) {
+    if(!credid) {
+        return 0;
+    }
+    
+    PwdStore *secrets = get_pwdstore();
+    if(!secrets) {
+        fprintf(stderr, "Error: no secrets store available\n");
+        return 0;
+    }
+    
+    if(pwdstore_has_id(secrets, credid)) {
+        if(!secrets->isdecrypted) {
+            if(decrypt_secrets(a, secrets)) {
+                return 0;
+            }
+        }
+        
+        PwdEntry *s_cred = pwdstore_get(secrets, credid);
+        if(s_cred) {
+            *user = s_cred->user;
+            *password = s_cred->password;
+            return 1;
+        }
+    } else {
+        fprintf(stderr, "Error: credentials id '%s' not found\n", credid);
+    }
+    
+    return 0;
+}
+
+
+static int get_location_credentials(CmdArgs *a, Repository *repo, char *path, char **user, char **password) {
+    PwdStore *secrets = get_pwdstore();
+    if(!secrets) {
+        return 0;
+    }
+    
+    /*
+     * The list secrets->location contains urls or repo names as
+     * location strings. We need a list, that contains only urls
+     */
+    UcxList *locations = NULL;
+    UCX_FOREACH(elm, secrets->locations) {
+        PwdIndexEntry *e = elm->data;
+        
+        UCX_FOREACH(loc, e->locations) {
+            char *path;
+            Repository *r = url2repo(loc->data, &path);
+            CredLocation *urlentry = calloc(1, sizeof(CredLocation));
+            urlentry->id = e->id;
+            urlentry->location = util_concat_path(r->url, path);
+            locations = ucx_list_append(locations, urlentry);
+        }
+    }
+    // the list must be sorted
+    locations = ucx_list_sort(locations, (cmp_func)cmp_url_cred_entry, NULL);
+    
+    // create full request url string and remove protocol prefix
+    sstr_t req_url_proto = sstr(util_concat_path(repo->url, path));
+    sstr_t req_url = req_url_proto;
+    if(sstrprefix(req_url, S("http://"))) {
+        req_url = sstrsubs(req_url, 7);
+    } else if(sstrprefix(req_url, S("https://"))) {
+        req_url = sstrsubs(req_url, 8);
+    }
+    
+    // iterate over sorted locations and check if a location is a prefix
+    // of the requested url
+    char *id = NULL;
+    int ret = 0;
+    UCX_FOREACH(elm, locations) {
+        CredLocation *cred = elm->data;
+        sstr_t cred_url = sstr(cred->location);
+        
+        // remove protocol prefix
+        if(sstrprefix(cred_url, S("http://"))) {
+            cred_url = sstrsubs(cred_url, 7);
+        } else if(sstrprefix(cred_url, S("https://"))) {
+            cred_url = sstrsubs(cred_url, 8);
+        }
+        
+        if(sstrprefix(req_url, cred_url)) {
+            id = cred->id;
+            break;
+        }
+    }
+    
+    // if an id is found and we can access the decrypted secret store
+    // we can set the user/password
+    if(id && (secrets->isdecrypted || !decrypt_secrets(a, secrets))) {
+        PwdEntry *cred = pwdstore_get(secrets, id);
+        if(cred) {
+            *user = cred->user;
+            *password = cred->password;
+            ret = 1;
+        }
+    }
+    
+    free(req_url_proto.ptr);
+    ucx_list_free_content(locations, (ucx_destructor)free_cred_location);
+    ucx_list_free(locations);
+    
+    return ret;
+}
+
+DavSession* connect_to_repo(DavContext *ctx, Repository *repo, char *path, dav_auth_func authfunc, CmdArgs *a) {
+    char *user = repo->user;
+    char *password = repo->password;
+    
+    if(!user && !password) {
+        if(!get_stored_credentials(a, repo->stored_user, &user, &password)) {
+            get_location_credentials(a, repo, path, &user, &password);
+        }
+    }
+    
+    DavSession *sn = dav_session_new_auth(ctx, repo->url, user, password);
+    sn->flags = get_repository_flags(repo);
+    sn->key = dav_context_get_key(ctx, repo->default_key);
+    curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods);
+    curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version);
+    if(repo->cert) {
+        curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert);
+    }
+    if(!repo->verification || cmd_getoption(a, "insecure")) {
+        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0);
+        curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0);
+    }
+    if(!cmd_getoption(a, "noinput")) {
+        dav_session_set_authcallback(sn, authfunc, repo);
+    }
+    return sn;
+}
+
+int request_auth(DavSession *sn, void *userdata) {
+    Repository *repo = userdata;
+    
+    char *user = NULL;
+    char ubuf[256];
+    if(repo->user) {
+       user = repo->user; 
+    } else {
+        fprintf(stderr, "User: ");
+        fflush(stderr);
+        user = fgets(ubuf, 256, stdin);
+    }
+    if(!user) {
+        return 0;
+    }
+    
+    char *password = util_password_input("Password: ");
+    if(!password || strlen(password) == 0) {
+        return 0;
+    }
+    
+    size_t ulen = strlen(user);
+    if(user[ulen-1] == '\n') {
+        user[ulen-1] = '\0';
+    }
+    
+    dav_session_set_auth(sn, user, password);
+    free(password);
+    
+    return 0;
+}

mercurial