Tue, 29 Oct 2024 17:51:02 +0100
implement key management UI
application/config.c | file | annotate | diff | comparison | revisions | |
application/settings.c | file | annotate | diff | comparison | revisions | |
application/settings.h | file | annotate | diff | comparison | revisions | |
libidav/config.c | file | annotate | diff | comparison | revisions | |
libidav/config.h | file | annotate | diff | comparison | revisions | |
ui/gtk/text.c | file | annotate | diff | comparison | revisions | |
ui/gtk/text.h | file | annotate | diff | comparison | revisions | |
ui/gtk/window.c | file | annotate | diff | comparison | revisions |
--- a/application/config.c Tue Oct 29 11:51:25 2024 +0100 +++ b/application/config.c Tue Oct 29 17:51:02 2024 +0100 @@ -189,6 +189,9 @@ void set_config(DavConfig *cfg) { + if(davconfig) { + dav_config_free(davconfig); + } davconfig = cfg; } @@ -214,7 +217,7 @@ } // should only fail if we run out of disk space or something like that - // in that case, the config file is only destroyed + // in that case, the config file is destroyed // could only be prevented, if we write to a temp file first and than // rename it fwrite(buf->space, buf->size, 1, cout);
--- a/application/settings.c Tue Oct 29 11:51:25 2024 +0100 +++ b/application/settings.c Tue Oct 29 17:51:02 2024 +0100 @@ -39,6 +39,9 @@ #define SETTINGS_STATE_REPO_ENCRYPTION 20 #define SETTINGS_STATE_CREDENTIALS_SELECTED 30 #define SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED 31 +#define SETTINGS_STATE_KEYS_SELECTED 40 + +#define SETTINGS_STATE_DISABLED 9999 static void repolist_activate(UiEvent *event, void *userdata) { @@ -186,6 +189,9 @@ static void credentials_location_edit(UiEvent *event, void *userdata) { SettingsWindow *settings = event->window; char *location = ui_list_get(settings->credentials_locations, settings->credentials_location_selected_index); + if(!location) { + return; + } ui_dialog(event->obj, .title = "Edit Location", .content = "Location URL", @@ -267,6 +273,178 @@ .closebutton_label = "Cancel"); } + + + +#define ADDKEY_DIALOG_STATE_NO_IMPORT 10 + +static void addkey_onclick(UiEvent *event, void *userdata) { + SettingsNewKeyDialog *wdata = event->window; + if(event->intval == 4) { + ui_set(wdata->message, ""); + + // validate form + char *name = ui_get(wdata->name); + if(strlen(name) == 0) { + ui_set(wdata->message, "A name must be specified!"); + return; + } + char *file = ui_get(wdata->file); + if(strlen(file) == 0) { + ui_set(wdata->message, "A file path must be specified!"); + return; + } + UiListSelection sel = ui_list_getselection(wdata->type); + if(sel.count == 0) { + ui_set(wdata->message, "No type selected!"); + return; + } + DavCfgKeyType type = sel.rows[0]; + + // generate key file + char *path = NULL; + char *cfg_path = NULL; + if(file[0] == '/') { + path = file; + } else { + cfg_path = config_file_path(file); + path = cfg_path; + } + FILE *f = fopen(path, "w"); + if(!f) { + cxmutstr msg = cx_asprintf("Error: cannot write file '%s': %s", path, strerror(errno)); + ui_set(wdata->message, msg.ptr); + free(msg.ptr); + free(cfg_path); + return; + } + free(cfg_path); + + unsigned char key[32]; + dav_rand_bytes(key, 32); + + size_t len; + switch(type) { + default: + case DAV_KEY_TYPE_AES256: len = 32; break; + case DAV_KEY_TYPE_AES128: len = 16; break; + } + size_t w = fwrite(key, 1, len, f); + fclose(f); + + if(w != len) { + cxmutstr msg = cx_asprintf("Could not write key data: %s", strerror(errno)); + ui_set(wdata->message, msg.ptr); + free(msg.ptr); + return; + } + + // add key to config + DavCfgKey *newkey = dav_key_new(wdata->settings->config); + const CxAllocator *a = wdata->settings->config->mp->allocator; + newkey->name.value = cx_strdup_a(a, cx_str(name)); + newkey->file.value = cx_strdup_a(a, cx_str(file)); + newkey->type = type; + + dav_config_add_key(wdata->settings->config, newkey); + settings_reload_repo_keys(wdata->settings); + settings_reload_keys(wdata->settings); + } + + ui_close(event->obj); +} + +static void addkey_name_changed(UiEvent *event, void *userdata) { + SettingsNewKeyDialog *wdata = event->window; + char *name = ui_get(wdata->name); + cxmutstr file = cx_asprintf("keys/%s", name); + ui_set(wdata->file, file.ptr); + free(file.ptr); +} + +static void keys_add(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + + UiObject *dialog = ui_dialog_window(event->obj, + .modal = UI_ON, + .title = "Add Key", + .show_closebutton = UI_OFF, + .lbutton1 = "Cancel", + .rbutton4 = "Add", + .default_button = 4, + .onclick = addkey_onclick); + SettingsNewKeyDialog *wdata = ui_malloc(dialog->ctx, sizeof(SettingsNewKeyDialog)); + wdata->settings = settings; + wdata->name = ui_string_new(dialog->ctx, NULL); + wdata->file = ui_string_new(dialog->ctx, NULL); + wdata->import_path = ui_string_new(dialog->ctx, NULL); + wdata->message = ui_string_new(dialog->ctx, NULL); + wdata->secretstore = ui_int_new(dialog->ctx, NULL); + wdata->type = ui_list_new(dialog->ctx, NULL); + dialog->window = wdata; + + ui_list_append(wdata->type, "AES256"); + ui_list_append(wdata->type, "AES128"); + + ui_grid(dialog, .margin = 16, .columnspacing = 40, .rowspacing = 10) { + /* + ui_hbox(dialog, .colspan = 2) { + ui_button(dialog, .label = "Import Key..."); + } + ui_newline(dialog); + ui_llabel(dialog, .value = wdata->import_path); + ui_newline(dialog); + */ + + ui_llabel(dialog, .label = "Name"); + ui_textfield(dialog, .value = wdata->name, .onchange = addkey_name_changed, .hexpand = TRUE); + ui_newline(dialog); + + ui_llabel(dialog, .label = "File"); + ui_textfield(dialog, .value = wdata->file); + ui_newline(dialog); + + ui_llabel(dialog, .label = "Type"); + ui_combobox(dialog, .list = wdata->type, .groups = UI_GROUPS(ADDKEY_DIALOG_STATE_NO_IMPORT)); + ui_newline(dialog); + + ui_llabel(dialog, .value = wdata->message, .colspan = 2); + } + + ui_set_group(dialog->ctx, ADDKEY_DIALOG_STATE_NO_IMPORT); + ui_list_setselection(wdata->type, 0); + + ui_show(dialog); +} + +static void keys_remove(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + DavCfgKey *key = ui_list_get(settings->keys_list, settings->keys_selected_index); + if(key) { + dav_key_remove_and_free(settings->config, key); + ui_list_remove(settings->keys_list, settings->keys_selected_index); + ui_list_update(settings->keys_list); + settings_reload_repo_keys(settings); + } +} + +static void keys_onselect(UiEvent *event, void *userdata) { + SettingsWindow *settings = event->window; + UiListSelection *sel = event->eventdata; + if(sel->count > 0) { + settings->keys_selected_index = sel->rows[0]; + DavCfgKey *key = ui_list_get(settings->keys_list, sel->rows[0]); + if(key) { + settings_edit_key(settings, key); + ui_set_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); + } + } else { + settings->keys_selected_index = -1; + settings_clear_key(settings); + ui_unset_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED); + } +} + static void list_str_destructor(void *data, void *ptr) { UiContext *ctx = data; char *s = ptr; @@ -290,7 +468,7 @@ settings_store_repository(settings); settings_credentials_save(settings); - set_config(settings->config); // TODO: free old config + set_config(settings->config); if(store_config()) { ui_dialog(event->obj, .title = "Error", .content = "Cannot store settings", .closebutton_label = "OK"); } @@ -461,13 +639,13 @@ } ui_tab(obj, "Credentials") { - ui_hbox(obj, .margin = 16, .spacing = 10) { - ui_vbox(obj, .fill = UI_OFF) { - ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect); + ui_hbox(obj, .margin = 16, .spacing = 30) { + ui_vbox(obj, .fill = UI_OFF, .spacing = 4) { ui_hbox(obj, .fill = UI_OFF, .spacing = 4) { ui_button(obj, .label = "Add", .onclick = credentials_add); ui_button(obj, .label = "Remove", .onclick = credentials_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED)); } + ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect); } ui_grid(obj, .columnspacing = 30, .rowspacing = 10) { @@ -506,16 +684,41 @@ } ui_tab(obj, "Keys") { - ui_grid(obj, .margin = 10) { - ui_label(obj, .label = "TODO"); + ui_hbox(obj, .margin = 16, .spacing = 30) { + ui_vbox(obj, .fill = UI_OFF, .spacing = 4) { + ui_hbox(obj, .fill = UI_OFF, .spacing = 4) { + ui_button(obj, .label = "Add", .onclick = keys_add); + ui_button(obj, .label = "Remove", .onclick = keys_remove, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + } + ui_listview(obj, .list = wdata->keys_list, .fill = UI_ON, .onselection = keys_onselect, .getvalue = keylist_getvalue); + } + + ui_grid(obj, .columnspacing = 30, .rowspacing = 10) { + ui_llabel(obj, .label = "Identifier"); + ui_hbox0(obj) { + ui_textfield(obj, .value = wdata->key_name, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + } + ui_newline(obj); + ui_llabel(obj, .label = "Type"); + ui_hbox0(obj) { + ui_textfield(obj, .value = wdata->key_type, .groups = UI_GROUPS(SETTINGS_STATE_DISABLED)); + } + ui_newline(obj); + ui_llabel(obj, .label = "File"); + ui_hbox0(obj) { + ui_textfield(obj, .value = wdata->key_file, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED)); + } + } } } + /* ui_tab(obj, "Properties") { ui_grid(obj, .margin = 10) { ui_label(obj, .label = "TODO"); } } + */ } ui_hbox(obj, .fill = UI_OFF, .margin = 10) { @@ -564,11 +767,18 @@ credentials_locations->collection.destructor_data = settings->obj->ctx; credentials_locations->collection.cmpfunc = (cx_compare_func)strcmp; + settings->keys_list = ui_list_new(obj->ctx, NULL); + settings->key_name = ui_string_new(obj->ctx, NULL); + settings->key_type = ui_string_new(obj->ctx, NULL); + settings->key_file = ui_string_new(obj->ctx, NULL); + settings->keys_selected_index = -1; + // load some list values, that can be reused settings_update_repolist(settings); - settings_reload_keys(settings); + settings_reload_repo_keys(settings); settings_reload_credentials(settings); settings_reload_repo_credentials(settings); + settings_reload_keys(settings); settings->selected_repo = -1; } @@ -796,7 +1006,7 @@ return NULL; } -void settings_reload_keys(SettingsWindow *settings) { +void settings_reload_repo_keys(SettingsWindow *settings) { DavConfig *config = settings->config; DavCfgKey *key = config->keys; ui_list_clear(settings->repo_keys); @@ -807,9 +1017,20 @@ } key = key->next; } - if(settings->repo_keys->update) { - ui_list_update(settings->repo_keys); + ui_list_update(settings->repo_keys); +} + +void settings_reload_keys(SettingsWindow *settings) { + DavConfig *config = settings->config; + DavCfgKey *key = config->keys; + ui_list_clear(settings->keys_list); + while(key) { + if(key->name.value.ptr) { + ui_list_append(settings->keys_list, key); + } + key = key->next; } + ui_list_update(settings->keys_list); } const char* dav_tlsversion2str(int value) { @@ -1013,3 +1234,42 @@ return 0; } + +void *keylist_getvalue(void *data, int col) { + DavCfgKey *key = data; + return key->name.value.ptr; +} + +void settings_edit_key(SettingsWindow *settings, DavCfgKey *key) { + if(key->name.value.ptr) { + ui_set(settings->key_name, key->name.value.ptr); + } + const char *key_type; + switch(key->type) { + default: key_type = ""; break; + case DAV_KEY_TYPE_AES256: { + key_type = "AES256"; + break; + } + case DAV_KEY_TYPE_AES128: { + key_type = "AES128"; + break; + } + case DAV_KEY_TYPE_UNKNOWN: { + key_type = "unknown"; + break; + } + } + ui_set(settings->key_type, key_type); + + if(key->file.value.ptr) { + ui_set(settings->key_file, key->file.value.ptr); + } +} + +void settings_clear_key(SettingsWindow *settings) { + ui_set(settings->key_name, ""); + ui_set(settings->key_type, ""); + ui_set(settings->key_file, ""); +} +
--- a/application/settings.h Tue Oct 29 11:51:25 2024 +0100 +++ b/application/settings.h Tue Oct 29 17:51:02 2024 +0100 @@ -103,8 +103,37 @@ */ UiList *credentials_locations; + /* + * value: char* key name + * + * List of encryption keys (DavKey) + * The value is a copy (allocated by the UiContext) + */ + UiList *keys_list; + UiString *key_name; + UiString *key_file; + UiString *key_type; + UiInteger *key_secretstore; + int keys_selected_index; + int selected_repo; } SettingsWindow; + +typedef struct SettingsNewKeyDialog { + SettingsWindow *settings; + + UiString *name; + UiString *file; + UiString *import_path; + UiString *message; + /* + * value: char* key type name + * + * Static list of DavKey type names + */ + UiList *type; + UiInteger *secretstore; +} SettingsNewKeyDialog; void settings_window_open(); @@ -137,6 +166,7 @@ void* settings_repolist_getvalue(DavCfgRepository *repo, int col); +void settings_reload_repo_keys(SettingsWindow *settings); void settings_reload_keys(SettingsWindow *settings); const char* dav_tlsversion2str(int value); @@ -160,6 +190,12 @@ int settings_credentials_save(SettingsWindow *settings); +void *keylist_getvalue(void *data, int col); + +void settings_edit_key(SettingsWindow *settings, DavCfgKey *key); + +void settings_clear_key(SettingsWindow *settings); + #ifdef __cplusplus }
--- a/libidav/config.c Tue Oct 29 11:51:25 2024 +0100 +++ b/libidav/config.c Tue Oct 29 17:51:02 2024 +0100 @@ -611,6 +611,74 @@ } +void dav_config_add_key(DavConfig *config, DavCfgKey *key) { + cx_linked_list_add( + (void**)&config->keys, + NULL, + offsetof(DavCfgKey, prev), + offsetof(DavCfgKey, next), + key); + + if(key->node) { + fprintf(stderr, "Error: dav_config_add_key: node already exists\n"); + return; + } + + xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key"); + xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(keyNode, rtext1); + key->node = keyNode; + + if(key->name.value.ptr) { + key->name.node = addXmlNode(keyNode, "name", key->name.value); + } + const char *type = dav_config_keytype_str(key->type); + if(type) { + key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type)); + } + if(key->file.value.ptr) { + key->file.node = addXmlNode(keyNode, "file", key->file.value); + } + + // indent closing tag + xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t"); + xmlAddChild(keyNode, rtext2); + + // add key element to the xml document + xmlNode *xml_root = xmlDocGetRootElement(config->doc); + + xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t"); + xmlAddChild(xml_root, text1); + + xmlAddChild(xml_root, keyNode); + + xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n"); + xmlAddChild(xml_root, text2); +} + +DavCfgKey* dav_key_new(DavConfig *config) { + DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey)); + memset(key, 0, sizeof(DavCfgKey)); + key->type = DAV_KEY_TYPE_AES256; + return key; +} + +void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) { + cx_linked_list_remove( + (void**)&config->keys, + NULL, + offsetof(DavCfgKey, prev), + offsetof(DavCfgKey, next), + key); + if(key->node) { + // TODO: remove newline after key node + + xmlUnlinkNode(key->node); + xmlFreeNode(key->node); + } +} + + static int load_key( DavConfig *config, DavCfgKey **list_begin, @@ -621,6 +689,7 @@ DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey)); memset(key, 0, sizeof(DavCfgKey)); key->type = DAV_KEY_TYPE_AES256; + key->node = keynode; int error = 0; while(node) { @@ -922,6 +991,15 @@ return 0; } +const char* dav_config_keytype_str(DavCfgKeyType type) { + switch(type) { + default: break; + case DAV_KEY_TYPE_AES256: return "aes256"; + case DAV_KEY_TYPE_AES128: return "aes128"; + } + return NULL; +} + 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;
--- a/libidav/config.h Tue Oct 29 11:51:25 2024 +0100 +++ b/libidav/config.h Tue Oct 29 17:51:02 2024 +0100 @@ -131,6 +131,8 @@ }; struct DavCfgKey { + xmlNode *node; + CfgString name; CfgString file; DavCfgKeyType type; @@ -178,6 +180,11 @@ void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password); cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo); +void dav_config_add_key(DavConfig *config, DavCfgKey *key); + +DavCfgKey* dav_key_new(DavConfig *config); +void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key); + int dav_str2ssl_version(const char *str); int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node); @@ -198,6 +205,7 @@ DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path); int dav_config_keytype(DavCfgKeyType type); +const char* dav_config_keytype_str(DavCfgKeyType type); int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey); int dav_config_register_namespaces(DavConfig *config, DavContext *ctx);
--- a/ui/gtk/text.c Tue Oct 29 11:51:25 2024 +0100 +++ b/ui/gtk/text.c Tue Oct 29 17:51:02 2024 +0100 @@ -536,8 +536,10 @@ UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING); UiTextField *uitext = malloc(sizeof(UiTextField)); - uitext->ctx = obj->ctx; + uitext->obj = obj; uitext->var = var; + uitext->onchange = args.onchange; + uitext->onchangedata = args.onchangedata; g_signal_connect( textfield, @@ -576,7 +578,9 @@ value->value.ptr = NULL; value->value.free = NULL; value->obj = GTK_ENTRY(textfield); - + } + + if(args.onchange || var) { g_signal_connect( textfield, "changed", @@ -605,15 +609,20 @@ } void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) { - // changed event is only registered, if the textfield->var != NULL UiString *value = textfield->var->value; - if(value->observers) { - UiEvent e; - e.obj = textfield->ctx->obj; - e.window = e.obj->window; - e.document = textfield->ctx->document; - e.eventdata = value; - e.intval = 0; + + UiEvent e; + e.obj = textfield->obj; + e.window = e.obj->window; + e.document = textfield->obj->ctx->document; + e.eventdata = value; + e.intval = 0; + + if(textfield->onchange) { + textfield->onchange(&e, textfield->onchangedata); + } + + if(textfield->var) { ui_notify_evt(value->observers, &e); } }
--- a/ui/gtk/text.h Tue Oct 29 11:51:25 2024 +0100 +++ b/ui/gtk/text.h Tue Oct 29 17:51:02 2024 +0100 @@ -67,9 +67,10 @@ } UiTextArea; typedef struct UiTextField { - UiContext *ctx; - UiVar *var; - // TODO: validatefunc + UiObject *obj; + UiVar *var; + ui_callback onchange; + void *onchangedata; } UiTextField; typedef struct UiPathTextField {
--- a/ui/gtk/window.c Tue Oct 29 11:51:25 2024 +0100 +++ b/ui/gtk/window.c Tue Oct 29 17:51:02 2024 +0100 @@ -659,13 +659,21 @@ #define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button) #else #define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set) -#define DEFAULT_BUTTON(window, button) gtk_window_set_default(GTK_WINDOW(window), button) +#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button) #endif UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) { GtkWidget *dialog = DIALOG_NEW(); + if(args.width > 0 || args.height > 0) { + gtk_window_set_default_size( + GTK_WINDOW(dialog), + args.width, + args.height); + } + + gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget)); if(args.modal != UI_OFF) { gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);