Sat, 15 Sep 2018 11:56:36 +0200
adds encrypted password store
new repo config element: <stored-user>
new dav command: add-user
/* * 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <ucx/string.h> #include <ucx/utils.h> #include "libxattr.h" #include "tags.h" #ifdef __APPLE__ #include <CoreFoundation/CoreFoundation.h> #endif void free_dav_tag(DavTag* tag) { free(tag->name); if(tag->color) { free(tag->color); } free(tag); } int compare_tagname(DavTag* left, DavTag* right, void* ignorecase) { sstr_t leftname = sstr(left->name); sstr_t rightname = sstr(right->name); if (ignorecase && *((int*) ignorecase)) { return sstrcasecmp(leftname, rightname); } else { return sstrcmp(leftname, rightname); } } UcxList* parse_text_taglist(const char *buf, size_t length) { UcxList *tags = NULL; int line_start = 0; for(int i=0;i<length;i++) { if(buf[i] == '\n' || i == length-1) { sstr_t line = sstrtrim(sstrn((char*)buf + line_start, i - line_start)); if(line.length > 0) { DavTag *tag = calloc(1, sizeof(DavTag)); sstr_t color = sstrchr(line, '#'); if(color.length>0) { sstr_t name = line; name.length = (int)(color.ptr-line.ptr); if(name.length != 0) { tag->name = sstrdup(name).ptr; color.ptr++; color.length--; if(color.length > 0) { tag->color = sstrdup(color).ptr; } } else { free(tag); } } else { tag->name = sstrdup(line).ptr; tag->color = NULL; } tags = ucx_list_append(tags, tag); } line_start = i+1; } } return tags; } UcxBuffer* create_text_taglist(UcxList *tags) { if(!tags) { return NULL; } UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); UCX_FOREACH(elm, tags) { DavTag *tag = elm->data; if(tag->color) { ucx_bprintf(buf, "%s#%s\n", tag->name, tag->color); } else { ucx_bprintf(buf, "%s\n", tag->name); } } return buf; } UcxList* parse_csv_taglist(const char *buf, size_t length) { UcxList *taglist = NULL; sstr_t str = sstrn((char*)buf, length); ssize_t count = 0; sstr_t *tags = sstrsplit(str, S(","), &count); for(int i=0;i<count;i++) { sstr_t trimmed_tag = sstrtrim(tags[i]); if (trimmed_tag.length > 0) { DavTag *tag = malloc(sizeof(DavTag)); tag->name = sstrdup(trimmed_tag).ptr; tag->color = NULL; taglist = ucx_list_append(taglist, tag); } free(tags[i].ptr); } if(tags) { free(tags); } return taglist; } UcxBuffer* create_csv_taglist(UcxList *tags) { UcxBuffer *buf = ucx_buffer_new(NULL, 128, UCX_BUFFER_AUTOEXTEND); int insertsep = 0; UCX_FOREACH(elm, tags) { DavTag *tag = elm->data; if(insertsep) { ucx_buffer_putc(buf, ','); } ucx_buffer_puts(buf, tag->name); insertsep = 1; } return buf; } static DavTag* parse_xml_dav_tag(DavXmlNode *node) { char *name = NULL; char *color = NULL; DavXmlNode *c = node->children; while(c) { if(c->type == DAV_XML_ELEMENT) { char *value = dav_xml_getstring(c->children); if(value) { if(!strcmp(c->namespace, DAV_NS)) { if(!strcmp(c->name, "name")) { char *value = dav_xml_getstring(c->children); if(value) { name = value; } } if(!strcmp(c->name, "color")) { char *value = dav_xml_getstring(c->children); if(value) { color = value; } } } } } c = c->next; } DavTag *tag = NULL; if(name) { tag = malloc(sizeof(DavTag)); tag->name = strdup(name); tag->color = color ? strdup(color) : NULL; } return tag; } UcxList* parse_dav_xml_taglist(DavXmlNode *taglistnode) { UcxList *tags = NULL; DavXmlNode *node = taglistnode; while(node) { if(node->type == DAV_XML_ELEMENT) { if(!strcmp(node->namespace, DAV_NS) && !strcmp(node->name, "tag")) { DavTag *tag = parse_xml_dav_tag(node); if(tag) { tags = ucx_list_append(tags, tag); } } } node = node->next; } return tags; } DavXmlNode* create_xml_taglist(UcxList *tags) { DavXmlNode *tag1 = NULL; DavXmlNode *lasttag = NULL; UCX_FOREACH(elm, tags) { DavTag *tag = elm->data; DavXmlNode *tagelm = dav_xml_createnode(DAV_NS, "tag"); DavXmlNode *tagname = dav_xml_createnode_with_text(DAV_NS, "name", tag->name); tagelm->children = tagname; if(tag->color) { DavXmlNode *tagcolor = dav_xml_createnode_with_text(DAV_NS, "color", tag->color); tagname->next = tagcolor; } if(lasttag) { lasttag->next = tagelm; tagelm->prev = lasttag; } else { tag1 = tagelm; } lasttag = tagelm; } return tag1; } #ifdef __APPLE__ static DavTag* tagstr2davtag(const char *str) { const char *name = str; const char *color = NULL; size_t len = strlen(str); size_t namelen = len; if(len == 0) { return NULL; } // start with 1 because the first char should not be a linebreak for(int i=1;i<len;i++) { if(str[i] == '\n') { if(!color) { color = str + i + 1; namelen = i; } } } int colorlen = len - namelen - 1; DavTag *tag = malloc(sizeof(DavTag)); tag->name = malloc(namelen + 1); memcpy(tag->name, name, namelen); tag->name[namelen] = 0; if(colorlen > 0) { tag->color = malloc(colorlen + 1); memcpy(tag->color, color, colorlen); tag->color[colorlen] = 0; } else { tag->color = NULL; } return tag; } UcxList* parse_macos_taglist(const char *buf, size_t length) { UcxList *taglist = NULL; CFDataRef data = CFDataCreateWithBytesNoCopy( kCFAllocatorDefault, (const UInt8*)buf, length, kCFAllocatorNull); CFPropertyListRef propertylist = CFPropertyListCreateWithData(kCFAllocatorDefault, data, 0, NULL, NULL); CFArrayRef array = propertylist; int count = CFArrayGetCount(array); for(int i=0;i<count;i++) { CFStringRef str = CFArrayGetValueAtIndex(array, i); int slen = CFStringGetLength(str); size_t cstrbuflen = slen * 4 + 4; char *cstr = malloc(cstrbuflen); if(CFStringGetCString(str, cstr, cstrbuflen, kCFStringEncodingUTF8)) { DavTag *tag = tagstr2davtag(cstr); if(tag) { taglist = ucx_list_append(taglist, tag); } } free(cstr); } CFRelease(propertylist); CFRelease(data); return taglist; } UcxBuffer* create_macos_taglist(UcxList *tags) { size_t count = ucx_list_size(tags); if(count == 0) { return NULL; } CFStringRef *strings = calloc(sizeof(CFStringRef), count); int i = 0; UCX_FOREACH(elm, tags) { DavTag *tag = elm->data; CFStringRef str = NULL; if(tag->color) { sstr_t s = sstrcat(3, sstr(tag->name), S("\n"), sstr(tag->color)); str = CFStringCreateWithCString(kCFAllocatorDefault, s.ptr, kCFStringEncodingUTF8); free(s.ptr); } else { str = CFStringCreateWithCString(kCFAllocatorDefault, tag->name, kCFStringEncodingUTF8); } strings[i] = str; i++; } CFPropertyListRef array = CFArrayCreate(kCFAllocatorDefault, (const void**)strings, count, &kCFTypeArrayCallBacks); CFDataRef data = CFPropertyListCreateData(kCFAllocatorDefault, array, kCFPropertyListBinaryFormat_v1_0, 0, NULL); UcxBuffer *buf = NULL; if(data) { int datalen = CFDataGetLength(data); CFRange range; range.location = 0; range.length = datalen; buf = ucx_buffer_new(NULL, datalen, 0); CFDataGetBytes(data, range, (UInt8*)buf->space); buf->size = datalen; CFRelease(data); } for(int i=0;i<count;i++) { CFRelease(strings[i]); } CFRelease(array); return buf; } #else UcxList* parse_macos_taglist(const char *buf, size_t length) { fprintf(stderr, "Error: macos tags not supported on this platform.\n"); return NULL; } UcxBuffer* create_macos_taglist(UcxList *tags) { fprintf(stderr, "Error: macos tags not supported on this platform.\n"); return NULL; } #endif /* ----------- ----------- tag filter ---------------------- */ // TODO: use scstr_t after update to UCX 2.0 static size_t rtrimskip(sstr_t str, size_t skip) { while (skip < str.length && isspace(str.ptr[skip])) skip++; return skip; } static size_t parse_tagfilter_taglist(sstr_t fs, SyncTagFilter* tagfilter) { size_t csvlen; for (csvlen = 0 ; csvlen < fs.length ; ++csvlen) { if (fs.ptr[csvlen] == ')') break; } fs.length = csvlen; tagfilter->tags = parse_csv_taglist(fs.ptr, fs.length); return csvlen; } static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* tagfilter); static size_t parse_tagfilter_filter(sstr_t fs, SyncTagFilter* tagfilter) { size_t consumed = rtrimskip(fs, 0); fs = sstrsubs(fs, consumed); if (fs.length == 0) { return consumed; } else { // optional operator int hasop = 0; if (fs.ptr[0] == '&') { tagfilter->mode = DAV_SYNC_TAGFILTER_AND; hasop = 1; } else if (fs.ptr[0] == '|') { tagfilter->mode = DAV_SYNC_TAGFILTER_OR; hasop = 1; } else if (fs.ptr[0] == '0') { tagfilter->mode = DAV_SYNC_TAGFILTER_NONE; hasop = 1; } else if (fs.ptr[0] == '1') { tagfilter->mode = DAV_SYNC_TAGFILTER_ONE; hasop = 1; } else { // default operator is AND tagfilter->mode = DAV_SYNC_TAGFILTER_AND; } if (hasop) { size_t skip = rtrimskip(fs, 1); consumed += skip; fs = sstrsubs(fs, skip); } if (fs.length > 0 && fs.ptr[0] == '(') { size_t c = parse_tagfilter_subfilters(fs, tagfilter); if (c) { return consumed + c; } else { return 0; } } else { tagfilter->subfilter_count = 0; tagfilter->subfilters = NULL; return consumed + parse_tagfilter_taglist(fs, tagfilter); } } } /* * Parses: ( "(" , filter , ")" )+ */ static size_t parse_tagfilter_subfilters(sstr_t fs, SyncTagFilter* f) { // strategy: allocate much and give back later (instead of reallocs in loop) size_t subfilter_cap = 8; f->subfilters = calloc(subfilter_cap, sizeof(SyncTagFilter*)); f->subfilter_count = 0; size_t total_consumed = 0; size_t c; do { // skip leading parenthesis (and white spaces) c = rtrimskip(fs, 1); fs = sstrsubs(fs, c); total_consumed += c; // increase array capacity, if necessary if (f->subfilter_count >= subfilter_cap) { subfilter_cap *= 2; SyncTagFilter** newarr = realloc(f->subfilters, subfilter_cap * sizeof(SyncTagFilter*)); if (newarr) { f->subfilters = newarr; } else { abort(); // no error handling reachable, so we are fucked } } // allocate space for a new filter SyncTagFilter* subf = calloc(1, sizeof(SyncTagFilter)); // parse that filter c = parse_tagfilter_filter(fs, subf); // sanity check: we must end with a closing parenthesis if (c > 0 && fs.ptr[c] == ')') { f->subfilters[f->subfilter_count++] = subf; // consume ')' and find the next parenthesis or the end-of-string c = rtrimskip(fs, 1+c); fs = sstrsubs(fs, c); total_consumed += c; if (fs.length == 0 || fs.ptr[0] == ')') { // our job is done break; } else if (fs.ptr[0] != '(') { // anything else than a parenthesis or end-of-string is an error return 0; } } else { free(subf); break; } } while(1); // try to shrink the array if (f->subfilter_count > 0) { SyncTagFilter** shrinked_array = realloc(f->subfilters, f->subfilter_count * sizeof(SyncTagFilter*)); if (shrinked_array) { f->subfilters = shrinked_array; } } else { free(f->subfilters); f->subfilters = NULL; } return total_consumed; } SyncTagFilter* parse_tagfilter_string(const char* filterstring, int scope) { SyncTagFilter* tagfilter = calloc(1, sizeof(SyncTagFilter)); tagfilter->scope = scope; if (!filterstring) { return tagfilter; } // TODO: use scstr_t after update to UCX 2.0 sstr_t fs = sstr((char*) filterstring); size_t consumed = parse_tagfilter_filter(fs, tagfilter); if (!consumed) { free_tagfilter(tagfilter); return NULL; } // consume trailing white spaces consumed = rtrimskip(fs, consumed); // sanity check: have we consumed the whole string? if (consumed != fs.length) { free_tagfilter(tagfilter); return NULL; } return tagfilter; } void free_tagfilter(SyncTagFilter* filter) { for (size_t i = 0 ; i < filter->subfilter_count ; i++) { free_tagfilter(filter->subfilters[i]); } free(filter->subfilters); free(filter); } static int matches_tags_and(UcxList *dav_tags, UcxList *tags, int ignorecase) { UCX_FOREACH(e, tags) { if (!ucx_list_contains(dav_tags, e->data, (cmp_func) compare_tagname, &ignorecase)) { return 0; } } return 1; } static int matches_tags_or(UcxList *dav_tags, UcxList *tags, int ignorecase) { UCX_FOREACH(e, tags) { if (ucx_list_contains(dav_tags, e->data, (cmp_func) compare_tagname, &ignorecase)) { return 1; } } return 0; } static int matches_tags_one(UcxList *dav_tags, UcxList *tags, int ignorecase) { int matches_exactly_one = 0; UCX_FOREACH(e, tags) { if (ucx_list_contains(dav_tags, e->data, (cmp_func) compare_tagname, &ignorecase)) { if (matches_exactly_one) { return 0; } else { matches_exactly_one = 1; } } } return matches_exactly_one; } static int matches_subfilters_and(UcxList *dav_tags, SyncTagFilter *filter) { int ret = 1; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { ret &= matches_tagfilter(dav_tags, filter->subfilters[i]); } return ret; } static int matches_subfilters_or(UcxList *dav_tags, SyncTagFilter *filter) { int ret = 0; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { ret |= matches_tagfilter(dav_tags, filter->subfilters[i]); } return ret; } static int matches_subfilters_one(UcxList *dav_tags, SyncTagFilter *filter) { int one = 0; for (size_t i = 0 ; i < filter->subfilter_count ; i++) { if (matches_tagfilter(dav_tags, filter->subfilters[i])) { if (one) { return 0; } else { one = 1; } } } return one; } int matches_tagfilter(UcxList *dav_tags, SyncTagFilter *tagfilter) { if (tagfilter->subfilter_count > 0) { switch (tagfilter->mode) { case DAV_SYNC_TAGFILTER_OR: return matches_subfilters_or(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_AND: return matches_subfilters_and(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_NONE: return !matches_subfilters_or(dav_tags, tagfilter); case DAV_SYNC_TAGFILTER_ONE: return matches_subfilters_one(dav_tags, tagfilter); default: abort(); } } else { int ignorecase = 0; // TODO: maybe add support later switch (tagfilter->mode) { case DAV_SYNC_TAGFILTER_OR: return matches_tags_or(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_AND: return matches_tags_and(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_NONE: return !matches_tags_or(dav_tags, tagfilter->tags, ignorecase); case DAV_SYNC_TAGFILTER_ONE: return matches_tags_one(dav_tags, tagfilter->tags, ignorecase); default: abort(); } } }