diff options
Diffstat (limited to 'src/core')
-rw-r--r-- | src/core/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/core/Makefile.am | 2 | ||||
-rw-r--r-- | src/core/wee-command.c | 235 | ||||
-rw-r--r-- | src/core/wee-completion.c | 44 | ||||
-rw-r--r-- | src/core/wee-config.c | 10 | ||||
-rw-r--r-- | src/core/wee-debug.c | 8 | ||||
-rw-r--r-- | src/core/wee-eval.c | 46 | ||||
-rw-r--r-- | src/core/wee-network.c | 34 | ||||
-rw-r--r-- | src/core/wee-network.h | 5 | ||||
-rw-r--r-- | src/core/wee-secure.c | 1295 | ||||
-rw-r--r-- | src/core/wee-secure.h | 70 | ||||
-rw-r--r-- | src/core/wee-string.c | 96 | ||||
-rw-r--r-- | src/core/wee-string.h | 4 | ||||
-rw-r--r-- | src/core/wee-upgrade.c | 4 | ||||
-rw-r--r-- | src/core/weechat.c | 18 |
15 files changed, 1811 insertions, 61 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 9122f4234..0a4032e01 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -37,6 +37,7 @@ wee-list.c wee-list.h wee-log.c wee-log.h wee-network.c wee-network.h wee-proxy.c wee-proxy.h +wee-secure.c wee-secure.h wee-string.c wee-string.h wee-upgrade.c wee-upgrade.h wee-upgrade-file.c wee-upgrade-file.h diff --git a/src/core/Makefile.am b/src/core/Makefile.am index 297a044b5..46f6f3e51 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -55,6 +55,8 @@ lib_weechat_core_a_SOURCES = weechat.c \ wee-network.h \ wee-proxy.c \ wee-proxy.h \ + wee-secure.c \ + wee-secure.h \ wee-string.c \ wee-string.h \ wee-upgrade.c \ diff --git a/src/core/wee-command.c b/src/core/wee-command.c index 6f8577488..0ed5eb6e1 100644 --- a/src/core/wee-command.c +++ b/src/core/wee-command.c @@ -45,6 +45,7 @@ #include "wee-list.h" #include "wee-log.h" #include "wee-proxy.h" +#include "wee-secure.h" #include "wee-string.h" #include "wee-upgrade.h" #include "wee-utf8.h" @@ -4482,6 +4483,168 @@ COMMAND_CALLBACK(save) } /* + * Displays a secured data. + */ + +void +command_secure_display_data (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + /* make C compiler happy */ + (void) data; + (void) hashtable; + (void) value; + + if (key) + gui_chat_printf (NULL, " %s", key); +} + +/* + * Callback for command "/secure": manage secured data + */ + +COMMAND_CALLBACK(secure) +{ + int passphrase_was_set, count_encrypted; + + /* make C compiler happy */ + (void) data; + (void) buffer; + + /* list of secured data */ + if (argc == 1) + { + secure_buffer_open (); + return WEECHAT_RC_OK; + } + + count_encrypted = secure_hashtable_data_encrypted->items_count; + + /* decrypt data still encrypted */ + if (string_strcasecmp (argv[1], "decrypt") == 0) + { + COMMAND_MIN_ARGS(3, "secure decrypt"); + if (count_encrypted == 0) + { + gui_chat_printf (NULL, _("There is no encrypted data")); + return WEECHAT_RC_OK; + } + if (strcmp (argv[2], "-discard") == 0) + { + hashtable_remove_all (secure_hashtable_data_encrypted); + gui_chat_printf (NULL, _("Encrypted data deleted")); + return WEECHAT_RC_OK; + } + if (secure_decrypt_data_not_decrypted (argv_eol[2]) > 0) + { + gui_chat_printf (NULL, + _("Encrypted data has been successfully decrypted")); + if (secure_passphrase) + free (secure_passphrase); + secure_passphrase = strdup (argv_eol[2]); + } + else + { + gui_chat_printf (NULL, + _("%sFailed to decrypt data (wrong passphrase?)"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]); + } + return WEECHAT_RC_OK; + } + + if (count_encrypted > 0) + { + gui_chat_printf (NULL, + _("%sYou must decrypt data still encrypted before " + "doing any operation on secured data or passphrase"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]); + return WEECHAT_RC_OK; + } + + /* set the passphrase */ + if (string_strcasecmp (argv[1], "passphrase") == 0) + { + COMMAND_MIN_ARGS(3, "secure passphrase"); + passphrase_was_set = 0; + if (secure_passphrase) + { + free (secure_passphrase); + secure_passphrase = NULL; + passphrase_was_set = 1; + } + if (strcmp (argv[2], "-delete") == 0) + { + gui_chat_printf (NULL, + (passphrase_was_set) ? + _("Passphrase deleted") : _("Passphrase is not set")); + if (passphrase_was_set) + { + if (secure_hashtable_data->items_count > 0) + command_save_file (secure_config_file); + secure_buffer_display (); + } + } + else + { + secure_passphrase = strdup (argv_eol[2]); + gui_chat_printf (NULL, + (passphrase_was_set) ? + _("Passphrase changed") : _("Passphrase added")); + if (secure_hashtable_data->items_count > 0) + command_save_file (secure_config_file); + secure_buffer_display (); + } + return WEECHAT_RC_OK; + } + + /* set a secured data */ + if (string_strcasecmp (argv[1], "set") == 0) + { + COMMAND_MIN_ARGS(4, "secure set"); + hashtable_set (secure_hashtable_data, argv[2], argv_eol[3]); + gui_chat_printf (NULL, _("Secured data \"%s\" set"), argv[2]); + command_save_file (secure_config_file); + secure_buffer_display (); + return WEECHAT_RC_OK; + } + + /* delete a secured data */ + if (string_strcasecmp (argv[1], "del") == 0) + { + COMMAND_MIN_ARGS(3, "secure del"); + if (hashtable_has_key (secure_hashtable_data, argv[2])) + { + hashtable_remove (secure_hashtable_data, argv[2]); + gui_chat_printf (NULL, _("Secured data \"%s\" deleted"), argv[2]); + command_save_file (secure_config_file); + secure_buffer_display (); + } + else + { + gui_chat_printf (NULL, + _("%sSecured data \"%s\" not found"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + argv[2]); + } + return WEECHAT_RC_OK; + } + + /* toggle values on secured data buffer */ + if (string_strcasecmp (argv[1], "toggle_values") == 0) + { + if (secure_buffer) + { + secure_buffer_display_values ^= 1; + secure_buffer_display (); + } + return WEECHAT_RC_OK; + } + + return WEECHAT_RC_OK; +} + +/* * Displays a configuration section. */ @@ -5159,6 +5322,14 @@ COMMAND_CALLBACK(upgrade) return WEECHAT_RC_OK; } + /* + * set passphrase in environment var, so that it will not be asked to user + * when starting the new binary + */ + if (secure_passphrase) + setenv (SECURE_ENV_PASSPHRASE, secure_passphrase, 1); + + /* execute binary */ exec_args[0] = ptr_binary; exec_args[3] = strdup (weechat_home); execvp (exec_args[0], exec_args); @@ -5166,7 +5337,8 @@ COMMAND_CALLBACK(upgrade) /* this code should not be reached if execvp is OK */ string_iconv_fprintf (stderr, "\n\n*****\n"); string_iconv_fprintf (stderr, - _("***** Error: exec failed (program: \"%s\"), exiting WeeChat"), + _("***** Error: exec failed (program: \"%s\"), " + "exiting WeeChat"), exec_args[0]); string_iconv_fprintf (stderr, "\n*****\n\n"); @@ -6634,6 +6806,55 @@ command_init () "saved."), "%(config_files)|%*", &command_save, NULL); + hook_command (NULL, "secure", + N_("manage secured data (passwords or private data encrypted " + "in file sec.conf)"), + N_("passphrase <passphrase>|-delete" + " || decrypt <passphrase>|-discard" + " || set <name> <value>" + " || del <name>"), + N_("passphrase: set or change the passphrase used for " + "encryption (without passphrase, data is stored as " + "plain text in file sec.conf)\n" + " -delete: delete passphrase\n" + " decrypt: decrypt data still encrypted (it happens only " + "if no passphrase was given for encrypted data on startup)\n" + " -discard: discard all encrypted data (WARNING: this " + "will clear the file sec.conf)\n" + " set: add or change secured data\n" + " del: delete secured data\n\n" + "Without argument, this command displays secured data " + "in a new buffer.\n\n" + "When a passphrase is used (data encrypted), it is asked " + "by WeeChat on startup.\n" + "It is possible to set environment variable \"" + SECURE_ENV_PASSPHRASE "\" to prevent the prompt (this same " + "variable is used by WeeChat on /upgrade).\n\n" + "Secured data with format ${sec.data.xxx} can be used in:\n" + " - command line argument \"--run-command\"\n" + " - irc server options: autojoin, command, password, " + "sasl_{username|password}\n" + " - options weechat.startup.command_{before|after}_plugins\n" + " - command /eval.\n\n" + "Examples:\n" + " set a passphrase:\n" + " /secure passphrase this is my passphrase\n" + " encrypt freenode SASL password:\n" + " /secure set freenode mypassword\n" + " /set irc.server.freenode.sasl_password " + "\"${sec.data.freenode}\"\n" + " encrypt oftc password for nickserv:\n" + " /secure set oftc mypassword\n" + " /set irc.server.oftc.command \"/msg nickserv identify " + "${sec.data.oftc}\"\n" + " alias to ghost \"mynick\":\n" + " /alias ghost /eval /msg -server freenode nickserv " + "ghost mynick ${sec.data.freenode}"), + "passphrase -delete" + " || decrypt -discard" + " || set %(secured_data)" + " || del %(secured_data)", + &command_secure, NULL); hook_command (NULL, "set", N_("set config options"), N_("[<option> [<value>]] || diff [<option> [<option>...]]"), @@ -6864,12 +7085,16 @@ command_init () void command_exec_list (const char *command_list) { - char **commands, **ptr_cmd; + char *command_list2, **commands, **ptr_cmd; struct t_gui_buffer *weechat_buffer; - if (command_list && command_list[0]) + if (!command_list || !command_list[0]) + return; + + command_list2 = eval_expression (command_list, NULL, NULL); + if (command_list2 && command_list2[0]) { - commands = string_split_command (command_list, ';'); + commands = string_split_command (command_list2, ';'); if (commands) { weechat_buffer = gui_buffer_search_main (); @@ -6880,6 +7105,8 @@ command_exec_list (const char *command_list) string_free_split_command (commands); } } + if (command_list2) + free (command_list2); } /* diff --git a/src/core/wee-completion.c b/src/core/wee-completion.c index 3639d101b..65f31632c 100644 --- a/src/core/wee-completion.c +++ b/src/core/wee-completion.c @@ -39,6 +39,7 @@ #include "wee-hook.h" #include "wee-list.h" #include "wee-proxy.h" +#include "wee-secure.h" #include "wee-string.h" #include "../gui/gui-completion.h" #include "../gui/gui-bar.h" @@ -1298,6 +1299,46 @@ completion_list_add_layouts_names_cb (void *data, } /* + * Adds a secured data to completion list. + */ + +void +completion_list_map_add_secured_data_cb (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + /* make C compiler happy */ + (void) hashtable; + (void) value; + + gui_completion_list_add ((struct t_gui_completion *)data, + (const char *)key, + 0, WEECHAT_LIST_POS_SORT); +} + +/* + * Adds secured data to completion list. + */ + +int +completion_list_add_secured_data_cb (void *data, + const char *completion_item, + struct t_gui_buffer *buffer, + struct t_gui_completion *completion) +{ + /* make C compiler happy */ + (void) data; + (void) completion_item; + (void) buffer; + + hashtable_map (secure_hashtable_data, + &completion_list_map_add_secured_data_cb, + completion); + + return WEECHAT_RC_OK; +} + +/* * Adds hooks for completions done by WeeChat core. */ @@ -1392,4 +1433,7 @@ completion_init () hook_completion (NULL, "layouts_names", N_("names of layouts"), &completion_list_add_layouts_names_cb, NULL); + hook_completion (NULL, "secured_data", + N_("names of secured data (file sec.conf, section data)"), + &completion_list_add_secured_data_cb, NULL); } diff --git a/src/core/wee-config.c b/src/core/wee-config.c index b7f794713..fb86fe2ad 100644 --- a/src/core/wee-config.c +++ b/src/core/wee-config.c @@ -690,7 +690,7 @@ config_change_network_gnutls_ca_file (void *data, (void) data; (void) option; - if (network_init_ok) + if (network_init_gnutls_ok) network_set_gnutls_ca_file (); } @@ -1706,7 +1706,7 @@ config_weechat_filter_read_cb (void *data, } /* - * Writes a filter option in WeeChat configuration file. + * Writes section "filter" in WeeChat configuration file. */ int @@ -1873,12 +1873,14 @@ config_weechat_init_options () config_startup_command_after_plugins = config_file_new_option ( weechat_config_file, ptr_section, "command_after_plugins", "string", - N_("command executed when WeeChat starts, after loading plugins"), + N_("command executed when WeeChat starts, after loading plugins " + "(note: content is evaluated, see /help eval)"), NULL, 0, 0, "", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); config_startup_command_before_plugins = config_file_new_option ( weechat_config_file, ptr_section, "command_before_plugins", "string", - N_("command executed when WeeChat starts, before loading plugins"), + N_("command executed when WeeChat starts, before loading plugins " + "(note: content is evaluated, see /help eval)"), NULL, 0, 0, "", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); config_startup_display_logo = config_file_new_option ( weechat_config_file, ptr_section, diff --git a/src/core/wee-debug.c b/src/core/wee-debug.c index cf8770d24..17e645fa7 100644 --- a/src/core/wee-debug.c +++ b/src/core/wee-debug.c @@ -369,10 +369,8 @@ debug_hdata_map_cb (void *data, struct t_hashtable *hashtable, gui_chat_printf (NULL, " hdata 0x%lx: \"%s\", %d vars, %d lists:", ptr_hdata, (const char *)key, - hashtable_get_integer (ptr_hdata->hash_var, - "items_count"), - hashtable_get_integer (ptr_hdata->hash_list, - "items_count")); + ptr_hdata->hash_var->items_count, + ptr_hdata->hash_list->items_count); /* display lists */ hashtable_map (ptr_hdata->hash_list, @@ -412,7 +410,7 @@ debug_hdata () { int count; - count = hashtable_get_integer (weechat_hdata, "items_count"); + count = weechat_hdata->items_count; gui_chat_printf (NULL, ""); gui_chat_printf (NULL, "%d hdata in memory", count); diff --git a/src/core/wee-eval.c b/src/core/wee-eval.c index 2fce50097..866f44f8c 100644 --- a/src/core/wee-eval.c +++ b/src/core/wee-eval.c @@ -33,6 +33,7 @@ #include "wee-hashtable.h" #include "wee-hdata.h" #include "wee-hook.h" +#include "wee-secure.h" #include "wee-string.h" #include "../gui/gui-buffer.h" #include "../gui/gui-color.h" @@ -245,25 +246,34 @@ eval_replace_vars_cb (void *data, const char *text) return strdup (ptr_value); /* 2. look for name of option: if found, return this value */ - config_file_search_with_string (text, NULL, NULL, &ptr_option, NULL); - if (ptr_option) + if (strncmp (text, "sec.data.", 9) == 0) { - switch (ptr_option->type) + ptr_value = hashtable_get (secure_hashtable_data, text + 9); + if (ptr_value) + return strdup (ptr_value); + } + else + { + config_file_search_with_string (text, NULL, NULL, &ptr_option, NULL); + if (ptr_option) { - case CONFIG_OPTION_TYPE_BOOLEAN: - return strdup (CONFIG_BOOLEAN(ptr_option) ? EVAL_STR_TRUE : EVAL_STR_FALSE); - case CONFIG_OPTION_TYPE_INTEGER: - if (ptr_option->string_values) - return strdup (ptr_option->string_values[CONFIG_INTEGER(ptr_option)]); - snprintf (str_value, sizeof (str_value), - "%d", CONFIG_INTEGER(ptr_option)); - return strdup (str_value); - case CONFIG_OPTION_TYPE_STRING: - return strdup (CONFIG_STRING(ptr_option)); - case CONFIG_OPTION_TYPE_COLOR: - return strdup (gui_color_get_name (CONFIG_COLOR(ptr_option))); - case CONFIG_NUM_OPTION_TYPES: - return NULL; + switch (ptr_option->type) + { + case CONFIG_OPTION_TYPE_BOOLEAN: + return strdup (CONFIG_BOOLEAN(ptr_option) ? EVAL_STR_TRUE : EVAL_STR_FALSE); + case CONFIG_OPTION_TYPE_INTEGER: + if (ptr_option->string_values) + return strdup (ptr_option->string_values[CONFIG_INTEGER(ptr_option)]); + snprintf (str_value, sizeof (str_value), + "%d", CONFIG_INTEGER(ptr_option)); + return strdup (str_value); + case CONFIG_OPTION_TYPE_STRING: + return strdup (CONFIG_STRING(ptr_option)); + case CONFIG_OPTION_TYPE_COLOR: + return strdup (gui_color_get_name (CONFIG_COLOR(ptr_option))); + case CONFIG_NUM_OPTION_TYPES: + return NULL; + } } } @@ -345,7 +355,7 @@ eval_replace_vars (const char *expr, struct t_hashtable *pointers, ptr[0] = pointers; ptr[1] = extra_vars; - return string_replace_with_callback (expr, + return string_replace_with_callback (expr, "${", "}", &eval_replace_vars_cb, ptr, &errors); diff --git a/src/core/wee-network.c b/src/core/wee-network.c index a15eea8b2..aa5ea105e 100644 --- a/src/core/wee-network.c +++ b/src/core/wee-network.c @@ -55,7 +55,7 @@ #include "../plugins/plugin.h" -int network_init_ok = 0; +int network_init_gnutls_ok = 0; #ifdef HAVE_GNUTLS gnutls_certificate_credentials_t gnutls_xcred; /* GnuTLS client credentials */ @@ -63,6 +63,21 @@ gnutls_certificate_credentials_t gnutls_xcred; /* GnuTLS client credentials */ /* + * Initializes gcrypt. + */ + +void +network_init_gcrypt () +{ + if (!weechat_no_gcrypt) + { + gcry_check_version (GCRYPT_VERSION); + gcry_control (GCRYCTL_DISABLE_SECMEM, 0); + gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); + } +} + +/* * Sets trust file with option "gnutls_ca_file". */ @@ -91,11 +106,11 @@ network_set_gnutls_ca_file () } /* - * Initializes network. + * Initializes GnuTLS. */ void -network_init () +network_init_gnutls () { #ifdef HAVE_GNUTLS if (!weechat_no_gnutls) @@ -121,14 +136,7 @@ network_init () } #endif /* HAVE_GNUTLS */ - if (!weechat_no_gcrypt) - { - gcry_check_version (GCRYPT_VERSION); - gcry_control (GCRYCTL_DISABLE_SECMEM, 0); - gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0); - } - - network_init_ok = 1; + network_init_gnutls_ok = 1; } /* @@ -138,7 +146,7 @@ network_init () void network_end () { - if (network_init_ok) + if (network_init_gnutls_ok) { #ifdef HAVE_GNUTLS if (!weechat_no_gnutls) @@ -147,7 +155,7 @@ network_end () gnutls_global_deinit(); } #endif - network_init_ok = 0; + network_init_gnutls_ok = 0; } } diff --git a/src/core/wee-network.h b/src/core/wee-network.h index e421db020..10294f2cb 100644 --- a/src/core/wee-network.h +++ b/src/core/wee-network.h @@ -40,10 +40,11 @@ struct t_network_socks5 /* auth(user/pass) (2), ... */ }; -extern int network_init_ok; +extern int network_init_gnutls_ok; +extern void network_init_gcrypt (); extern void network_set_gnutls_ca_file (); -extern void network_init (); +extern void network_init_gnutls (); extern void network_end (); extern int network_pass_proxy (const char *proxy, int sock, const char *address, int port); diff --git a/src/core/wee-secure.c b/src/core/wee-secure.c new file mode 100644 index 000000000..9053d76bd --- /dev/null +++ b/src/core/wee-secure.c @@ -0,0 +1,1295 @@ +/* + * wee-secure.c - secured data configuration options (file sec.conf) + * + * Copyright (C) 2013 Sebastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <stdio.h> +#include <gcrypt.h> + +#include "weechat.h" +#include "wee-config-file.h" +#include "wee-hashtable.h" +#include "wee-secure.h" +#include "wee-string.h" +#include "../gui/gui-buffer.h" +#include "../gui/gui-chat.h" +#include "../gui/gui-color.h" +#include "../gui/gui-main.h" +#include "../gui/gui-window.h" +#include "../plugins/plugin.h" + +#define SALT_SIZE 8 + +struct t_config_file *secure_config_file = NULL; +struct t_config_section *secure_config_section_crypt = NULL; +struct t_config_section *secure_config_section_data = NULL; + +struct t_config_option *secure_config_crypt_cipher = NULL; +struct t_config_option *secure_config_crypt_hash_algo = NULL; +struct t_config_option *secure_config_crypt_passphrase_file = NULL; +struct t_config_option *secure_config_crypt_salt = NULL; + +/* the passphrase used to encrypt/decrypt data */ +char *secure_passphrase = NULL; + +/* decrypted data */ +struct t_hashtable *secure_hashtable_data = NULL; + +/* data still encrypted (if passphrase not set) */ +struct t_hashtable *secure_hashtable_data_encrypted = NULL; + +/* hash algorithms */ +char *secure_hash_algo_string[] = { "sha224", "sha256", "sha384", "sha512", + NULL }; +int secure_hash_algo[] = { GCRY_MD_SHA224, GCRY_MD_SHA256, GCRY_MD_SHA384, + GCRY_MD_SHA512 }; + +/* ciphers */ +char *secure_cipher_string[] = { "aes128", "aes192", "aes256", NULL }; +int secure_cipher[] = { GCRY_CIPHER_AES128, GCRY_CIPHER_AES192, + GCRY_CIPHER_AES256 }; + +char *secure_decrypt_error[] = { "memory", "buffer", "key", "cipher", "setkey", + "decrypt", "hash", "hash mismatch" }; + +/* used only when reading sec.conf: 1 if flag __passphrase__ is enabled */ +int secure_data_encrypted = 0; + +/* secured data buffer */ +struct t_gui_buffer *secure_buffer = NULL; +int secure_buffer_display_values = 0; + + +/* + * Searches for a hash algorithm. + * + * Returns hash algorithm value (from gcrypt constant), -1 if hash algorithm is + * not found. + */ + +int +secure_search_hash_algo (const char *hash_algo) +{ + int i; + + if (!hash_algo) + return -1; + + for (i = 0; secure_hash_algo_string[i]; i++) + { + if (strcmp (secure_hash_algo_string[i], hash_algo) == 0) + return secure_hash_algo[i]; + } + + /* hash algorithm not found */ + return -1; +} + +/* + * Searches for a cipher. + * + * Returns cipher value (from gcrypt constant), -1 if cipher is not + * found. + */ + +int +secure_search_cipher (const char *cipher) +{ + int i; + + if (!cipher) + return -1; + + for (i = 0; secure_cipher_string[i]; i++) + { + if (strcmp (secure_cipher_string[i], cipher) == 0) + return secure_cipher[i]; + } + + /* cipher not found */ + return -1; +} + +/* + * Encrypts data using a hash algorithm + cipher + passphrase. + * + * Following actions are performed: + * 1. derive a key from the passphrase (with optional salt) + * 2. compute hash of data + * 3. store hash + data in a buffer + * 4. encrypt the buffer (hash + data), using the key + * 5. return salt + encrypted hash/data + * + * Output buffer has following content: + * - salt (8 bytes, used to derive a key from the passphrase) + * - encrypted hash(data) + data + * + * So it looks like: + * + * +----------+------------+------------------------------+ + * | salt | hash | data | + * +----------+------------+------------------------------+ + * \_ _ _ _ _/\_ _ _ _ _ _ /\_ _ _ _ _ _ _ _ _ _ _ _ _ _ _/ + * 8 bytes N bytes variable length + * \_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _/ + * encrypted data + * + * Returns: + * 0: OK + * -1: not enough memory + * -2: key derive error + * -3: compute hash error + * -4: cipher open error + * -5: setkey error + * -6: encrypt error + */ + +int +secure_encrypt_data (const char *data, int length_data, + int hash_algo, int cipher, const char *passphrase, + char **encrypted, int *length_encrypted) +{ + int rc, length_salt, length_hash, length_hash_data, length_key; + gcry_md_hd_t *hd_md; + gcry_cipher_hd_t *hd_cipher; + unsigned char salt[SALT_SIZE], *ptr_hash, *key, *hash_and_data; + + rc = -1; + + hd_md = NULL; + hd_cipher = NULL; + key = NULL; + hash_and_data = NULL; + + hd_md = malloc (sizeof (gcry_md_hd_t)); + if (!hd_md) + return -1; + hd_cipher = malloc (sizeof (gcry_cipher_hd_t)); + if (!hd_cipher) + { + free (hd_md); + return -1; + } + + /* derive a key from the passphrase */ + length_key = gcry_cipher_get_algo_keylen (cipher); + key = malloc (length_key); + if (!key) + goto encend; + if (CONFIG_BOOLEAN(secure_config_crypt_salt)) + gcry_randomize (salt, SALT_SIZE, GCRY_STRONG_RANDOM); + else + { + length_salt = strlen (SECURE_SALT_DEFAULT); + if (length_salt < SALT_SIZE) + memset (salt, 0, SALT_SIZE); + memcpy (salt, SECURE_SALT_DEFAULT, + (length_salt <= SALT_SIZE) ? length_salt : SALT_SIZE); + } + if (gcry_kdf_derive (passphrase, strlen (passphrase), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + salt, SALT_SIZE, 65536, length_key, key) != 0) + { + rc = -2; + goto encend; + } + + /* compute hash of data */ + if (gcry_md_open (hd_md, hash_algo, 0) != 0) + { + rc = -3; + goto encend; + } + length_hash = gcry_md_get_algo_dlen (hash_algo); + gcry_md_write (*hd_md, data, length_data); + ptr_hash = gcry_md_read (*hd_md, hash_algo); + if (!ptr_hash) + { + rc = -3; + goto encend; + } + + /* build a buffer with hash + data */ + length_hash_data = length_hash + length_data; + hash_and_data = malloc (length_hash_data); + if (!hash_and_data) + goto encend; + memcpy (hash_and_data, ptr_hash, length_hash); + memcpy (hash_and_data + length_hash, data, length_data); + + /* encrypt hash + data */ + if (gcry_cipher_open (hd_cipher, cipher, GCRY_CIPHER_MODE_CFB, 0) != 0) + { + rc = -4; + goto encend; + } + if (gcry_cipher_setkey (*hd_cipher, key, length_key) != 0) + { + rc = -5; + goto encend; + } + if (gcry_cipher_encrypt (*hd_cipher, hash_and_data, length_hash_data, + NULL, 0) != 0) + { + rc = -6; + goto encend; + } + + /* create buffer and copy salt + encrypted hash/data into this buffer*/ + *length_encrypted = SALT_SIZE + length_hash_data; + *encrypted = malloc (*length_encrypted); + if (!*encrypted) + goto encend; + memcpy (*encrypted, salt, SALT_SIZE); + memcpy (*encrypted + SALT_SIZE, hash_and_data, length_hash_data); + + rc = 0; + +encend: + if (hd_md) + { + gcry_md_close (*hd_md); + free (hd_md); + } + if (hd_cipher) + { + gcry_cipher_close (*hd_cipher); + free (hd_cipher); + } + if (key) + free (key); + if (hash_and_data) + free (hash_and_data); + + return rc; +} + +/* + * Decrypts data using a hash algorithm + cipher + passphrase. + * + * The buffer must contain: + * - salt (8 bytes, used to derive a key from the passphrase) + * - encrypted hash(data) + data + * + * Following actions are performed: + * 1. check length of buffer (it must have at least salt + hash + some data) + * 2. derive a key from the passphrase using salt (at beginning of buffer) + * 3. decrypt hash + data in a buffer + * 4. compute hash of decrypted data + * 5. check that decrypted hash is equal to hash of data + * 6. return decrypted data + * + * Returns: + * 0: OK + * -1: not enough memory + * -2: buffer is not long enough + * -3: key derive error + * -4: cipher open error + * -5: setkey error + * -6: decrypt error + * -7: compute hash error + * -8: hash does not match the decrypted data + * + * Note: when adding a return code, change the array "secure_decrypt_error" + * accordingly. + */ + +int +secure_decrypt_data (const char *buffer, int length_buffer, + int hash_algo, int cipher, const char *passphrase, + char **decrypted, int *length_decrypted) +{ + int rc, length_hash, length_key; + gcry_md_hd_t *hd_md; + gcry_cipher_hd_t *hd_cipher; + unsigned char *ptr_hash, *key, *decrypted_hash_data; + + rc = -1; + + /* check length of buffer */ + length_hash = gcry_md_get_algo_dlen (hash_algo); + if (length_buffer <= SALT_SIZE + length_hash) + return -2; + + hd_md = NULL; + hd_cipher = NULL; + key = NULL; + decrypted_hash_data = NULL; + + hd_md = malloc (sizeof (gcry_md_hd_t)); + if (!hd_md) + return rc; + hd_cipher = malloc (sizeof (gcry_cipher_hd_t)); + if (!hd_cipher) + { + free (hd_md); + return rc; + } + + /* derive a key from the passphrase */ + length_key = gcry_cipher_get_algo_keylen (cipher); + key = malloc (length_key); + if (!key) + goto decend; + if (gcry_kdf_derive (passphrase, strlen (passphrase), + GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256, + buffer, SALT_SIZE, 65536, length_key, key) != 0) + { + rc = -3; + goto decend; + } + + /* decrypt hash + data */ + decrypted_hash_data = malloc (length_buffer - SALT_SIZE); + if (!decrypted_hash_data) + goto decend; + if (gcry_cipher_open (hd_cipher, cipher, GCRY_CIPHER_MODE_CFB, 0) != 0) + { + rc = -4; + goto decend; + } + if (gcry_cipher_setkey (*hd_cipher, key, length_key) != 0) + { + rc = -5; + goto decend; + } + if (gcry_cipher_decrypt (*hd_cipher, + decrypted_hash_data, length_buffer - SALT_SIZE, + buffer + SALT_SIZE, length_buffer - SALT_SIZE) != 0) + { + rc = -6; + goto decend; + } + + /* check if hash is OK for decrypted data */ + if (gcry_md_open (hd_md, hash_algo, 0) != 0) + { + rc = -7; + goto decend; + } + gcry_md_write (*hd_md, decrypted_hash_data + length_hash, + length_buffer - SALT_SIZE - length_hash); + ptr_hash = gcry_md_read (*hd_md, hash_algo); + if (!ptr_hash) + { + rc = -7; + goto decend; + } + if (memcmp (ptr_hash, decrypted_hash_data, length_hash) != 0) + { + rc = -8; + goto decend; + } + + /* return the decrypted data */ + *length_decrypted = length_buffer - SALT_SIZE - length_hash; + *decrypted = malloc (*length_decrypted); + if (!*decrypted) + goto decend; + + memcpy (*decrypted, decrypted_hash_data + length_hash, *length_decrypted); + + rc = 0; + +decend: + if (hd_md) + { + gcry_md_close (*hd_md); + free (hd_md); + } + if (hd_cipher) + { + gcry_cipher_close (*hd_cipher); + free (hd_cipher); + } + if (key) + free (key); + if (decrypted_hash_data) + free (decrypted_hash_data); + + return rc; +} + +/* + * Decrypts data still encrypted (data that could not be decrypted when reading + * secured data configuration file (because no passphrase was given). + * + * Returns: + * > 0: number of decrypted data + * 0: error decrypting data + */ + +int +secure_decrypt_data_not_decrypted (const char *passphrase) +{ + char **keys, *buffer, *decrypted; + const char *value; + int num_ok, num_keys, i, length_buffer, length_decrypted, rc; + + /* we need a passphrase to decrypt data! */ + if (!passphrase || !passphrase[0]) + return 0; + + num_ok = 0; + + keys = string_split (hashtable_get_string (secure_hashtable_data_encrypted, + "keys"), + ",", 0, 0, &num_keys); + if (keys) + { + for (i = 0; i < num_keys; i++) + { + value = hashtable_get (secure_hashtable_data_encrypted, keys[i]); + if (value && value[0]) + { + buffer = malloc (strlen (value) + 1); + if (buffer) + { + length_buffer = string_decode_base16 (value, buffer); + decrypted = NULL; + length_decrypted = 0; + rc = secure_decrypt_data (buffer, + length_buffer, + secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)], + secure_cipher[CONFIG_INTEGER(secure_config_crypt_cipher)], + passphrase, + &decrypted, + &length_decrypted); + if ((rc == 0) && decrypted) + { + hashtable_set (secure_hashtable_data, keys[i], + decrypted); + hashtable_remove (secure_hashtable_data_encrypted, + keys[i]); + num_ok++; + } + if (decrypted) + free (decrypted); + free (buffer); + } + } + } + string_free_split (keys); + } + + return num_ok; +} + +/* + * Gets passphrase from user and puts it in variable "secure_passphrase". + */ + +void +secure_get_passphrase_from_user (const char *error) +{ + char passphrase[1024]; + + while (1) + { + gui_main_get_password (_("Please enter your passphrase to decrypt the " + "data secured by WeeChat:"), + _("(enter just one space to skip the passphrase, " + "but this will DISABLE all secured data!)"), + error, + passphrase, sizeof (passphrase)); + if (secure_passphrase) + { + free (secure_passphrase); + secure_passphrase = NULL; + } + if (passphrase[0]) + { + /* the special value " " (one space) disables passphrase */ + if (strcmp (passphrase, " ") == 0) + { + gui_chat_printf (NULL, + _("To recover your secured data, you can issue " + "commands: \"/secure passphrase xxx\" and " + "\"/reload sec\"")); + } + else + secure_passphrase = strdup (passphrase); + return; + } + } +} + +/* + * Gets passphrase from a file. + * + * Returns passphrase read in file (only the first line with max length of + * 1024 chars), or NULL if error. + */ + +char * +secure_get_passphrase_from_file (const char *filename) +{ + FILE *file; + char *passphrase, *filename2, buffer[1024+1], *pos; + size_t num_read; + + passphrase = NULL; + + filename2 = string_expand_home (filename); + if (!filename2) + return NULL; + + file = fopen (filename2, "r"); + if (file) + { + num_read = fread (buffer, 1, sizeof (buffer) - 1, file); + if (num_read > 0) + { + buffer[num_read] = '\0'; + pos = strchr (buffer, '\r'); + if (pos) + pos[0] = '\0'; + pos = strchr (buffer, '\n'); + if (pos) + pos[0] = '\0'; + if (buffer[0]) + passphrase = strdup (buffer); + } + fclose (file); + } + + free (filename2); + + return passphrase; +} + +/* + * Checks option "sec.crypt.passphrase_file". + */ + +int +secure_check_crypt_passphrase_file (void *data, + struct t_config_option *option, + const char *value) +{ + char *passphrase; + + /* make C compiler happy */ + (void) data; + (void) option; + + /* empty value is OK in option (no file used for passphrase) */ + if (!value || !value[0]) + return 1; + + passphrase = secure_get_passphrase_from_file (value); + if (passphrase) + free (passphrase); + else + { + gui_chat_printf (NULL, + _("%sWarning: unable to read passphrase from file " + "\"%s\""), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + value); + } + + return 1; +} + +/* + * Reloads secured data configuration file. + * + * Returns: + * WEECHAT_CONFIG_READ_OK: OK + * WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory + * WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found + */ + +int +secure_reload_cb (void *data, struct t_config_file *config_file) +{ + /* make C compiler happy */ + (void) data; + + if (secure_hashtable_data_encrypted->items_count > 0) + { + gui_chat_printf (NULL, + _("%sError: not possible to reload file sec.conf " + "because there is still encrypted data (use /secure " + "decrypt, see /help secure)"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]); + return WEECHAT_CONFIG_READ_FILE_NOT_FOUND; + } + + secure_data_encrypted = 0; + + /* remove all secured data */ + hashtable_remove_all (secure_hashtable_data); + + return config_file_reload (config_file); +} + +/* + * Reads a data option in secured data configuration file. + */ + +int +secure_data_read_cb (void *data, + struct t_config_file *config_file, + struct t_config_section *section, + const char *option_name, const char *value) +{ + char *buffer, *decrypted, str_error[1024]; + int length_buffer, length_decrypted, rc; + + /* make C compiler happy */ + (void) data; + (void) config_file; + (void) section; + + if (!option_name || !value || !value[0]) + { + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + } + + /* special line indicating if a passphrase must be used to decrypt data */ + if (strcmp (option_name, SECURE_DATA_PASSPHRASE_FLAG) == 0) + { + secure_data_encrypted = config_file_string_to_boolean (value); + if (secure_data_encrypted && !secure_passphrase && !gui_init_ok) + { + /* if a passphrase file is set, use it */ + if (CONFIG_STRING(secure_config_crypt_passphrase_file)[0]) + secure_passphrase = secure_get_passphrase_from_file (CONFIG_STRING(secure_config_crypt_passphrase_file)); + + /* ask passphrase to the user (if no file, or file not found) */ + if (!secure_passphrase) + secure_get_passphrase_from_user (""); + } + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + } + + if (!secure_data_encrypted) + { + /* clear data: just store value in hashtable */ + hashtable_set (secure_hashtable_data, option_name, value); + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + } + + /* check that passphrase is set */ + if (!secure_passphrase) + { + gui_chat_printf (NULL, + _("%sPassphrase is not set, unable to decrypt data \"%s\""), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + option_name); + hashtable_set (secure_hashtable_data_encrypted, option_name, value); + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + } + + /* decrypt data */ + buffer = malloc (strlen (value) + 1); + if (!buffer) + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; + + length_buffer = string_decode_base16 (value, buffer); + while (1) + { + decrypted = NULL; + length_decrypted = 0; + rc = secure_decrypt_data (buffer, + length_buffer, + secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)], + secure_cipher[CONFIG_INTEGER(secure_config_crypt_cipher)], + secure_passphrase, + &decrypted, + &length_decrypted); + if (rc == 0) + { + if (decrypted) + { + hashtable_set (secure_hashtable_data, option_name, + decrypted); + free (decrypted); + break; + } + } + else + { + if (decrypted) + free (decrypted); + if (gui_init_ok) + { + gui_chat_printf (NULL, + _("%sWrong passphrase, unable to decrypt data " + "\"%s\""), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + option_name); + break; + } + snprintf (str_error, sizeof (str_error), + _("*** Wrong passphrase (decrypt error: %s) ***"), + secure_decrypt_error[(rc * -1) - 1]); + secure_get_passphrase_from_user (str_error); + if (!secure_passphrase) + { + gui_chat_printf (NULL, + _("%sPassphrase is not set, unable to decrypt " + "data \"%s\""), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + option_name); + hashtable_set (secure_hashtable_data_encrypted, option_name, + value); + break; + } + } + } + free (buffer); + + return WEECHAT_CONFIG_OPTION_SET_OK_SAME_VALUE; +} + +/* + * Encrypts data and writes it in secured data configuration file. + */ + +void +secure_data_write_map_cb (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + struct t_config_file *config_file; + char *buffer, *buffer_base16; + int length_buffer, rc; + + /* make C compiler happy */ + (void) hashtable; + + config_file = (struct t_config_file *)data; + + buffer = NULL; + length_buffer = 0; + + if (secure_passphrase) + { + /* encrypt password using passphrase */ + rc = secure_encrypt_data (value, strlen (value) + 1, + secure_hash_algo[CONFIG_INTEGER(secure_config_crypt_hash_algo)], + secure_cipher[CONFIG_INTEGER(secure_config_crypt_cipher)], + secure_passphrase, + &buffer, + &length_buffer); + if (rc == 0) + { + if (buffer) + { + buffer_base16 = malloc ((length_buffer * 2) + 1); + if (buffer_base16) + { + string_encode_base16 (buffer, length_buffer, buffer_base16); + config_file_write_line (config_file, key, + "\"%s\"", buffer_base16); + free (buffer_base16); + } + free (buffer); + } + } + else + { + gui_chat_printf (NULL, + _("%sError encrypting data \"%s\" (%d)"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR], + key, rc); + } + } + else + { + /* store password as plain text */ + config_file_write_line (config_file, key, "\"%s\"", value); + } +} + +/* + * Writes already encrypted data in secured data configuration file. + */ + +void +secure_data_write_map_encrypted_cb (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + struct t_config_file *config_file; + + /* make C compiler happy */ + (void) hashtable; + + config_file = (struct t_config_file *)data; + + /* store data as-is (it is already encrypted) */ + config_file_write_line (config_file, key, "\"%s\"", value); +} + +/* + * Writes section "data" in secured data configuration file. + */ + +int +secure_data_write_cb (void *data, struct t_config_file *config_file, + const char *section_name) +{ + /* make C compiler happy */ + (void) data; + + /* write name of section */ + if (!config_file_write_line (config_file, section_name, NULL)) + return WEECHAT_CONFIG_WRITE_ERROR; + + if (secure_hashtable_data->items_count > 0) + { + /* + * write a special line indicating if a passphrase must be used to + * decrypt data (if not, then data is stored as plain text) + */ + if (!config_file_write_line (config_file, + SECURE_DATA_PASSPHRASE_FLAG, + (secure_passphrase) ? "on" : "off")) + { + return WEECHAT_CONFIG_WRITE_ERROR; + } + /* encrypt and write secured data */ + hashtable_map (secure_hashtable_data, + &secure_data_write_map_cb, config_file); + } + else if (secure_hashtable_data_encrypted->items_count > 0) + { + /* + * if there is encrypted data, that means passphrase was not set and + * we were unable to decrypt => just save the encrypted content + * as-is (so that content of sec.conf is not lost) + */ + if (!config_file_write_line (config_file, + SECURE_DATA_PASSPHRASE_FLAG, "on")) + { + return WEECHAT_CONFIG_WRITE_ERROR; + } + hashtable_map (secure_hashtable_data_encrypted, + &secure_data_write_map_encrypted_cb, config_file); + } + + return WEECHAT_CONFIG_WRITE_OK; +} + +/* + * Creates options in secured data configuration. + * + * Returns: + * 1: OK + * 0: error + */ + +int +secure_init_options () +{ + struct t_config_section *ptr_section; + + secure_config_file = config_file_new (NULL, SECURE_CONFIG_NAME, + &secure_reload_cb, NULL); + if (!secure_config_file) + return 0; + + /* crypt */ + ptr_section = config_file_new_section (secure_config_file, "crypt", + 0, 0, + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL); + if (!ptr_section) + { + config_file_free (secure_config_file); + return 0; + } + + secure_config_crypt_cipher = config_file_new_option ( + secure_config_file, ptr_section, + "cipher", "integer", + N_("cipher used to crypt data (the number after algorithm is the size " + "of the key in bits)"), + "aes128|aes192|aes256", 0, 0, "aes256", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + secure_config_crypt_hash_algo = config_file_new_option ( + secure_config_file, ptr_section, + "hash_algo", "integer", + N_("hash algorithm used to check the decrypted data"), + "sha224|sha256|sha384|sha512", 0, 0, "sha256", NULL, 0, + NULL, NULL, NULL, NULL, NULL, NULL); + secure_config_crypt_passphrase_file = config_file_new_option ( + secure_config_file, ptr_section, + "passphrase_file", "string", + N_("path to a file containing the passphrase to encrypt/decrypt secured " + "data (used only when reading file sec.conf); only first line of " + "file is used; this file is used only if the environment variable \"" + SECURE_ENV_PASSPHRASE "\" is not set (the environment variable has " + "higher priority); security note: it is recommended to keep this " + "file readable only by you and store it outside WeeChat home (for " + "example in your home); example: \"~/.weechat-passphrase\""), + NULL, 0, 0, "", NULL, 0, + &secure_check_crypt_passphrase_file, NULL, NULL, NULL, NULL, NULL); + secure_config_crypt_salt = config_file_new_option ( + secure_config_file, ptr_section, + "salt", "boolean", + N_("use salt when generating key used in encryption (recommended for " + "maximum security); when enabled, the content of crypted data in " + "file sec.conf will be different on each write of the file; if you " + "put the file sec.conf in a version control system, then you " + "can turn off this option to have always same content in file"), + NULL, 0, 0, "on", NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL); + + /* data */ + ptr_section = config_file_new_section (secure_config_file, "data", + 0, 0, + &secure_data_read_cb, NULL, + &secure_data_write_cb, NULL, + &secure_data_write_cb, NULL, + NULL, NULL, NULL, NULL); + if (!ptr_section) + { + config_file_free (secure_config_file); + return 0; + } + + return 1; +} + +/* + * Initializes secured data configuration. + * + * Returns: + * 1: OK + * 0: error + */ + +int +secure_init () +{ + int rc; + char *ptr_phrase; + + /* try to read passphrase (if not set) from env var "WEECHAT_PASSPHRASE" */ + if (!secure_passphrase) + { + ptr_phrase = getenv (SECURE_ENV_PASSPHRASE); + if (ptr_phrase) + { + if (ptr_phrase[0]) + secure_passphrase = strdup (ptr_phrase); + unsetenv (SECURE_ENV_PASSPHRASE); + } + } + + secure_hashtable_data = hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (!secure_hashtable_data) + return 0; + + secure_hashtable_data_encrypted = hashtable_new (32, + WEECHAT_HASHTABLE_STRING, + WEECHAT_HASHTABLE_STRING, + NULL, + NULL); + if (!secure_hashtable_data_encrypted) + { + hashtable_free (secure_hashtable_data); + return 0; + } + + rc = secure_init_options (); + + if (!rc) + { + gui_chat_printf (NULL, + _("FATAL: error initializing configuration options")); + } + + return rc; +} + +/* + * Reads secured data configuration file. + * + * Returns: + * WEECHAT_CONFIG_READ_OK: OK + * WEECHAT_CONFIG_READ_MEMORY_ERROR: not enough memory + * WEECHAT_CONFIG_READ_FILE_NOT_FOUND: file not found + */ + +int +secure_read () +{ + int rc; + + secure_data_encrypted = 0; + + rc = config_file_read (secure_config_file); + + if (rc != WEECHAT_CONFIG_READ_OK) + { + gui_chat_printf (NULL, + _("%sError reading configuration"), + gui_chat_prefix[GUI_CHAT_PREFIX_ERROR]); + } + + return rc; +} + +/* + * Writes secured data configuration file. + * + * Returns: + * WEECHAT_CONFIG_WRITE_OK: OK + * WEECHAT_CONFIG_WRITE_ERROR: error + * WEECHAT_CONFIG_WRITE_MEMORY_ERROR: not enough memory + */ + +int +secure_write () +{ + return config_file_write (secure_config_file); +} + +/* + * Frees secured data file and variables. + */ + +void +secure_free () +{ + config_file_free (secure_config_file); + + if (secure_hashtable_data) + { + hashtable_free (secure_hashtable_data); + secure_hashtable_data = NULL; + } + if (secure_hashtable_data_encrypted) + { + hashtable_free (secure_hashtable_data_encrypted); + secure_hashtable_data_encrypted = NULL; + } +} + +/* + * Displays a secured data. + */ + +void +secure_buffer_display_data (void *data, + struct t_hashtable *hashtable, + const void *key, const void *value) +{ + int *line; + + /* make C compiler happy */ + (void) value; + + line = (int *)data; + + if (secure_buffer_display_values && (hashtable == secure_hashtable_data)) + { + gui_chat_printf_y (secure_buffer, (*line)++, + " %s%s = %s\"%s%s%s\"", + key, + GUI_COLOR(GUI_COLOR_CHAT_DELIMITERS), + GUI_COLOR(GUI_COLOR_CHAT), + GUI_COLOR(GUI_COLOR_CHAT_VALUE), + value, + GUI_COLOR(GUI_COLOR_CHAT)); + } + else + { + gui_chat_printf_y (secure_buffer, (*line)++, + " %s", key); + } +} + +/* + * Displays content of secured data buffer. + */ + +void +secure_buffer_display () +{ + int line, count, count_encrypted; + + if (!secure_buffer) + return; + + gui_buffer_clear (secure_buffer); + + /* set title buffer */ + gui_buffer_set_title (secure_buffer, + _("WeeChat secured data (sec.conf) | " + "Keys: [alt-v] Toggle values")); + + line = 0; + + gui_chat_printf_y (secure_buffer, line++, + "Hash algo: %s Cipher: %s Salt: %s", + secure_hash_algo_string[CONFIG_INTEGER(secure_config_crypt_hash_algo)], + secure_cipher_string[CONFIG_INTEGER(secure_config_crypt_cipher)], + (CONFIG_BOOLEAN(secure_config_crypt_salt)) ? _("on") : _("off")); + + /* display passphrase */ + line++; + if (secure_passphrase) + { + if (secure_buffer_display_values) + { + gui_chat_printf_y (secure_buffer, line++, + "%s%s = %s\"%s%s%s\"", + _("Passphrase"), + GUI_COLOR(GUI_COLOR_CHAT_DELIMITERS), + GUI_COLOR(GUI_COLOR_CHAT), + GUI_COLOR(GUI_COLOR_CHAT_VALUE), + secure_passphrase, + GUI_COLOR(GUI_COLOR_CHAT)); + } + else + gui_chat_printf_y (secure_buffer, line++, _("Passphrase is set")); + } + else + { + gui_chat_printf_y (secure_buffer, line++, + _("Passphrase is NOT set")); + } + + /* display secured data */ + count = secure_hashtable_data->items_count; + count_encrypted = secure_hashtable_data_encrypted->items_count; + if (count > 0) + { + line++; + gui_chat_printf_y (secure_buffer, line++, _("Secured data:")); + line++; + hashtable_map (secure_hashtable_data, + &secure_buffer_display_data, &line); + } + /* display secured data not decrypted */ + if (count_encrypted > 0) + { + line++; + gui_chat_printf_y (secure_buffer, line++, + _("Secured data STILL ENCRYPTED: (use /secure decrypt, " + "see /help secure)")); + line++; + hashtable_map (secure_hashtable_data_encrypted, + &secure_buffer_display_data, &line); + } + if ((count == 0) && (count_encrypted == 0)) + { + line++; + gui_chat_printf_y (secure_buffer, line++, _("No secured data set")); + } +} + +/* + * Input callback for secured data buffer. + */ + +int +secure_buffer_input_cb (void *data, struct t_gui_buffer *buffer, + const char *input_data) +{ + /* make C compiler happy */ + (void) data; + + if (string_strcasecmp (input_data, "q") == 0) + { + gui_buffer_close (buffer); + } + + return WEECHAT_RC_OK; +} + +/* + * Close callback for secured data buffer. + */ + +int +secure_buffer_close_cb (void *data, struct t_gui_buffer *buffer) +{ + /* make C compiler happy */ + (void) data; + (void) buffer; + + secure_buffer = NULL; + + return WEECHAT_RC_OK; +} + +/* + * Assigns secured data buffer to pointer if it is not yet set. + */ + +void +secure_buffer_assign () +{ + if (!secure_buffer) + { + secure_buffer = gui_buffer_search_by_name (NULL, SECURE_BUFFER_NAME); + if (secure_buffer) + { + secure_buffer->input_callback = &secure_buffer_input_cb; + secure_buffer->close_callback = &secure_buffer_close_cb; + } + } +} + +/* + * Opens a buffer to display secured data. + */ + +void +secure_buffer_open () +{ + if (!secure_buffer) + { + secure_buffer = gui_buffer_new (NULL, SECURE_BUFFER_NAME, + &secure_buffer_input_cb, NULL, + &secure_buffer_close_cb, NULL); + if (secure_buffer) + { + if (!secure_buffer->short_name) + secure_buffer->short_name = strdup (SECURE_BUFFER_NAME); + gui_buffer_set (secure_buffer, "type", "free"); + gui_buffer_set (secure_buffer, "localvar_set_no_log", "1"); + gui_buffer_set (secure_buffer, "key_bind_meta-v", "/secure toggle_values"); + } + secure_buffer_display_values = 0; + } + + if (!secure_buffer) + return; + + gui_window_switch_to_buffer (gui_current_window, secure_buffer, 1); + + secure_buffer_display (); +} diff --git a/src/core/wee-secure.h b/src/core/wee-secure.h new file mode 100644 index 000000000..9db72fcd4 --- /dev/null +++ b/src/core/wee-secure.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013 Sebastien Helleu <flashcode@flashtux.org> + * + * This file is part of WeeChat, the extensible chat client. + * + * WeeChat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * WeeChat is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with WeeChat. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef __WEECHAT_SECURE_H +#define __WEECHAT_SECURE_H 1 + +#define SECURE_CONFIG_NAME "sec" + +#define SECURE_ENV_PASSPHRASE "WEECHAT_PASSPHRASE" +#define SECURE_SALT_DEFAULT "WeeChat!" +#define SECURE_DATA_PASSPHRASE_FLAG "__passphrase__" + +#define SECURE_BUFFER_NAME "secured_data" + +enum t_secure_config_hash_algo +{ + SECURE_CONFIG_HASH_SHA224 = 0, + SECURE_CONFIG_HASH_SHA256, + SECURE_CONFIG_HASH_SHA384, + SECURE_CONFIG_HASH_SHA512, +}; + +enum t_secure_config_cipher +{ + SECURE_CONFIG_CIPHER_AES128 = 0, + SECURE_CONFIG_CIPHER_AES192, + SECURE_CONFIG_CIPHER_AES256, +}; + +extern struct t_config_file *secure_config_file; +extern struct t_config_section *secure_config_section_pwd; + +extern struct t_config_option *secure_config_crypt_cipher; +extern struct t_config_option *secure_config_crypt_hash_algo; +extern struct t_config_option *secure_config_crypt_passphrase_file; +extern struct t_config_option *secure_config_crypt_salt; + +extern char *secure_passphrase; +extern struct t_hashtable *secure_hashtable_data; +extern struct t_hashtable *secure_hashtable_data_encrypted; + +extern struct t_gui_buffer *secure_buffer; +extern int secure_buffer_display_values; + +extern int secure_decrypt_data_not_decrypted (const char *passphrase); +extern int secure_init (); +extern int secure_read (); +extern int secure_write (); +extern void secure_free (); +extern void secure_buffer_display (); +extern void secure_buffer_assign (); +extern void secure_buffer_open (); + +#endif /* __WEECHAT_SECURE_H */ diff --git a/src/core/wee-string.c b/src/core/wee-string.c index 94402b52c..34ad3b1bb 100644 --- a/src/core/wee-string.c +++ b/src/core/wee-string.c @@ -1797,6 +1797,78 @@ string_format_size (unsigned long long size) } /* + * Encodes a string in base16 (hexadecimal). + * + * Argument "length" is number of bytes in "from" to convert (commonly + * strlen(from)). + */ + +void +string_encode_base16 (const char *from, int length, char *to) +{ + int i; + const char *hexa = "0123456789ABCDEF"; + char *ptr_to; + + ptr_to = to; + ptr_to[0] = '\0'; + for (i = 0; i < length; i++) + { + ptr_to[0] = hexa[((unsigned char)from[i]) / 16]; + ptr_to[1] = hexa[((unsigned char)from[i]) % 16]; + ptr_to += 2; + } + ptr_to[0] = '\0'; +} + +/* + * Decodes a base16 string (hexadecimal). + * + * Returns length of string in "*to" (it does not count final \0). + */ + +int +string_decode_base16 (const char *from, char *to) +{ + int length, to_length, i, pos; + unsigned char *ptr_to, value; + + length = strlen (from) / 2; + + ptr_to = (unsigned char *)to; + ptr_to[0] = '\0'; + to_length = 0; + + for (i = 0; i < length; i++) + { + pos = i * 2; + value = 0; + /* 4 bits on the left */ + if ((from[pos] >= '0') && (from[pos] <= '9')) + value |= (from[pos] - '0') << 4; + else if ((from[pos] >= 'a') && (from[pos] <= 'f')) + value |= (from[pos] - 'a' + 10) << 4; + else if ((from[pos] >= 'A') && (from[pos] <= 'F')) + value |= (from[pos] - 'A' + 10) << 4; + /* 4 bits on the right */ + pos++; + if ((from[pos] >= '0') && (from[pos] <= '9')) + value |= from[pos] - '0'; + else if ((from[pos] >= 'a') && (from[pos] <= 'f')) + value |= from[pos] - 'a' + 10; + else if ((from[pos] >= 'A') && (from[pos] <= 'F')) + value |= from[pos] - 'A' + 10; + + ptr_to[0] = value; + ptr_to++; + to_length++; + } + ptr_to[0] = '\0'; + + return to_length; +} + +/* * Converts 3 bytes of 8 bits in 4 bytes of 6 bits. */ @@ -2031,19 +2103,25 @@ string_input_for_buffer (const char *string) char * string_replace_with_callback (const char *string, + const char *prefix, + const char *suffix, char *(*callback)(void *data, const char *text), void *callback_data, int *errors) { - int length, length_value, index_string, index_result; + int length_prefix, length_suffix, length, length_value, index_string; + int index_result; char *result, *result2, *key, *value; const char *pos_end_name; *errors = 0; - if (!string) + if (!string || !prefix || !prefix[0] || !suffix || !suffix[0]) return NULL; + length_prefix = strlen (prefix); + length_suffix = strlen (suffix); + length = strlen (string) + 1; result = malloc (length); if (result) @@ -2053,19 +2131,18 @@ string_replace_with_callback (const char *string, while (string[index_string]) { if ((string[index_string] == '\\') - && (string[index_string + 1] == '$')) + && (string[index_string + 1] == prefix[0])) { index_string++; result[index_result++] = string[index_string++]; } - else if ((string[index_string] == '$') - && (string[index_string + 1] == '{')) + else if (strncmp (string + index_string, prefix, length_prefix) == 0) { - pos_end_name = strchr (string + index_string + 2, '}'); + pos_end_name = strstr (string + index_string + length_prefix, suffix); if (pos_end_name) { - key = string_strndup (string + index_string + 2, - pos_end_name - (string + index_string + 2)); + key = string_strndup (string + index_string + length_prefix, + pos_end_name - (string + index_string + length_prefix)); if (key) { value = (*callback) (callback_data, key); @@ -2086,7 +2163,7 @@ string_replace_with_callback (const char *string, strcpy (result + index_result, value); index_result += length_value; index_string += pos_end_name - string - - index_string + 1; + index_string + length_suffix; free (value); } else @@ -2094,7 +2171,6 @@ string_replace_with_callback (const char *string, result[index_result++] = string[index_string++]; (*errors)++; } - free (key); } else diff --git a/src/core/wee-string.h b/src/core/wee-string.h index 0e6e50568..f79795707 100644 --- a/src/core/wee-string.h +++ b/src/core/wee-string.h @@ -72,11 +72,15 @@ extern char *string_iconv_from_internal (const char *charset, const char *string); extern int string_iconv_fprintf (FILE *file, const char *data, ...); extern char *string_format_size (unsigned long long size); +extern void string_encode_base16 (const char *from, int length, char *to); +extern int string_decode_base16 (const char *from, char *to); extern void string_encode_base64 (const char *from, int length, char *to); extern int string_decode_base64 (const char *from, char *to); extern int string_is_command_char (const char *string); extern const char *string_input_for_buffer (const char *string); extern char *string_replace_with_callback (const char *string, + const char *prefix, + const char *suffix, char *(*callback)(void *data, const char *text), void *callback_data, int *errors); diff --git a/src/core/wee-upgrade.c b/src/core/wee-upgrade.c index 44f19f12c..09af3d0b2 100644 --- a/src/core/wee-upgrade.c +++ b/src/core/wee-upgrade.c @@ -32,6 +32,7 @@ #include "wee-upgrade.h" #include "wee-hook.h" #include "wee-infolist.h" +#include "wee-secure.h" #include "wee-string.h" #include "wee-util.h" #include "../gui/gui-buffer.h" @@ -729,6 +730,9 @@ upgrade_weechat_load () gui_color_buffer_assign (); gui_color_buffer_display (); + secure_buffer_assign (); + secure_buffer_display (); + if (upgrade_layout->layout_buffers) gui_layout_buffer_apply (upgrade_layout); if (upgrade_layout->layout_windows) diff --git a/src/core/weechat.c b/src/core/weechat.c index 984477cc7..750493b95 100644 --- a/src/core/weechat.c +++ b/src/core/weechat.c @@ -61,6 +61,7 @@ #include "wee-log.h" #include "wee-network.h" #include "wee-proxy.h" +#include "wee-secure.h" #include "wee-string.h" #include "wee-upgrade.h" #include "wee-utf8.h" @@ -440,14 +441,19 @@ main (int argc, char *argv[]) command_init (); /* initialize WeeChat commands */ completion_init (); /* add core completion hooks */ gui_key_init (); /* init keys */ - if (!config_weechat_init ()) /* init options with default values */ + network_init_gcrypt (); /* init gcrypt */ + if (!secure_init ()) /* init secured data options (sec.*)*/ + exit (EXIT_FAILURE); + if (!config_weechat_init ()) /* init WeeChat options (weechat.*) */ exit (EXIT_FAILURE); weechat_parse_args (argc, argv); /* parse command line args */ weechat_create_home_dir (); /* create WeeChat home directory */ log_init (); /* init log file */ - if (config_weechat_read () < 0) /* read WeeChat configuration */ + if (secure_read () < 0) /* read secured data options */ + exit (EXIT_FAILURE); + if (config_weechat_read () < 0) /* read WeeChat options */ exit (EXIT_FAILURE); - network_init (); /* init networking */ + network_init_gnutls (); /* init GnuTLS */ gui_main_init (); /* init WeeChat interface */ if (weechat_upgrading) { @@ -470,10 +476,12 @@ main (int argc, char *argv[]) gui_layout_save_on_exit (); /* save layout */ plugin_end (); /* end plugin interface(s) */ if (CONFIG_BOOLEAN(config_look_save_config_on_exit)) - (void) config_weechat_write (NULL); /* save WeeChat config file */ + (void) config_weechat_write (); /* save WeeChat config file */ + (void) secure_write (); /* save secured data */ gui_main_end (1); /* shut down WeeChat GUI */ proxy_free_all (); /* free all proxies */ - config_weechat_free (); /* free weechat.conf and vars */ + config_weechat_free (); /* free WeeChat options */ + secure_free (); /* free secured data options */ config_file_free_all (); /* free all configuration files */ gui_key_end (); /* remove all keys */ unhook_all (); /* remove all hooks */ |