#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <cx/hash_map.h>
#include <errno.h>
#include <libxml/tree.h>
#include "utils.h"
#define xstreq(a,b) xmlStrEqual(
BAD_CAST a,
BAD_CAST b)
#define xstrEQ(a,b) !xmlStrcasecmp(
BAD_CAST a,
BAD_CAST b)
#define print_error(lineno, ...) \
do {\
fprintf(stderr,
"Error (config.xml line %u): ", lineno); \
fprintf(stderr,
__VA_ARGS__); \
fprintf(stderr,
"Abort.\n"); \
}
while(
0);
#define print_warning(lineno, ...) \
do {\
fprintf(stderr,
"Warning (config.xml line %u): ", lineno); \
fprintf(stderr,
__VA_ARGS__); \
}
while(
0);
#ifdef _WIN32
#define ENV_HOME getenv(
"USERPROFILE")
#else
#define ENV_HOME getenv(
"HOME")
#endif
static int load_repository(
DavConfig *config,
DavCfgRepository **list_begin,
DavCfgRepository **list_end,
xmlNode *reponode);
static int load_key(
DavConfig *config,
DavCfgKey **list_begin,
DavCfgKey **list_end,
xmlNode *keynode);
static int load_proxy(
DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode,
int type);
static int load_namespace(
DavConfig *config,
DavCfgNamespace **list_begin,
DavCfgNamespace **list_end,
xmlNode *node);
static int load_secretstore(DavConfig *config, xmlNode *node);
int dav_cfg_string_set_value(DavConfig *config, CfgString *str, xmlNode *node) {
str->node = node;
char *value = util_xml_get_text(node);
if(value) {
str->value = cx_strdup_a(config->mp->allocator, cx_str(value));
return 0;
}
else {
str->value = (cxmutstr){
NULL,
0};
return 1;
}
}
void dav_cfg_bool_set_value(DavConfig *config, CfgBool *cbool, xmlNode *node) {
cbool->node = node;
char *value = util_xml_get_text(node);
cbool->value = util_getboolean(value);
}
DavConfig* dav_config_load(cxmutstr xmlfilecontent,
int *error) {
xmlDoc *doc = xmlReadMemory(xmlfilecontent.ptr, xmlfilecontent.length,
NULL,
NULL,
0);
if(!doc) {
if(error) {
*error =
DAV_CONFIG_ERROR_XML;
}
return NULL;
}
CxMempool *cfg_mp = cxMempoolCreate(
128,
NULL);
cxMempoolRegister(cfg_mp, doc, (cx_destructor_func)xmlFreeDoc);
DavConfig *config = cxMalloc(cfg_mp->allocator,
sizeof(DavConfig));
memset(config,
0,
sizeof(DavConfig));
config->mp = cfg_mp;
config->doc = doc;
DavCfgRepository *repos_begin =
NULL;
DavCfgRepository *repos_end =
NULL;
DavCfgKey *keys_begin =
NULL;
DavCfgKey *keys_end =
NULL;
DavCfgNamespace *namespaces_begin =
NULL;
DavCfgNamespace *namespaces_end =
NULL;
xmlNode *xml_root = xmlDocGetRootElement(doc);
xmlNode *node = xml_root->children;
int ret =
0;
while(node && !ret) {
if(node->type ==
XML_ELEMENT_NODE) {
if(xstreq(node->name,
"repository")) {
ret = load_repository(config, &repos_begin, &repos_end, node);
}
else if(xstreq(node->name,
"key")) {
ret = load_key(config, &keys_begin, &keys_end, node);
}
else if (xstreq(node->name,
"http-proxy")) {
config->http_proxy = cxCalloc(config->mp->allocator,
1,
sizeof(DavCfgProxy));
ret = load_proxy(config, config->http_proxy, node,
DAV_HTTP_PROXY);
}
else if (xstreq(node->name,
"https-proxy")) {
config->https_proxy = cxCalloc(config->mp->allocator,
1,
sizeof(DavCfgProxy));
ret = load_proxy(config, config->https_proxy, node,
DAV_HTTPS_PROXY);
}
else if (xstreq(node->name,
"namespace")) {
ret = load_namespace(config, &namespaces_begin, &namespaces_end, node);
}
else if (xstreq(node->name,
"secretstore")) {
ret = load_secretstore(config, node);
}
else {
fprintf(stderr,
"Unknown config element: %s\n", node->name);
ret =
1;
}
}
node = node->next;
}
config->repositories = repos_begin;
config->keys = keys_begin;
config->namespaces = namespaces_begin;
if(ret !=
0 && error) {
*error = ret;
cxMempoolDestroy(cfg_mp);
}
return config;
}
void dav_config_free(DavConfig *config) {
cxMempoolDestroy(config->mp);
}
CxBuffer* dav_config2buf(DavConfig *config) {
xmlChar* xmlText =
NULL;
int textLen =
0;
xmlDocDumpFormatMemory(config->doc, &xmlText, &textLen,
1);
if(!xmlText) {
return NULL;
}
CxBuffer *buf = cxBufferCreate(
NULL, textLen, cxDefaultAllocator,
CX_BUFFER_AUTO_EXTEND|
CX_BUFFER_FREE_CONTENTS);
cxBufferWrite(xmlText,
1, textLen, buf);
xmlFree(xmlText);
return buf;
}
static int repo_add_config(
DavConfig *config,
DavCfgRepository *repo,
xmlNode* node)
{
unsigned short lineno = node->line;
char *key = (
char*)node->name;
char *value = util_xml_get_text(node);
if(!value) {
print_error(lineno,
"missing value for config element: %s\n", key);
return 1;
}
if(xstreq(key,
"name")) {
dav_cfg_string_set_value(config, &repo->name, node);
}
else if(xstreq(key,
"url")) {
dav_cfg_string_set_value(config, &repo->url, node);
}
else if(xstreq(key,
"user")) {
dav_cfg_string_set_value(config, &repo->user, node);
}
else if(xstreq(key,
"password")) {
dav_cfg_string_set_value(config, &repo->password, node);
}
else if(xstreq(key,
"stored-user")) {
dav_cfg_string_set_value(config, &repo->stored_user, node);
}
else if(xstreq(key,
"default-key")) {
dav_cfg_string_set_value(config, &repo->default_key, node);
}
else if(xstreq(key,
"full-encryption")) {
dav_cfg_bool_set_value(config, &repo->full_encryption, node);
}
else if(xstreq(key,
"content-encryption")) {
dav_cfg_bool_set_value(config, &repo->content_encryption, node);
}
else if(xstreq(key,
"decrypt-content")) {
dav_cfg_bool_set_value(config, &repo->decrypt_content, node);
}
else if(xstreq(key,
"decrypt-name")) {
dav_cfg_bool_set_value(config, &repo->decrypt_name, node);
}
else if(xstreq(key,
"cert")) {
dav_cfg_string_set_value(config, &repo->cert, node);
}
else if(xstreq(key,
"verification")) {
dav_cfg_bool_set_value(config, &repo->verification, node);
}
else if(xstreq(key,
"ssl-version
/span>)) {
repo->ssl_version.node = node;
if(xstrEQ(value, "TLSv1")) {
repo->ssl_version.value = CURL_SSLVERSION_TLSv1;
} else if(xstrEQ(value, "SSLv2")) {
repo->ssl_version.value = CURL_SSLVERSION_SSLv2;
} else if(xstrEQ(value, "SSLv3")) {
repo->ssl_version.value = CURL_SSLVERSION_SSLv3;
}
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034
else if(xstrEQ(value, "TLSv1.0")) {
repo->ssl_version.value = CURL_SSLVERSION_TLSv1_0;
} else if(xstrEQ(value, "TLSv1.1")) {
repo->ssl_version.value = CURL_SSLVERSION_TLSv1_1;
} else if(xstrEQ(value, "TLSv1.2")) {
repo->ssl_version.value = CURL_SSLVERSION_TLSv1_2;
}
#endif
#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052
else if(xstrEQ(value, "TLSv1.3")) {
repo->ssl_version.value = CURL_SSLVERSION_TLSv1_3;
}
#endif
else {
print_warning(lineno, "unknown ssl version: %s\n", value);
repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
}
} else if(xstreq(key, "authmethods")) {
repo->authmethods.node = node;
repo->authmethods.value = CURLAUTH_NONE;
const char *delims = " \t\r\n";
char *meths = strdup(value);
char *meth = strtok(meths, delims);
while (meth) {
if(xstrEQ(meth, "basic")) {
repo->authmethods.value |= CURLAUTH_BASIC;
} else if(xstrEQ(meth, "digest")) {
repo->authmethods.value |= CURLAUTH_DIGEST;
} else if(xstrEQ(meth, "negotiate")) {
repo->authmethods.value |= CURLAUTH_GSSNEGOTIATE;
} else if(xstrEQ(meth, "ntlm")) {
repo->authmethods.value |= CURLAUTH_NTLM;
} else if(xstrEQ(meth, "any")) {
repo->authmethods.value = CURLAUTH_ANY;
} else if(xstrEQ(meth, "none")) {
} else {
print_warning(lineno,
"unknown authentication method: %s\n", meth);
}
meth = strtok(NULL, delims);
}
free(meths);
} else {
print_error(lineno, "unkown repository config element: %s\n", key);
return 1;
}
return 0;
}
static int load_repository(
DavConfig *config,
DavCfgRepository **list_begin,
DavCfgRepository **list_end,
xmlNode *reponode)
{
DavCfgRepository *repo = dav_repository_new(config);
repo->node = reponode;
xmlNode *node = reponode->children;
int ret = 0;
while(node && !ret) {
if(node->type == XML_ELEMENT_NODE) {
ret = repo_add_config(config, repo, node);
}
node = node->next;
}
if(ret) {
return 1;
} else {
cx_linked_list_add(
(void**)list_begin,
(void**)list_end,
offsetof(DavCfgRepository, prev),
offsetof(DavCfgRepository, next),
repo);
}
return 0;
}
static xmlNode* addXmlNode(xmlNode *node, const char *name, cxmutstr content) {
xmlNode *text1 = xmlNewDocText(node->doc, BAD_CAST "\t\t");
xmlAddChild(node, text1);
cxmutstr ctn = cx_strdup(cx_strcast(content));
xmlNode *newNode = xmlNewChild(node, NULL, BAD_CAST name, BAD_CAST ctn.ptr);
free(ctn.ptr);
xmlNode *text2 = xmlNewDocText(node->doc, BAD_CAST "\n");
xmlAddChild(node, text2);
return newNode;
}
void dav_config_add_repository(DavConfig *config, DavCfgRepository *repo) {
if(repo->node) {
fprintf(stderr, "Error: dav_config_add_repository: node already exists\n");
return;
}
xmlNode *repoNode = xmlNewNode(NULL, BAD_CAST "repository");
xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
xmlAddChild(repoNode, rtext1);
if(repo->name.value.ptr) {
repo->name.node = addXmlNode(repoNode, "name", repo->name.value);
}
if(repo->url.value.ptr) {
repo->url.node = addXmlNode(repoNode, "url", repo->url.value);
}
if(repo->user.value.ptr) {
repo->user.node = addXmlNode(repoNode, "user", repo->user.value);
}
if(repo->password.value.ptr) {
repo->password.node = addXmlNode(repoNode, "password", repo->password.value);
}
if(repo->stored_user.value.ptr) {
repo->stored_user.node = addXmlNode(repoNode, "stored-user", repo->stored_user.value);
}
if(repo->default_key.value.ptr) {
repo->default_key.node = addXmlNode(repoNode, "default-key", repo->default_key.value);
}
if(repo->cert.value.ptr) {
repo->cert.node = addXmlNode(repoNode, "cert", repo->cert.value);
}
xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
xmlAddChild(repoNode, rtext2);
DavCfgRepository **list_begin = &config->repositories;
cx_linked_list_add(
(void**)list_begin,
NULL,
offsetof(DavCfgRepository, prev),
offsetof(DavCfgRepository, next),
repo);
xmlNode *xml_root = xmlDocGetRootElement(config->doc);
xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
xmlAddChild(xml_root, text1);
xmlAddChild(xml_root, repoNode);
xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
xmlAddChild(xml_root, text2);
}
DavCfgRepository* dav_repository_new(DavConfig *config) {
DavCfgRepository *repo = cxMalloc(config->mp->allocator, sizeof(DavCfgRepository));
memset(repo, 0, sizeof(DavCfgRepository));
repo->decrypt_name.value = false;
repo->decrypt_content.value = true;
repo->decrypt_properties.value = false;
repo->verification.value = true;
repo->ssl_version.value = CURL_SSLVERSION_DEFAULT;
repo->authmethods.value = CURLAUTH_BASIC;
return repo;
}
void dav_repository_free(DavConfig *config, DavCfgRepository *repo) {
}
void dav_repository_remove_and_free(DavConfig *config, DavCfgRepository *repo) {
if(repo->prev) {
repo->prev->next = repo->next;
}
if(repo->next) {
repo->next->prev = repo->prev;
}
if(repo->node) {
xmlUnlinkNode(repo->node);
xmlFreeNode(repo->node);
}
}
int dav_repository_get_flags(DavCfgRepository *repo) {
int flags = 0;
DavBool encrypt_content = FALSE;
DavBool encrypt_name = FALSE;
DavBool encrypt_properties = FALSE;
DavBool decrypt_content = FALSE;
DavBool decrypt_name = FALSE;
DavBool decrypt_properties = FALSE;
if(repo->full_encryption.value) {
encrypt_content = TRUE;
encrypt_name = TRUE;
encrypt_properties = TRUE;
decrypt_content = TRUE;
decrypt_name = TRUE;
decrypt_properties = TRUE;
} else if(repo->content_encryption.value) {
encrypt_content = TRUE;
decrypt_content = TRUE;
}
if(decrypt_content) {
flags |= DAV_SESSION_DECRYPT_CONTENT;
}
if(decrypt_name) {
flags |= DAV_SESSION_DECRYPT_NAME;
}
if(decrypt_properties) {
flags |= DAV_SESSION_DECRYPT_PROPERTIES;
}
if(encrypt_content) {
flags |= DAV_SESSION_ENCRYPT_CONTENT;
}
if(encrypt_name) {
flags |= DAV_SESSION_ENCRYPT_NAME;
}
if(encrypt_properties) {
flags |= DAV_SESSION_ENCRYPT_PROPERTIES;
}
return flags;
}
void dav_repository_set_url(DavConfig *config, DavCfgRepository *repo, cxstring newurl) {
if(repo->url.value.ptr) {
cxFree(config->mp->allocator, repo->url.value.ptr);
}
repo->url.value = cx_strdup_a(config->mp->allocator, newurl);
}
void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password) {
const CxAllocator *a = config->mp->allocator;
repo->user.value = cx_strdup_a(a, user);
char *pwenc = util_base64encode(password.ptr, password.length);
repo->password.value = cx_strdup_a(a, cx_str(pwenc));
free(pwenc);
}
cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo) {
cxmutstr pw = { NULL, 0 };
if(repo->password.value.ptr) {
pw = cx_mutstr(util_base64decode(repo->password.value.ptr));
}
return pw;
}
static int load_key(
DavConfig *config,
DavCfgKey **list_begin,
DavCfgKey **list_end,
xmlNode *keynode)
{
xmlNode *node = keynode->children;
DavCfgKey *key = cxMalloc(config->
->allocator, sizeof(DavCfgKey));
memset(key, 0, sizeof(DavCfgKey));
key->type = DAV_KEY_TYPE_AES256;
int error = 0;
while(node) {
if(node->type == XML_ELEMENT_NODE) {
if(xstreq(node->name, "name")) {
dav_cfg_string_set_value(config, &key->name, node);
} else if(xstreq(node->name, "file")) {
dav_cfg_string_set_value(config, &key->file, node);
} else if(xstreq(node->name, "type")) {
const char *value = util_xml_get_text(node);
key->type_node = node;
if(!strcmp(value, "aes128")) {
key->type = DAV_KEY_TYPE_AES128;
} else if(!strcmp(value, "aes256")) {
key->type = DAV_KEY_TYPE_AES256;
} else {
print_error(node->line, "unknown key type %s\n", value);
error = 1;
}
} else {
key->unknown_elements++;
}
}
node = node->next;
}
if(!key->name.value.ptr) {
error = 1;
}
if(!error) {
error = 0;
size_t expected_length = 0;
if(key->type == DAV_KEY_TYPE_AES128) {
expected_length = 16;
}
if(key->type == DAV_KEY_TYPE_AES256) {
expected_length = 32;
}
}
if(error) {
return 1;
} else {
cx_linked_list_add(
(void**)list_begin,
(void**)list_end,
offsetof(DavCfgKey, prev),
offsetof(DavCfgKey, next),
key);
return 0;
}
}
static int load_proxy(
DavConfig *config, DavCfgProxy *proxy, xmlNode *proxynode, int type)
{
const char *stype;
if(type == DAV_HTTPS_PROXY) {
stype = "https";
} else if(type == DAV_HTTP_PROXY) {
stype = "http";
} else {
fprintf(stderr, "unknown proxy type\n");
return 1;
}
if(!proxy) {
fprintf(stderr, "no memory reserved for %s proxy.\n", stype);
return 1;
}
xmlNode *node = proxynode->children;
int ret = 0;
while(node && !ret) {
if(node->type == XML_ELEMENT_NODE) {
int reportmissingvalue = 0;
if(xstreq(node->name, "url")) {
reportmissingvalue = dav_cfg_string_set_value(config, &proxy->url, node);
} else if(xstreq(node->name, "user")) {
reportmissingvalue = dav_cfg_string_set_value(config, &proxy->user, node);
} else if(xstreq(node->name, "password")) {
reportmissingvalue = dav_cfg_string_set_value(config, &proxy->password, node);
} else if(xstreq(node->name, "no")) {
reportmissingvalue = dav_cfg_string_set_value(config, &proxy->noproxy, node);
} else {
proxy->unknown_elements++;
}
if (reportmissingvalue) {
print_error(node->line,
"missing value for proxy configuration element: %s\n",
node->name);
ret = 1;
break;
}
}
node = node->next;
}
if(!ret && !proxy->url.value.ptr) {
print_error(proxynode->line, "missing url for %s proxy.\n", stype);
return 1;
}
return ret;
}
static char* get_attr_content(xmlNode *node) {
while(node) {
if(node->type == XML_TEXT_NODE) {
return (char*)node->content;
}
node = node->next;
}
return NULL;
}
static int load_namespace(
DavConfig *config,
DavCfgNamespace **list_begin,
DavCfgNamespace **list_end,
xmlNode *node)
{
const char *prefix = NULL;
const char *uri = NULL;
xmlAttr *attr = node->properties;
while(attr) {
if(attr->type == XML_ATTRIBUTE_NODE) {
char *value = get_attr_content(attr->children);
if(!value) {
print_error(
node->line,
"missing value for attribute %s\n", (char*)attr->name);
return 1;
}
if(xstreq(attr->name, "prefix")) {
prefix = value;
} else if(xstreq(attr->name, "uri")) {
uri = value;
} else {
print_error(
node->line,
"unexpected attribute %s\n", (char*)attr->name);
return 1;
}
}
attr = attr->next;
}
if(!prefix) {
print_error(node->line, "missing prefix attribute\n");
return 1;
}
if(!uri) {
print_error(node->line, "missing uri attribute\n");
return 1;
}
DavCfgNamespace *ns = cxMalloc(config->mp->allocator, sizeof(DavCfgNamespace));
memset(ns, 0, sizeof(DavCfgNamespace));
ns->node = node;
ns->prefix = cx_strdup_a(config->mp->allocator, cx_str(prefix));
ns->uri = cx_strdup_a(config->mp->allocator, cx_str(uri));
cx_linked_list_add(
(void**)list_begin,
(void**)list_end,
offsetof(DavCfgNamespace, prev),
offsetof(DavCfgNamespace, next),
ns);
return 0;
}
static int load_secretstore(DavConfig *config, xmlNode *node) {
if(config->secretstore) {
return 1;
}
config->secretstore = cxCalloc(config->mp->allocator, 1, sizeof(DavCfgSecretStore));
node = node->children;
int error = 0;
while(node) {
if(node->type == XML_ELEMENT_NODE) {
if(xstreq(node->name, "unlock-command")) {
dav_cfg_string_set_value(config, &config->secretstore->unlock_cmd, node);
} else if(xstreq(node->name, "lock-command")) {
dav_cfg_string_set_value(config, &config->secretstore->lock_cmd, node);
}
}
node = node->next;
}
return error;
}
DavCfgRepository* dav_config_get_repository(DavConfig *config, cxstring name) {
DavCfgRepository *repo = config->repositories;
while(repo) {
if(!cx_strcmp(cx_strcast(repo->name.value), name)) {
return repo;
}
repo = repo->next;
}
return NULL;
}
DavCfgRepository* dav_config_url2repo(DavConfig *config, const char *url, char **path) {
cxmutstr p;
DavCfgRepository *repo = dav_config_url2repo_s(config, cx_str(url), &p);
*path = p.ptr;
return repo;
}
DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path) {
path->ptr = NULL;
path->length = 0;
int s;
if(cx_strprefix(url, CX_STR("http://"))) {
s = 7;
} else if(cx_strprefix(url, CX_STR("https://"))) {
s = 8;
} else {
s = 1;
}
cxstring r = cx_strsubs(url, s);
cxstring p = cx_strchr(r, '/');
r = cx_strsubsl(url, 0, url.length-p.length);
if(p.length == 0) {
p = cx_strn("/", 1);
}
DavCfgRepository *repo = dav_config_get_repository(config, r);
if(repo) {
*path = cx_strdup(p);
} else {
repo = dav_repository_new(config);
repo->name.value = cx_strdup_a(config->mp->allocator, CX_STR(""));
if(url.ptr[url.length-1] == '/') {
repo->url.value = cx_strdup_a(config->mp->allocator, url);
*path = cx_strdup(CX_STR("/"));
} else if (cx_strchr(url, '/').length > 0) {
cxstring repo_url = util_url_base_s(url);
repo->url.value = cx_strdup_a(config->mp->allocator, repo_url);
*path = cx_strdup(util_url_path_s(url));
} else {
repo->url.value = cx_strdup(url);
*path = cx_strdup(CX_STR("/"));
}
}
return repo;
}
int dav_config_keytype(DavCfgKeyType type) {
switch(type) {
default: break;
case DAV_KEY_TYPE_AES256: return DAV_KEY_AES256;
case DAV_KEY_TYPE_AES128: return DAV_KEY_AES128;
}
return 0;
}
int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) {
for(DavCfgKey *key=config->keys;key;key=key->next) {
char *file = cx_strdup_m(key->file.value).ptr;
cxmutstr keycontent = loadkey(file);
free(file);
if(!keycontent.ptr) {
return 1;
}
DavKey *davkey = calloc(1, sizeof(DavKey));
davkey->name = cx_strdup_m(key->name.value).ptr;
davkey->type = dav_config_keytype(key->type);
davkey->data = keycontent.ptr;
davkey->length = keycontent.length;
dav_context_add_key(ctx, davkey);
}
return 0;
}