~ubuntu-branches/ubuntu/precise/gnome-control-center/precise-updates

« back to all changes in this revision

Viewing changes to capplets/about-me/gnome-about-me-password.c

Tags: upstream-3.0.1.1
ImportĀ upstreamĀ versionĀ 3.0.1.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* gnome-about-me.c
2
 
 * Copyright (C) 2002 Diego Gonzalez
3
 
 * Copyright (C) 2006 Johannes H. Jensen
4
 
 *
5
 
 * Written by: Diego Gonzalez <diego@pemas.net>
6
 
 * Modified by: Johannes H. Jensen <joh@deworks.net>
7
 
 *
8
 
 * This program is free software; you can redistribute it and/or modify
9
 
 * it under the terms of the GNU General Public License as published by
10
 
 * the Free Software Foundation; either version 2, or (at your option)
11
 
 * any later version.
12
 
 *
13
 
 * This program is distributed in the hope that it will be useful,
14
 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 
 * GNU General Public License for more details.
17
 
 *
18
 
 * You should have received a copy of the GNU General Public License
19
 
 * along with this program; if not, write to the Free Software
20
 
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
21
 
 * 02111-1307, USA.
22
 
 *
23
 
 * Parts of this code come from Gnome-System-Tools.
24
 
 */
25
 
 
26
 
#ifdef HAVE_CONFIG_H
27
 
#  include <config.h>
28
 
#endif
29
 
 
30
 
/* Are all of these needed? */
31
 
#include <gdk/gdkkeysyms.h>
32
 
#include <pwd.h>
33
 
#include <stdlib.h>
34
 
#include <unistd.h>
35
 
#include <errno.h>
36
 
#include <string.h>
37
 
#include <sys/wait.h>
38
 
 
39
 
#if __sun
40
 
#include <sys/types.h>
41
 
#include <signal.h>
42
 
#endif
43
 
 
44
 
#include "capplet-util.h"
45
 
#include "eel-alert-dialog.h"
46
 
 
47
 
/* Passwd states */
48
 
typedef enum {
49
 
        PASSWD_STATE_NONE,              /* Passwd is not asking for anything */
50
 
        PASSWD_STATE_AUTH,              /* Passwd is asking for our current password */
51
 
        PASSWD_STATE_NEW,               /* Passwd is asking for our new password */
52
 
        PASSWD_STATE_RETYPE,    /* Passwd is asking for our retyped new password */
53
 
        PASSWD_STATE_ERR                /* Passwd reported an error but has not yet exited */
54
 
} PasswdState;
55
 
 
56
 
typedef struct {
57
 
        GtkBuilder  *ui;
58
 
 
59
 
        /* Commonly used widgets */
60
 
        GtkEntry *current_password;
61
 
        GtkEntry *new_password;
62
 
        GtkEntry *retyped_password;
63
 
        GtkImage *dialog_image;
64
 
        GtkLabel *status_label;
65
 
 
66
 
        /* Whether we have authenticated */
67
 
        gboolean authenticated;
68
 
 
69
 
        /* Communication with the passwd program */
70
 
        GPid backend_pid;
71
 
 
72
 
        GIOChannel *backend_stdin;
73
 
        GIOChannel *backend_stdout;
74
 
 
75
 
        GQueue *backend_stdin_queue;            /* Write queue to backend_stdin */
76
 
 
77
 
        /* GMainLoop IDs */
78
 
        guint backend_child_watch_id;           /* g_child_watch_add (PID) */
79
 
        guint backend_stdout_watch_id;          /* g_io_add_watch (stdout) */
80
 
 
81
 
        /* State of the passwd program */
82
 
        PasswdState backend_state;
83
 
 
84
 
} PasswordDialog;
85
 
 
86
 
/* Buffer size for backend output */
87
 
#define BUFSIZE 64
88
 
 
89
 
/*
90
 
 * Error handling {{
91
 
 */
92
 
#define PASSDLG_ERROR (gnome_about_me_password_error_quark())
93
 
 
94
 
GQuark gnome_about_me_password_error_quark(void)
95
 
{
96
 
        static GQuark q = 0;
97
 
 
98
 
        if (q == 0) {
99
 
                q = g_quark_from_static_string("gnome_about_me_password_error");
100
 
        }
101
 
 
102
 
        return q;
103
 
}
104
 
 
105
 
/* error codes */
106
 
enum {
107
 
        PASSDLG_ERROR_NONE,
108
 
        PASSDLG_ERROR_NEW_PASSWORD_EMPTY,
109
 
        PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY,
110
 
        PASSDLG_ERROR_PASSWORDS_NOT_EQUAL,
111
 
        PASSDLG_ERROR_BACKEND,  /* Backend error */
112
 
        PASSDLG_ERROR_USER,             /* Generic user error */
113
 
        PASSDLG_ERROR_FAILED    /* Fatal failure, error->message should explain */
114
 
};
115
 
 
116
 
/*
117
 
 * }} Error handling
118
 
 */
119
 
 
120
 
/*
121
 
 * Prototypes {{
122
 
 */
123
 
static void
124
 
stop_passwd (PasswordDialog *pdialog);
125
 
 
126
 
static void
127
 
free_passwd_resources (PasswordDialog *pdialog);
128
 
 
129
 
static gboolean
130
 
io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog);
131
 
 
132
 
static void
133
 
passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state);
134
 
 
135
 
static void
136
 
passdlg_set_status (PasswordDialog *pdialog, gchar *msg);
137
 
 
138
 
static void
139
 
passdlg_set_busy (PasswordDialog *pdialog, gboolean busy);
140
 
 
141
 
static void
142
 
passdlg_clear (PasswordDialog *pdialog);
143
 
 
144
 
static guint
145
 
passdlg_refresh_password_state (PasswordDialog *pdialog);
146
 
 
147
 
/*
148
 
 * }} Prototypes
149
 
 */
150
 
 
151
 
/*
152
 
 * Spawning and closing of backend {{
153
 
 */
