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 44 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) 45 #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) 46 47 #define print_error(lineno, ...) \ 48 do {\ 49 fprintf(stderr, "Error (config.xml line %u): ", lineno); \ 50 fprintf(stderr, __VA_ARGS__); \ 51 fprintf(stderr, "Abort.\n"); \ 52 } while(0); 53 #define print_warning(lineno, ...) \ 54 do {\ 55 fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ 56 fprintf(stderr, __VA_ARGS__); \ 57 } while(0); 58 59 #ifdef _WIN32 60 #define ENV_HOME getenv("USERPROFILE") 61 #else 62 #define ENV_HOME getenv("HOME") 63 #endif /* _WIN32 */ 64 65 static CxMap *repos; 66 static CxMap *keys; 67 68 static DavConfig *davconfig; 69 static PwdStore *pstore; 70 71 static char *secretstore_unlock_cmd; 72 static char *secretstore_lock_cmd; 73 74 int check_config_dir(void) { 75 char *file = util_concat_path(ENV_HOME, ".dav"); 76 int ret = 0; 77 if(util_mkdir(file, S_IRWXU)) { 78 if(errno != EEXIST) { 79 ret = 1; 80 } 81 } 82 free(file); 83 return ret; 84 } 85 86 static DavContext *context; 87 88 void create_default_config(char *file) { 89 xmlDoc *doc = xmlNewDoc(BAD_CAST "1.0"); 90 xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); 91 xmlDocSetRootElement(doc, root); 92 xmlSaveFormatFileEnc(file, doc, "UTF-8", 1); 93 xmlFreeDoc(doc); 94 } 95 96 char* config_file_path(char *name) { 97 char *davd = util_concat_path(ENV_HOME, ".dav"); 98 if(!davd) { 99 return NULL; 100 } 101 char *path = util_concat_path(davd, name); 102 free(davd); 103 return path; 104 } 105 106 cxmutstr config_load_file(const char *path) { 107 FILE *file = sys_fopen(path, "r"); 108 if(!file) { 109 return (cxmutstr){NULL,0}; 110 } 111 112 CxBuffer buf; 113 cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); 114 cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); 115 fclose(file); 116 117 return cx_mutstrn(buf.space, buf.size); 118 } 119 120 int load_config(DavContext *ctx) { 121 context = ctx; 122 // TODO: free the config somewhere 123 repos = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 124 keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 125 126 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 127 pstore = pwdstore_open(pwfile); 128 free(pwfile); 129 130 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 131 132 struct stat s; 133 if(stat(file, &s)) { 134 switch(errno) { 135 case ENOENT: { 136 davconfig = dav_config_new(NULL); 137 return 0; 138 } 139 default: { 140 perror("Cannot load config.xml"); 141 } 142 } 143 return 1; 144 } 145 146 cxmutstr config_content = config_load_file(file); 147 int config_error; 148 davconfig = dav_config_load(config_content, &config_error); 149 free(config_content.ptr); 150 free(file); 151 152 if(!davconfig) { 153 fprintf(stderr, "Cannot load config.xml\n"); 154 return 1; 155 } 156 157 if(dav_config_register_namespaces(davconfig, ctx)) { 158 return 1; 159 } 160 161 return dav_config_register_keys(davconfig, ctx, load_key_file); 162 } 163 164 DavConfig* load_config_file(void) { 165 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 166 167 struct stat s; 168 if(stat(file, &s)) { 169 switch(errno) { 170 case ENOENT: { 171 davconfig = dav_config_new(NULL); 172 return NULL; 173 } 174 default: { 175 perror("Cannot load config.xml"); 176 } 177 } 178 return NULL; 179 } 180 181 cxmutstr config_content = config_load_file(file); 182 int config_error; 183 DavConfig *cfg = dav_config_load(config_content, &config_error); 184 free(config_content.ptr); 185 free(file); 186 return cfg; 187 } 188 189 190 void set_config(DavConfig *cfg) { 191 if(davconfig) { 192 dav_config_free(davconfig); 193 } 194 davconfig = cfg; 195 } 196 197 DavConfig* get_config(void) { 198 return davconfig; 199 } 200 201 int store_config(void) { 202 if(check_config_dir()) { 203 return 1; 204 } 205 206 CxBuffer *buf = dav_config2buf(davconfig); 207 if(!buf) { 208 return 1; 209 } 210 211 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 212 FILE *cout = sys_fopen(file, "w"); 213 if(!cout) { 214 cxBufferFree(buf); 215 return 1; 216 } 217 218 // should only fail if we run out of disk space or something like that 219 // in that case, the config file is destroyed 220 // could only be prevented, if we write to a temp file first and than 221 // rename it 222 fwrite(buf->space, buf->size, 1, cout); 223 224 cxBufferFree(buf); 225 fclose(cout); 226 227 return 0; 228 } 229 230 void free_config(void) { 231 if(davconfig) { 232 dav_config_free(davconfig); 233 } 234 } 235 236 cxmutstr load_key_file(const char *filename) { 237 cxmutstr k; 238 k.ptr = NULL; 239 k.length = 0; 240 241 FILE *file = NULL; 242 if(filename[0] == '/') { 243 file = sys_fopen(filename, "r"); 244 } else { 245 char *path = util_concat_path(ENV_HOME, ".dav/"); 246 char *p2 = util_concat_path(path, filename); 247 file = sys_fopen(p2, "r"); 248 free(path); 249 free(p2); 250 } 251 252 if(!file) { 253 fprintf(stderr, "Error: cannot load keyfile %s\n", filename); 254 return k; 255 } 256 257 char *data = malloc(256); 258 size_t r = fread(data, 1, 256, file); 259 k.ptr = data; 260 k.length = r; 261 262 fclose(file); 263 return k; 264 } 265 266 PwdStore* get_pwdstore(void) { 267 return pstore; 268 } 269 270 void set_pwdstore(PwdStore *newstore) { 271 if(pstore) { 272 // TODO: free 273 } 274 pstore = newstore; 275 } 276 277 int pwdstore_save(PwdStore *pwdstore) { 278 if(check_config_dir()) { 279 return 1; 280 } 281 282 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 283 int ret = pwdstore_store(pwdstore, pwfile); 284 free(pwfile); 285 return ret; 286 } 287 288 typedef struct CredLocation { 289 char *id; 290 char *location; 291 } CredLocation; 292 293 static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) { 294 return strcmp(e2->location, e1->location); 295 } 296 297 static void free_cred_location(CredLocation *c) { 298 // c->id is not a copy, therefore we don't have to free it 299 free(c->location); 300 free(c); 301 } 302 303 int get_stored_credentials(const char *credid, char **user, char **password) { 304 if(!credid) { 305 return 0; 306 } 307 308 PwdStore *secrets = get_pwdstore(); 309 if(!secrets) { 310 fprintf(stderr, "Error: no secrets store available\n"); 311 return 0; 312 } 313 314 if(pwdstore_has_id(secrets, credid)) { 315 if(!secrets->isdecrypted) { 316 if(pwdstore_decrypt_secrets(secrets)) { 317 return 0; 318 } 319 } 320 321 PwdEntry *s_cred = pwdstore_get(secrets, credid); 322 if(s_cred) { 323 *user = s_cred->user; 324 *password = s_cred->password; 325 return 1; 326 } 327 } else { 328 fprintf(stderr, "Error: credentials id ''%s'' not found\n", credid); 329 } 330 331 return 0; 332 } 333 334 335 const char* get_location_credentials(DavCfgRepository *repo, const char *path) { 336 PwdStore *secrets = get_pwdstore(); 337 if(!secrets) { 338 return NULL; 339 } 340 341 /* 342 * The list secrets->location contains urls or repo names as 343 * location strings. We need a list, that contains only urls 344 */ 345 CxList *locations = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)cmp_url_cred_entry, CX_STORE_POINTERS); 346 cxDefineDestructor(locations, free_cred_location); 347 CxIterator i = cxListIterator(secrets->locations); 348 cx_foreach(PwdIndexEntry*, e, i) { 349 CxIterator entry_iter = cxListIterator(e->locations); 350 cx_foreach(char *, loc, entry_iter) { 351 cxmutstr rpath; 352 DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath); 353 CredLocation *urlentry = calloc(1, sizeof(CredLocation)); 354 urlentry->id = e->id; 355 urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr; 356 cxListAdd(locations, urlentry); 357 free(rpath.ptr); 358 } 359 } 360 // the list must be sorted 361 cxListSort(locations); 362 363 // create full request url string and remove protocol prefix 364 cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path)); 365 cxstring req_url = cx_strcast(req_url_proto); 366 if(cx_strprefix(req_url, CX_STR("http://"))) { 367 req_url = cx_strsubs(req_url, 7); 368 } else if(cx_strprefix(req_url, CX_STR("https://"))) { 369 req_url = cx_strsubs(req_url, 8); 370 } 371 372 // iterate over sorted locations and check if a location is a prefix 373 // of the requested url 374 const char *id = NULL; 375 i = cxListIterator(locations); 376 cx_foreach(CredLocation*, cred, i) { 377 cxstring cred_url = cx_str(cred->location); 378 379 // remove protocol prefix 380 if(cx_strprefix(cred_url, CX_STR("http://"))) { 381 cred_url = cx_strsubs(cred_url, 7); 382 } else if(cx_strprefix(cred_url, CX_STR("https://"))) { 383 cred_url = cx_strsubs(cred_url, 8); 384 } 385 386 if(cx_strprefix(req_url, cred_url)) { 387 id = cred->id; 388 break; 389 } 390 } 391 392 free(req_url_proto.ptr); 393 cxListDestroy(locations); 394 395 return id; 396 } 397