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