UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2018 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include <stdio.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <sys/types.h> 33 #include <cx/hash_map.h> 34 #include <cx/utils.h> 35 #include <errno.h> 36 #include <libxml/tree.h> 37 38 #include "config.h" 39 #include "system.h" 40 41 #include <libidav/utils.h> 42 #include <libidav/config.h> 43 #include <libidav/pwdstore.h> 44 45 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) 46 #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) 47 48 #define print_error(lineno, ...) \ 49 do {\ 50 fprintf(stderr, "Error (config.xml line %u): ", lineno); \ 51 fprintf(stderr, __VA_ARGS__); \ 52 fprintf(stderr, "Abort.\n"); \ 53 } while(0); 54 #define print_warning(lineno, ...) \ 55 do {\ 56 fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ 57 fprintf(stderr, __VA_ARGS__); \ 58 } while(0); 59 60 #ifdef _WIN32 61 #define ENV_HOME getenv("USERPROFILE") 62 #else 63 #define ENV_HOME getenv("HOME") 64 #endif /* _WIN32 */ 65 66 static CxMap *repos; 67 static CxMap *keys; 68 69 static DavConfig *davconfig; 70 static PwdStore *pstore; 71 72 static char *secretstore_unlock_cmd; 73 static char *secretstore_lock_cmd; 74 75 int check_config_dir(void) { 76 char *file = util_concat_path(ENV_HOME, ".dav"); 77 int ret = 0; 78 if(util_mkdir(file, S_IRWXU)) { 79 if(errno != EEXIST) { 80 ret = 1; 81 } 82 } 83 free(file); 84 return ret; 85 } 86 87 static DavContext *context; 88 89 void create_default_config(char *file) { 90 xmlDoc *doc = xmlNewDoc(BAD_CAST "1.0"); 91 xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); 92 xmlDocSetRootElement(doc, root); 93 xmlSaveFormatFileEnc(file, doc, "UTF-8", 1); 94 xmlFreeDoc(doc); 95 } 96 97 char* config_file_path(char *name) { 98 char *davd = util_concat_path(ENV_HOME, ".dav"); 99 if(!davd) { 100 return NULL; 101 } 102 char *path = util_concat_path(davd, name); 103 free(davd); 104 return path; 105 } 106 107 cxmutstr config_load_file(const char *path) { 108 FILE *file = sys_fopen(path, "r"); 109 if(!file) { 110 return (cxmutstr){NULL,0}; 111 } 112 113 CxBuffer buf; 114 cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); 115 cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); 116 fclose(file); 117 118 return cx_mutstrn(buf.space, buf.size); 119 } 120 121 int load_config(DavContext *ctx) { 122 context = ctx; 123 // TODO: free the config somewhere 124 repos = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 125 keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 126 127 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 128 pstore = pwdstore_open(pwfile); 129 free(pwfile); 130 131 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 132 133 struct stat s; 134 if(stat(file, &s)) { 135 switch(errno) { 136 case ENOENT: { 137 davconfig = dav_config_new(NULL); 138 return 0; 139 } 140 default: { 141 perror("Cannot load config.xml"); 142 } 143 } 144 return 1; 145 } 146 147 cxmutstr config_content = config_load_file(file); 148 int config_error; 149 davconfig = dav_config_load(config_content, &config_error); 150 free(config_content.ptr); 151 free(file); 152 153 if(!davconfig) { 154 fprintf(stderr, "Cannot load config.xml\n"); 155 return 1; 156 } 157 158 if(dav_config_register_namespaces(davconfig, ctx)) { 159 return 1; 160 } 161 162 return dav_config_register_keys(davconfig, ctx, load_key_file); 163 } 164 165 DavConfig* get_config(void) { 166 return davconfig; 167 } 168 169 int store_config(void) { 170 if(check_config_dir()) { 171 return 1; 172 } 173 174 CxBuffer *buf = dav_config2buf(davconfig); 175 if(!buf) { 176 return 1; 177 } 178 179 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 180 FILE *cout = sys_fopen(file, "w"); 181 if(!cout) { 182 cxBufferFree(buf); 183 return 1; 184 } 185 186 // should only fail if we run out of disk space or something like that 187 // in that case, the config file is only destroyed 188 // could only be prevented, if we write to a temp file first and than 189 // rename it 190 fwrite(buf->space, buf->size, 1, cout); 191 192 cxBufferFree(buf); 193 fclose(cout); 194 195 return 0; 196 } 197 198 void free_config(void) { 199 if(davconfig) { 200 dav_config_free(davconfig); 201 } 202 } 203 204 cxmutstr load_key_file(const char *filename) { 205 cxmutstr k; 206 k.ptr = NULL; 207 k.length = 0; 208 209 FILE *file = NULL; 210 if(filename[0] == '/') { 211 file = sys_fopen(filename, "r"); 212 } else { 213 char *path = util_concat_path(ENV_HOME, ".dav/"); 214 char *p2 = util_concat_path(path, filename); 215 file = sys_fopen(p2, "r"); 216 free(path); 217 free(p2); 218 } 219 220 if(!file) { 221 fprintf(stderr, "Error: cannot load keyfile %s\n", filename); 222 return k; 223 } 224 225 char *data = malloc(256); 226 size_t r = fread(data, 1, 256, file); 227 k.ptr = data; 228 k.length = r; 229 230 fclose(file); 231 return k; 232 } 233 234 PwdStore* get_pwdstore(void) { 235 return pstore; 236 } 237 238 int pwdstore_save(PwdStore *pwdstore) { 239 if(check_config_dir()) { 240 return 1; 241 } 242 243 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 244 int ret = pwdstore_store(pwdstore, pwfile); 245 free(pwfile); 246 return ret; 247 } 248 249 typedef struct CredLocation { 250 char *id; 251 char *location; 252 } CredLocation; 253 254 static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) { 255 return strcmp(e2->location, e1->location); 256 } 257 258 static void free_cred_location(CredLocation *c) { 259 // c->id is not a copy, therefore we don't have to free it 260 free(c->location); 261 free(c); 262 } 263 264 int get_stored_credentials(char *credid, char **user, char **password) { 265 if(!credid) { 266 return 0; 267 } 268 269 PwdStore *secrets = get_pwdstore(); 270 if(!secrets) { 271 fprintf(stderr, "Error: no secrets store available\n"); 272 return 0; 273 } 274 275 if(pwdstore_has_id(secrets, credid)) { 276 if(!secrets->isdecrypted) { 277 if(pwdstore_decrypt_secrets(secrets)) { 278 return 0; 279 } 280 } 281 282 PwdEntry *s_cred = pwdstore_get(secrets, credid); 283 if(s_cred) { 284 *user = s_cred->user; 285 *password = s_cred->password; 286 return 1; 287 } 288 } else { 289 fprintf(stderr, "Error: credentials id ''%s'' not found\n", credid); 290 } 291 292 return 0; 293 } 294 295 296 int get_location_credentials(DavCfgRepository *repo, const char *path, char **user, char **password) { 297 PwdStore *secrets = get_pwdstore(); 298 if(!secrets) { 299 return 0; 300 } 301 302 /* 303 * The list secrets->location contains urls or repo names as 304 * location strings. We need a list, that contains only urls 305 */ 306 CxList *locations = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)cmp_url_cred_entry, CX_STORE_POINTERS); 307 cxDefineDestructor(locations, free_cred_location); 308 CxIterator i = cxListIterator(secrets->locations); 309 cx_foreach(PwdIndexEntry*, e, i) { 310 CxIterator entry_iter = cxListIterator(e->locations); 311 cx_foreach(char *, loc, entry_iter) { 312 cxmutstr rpath; 313 DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath); 314 CredLocation *urlentry = calloc(1, sizeof(CredLocation)); 315 urlentry->id = e->id; 316 urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr; 317 cxListAdd(locations, urlentry); 318 free(rpath.ptr); 319 } 320 } 321 // the list must be sorted 322 cxListSort(locations); 323 324 // create full request url string and remove protocol prefix 325 cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path)); 326 cxstring req_url = cx_strcast(req_url_proto); 327 if(cx_strprefix(req_url, CX_STR("http://"))) { 328 req_url = cx_strsubs(req_url, 7); 329 } else if(cx_strprefix(req_url, CX_STR("https://"))) { 330 req_url = cx_strsubs(req_url, 8); 331 } 332 333 // iterate over sorted locations and check if a location is a prefix 334 // of the requested url 335 char *id = NULL; 336 int ret = 0; 337 i = cxListIterator(locations); 338 cx_foreach(CredLocation*, cred, i) { 339 cxstring cred_url = cx_str(cred->location); 340 341 // remove protocol prefix 342 if(cx_strprefix(cred_url, CX_STR("http://"))) { 343 cred_url = cx_strsubs(cred_url, 7); 344 } else if(cx_strprefix(cred_url, CX_STR("https://"))) { 345 cred_url = cx_strsubs(cred_url, 8); 346 } 347 348 if(cx_strprefix(req_url, cred_url)) { 349 id = cred->id; 350 break; 351 } 352 } 353 354 // if an id is found and we can access the decrypted secret store 355 // we can set the user/password 356 if(id && (secrets->isdecrypted || !pwdstore_decrypt_secrets(secrets))) { 357 PwdEntry *cred = pwdstore_get(secrets, id); 358 if(cred) { 359 *user = cred->user; 360 *password = cred->password; 361 ret = 1; 362 } 363 } 364 365 free(req_url_proto.ptr); 366 cxListDestroy(locations); 367 368 return ret; 369 } 370