diff --git a/meson.build b/meson.build index ffcfd62f..289ca541 100644 --- a/meson.build +++ b/meson.build @@ -19,6 +19,7 @@ libgmodule_dep = dependency('gmodule-2.0') libcanberra_dep = dependency('libcanberra', version: '>= 0.22', required: get_option('libcanberra')) dbus_dep = dependency('gio-2.0', required: get_option('dbus')) +libsecret_dep = dependency('libsecret-1', required: false) global_deps = [] if cc.get_id() == 'msvc' @@ -40,6 +41,7 @@ config_h.set10('ENABLE_NLS', true) config_h.set('USE_OPENSSL', libssl_dep.found()) config_h.set('USE_LIBCANBERRA', libcanberra_dep.found()) config_h.set('USE_DBUS', dbus_dep.found()) +config_h.set('HAVE_LIBSECRET', libsecret_dep.found()) config_h.set('USE_PLUGIN', get_option('plugin')) config_h.set('USE_GTK_FRONTEND', get_option('gtk-frontend')) diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 468e4fe1..f870d613 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -32,6 +32,7 @@ + @@ -68,6 +69,7 @@ + diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 1a076104..5b5f19c1 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -74,6 +74,9 @@ Header Files + + Header Files + Header Files @@ -178,6 +181,9 @@ Source Files + + Source Files + Source Files diff --git a/src/common/meson.build b/src/common/meson.build index c3a9219f..ddba0d32 100644 --- a/src/common/meson.build +++ b/src/common/meson.build @@ -27,6 +27,8 @@ common_sources = [ 'util.c' ] +secretstore_sources = files('secretstore.c') + common_sysinfo_deps = [] libarchive_dep = dependency('libarchive', required: host_machine.system() != 'windows') @@ -38,6 +40,9 @@ common_deps = [ if libarchive_dep.found() common_deps += libarchive_dep endif +if libsecret_dep.found() + common_deps += libsecret_dep +endif common_includes = [ config_h_include, @@ -127,7 +132,7 @@ if get_option('plugin') endif zoitechat_common = static_library('zoitechatcommon', - sources: [textevents, public_suffix_data] + marshal + common_sources, + sources: [textevents, public_suffix_data] + marshal + common_sources + secretstore_sources, include_directories: config_h_include, dependencies: common_deps + common_sysinfo_deps, c_args: common_cflags, diff --git a/src/common/plugin.c b/src/common/plugin.c index ec765442..45069e6c 100644 --- a/src/common/plugin.c +++ b/src/common/plugin.c @@ -1213,8 +1213,6 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id) case 0x4889ba9b: /* password */ case 0x438fdf9: /* nickserv */ - if (sess->server->network) - return ((ircnet *)sess->server->network)->pass; return NULL; case 0xca022f43: /* server */ diff --git a/src/common/secretstore.c b/src/common/secretstore.c new file mode 100644 index 00000000..03616f24 --- /dev/null +++ b/src/common/secretstore.c @@ -0,0 +1,173 @@ +#include "zoitechat.h" +#include "cfgfiles.h" +#include "secretstore.h" + +#include + +#ifdef WIN32 +#include +#include +#endif + +#ifdef HAVE_LIBSECRET +#include +#endif + +static char *secretstore_target (const char *network_name) +{ + return g_strdup_printf ("zoitechat/network/%s", network_name ? network_name : "default"); +} + +int secretstore_is_keyring_available (void) +{ +#ifdef WIN32 + return TRUE; +#elif defined(HAVE_LIBSECRET) + return TRUE; +#else + return FALSE; +#endif +} + +char *secretstore_get_network_password (const char *network_name) +{ + char *target; + target = secretstore_target (network_name); +#ifdef WIN32 + { + PCREDENTIALA cred = NULL; + char *ret = NULL; + if (CredReadA (target, CRED_TYPE_GENERIC, 0, &cred)) + { + ret = g_strndup ((const char *) cred->CredentialBlob, cred->CredentialBlobSize); + CredFree (cred); + } + g_free (target); + return ret; + } +#elif defined(HAVE_LIBSECRET) + { + static const SecretSchema schema = { + "net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE, + { + { "network", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } + }; + char *ret = secret_password_lookup_sync (&schema, NULL, NULL, "network", target, NULL); + g_free (target); + return ret; + } +#else + g_free (target); + return NULL; +#endif +} + +int secretstore_set_network_password (const char *network_name, const char *password) +{ + char *target; + target = secretstore_target (network_name); +#ifdef WIN32 + { + CREDENTIALA cred; + memset (&cred, 0, sizeof (cred)); + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = target; + cred.CredentialBlobSize = (DWORD) strlen (password); + cred.CredentialBlob = (LPBYTE) password; + cred.Persist = CRED_PERSIST_LOCAL_MACHINE; + cred.UserName = "zoitechat"; + if (!CredWriteA (&cred, 0)) + { + g_free (target); + return FALSE; + } + g_free (target); + return TRUE; + } +#elif defined(HAVE_LIBSECRET) + { + static const SecretSchema schema = { + "net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE, + { + { "network", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } + }; + gboolean ok = secret_password_store_sync (&schema, SECRET_COLLECTION_DEFAULT, + "ZoiteChat network password", password, NULL, NULL, "network", target, NULL); + g_free (target); + return ok; + } +#else + g_free (target); + return FALSE; +#endif +} + +int secretstore_delete_network_password (const char *network_name) +{ + char *target; + target = secretstore_target (network_name); +#ifdef WIN32 + { + gboolean ok = CredDeleteA (target, CRED_TYPE_GENERIC, 0); + g_free (target); + return ok; + } +#elif defined(HAVE_LIBSECRET) + { + static const SecretSchema schema = { + "net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE, + { + { "network", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } + }; + gboolean ok = secret_password_clear_sync (&schema, NULL, NULL, "network", target, NULL); + g_free (target); + return ok; + } +#else + g_free (target); + return FALSE; +#endif +} + +int secretstore_require_unlock (const char *network_name) +{ +#ifdef WIN32 + return TRUE; +#elif defined(HAVE_LIBSECRET) + { + static const SecretSchema schema = { + "net.zoite.ZoiteChat.Network", SECRET_SCHEMA_NONE, + { + { "network", SECRET_SCHEMA_ATTRIBUTE_STRING }, + { NULL, 0 }, + } + }; + char *target; + char *password; + GError *error = NULL; + target = secretstore_target (network_name); + password = secret_password_lookup_sync (&schema, NULL, &error, "network", target, NULL); + g_free (target); + if (password) + { + memset (password, 0, strlen (password)); + g_free (password); + return TRUE; + } + if (error) + { + g_error_free (error); + return FALSE; + } + return TRUE; + } +#else + return TRUE; +#endif +} diff --git a/src/common/secretstore.h b/src/common/secretstore.h new file mode 100644 index 00000000..90060ad5 --- /dev/null +++ b/src/common/secretstore.h @@ -0,0 +1,10 @@ +#ifndef ZOITECHAT_SECRETSTORE_H +#define ZOITECHAT_SECRETSTORE_H + +char *secretstore_get_network_password (const char *network_name); +int secretstore_set_network_password (const char *network_name, const char *password); +int secretstore_delete_network_password (const char *network_name); +int secretstore_is_keyring_available (void); +int secretstore_require_unlock (const char *network_name); + +#endif diff --git a/src/common/servlist.c b/src/common/servlist.c index 29d9af09..8eadad3d 100644 --- a/src/common/servlist.c +++ b/src/common/servlist.c @@ -33,9 +33,146 @@ #include "text.h" #include "util.h" /* token_foreach */ #include "zoitechatc.h" +#include "secretstore.h" #include "servlist.h" +#ifdef USE_OPENSSL +#include +#include +#endif + +char * +servlist_password_encrypt_for_storage (const char *pass) +{ + gchar *material; + unsigned char salt[16]; + unsigned char iv[16]; + unsigned char key[32]; + unsigned char *ciphertext; + int outlen1; + int outlen2; + int inlen; + int cipherlen; + EVP_CIPHER_CTX *ctx; + char *b64; + char *ret; + if (!pass || !*pass) + return NULL; +#ifdef USE_OPENSSL + if (RAND_bytes (salt, sizeof (salt)) != 1 || RAND_bytes (iv, sizeof (iv)) != 1) + return NULL; + material = g_strdup_printf ("%s|%s", g_get_user_name (), get_xdir ()); + if (!PKCS5_PBKDF2_HMAC (material, -1, salt, sizeof (salt), 300000, EVP_sha256 (), sizeof (key), key)) + { + g_free (material); + return NULL; + } + g_free (material); + inlen = (int) strlen (pass); + ciphertext = g_malloc (inlen + EVP_MAX_BLOCK_LENGTH); + ctx = EVP_CIPHER_CTX_new (); + if (!ctx) + { + memset (key, 0, sizeof (key)); + g_free (ciphertext); + return NULL; + } + if (EVP_EncryptInit_ex (ctx, EVP_aes_256_cbc (), NULL, key, iv) != 1 || + EVP_EncryptUpdate (ctx, ciphertext, &outlen1, (const unsigned char *) pass, inlen) != 1 || + EVP_EncryptFinal_ex (ctx, ciphertext + outlen1, &outlen2) != 1) + { + EVP_CIPHER_CTX_free (ctx); + memset (key, 0, sizeof (key)); + g_free (ciphertext); + return NULL; + } + EVP_CIPHER_CTX_free (ctx); + cipherlen = outlen1 + outlen2; + { + gsize payload_len = sizeof (salt) + sizeof (iv) + (gsize) cipherlen; + unsigned char *payload = g_malloc (payload_len); + memcpy (payload, salt, sizeof (salt)); + memcpy (payload + sizeof (salt), iv, sizeof (iv)); + memcpy (payload + sizeof (salt) + sizeof (iv), ciphertext, cipherlen); + b64 = g_base64_encode (payload, payload_len); + memset (payload, 0, payload_len); + g_free (payload); + } + memset (key, 0, sizeof (key)); + memset (ciphertext, 0, inlen + EVP_MAX_BLOCK_LENGTH); + g_free (ciphertext); +#else + b64 = g_base64_encode ((const guchar *) pass, strlen (pass)); +#endif + ret = g_strdup_printf ("enc:%s", b64); + g_free (b64); + return ret; +} + +static char * +servlist_decrypt_password (const char *enc) +{ + guchar *raw; + gsize len; + char *ret; + if (!enc || !*enc) + return NULL; + if (!g_str_has_prefix (enc, "enc:")) + return g_strdup (enc); + raw = g_base64_decode (enc + 4, &len); +#ifdef USE_OPENSSL + if (len <= 32) + { + g_free (raw); + return NULL; + } + { + unsigned char *salt = raw; + unsigned char *iv = raw + 16; + unsigned char *ciphertext = raw + 32; + int cipherlen = (int) (len - 32); + unsigned char key[32]; + gchar *material = g_strdup_printf ("%s|%s", g_get_user_name (), get_xdir ()); + unsigned char *plaintext = g_malloc ((gsize) cipherlen + 1); + int outlen1; + int outlen2; + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new (); + if (!ctx || !PKCS5_PBKDF2_HMAC (material, -1, salt, 16, 300000, EVP_sha256 (), sizeof (key), key)) + { + g_free (material); + if (ctx) + EVP_CIPHER_CTX_free (ctx); + g_free (plaintext); + g_free (raw); + return NULL; + } + g_free (material); + if (EVP_DecryptInit_ex (ctx, EVP_aes_256_cbc (), NULL, key, iv) != 1 || + EVP_DecryptUpdate (ctx, plaintext, &outlen1, ciphertext, cipherlen) != 1 || + EVP_DecryptFinal_ex (ctx, plaintext + outlen1, &outlen2) != 1) + { + EVP_CIPHER_CTX_free (ctx); + memset (key, 0, sizeof (key)); + memset (plaintext, 0, (gsize) cipherlen + 1); + g_free (plaintext); + g_free (raw); + return NULL; + } + EVP_CIPHER_CTX_free (ctx); + memset (key, 0, sizeof (key)); + plaintext[outlen1 + outlen2] = 0; + ret = g_strdup ((const char *) plaintext); + memset (plaintext, 0, (gsize) cipherlen + 1); + g_free (plaintext); + } +#else + ret = g_strndup ((const char *) raw, len); +#endif + g_free (raw); + return ret; +} + struct defaultserver { @@ -344,10 +481,29 @@ servlist_connect (session *sess, ircnet *net, gboolean join) } serv->password[0] = 0; - - if (net->pass) + if ((net->flags & FLAG_USE_KEYRING) && net->name) { - safe_strcpy (serv->password, net->pass, sizeof (serv->password)); + char *stored_pass = secretstore_get_network_password (net->name); + if (stored_pass && *stored_pass) + { + safe_strcpy (serv->password, stored_pass, sizeof (serv->password)); + } + if (stored_pass) + { + memset (stored_pass, 0, strlen (stored_pass)); + g_free (stored_pass); + } + } + else if (net->pass) + { + char *plain = servlist_decrypt_password (net->pass); + if (plain && *plain) + safe_strcpy (serv->password, plain, sizeof (serv->password)); + if (plain) + { + memset (plain, 0, strlen (plain)); + g_free (plain); + } } if (net->flags & FLAG_USE_GLOBAL) @@ -982,24 +1138,6 @@ servlist_load (void) * * Should be removed at some point. */ - case 'A': - if (!net->pass) - { - net->pass = g_strdup (buf + 2); - if (!net->logintype) - { - net->logintype = LOGIN_SASL; - } - } - case 'B': - if (!net->pass) - { - net->pass = g_strdup (buf + 2); - if (!net->logintype) - { - net->logintype = LOGIN_NICKSERV; - } - } } } if (buf[0] == 'N') diff --git a/src/common/servlist.h b/src/common/servlist.h index f7ebdc15..fc90ca6f 100644 --- a/src/common/servlist.h +++ b/src/common/servlist.h @@ -62,7 +62,8 @@ extern GSList *network_list; #define FLAG_USE_PROXY 16 #define FLAG_ALLOW_INVALID 32 #define FLAG_FAVORITE 64 -#define FLAG_COUNT 7 +#define FLAG_USE_KEYRING 128 +#define FLAG_COUNT 8 /* Login methods. Use server password by default - if we had a NickServ password, it'd be set to 2 already by servlist_load() */ #define LOGIN_DEFAULT_REAL LOGIN_PASS /* this is to set the default login method for unknown servers */ @@ -124,5 +125,6 @@ favchannel *servlist_favchan_copy (favchannel *fav); GSList *servlist_favchan_listadd (GSList *chanlist, char *channel, char *key); gboolean joinlist_is_in_list (server *serv, char *channel); +char *servlist_password_encrypt_for_storage (const char *pass); #endif diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index c2a8d62e..5d7dfd7c 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -29,6 +29,7 @@ #include "../common/servlist.h" #include "../common/cfgfiles.h" #include "../common/fe.h" +#include "../common/secretstore.h" #include "../common/util.h" #include "fe-gtk.h" @@ -85,6 +86,11 @@ static GtkWidget *edit_entry_nick2; static GtkWidget *edit_entry_user; static GtkWidget *edit_entry_real; static GtkWidget *edit_entry_pass; +static GtkWidget *edit_check_show_pass; +static GtkWidget *edit_check_use_keyring; +static GtkWidget *edit_button_load_pass; +static int edit_pass_changed; +static char *edit_loaded_password; static GtkWidget *edit_label_nick; static GtkWidget *edit_label_nick2; static GtkWidget *edit_label_real; @@ -104,6 +110,68 @@ static session *servlist_sess; static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data); static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net); +static void +servlist_toggle_show_password_cb (GtkToggleButton *toggle, gpointer userdata) +{ + if (gtk_toggle_button_get_active (toggle)) + { + if (edit_loaded_password) + gtk_entry_set_text (GTK_ENTRY (userdata), edit_loaded_password); + gtk_entry_set_visibility (GTK_ENTRY (userdata), TRUE); + } + else + { + gtk_entry_set_visibility (GTK_ENTRY (userdata), FALSE); + if (edit_loaded_password && !edit_pass_changed) + gtk_entry_set_text (GTK_ENTRY (userdata), "***"); + } +} + + +static void +servlist_toggle_keyring_cb (GtkToggleButton *toggle, gpointer userdata) +{ + gboolean active = gtk_toggle_button_get_active (toggle); + gtk_widget_set_sensitive (edit_button_load_pass, active); + gtk_widget_set_sensitive (edit_check_show_pass, active); + if (!active) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_show_pass), FALSE); +} + +static void +servlist_password_changed_cb (GtkEditable *editable, gpointer userdata) +{ + edit_pass_changed = 1; + if (edit_loaded_password && strcmp (gtk_entry_get_text (GTK_ENTRY (editable)), "***")) + { + memset (edit_loaded_password, 0, strlen (edit_loaded_password)); + g_free (edit_loaded_password); + edit_loaded_password = NULL; + } +} + +static void +servlist_load_password_cb (GtkWidget *button, gpointer userdata) +{ + ircnet *net = userdata; + char *password; + if (!net || !net->name) + return; + password = secretstore_get_network_password (net->name); + if (!password) + return; + if (edit_loaded_password) + { + memset (edit_loaded_password, 0, strlen (edit_loaded_password)); + g_free (edit_loaded_password); + } + edit_loaded_password = g_strdup (password); + gtk_entry_set_text (GTK_ENTRY (edit_entry_pass), "***"); + edit_pass_changed = 0; + memset (password, 0, strlen (password)); + g_free (password); +} + static char * servlist_get_cert_file (ircnet *net) { @@ -1081,11 +1149,40 @@ servlist_update_from_entry (char **str, GtkWidget *entry) static void servlist_edit_update (ircnet *net) { + const char *password; servlist_update_from_entry (&net->nick, edit_entry_nick); servlist_update_from_entry (&net->nick2, edit_entry_nick2); servlist_update_from_entry (&net->user, edit_entry_user); servlist_update_from_entry (&net->real, edit_entry_real); - servlist_update_from_entry (&net->pass, edit_entry_pass); + password = gtk_entry_get_text (GTK_ENTRY (edit_entry_pass)); + if (net && net->name) + { + if (!edit_pass_changed) + return; + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring))) + { + net->flags |= FLAG_USE_KEYRING; + g_free (net->pass); + net->pass = NULL; + if (password && *password) + { + if (!secretstore_set_network_password (net->name, password)) + fe_message (_("No system keyring is available. ZoiteChat can save this password using local encrypted fallback storage, but it is less protected than your desktop keyring."), FE_MSG_WARN); + } + else + secretstore_delete_network_password (net->name); + } + else + { + char *enc = NULL; + net->flags &= ~FLAG_USE_KEYRING; + secretstore_delete_network_password (net->name); + if (password && *password) + enc = servlist_password_encrypt_for_storage (password); + g_free (net->pass); + net->pass = enc; + } + } } static void @@ -1093,6 +1190,12 @@ servlist_edit_close_cb (GtkWidget *button, gpointer userdata) { if (selected_net) servlist_edit_update (selected_net); + if (edit_loaded_password) + { + memset (edit_loaded_password, 0, strlen (edit_loaded_password)); + g_free (edit_loaded_password); + edit_loaded_password = NULL; + } gtk_widget_destroy (edit_win); edit_win = NULL; @@ -1126,6 +1229,10 @@ servlist_edit_cb (GtkWidget *but, gpointer none) { if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) return; + if (!selected_net || !selected_net->name) + return; + if ((selected_net->flags & FLAG_USE_KEYRING) && !secretstore_require_unlock (selected_net->name)) + return; edit_win = servlist_open_edit (serverlist_win, selected_net); gtkutil_set_icon (edit_win); @@ -2317,7 +2424,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) /* Checkboxes and entries */ - table3 = gtkutil_grid_new (14, 2, FALSE); + table3 = gtkutil_grid_new (16, 2, FALSE); gtk_box_pack_start (GTK_BOX (vbox5), table3, FALSE, FALSE, 0); gtk_grid_set_row_spacing (GTK_GRID (table3), 2); gtk_grid_set_column_spacing (GTK_GRID (table3), 8); @@ -2336,38 +2443,86 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) #endif servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, 5, 0, _("Use global user information")); - edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 6, net->nick, &edit_label_nick, 0); - edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 7, net->nick2, &edit_label_nick2, 0); - edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, &edit_label_real, 0); - edit_entry_user = servlist_create_entry (table3, _("_User name:"), 9, net->user, &edit_label_user, 0); + edit_check_use_keyring = gtk_check_button_new_with_mnemonic (_("Use system keyring")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring), net->flags & FLAG_USE_KEYRING); + servlist_table_attach (table3, edit_check_use_keyring, 0, 2, 6, 7, + FALSE, FALSE, + SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, + SERVLIST_X_PADDING, SERVLIST_Y_PADDING); + g_signal_connect (G_OBJECT (edit_check_use_keyring), "toggled", + G_CALLBACK (servlist_toggle_keyring_cb), NULL); + + edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 7, net->nick, &edit_label_nick, 0); + edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 8, net->nick2, &edit_label_nick2, 0); + edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 9, net->real, &edit_label_real, 0); + edit_entry_user = servlist_create_entry (table3, _("_User name:"), 10, net->user, &edit_label_user, 0); label_logintype = gtk_label_new (_("Login method:")); - servlist_table_attach (table3, label_logintype, 0, 1, 10, 11, + servlist_table_attach (table3, label_logintype, 0, 1, 11, 12, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_X_PADDING, SERVLIST_Y_PADDING); gtk_widget_set_halign (label_logintype, GTK_ALIGN_START); gtk_widget_set_valign (label_logintype, GTK_ALIGN_CENTER); combobox_logintypes = servlist_create_logintypecombo (notebook); - servlist_table_attach (table3, combobox_logintypes, 1, 2, 10, 11, + servlist_table_attach (table3, combobox_logintypes, 1, 2, 11, 12, FALSE, FALSE, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, 4, 2); - edit_entry_pass = servlist_create_entry (table3, _("Password:"), 11, net->pass, 0, _("Password used for login. If in doubt, leave blank.")); + edit_entry_pass = servlist_create_entry (table3, _("Password:"), 12, NULL, 0, _("Password used for login. If in doubt, leave blank.")); + if (edit_loaded_password) + { + memset (edit_loaded_password, 0, strlen (edit_loaded_password)); + g_free (edit_loaded_password); + edit_loaded_password = NULL; + } + edit_pass_changed = 0; + g_signal_connect (G_OBJECT (edit_entry_pass), "changed", + G_CALLBACK (servlist_password_changed_cb), NULL); + if (net->flags & FLAG_USE_KEYRING) + { + char *stored = secretstore_get_network_password (net->name); + if (stored && *stored) + gtk_entry_set_text (GTK_ENTRY (edit_entry_pass), "***"); + if (stored) + { + memset (stored, 0, strlen (stored)); + g_free (stored); + } + } + else if (net->pass && *net->pass) + { + gtk_entry_set_text (GTK_ENTRY (edit_entry_pass), "***"); + } + edit_pass_changed = 0; gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL) gtk_widget_set_sensitive (edit_entry_pass, FALSE); + edit_button_load_pass = gtk_button_new_with_mnemonic (_("Load from keyring")); + servlist_table_attach (table3, edit_button_load_pass, 0, 1, 13, 14, + FALSE, FALSE, + SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, + SERVLIST_X_PADDING, SERVLIST_Y_PADDING); + g_signal_connect (G_OBJECT (edit_button_load_pass), "clicked", + G_CALLBACK (servlist_load_password_cb), net); + edit_check_show_pass = gtk_check_button_new_with_mnemonic (_("Show password")); + servlist_table_attach (table3, edit_check_show_pass, 1, 2, 13, 14, + FALSE, FALSE, + SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, + 4, 2); + g_signal_connect (G_OBJECT (edit_check_show_pass), "toggled", + G_CALLBACK (servlist_toggle_show_password_cb), edit_entry_pass); label34 = gtk_label_new (_("Character set:")); - servlist_table_attach (table3, label34, 0, 1, 12, 13, + servlist_table_attach (table3, label34, 0, 1, 14, 15, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_X_PADDING, SERVLIST_Y_PADDING); gtk_widget_set_halign (label34, GTK_ALIGN_START); gtk_widget_set_valign (label34, GTK_ALIGN_CENTER); comboboxentry_charset = servlist_create_charsetcombo (); - servlist_table_attach (table3, comboboxentry_charset, 1, 2, 12, 13, + servlist_table_attach (table3, comboboxentry_charset, 1, 2, 14, 15, FALSE, FALSE, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, 4, 2); @@ -2393,7 +2548,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) G_CALLBACK (servlist_delete_client_cert_cb), net); gtk_box_pack_start (GTK_BOX (hbox_cert_buttons), edit_button_cert_delete, FALSE, FALSE, 0); - servlist_table_attach (table3, hbox_cert_buttons, 0, 2, 13, 14, + servlist_table_attach (table3, hbox_cert_buttons, 0, 2, 15, 16, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_X_PADDING, SERVLIST_Y_PADDING); @@ -2417,6 +2572,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) { servlist_toggle_global_user (FALSE); } + servlist_toggle_keyring_cb (GTK_TOGGLE_BUTTON (edit_check_use_keyring), NULL); gtk_widget_grab_focus (button10); gtk_widget_grab_default (button10); diff --git a/win32/zoitechat.props b/win32/zoitechat.props index 60b23b9d..b6d1ccf1 100644 --- a/win32/zoitechat.props +++ b/win32/zoitechat.props @@ -128,7 +128,7 @@ LIBARCHIVE_STATIC - $(Gtk3Lib);$(Gdk3Lib);wininet.lib;winmm.lib;ws2_32.lib;atk-1.0.lib;gio-2.0.lib;gdk_pixbuf-2.0.lib;pangowin32-1.0.lib;pangocairo-1.0.lib;pango-1.0.lib;cairo.lib;gobject-2.0.lib;gmodule-2.0.lib;glib-2.0.lib;$(IntlLib);$(IconvLib);$(ZlibLib);$(Xml2Lib);$(JpegLib);$(PngLib);$(ArchiveLib);$(OpenSslLibs) + $(Gtk3Lib);$(Gdk3Lib);wininet.lib;winmm.lib;ws2_32.lib;advapi32.lib;atk-1.0.lib;gio-2.0.lib;gdk_pixbuf-2.0.lib;pangowin32-1.0.lib;pangocairo-1.0.lib;pango-1.0.lib;cairo.lib;gobject-2.0.lib;gmodule-2.0.lib;glib-2.0.lib;$(IntlLib);$(IconvLib);$(ZlibLib);$(Xml2Lib);$(JpegLib);$(PngLib);$(ArchiveLib);$(OpenSslLibs) $(SolutionDir)..\data\\ $(SolutionDir)..\..\zoitechat-build