154
 
 
155
 
/* Child watcher */
156
 
static void
157
 
child_watch_cb (GPid pid, gint status, PasswordDialog *pdialog)
158
 
{
159
 
        if (WIFEXITED (status)) {
160
 
                if (WEXITSTATUS (status) >= 255) {
161
 
                        g_warning (_("Child exited unexpectedly"));
162
 
                }
163
 
        }
164
 
 
165
 
        free_passwd_resources (pdialog);
166
 
}
167
 
 
168
 
/* Spawn passwd backend
169
 
 * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
170
 
static gboolean
171
 
spawn_passwd (PasswordDialog *pdialog, GError **error)
172
 
{
173
 
        gchar   *argv[2];
174
 
        gchar   *envp[1];
175
 
        gint    my_stdin, my_stdout, my_stderr;
176
 
 
177
 
        argv[0] = "/usr/bin/passwd";    /* Is it safe to rely on a hard-coded path? */
178
 
        argv[1] = NULL;
179
 
 
180
 
        envp[0] = NULL;                                 /* If we pass an empty array as the environment,
181
 
                                                                         * will the childs environment be empty, and the
182
 
                                                                         * locales set to the C default? From the manual:
183
 
                                                                         * "If envp is NULL, the child inherits its
184
 
                                                                         * parent'senvironment."
185
 
                                                                         * If I'm wrong here, we somehow have to set
186
 
                                                                         * the locales here.
187
 
                                                                         */
188
 
 
189
 
        if (!g_spawn_async_with_pipes (NULL,                                            /* Working directory */
190
 
                                                                   argv,                                                /* Argument vector */
191
 
                                                                   envp,                                                /* Environment */
192
 
                                                                   G_SPAWN_DO_NOT_REAP_CHILD,   /* Flags */
193
 
                                                                   NULL,                                                /* Child setup */
194
 
                                                                   NULL,                                                /* Data to child setup */
195
 
                                                                   &pdialog->backend_pid,               /* PID */
196
 
                                                                   &my_stdin,                                           /* Stdin */
197
 
                                                                   &my_stdout,                                          /* Stdout */
198
 
                                                                   &my_stderr,                                          /* Stderr */
199
 
                                                                   error)) {                                    /* GError */
200
 
 
201
 
                /* An error occured */
202
 
                free_passwd_resources (pdialog);
203
 
 
204
 
                return FALSE;
205
 
        }
206
 
 
207
 
        /* 2>&1 */
208
 
        if (dup2 (my_stderr, my_stdout) == -1) {
209
 
                /* Failed! */
210
 
                g_set_error (error,
211
 
                                         PASSDLG_ERROR,
212
 
                                         PASSDLG_ERROR_BACKEND,
213
 
                                         strerror (errno));
214
 
 
215
 
                /* Clean up */
216
 
                stop_passwd (pdialog);
217
 
 
218
 
                return FALSE;
219
 
        }
220
 
 
221
 
        /* Open IO Channels */
222
 
        pdialog->backend_stdin = g_io_channel_unix_new (my_stdin);
223
 
        pdialog->backend_stdout = g_io_channel_unix_new (my_stdout);
224
 
 
225
 
        /* Set raw encoding */
226
 
        /* Set nonblocking mode */
