1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
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
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
184
185
186
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
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
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
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
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
396
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
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
486
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
504 cxListSort(locations);
505
506
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
516
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
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
537
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
573
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