diff -r 6bd37fe6d905 -r ee4e4742391e application/settings.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/application/settings.c Sun Oct 27 18:24:37 2024 +0100 @@ -0,0 +1,537 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2024 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 "settings.h" + +#include +#include + + +#define SETTINGS_STATE_REPOLIST_SELECTED 1 + +#define SETTINGS_STATE_REPO_ENCRYPTION 20 + + +static void repolist_activate(UiEvent *event, void *userdata) { + UiListSelection *selection = event->eventdata; + SettingsWindow *settings = event->window; + settings_edit_repository(settings, selection->rows[0]); +} + +static void repolist_selection(UiEvent *event, void *userdata) { + UiListSelection *selection = event->eventdata; + SettingsWindow *settings = event->window; + if(selection->count > 0) { + ui_set_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); + settings->selected_repo = selection->rows[0]; + } else { + ui_unset_group(event->obj->ctx, SETTINGS_STATE_REPOLIST_SELECTED); + } +} + +static void repolist_edit(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + if(settings->selected_repo >= 0) { + settings_edit_repository(settings, settings->selected_repo); + } +} + +static void repolist_remove(UiEvent *event, void *userdata) { + // TODO +} + +static void editrepo_go_back(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + settings_store_repository(settings); + ui_set(settings->repo_tabview, 0); +} + + +void settings_ok(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + settings->config_saved = TRUE; + // save any changed settings + settings_store_repository(settings); + + set_config(settings->config); // TODO: free old config + if(store_config()) { + ui_dialog(event->obj, .title = "Error", .content = "Cannot store settings", .closebutton_label = "OK"); + } + application_update_repolist(get_application()); + ui_close(event->obj); +} + +void settings_cancel(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + if(settings->config_saved) { + // function was called as context closefunc by ui_close + // in settings_ok + // don't free anything + return; + } + dav_config_free(settings->config); + if(userdata) { + ui_close(event->obj); + } +} + +void settings_window_open() { + DavConfig *config = load_config_file(); + if(!config) { + return; + } + + UiObject *obj = ui_simple_window("Settings", NULL); + ui_context_closefunc(obj->ctx, settings_cancel, NULL); + SettingsWindow *wdata = ui_malloc(obj->ctx, sizeof(SettingsWindow)); + wdata->config = config; + obj->window = wdata; + settings_init(obj, wdata); + + ui_tabview(obj) { + ui_tab(obj, "General") { + ui_grid(obj, .margin = 10) { + ui_label(obj, .label = "TODO"); + } + } + + ui_tab(obj, "Repositories") { + + ui_tabview(obj, .value = wdata->repo_tabview, .margin = 10, .tabview = UI_TABVIEW_INVISIBLE) { + ui_tab(obj, "list") { + ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10) { + ui_hbox(obj, .spacing = 10) { + ui_button(obj, .label = "Add"); + ui_button(obj, .label = "Edit", .onclick = repolist_edit, .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); + ui_button(obj, .label = "Remove", .groups = UI_GROUPS(SETTINGS_STATE_REPOLIST_SELECTED)); + } + ui_newline(obj); + + UiModel* model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING, "URL", UI_STRING, "User", UI_STRING, "Encrypted", -1); + model->getvalue = (ui_getvaluefunc) settings_repolist_getvalue; + ui_table(obj, + .model = model, + .list = wdata->repos, + .multiselection = FALSE, + .onactivate = repolist_activate, + .onselection = repolist_selection, + .vexpand = TRUE, .hexpand = TRUE, .colspan = 3); + } + } + + ui_tab(obj, "repo") { + ui_vbox(obj, .margin = 10, .spacing = 10) { + ui_hbox(obj, .fill = UI_OFF, .spacing = 4) { + ui_button(obj, .icon = UI_ICON_GO_BACK, .onclick = editrepo_go_back); + ui_label(obj, .label = "Repository List"); + } + + ui_scrolledwindow(obj, .hexpand = TRUE, .vexpand = TRUE, .subcontainer = UI_CONTAINER_NO_SUB) { + ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10) { + ui_llabel(obj, .label = "Name"); + ui_hbox0(obj) { + ui_textfield(obj, .value = wdata->repo_name, .width = 15); + } + ui_newline(obj); + ui_llabel(obj, .label = "URL"); + ui_textfield(obj, .value = wdata->repo_url, .hexpand = TRUE); + ui_newline(obj); + + ui_llabel(obj, .label = "Credentials", .style = UI_LABEL_STYLE_TITLE, .colspan = 2); + ui_newline(obj); + ui_hbox(obj, .spacing = 4, .colspan = 2) { + ui_combobox(obj, .list = wdata->repo_credentials); + ui_hbox0(obj) { + ui_button(obj, .label = "New Credentials"); + } + } + ui_newline(obj); + ui_expander(obj, .spacing = 10, .colspan = 2, .label = "Unencrypted User/Password", .margin = 10) { + ui_llabel(obj, .label = "Store the credentials unencrypted in the repository and not in the secret store", .style = UI_LABEL_STYLE_DIM); + ui_grid(obj, .rowspacing = 10, .columnspacing = 10, .fill = UI_OFF) { + ui_llabel(obj, .label = "User"); + ui_textfield(obj, .value = wdata->repo_user, .width = 15); + ui_newline(obj); + + ui_llabel(obj, .label = "Password"); + ui_passwordfield(obj, .value = wdata->repo_password, .width = 15); + } + } + ui_newline(obj); + + ui_llabel(obj, .label = "Encryption", .style = UI_LABEL_STYLE_TITLE, .colspan = 2); + ui_newline(obj); + + ui_checkbox(obj, .label = "Enable client-side encryption", .value = wdata->repo_encryption, .colspan = 2, .enable_group = SETTINGS_STATE_REPO_ENCRYPTION); + ui_newline(obj); + ui_llabel(obj, .label = "Default key"); + ui_hbox(obj, .spacing = 4) { + ui_combobox(obj, .list = wdata->repo_keys, .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); + ui_button(obj, .label = "Generate Key", .groups = UI_GROUPS(SETTINGS_STATE_REPO_ENCRYPTION)); + } + ui_newline(obj); + + ui_llabel(obj, .label = "TLS", .style = UI_LABEL_STYLE_TITLE, .colspan = 2); + ui_newline(obj); + + ui_llabel(obj, .label = "Cert Path"); + ui_hbox0(obj) { + ui_textfield(obj, .value = wdata->repo_cacert, .width = 15); + } + ui_newline(obj); + + ui_llabel(obj, .label = "TLS Version"); + ui_hbox0(obj) { + ui_combobox(obj, .list = wdata->repo_tls_versions); + } + ui_newline(obj); + ui_checkbox(obj, .label = "Disable TLS verification", .value = wdata->repo_disable_verification, .colspan = 2); + } + } + } + } + } + + + } + + ui_tab(obj, "Sync Directories") { + ui_grid(obj, .margin = 10) { + ui_label(obj, .label = "TODO"); + } + } + + ui_tab(obj, "Credentials") { + ui_grid(obj, .margin = 10) { + ui_label(obj, .label = "TODO"); + } + } + + ui_tab(obj, "Keys") { + ui_grid(obj, .margin = 10) { + ui_label(obj, .label = "TODO"); + } + } + + ui_tab(obj, "Properties") { + ui_grid(obj, .margin = 10) { + ui_label(obj, .label = "TODO"); + } + } + } + + ui_hbox(obj, .fill = UI_OFF, .margin = 10) { + ui_button(obj, .label = "Cancel", .onclick = settings_cancel, .onclickdata = "close"); + ui_label(obj, .fill = UI_ON); + ui_button(obj, .label = "Save", .onclick = settings_ok); + } + + + ui_show(obj); +} + +void settings_init(UiObject *obj, SettingsWindow *settings) { + settings->repos = ui_list_new(obj->ctx, NULL); + settings->repo_tabview = ui_int_new(obj->ctx, NULL); + + settings->repo_name = ui_string_new(obj->ctx, NULL); + settings->repo_url = ui_string_new(obj->ctx, NULL); + settings->repo_user = ui_string_new(obj->ctx, NULL); + settings->repo_password = ui_string_new(obj->ctx, NULL); + settings->repo_cacert = ui_string_new(obj->ctx, NULL); + settings->repo_credentials = ui_list_new(obj->ctx, NULL); + settings->repo_keys = ui_list_new(obj->ctx, NULL); + settings->repo_tls_versions = ui_list_new(obj->ctx, NULL); + settings->repo_encryption = ui_int_new(obj->ctx, NULL); + settings->repo_disable_verification = ui_int_new(obj->ctx, NULL); + + ui_list_append(settings->repo_tls_versions, "Default"); + ui_list_append(settings->repo_tls_versions, "TLSv1.3"); + ui_list_append(settings->repo_tls_versions, "TLSv1.2"); + ui_list_append(settings->repo_tls_versions, "TLSv1.1"); + ui_list_append(settings->repo_tls_versions, "TLSv1.0"); + + // load some list values, that can be reused + settings_update_repolist(settings); + settings_reload_keys(settings); + settings_reload_credentials(settings); + + settings->selected_repo = -1; +} + +#define SETTINGS_SET_STRING(str, setting) if(setting.value.ptr) ui_set(str, setting.value.ptr); + +void settings_edit_repository(SettingsWindow *settings, int repo_index) { + DavCfgRepository *repo = ui_list_get(settings->repos, repo_index); + if(!repo) { + fprintf(stderr, "Error: cannot get repository at index %d\n", repo_index); + return; + } + settings->selected_repo = repo_index; + + // load plain string values + SETTINGS_SET_STRING(settings->repo_name, repo->name); + SETTINGS_SET_STRING(settings->repo_url, repo->url); + SETTINGS_SET_STRING(settings->repo_cacert, repo->cert); + SETTINGS_SET_STRING(settings->repo_user, repo->user); + // load decrypted password + if(repo->password.value.ptr) { + char *decoded_pw = util_base64decode(repo->password.value.ptr); + ui_set(settings->repo_password, decoded_pw); + size_t decoded_pw_len = strlen(decoded_pw); + memset(decoded_pw, 0, decoded_pw_len); + free(decoded_pw); + } + + // select credentials dropdown value + CxList *cred = settings->repo_credentials->data; + cred->collection.cmpfunc = (cx_compare_func)strcmp; + ssize_t cred_index = repo->stored_user.value.ptr ? cxListFind(cred, repo->stored_user.value.ptr) : 0; + if(cred_index > 0) { + ui_list_setselection(settings->repo_credentials, cred_index); + } else { + ui_list_setselection(settings->repo_credentials, 0); + } + + // load encryption value and default key value + ui_set(settings->repo_encryption, repo->full_encryption.value); + CxList *keys = settings->repo_keys->data; + keys->collection.cmpfunc = (cx_compare_func)strcmp; + ssize_t key_index = repo->default_key.value.ptr ? cxListFind(keys, repo->default_key.value.ptr) : 0; + if(key_index > 0) { + ui_list_setselection(settings->repo_keys, key_index); + } else { + ui_list_setselection(settings->repo_keys, 0); + } + + // select tls version from dropdown menu + CxList *tlsVersions = settings->repo_tls_versions->data; + tlsVersions->collection.cmpfunc = (cx_compare_func)strcmp; + const char *tls_str = dav_tlsversion2str(repo->ssl_version.value); + if(!tls_str) tls_str = ""; + ssize_t tlsv_index = cxListFind(tlsVersions, tls_str); + if(tlsv_index > 0) { + ui_list_setselection(settings->repo_tls_versions, tlsv_index); + } else { + ui_list_setselection(settings->repo_tls_versions, 0); + } + + if(!repo->verification.value) { + ui_set(settings->repo_disable_verification, TRUE); + } + + + // switch to editing tab + ui_set(settings->repo_tabview, 1); +} + +/* + * sets a config value + * if new_value is null, the xml node is removed + */ +static void cfg_string_set_value_or_remove(DavConfig *config, CfgString *str, xmlNode *parent, cxstring new_value, const char *nodename) { + if(new_value.length == 0) { + new_value.ptr = NULL; + } + dav_cfg_string_set_value(config, str, parent, new_value, nodename); + if(!new_value.ptr) { + dav_cfg_string_remove(str); + } +} + +/* + * return the selected list value, if it is not the default value at index 0 + */ +static cxstring default_list_get_value(UiList *list) { + cxstring ret = { NULL, 0 }; + UiListSelection sel = ui_list_getselection(list); + if(sel.count > 0) { + int index = sel.rows[0]; + if(index > 0) { + ret = cx_str(ui_list_get(list, index)); + } + free(sel.rows); + } + return ret; +} + +void settings_store_repository(SettingsWindow *settings) { + DavConfig *config = settings->config; + DavCfgRepository *repo = ui_list_get(settings->repos, settings->selected_repo); + if(!repo) { + fprintf(stderr, "Error: cannot get repository at index %d\n", settings->selected_repo); + return; + } + + // always store name/url nodes + dav_cfg_string_set_value(config, &repo->name, repo->node, cx_str(ui_get(settings->repo_name)), "name"); + dav_cfg_string_set_value(config, &repo->url, repo->node, cx_str(ui_get(settings->repo_url)), "url"); + // store user of remove node, if no user is configured + cfg_string_set_value_or_remove(config, &repo->user, repo->node, cx_str(ui_get(settings->repo_user)), "user"); + // store cert or remove node + cfg_string_set_value_or_remove(config, &repo->cert, repo->node, cx_str(ui_get(settings->repo_cacert)), "cert"); + + + // store password or remove node, if no password is configured + char *pw = ui_get(settings->repo_password); + size_t pwlen = strlen(pw); + if(pwlen > 0) { + char *pwenc = util_base64encode(pw, pwlen); + memset(pw, 0, pwlen); + dav_cfg_string_set_value(config, &repo->password, repo->node, cx_str(pwenc), "password"); + free(pwenc); + } else { + // set password 0 NULL and remove password config node + cfg_string_set_value_or_remove(config, &repo->password, repo->node, cx_strn(NULL, 0), "password"); + } + + // get the selected credentials and set "stored-user" + // remove node if not needed + cxstring stored_user = default_list_get_value(settings->repo_credentials); + cfg_string_set_value_or_remove(config, &repo->stored_user, repo->node, stored_user, "stored-user"); + + // adjust full-encryption node if necessary + int encryption = ui_get(settings->repo_encryption); + if(encryption || repo->full_encryption.node) { + dav_cfg_bool_set_value(config, &repo->full_encryption, repo->node, encryption, "full-encryption"); + } + + // set default-key + cxstring key = default_list_get_value(settings->repo_keys); + cfg_string_set_value_or_remove(config, &repo->default_key, repo->node, key, "default-key"); + + // if disable_verification is enabled, set repo verification to false + // otherwise remove the node, because verification is enabled by default + int disable_verification = ui_get(settings->repo_disable_verification); + if(disable_verification) { + dav_cfg_bool_set_value(config, &repo->verification, repo->node, !disable_verification, "verification"); + } else { + dav_cfg_bool_remove(&repo->verification); + } + + // set tls version if configured + cxstring tlsversion_str = default_list_get_value(settings->repo_tls_versions); + int tlsversion = dav_str2ssl_version(tlsversion_str.ptr); + if(tlsversion >= 0) { + dav_cfg_int_set_value(config, &repo->ssl_version, repo->node, tlsversion, "ssl-version"); + } else { + dav_cfg_int_remove(&repo->ssl_version); + } + + settings->selected_repo = -1; +} + +void settings_update_repolist(SettingsWindow *settings) { + DavConfig *config = settings->config; + + ui_list_clear(settings->repos); + + for (DavCfgRepository *repo = config->repositories; repo; repo = repo->next) { + ui_list_append(settings->repos, repo); + } +} + +void* settings_repolist_getvalue(DavCfgRepository *repo, int col) { + switch(col) { + case 0: { + return repo->name.value.ptr; + } + case 1: { + return repo->url.value.ptr; + } + case 2: { + return repo->user.value.ptr; + } + case 3: { + return repo->full_encryption.value ? "yes" : "no"; + } + } + return NULL; +} + +void settings_reload_keys(SettingsWindow *settings) { + DavConfig *config = settings->config; + DavCfgKey *key = config->keys; + ui_list_clear(settings->repo_keys); + ui_list_append(settings->repo_keys, "-"); + while(key) { + if(key->name.value.ptr) { + ui_list_append(settings->repo_keys, key->name.value.ptr); + } + key = key->next; + } + if(settings->repo_keys->update) { + ui_list_update(settings->repo_keys); + } +} + +const char* dav_tlsversion2str(int value) { + if(value == CURL_SSLVERSION_TLSv1) { + return "TLSv1"; + } else if(value == CURL_SSLVERSION_SSLv2) { + return "SSLv2"; + } else if(value == CURL_SSLVERSION_SSLv3) { + return "SSLv3"; + } +#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7034 + else if(value == CURL_SSLVERSION_TLSv1_0) { + return "TLSv1.0"; + } else if(value == CURL_SSLVERSION_TLSv1_1) { + return "TLSv1.1"; + } else if(value == CURL_SSLVERSION_TLSv1_2) { + return "TLSv1.2"; + } +#endif +#if LIBCURL_VERSION_MAJOR * 1000 + LIBCURL_VERSION_MINOR >= 7052 + else if(value == CURL_SSLVERSION_TLSv1_3) { + return "TLSv1.3"; + } +#endif + return NULL; +} + +void settings_reload_credentials(SettingsWindow *settings) { + PwdStore *pwd = get_pwdstore(); + if(!pwd) { + return; + } + + ui_list_clear(settings->repo_credentials); + ui_list_append(settings->repo_credentials, "-"); + + CxIterator i = cxListIterator(pwd->noloc); + cx_foreach(PwdIndexEntry*, entry, i) { + ui_list_append(settings->repo_credentials, entry->id); + } + + if(settings->repo_credentials->update) { + ui_list_update(settings->repo_credentials); + } +} + +