1
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3
* Copyright 2009-2010 Red Hat, Inc,
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 3 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
* Written by: Matthias Clasen <mclasen@redhat.com>
26
#include <sys/types.h>
30
#include <glib/gi18n.h>
33
#include "um-password-dialog.h"
34
#include "um-user-manager.h"
35
#include "um-strength-bar.h"
37
#include "run-passwd.h"
39
#define MIN_PASSWORD_LEN 6
41
struct _UmPasswordDialog {
45
GtkWidget *action_label;
46
GtkWidget *action_combo;
47
GtkWidget *password_entry;
48
GtkWidget *verify_entry;
49
GtkWidget *strength_indicator;
50
GtkWidget *strength_indicator_label;
51
GtkWidget *normal_hint_entry;
52
GtkWidget *normal_hint_label;
53
GtkWidget *generate_button;
54
GtkWidget *generate_menu;
55
GtkWidget *show_password_button;
60
GtkWidget *old_password_label;
61
GtkWidget *old_password_entry;
62
gboolean old_password_ok;
64
PasswdHandler *passwd_handler;
68
generate_clicked (GtkButton *button,
71
gtk_menu_popup (GTK_MENU (um->generate_menu),
73
(GtkMenuPositionFunc) popup_menu_below_button, um->generate_button,
74
0, gtk_get_current_event_time ());
76
gtk_widget_set_has_tooltip (um->generate_button, FALSE);
80
generate_draw (GtkWidget *widget,
84
if (!gtk_widget_is_sensitive (widget))
87
down_arrow (gtk_widget_get_style_context (widget),
89
gtk_widget_get_allocated_width (widget) - 12,
90
gtk_widget_get_allocated_height (widget) - 12,
95
activate_password_item (GtkMenuItem *item,
100
password = gtk_menu_item_get_label (item);
102
gtk_entry_set_text (GTK_ENTRY (um->password_entry), password);
103
gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
104
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), TRUE);
105
gtk_widget_grab_focus (um->verify_entry);
108
static void generate_passwords (UmPasswordDialog *um);
111
activate_generate_item (GtkMenuItem *item,
112
UmPasswordDialog *um)
114
generate_passwords (um);
115
generate_clicked (GTK_BUTTON (um->generate_button), um);
119
on_generate_menu_unmap (GtkWidget *menu,
120
UmPasswordDialog *um)
122
gtk_widget_set_has_tooltip (um->generate_button, TRUE);
126
generate_passwords (UmPasswordDialog *um)
128
gint min_len, max_len;
129
gchar *output, *err, *cmdline;
138
if (um->generate_menu) {
139
gtk_widget_destroy (um->generate_menu);
142
um->generate_menu = gtk_menu_new ();
143
g_signal_connect (um->generate_menu, "unmap",
144
G_CALLBACK (on_generate_menu_unmap), um);
146
cmdline = g_strdup_printf ("apg -n 6 -M SNC -m %d -x %d", min_len, max_len);
150
if (!g_spawn_command_line_sync (cmdline, &output, &err, &status, &error)) {
151
g_warning ("Failed to run apg: %s", error->message);
152
g_error_free (error);
153
} else if (WEXITSTATUS (status) == 0) {
155
lines = g_strsplit (output, "\n", 0);
156
for (i = 0; lines[i]; i++) {
157
if (lines[i][0] == 0)
160
item = gtk_menu_item_new_with_label (lines[i]);
161
g_signal_connect (item, "activate",
162
G_CALLBACK (activate_password_item), um);
163
gtk_widget_show (item);
164
gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
168
g_warning ("agp returned an error: %s", err);
175
item = gtk_separator_menu_item_new ();
176
gtk_widget_show (item);
177
gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
179
item = gtk_menu_item_new_with_label (_("More choices..."));
180
g_signal_connect (item, "activate",
181
G_CALLBACK (activate_generate_item), um);
182
gtk_widget_show (item);
183
gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
186
/* This code is based on the Master Password dialog in Firefox
187
* (pref-masterpass.js)
188
* Original code triple-licensed under the MPL, GPL, and LGPL
189
* so is license-compatible with this file
192
compute_password_strength (const gchar *password)
195
gint upper, lower, digit, misc;
199
length = strlen (password);
205
for (i = 0; i < length ; i++) {
206
if (g_ascii_isdigit (password[i]))
208
else if (g_ascii_islower (password[i]))
210
else if (g_ascii_isupper (password[i]))
228
strength = ((length * 0.1) - 0.2) +
233
strength = CLAMP (strength, 0.0, 1.0);
239
finish_password_change (UmPasswordDialog *um)
241
gtk_widget_hide (um->dialog);
243
gtk_entry_set_text (GTK_ENTRY (um->password_entry), " ");
244
gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
245
gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
246
gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
248
um_password_dialog_set_user (um, NULL);
252
cancel_password_dialog (GtkButton *button,
253
UmPasswordDialog *um)
255
finish_password_change (um);
259
dialog_closed (GtkWidget *dialog,
261
UmPasswordDialog *um)
263
gtk_widget_destroy (dialog);
267
password_changed_cb (PasswdHandler *handler,
269
UmPasswordDialog *um)
272
const gchar *primary_text;
273
const gchar *secondary_text;
275
gtk_widget_set_sensitive (um->dialog, TRUE);
276
gdk_window_set_cursor (gtk_widget_get_window (um->dialog), NULL);
279
finish_password_change (um);
283
if (error->code == PASSWD_ERROR_REJECTED) {
284
primary_text = error->message;
285
secondary_text = _("Please choose another password.");
287
gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
288
gtk_widget_grab_focus (um->password_entry);
290
gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
292
else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
293
primary_text = error->message;
294
secondary_text = _("Please type your current password again.");
296
gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
297
gtk_widget_grab_focus (um->old_password_entry);
300
primary_text = _("Password could not be changed");
301
secondary_text = error->message;
304
dialog = gtk_message_dialog_new (GTK_WINDOW (um->dialog),
309
gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
310
"%s", secondary_text);
311
g_signal_connect (dialog, "response",
312
G_CALLBACK (dialog_closed), um);
313
gtk_window_present (GTK_WINDOW (dialog));
318
accept_password_dialog (GtkButton *button,
319
UmPasswordDialog *um)
325
const gchar *password;
327
model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
328
gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->action_combo), &iter);
329
gtk_tree_model_get (model, &iter, 1, &mode, -1);
331
password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
332
hint = gtk_entry_get_text (GTK_ENTRY (um->normal_hint_entry));
334
if (mode == 0 && um_user_get_uid (um->user) == getuid ()) {
338
/* When setting a password for the current user,
339
* use passwd directly, to preserve the audit trail
340
* and to e.g. update the keyring password.
342
passwd_change_password (um->passwd_handler, password, (PasswdCallback) password_changed_cb, um);
343
gtk_widget_set_sensitive (um->dialog, FALSE);
344
display = gtk_widget_get_display (um->dialog);
345
cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
346
gdk_window_set_cursor (gtk_widget_get_window (um->dialog), cursor);
347
gdk_display_flush (display);
348
g_object_unref (cursor);
351
um_user_set_password (um->user, mode, password, hint);
352
finish_password_change (um);
357
update_sensitivity (UmPasswordDialog *um)
359
const gchar *password, *verify;
360
const gchar *old_password;
361
const gchar *tooltip;
364
password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
365
verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
366
old_password = gtk_entry_get_text (GTK_ENTRY (um->old_password_entry));
368
/* TODO: configurable policies for acceptable passwords */
369
if (strlen (password) < MIN_PASSWORD_LEN) {
371
if (password[0] == '\0') {
372
tooltip = _("You need to enter a new password");
375
tooltip = _("The new password is too short");
378
else if (strcmp (password, verify) != 0) {
380
if (verify[0] == '\0') {
381
tooltip = _("You need to confirm the password");
384
tooltip = _("The passwords do not match");
387
else if (!um->old_password_ok) {
389
if (old_password[0] == '\0') {
390
tooltip = _("You need to enter your current password");
393
tooltip = _("The current password is not correct");
401
gtk_widget_set_sensitive (um->ok_button, can_change);
402
gtk_widget_set_tooltip_text (um->ok_button, tooltip);
406
action_changed (GtkComboBox *combo,
407
UmPasswordDialog *um)
411
active = gtk_combo_box_get_active (combo);
413
gtk_widget_set_sensitive (um->password_entry, TRUE);
414
gtk_widget_set_sensitive (um->generate_button, TRUE);
415
gtk_widget_set_has_tooltip (um->generate_button, TRUE);
416
gtk_widget_set_sensitive (um->verify_entry, TRUE);
417
gtk_widget_set_sensitive (um->old_password_entry, TRUE);
418
gtk_widget_set_sensitive (um->normal_hint_entry, TRUE);
419
gtk_widget_set_sensitive (um->normal_hint_label, TRUE);
420
gtk_widget_set_sensitive (um->strength_indicator_label, TRUE);
421
gtk_widget_set_sensitive (um->show_password_button, TRUE);
423
update_sensitivity (um);
426
gtk_widget_set_sensitive (um->password_entry, FALSE);
427
gtk_widget_set_sensitive (um->generate_button, FALSE);
428
gtk_widget_set_has_tooltip (um->generate_button, FALSE);
429
gtk_widget_set_sensitive (um->verify_entry, FALSE);
430
gtk_widget_set_sensitive (um->old_password_entry, FALSE);
431
gtk_widget_set_sensitive (um->normal_hint_entry, FALSE);
432
gtk_widget_set_sensitive (um->normal_hint_label, FALSE);
433
gtk_widget_set_sensitive (um->strength_indicator_label, FALSE);
434
gtk_widget_set_sensitive (um->show_password_button, FALSE);
435
gtk_widget_set_sensitive (um->ok_button, TRUE);
440
show_password_toggled (GtkToggleButton *button,
441
UmPasswordDialog *um)
445
active = gtk_toggle_button_get_active (button);
446
gtk_entry_set_visibility (GTK_ENTRY (um->password_entry), active);
447
gtk_entry_set_visibility (GTK_ENTRY (um->verify_entry), active);
451
update_password_strength (UmPasswordDialog *um)
453
const gchar *password;
457
password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
459
strength = compute_password_strength (password);
461
if (strlen (password) < MIN_PASSWORD_LEN) {
463
if (password[0] == '\0')
466
hint = C_("Password strength", "Too short");
468
else if (strength < 0.50)
469
hint = C_("Password strength", "Weak");
470
else if (strength < 0.75)
471
hint = C_("Password strength", "Fair");
472
else if (strength < 0.90)
473
hint = C_("Password strength", "Good");
475
hint = C_("Password strength", "Strong");
477
um_strength_bar_set_strength (UM_STRENGTH_BAR (um->strength_indicator), strength);
478
gtk_label_set_label (GTK_LABEL (um->strength_indicator_label), hint);
482
password_entry_changed (GtkEntry *entry,
484
UmPasswordDialog *um)
486
update_password_strength (um);
487
update_sensitivity (um);
491
verify_entry_changed (GtkEntry *entry,
493
UmPasswordDialog *um)
495
clear_entry_validation_error (GTK_ENTRY (entry));
496
update_password_strength (um);
497
update_sensitivity (um);
501
verify_entry_focus_out (GtkWidget *entry,
502
GdkEventFocus *event,
503
UmPasswordDialog *um)
505
const char *password;
508
password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
509
verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
511
if (strlen (password) > 0 && strlen (verify) > 0) {
512
if (strcmp (password, verify) != 0) {
513
set_entry_validation_error (GTK_ENTRY (um->verify_entry),
514
_("Passwords do not match"));
517
clear_entry_validation_error (GTK_ENTRY (um->verify_entry));
525
entry_size_changed (GtkWidget *entry,
526
GtkAllocation *allocation,
529
gtk_widget_set_size_request (label, allocation->width, -1);
533
auth_cb (PasswdHandler *handler,
535
UmPasswordDialog *um)
538
um->old_password_ok = FALSE;
539
set_entry_validation_error (GTK_ENTRY (um->old_password_entry),
540
_("Wrong password"));
543
um->old_password_ok = TRUE;
544
clear_entry_validation_error (GTK_ENTRY (um->old_password_entry));
547
update_sensitivity (um);
551
old_password_entry_focus_out (GtkWidget *entry,
552
GdkEventFocus *event,
553
UmPasswordDialog *um)
557
text = gtk_entry_get_text (GTK_ENTRY (entry));
558
if (strlen (text) > 0) {
559
passwd_authenticate (um->passwd_handler, text,
560
(PasswdCallback)auth_cb, um);
567
old_password_entry_activate (GtkWidget *entry,
568
UmPasswordDialog *um)
572
text = gtk_entry_get_text (GTK_ENTRY (entry));
573
if (strlen (text) > 0) {
574
passwd_authenticate (um->passwd_handler, text,
575
(PasswdCallback)auth_cb, um);
581
old_password_entry_changed (GtkEntry *entry,
583
UmPasswordDialog *um)
585
clear_entry_validation_error (GTK_ENTRY (entry));
586
um->old_password_ok = FALSE;
587
update_sensitivity (um);
591
um_password_dialog_set_privileged (UmPasswordDialog *um,
595
gtk_widget_set_visible (um->action_label, TRUE);
596
gtk_widget_set_visible (um->action_combo, TRUE);
599
gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
600
gtk_widget_set_visible (um->action_label, FALSE);
601
gtk_widget_set_visible (um->action_combo, FALSE);
606
um_password_dialog_new (void)
610
const gchar *filename;
611
UmPasswordDialog *um;
613
const char *old_label;
617
builder = gtk_builder_new ();
620
filename = UIDIR "/password-dialog.ui";
621
if (!g_file_test (filename, G_FILE_TEST_EXISTS))
622
filename = "data/password-dialog.ui";
623
if (!gtk_builder_add_from_file (builder, filename, &error)) {
624
g_error ("%s", error->message);
625
g_error_free (error);
629
um = g_new0 (UmPasswordDialog, 1);
631
um->action_label = (GtkWidget *) gtk_builder_get_object (builder, "action-label");
632
widget = (GtkWidget *) gtk_builder_get_object (builder, "action-combo");
633
g_signal_connect (widget, "changed",
634
G_CALLBACK (action_changed), um);
635
um->action_combo = widget;
637
widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
638
g_signal_connect (widget, "delete-event",
639
G_CALLBACK (gtk_widget_hide_on_delete), NULL);
642
um->user_icon = (GtkWidget *) gtk_builder_get_object (builder, "user-icon");
643
um->user_name = (GtkWidget *) gtk_builder_get_object (builder, "user-name");
645
widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
646
g_signal_connect (widget, "clicked",
647
G_CALLBACK (cancel_password_dialog), um);
649
widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
650
g_signal_connect (widget, "clicked",
651
G_CALLBACK (accept_password_dialog), um);
652
gtk_widget_grab_default (widget);
653
um->ok_button = widget;
655
widget = (GtkWidget *) gtk_builder_get_object (builder, "password-normal-strength-hints-label");
656
old_label = gtk_label_get_label (GTK_LABEL (widget));
657
label = g_strdup_printf ("<a href=\"%s\">%s</a>",
658
"ghelp:gnome-help?user-goodpassword",
660
gtk_label_set_markup (GTK_LABEL (widget), label);
663
widget = (GtkWidget *) gtk_builder_get_object (builder, "show-password-checkbutton");
664
g_signal_connect (widget, "toggled",
665
G_CALLBACK (show_password_toggled), um);
666
um->show_password_button = widget;
668
widget = (GtkWidget *) gtk_builder_get_object (builder, "password-entry");
669
g_signal_connect (widget, "notify::text",
670
G_CALLBACK (password_entry_changed), um);
671
gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
673
um->password_entry = widget;
675
widget = (GtkWidget *) gtk_builder_get_object (builder, "old-password-entry");
676
g_signal_connect_after (widget, "focus-out-event",
677
G_CALLBACK (old_password_entry_focus_out), um);
678
g_signal_connect (widget, "notify::text",
679
G_CALLBACK (old_password_entry_changed), um);
680
g_signal_connect (widget, "activate",
681
G_CALLBACK (old_password_entry_activate), um);
682
um->old_password_entry = widget;
683
um->old_password_label = (GtkWidget *) gtk_builder_get_object (builder, "old-password-label");
685
widget = (GtkWidget *) gtk_builder_get_object (builder, "verify-entry");
686
g_signal_connect (widget, "notify::text",
687
G_CALLBACK (verify_entry_changed), um);
688
g_signal_connect_after (widget, "focus-out-event",
689
G_CALLBACK (verify_entry_focus_out), um);
690
um->verify_entry = widget;
693
len = MAX (len, strlen (C_("Password strength", "Too short")));
694
len = MAX (len, strlen (C_("Password strength", "Weak")));
695
len = MAX (len, strlen (C_("Password strength", "Fair")));
696
len = MAX (len, strlen (C_("Password strength", "Good")));
697
len = MAX (len, strlen (C_("Password strength", "Strong")));
700
widget = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
701
gtk_label_set_width_chars (GTK_LABEL (widget), len);
704
widget = (GtkWidget *) gtk_builder_get_object (builder, "generate-again-button");
705
g_signal_connect (widget, "clicked",
706
G_CALLBACK (generate_clicked), um);
708
g_signal_connect (widget, "state-changed",
709
G_CALLBACK (generate_state_changed), um);
711
um->generate_button = widget;
712
g_signal_connect_after (widget, "draw",
713
G_CALLBACK (generate_draw), um);
715
um->normal_hint_entry = (GtkWidget *) gtk_builder_get_object (builder, "normal-hint-entry");
718
* This only sort-of works because the dialog is non-resizable.
720
widget = (GtkWidget *)gtk_builder_get_object (builder, "password-normal-hint-description-label");
721
g_signal_connect (um->normal_hint_entry, "size-allocate",
722
G_CALLBACK (entry_size_changed), widget);
723
um->normal_hint_label = widget;
725
um->strength_indicator = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator");
727
um->strength_indicator_label = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
729
g_object_unref (builder);
731
generate_passwords (um);
737
um_password_dialog_free (UmPasswordDialog *um)
739
gtk_widget_destroy (um->dialog);
742
g_object_unref (um->user);
744
if (um->passwd_handler)
745
passwd_destroy (um->passwd_handler);
751
visible_func (GtkTreeModel *model,
753
UmPasswordDialog *um)
757
gboolean locked = um_user_get_locked (um->user);
759
gtk_tree_model_get (model, iter, 1, &mode, -1);
761
if (mode == 3 && locked)
764
if (mode == 4 && !locked)
774
um_password_dialog_set_user (UmPasswordDialog *um,
781
g_object_unref (um->user);
785
um->user = g_object_ref (user);
787
pixbuf = um_user_render_icon (user, FALSE, 48);
788
gtk_image_set_from_pixbuf (GTK_IMAGE (um->user_icon), pixbuf);
789
g_object_unref (pixbuf);
791
gtk_label_set_label (GTK_LABEL (um->user_name),
792
um_user_get_real_name (user));
794
gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
795
gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
796
gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
797
gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
798
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), FALSE);
799
if (um_user_get_uid (um->user) == getuid()) {
800
gtk_widget_show (um->old_password_label);
801
gtk_widget_show (um->old_password_entry);
802
if (um->passwd_handler != NULL)
803
passwd_destroy (um->passwd_handler);
804
um->passwd_handler = passwd_init ();
805
um->old_password_ok = FALSE;
808
gtk_widget_hide (um->old_password_label);
809
gtk_widget_hide (um->old_password_entry);
810
um->old_password_ok = TRUE;
814
model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
815
if (!GTK_IS_TREE_MODEL_FILTER (model)) {
816
model = gtk_tree_model_filter_new (model, NULL);
817
gtk_combo_box_set_model (GTK_COMBO_BOX (um->action_combo), model);
818
gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
819
(GtkTreeModelFilterVisibleFunc) visible_func,
823
gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
824
gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
828
um_password_dialog_show (UmPasswordDialog *um,
831
gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
832
gtk_window_present (GTK_WINDOW (um->dialog));
833
gtk_widget_grab_focus (um->password_entry);