227
 
        if (g_io_channel_set_encoding (pdialog->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
228
 
                g_io_channel_set_encoding (pdialog->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
229
 
                g_io_channel_set_flags (pdialog->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
230
 
                g_io_channel_set_flags (pdialog->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
231
 
 
232
 
                /* Clean up */
233
 
                stop_passwd (pdialog);
234
 
                return FALSE;
235
 
        }
236
 
 
237
 
        /* Turn off buffering */
238
 
        g_io_channel_set_buffered (pdialog->backend_stdin, FALSE);
239
 
        g_io_channel_set_buffered (pdialog->backend_stdout, FALSE);
240
 
 
241
 
        /* Add IO Channel watcher */
242
 
        pdialog->backend_stdout_watch_id = g_io_add_watch (pdialog->backend_stdout,
243
 
                                                                                                           G_IO_IN | G_IO_PRI,
244
 
                                                                                                           (GIOFunc) io_watch_stdout, pdialog);
245
 
 
246
 
        /* Add child watcher */
247
 
        pdialog->backend_child_watch_id = g_child_watch_add (pdialog->backend_pid, (GChildWatchFunc) child_watch_cb, pdialog);
248
 
 
249
 
        /* Success! */
250
 
 
251
 
        return TRUE;
252
 
}
253
 
 
254
 
/* Stop passwd backend */
255
 
static void
256
 
stop_passwd (PasswordDialog *pdialog)
257
 
{
258
 
        /* This is the standard way of returning from the dialog with passwd.
259
 
         * If we return this way we can safely kill passwd as it has completed
260
 
         * its task.
261
 
         */
262
 
 
263
 
        if (pdialog->backend_pid != -1) {
264
 
                kill (pdialog->backend_pid, 9);
265
 
        }
266
 
 
267
 
        /* We must run free_passwd_resources here and not let our child
268
 
         * watcher do it, since it will access invalid memory after the
269
 
         * dialog has been closed and cleaned up.
270
 
         *
271
 
         * If we had more than a single thread we'd need to remove
272
 
         * the child watch before trying to kill the child.
273
 
         */
274
 
        free_passwd_resources (pdialog);
275
 
}
276
 
 
277
 
/* Clean up passwd resources */
278
 
static void
279
 
free_passwd_resources (PasswordDialog *pdialog)
280
 
{
281
 
        GError  *error = NULL;
282
 
 
283
 
        /* Remove the child watcher */
284
 
        if (pdialog->backend_child_watch_id != 0) {
285
 
 
286
 
                g_source_remove (pdialog->backend_child_watch_id);
287
 
 
288
 
                pdialog->backend_child_watch_id = 0;
289
 
        }
290
 
 
291
 
 
292
 
        /* Close IO channels (internal file descriptors are automatically closed) */
293
 
        if (pdialog->backend_stdin != NULL) {
294
 
 
295
 
                if (g_io_channel_shutdown (pdialog->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
296
 
                        g_warning (_("Could not shutdown backend_stdin IO channel: %s"), error->message);
297
 
                        g_error_free (error);
298
 
                        error = NULL;
299
 
                }
300
 
 
301
 
                g_io_channel_unref (pdialog->backend_stdin);
302
 
 
303
 
                pdialog->backend_stdin = NULL;
304
 
        }
305
 
 
306
 
        if (pdialog->backend_stdout != NULL) {
307
 
 
308
 
                if (g_io_channel_shutdown (pdialog->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
309
 
                        g_warning (_("Could not shutdown backend_stdout IO channel: %s"), error->message);
310
 
                        g_error_free (error);
311
 
                        error = NULL;
312
 
                }
313
 
 
314
 
                g_io_channel_unref (pdialog->backend_stdout);
315
 
 
316
 
                pdialog->backend_stdout = NULL;
317
 
        }
318
 
 
319
 
        /* Remove IO watcher */
320
 
        if (pdialog->backend_stdout_watch_id != 0) {
321
 
 
322
 
                g_source_remove (pdialog->backend_stdout_watch_id);
323
 
 
324
 
                pdialog->backend_stdout_watch_id = 0;
325
 
        }
326
 
 
327
 
        /* Close PID */
328
 
        if (pdialog->backend_pid != -1) {
329
 
 
330
 
                g_spawn_close_pid (pdialog->backend_pid);
331
 
 
332
 
                pdialog->backend_pid = -1;
333
 
        }
334
 
 
335
 
        /* Clear backend state */
336
 
        pdialog->backend_state = PASSWD_STATE_NONE;
337
 
}
338
 
 
339
 
/*
340
 
 * }} Spawning and closing of backend
341
 
 */
342
 
 
343
 
/*
344
 
 * Backend communication code {{
345
 
 */
346
 
 
347
 
/* Write the first element of queue through channel */
348
 
static void
349
 
io_queue_pop (GQueue *queue, GIOChannel *channel)
350
 
{
351
 
        gchar   *buf;
352
 
        gsize   bytes_written;
353
 
        GError  *error = NULL;
354
 
 
355
 
        buf = g_queue_pop_head (queue);
356
 
 
357
 
        if (buf != NULL) {
358
 
 
359
 
                if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
360
 
                        g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
361
 
                        g_error_free (error);
362
 
                }
363
 
 
364
 
                g_free (buf);
365
 
        }
366
 
}
367
 
 
368
 
/* Goes through the argument list, checking if one of them occurs in str
369
 
 * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
370
 
static gboolean
371
 
is_string_complete (gchar *str, ...)
372
 
{
373
 
        va_list ap;
374
 
        gchar   *arg;
375
 
 
376
 
        if (strlen (str) == 0) {
377
 
                return FALSE;
378
 
        }
379
 
 
380
 
        va_start (ap, str);
381
 
 
382
 
        while ((arg = va_arg (ap, char *)) != NULL) {
383
 
                if (g_strrstr (str, arg) != NULL) {
384
 
                        va_end (ap);
385
 
                        return TRUE;
386
 
                }
387
 
        }
388
 
 
389
 
        va_end (ap);
390
 
 
391
 
        return FALSE;
392
 
}
393
 
 
394
 
/* Authentication attempt succeeded. Update the GUI accordingly. */
395
 
static void
396
 
authenticated_user (PasswordDialog *pdialog)
397
 
{
398
 
        pdialog->backend_state = PASSWD_STATE_NEW;
399
 
 
400
 
        if (pdialog->authenticated) {
401
 
                /* This is a re-authentication
402
 
                 * It succeeded, so pop our new password from the queue */
403
 
                io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
404
 
        }
405
 
 
406
 
        /* Update UI state */
407
 
        passdlg_set_auth_state (pdialog, TRUE);
408
 
        passdlg_set_status (pdialog, _("Authenticated!"));
409
 
 
410
 
        /* Check to see if the passwords are valid
411
 
         * (They might be non-empty if the user had to re-authenticate,
412
 
         *  and thus we need to enable the change-password-button) */
413
 
        passdlg_refresh_password_state (pdialog);
414
 
}
415
 
 
416
 
/*
417
 
 * IO watcher for stdout, called whenever there is data to read from the backend.
418
 
 * This is where most of the actual IO handling happens.
419
 
 */
420
 
static gboolean
421
 
io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswordDialog *pdialog)
422
 
{
423
 
        static GString *str = NULL;     /* Persistent buffer */
424
 
 
425
 
        gchar           buf[BUFSIZE];           /* Temporary buffer */
426
 
        gsize           bytes_read;
427
 
        GError          *error = NULL;
428
 
 
429
 
        gchar           *msg = NULL;            /* Status error message */
430
 
        GtkBuilder      *dialog;
431
 
 
432
 
        gboolean        reinit = FALSE;
433
 
 
434
 
        /* Initialize buffer */
435
 
        if (str == NULL) {
436
 
                str = g_string_new ("");
437
 
        }
438
 
 
439
 
        dialog = pdialog->ui;
440
 
 
441
 
        if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &error) != G_IO_STATUS_NORMAL) {
442
 
                g_warning ("IO Channel read error: %s", error->message);
443
 
                g_error_free (error);
444
 
 
445
 
                return TRUE;
446
 
        }
447
 
 
448
 
        str = g_string_append_len (str, buf, bytes_read);
449
 
 
450
 
        /* In which state is the backend? */
451
 
        switch (pdialog->backend_state) {
452
 
                case PASSWD_STATE_AUTH:
453
 
                        /* Passwd is asking for our current password */
454
 
 
455
 
                        if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
456
 
                                /* Which response did we get? */
457
 
                                passdlg_set_busy (pdialog, FALSE);
458
 
 
459
 
                                if (g_strrstr (str->str, "assword: ") != NULL) {
460
 
                                        /* Authentication successful */
461
 
 
462
 
                                        authenticated_user (pdialog);
463
 
 
464
 
                                } else {
465
 
                                        /* Authentication failed */
466
 
 
467
 
                                        if (pdialog->authenticated) {
468
 
                                                /* This is a re-auth, and it failed.
469
 
                                                 * The password must have been changed in the meantime!
470
 
                                                 * Ask the user to re-authenticate
471
 
                                                 */
472
 
 
473
 
                                                /* Update status message and auth state */
474
 
                                                passdlg_set_status (pdialog, _("Your password has been changed since you initially authenticated! Please re-authenticate."));
475
 
                                        } else {
476
 
                                                passdlg_set_status (pdialog, _("That password was incorrect."));
477
 
                                        }
478
 
 
479
 
                                        /* Focus current password */
480
 
                                        gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password));
481
 
                                }
482
 
 
483
 
                                reinit = TRUE;
484
 
                        }
