1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright 2009-2010  Red Hat, Inc,
4  *
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 2 of the License, or
8  * (at your option) any later version.
9  *
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.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Written by: Matthias Clasen <mclasen@redhat.com>
19  */
20 
21 #include "config.h"
22 
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/types.h>
26 #include <sys/wait.h>
27 
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <act/act.h>
32 
33 #include "cc-password-dialog.h"
34 #include "cc-user-accounts-resources.h"
35 #include "pw-utils.h"
36 #include "run-passwd.h"
37 #include "user-utils.h"
38 
39 #define PASSWORD_CHECK_TIMEOUT 600
40 
41 struct _CcPasswordDialog
42 {
43         GtkDialog           parent_instance;
44 
45         GtkBox             *action_radio_box;
46         GtkRadioButton     *action_now_radio;
47         GtkRadioButton     *action_login_radio;
48         GtkButton          *ok_button;
49         GtkLabel           *old_password_label;
50         GtkEntry           *old_password_entry;
51         GtkEntry           *password_entry;
52         GtkLabel           *password_hint_label;
53         GtkLevelBar        *strength_indicator;
54         GtkEntry           *verify_entry;
55         GtkLabel           *verify_hint_label;
56 
57         gint                password_entry_timeout_id;
58 
59         ActUser            *user;
60         ActUserPasswordMode password_mode;
61 
62         gboolean            old_password_ok;
63         gint                old_password_entry_timeout_id;
64 
65         PasswdHandler      *passwd_handler;
66 };
67 
G_DEFINE_TYPE(CcPasswordDialog,cc_password_dialog,GTK_TYPE_DIALOG)68 G_DEFINE_TYPE (CcPasswordDialog, cc_password_dialog, GTK_TYPE_DIALOG)
69 
70 static gint
71 update_password_strength (CcPasswordDialog *self)
72 {
73         const gchar *password;
74         const gchar *old_password;
75         const gchar *username;
76         gint strength_level;
77         const gchar *hint;
78         const gchar *verify;
79 
80         password = gtk_entry_get_text (self->password_entry);
81         old_password = gtk_entry_get_text (self->old_password_entry);
82         username = act_user_get_user_name (self->user);
83 
84         pw_strength (password, old_password, username,
85                      &hint, &strength_level);
86 
87         gtk_level_bar_set_value (self->strength_indicator, strength_level);
88         gtk_label_set_label (self->password_hint_label, hint);
89 
90         if (strength_level > 1) {
91                 set_entry_validation_checkmark (self->password_entry);
92         } else if (strlen (password) == 0) {
93                 set_entry_generation_icon (self->password_entry);
94         } else {
95                 clear_entry_validation_error (self->password_entry);
96         }
97 
98         verify = gtk_entry_get_text (self->verify_entry);
99         if (strlen (verify) == 0) {
100                 gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), strength_level > 1);
101         }
102 
103         return strength_level;
104 }
105 
106 static void
password_changed_cb(PasswdHandler * handler,GError * error,CcPasswordDialog * self)107 password_changed_cb (PasswdHandler    *handler,
108                      GError           *error,
109                      CcPasswordDialog *self)
110 {
111         GtkWidget *dialog;
112         const gchar *primary_text;
113         const gchar *secondary_text;
114 
115         gtk_widget_set_sensitive (GTK_WIDGET (self), TRUE);
116         gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), NULL);
117 
118         if (!error) {
119                 gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
120                 return;
121         }
122 
123         if (error->code == PASSWD_ERROR_REJECTED) {
124                 primary_text = error->message;
125                 secondary_text = _("Please choose another password.");
126 
127                 gtk_entry_set_text (self->password_entry, "");
128                 gtk_widget_grab_focus (GTK_WIDGET (self->password_entry));
129 
130                 gtk_entry_set_text (self->verify_entry, "");
131         }
132         else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
133                 primary_text = error->message;
134                 secondary_text = _("Please type your current password again.");
135 
136                 gtk_entry_set_text (self->old_password_entry, "");
137                 gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry));
138         }
139         else {
140                 primary_text = _("Password could not be changed");
141                 secondary_text = error->message;
142         }
143 
144         dialog = gtk_message_dialog_new (GTK_WINDOW (self),
145                                          GTK_DIALOG_MODAL,
146                                          GTK_MESSAGE_ERROR,
147                                          GTK_BUTTONS_CLOSE,
148                                          "%s", primary_text);
149         gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
150                                                   "%s", secondary_text);
151         gtk_dialog_run (GTK_DIALOG (dialog));
152         gtk_widget_destroy (dialog);
153 }
154 
155 static void
ok_button_clicked_cb(CcPasswordDialog * self)156 ok_button_clicked_cb (CcPasswordDialog *self)
157 {
158         const gchar *password;
159 
160         password = gtk_entry_get_text (self->password_entry);
161 
162         switch (self->password_mode) {
163                 case ACT_USER_PASSWORD_MODE_REGULAR:
164                         if (act_user_get_uid (self->user) == getuid ()) {
165                                 GdkDisplay *display;
166                                 g_autoptr(GdkCursor) cursor = NULL;
167 
168                                 /* When setting a password for the current user,
169                                  * use passwd directly, to preserve the audit trail
170                                  * and to e.g. update the keyring password.
171                                  */
172                                 passwd_change_password (self->passwd_handler, password,
173                                                         (PasswdCallback) password_changed_cb, self);
174                                 gtk_widget_set_sensitive (GTK_WIDGET (self), FALSE);
175                                 display = gtk_widget_get_display (GTK_WIDGET (self));
176                                 cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
177                                 gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (self)), cursor);
178                                 gdk_display_flush (display);
179                                 return;
180                         }
181 
182                         act_user_set_password_mode (self->user, ACT_USER_PASSWORD_MODE_REGULAR);
183                         act_user_set_password (self->user, password, "");
184                         break;
185 
186                 case ACT_USER_PASSWORD_MODE_SET_AT_LOGIN:
187                         act_user_set_password_mode (self->user, self->password_mode);
188                         act_user_set_automatic_login (self->user, FALSE);
189                         break;
190 
191                 default:
192                         g_assert_not_reached ();
193         }
194 
195         gtk_dialog_response (GTK_DIALOG (self), GTK_RESPONSE_ACCEPT);
196 }
197 
198 static void
update_sensitivity(CcPasswordDialog * self)199 update_sensitivity (CcPasswordDialog *self)
200 {
201         const gchar *password, *verify;
202         gboolean can_change;
203         int strength;
204 
205         password = gtk_entry_get_text (self->password_entry);
206         verify = gtk_entry_get_text (self->verify_entry);
207 
208         if (self->password_mode == ACT_USER_PASSWORD_MODE_REGULAR) {
209                 strength = update_password_strength (self);
210                 can_change = strength > 1 && strcmp (password, verify) == 0 &&
211                              (self->old_password_ok || !gtk_widget_get_visible (GTK_WIDGET (self->old_password_entry)));
212         }
213         else {
214                 can_change = TRUE;
215         }
216 
217         gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), can_change);
218 }
219 
220 static void
mode_change(CcPasswordDialog * self,ActUserPasswordMode mode)221 mode_change (CcPasswordDialog *self,
222              ActUserPasswordMode mode)
223 {
224         gboolean active;
225 
226         active = (mode == ACT_USER_PASSWORD_MODE_REGULAR);
227         gtk_widget_set_sensitive (GTK_WIDGET (self->password_entry), active);
228         gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), active);
229         gtk_widget_set_sensitive (GTK_WIDGET (self->old_password_entry), active);
230         gtk_widget_set_sensitive (GTK_WIDGET (self->password_hint_label), active);
231         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_now_radio), active);
232         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->action_login_radio), !active);
233 
234         self->password_mode = mode;
235         update_sensitivity (self);
236 }
237 
238 static void
action_now_radio_toggled_cb(CcPasswordDialog * self)239 action_now_radio_toggled_cb (CcPasswordDialog *self)
240 {
241         gint active;
242         ActUserPasswordMode mode;
243 
244         active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (self->action_now_radio));
245         mode = active ? ACT_USER_PASSWORD_MODE_REGULAR : ACT_USER_PASSWORD_MODE_SET_AT_LOGIN;
246         mode_change (self, mode);
247 }
248 
249 static void
update_password_match(CcPasswordDialog * self)250 update_password_match (CcPasswordDialog *self)
251 {
252         const gchar *password;
253         const gchar *verify;
254         const gchar *message = "";
255 
256         password = gtk_entry_get_text (self->password_entry);
257         verify = gtk_entry_get_text (self->verify_entry);
258 
259         if (strlen (verify) > 0) {
260                 if (strcmp (password, verify) != 0) {
261                         message = _("The passwords do not match.");
262                 }
263                 else {
264                         set_entry_validation_checkmark (self->verify_entry);
265                 }
266         }
267         gtk_label_set_label (self->verify_hint_label, message);
268 }
269 
270 static gboolean
password_entry_timeout(CcPasswordDialog * self)271 password_entry_timeout (CcPasswordDialog *self)
272 {
273         update_password_strength (self);
274         update_sensitivity (self);
275         update_password_match (self);
276 
277         self->password_entry_timeout_id = 0;
278 
279         return FALSE;
280 }
281 
282 static void
recheck_password_match(CcPasswordDialog * self)283 recheck_password_match (CcPasswordDialog *self)
284 {
285         const gchar *password;
286 
287         if (self->password_entry_timeout_id != 0) {
288                 g_source_remove (self->password_entry_timeout_id);
289                 self->password_entry_timeout_id = 0;
290         }
291 
292         gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE);
293 
294         password = gtk_entry_get_text (self->password_entry);
295         if (strlen (password) == 0) {
296                 gtk_entry_set_visibility (self->password_entry, FALSE);
297         }
298 
299         self->password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT,
300                                                          (GSourceFunc) password_entry_timeout,
301                                                          self);
302 }
303 
304 static void
password_entry_changed(CcPasswordDialog * self)305 password_entry_changed (CcPasswordDialog *self)
306 {
307         clear_entry_validation_error (self->password_entry);
308         clear_entry_validation_error (self->verify_entry);
309         recheck_password_match (self);
310 }
311 
312 static void
verify_entry_changed(CcPasswordDialog * self)313 verify_entry_changed (CcPasswordDialog *self)
314 {
315         clear_entry_validation_error (self->verify_entry);
316         recheck_password_match (self);
317 }
318 
319 static gboolean
password_entry_focus_out_cb(CcPasswordDialog * self)320 password_entry_focus_out_cb (CcPasswordDialog *self)
321 {
322         if (self->password_entry_timeout_id != 0) {
323                 g_source_remove (self->password_entry_timeout_id);
324                 self->password_entry_timeout_id = 0;
325         }
326 
327         if (self->user != NULL)
328                 password_entry_timeout (self);
329 
330         return FALSE;
331 }
332 
333 static gboolean
password_entry_key_press_cb(CcPasswordDialog * self,GdkEvent * event)334 password_entry_key_press_cb (CcPasswordDialog *self,
335                              GdkEvent         *event)
336 {
337         GdkEventKey *key = (GdkEventKey *)event;
338 
339         if (self->password_entry_timeout_id != 0) {
340                 g_source_remove (self->password_entry_timeout_id);
341                 self->password_entry_timeout_id = 0;
342         }
343 
344         if (key->keyval == GDK_KEY_Tab)
345                password_entry_timeout (self);
346 
347         return FALSE;
348 }
349 
350 static void
auth_cb(PasswdHandler * handler,GError * error,CcPasswordDialog * self)351 auth_cb (PasswdHandler    *handler,
352          GError           *error,
353          CcPasswordDialog *self)
354 {
355         if (error) {
356                 self->old_password_ok = FALSE;
357         }
358         else {
359                 self->old_password_ok = TRUE;
360                 set_entry_validation_checkmark (self->old_password_entry);
361         }
362 
363         update_sensitivity (self);
364 }
365 
366 static gboolean
old_password_entry_timeout(CcPasswordDialog * self)367 old_password_entry_timeout (CcPasswordDialog *self)
368 {
369         const gchar *text;
370 
371         update_sensitivity (self);
372 
373         text = gtk_entry_get_text (self->old_password_entry);
374         if (!self->old_password_ok) {
375                 passwd_authenticate (self->passwd_handler, text, (PasswdCallback)auth_cb, self);
376         }
377 
378         self->old_password_entry_timeout_id = 0;
379 
380         return FALSE;
381 }
382 
383 static gboolean
old_password_entry_focus_out_cb(CcPasswordDialog * self)384 old_password_entry_focus_out_cb (CcPasswordDialog *self)
385 {
386         if (self->old_password_entry_timeout_id != 0) {
387                 g_source_remove (self->old_password_entry_timeout_id);
388                 self->old_password_entry_timeout_id = 0;
389         }
390 
391         if (self->user != NULL)
392                 old_password_entry_timeout (self);
393 
394         return FALSE;
395 }
396 
397 static void
old_password_entry_changed(CcPasswordDialog * self)398 old_password_entry_changed (CcPasswordDialog *self)
399 {
400         if (self->old_password_entry_timeout_id != 0) {
401                 g_source_remove (self->old_password_entry_timeout_id);
402                 self->old_password_entry_timeout_id = 0;
403         }
404 
405         clear_entry_validation_error (self->old_password_entry);
406         gtk_widget_set_sensitive (GTK_WIDGET (self->ok_button), FALSE);
407 
408         self->old_password_ok = FALSE;
409         self->old_password_entry_timeout_id = g_timeout_add (PASSWORD_CHECK_TIMEOUT,
410                                                              (GSourceFunc) old_password_entry_timeout,
411                                                              self);
412 }
413 
414 static void
password_entry_icon_press_cb(CcPasswordDialog * self)415 password_entry_icon_press_cb (CcPasswordDialog *self)
416 {
417         g_autofree gchar *pwd = NULL;
418 
419         pwd = pw_generate ();
420         if (pwd == NULL)
421                 return;
422 
423         gtk_entry_set_text (self->password_entry, pwd);
424         gtk_entry_set_text (self->verify_entry, pwd);
425         gtk_entry_set_visibility (self->password_entry, TRUE);
426         gtk_widget_set_sensitive (GTK_WIDGET (self->verify_entry), TRUE);
427 }
428 
429 static void
cc_password_dialog_dispose(GObject * object)430 cc_password_dialog_dispose (GObject *object)
431 {
432         CcPasswordDialog *self = CC_PASSWORD_DIALOG (object);
433 
434         g_clear_object (&self->user);
435 
436         if (self->passwd_handler) {
437                 passwd_destroy (self->passwd_handler);
438                 self->passwd_handler = NULL;
439         }
440 
441         if (self->old_password_entry_timeout_id != 0) {
442                 g_source_remove (self->old_password_entry_timeout_id);
443                 self->old_password_entry_timeout_id = 0;
444         }
445 
446         if (self->password_entry_timeout_id != 0) {
447                 g_source_remove (self->password_entry_timeout_id);
448                 self->password_entry_timeout_id = 0;
449         }
450 
451         G_OBJECT_CLASS (cc_password_dialog_parent_class)->dispose (object);
452 }
453 
454 static void
cc_password_dialog_class_init(CcPasswordDialogClass * klass)455 cc_password_dialog_class_init (CcPasswordDialogClass *klass)
456 {
457         GObjectClass   *object_class = G_OBJECT_CLASS (klass);
458         GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
459 
460         object_class->dispose = cc_password_dialog_dispose;
461 
462         gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/user-accounts/cc-password-dialog.ui");
463 
464         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_radio_box);
465         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_now_radio);
466         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, action_login_radio);
467         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, ok_button);
468         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_label);
469         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, old_password_entry);
470         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_entry);
471         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, password_hint_label);
472         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, strength_indicator);
473         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_entry);
474         gtk_widget_class_bind_template_child (widget_class, CcPasswordDialog, verify_hint_label);
475 
476         gtk_widget_class_bind_template_callback (widget_class, action_now_radio_toggled_cb);
477         gtk_widget_class_bind_template_callback (widget_class, old_password_entry_changed);
478         gtk_widget_class_bind_template_callback (widget_class, old_password_entry_focus_out_cb);
479         gtk_widget_class_bind_template_callback (widget_class, ok_button_clicked_cb);
480         gtk_widget_class_bind_template_callback (widget_class, password_entry_changed);
481         gtk_widget_class_bind_template_callback (widget_class, password_entry_focus_out_cb);
482         gtk_widget_class_bind_template_callback (widget_class, password_entry_icon_press_cb);
483         gtk_widget_class_bind_template_callback (widget_class, password_entry_key_press_cb);
484         gtk_widget_class_bind_template_callback (widget_class, verify_entry_changed);
485 }
486 
487 static void
cc_password_dialog_init(CcPasswordDialog * self)488 cc_password_dialog_init (CcPasswordDialog *self)
489 {
490         g_resources_register (cc_user_accounts_get_resource ());
491 
492         gtk_widget_init_template (GTK_WIDGET (self));
493 }
494 
495 CcPasswordDialog *
cc_password_dialog_new(ActUser * user)496 cc_password_dialog_new (ActUser *user)
497 {
498         CcPasswordDialog *self;
499 
500         g_return_val_if_fail (ACT_IS_USER (user), NULL);
501 
502         self = g_object_new (CC_TYPE_PASSWORD_DIALOG,
503                              "use-header-bar", 1,
504                              NULL);
505 
506         self->user = g_object_ref (user);
507 
508         if (act_user_get_uid (self->user) == getuid ()) {
509                 gboolean visible;
510 
511                 mode_change (self, ACT_USER_PASSWORD_MODE_REGULAR);
512                 gtk_widget_hide (GTK_WIDGET (self->action_radio_box));
513 
514                 visible = (act_user_get_password_mode (user) != ACT_USER_PASSWORD_MODE_NONE);
515                 gtk_widget_set_visible (GTK_WIDGET (self->old_password_label), visible);
516                 gtk_widget_set_visible (GTK_WIDGET (self->old_password_entry), visible);
517                 self->old_password_ok = !visible;
518 
519                 self->passwd_handler = passwd_init ();
520         }
521         else {
522                 mode_change (self, ACT_USER_PASSWORD_MODE_SET_AT_LOGIN);
523                 gtk_widget_show (GTK_WIDGET (self->action_radio_box));
524 
525                 gtk_widget_hide (GTK_WIDGET (self->old_password_label));
526                 gtk_widget_hide (GTK_WIDGET (self->old_password_entry));
527                 self->old_password_ok = TRUE;
528         }
529 
530         if (self->old_password_ok == FALSE)
531                 gtk_widget_grab_focus (GTK_WIDGET (self->old_password_entry));
532         else
533                 gtk_widget_grab_focus (GTK_WIDGET (self->password_entry));
534 
535         gtk_widget_grab_default (GTK_WIDGET (self->ok_button));
536 
537         return self;
538 }
539