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 "pwd.h" 39 #include "config.h" 40 #include "main.h" 41 #include "pwd.h" 42 #include "system.h" 43 44 #include <libidav/utils.h> 45 #include <libidav/config.h> 46 47 #define xstreq(a,b) xmlStrEqual(BAD_CAST a, BAD_CAST b) 48 #define xstrEQ(a,b) !xmlStrcasecmp(BAD_CAST a, BAD_CAST b) 49 50 #define print_error(lineno, ...) \ 51 do {\ 52 fprintf(stderr, "Error (config.xml line %u): ", lineno); \ 53 fprintf(stderr, __VA_ARGS__); \ 54 fprintf(stderr, "Abort.\n"); \ 55 } while(0); 56 #define print_warning(lineno, ...) \ 57 do {\ 58 fprintf(stderr, "Warning (config.xml line %u): ", lineno); \ 59 fprintf(stderr, __VA_ARGS__); \ 60 } while(0); 61 62 #ifdef _WIN32 63 #define ENV_HOME getenv("USERPROFILE") 64 #else 65 #define ENV_HOME getenv("HOME") 66 #endif /* _WIN32 */ 67 68 static CxMap *repos; 69 static CxMap *keys; 70 71 static DavConfig *davconfig; 72 static PwdStore *pstore; 73 74 static char *secretstore_unlock_cmd; 75 static char *secretstore_lock_cmd; 76 77 int check_config_dir(void) { 78 char *file = util_concat_path(ENV_HOME, ".dav"); 79 int ret = 0; 80 if(util_mkdir(file, S_IRWXU)) { 81 if(errno != EEXIST) { 82 ret = 1; 83 } 84 } 85 free(file); 86 return ret; 87 } 88 89 static DavContext *context; 90 91 void create_default_config(char *file) { 92 xmlDoc *doc = xmlNewDoc(BAD_CAST "1.0"); 93 xmlNode *root = xmlNewNode(NULL, BAD_CAST "configuration"); 94 xmlDocSetRootElement(doc, root); 95 xmlSaveFormatFileEnc(file, doc, "UTF-8", 1); 96 xmlFreeDoc(doc); 97 } 98 99 char* config_file_path(char *name) { 100 char *davd = util_concat_path(ENV_HOME, ".dav"); 101 if(!davd) { 102 return NULL; 103 } 104 char *path = util_concat_path(davd, name); 105 free(davd); 106 return path; 107 } 108 109 cxmutstr config_load_file(const char *path) { 110 FILE *file = sys_fopen(path, "r"); 111 if(!file) { 112 return (cxmutstr){NULL,0}; 113 } 114 115 CxBuffer buf; 116 cxBufferInit(&buf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND); 117 cx_stream_copy(file, &buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); 118 fclose(file); 119 120 return cx_mutstrn(buf.space, buf.size); 121 } 122 123 int load_config(DavContext *ctx) { 124 context = ctx; 125 // TODO: free the config somewhere 126 repos = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 127 keys = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); 128 129 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 130 pstore = pwdstore_open(pwfile); 131 free(pwfile); 132 133 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 134 135 struct stat s; 136 if(stat(file, &s)) { 137 switch(errno) { 138 case ENOENT: { 139 return 0; 140 } 141 default: { 142 perror("Cannot load config.xml"); 143 } 144 } 145 return 1; 146 } 147 148 cxmutstr config_content = config_load_file(file); 149 int config_error; 150 davconfig = dav_config_load(config_content, &config_error); 151 free(config_content.ptr); 152 free(file); 153 154 if(!davconfig) { 155 fprintf(stderr, "Cannot load config.xml\n"); 156 return 1; 157 } 158 159 return dav_config_register_keys(davconfig, ctx, load_key_file); 160 } 161 162 DavConfig* get_config(void) { 163 return davconfig; 164 } 165 166 int store_config(void) { 167 if(check_config_dir()) { 168 return 1; 169 } 170 171 CxBuffer *buf = dav_config2buf(davconfig); 172 if(!buf) { 173 return 1; 174 } 175 176 char *file = util_concat_path(ENV_HOME, ".dav/config.xml"); 177 FILE *cout = sys_fopen(file, "w"); 178 if(!cout) { 179 cxBufferFree(buf); 180 return 1; 181 } 182 183 // should only fail if we run out of disk space or something like that 184 // in that case, the config file is only destroyed 185 // could only be prevented, if we write to a temp file first and than 186 // rename it 187 fwrite(buf->space, buf->size, 1, cout); 188 189 cxBufferFree(buf); 190 fclose(cout); 191 192 return 0; 193 } 194 195 void free_config(void) { 196 if(davconfig) { 197 dav_config_free(davconfig); 198 } 199 } 200 201 cxmutstr load_key_file(const char *filename) { 202 cxmutstr k; 203 k.ptr = NULL; 204 k.length = 0; 205 206 FILE *file = NULL; 207 if(filename[0] == '/') { 208 file = sys_fopen(filename, "r"); 209 } else { 210 char *path = util_concat_path(ENV_HOME, ".dav/"); 211 char *p2 = util_concat_path(path, filename); 212 file = sys_fopen(p2, "r"); 213 free(path); 214 free(p2); 215 } 216 217 if(!file) { 218 fprintf(stderr, "Error: cannot load keyfile %s\n", filename); 219 return k; 220 } 221 222 char *data = malloc(256); 223 size_t r = fread(data, 1, 256, file); 224 k.ptr = data; 225 k.length = r; 226 227 fclose(file); 228 return k; 229 } 230 231 static char* get_attr_content(xmlNode *node) { 232 // TODO: remove code duplication (util_xml_get_text) 233 while(node) { 234 if(node->type == XML_TEXT_NODE) { 235 return (char*)node->content; 236 } 237 node = node->next; 238 } 239 return NULL; 240 } 241 242 int load_namespace(const xmlNode *node) { 243 const char *prefix = NULL; 244 const char *uri = NULL; 245 246 xmlAttr *attr = node->properties; 247 while(attr) { 248 if(attr->type == XML_ATTRIBUTE_NODE) { 249 char *value = get_attr_content(attr->children); 250 if(!value) { 251 print_error( 252 node->line, 253 "missing value for attribute %s\n", (char*)attr->name); 254 return 1; 255 } 256 if(xstreq(attr->name, "prefix")) { 257 prefix = value; 258 } else if(xstreq(attr->name, "uri")) { 259 uri = value; 260 } else { 261 print_error( 262 node->line, 263 "unexpected attribute %s\n", (char*)attr->name); 264 return 1; 265 } 266 } 267 attr = attr->next; 268 } 269 270 if(!prefix) { 271 print_error(node->line, "missing prefix attribute\n"); 272 return 1; 273 } 274 if(!uri) { 275 print_error(node->line, "missing uri attribute\n"); 276 return 1; 277 } 278 279 if(dav_get_namespace(context, prefix)) { 280 print_error(node->line, "namespace prefix ''%s'' already used\n", prefix); 281 return 1; 282 } 283 284 return dav_add_namespace(context, prefix, uri); 285 } 286 287 int load_secretstore(const xmlNode *node) { 288 // currently only one secretstore is supported 289 290 if(!pstore) { 291 return 0; 292 } 293 294 node = node->children; 295 int error = 0; 296 while(node) { 297 if(node->type == XML_ELEMENT_NODE) { 298 char *value = util_xml_get_text(node); 299 if(value) { 300 if(xstreq(node->name, "unlock-command")) { 301 pstore->unlock_cmd = strdup(value); 302 } else if(xstreq(node->name, "lock-command")) { 303 pstore->lock_cmd = strdup(value); 304 } 305 } 306 } 307 node = node->next; 308 } 309 310 return error; 311 } 312 313 PwdStore* get_pwdstore(void) { 314 return pstore; 315 } 316 317 int pwdstore_save(PwdStore *pwdstore) { 318 if(check_config_dir()) { 319 return 1; 320 } 321 322 char *pwfile = util_concat_path(ENV_HOME, ".dav/secrets.crypt"); 323 int ret = pwdstore_store(pwdstore, pwfile); 324 free(pwfile); 325 return ret; 326 } 327 328 329 330 /* 331 Repository* url2repo_s(cxstring url, char **path) { 332 *path = NULL; 333 334 int s; 335 if(cx_strprefix(url, CX_STR("http://"))) { 336 s = 7; 337 } else if(cx_strprefix(url, CX_STR("https://"))) { 338 s = 8; 339 } else { 340 s = 1; 341 } 342 343 // split URL into repository and path 344 cxstring r = cx_strsubs(url, s); 345 cxstring p = cx_strchr(r, '/'); 346 r = cx_strsubsl(url, 0, url.length-p.length); 347 if(p.length == 0) { 348 p = cx_strn("/", 1); 349 } 350 351 Repository *repo = get_repository(r); 352 if(repo) { 353 *path = cx_strdup(p).ptr; 354 } else { 355 // TODO: who is responsible for freeing this repository? 356 // how can the callee know, if he has to call free()? 357 repo = calloc(1, sizeof(Repository)); 358 repo->name = strdup(""); 359 repo->decrypt_content = true; 360 repo->verification = true; 361 repo->authmethods = CURLAUTH_BASIC; 362 if(url.ptr[url.length-1] == '/') { 363 repo->url = cx_strdup(url).ptr; 364 *path = strdup("/"); 365 } else if (cx_strchr(url, '/').length > 0) { 366 // TODO: fix the following workaround after 367 // fixing the inconsistent behavior of util_url_*() 368 repo->url = util_url_base_s(url); 369 cxmutstr truncated = cx_strdup(url); 370 *path = strdup(util_url_path(truncated.ptr)); 371 free(truncated.ptr); 372 } else { 373 repo->url = cx_strdup(url).ptr; 374 *path = strdup("/"); 375 } 376 } 377 378 return repo; 379 } 380 381 Repository* url2repo(const char *url, char **path) { 382 return url2repo_s(cx_str(url), path); 383 } 384 */ 385 386 static int decrypt_secrets(CmdArgs *a, PwdStore *secrets) { 387 if(cmd_getoption(a, "noinput")) { 388 return 1; 389 } 390 391 char *ps_password = NULL; 392 if(secrets->unlock_cmd && strlen(secrets->unlock_cmd) > 0) { 393 CxBuffer *cmd_out = cxBufferCreate(NULL, 128, cxDefaultAllocator, CX_BUFFER_FREE_CONTENTS|CX_BUFFER_AUTO_EXTEND); 394 if(!util_exec_command(secrets->unlock_cmd, cmd_out)) { 395 // command successful, get first line from output without newline 396 // and use that as password for the secretstore 397 size_t len = 0; 398 for(size_t i=0;i<=cmd_out->size;i++) { 399 if(i == cmd_out->size || cmd_out->space[i] == '\n') { 400 len = i; 401 break; 402 } 403 } 404 if(len > 0) { 405 ps_password = malloc(len + 1); 406 memcpy(ps_password, cmd_out->space, len); 407 ps_password[len] = 0; 408 } 409 } 410 cxBufferFree(cmd_out); 411 } 412 413 if(!ps_password) { 414 ps_password = util_password_input("Master password: "); 415 if(!ps_password) { 416 return 1; 417 } 418 } 419 420 if(pwdstore_setpassword(secrets, ps_password)) { 421 fprintf(stderr, "Error: cannot create key from password\n"); 422 return 1; 423 } 424 if(pwdstore_decrypt(secrets)) { 425 fprintf(stderr, "Error: cannot decrypt secrets store\n"); 426 return 1; 427 } 428 return 0; 429 } 430 431 typedef struct CredLocation { 432 char *id; 433 char *location; 434 } CredLocation; 435 436 static int cmp_url_cred_entry(CredLocation *e1, CredLocation *e2, void *n) { 437 return strcmp(e2->location, e1->location); 438 } 439 440 static void free_cred_location(CredLocation *c) { 441 // c->id is not a copy, therefore we don't have to free it 442 free(c->location); 443 free(c); 444 } 445 446 static int get_stored_credentials(CmdArgs *a, char *credid, char **user, char **password) { 447 if(!credid) { 448 return 0; 449 } 450 451 PwdStore *secrets = get_pwdstore(); 452 if(!secrets) { 453 fprintf(stderr, "Error: no secrets store available\n"); 454 return 0; 455 } 456 457 if(pwdstore_has_id(secrets, credid)) { 458 if(!secrets->isdecrypted) { 459 if(decrypt_secrets(a, secrets)) { 460 return 0; 461 } 462 } 463 464 PwdEntry *s_cred = pwdstore_get(secrets, credid); 465 if(s_cred) { 466 *user = s_cred->user; 467 *password = s_cred->password; 468 return 1; 469 } 470 } else { 471 fprintf(stderr, "Error: credentials id ''%s'' not found\n", credid); 472 } 473 474 return 0; 475 } 476 477 478 static int get_location_credentials(CmdArgs *a, DavCfgRepository *repo, const char *path, char **user, char **password) { 479 PwdStore *secrets = get_pwdstore(); 480 if(!secrets) { 481 return 0; 482 } 483 484 /* 485 * The list secrets->location contains urls or repo names as 486 * location strings. We need a list, that contains only urls 487 */ 488 CxList *locations = cxLinkedListCreate(cxDefaultAllocator, (cx_compare_func)cmp_url_cred_entry, CX_STORE_POINTERS); 489 locations->simple_destructor = (cx_destructor_func)free_cred_location; 490 CxIterator i = cxListIterator(secrets->locations); 491 cx_foreach(PwdIndexEntry*, e, i) { 492 CxIterator entry_iter = cxListIterator(e->locations); 493 cx_foreach(char *, loc, entry_iter) { 494 cxmutstr rpath; 495 DavCfgRepository *r = dav_config_url2repo_s(davconfig, cx_str(loc), &rpath); 496 CredLocation *urlentry = calloc(1, sizeof(CredLocation)); 497 urlentry->id = e->id; 498 urlentry->location = util_concat_path_s(cx_strcast(r->url.value), cx_strcast(rpath)).ptr; 499 cxListAdd(locations, urlentry); 500 free(rpath.ptr); 501 } 502 } 503 // the list must be sorted 504 cxListSort(locations); 505 506 // create full request url string and remove protocol prefix 507 cxmutstr req_url_proto = util_concat_path_s(cx_strcast(repo->url.value), cx_str(path)); 508 cxstring req_url = cx_strcast(req_url_proto); 509 if(cx_strprefix(req_url, CX_STR("http://"))) { 510 req_url = cx_strsubs(req_url, 7); 511 } else if(cx_strprefix(req_url, CX_STR("https://"))) { 512 req_url = cx_strsubs(req_url, 8); 513 } 514 515 // iterate over sorted locations and check if a location is a prefix 516 // of the requested url 517 char *id = NULL; 518 int ret = 0; 519 i = cxListIterator(locations); 520 cx_foreach(CredLocation*, cred, i) { 521 cxstring cred_url = cx_str(cred->location); 522 523 // remove protocol prefix 524 if(cx_strprefix(cred_url, CX_STR("http://"))) { 525 cred_url = cx_strsubs(cred_url, 7); 526 } else if(cx_strprefix(cred_url, CX_STR("https://"))) { 527 cred_url = cx_strsubs(cred_url, 8); 528 } 529 530 if(cx_strprefix(req_url, cred_url)) { 531 id = cred->id; 532 break; 533 } 534 } 535 536 // if an id is found and we can access the decrypted secret store 537 // we can set the user/password 538 if(id && (secrets->isdecrypted || !decrypt_secrets(a, secrets))) { 539 PwdEntry *cred = pwdstore_get(secrets, id); 540 if(cred) { 541 *user = cred->user; 542 *password = cred->password; 543 ret = 1; 544 } 545 } 546 547 free(req_url_proto.ptr); 548 cxListDestroy(locations); 549 550 return ret; 551 } 552 553 DavSession* connect_to_repo(DavContext *ctx, DavCfgRepository *repo, const char *path, dav_auth_func authfunc, CmdArgs *a) { 554 cxmutstr decodedpw = dav_repository_get_decodedpassword(repo); 555 556 char *user = repo->user.value.ptr; 557 char *password = decodedpw.ptr; 558 559 if(!user && !password) { 560 if(!get_stored_credentials(a, repo->stored_user.value.ptr, &user, &password)) { 561 get_location_credentials(a, repo, path, &user, &password); 562 } 563 } 564 565 DavSession *sn = dav_session_new_auth(ctx, repo->url.value.ptr, user, password); 566 if(password) { 567 free(password); 568 } 569 570 sn->flags = dav_repository_get_flags(repo); 571 sn->key = dav_context_get_key(ctx, repo->default_key.value.ptr); 572 // TODO: reactivate 573 //curl_easy_setopt(sn->handle, CURLOPT_HTTPAUTH, repo->authmethods); 574 curl_easy_setopt(sn->handle, CURLOPT_SSLVERSION, repo->ssl_version); 575 if(repo->cert.value.ptr) { 576 curl_easy_setopt(sn->handle, CURLOPT_CAINFO, repo->cert.value.ptr); 577 } 578 if(!repo->verification.value || cmd_getoption(a, "insecure")) { 579 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYPEER, 0); 580 curl_easy_setopt(sn->handle, CURLOPT_SSL_VERIFYHOST, 0); 581 } 582 if(!cmd_getoption(a, "noinput")) { 583 dav_session_set_authcallback(sn, authfunc, repo); 584 } 585 return sn; 586 } 587 588 int request_auth(DavSession *sn, void *userdata) { 589 DavCfgRepository *repo = userdata; 590 591 cxstring user = {NULL, 0}; 592 char ubuf[256]; 593 if(repo->user.value.ptr) { 594 user = cx_strcast(repo->user.value); 595 } else { 596 fprintf(stderr, "User: "); 597 fflush(stderr); 598 user = cx_str(fgets(ubuf, 256, stdin)); 599 } 600 if(!user.ptr) { 601 return 0; 602 } 603 604 char *password = util_password_input("Password: "); 605 if(!password || strlen(password) == 0) { 606 return 0; 607 } 608 609 dav_session_set_auth_s(sn, user, cx_str(password)); 610 free(password); 611 612 return 0; 613 } 614