485
 
                        break;
486
 
                case PASSWD_STATE_NEW:
487
 
                        /* Passwd is asking for our new password */
488
 
 
489
 
                        if (is_string_complete (str->str, "assword: ", NULL)) {
490
 
                                /* Advance to next state */
491
 
                                pdialog->backend_state = PASSWD_STATE_RETYPE;
492
 
 
493
 
                                /* Pop retyped password from queue and into IO channel */
494
 
                                io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
495
 
 
496
 
                                reinit = TRUE;
497
 
                        }
498
 
                        break;
499
 
                case PASSWD_STATE_RETYPE:
500
 
                        /* Passwd is asking for our retyped new password */
501
 
 
502
 
                        if (is_string_complete (str->str, "successfully",
503
 
                                                          "short",
504
 
                                                          "longer",
505
 
                                                          "palindrome",
506
 
                                                          "dictionary",
507
 
                                                          "simpl", /* catches both simple and simplistic */
508
 
                                                          "similar",
509
 
                                                          "different",
510
 
                                                          "case",
511
 
                                                          "wrapped",
512
 
                                                          "recovered",
513
 
                                                          "recent"
514
 
                                                          "unchanged",
515
 
                                                          "match",
516
 
                                                          "1 numeric or special",
517
 
                                                          "failure",
518
 
                                                          NULL)) {
519
 
 
520
 
                                /* What response did we get? */
521
 
                                passdlg_set_busy (pdialog, FALSE);
522
 
 
523
 
                                if (g_strrstr (str->str, "successfully") != NULL) {
524
 
                                        /* Hooray! */
525
 
 
526
 
                                        passdlg_clear (pdialog);
527
 
                                        passdlg_set_status (pdialog, _("Your password has been changed."));
528
 
                                } else {
529
 
                                        /* Ohnoes! */
530
 
 
531
 
                                        /* Focus new password */
532
 
                                        gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password));
533
 
 
534
 
                                        if (g_strrstr (str->str, "recovered") != NULL) {
535
 
                                                /* What does this indicate?
536
 
                                                 * "Authentication information cannot be recovered?" from libpam? */
537
 
                                                msg = g_strdup_printf (_("System error: %s."), str->str);
538
 
                                        } else if (g_strrstr (str->str, "short") != NULL ||
539
 
                                                   g_strrstr (str->str, "longer") != NULL) {
540
 
                                                msg = g_strdup (_("The password is too short."));
541
 
                                        } else if (g_strrstr (str->str, "palindrome") != NULL ||
542
 
                                                   g_strrstr (str->str, "simpl") != NULL ||
543
 
                                                   g_strrstr (str->str, "dictionary") != NULL) {
544
 
                                                msg = g_strdup (_("The password is too simple."));
545
 
                                        } else if (g_strrstr (str->str, "similar") != NULL ||
546
 
                                                   g_strrstr (str->str, "different") != NULL ||
547
 
                                                   g_strrstr (str->str, "case") != NULL ||
548
 
                                                   g_strrstr (str->str, "wrapped") != NULL) {
549
 
                                                msg = g_strdup (_("The old and new passwords are too similar."));
550
 
                                        } else if (g_strrstr (str->str, "1 numeric or special") != NULL) {
551
 
                                                msg = g_strdup (_("The new password must contain numeric or special character(s)."));
552
 
                                        } else if (g_strrstr (str->str, "unchanged") != NULL ||
553
 
                                                   g_strrstr (str->str, "match") != NULL) {
554
 
                                                msg = g_strdup (_("The old and new passwords are the same."));
555
 
                                        } else if (g_strrstr (str->str, "recent") != NULL) {
556
 
                                                msg = g_strdup (_("The new password has already been used recently."));
557
 
                                        } else if (g_strrstr (str->str, "failure") != NULL) {
558
 
                                                /* Authentication failure */
559
 
                                                msg = g_strdup (_("Your password has been changed since you initially authenticated! Please re-authenticate."));
560
 
 
561
 
                                                passdlg_set_auth_state (pdialog, FALSE);
562
 
                                        }
563
 
                                }
564
 
 
565
 
                                reinit = TRUE;
566
 
 
567
 
                                if (msg != NULL) {
568
 
                                        /* An error occured! */
569
 
                                        passdlg_set_status (pdialog, msg);
570
 
                                        g_free (msg);
571
 
 
572
 
                                        /* At this point, passwd might have exited, in which case
573
 
                                         * child_watch_cb should clean up for us and remove this watcher.
574
 
                                         * On some error conditions though, passwd just re-prompts us
575
 
                                         * for our new password. */
576
 
 
577
 
                                        pdialog->backend_state = PASSWD_STATE_ERR;
578
 
                                }
579
 
 
580
 
                                /* child_watch_cb should clean up for us now */
581
 
                        }
582
 
                        break;
583
 
                case PASSWD_STATE_NONE:
584
 
                        /* Passwd is not asking for anything yet */
