diff --git a/src/common/fe.h b/src/common/fe.h index 3047aab0..27ea3a26 100644 --- a/src/common/fe.h +++ b/src/common/fe.h @@ -142,6 +142,7 @@ void fe_set_away (server *serv); void fe_serverlist_open (session *sess); void fe_get_bool (char *title, char *prompt, void *callback, void *userdata); void fe_get_str (char *prompt, char *def, void *callback, void *ud); +void fe_get_password (char *prompt, void *callback, void *ud); void fe_get_int (char *prompt, int def, void *callback, void *ud); #define FRF_WRITE 1 /* save file */ #define FRF_MULTIPLE 2 /* multi-select */ diff --git a/src/common/servlist.c b/src/common/servlist.c index d05d5d1d..9e4c22b6 100644 --- a/src/common/servlist.c +++ b/src/common/servlist.c @@ -439,8 +439,99 @@ servlist_favchan_copy (favchannel *fav) return newfav; } +static void servlist_connect_with_password (session *sess, ircnet *net, gboolean join, const char *password); + +typedef struct +{ + session *sess; + ircnet *net; + gboolean join; +} servlist_password_prompt; + +static gboolean +servlist_login_uses_password_sasl (int logintype) +{ + return logintype == LOGIN_SASL || + logintype == LOGIN_SASL_SCRAM_SHA_1 || + logintype == LOGIN_SASL_SCRAM_SHA_256 || + logintype == LOGIN_SASL_SCRAM_SHA_512; +} + +static void servlist_prompt_sasl_password (servlist_password_prompt *prompt); + +static void +servlist_connect_without_password_cb (int value, void *userdata) +{ + servlist_password_prompt *prompt = userdata; + + if (value) + { + servlist_connect_with_password (prompt->sess, prompt->net, prompt->join, NULL); + g_free (prompt); + } + else + { + servlist_prompt_sasl_password (prompt); + } +} + +static void +servlist_connect_password_cb (int cancel, char *text, void *userdata) +{ + servlist_password_prompt *prompt = userdata; + char *msg; + + if (cancel) + { + fe_serverlist_open (prompt->sess); + g_free (prompt); + return; + } + + if (text && *text) + { + servlist_connect_with_password (prompt->sess, prompt->net, prompt->join, text); + g_free (prompt); + return; + } + + msg = g_strdup_printf (_("Continue connecting to %s without a SASL password?"), prompt->net->name ? prompt->net->name : _("this network")); + fe_get_bool (_("No SASL password"), msg, servlist_connect_without_password_cb, prompt); + g_free (msg); +} + +static void +servlist_prompt_sasl_password (servlist_password_prompt *prompt) +{ + char *msg; + + msg = g_strdup_printf (_("Enter SASL password for %s:"), prompt->net->name ? prompt->net->name : _("this network")); + fe_get_password (msg, servlist_connect_password_cb, prompt); + g_free (msg); +} + void servlist_connect (session *sess, ircnet *net, gboolean join) +{ + int logintype; + servlist_password_prompt *prompt; + + logintype = net && net->logintype ? net->logintype : LOGIN_DEFAULT_REAL; + if (net && (net->flags & FLAG_PROMPT_PASSWORD) && servlist_login_uses_password_sasl (logintype)) + { + prompt = g_new0 (servlist_password_prompt, 1); + prompt->sess = sess; + prompt->net = net; + prompt->join = join; + servlist_prompt_sasl_password (prompt); + return; + } + + servlist_connect_with_password (sess, net, join, NULL); +} + +static void +servlist_connect_with_password (session *sess, ircnet *net, gboolean join, const char *password) { ircserver *ircserv; GSList *list; @@ -487,7 +578,11 @@ servlist_connect (session *sess, ircnet *net, gboolean join) } serv->password[0] = 0; - if ((net->flags & FLAG_USE_KEYRING) && net->name) + if (password && *password) + { + safe_strcpy (serv->password, password, sizeof (serv->password)); + } + else if ((net->flags & FLAG_USE_KEYRING) && net->name) { char *stored_pass = secretstore_get_network_password (net->name); if (stored_pass && *stored_pass) diff --git a/src/common/servlist.h b/src/common/servlist.h index b5c286d0..42f5f280 100644 --- a/src/common/servlist.h +++ b/src/common/servlist.h @@ -63,7 +63,8 @@ extern GSList *network_list; #define FLAG_ALLOW_INVALID 32 #define FLAG_FAVORITE 64 #define FLAG_USE_KEYRING 128 -#define FLAG_COUNT 8 +#define FLAG_PROMPT_PASSWORD 256 +#define FLAG_COUNT 9 /* 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 */ diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c index 86e8d2fc..6ecefc1e 100644 --- a/src/fe-gtk/gtkutil.c +++ b/src/fe-gtk/gtkutil.c @@ -527,6 +527,43 @@ gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad) gtk_widget_destroy (dgad); } +static void +gtkutil_place_dialog (GtkWidget *dialog) +{ + GList *list; + GList *item; + GtkWindow *anchor = NULL; + gint x; + gint y; + extern GtkWidget *parent_window; + + list = gtk_window_list_toplevels (); + for (item = list; item; item = item->next) + { + if (item->data != dialog && GTK_IS_WINDOW (item->data) && + gtk_widget_get_visible (GTK_WIDGET (item->data)) && + gtk_window_is_active (GTK_WINDOW (item->data))) + { + anchor = GTK_WINDOW (item->data); + break; + } + } + g_list_free (list); + + if (!anchor && parent_window && parent_window != dialog && GTK_IS_WINDOW (parent_window)) + anchor = GTK_WINDOW (parent_window); + + if (anchor) + { + gtk_window_get_position (anchor, &x, &y); + gtk_window_move (GTK_WINDOW (dialog), x, y); + } + else + { + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + } +} + static void gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry) { @@ -540,15 +577,15 @@ gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry) switch (arg1) { - case GTK_RESPONSE_REJECT: - callback (TRUE, text, user_data); - gtk_widget_destroy (GTK_WIDGET (dialog)); - break; case GTK_RESPONSE_ACCEPT: callback (FALSE, text, user_data); - gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + default: + callback (TRUE, text, user_data); break; } + + gtk_widget_destroy (GTK_WIDGET (dialog)); } static void @@ -557,8 +594,8 @@ gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog) gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); } -void -fe_get_str (char *msg, char *def, void *callback, void *userdata) +static void +gtkutil_get_str (char *msg, char *def, void *callback, void *userdata, gboolean visible) { GtkWidget *dialog; GtkWidget *entry; @@ -572,18 +609,11 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata) NULL); theme_manager_attach_window (dialog); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + if (parent_window && GTK_IS_WINDOW (parent_window)) + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtkutil_place_dialog (dialog); gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE); - if (userdata == (void *)1) /* nick box is usually on the very bottom, make it centered */ - { - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER); - } - else - { - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); - } - hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0); g_object_set_data (G_OBJECT (dialog), "cb", callback); @@ -592,7 +622,8 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata) entry = gtk_entry_new (); g_signal_connect (G_OBJECT (entry), "activate", G_CALLBACK (gtkutil_str_enter), dialog); - gtk_entry_set_text (GTK_ENTRY (entry), def); + gtk_entry_set_text (GTK_ENTRY (entry), def ? def : ""); + gtk_entry_set_visibility (GTK_ENTRY (entry), visible); gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0); label = gtk_label_new (msg); @@ -606,6 +637,18 @@ fe_get_str (char *msg, char *def, void *callback, void *userdata) gtk_widget_show_all (dialog); } +void +fe_get_str (char *msg, char *def, void *callback, void *userdata) +{ + gtkutil_get_str (msg, def, callback, userdata, TRUE); +} + +void +fe_get_password (char *msg, void *callback, void *userdata) +{ + gtkutil_get_str (msg, "", callback, userdata, FALSE); +} + static void gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin) { @@ -641,15 +684,15 @@ gtkutil_get_bool_response (GtkDialog *dialog, gint arg1, gpointer spin) switch (arg1) { - case GTK_RESPONSE_REJECT: - callback (0, user_data); - gtk_widget_destroy (GTK_WIDGET (dialog)); - break; case GTK_RESPONSE_ACCEPT: callback (1, user_data); - gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + default: + callback (0, user_data); break; } + + gtk_widget_destroy (GTK_WIDGET (dialog)); } void @@ -667,9 +710,10 @@ fe_get_int (char *msg, int def, void *callback, void *userdata) _("_OK"), GTK_RESPONSE_ACCEPT, NULL); theme_manager_attach_window (dialog); + if (parent_window && GTK_IS_WINDOW (parent_window)) + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtkutil_place_dialog (dialog); gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE); - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); hbox = gtkutil_box_new (GTK_ORIENTATION_HORIZONTAL, TRUE, 0); @@ -707,10 +751,10 @@ fe_get_bool (char *title, char *prompt, void *callback, void *userdata) _("_Yes"), GTK_RESPONSE_ACCEPT, NULL); theme_manager_attach_window (dialog); + if (parent_window && GTK_IS_WINDOW (parent_window)) + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtkutil_place_dialog (dialog); gtk_box_set_homogeneous (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), TRUE); - gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); - gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); - g_object_set_data (G_OBJECT (dialog), "cb", callback); g_object_set_data (G_OBJECT (dialog), "ud", userdata); diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c index 0bfa4eb4..a0e508c8 100644 --- a/src/fe-gtk/servlistgui.c +++ b/src/fe-gtk/servlistgui.c @@ -88,6 +88,7 @@ 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_check_ask_pass; static GtkWidget *edit_button_encrypt_pass; static GtkWidget *edit_button_import_pass; static int edit_pass_changed; @@ -122,7 +123,11 @@ servlist_update_password_tools (ircnet *net) return; use_keyring = net && (net->flags & FLAG_USE_KEYRING); - has_local = net && net->pass && *net->pass && !use_keyring && !edit_pass_changed; + if ((net && (net->flags & FLAG_PROMPT_PASSWORD)) || + (edit_check_ask_pass && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass)))) + use_keyring = FALSE; + has_local = net && net->pass && *net->pass && !use_keyring && !edit_pass_changed && + (!edit_check_ask_pass || !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass))); gtk_widget_set_sensitive (edit_button_encrypt_pass, has_local && !servlist_password_is_encrypted (net->pass)); gtk_widget_set_sensitive (edit_button_import_pass, has_local); } @@ -135,6 +140,17 @@ servlist_entry_set_text_silent (GtkWidget *entry, const char *text) g_signal_handlers_unblock_by_func (G_OBJECT (entry), G_CALLBACK (servlist_password_changed_cb), NULL); } +static void +servlist_toggle_ask_pass_cb (GtkToggleButton *toggle, gpointer userdata) +{ + gboolean active = gtk_toggle_button_get_active (toggle); + + gtk_widget_set_sensitive (edit_check_use_keyring, !active); + gtk_widget_set_sensitive (edit_entry_pass, !active && (!selected_net || selected_net->logintype != LOGIN_SASLEXTERNAL)); + gtk_widget_set_sensitive (edit_check_show_pass, !active); + servlist_update_password_tools (selected_net); +} + static char * servlist_display_password (ircnet *net) { @@ -1261,6 +1277,7 @@ static void servlist_edit_update (ircnet *net) { gboolean use_keyring; + gboolean ask_pass; gboolean keyring_changed; char *password = NULL; servlist_update_from_entry (&net->nick, edit_entry_nick); @@ -1269,8 +1286,23 @@ servlist_edit_update (ircnet *net) servlist_update_from_entry (&net->real, edit_entry_real); if (net && net->name) { - use_keyring = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring)); + ask_pass = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass)); + use_keyring = !ask_pass && gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (edit_check_use_keyring)); keyring_changed = !!(net->flags & FLAG_USE_KEYRING) != !!use_keyring; + if (ask_pass) + { + secretstore_delete_network_password (net->name); + if (net->pass) + { + memset (net->pass, 0, strlen (net->pass)); + g_free (net->pass); + net->pass = NULL; + } + net->flags &= ~FLAG_USE_KEYRING; + net->flags |= FLAG_PROMPT_PASSWORD; + return; + } + net->flags &= ~FLAG_PROMPT_PASSWORD; if (!edit_pass_changed && !keyring_changed) return; if (edit_pass_changed) @@ -1338,6 +1370,7 @@ servlist_edit_close_cb (GtkWidget *button, gpointer userdata) edit_win = NULL; edit_entry_pass = NULL; edit_check_show_pass = NULL; + edit_check_ask_pass = NULL; edit_button_encrypt_pass = NULL; edit_button_import_pass = NULL; } @@ -2565,7 +2598,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) /* Checkboxes and entries */ - table3 = gtkutil_grid_new (17, 2, FALSE); + table3 = gtkutil_grid_new (19, 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); @@ -2584,34 +2617,43 @@ 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_check_ask_pass = gtk_check_button_new_with_mnemonic (_("Ask for password on connect")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (edit_check_ask_pass), net->flags & FLAG_PROMPT_PASSWORD); + servlist_table_attach (table3, edit_check_ask_pass, 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_ask_pass), "toggled", + G_CALLBACK (servlist_toggle_ask_pass_cb), NULL); + 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, + servlist_table_attach (table3, edit_check_use_keyring, 0, 2, 7, 8, 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); + edit_entry_nick = servlist_create_entry (table3, _("_Nick name:"), 8, net->nick, &edit_label_nick, 0); + edit_entry_nick2 = servlist_create_entry (table3, _("Second choice:"), 9, net->nick2, &edit_label_nick2, 0); + edit_entry_real = servlist_create_entry (table3, _("Rea_l name:"), 10, net->real, &edit_label_real, 0); + edit_entry_user = servlist_create_entry (table3, _("_User name:"), 11, net->user, &edit_label_user, 0); label_logintype = gtk_label_new (_("Login method:")); - servlist_table_attach (table3, label_logintype, 0, 1, 11, 12, + servlist_table_attach (table3, label_logintype, 0, 1, 12, 13, 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, 11, 12, + servlist_table_attach (table3, combobox_logintypes, 1, 2, 12, 13, FALSE, FALSE, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, 4, 2); - edit_entry_pass = servlist_create_entry (table3, _("Password:"), 12, NULL, 0, _("Password used for login. If in doubt, leave blank.")); + edit_entry_pass = servlist_create_entry (table3, _("Password:"), 13, NULL, 0, _("Password used for login. If in doubt, leave blank.")); if (edit_loaded_password) { memset (edit_loaded_password, 0, strlen (edit_loaded_password)); @@ -2644,7 +2686,7 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) if (selected_net && selected_net->logintype == LOGIN_SASLEXTERNAL) gtk_widget_set_sensitive (edit_entry_pass, FALSE); edit_check_show_pass = gtk_check_button_new_with_mnemonic (_("Show password")); - servlist_table_attach (table3, edit_check_show_pass, 0, 2, 13, 14, + servlist_table_attach (table3, edit_check_show_pass, 0, 2, 14, 15, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, 4, 2); @@ -2652,14 +2694,14 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) G_CALLBACK (servlist_toggle_show_password_cb), edit_entry_pass); edit_button_encrypt_pass = gtk_button_new_with_mnemonic (_("Encrypt saved password")); - servlist_table_attach (table3, edit_button_encrypt_pass, 0, 1, 14, 15, + servlist_table_attach (table3, edit_button_encrypt_pass, 0, 1, 15, 16, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_X_PADDING, SERVLIST_Y_PADDING); g_signal_connect (G_OBJECT (edit_button_encrypt_pass), "clicked", G_CALLBACK (servlist_encrypt_password_cb), net); edit_button_import_pass = gtk_button_new_with_mnemonic (_("Move password to keyring")); - servlist_table_attach (table3, edit_button_import_pass, 1, 2, 14, 15, + servlist_table_attach (table3, edit_button_import_pass, 1, 2, 15, 16, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, 4, 2); @@ -2667,14 +2709,14 @@ servlist_open_edit (GtkWidget *parent, ircnet *net) G_CALLBACK (servlist_import_password_cb), net); label34 = gtk_label_new (_("Character set:")); - servlist_table_attach (table3, label34, 0, 1, 15, 16, + servlist_table_attach (table3, label34, 0, 1, 16, 17, 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, 15, 16, + servlist_table_attach (table3, comboboxentry_charset, 1, 2, 16, 17, FALSE, FALSE, SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_FILL, 4, 2); @@ -2700,7 +2742,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, 16, 17, + servlist_table_attach (table3, hbox_cert_buttons, 0, 2, 17, 18, FALSE, FALSE, SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER, SERVLIST_X_PADDING, SERVLIST_Y_PADDING); diff --git a/src/fe-text/fe-text.c b/src/fe-text/fe-text.c index 05a509de..1bc792ba 100644 --- a/src/fe-text/fe-text.c +++ b/src/fe-text/fe-text.c @@ -857,6 +857,10 @@ fe_get_str (char *prompt, char *def, void *callback, void *ud) { } void +fe_get_password (char *prompt, void *callback, void *ud) +{ +} +void fe_get_int (char *prompt, int def, void *callback, void *ud) { }