# HG changeset patch # User Olaf Wintermann # Date 1729669593 -7200 # Node ID dff5f4c23aa70428ca11771e6c5b7ef008a3be83 # Parent f2ee3a5c976ed335fb49457f9bc042ae60a81287 move pwdstore to libidav diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/Makefile --- a/dav/Makefile Mon Oct 21 12:47:57 2024 +0200 +++ b/dav/Makefile Wed Oct 23 09:46:33 2024 +0200 @@ -35,9 +35,9 @@ DAV_SRC += assistant.c DAV_SRC += tar.c DAV_SRC += system.c -DAV_SRC += pwd.c DAV_SRC += libxattr.c DAV_SRC += finfo.c +DAV_SRC += connect.c SYNC_SRC = sync.c SYNC_SRC += config.c @@ -50,7 +50,7 @@ SYNC_SRC += finfo.c SYNC_SRC += tags.c SYNC_SRC += system.c -SYNC_SRC += pwd.c +SYNC_SRC += connect.c XATTR_SRC = xattrtool.c XATTR_SRC += libxattr.c diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/config.c --- a/dav/config.c Mon Oct 21 12:47:57 2024 +0200 +++ b/dav/config.c Wed Oct 23 09:46:33 2024 +0200 @@ -233,88 +233,6 @@ return k; } -static char* get_attr_content(xmlNode *node) { - // TODO: remove code duplication (util_xml_get_text) - while(node) { - if(node->type == XML_TEXT_NODE) { - return (char*)node->content; - } - node = node->next; - } - return NULL; -} - -int load_namespace(const xmlNode *node) { - const char *prefix = NULL; - const char *uri = NULL; - - xmlAttr *attr = node->properties; - while(attr) { - if(attr->type == XML_ATTRIBUTE_NODE) { - char *value = get_attr_content(attr->children); - if(!value) { - print_error( - node->line, - "missing value for attribute %s\n", (char*)attr->name); - return 1; - } - if(xstreq(attr->name, "prefix")) { - prefix = value; - } else if(xstreq(attr->name, "uri")) { - uri = value; - } else { - print_error( - node->line, - "unexpected attribute %s\n", (char*)attr->name); - return 1; - } - } - attr = attr->next; - } - - if(!prefix) { - print_error(node->line, "missing prefix attribute\n"); - return 1; - } - if(!uri) { - print_error(node->line, "missing uri attribute\n"); - return 1; - } - - if(dav_get_namespace(context, prefix)) { - print_error(node->line, "namespace prefix '%s' already used\n", prefix); - return 1; - } - - return dav_add_namespace(context, prefix, uri); -} - -int load_secretstore(const xmlNode *node) { - // currently only one secretstore is supported - - if(!pstore) { - return 0; - } - - node = node->children; - int error = 0; - while(node) { - if(node->type == XML_ELEMENT_NODE) { - char *value = util_xml_get_text(node); - if(value) { - if(xstreq(node->name, "unlock-command")) { - pstore->unlock_cmd = strdup(value); - } else if(xstreq(node->name, "lock-command")) { - pstore->lock_cmd = strdup(value); - } - } - } - node = node->next; - } - - return error; -} - PwdStore* get_pwdstore(void) { return pstore; } @@ -330,109 +248,6 @@ return ret; } - - -/* -Repository* url2repo_s(cxstring url, char **path) { - *path = NULL; - - int s; - if(cx_strprefix(url, CX_STR("http://"))) { - s = 7; - } else if(cx_strprefix(url, CX_STR("https://"))) { - s = 8; - } else { - s = 1; - } - - // split URL into repository and path - cxstring r = cx_strsubs(url, s); - cxstring p = cx_strchr(r, '/'); - r = cx_strsubsl(url, 0, url.length-p.length); - if(p.length == 0) { - p = cx_strn("/", 1); - } - - Repository *repo = get_repository(r); - if(repo) { - *path = cx_strdup(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 = cx_strdup(url).ptr; - *path = strdup("/"); - } else if (cx_strchr(url, '/').length > 0) { - // TODO: fix the following workaround after - // fixing the inconsistent behavior of util_url_*() - repo->url = util_url_base_s(url); - cxmutstr truncated = cx_strdup(url); - *path = strdup(util_url_path(truncated.ptr)); - free(truncated.ptr); - } else { - repo->url = cx_strdup(url).ptr; - *path = strdup("/"); - } - } - - return repo; -} - -Repository* url2repo(const char *url, char **path) { - return url2repo_s(cx_str(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) { - CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - 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; - } - } - cxBufferFree(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; @@ -448,7 +263,7 @@ free(c); } -static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) { +int get_stored_credentials(char *credid, char **user, char **password) { if(!credid) { return 0; } @@ -461,7 +276,7 @@ if(pwdstore_has_id(secrets, credid)) { if(!secrets->isdecrypted) { - if(decrypt_secrets(a, secrets)) { + if(pwdstore_decrypt_secrets(secrets)) { return 0; } } @@ -480,7 +295,7 @@ } -static int get_location_credentials(CmdArgs *a, DavCfgRepository *repo, const char *path, char **user, char **password) { +int get_location_credentials(DavCfgRepository *repo, const char *path, char **user, char **password) { PwdStore *secrets = get_pwdstore(); if(!secrets) { return 0; @@ -540,7 +355,7 @@ // 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))) { + if(id && (secrets->isdecrypted || !pwdstore_decrypt_secrets(secrets))) { PwdEntry *cred = pwdstore_get(secrets, id); if(cred) { *user = cred->user; @@ -554,71 +369,3 @@ return ret; } - -DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a) { - cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); - - char *user = repo->user.value.ptr; - char *password = decodedpw.ptr; - - if(!user && !password) { - if(!get_stored_credentials(a, repo->stored_user.value.ptr, &user, &password)) { - get_location_credentials(a, repo, path, &user, &password); - } - } - - DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password); - if(password) { - free(password); - } - - sn->flags = dav_repository_get_flags(repo); - sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr); - // TODO: reactivate - //curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods); - curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); - if(repo->cert.value.ptr) { - curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr); - } - if(!repo->verification.value || 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) { - DavCfgRepository *repo = userdata; - - cxstring user = {NULL, 0}; - char ubuf[256]; - if(repo->user.value.ptr) { - user = cx_strcast(repo->user.value); - } else { - fprintf(stderr, "User: "); - fflush(stderr); - user = cx_str(fgets(ubuf, 256, stdin)); - } - if(!user.ptr) { - return 0; - } - if(user.length > 0 && user.ptr[user.length-1] == '\n') { - user.length--; - } - if(user.length > 0 && user.ptr[user.length-1] == '\r') { - user.length--; - } - - char *password = util_password_input("Password: "); - if(!password || strlen(password) == 0) { - return 0; - } - - dav_session_set_auth_s(sn, user, cx_str(password)); - free(password); - - return 0; -} diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/config.h --- a/dav/config.h Mon Oct 21 12:47:57 2024 +0200 +++ b/dav/config.h Wed Oct 23 09:46:33 2024 +0200 @@ -32,10 +32,10 @@ #include #include #include -#include "pwd.h" #include "opt.h" #include +#include #ifdef __cplusplus extern "C" { @@ -62,6 +62,8 @@ PwdStore* get_pwdstore(void); int pwdstore_save(PwdStore *pwdstore); +int get_stored_credentials(char *credid, char **user, char **password); +int get_location_credentials(DavCfgRepository *repo, const char *path, char **user, char **password); //Repository* url2repo_s(cxstring url, char **path); //Repository* url2repo(const char *url, char **path); diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/connect.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dav/connect.c Wed Oct 23 09:46:33 2024 +0200 @@ -0,0 +1,108 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "connect.h" +#include "opt.h" +#include "optparser.h" + +#include + +#include +#include + +DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a) { + cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); + + char *user = repo->user.value.ptr; + char *password = decodedpw.ptr; + + if(!user && !password) { + if(!get_stored_credentials(repo->stored_user.value.ptr, &user, &password)) { + get_location_credentials(repo, path, &user, &password); + } + } + + DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password); + if(password) { + free(password); + } + + sn->flags = dav_repository_get_flags(repo); + sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr); + // TODO: reactivate + //curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods); + curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); + if(repo->cert.value.ptr) { + curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr); + } + if(!repo->verification.value || 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) { + DavCfgRepository *repo = userdata; + + cxstring user = {NULL, 0}; + char ubuf[256]; + if(repo->user.value.ptr) { + user = cx_strcast(repo->user.value); + } else { + fprintf(stderr, "User: "); + fflush(stderr); + user = cx_str(fgets(ubuf, 256, stdin)); + } + if(!user.ptr) { + return 0; + } + if(user.length > 0 && user.ptr[user.length-1] == '\n') { + user.length--; + } + if(user.length > 0 && user.ptr[user.length-1] == '\r') { + user.length--; + } + + char *password = util_password_input("Password: "); + if(!password || strlen(password) == 0) { + return 0; + } + + dav_session_set_auth_s(sn, user, cx_str(password)); + free(password); + + return 0; +} diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/connect.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dav/connect.h Wed Oct 23 09:46:33 2024 +0200 @@ -0,0 +1,46 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef DAV_CONNECT_H +#define DAV_CONNECT_H + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a); + + +#ifdef __cplusplus +} +#endif + +#endif /* DAV_CONNECT_H */ + diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/pwd.c --- a/dav/pwd.c Mon Oct 21 12:47:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,463 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#include -#include -#include - -#include "pwd.h" - -#include -#include - -#ifdef _WIN32 -#include -#pragma comment(lib, "Ws2_32.lib") -#else -#include -#endif - -PwdStore* pwdstore_open(const char *file) { - FILE *in = fopen(file, "r"); - if(!in) { - return NULL; - } - - CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); - fclose(in); - - if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) { - cxBufferFree(buf); - return NULL; - } - - PwdStore *p = malloc(sizeof(PwdStore)); - p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); - p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); - p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - p->content = buf; - p->key = NULL; - p->unlock_cmd = NULL; - p->lock_cmd = NULL; - p->encoffset = PWDS_HEADER_SIZE; - p->isdecrypted = 0; - - if(pwdstore_getindex(p)) { - pwdstore_free(p); - return NULL; - } - - return p; -} - -PwdStore* pwdstore_new(void) { - PwdStore *p = calloc(1, sizeof(PwdStore)); - p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); - p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); - p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); - p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - PWDS_MAGIC(p) = PWDS_MAGIC_CHAR; - PWDS_VERSION(p) = 1; - PWDS_ENC(p) = DAV_KEY_AES256; - PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256; - dav_rand_bytes((unsigned char*)p->content->space+4, 16); - p->isdecrypted = 1; - p->encoffset = PWDS_HEADER_SIZE; - return p; -} - -static int readval(CxBuffer *in, char **val, int allowzero) { - // value = length string - // length = uint32 - // string = bytes - - *val = NULL; - - // get length - uint32_t length = 0; - if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) { - return 0; - } - length = ntohl(length); // convert from BE to host byte order - if(length == 0) { - if(allowzero) { - return 1; - } else { - return 0; - } - } - if(length > PWDSTORE_MAX_LEN) { - return 0; - } - - // get value - char *value = malloc(length + 1); - value[length] = 0; - if(cxBufferRead(value, 1, length, in) != length) { - free(value); - return 0; - } - - *val = value; - return 1; -} - -static int read_indexentry(PwdStore *p, CxBuffer *in) { - // read type of index element - int type = cxBufferGet(in); - if(type == EOF || type != 0) { - // only type 0 supported yet - return 0; - } - - char *id = NULL; - CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); - cxDefineDestructor(locations, free); - - // get id (required) - int ret = 0; - if(readval(in, &id, FALSE)) { - // get locations - char *location = NULL; - while((ret = readval(in, &location, TRUE)) == 1) { - if(!location) { - break; - } - cxListAdd(locations, location); - } - } - - if(ret) { - pwdstore_put_index(p, id, locations); - } else { - if(id) free(id); - cxListDestroy(locations); - } - - return ret; -} - -static int read_pwdentry(PwdStore *p, CxBuffer *in) { - int type = cxBufferGet(in); - if(type == EOF || type != 0) { - // only type 0 supported yet - return 0; - } - - char *id = NULL; - char *user = NULL; - char *password = NULL; - - int ret = 0; - if(readval(in, &id, FALSE)) { - if(readval(in, &user, FALSE)) { - if(readval(in, &password, FALSE)) { - pwdstore_put(p, id, user, password); - ret = 1; - } - } - } - - if(id) free(id); - if(user) free(user); - if(password) free(password); - - return ret; -} - -static void remove_list_entries(PwdStore *s, const char *id) { - CxIterator i = cxListMutIterator(s->locations); - cx_foreach(PwdIndexEntry*, ie, i) { - if(!strcmp(ie->id, id)) { - cxIteratorFlagRemoval(i); - cxIteratorNext(i); - break; - } - } - i = cxListMutIterator(s->noloc); - cx_foreach(PwdIndexEntry*, ie, i) { - if(!strcmp(ie->id, id)) { - cxIteratorFlagRemoval(i); - cxIteratorNext(i); - break; - } - } -} - -void pwdstore_remove_entry(PwdStore *s, const char *id) { - remove_list_entries(s, id); - - CxHashKey key = cx_hash_key_str(id); - PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key); - PwdEntry *e = cxMapRemoveAndGet(s->ids, key); - - if(i) { - cxListDestroy(i->locations); - free(i->id); - free(i); - } - if(e) { - free(e->id); - free(e->user); - free(e->password); - free(e); - } -} - -int pwdstore_getindex(PwdStore *s) { - uint32_t netindexlen; - - // set the position to the last 4 bytes of the header - // for reading index length - s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); - - // read indexlen and convert to host byte order - if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) { - return 1; - } - uint32_t indexlen = ntohl(netindexlen); - - // integer overflow check - if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) { - return 1; - } - if(s->content->size < PWDS_HEADER_SIZE + indexlen) { - return 1; - } - // encrypted content starts after the index content - s->encoffset = PWDS_HEADER_SIZE + indexlen; - - // the index starts after the header - CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0); - index->size = indexlen; - - // read index - while(read_indexentry(s, index)) {} - - // free index buffer structure (not the content) - cxBufferFree(index); - - return 0; -} - -int pwdstore_decrypt(PwdStore *p) { - if(!p->key) { - return 1; - } - if(p->isdecrypted) { - return 0; - } - - // decrypt contet - size_t encsz = p->content->size - p->encoffset; - CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0); - enc->size = encsz; - enc->size = p->content->size - p->encoffset; - CxBuffer *content = aes_decrypt_buffer(enc, p->key); - cxBufferFree(enc); - if(!content) { - return 1; - } - - while(read_pwdentry(p, content)) {} - - cxBufferFree(content); - - return 0; -} - -int pwdstore_setpassword(PwdStore *p, const char *password) { - DavKey *key = dav_pw2key( - password, - (unsigned char*)(p->content->space + 4), - 16, - PWDS_PWFUNC(p), - PWDS_ENC(p)); - if(!key) { - return 1; - } - - p->key = key; - return 0; -} - -void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) { - PWDS_ENC(p) = enc; - PWDS_PWFUNC(p) = pwfunc; -} - -void pwdstore_free_entry(PwdEntry *e) { - if(e->id) free(e->id); - if(e->user) free(e->user); - if(e->password) free(e->password); - free(e); -} - -void pwdstore_free(PwdStore* p) { - cxDefineDestructor(p->ids, pwdstore_free_entry); - cxMapDestroy(p->ids); - - cxListDestroy(p->locations); - - if(p->content) { - cxBufferFree(p->content); - } - - free(p); -} - -int pwdstore_has_id(PwdStore *s, const char *id) { - return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0; -} - -PwdEntry* pwdstore_get(PwdStore *p, const char *id) { - PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id)); - if(e && e->user && e->password) { - return e; - } else { - return NULL; - } -} - -void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) { - PwdEntry *entry = malloc(sizeof(PwdEntry)); - entry->id = strdup(id); - entry->user = strdup(username); - entry->password = strdup(password); - cxMapPut(p->ids, cx_hash_key_str(id), entry); -} - -void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) { - PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id)); - if(e) { - return; - } - PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry)); - newentry->id = id; - if(locations) { - newentry->locations = locations; - cxListAdd(p->locations, newentry); - } else { - newentry->locations = NULL; - cxListAdd(p->noloc, newentry); - } - cxMapPut(p->index, cx_hash_key_str(id), newentry); -} - -void write_index_entry(CxBuffer *out, PwdIndexEntry *e) { - uint32_t idlen = strlen(e->id); - uint32_t netidlen = htonl(idlen); - - cxBufferPut(out, 0); // type - - cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out); - cxBufferWrite(e->id, 1, idlen, out); - - CxIterator i = cxListIterator(e->locations); - cx_foreach(char *, location, i) { - uint32_t locationlen = strlen(location); - uint32_t netlocationlen = htonl(locationlen); - - cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out); - cxBufferWrite(location, 1, locationlen, out); - } - - uint32_t terminate = 0; - cxBufferWrite(&terminate, 1, sizeof(uint32_t), out); -} - -int pwdstore_store(PwdStore *p, const char *file) { - if(!p->key) { - return 1; - } - - CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); - - // create index - CxIterator i = cxListIterator(p->noloc); - cx_foreach(PwdIndexEntry*, e, i) { - write_index_entry(index, e); - } - i = cxListIterator(p->locations); - cx_foreach(PwdIndexEntry*, e, i) { - write_index_entry(index, e); - } - - i = cxMapIteratorValues(p->ids); - cx_foreach(PwdEntry*, value, i) { - if(!value->id || !value->user || !value->password) { - continue; - } - - uint32_t idlen = strlen(value->id); - uint32_t ulen = strlen(value->user); - uint32_t plen = strlen(value->password); - uint32_t netidlen = htonl(idlen); - uint32_t netulen = htonl(ulen); - uint32_t netplen = htonl(plen); - - // content buffer - cxBufferPut(content, 0); // type - - cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content); - cxBufferWrite(value->id, 1, idlen, content); - cxBufferWrite(&netulen, 1, sizeof(uint32_t), content); - cxBufferWrite(value->user, 1, ulen, content); - cxBufferWrite(&netplen, 1, sizeof(uint32_t), content); - cxBufferWrite(value->password, 1, plen, content); - } - - content->pos = 0; - CxBuffer *enc = aes_encrypt_buffer(content, p->key); - - p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); - p->content->size = PWDS_HEADER_SIZE; - - // add index after header - uint32_t netindexlen = htonl((uint32_t)index->size); - cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content); - cxBufferWrite(index->space, 1, index->size, p->content); - - // add encrypted buffer - cxBufferWrite(enc->space, 1, enc->size, p->content); - - cxBufferFree(enc); - - FILE *out = fopen(file, "w"); - if(!out) { - return 1; - } - fwrite(p->content->space, 1, p->content->size, out); - fclose(out); - - return 0; -} diff -r f2ee3a5c976e -r dff5f4c23aa7 dav/pwd.h --- a/dav/pwd.h Mon Oct 21 12:47:57 2024 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,197 +0,0 @@ -/* - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. - * - * Copyright 2018 Olaf Wintermann. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef PWD_H -#define PWD_H - -#include -#include - -#include -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define PWDSTORE_MAX_LEN 4096 - -/* - * File Format: - * - * file = header, index, enc_content - * header = magic, version, enc, pwfunc, salt, indexlen - * magic = 1 byte - * version = 1 byte - * enc = 1 byte - * pwfunc = 1 byte - * salt = 16 bytes - * indexlen = uint32 - * index = { itype length id locations zero } - * enc_content = iv bytes - * iv = 16 bytes - * content = { entry } - * entry = itype length id length username length password - * length = uint32 - * zero = 4 zero bytes - * itype = 1 byte - * id = string - * locations = { length string } - * username = string - * password = string - * - * The content is AES encrypted with a key derived from a password - * and the salt. The first 16 bytes are the aes iv. - * - * All integers are big endian - */ - -#define PWDS_HEADER_SIZE 24 - -typedef struct PwdStore PwdStore; -typedef struct PwdEntry PwdEntry; -typedef struct PwdIndexEntry PwdIndexEntry; - -struct PwdStore { - /* - * map of all credentials - * key is the username - * value is PwdEntry* - */ - CxMap *ids; - - /* - * list of all credentials with location - * value is PwdIndexEntry* - */ - CxList *locations; - - /* - * list of all credentials without location - * value is PwdIndexEntry* - */ - CxList *noloc; - - /* - * index map that contains all elements from the lists - * 'locations' and 'noloc' - */ - CxMap *index; - - /* - * a buffer containing the complete file content - */ - CxBuffer *content; - - /* - * key used for encryption/decryption - */ - DavKey *key; - - /* - * optional shell command, that is used for getting the master password - */ - char *unlock_cmd; - - /* - * optional shell command, that is exected when the secretstore is closed - */ - char *lock_cmd; - - /* - * start offset of the encrypted buffer - */ - uint32_t encoffset; - - /* - * indicates if the PwdStore is decrypted with pwdstore_decrypt - */ - uint8_t isdecrypted; -}; - -#define PWDS_MAGIC(p) (p)->content->space[0] -#define PWDS_VERSION(p) (p)->content->space[1] -#define PWDS_ENC(p) (p)->content->space[2] -#define PWDS_PWFUNC(p) (p)->content->space[3] - -#define PWDS_MAGIC_CHAR 'P' - -struct PwdEntry { - char *id; - char *user; - char *password; -}; - -struct PwdIndexEntry { - char *id; - CxList *locations; -}; - -/* - * opens the password store - * the content is still encrypted and must be decrypted using pwdstore_decrypt - */ -PwdStore* pwdstore_open(const char *file); - -PwdStore* pwdstore_new(void); - -/* - * decrypts the password store with a password - */ -int pwdstore_decrypt(PwdStore *p); - -int pwdstore_setpassword(PwdStore *p, const char *password); - -void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc); - -void pwdstore_free_entry(PwdEntry *e); -void pwdstore_free(PwdStore* p); - -int pwdstore_has_id(PwdStore *s, const char *id); -int pwdstore_has_location(PwdStore *s, const char *location); - -PwdEntry* pwdstore_get(PwdStore *p, const char *id); - -void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password); -void pwdstore_put_index(PwdStore *p, char *id, CxList *locations); - -void pwdstore_remove_entry(PwdStore *s, const char *id); - -int pwdstore_store(PwdStore *p, const char *file); - -/* private */ -int pwdstore_getindex(PwdStore *s); - -#ifdef __cplusplus -} -#endif - -#endif /* PWD_H */ - diff -r f2ee3a5c976e -r dff5f4c23aa7 libidav/Makefile --- a/libidav/Makefile Mon Oct 21 12:47:57 2024 +0200 +++ b/libidav/Makefile Wed Oct 23 09:46:33 2024 +0200 @@ -40,6 +40,7 @@ SRC += xml.c SRC += versioning.c SRC += config.c +SRC += pwdstore.c OBJ = $(SRC:%.c=../build/libidav/%$(OBJ_EXT)) diff -r f2ee3a5c976e -r dff5f4c23aa7 libidav/pwdstore.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/pwdstore.c Wed Oct 23 09:46:33 2024 +0200 @@ -0,0 +1,520 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "pwdstore.h" + +#include "utils.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#pragma comment(lib, "Ws2_32.lib") +#else +#include +#endif + + +static pwdstore_pwinput_func pw_input = (pwdstore_pwinput_func)pwdstore_default_pwinput; +static void *pw_input_data = "Master password: "; + +char * pwdstore_default_pwinput(char *prompt) { + return util_password_input(prompt); +} + +PwdStore* pwdstore_open(const char *file) { + FILE *in = fopen(file, "r"); + if(!in) { + return NULL; + } + + CxBuffer *buf = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + cx_stream_copy(in, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); + fclose(in); + + if(buf->size < PWDS_HEADER_SIZE || buf->space[0] != PWDS_MAGIC_CHAR) { + cxBufferFree(buf); + return NULL; + } + + PwdStore *p = malloc(sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = buf; + p->key = NULL; + p->unlock_cmd = NULL; + p->lock_cmd = NULL; + p->encoffset = PWDS_HEADER_SIZE; + p->isdecrypted = 0; + + if(pwdstore_getindex(p)) { + pwdstore_free(p); + return NULL; + } + + return p; +} + +PwdStore* pwdstore_new(void) { + PwdStore *p = calloc(1, sizeof(PwdStore)); + p->ids = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->noloc = cxLinkedListCreateSimple(CX_STORE_POINTERS); + p->index = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); + p->content = cxBufferCreate(NULL, PWDS_HEADER_SIZE, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + PWDS_MAGIC(p) = PWDS_MAGIC_CHAR; + PWDS_VERSION(p) = 1; + PWDS_ENC(p) = DAV_KEY_AES256; + PWDS_PWFUNC(p) = DAV_PWFUNC_PBKDF2_SHA256; + dav_rand_bytes((unsigned char*)p->content->space+4, 16); + p->isdecrypted = 1; + p->encoffset = PWDS_HEADER_SIZE; + return p; +} + +static int readval(CxBuffer *in, char **val, int allowzero) { + // value = length string + // length = uint32 + // string = bytes + + *val = NULL; + + // get length + uint32_t length = 0; + if(cxBufferRead(&length, 1, sizeof(uint32_t), in) != sizeof(uint32_t)) { + return 0; + } + length = ntohl(length); // convert from BE to host byte order + if(length == 0) { + if(allowzero) { + return 1; + } else { + return 0; + } + } + if(length > PWDSTORE_MAX_LEN) { + return 0; + } + + // get value + char *value = malloc(length + 1); + value[length] = 0; + if(cxBufferRead(value, 1, length, in) != length) { + free(value); + return 0; + } + + *val = value; + return 1; +} + +static int read_indexentry(PwdStore *p, CxBuffer *in) { + // read type of index element + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + CxList *locations = cxLinkedListCreateSimple(CX_STORE_POINTERS); + cxDefineDestructor(locations, free); + + // get id (required) + int ret = 0; + if(readval(in, &id, FALSE)) { + // get locations + char *location = NULL; + while((ret = readval(in, &location, TRUE)) == 1) { + if(!location) { + break; + } + cxListAdd(locations, location); + } + } + + if(ret) { + pwdstore_put_index(p, id, locations); + } else { + if(id) free(id); + cxListDestroy(locations); + } + + return ret; +} + +static int read_pwdentry(PwdStore *p, CxBuffer *in) { + int type = cxBufferGet(in); + if(type == EOF || type != 0) { + // only type 0 supported yet + return 0; + } + + char *id = NULL; + char *user = NULL; + char *password = NULL; + + int ret = 0; + if(readval(in, &id, FALSE)) { + if(readval(in, &user, FALSE)) { + if(readval(in, &password, FALSE)) { + pwdstore_put(p, id, user, password); + ret = 1; + } + } + } + + if(id) free(id); + if(user) free(user); + if(password) free(password); + + return ret; +} + +static void remove_list_entries(PwdStore *s, const char *id) { + CxIterator i = cxListMutIterator(s->locations); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + cxIteratorNext(i); + break; + } + } + i = cxListMutIterator(s->noloc); + cx_foreach(PwdIndexEntry*, ie, i) { + if(!strcmp(ie->id, id)) { + cxIteratorFlagRemoval(i); + cxIteratorNext(i); + break; + } + } +} + +void pwdstore_remove_entry(PwdStore *s, const char *id) { + remove_list_entries(s, id); + + CxHashKey key = cx_hash_key_str(id); + PwdIndexEntry *i = cxMapRemoveAndGet(s->index, key); + PwdEntry *e = cxMapRemoveAndGet(s->ids, key); + + if(i) { + cxListDestroy(i->locations); + free(i->id); + free(i); + } + if(e) { + free(e->id); + free(e->user); + free(e->password); + free(e); + } +} + +int pwdstore_getindex(PwdStore *s) { + uint32_t netindexlen; + + // set the position to the last 4 bytes of the header + // for reading index length + s->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); + + // read indexlen and convert to host byte order + if(cxBufferRead(&netindexlen, 1, sizeof(uint32_t), s->content) != sizeof(uint32_t)) { + return 1; + } + uint32_t indexlen = ntohl(netindexlen); + + // integer overflow check + if(UINT32_MAX - PWDS_HEADER_SIZE < indexlen) { + return 1; + } + if(s->content->size < PWDS_HEADER_SIZE + indexlen) { + return 1; + } + // encrypted content starts after the index content + s->encoffset = PWDS_HEADER_SIZE + indexlen; + + // the index starts after the header + CxBuffer *index = cxBufferCreate(s->content->space+PWDS_HEADER_SIZE, indexlen, cxDefaultAllocator, 0); + index->size = indexlen; + + // read index + while(read_indexentry(s, index)) {} + + // free index buffer structure (not the content) + cxBufferFree(index); + + return 0; +} + +int pwdstore_decrypt(PwdStore *p) { + if(!p->key) { + return 1; + } + if(p->isdecrypted) { + return 0; + } + + // decrypt contet + size_t encsz = p->content->size - p->encoffset; + CxBuffer *enc = cxBufferCreate(p->content->space + p->encoffset, encsz, cxDefaultAllocator, 0); + enc->size = encsz; + enc->size = p->content->size - p->encoffset; + CxBuffer *content = aes_decrypt_buffer(enc, p->key); + cxBufferFree(enc); + if(!content) { + return 1; + } + + while(read_pwdentry(p, content)) {} + + cxBufferFree(content); + + return 0; +} + +int pwdstore_setpassword(PwdStore *p, const char *password) { + DavKey *key = dav_pw2key( + password, + (unsigned char*)(p->content->space + 4), + 16, + PWDS_PWFUNC(p), + PWDS_ENC(p)); + if(!key) { + return 1; + } + + p->key = key; + return 0; +} + +void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc) { + PWDS_ENC(p) = enc; + PWDS_PWFUNC(p) = pwfunc; +} + +void pwdstore_free_entry(PwdEntry *e) { + if(e->id) free(e->id); + if(e->user) free(e->user); + if(e->password) free(e->password); + free(e); +} + +void pwdstore_free(PwdStore* p) { + cxDefineDestructor(p->ids, pwdstore_free_entry); + cxMapDestroy(p->ids); + + cxListDestroy(p->locations); + + if(p->content) { + cxBufferFree(p->content); + } + + free(p); +} + +int pwdstore_has_id(PwdStore *s, const char *id) { + return cxMapGet(s->index, cx_hash_key_str(id)) ? 1 : 0; +} + +PwdEntry* pwdstore_get(PwdStore *p, const char *id) { + PwdEntry *e = cxMapGet(p->ids, cx_hash_key_str(id)); + if(e && e->user && e->password) { + return e; + } else { + return NULL; + } +} + +void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password) { + PwdEntry *entry = malloc(sizeof(PwdEntry)); + entry->id = strdup(id); + entry->user = strdup(username); + entry->password = strdup(password); + cxMapPut(p->ids, cx_hash_key_str(id), entry); +} + +void pwdstore_put_index(PwdStore *p, char *id, CxList *locations) { + PwdIndexEntry *e = cxMapGet(p->index, cx_hash_key_str(id)); + if(e) { + return; + } + PwdIndexEntry *newentry = malloc(sizeof(PwdIndexEntry)); + newentry->id = id; + if(locations) { + newentry->locations = locations; + cxListAdd(p->locations, newentry); + } else { + newentry->locations = NULL; + cxListAdd(p->noloc, newentry); + } + cxMapPut(p->index, cx_hash_key_str(id), newentry); +} + +void write_index_entry(CxBuffer *out, PwdIndexEntry *e) { + uint32_t idlen = strlen(e->id); + uint32_t netidlen = htonl(idlen); + + cxBufferPut(out, 0); // type + + cxBufferWrite(&netidlen, 1, sizeof(uint32_t), out); + cxBufferWrite(e->id, 1, idlen, out); + + CxIterator i = cxListIterator(e->locations); + cx_foreach(char *, location, i) { + uint32_t locationlen = strlen(location); + uint32_t netlocationlen = htonl(locationlen); + + cxBufferWrite(&netlocationlen, 1, sizeof(uint32_t), out); + cxBufferWrite(location, 1, locationlen, out); + } + + uint32_t terminate = 0; + cxBufferWrite(&terminate, 1, sizeof(uint32_t), out); +} + +int pwdstore_store(PwdStore *p, const char *file) { + if(!p->key) { + return 1; + } + + CxBuffer *index = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + CxBuffer *content = cxBufferCreate(NULL, 2048, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + + // create index + CxIterator i = cxListIterator(p->noloc); + cx_foreach(PwdIndexEntry*, e, i) { + write_index_entry(index, e); + } + i = cxListIterator(p->locations); + cx_foreach(PwdIndexEntry*, e, i) { + write_index_entry(index, e); + } + + i = cxMapIteratorValues(p->ids); + cx_foreach(PwdEntry*, value, i) { + if(!value->id || !value->user || !value->password) { + continue; + } + + uint32_t idlen = strlen(value->id); + uint32_t ulen = strlen(value->user); + uint32_t plen = strlen(value->password); + uint32_t netidlen = htonl(idlen); + uint32_t netulen = htonl(ulen); + uint32_t netplen = htonl(plen); + + // content buffer + cxBufferPut(content, 0); // type + + cxBufferWrite(&netidlen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->id, 1, idlen, content); + cxBufferWrite(&netulen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->user, 1, ulen, content); + cxBufferWrite(&netplen, 1, sizeof(uint32_t), content); + cxBufferWrite(value->password, 1, plen, content); + } + + content->pos = 0; + CxBuffer *enc = aes_encrypt_buffer(content, p->key); + + p->content->pos = PWDS_HEADER_SIZE - sizeof(uint32_t); + p->content->size = PWDS_HEADER_SIZE; + + // add index after header + uint32_t netindexlen = htonl((uint32_t)index->size); + cxBufferWrite(&netindexlen, 1, sizeof(uint32_t), p->content); + cxBufferWrite(index->space, 1, index->size, p->content); + + // add encrypted buffer + cxBufferWrite(enc->space, 1, enc->size, p->content); + + cxBufferFree(enc); + + FILE *out = fopen(file, "w"); + if(!out) { + return 1; + } + fwrite(p->content->space, 1, p->content->size, out); + fclose(out); + + return 0; +} + +int pwdstore_decrypt_secrets(PwdStore *secrets) { + if(!pw_input) { + return 1; + } + + char *ps_password = NULL; + if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) { + CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); + 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; + } + } + cxBufferFree(cmd_out); + } + + if(!ps_password) { + ps_password = pw_input(pw_input_data); + if(!ps_password) { + return 1; + } + } + + int err = pwdstore_setpassword(secrets, ps_password); + free(ps_password); + if(err) { + 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; +} diff -r f2ee3a5c976e -r dff5f4c23aa7 libidav/pwdstore.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libidav/pwdstore.h Wed Oct 23 09:46:33 2024 +0200 @@ -0,0 +1,217 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef LIBIDAV_PWDSTORE_H +#define LIBIDAV_PWDSTORE_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include +#include +#include +#include "crypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define PWDSTORE_MAX_LEN 4096 + +/* + * File Format: + * + * file = header, index, enc_content + * header = magic, version, enc, pwfunc, salt, indexlen + * magic = 1 byte + * version = 1 byte + * enc = 1 byte + * pwfunc = 1 byte + * salt = 16 bytes + * indexlen = uint32 + * index = { itype length id locations zero } + * enc_content = iv bytes + * iv = 16 bytes + * content = { entry } + * entry = itype length id length username length password + * length = uint32 + * zero = 4 zero bytes + * itype = 1 byte + * id = string + * locations = { length string } + * username = string + * password = string + * + * The content is AES encrypted with a key derived from a password + * and the salt. The first 16 bytes are the aes iv. + * + * All integers are big endian + */ + +#define PWDS_HEADER_SIZE 24 + +typedef struct PwdStore PwdStore; +typedef struct PwdEntry PwdEntry; +typedef struct PwdIndexEntry PwdIndexEntry; + +struct PwdStore { + /* + * map of all credentials + * key is the username + * value is PwdEntry* + */ + CxMap *ids; + + /* + * list of all credentials with location + * value is PwdIndexEntry* + */ + CxList *locations; + + /* + * list of all credentials without location + * value is PwdIndexEntry* + */ + CxList *noloc; + + /* + * index map that contains all elements from the lists + * 'locations' and 'noloc' + */ + CxMap *index; + + /* + * a buffer containing the complete file content + */ + CxBuffer *content; + + /* + * key used for encryption/decryption + */ + DavKey *key; + + /* + * optional shell command, that is used for getting the master password + */ + char *unlock_cmd; + + /* + * optional shell command, that is exected when the secretstore is closed + */ + char *lock_cmd; + + /* + * start offset of the encrypted buffer + */ + uint32_t encoffset; + + /* + * indicates if the PwdStore is decrypted with pwdstore_decrypt + */ + uint8_t isdecrypted; +}; + +#define PWDS_MAGIC(p) (p)->content->space[0] +#define PWDS_VERSION(p) (p)->content->space[1] +#define PWDS_ENC(p) (p)->content->space[2] +#define PWDS_PWFUNC(p) (p)->content->space[3] + +#define PWDS_MAGIC_CHAR 'P' + +struct PwdEntry { + char *id; + char *user; + char *password; +}; + +struct PwdIndexEntry { + char *id; + CxList *locations; +}; + +/* + * opens the password store + * the content is still encrypted and must be decrypted using pwdstore_decrypt + */ +PwdStore* pwdstore_open(const char *file); + +PwdStore* pwdstore_new(void); + +/* + * decrypts the password store with the previously set password + */ +int pwdstore_decrypt(PwdStore *p); + +int pwdstore_setpassword(PwdStore *p, const char *password); + +void pwdstore_encsettings(PwdStore *p, uint8_t enc, uint8_t pwfunc); + +void pwdstore_free_entry(PwdEntry *e); +void pwdstore_free(PwdStore* p); + +int pwdstore_has_id(PwdStore *s, const char *id); +int pwdstore_has_location(PwdStore *s, const char *location); + +PwdEntry* pwdstore_get(PwdStore *p, const char *id); + +void pwdstore_put(PwdStore *p, const char *id, const char *username, const char *password); +void pwdstore_put_index(PwdStore *p, char *id, CxList *locations); + +void pwdstore_remove_entry(PwdStore *s, const char *id); + +int pwdstore_store(PwdStore *p, const char *file); + + +int pwdstore_decrypt_secrets(PwdStore *secrets); + + + + + + +typedef char*(*pwdstore_pwinput_func)(void *userdata); + +void pwdstore_set_pwinput_func(pwdstore_pwinput_func func, void *userdata); + +char * pwdstore_default_pwinput(char *prompt); + + + +/* private */ +int pwdstore_getindex(PwdStore *s); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBIDAV_PWDSTORE_H */ +