585
 
                        if (is_string_complete (str->str, "assword: ", NULL)) {
586
 
 
587
 
                                /* If the user does not have a password set,
588
 
                                 * passwd will immediately ask for the new password,
589
 
                                 * so skip the AUTH phase */
590
 
                                if (is_string_complete (str->str, "new", "New", NULL)) {
591
 
                                        gchar *pw;
592
 
 
593
 
                                        pdialog->backend_state = PASSWD_STATE_NEW;
594
 
 
595
 
                                        passdlg_set_busy (pdialog, FALSE);
596
 
                                        authenticated_user (pdialog);
597
 
 
598
 
                                        /* since passwd didn't ask for our old password
599
 
                                         * in this case, simply remove it from the queue */
600
 
                                        pw = g_queue_pop_head (pdialog->backend_stdin_queue);
601
 
                                        g_free (pw);
602
 
                                } else {
603
 
 
604
 
                                        pdialog->backend_state = PASSWD_STATE_AUTH;
605
 
 
606
 
                                        /* Pop the IO queue, i.e. send current password */
607
 
                                        io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
608
 
                                }
609
 
 
610
 
                                reinit = TRUE;
611
 
                        }
612
 
                        break;
613
 
                default:
614
 
                        /* Passwd has returned an error */
615
 
                        reinit = TRUE;
616
 
                        break;
617
 
        }
618
 
 
619
 
        if (reinit) {
620
 
                g_string_free (str, TRUE);
621
 
                str = NULL;
622
 
        }
623
 
 
624
 
        /* Continue calling us */
625
 
        return TRUE;
626
 
}
627
 
 
628
 
/*
629
 
 * }} Backend communication code
630
 
 */
631
 
 
632
 
/* Adds the current password to the IO queue */
633
 
static void
634
 
authenticate (PasswordDialog *pdialog)
635
 
{
636
 
        gchar   *s;
637
 
 
638
 
        s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->current_password));
639
 
 
640
 
        g_queue_push_tail (pdialog->backend_stdin_queue, s);
641
 
}
642
 
 
643
 
/* Adds the new password twice to the IO queue */
644
 
static void
645
 
update_password (PasswordDialog *pdialog)
646
 
{
647
 
        gchar   *s;
648
 
 
649
 
        s = g_strdup_printf ("%s\n", gtk_entry_get_text (pdialog->new_password));
650
 
 
651
 
        g_queue_push_tail (pdialog->backend_stdin_queue, s);
652
 
        /* We need to allocate new space because io_queue_pop() g_free()s
653
 
         * every element of the queue after it's done */
654
 
        g_queue_push_tail (pdialog->backend_stdin_queue, g_strdup (s));
655
 
}
656
 
 
657
 
/* Sets dialog busy state according to busy
658
 
 *
659
 
 * When busy:
660
 
 *      Sets the cursor to busy
661
 
 *  Disables the interface to prevent that the user interferes
662
 
 * Reverts all this when non-busy
663
 
 *
664
 
 * Note that this function takes into account the
665
 
 * authentication state of the dialog. So setting the
666
 
 * dialog to busy and then back to normal should leave
667
 
 * the dialog unchanged.
668
 
 */
669
 
static void
670
 
passdlg_set_busy (PasswordDialog *pdialog, gboolean busy)
671
 
{
672
 
        GtkBuilder *dialog;
673
 
        GtkWidget  *toplevel;
674
 
        GdkCursor  *cursor = NULL;
675
 
        GdkDisplay *display;
676
 
 
677
 
        dialog = pdialog->ui;
678
 
 
679
 
        /* Set cursor */
680
 
        toplevel = WID ("change-password");
681
 
        display = gtk_widget_get_display (toplevel);
682
 
        if (busy) {
683
 
                cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
684
 
        }
685
 
 
686
 
        gdk_window_set_cursor (gtk_widget_get_window (toplevel), cursor);
687
 
        gdk_display_flush (display);
688
 
 
689
 
        if (busy) {
690
 
                gdk_cursor_unref (cursor);
691
 
        }
692
 
 
693
 
        /* Disable/Enable UI */
694
 
        if (pdialog->authenticated) {
695
 
                /* Authenticated state */
696
 
 
697
 
                /* Enable/disable new password section */
698
 
                g_object_set (pdialog->new_password, "sensitive", !busy, NULL);
699
 
                g_object_set (pdialog->retyped_password, "sensitive", !busy, NULL);
700
 
                g_object_set (WID ("new-password-label"), "sensitive", !busy, NULL);
701
 
                g_object_set (WID ("retyped-password-label"), "sensitive", !busy, NULL);
702
 
 
703
 
                /* Enable/disable change password button */
704
 
                g_object_set (WID ("change-password-button"), "sensitive", !busy, NULL);
705
 
 
706
 
        } else {
707
 
                /* Not-authenticated state */
708
 
 
709
 
                /* Enable/disable auth section state */
710
 
                g_object_set (pdialog->current_password, "sensitive", !busy, NULL);
711
 
                g_object_set (WID ("authenticate-button"), "sensitive", !busy, NULL);
712
 
                g_object_set (WID ("current-password-label"), "sensitive", !busy, NULL);
713
 
        }
714
 
}
715
 
 
716
 
/* Launch an error dialog */
717
 
static void
718
 
passdlg_error_dialog (GtkWindow *parent, const gchar *title,
719
 
                      const gchar *msg, const gchar *details)
720
 
{
721
 
        GtkWidget *dialog;
722
 
 
723
 
        dialog = gtk_message_dialog_new (parent, GTK_DIALOG_MODAL,
724
 
                                         GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
725
 
                                         msg);
726
 
        if (title)
727
 
                gtk_window_set_title (GTK_WINDOW (dialog), title);
728
 
 
729
 
        if (details)
730
 
                gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
731
 
                                                          details);
732
 
        gtk_dialog_run (GTK_DIALOG (dialog));
733
 
        gtk_widget_destroy (dialog);
734
 
}
735
 
 
736
 
/* Set authenticated state of dialog according to state
737
 
 *
738
 
 * When in authenticated state:
739
 
 *      Disables authentication-part of interface
740
 
 *  Enables new-password-part of interface
741
 
 * When in not-authenticated state:
742
 
 *      Enables authentication-part of interface
743
 
 *  Disables new-password-part of interface
744
 
 *      Disables the change-password-button
745
 
 */
746
 
static void
747
 
passdlg_set_auth_state (PasswordDialog *pdialog, gboolean state)
748
 
{
749
 
        GtkBuilder *dialog;
750
 
 
751
 
        dialog = pdialog->ui;
752
 
 
753
 
        /* Widgets which require a not-authenticated state to be accessible */
754
 
        g_object_set (pdialog->current_password, "sensitive", !state, NULL);
755
 
        g_object_set (WID ("current-password-label"), "sensitive", !state, NULL);
756
 
        g_object_set (WID ("authenticate-button"), "sensitive", !state, NULL);
757
 
 
758
 
        /* Widgets which require authentication to be accessible */
759
 
        g_object_set (pdialog->new_password, "sensitive", state, NULL);
760
 
        g_object_set (pdialog->retyped_password, "sensitive", state, NULL);
761
 
        g_object_set (WID ("new-password-label"), "sensitive", state, NULL);
762
 
        g_object_set (WID ("retyped-password-label"), "sensitive", state, NULL);
763
 
 
764
 
        if (!state) {
765
 
                /* Disable change-password-button when in not-authenticated state */
766
 
                g_object_set (WID ("change-password-button"), "sensitive", FALSE, NULL);
767
 
        }
768
 
 
769
 
        pdialog->authenticated = state;
770
 
 
771
 
        if (state) {
772
 
                /* Authenticated state */
773
 
 
774
 
                /* Focus new password */
775
 
                gtk_widget_grab_focus (GTK_WIDGET (pdialog->new_password));
776
 
 
777
 
                /* Set open lock image */
778
 
                gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-allow", GTK_ICON_SIZE_DIALOG);
779
 
        } else {
780
 
                /* Not authenticated state */
781
 
 
782
 
                /* Focus current password */
783
 
                gtk_widget_grab_focus (GTK_WIDGET (pdialog->current_password));
784
 
 
785
 
                /* Set closed lock image */
786
 
                gtk_image_set_from_icon_name (GTK_IMAGE (pdialog->dialog_image), "changes-prevent", GTK_ICON_SIZE_DIALOG);
787
 
        }
788
 
}
789
 
 
790
 
/* Set status field message */
791
 
static void
792
 
passdlg_set_status (PasswordDialog *pdialog, gchar *msg)
793
 
{
794
 
        g_object_set (pdialog->status_label, "label", msg, NULL);
795
 
}
796
 
 
797
 
/* Clear dialog (except the status message) */
798
 
static void
799
 
passdlg_clear (PasswordDialog *pdialog)
800
 
{
801
 
        /* Set non-authenticated state */
802
 
        passdlg_set_auth_state (pdialog, FALSE);
803
 
 
804
 
        /* Clear password entries */
805
 
        gtk_entry_set_text (pdialog->current_password, "");
806
 
        gtk_entry_set_text (pdialog->new_password, "");
807
 
        gtk_entry_set_text (pdialog->retyped_password, "");
808
 
}
809
 
 
810
 
/* Start backend and handle errors
811
 
 * If backend is already running, stop it
812
 
 * If an error occurs, show error dialog */
813
 
static gboolean
814
 
passdlg_spawn_passwd (PasswordDialog *pdialog)
815
 
{
816
 
        GError  *error = NULL;
817
 
        gchar   *details;
818
 
 
819
 
        /* If backend is running, stop it */
820
 
        stop_passwd (pdialog);
821
 
 
822
 
        /* Spawn backend */
823
 
        if (!spawn_passwd (pdialog, &error)) {
824
 
                GtkWidget *parent = GTK_WIDGET (gtk_builder_get_object (pdialog->ui, "change-password"));
825
 
 
826
 
                /* translators: Unable to launch <program>: <error message> */
827
 
                details = g_strdup_printf (_("Unable to launch %s: %s"),
828
 
                                           "/usr/bin/passwd", error->message);
829
 
 
830
 
                passdlg_error_dialog (GTK_WINDOW (parent),
831
 
                                      _("Unable to launch backend"),
832
 
                                      _("A system error has occurred"),
833
 
                                      details);
834
 
 
835
 
                g_free (details);
836
 
                g_error_free (error);
837
 
 
838
 
                return FALSE;
839
 
        }
840
 
 
841
 
        return TRUE;
842
 
}
843
 
 
844
 
/* Called when the "Authenticate" button is clicked */
845
 
static void
846
 
passdlg_authenticate (GtkButton *button, PasswordDialog *pdialog)
847
 
{
848
 
        /* Set busy as this can be a long process */
849
 
        passdlg_set_busy (pdialog, TRUE);
850
 
 
851
 
        /* Update status message */
852
 
        passdlg_set_status (pdialog, _("Checking password..."));
853
 
 
854
 
        /* Spawn backend */
855
 
        if (!passdlg_spawn_passwd (pdialog)) {
856
 
                passdlg_set_busy (pdialog, FALSE);
857
 
                return;
858
 
        }
859
 
 
860
 
        authenticate (pdialog);
861
 
 
862
 
        /* Our IO watcher should now handle the rest */
863
 
}
864
 
 
865
 
/* Validate passwords
866
 
 * Returns:
867
 
 * PASSDLG_ERROR_NONE (0) if passwords are valid
868
 
 * PASSDLG_ERROR_NEW_PASSWORD_EMPTY
869
 
 * PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY
870
 
 * PASSDLG_ERROR_PASSWORDS_NOT_EQUAL
871
 
 */
872
 
static guint
873
 
passdlg_validate_passwords (PasswordDialog *pdialog)
874
 
{
875
 
        GtkBuilder      *dialog;
876
 
        const gchar     *new_password, *retyped_password;
877
 
        glong                   nlen, rlen;
878
 
 
879
 
        dialog = pdialog->ui;
880
 
 
881
 
        new_password = gtk_entry_get_text (pdialog->new_password);
882
 
        retyped_password = gtk_entry_get_text (pdialog->retyped_password);
883
 
 
884
 
        nlen = g_utf8_strlen (new_password, -1);
885
 
        rlen = g_utf8_strlen (retyped_password, -1);
886
 
 
887
 
        if (nlen == 0) {
888
 
                /* New password empty */
889
 
                return PASSDLG_ERROR_NEW_PASSWORD_EMPTY;
890
 
        }
891
 
 
892
 
        if (rlen == 0) {
893
 
                /* Retyped password empty */
894
 
                return PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY;
895
 
        }
896
 
 
897
 
        if (nlen != rlen || strncmp (new_password, retyped_password, nlen) != 0) {
898
 
                /* Passwords not equal */
899
 
                return PASSDLG_ERROR_PASSWORDS_NOT_EQUAL;
900
 
        }
901
 
 
902
 
        /* Success */
903
 
        return PASSDLG_ERROR_NONE;
904
 
}
905
 
 
906
 
/* Refresh the valid password UI state, i.e. re-validate
907
 
 * and enable/disable the Change Password button.
908
 
 * Returns: Return value of passdlg_validate_passwords */
909
 
static guint
910
 
passdlg_refresh_password_state (PasswordDialog *pdialog)
911
 
{
912
 
        GtkBuilder *dialog;
913
 
        guint           ret;
914
 
        gboolean        valid = FALSE;
915
 
 
916
 
        dialog = pdialog->ui;
917
 
 
918
 
        ret = passdlg_validate_passwords (pdialog);
919
 
 
920
 
        if (ret == PASSDLG_ERROR_NONE) {
921
 
                valid = TRUE;
922
 
        }
923
 
 
924
 
        g_object_set (WID ("change-password-button"), "sensitive", valid, NULL);
925
 
 
926
 
        return ret;
927
 
}
928
 
 
929
 
/* Called whenever any of the new password fields have changed */
930
 
static void
931
 
passdlg_check_password (GtkEntry *entry, PasswordDialog *pdialog)
932
 
{
933
 
        guint   ret;
934
 
 
935
 
        ret = passdlg_refresh_password_state (pdialog);
936
 
 
937
 
        switch (ret) {
938
 
                case PASSDLG_ERROR_NONE:
939
 
                        passdlg_set_status (pdialog, _("Click <b>Change password</b> to change your password."));
940
 
                        break;
941
 
                case PASSDLG_ERROR_NEW_PASSWORD_EMPTY:
942
 
                        passdlg_set_status (pdialog, _("Please type your password in the <b>New password</b> field."));
943
 
                        break;
944
 
                case PASSDLG_ERROR_RETYPED_PASSWORD_EMPTY:
945
 
                        passdlg_set_status (pdialog, _("Please type your password again in the <b>Retype new password</b> field."));
946
 
                        break;
947
 
                case PASSDLG_ERROR_PASSWORDS_NOT_EQUAL:
948
 
                        passdlg_set_status (pdialog, _("The two passwords are not equal."));
949
 
                        break;
950
 
                default:
951
 
                        g_warning ("Unknown passdlg_check_password error: %d", ret);
952
 
                        break;
953
 
        }
954
 
}
955
 
 
956
 
/* Called when the "Change password" dialog-button is clicked
957
 
 * Returns: TRUE if we want to keep the dialog running, FALSE otherwise */
958
 
static gboolean
959
 
passdlg_process_response (PasswordDialog *pdialog, gint response_id)
960
 
{
961
 
 
962
 
        if (response_id == GTK_RESPONSE_OK) {
963
 
                /* Set busy as this can be a long process */
964
 
                passdlg_set_busy (pdialog, TRUE);
965
 
 
966
 
                /* Stop passwd if an error occured and it is still running */
967
 
                if (pdialog->backend_state == PASSWD_STATE_ERR) {
968
 
 
969
 
                        /* Stop passwd, free resources */
970
 
                        stop_passwd (pdialog);
971
 
                }
972
 
 
973
 
                /* Check that the backend is still running, or that an error
974
 
                 * hass occured but it has not yet exited */
975
 
                if (pdialog->backend_pid == -1) {
976
 
                        /* If it is not, re-run authentication */
977
 
 
978
 
                        /* Spawn backend */
979
 
                        if (!passdlg_spawn_passwd (pdialog)) {
980
 
                                return TRUE;
981
 
                        }
982
 
 
983
 
                        /* Add current and new passwords to queue */
984
 
                        authenticate (pdialog);
985
 
                        update_password (pdialog);
986
 
                } else {
987
 
                        /* Only add new passwords to queue */
988
 
                        update_password (pdialog);
989
 
 
990
 
                        /* Pop new password through the backend */
991
 
                        io_queue_pop (pdialog->backend_stdin_queue, pdialog->backend_stdin);
992
 
                }
993
 
 
994
 
                /* Our IO watcher should now handle the rest */
995
 
 
996
 
                /* Keep the dialog running */
997
 
                return TRUE;
998
 
        }
999
 
 
1000
 
        return FALSE;
1001
 
}
1002
 
 
1003
 
/* Activates (moves focus or activates) widget w */
1004
 
static void
1005
 
passdlg_activate (GtkEntry *entry, GtkWidget *w)
1006
 
{
1007
 
        if (GTK_IS_BUTTON (w)) {
1008
 
                gtk_widget_activate (w);
1009
 
        } else {
1010
 
                gtk_widget_grab_focus (w);
1011
 
        }
1012
 
}
1013
 
 
1014
 
/* Initialize password dialog */
1015
 
static void
1016
 
passdlg_init (PasswordDialog *pdialog, GtkWindow *parent)
1017
 
{
1018
 
        GtkBuilder              *dialog;
1019
 
        GtkWidget               *wpassdlg;
1020
 
        GtkAccelGroup   *group;
1021
 
 
1022
 
        /* Initialize dialog */
1023
 
        dialog = gtk_builder_new ();
1024
 
    gtk_builder_add_from_file (dialog, GNOMECC_UI_DIR "/gnome-about-me-password.ui", NULL);
1025
 
        pdialog->ui = dialog;
1026
 
 
1027
 
        wpassdlg = WID ("change-password");
1028
 
        capplet_set_icon (wpassdlg, "user-info");
1029
 
 
1030
 
        group = gtk_accel_group_new ();
1031
 
 
1032
 
        /*
1033
 
         * Initialize backend
1034
 
         */
1035
 
 
1036
 
        /* Initialize backend_pid. -1 means the backend is not running */
1037
 
        pdialog->backend_pid = -1;
1038
 
 
1039
 
        /* Initialize IO Channels */
1040
 
        pdialog->backend_stdin = NULL;
1041
 
        pdialog->backend_stdout = NULL;
1042
 
 
1043
 
        /* Initialize write queue */
1044
 
        pdialog->backend_stdin_queue = g_queue_new ();
1045
 
 
1046
 
        /* Initialize watchers */
1047
 
        pdialog->backend_child_watch_id = 0;
1048
 
        pdialog->backend_stdout_watch_id = 0;
1049
 
 
1050
 
        /* Initialize backend state */
1051
 
        pdialog->backend_state = PASSWD_STATE_NONE;
1052
 
 
1053
 
        /*
1054
 
         * Initialize UI
1055
 
         */
1056
 
 
1057
 
        /* Initialize pdialog widgets */
1058
 
        pdialog->current_password       = GTK_ENTRY (WID ("current-password"));
1059
 
        pdialog->new_password           = GTK_ENTRY (WID ("new-password"));
1060
 
        pdialog->retyped_password       = GTK_ENTRY (WID ("retyped-password"));
1061
 
        pdialog->dialog_image           = GTK_IMAGE (WID ("dialog-image"));
1062
 
        pdialog->status_label           = GTK_LABEL (WID ("status-label"));
1063
 
 
1064
 
        /* Initialize accelerators */
1065
 
        gtk_widget_add_accelerator (GTK_WIDGET (pdialog->current_password),
1066
 
                                                                "activate", group,
1067
 
                                                                GDK_Return, 0,
1068
 
                                                                0);
1069
 
 
1070
 
        gtk_widget_add_accelerator (GTK_WIDGET (pdialog->new_password),
1071
 
                                                                "activate", group,
1072
 
                                                                GDK_Return, 0,
1073
 
                                                                0);
1074
 
 
1075
 
        /* Activate authenticate-button when enter is pressed in current-password */
1076
 
        g_signal_connect (G_OBJECT (pdialog->current_password), "activate",
1077
 
                                          G_CALLBACK (passdlg_activate), WID ("authenticate-button"));
1078
 
 
1079
 
        /* Activate retyped-password when enter is pressed in new-password */
1080
 
        g_signal_connect (G_OBJECT (pdialog->new_password), "activate",
1081
 
                                          G_CALLBACK (passdlg_activate), pdialog->retyped_password);
1082
 
 
1083
 
        /* Clear status message */
1084
 
        passdlg_set_status (pdialog, "");
1085
 
 
1086
 
        /* Set non-authenticated state */
1087
 
        passdlg_set_auth_state (pdialog, FALSE);
1088
 
 
1089
 
        /* Connect signal handlers */
1090
 
        g_signal_connect (G_OBJECT (WID ("authenticate-button")), "clicked",
1091
 
                                          G_CALLBACK (passdlg_authenticate), pdialog);
1092
 
 
1093
 
        /* Verify new passwords on-the-fly */
1094
 
        g_signal_connect (G_OBJECT (WID ("new-password")), "changed",
1095
 
                                          G_CALLBACK (passdlg_check_password), pdialog);
1096
 
        g_signal_connect (G_OBJECT (WID ("retyped-password")), "changed",
1097
 
                                          G_CALLBACK (passdlg_check_password), pdialog);
1098
 
 
1099
 
        /* Set misc dialog properties */
1100
 
        gtk_window_set_resizable (GTK_WINDOW (wpassdlg), FALSE);
1101
 
        gtk_window_set_transient_for (GTK_WINDOW (wpassdlg), GTK_WINDOW (parent));
1102
 
}
1103
 
 
1104
 
/* Main */
1105
 
void
1106
 
gnome_about_me_password (GtkWindow *parent)
1107
 
{
1108
 
        PasswordDialog  *pdialog;
1109
 
        GtkBuilder              *dialog;
1110
 
        GtkWidget               *wpassdlg;
1111
 
 
1112
 
        gint                    result;
1113
 
        gboolean                response;
1114
 
 
1115
 
        /* Initialize dialog */
1116
 
        pdialog = g_new0 (PasswordDialog, 1);
1117
 
        passdlg_init (pdialog, parent);
1118
 
 
1119
 
        dialog = pdialog->ui;
1120
 
        wpassdlg = WID ("change-password");
1121
 
 
1122
 
        /* Go! */
1123
 
        gtk_widget_show_all (wpassdlg);
1124
 
 
1125
 
        do {
1126
 
                result = gtk_dialog_run (GTK_DIALOG (wpassdlg));
1127
 
                response = passdlg_process_response (pdialog, result);
1128
 
        } while (response);
1129
 
 
1130
 
        /* Clean up */
1131
 
        stop_passwd (pdialog);
1132
 
        gtk_widget_destroy (wpassdlg);
1133
 
        g_queue_free (pdialog->backend_stdin_queue);
1134
 
        g_object_unref (dialog);
1135
 
        g_free (pdialog);
1136
 
}