1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend
3  * for user administration.
4  *
5  * Copyright (C) 2002 Diego Gonzalez
6  * Copyright (C) 2006 Johannes H. Jensen
7  * Copyright (C) 2010 Milan Bouchet-Valat
8  *
9  * Written by: Diego Gonzalez <diego@pemas.net>
10  * Modified by: Johannes H. Jensen <joh@deworks.net>,
11  *              Milan Bouchet-Valat <nalimilan@club.fr>.
12  *
13  * This program is free software; you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation; either version 2, or (at your option)
16  * any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program; if not, see <http://www.gnu.org/licenses/>.
25  *
26  * Most of this code originally comes from gnome-about-me-password.c,
27  * from gnome-control-center.
28  */
29 
30 #include <config.h>
31 #include <glib/gi18n.h>
32 
33 #include <unistd.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <sys/wait.h>
37 
38 #if __sun
39 #include <sys/types.h>
40 #include <signal.h>
41 #endif
42 
43 #include "run-passwd.h"
44 
45 /* Passwd states */
46 typedef enum {
47         PASSWD_STATE_NONE,              /* Passwd is not asking for anything */
48         PASSWD_STATE_AUTH,              /* Passwd is asking for our current password */
49         PASSWD_STATE_NEW,               /* Passwd is asking for our new password */
50         PASSWD_STATE_RETYPE,            /* Passwd is asking for our retyped new password */
51         PASSWD_STATE_DONE,              /* Passwd succeeded but has not yet exited */
52         PASSWD_STATE_ERR                /* Passwd reported an error but has not yet exited */
53 } PasswdState;
54 
55 struct PasswdHandler {
56         const char *current_password;
57         const char *new_password;
58 
59         /* Communication with the passwd program */
60         GPid backend_pid;
61 
62         GIOChannel *backend_stdin;
63         GIOChannel *backend_stdout;
64 
65         GQueue *backend_stdin_queue;            /* Write queue to backend_stdin */
66 
67         /* GMainLoop IDs */
68         guint backend_child_watch_id;           /* g_child_watch_add (PID) */
69         guint backend_stdout_watch_id;          /* g_io_add_watch (stdout) */
70 
71         /* State of the passwd program */
72         PasswdState backend_state;
73         gboolean    changing_password;
74 
75         PasswdCallback auth_cb;
76         gpointer       auth_cb_data;
77 
78         PasswdCallback chpasswd_cb;
79         gpointer       chpasswd_cb_data;
80 };
81 
82 /* Buffer size for backend output */
83 #define BUFSIZE 64
84 
85 
86 static GQuark
passwd_error_quark(void)87 passwd_error_quark (void)
88 {
89         static GQuark q = 0;
90 
91         if (q == 0) {
92                 q = g_quark_from_static_string("passwd_error");
93         }
94 
95         return q;
96 }
97 
98 /* Error handling */
99 #define PASSWD_ERROR (passwd_error_quark ())
100 
101 
102 static void
103 stop_passwd (PasswdHandler *passwd_handler);
104 
105 static void
106 free_passwd_resources (PasswdHandler *passwd_handler);
107 
108 static gboolean
109 io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
110 
111 
112 /*
113  * Spawning and closing of backend {{
114  */
115 
116 /* Child watcher */
117 static void
child_watch_cb(GPid pid,gint status,PasswdHandler * passwd_handler)118 child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
119 {
120         if (WIFEXITED (status)) {
121                 if (WEXITSTATUS (status) >= 255) {
122                         g_warning ("Child exited unexpectedly");
123                 }
124                 if (WEXITSTATUS (status) == 0) {
125                         if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
126                                 passwd_handler->backend_state = PASSWD_STATE_DONE;
127                                 if (passwd_handler->chpasswd_cb)
128                                                 passwd_handler->chpasswd_cb (passwd_handler,
129                                                                              NULL,
130                                                                              passwd_handler->chpasswd_cb_data);
131                         }
132                 }
133         }
134 
135         free_passwd_resources (passwd_handler);
136 }
137 
138 static void
ignore_sigpipe(gpointer data)139 ignore_sigpipe (gpointer data)
140 {
141         signal (SIGPIPE, SIG_IGN);
142 }
143 
144 /* Spawn passwd backend
145  * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
146 static gboolean
spawn_passwd(PasswdHandler * passwd_handler,GError ** error)147 spawn_passwd (PasswdHandler *passwd_handler, GError **error)
148 {
149         gchar   *argv[2];
150         gchar  **envp;
151         gint    my_stdin, my_stdout, my_stderr;
152 
153         argv[0] = "/usr/bin/passwd";    /* Is it safe to rely on a hard-coded path? */
154         argv[1] = NULL;
155 
156         envp = g_get_environ ();
157         envp = g_environ_setenv (envp, "LC_ALL", "C", TRUE);
158 
159         if (!g_spawn_async_with_pipes (NULL,                            /* Working directory */
160                                        argv,                            /* Argument vector */
161                                        envp,                            /* Environment */
162                                        G_SPAWN_DO_NOT_REAP_CHILD,       /* Flags */
163                                        ignore_sigpipe,                  /* Child setup */
164                                        NULL,                            /* Data to child setup */
165                                        &passwd_handler->backend_pid,    /* PID */
166                                        &my_stdin,                       /* Stdin */
167                                        &my_stdout,                      /* Stdout */
168                                        &my_stderr,                      /* Stderr */
169                                        error)) {                        /* GError */
170 
171                 /* An error occurred */
172                 free_passwd_resources (passwd_handler);
173 
174                 g_strfreev (envp);
175 
176                 return FALSE;
177         }
178 
179         g_strfreev (envp);
180 
181         /* 2>&1 */
182         if (dup2 (my_stderr, my_stdout) == -1) {
183                 /* Failed! */
184                 g_set_error_literal (error,
185                                      PASSWD_ERROR,
186                                      PASSWD_ERROR_BACKEND,
187                                      strerror (errno));
188 
189                 /* Clean up */
190                 stop_passwd (passwd_handler);
191 
192                 return FALSE;
193         }
194 
195         /* Open IO Channels */
196         passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
197         passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
198 
199         /* Set raw encoding */
200         /* Set nonblocking mode */
201         if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
202                 g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
203                 g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
204                 g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
205 
206                 /* Clean up */
207                 stop_passwd (passwd_handler);
208                 return FALSE;
209         }
210 
211         /* Turn off buffering */
212         g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
213         g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
214 
215         /* Add IO Channel watcher */
216         passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
217                                                                   G_IO_IN | G_IO_PRI,
218                                                                   (GIOFunc) io_watch_stdout, passwd_handler);
219 
220         /* Add child watcher */
221         passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
222 
223         /* Success! */
224 
225         return TRUE;
226 }
227 
228 /* Stop passwd backend */
229 static void
stop_passwd(PasswdHandler * passwd_handler)230 stop_passwd (PasswdHandler *passwd_handler)
231 {
232         /* This is the standard way of returning from the dialog with passwd.
233          * If we return this way we can safely kill passwd as it has completed
234          * its task.
235          */
236 
237         if (passwd_handler->backend_pid != -1) {
238                 kill (passwd_handler->backend_pid, 9);
239         }
240 
241         /* We must run free_passwd_resources here and not let our child
242          * watcher do it, since it will access invalid memory after the
243          * dialog has been closed and cleaned up.
244          *
245          * If we had more than a single thread we'd need to remove
246          * the child watch before trying to kill the child.
247          */
248         free_passwd_resources (passwd_handler);
249 }
250 
251 /* Clean up passwd resources */
252 static void
free_passwd_resources(PasswdHandler * passwd_handler)253 free_passwd_resources (PasswdHandler *passwd_handler)
254 {
255         /* Remove the child watcher */
256         if (passwd_handler->backend_child_watch_id != 0) {
257 
258                 g_source_remove (passwd_handler->backend_child_watch_id);
259 
260                 passwd_handler->backend_child_watch_id = 0;
261         }
262 
263 
264         /* Close IO channels (internal file descriptors are automatically closed) */
265         if (passwd_handler->backend_stdin != NULL) {
266                 g_autoptr(GError) error = NULL;
267 
268                 if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
269                         g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
270                 }
271 
272                 g_clear_pointer (&passwd_handler->backend_stdin, g_io_channel_unref);
273         }
274 
275         if (passwd_handler->backend_stdout != NULL) {
276                 g_autoptr(GError) error = NULL;
277 
278                 if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
279                         g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
280                 }
281 
282                 g_clear_pointer (&passwd_handler->backend_stdout, g_io_channel_unref);
283         }
284 
285         /* Remove IO watcher */
286         if (passwd_handler->backend_stdout_watch_id != 0) {
287 
288                 g_source_remove (passwd_handler->backend_stdout_watch_id);
289 
290                 passwd_handler->backend_stdout_watch_id = 0;
291         }
292 
293         /* Close PID */
294         if (passwd_handler->backend_pid != -1) {
295 
296                 g_spawn_close_pid (passwd_handler->backend_pid);
297 
298                 passwd_handler->backend_pid = -1;
299         }
300 
301         /* Clear backend state */
302         passwd_handler->backend_state = PASSWD_STATE_NONE;
303 }
304 
305 /*
306  * }} Spawning and closing of backend
307  */
308 
309 /*
310  * Backend communication code {{
311  */
312 
313 /* Write the first element of queue through channel */
314 static void
io_queue_pop(GQueue * queue,GIOChannel * channel)315 io_queue_pop (GQueue *queue, GIOChannel *channel)
316 {
317         g_autofree gchar *buf = NULL;
318         gsize   bytes_written;
319         g_autoptr(GError) error = NULL;
320 
321         buf = g_queue_pop_head (queue);
322 
323         if (buf != NULL) {
324 
325                 if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
326                         g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
327                 }
328 
329                 /* Ensure passwords are cleared from memory */
330                 memset (buf, 0, strlen (buf));
331         }
332 }
333 
334 /* Goes through the argument list, checking if one of them occurs in str
335  * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
336 static gboolean
is_string_complete(gchar * str,...)337 is_string_complete (gchar *str, ...)
338 {
339         va_list ap;
340         gchar   *arg;
341 
342         if (strlen (str) == 0) {
343                 return FALSE;
344         }
345 
346         va_start (ap, str);
347 
348         while ((arg = va_arg (ap, char *)) != NULL) {
349                 if (strstr (str, arg) != NULL) {
350                         va_end (ap);
351                         return TRUE;
352                 }
353         }
354 
355         va_end (ap);
356 
357         return FALSE;
358 }
359 
360 /*
361  * IO watcher for stdout, called whenever there is data to read from the backend.
362  * This is where most of the actual IO handling happens.
363  */
364 static gboolean
io_watch_stdout(GIOChannel * source,GIOCondition condition,PasswdHandler * passwd_handler)365 io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
366 {
367         static GString *str = NULL;     /* Persistent buffer */
368 
369         gchar           buf[BUFSIZE];           /* Temporary buffer */
370         gsize           bytes_read;
371         g_autoptr(GError) gio_error = NULL;
372 
373         gboolean        reinit = FALSE;
374 
375         /* Initialize buffer */
376         if (str == NULL) {
377                 str = g_string_new ("");
378         }
379 
380         if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
381             != G_IO_STATUS_NORMAL) {
382                 g_warning ("IO Channel read error: %s", gio_error->message);
383                 return TRUE;
384         }
385 
386         str = g_string_append_len (str, buf, bytes_read);
387 
388         /* In which state is the backend? */
389         switch (passwd_handler->backend_state) {
390                 case PASSWD_STATE_AUTH:
391                         /* Passwd is asking for our current password */
392 
393                         if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
394 
395                                 if (strstr (str->str, "assword: ") != NULL &&
396                                     strstr (str->str, "incorrect") == NULL &&
397                                     strstr (str->str, "urrent") == NULL) {
398                                         /* Authentication successful */
399 
400                                         passwd_handler->backend_state = PASSWD_STATE_NEW;
401 
402                                         /* Trigger callback to update authentication status */
403                                         if (passwd_handler->auth_cb)
404                                                 passwd_handler->auth_cb (passwd_handler,
405                                                                          NULL,
406                                                                          passwd_handler->auth_cb_data);
407 
408                                 } else {
409                                         /* Authentication failed */
410                                         g_autoptr(GError) error = NULL;
411 
412                                         error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
413                                                                      _("Authentication failed"));
414 
415                                         passwd_handler->changing_password = FALSE;
416 
417                                         /* This error can happen both while authenticating or while changing password:
418                                          * if chpasswd_cb is set, this means we're already changing password */
419                                         if (passwd_handler->chpasswd_cb)
420                                                 passwd_handler->chpasswd_cb (passwd_handler,
421                                                                              error,
422                                                                              passwd_handler->chpasswd_cb_data);
423                                         else if (passwd_handler->auth_cb)
424                                                 passwd_handler->auth_cb (passwd_handler,
425                                                                          error,
426                                                                          passwd_handler->auth_cb_data);
427                                 }
428 
429                                 reinit = TRUE;
430                         }
431                         break;
432                 case PASSWD_STATE_NEW:
433                         /* Passwd is asking for our new password */
434 
435                         if (is_string_complete (str->str, "assword: ", NULL)) {
436                                 /* Advance to next state */
437                                 passwd_handler->backend_state = PASSWD_STATE_RETYPE;
438 
439                                 /* Pop retyped password from queue and into IO channel */
440                                 io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
441 
442                                 reinit = TRUE;
443                         }
444                         break;
445                 case PASSWD_STATE_RETYPE:
446                         /* Passwd is asking for our retyped new password */
447 
448                         if (is_string_complete (str->str,
449                                                 "successfully",
450                                                 "short",
451                                                 "longer",
452                                                 "palindrome",
453                                                 "dictionary",
454                                                 "simple",
455                                                 "simplistic",
456                                                 "similar",
457                                                 "case",
458                                                 "different",
459                                                 "wrapped",
460                                                 "recovered",
461                                                 "recent",
462                                                 "unchanged",
463                                                 "match",
464                                                 "1 numeric or special",
465                                                 "failure",
466                                                 "DIFFERENT",
467                                                 "BAD PASSWORD",
468                                                 NULL)) {
469 
470                                 if (strstr (str->str, "successfully") != NULL) {
471                                         /* Hooray! */
472 
473                                         passwd_handler->backend_state = PASSWD_STATE_DONE;
474                                         /* Trigger callback to update status */
475                                         if (passwd_handler->chpasswd_cb)
476                                                 passwd_handler->chpasswd_cb (passwd_handler,
477                                                                              NULL,
478                                                                              passwd_handler->chpasswd_cb_data);
479                                 }
480                                 else {
481                                         /* Ohnoes! */
482                                         g_autoptr(GError) error = NULL;
483 
484                                         if (strstr (str->str, "recovered") != NULL) {
485                                                 /* What does this indicate?
486                                                  * "Authentication information cannot be recovered?" from libpam? */
487                                                 error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
488                                                                              str->str);
489                                         } else if (strstr (str->str, "short") != NULL ||
490                                                    strstr (str->str, "longer") != NULL) {
491                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
492                                                                      _("The new password is too short"));
493                                         } else if (strstr (str->str, "palindrome") != NULL ||
494                                                    strstr (str->str, "simple") != NULL ||
495                                                    strstr (str->str, "simplistic") != NULL ||
496                                                    strstr (str->str, "dictionary") != NULL) {
497                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
498                                                                      _("The new password is too simple"));
499                                         } else if (strstr (str->str, "similar") != NULL ||
500                                                    strstr (str->str, "different") != NULL ||
501                                                    strstr (str->str, "case") != NULL ||
502                                                    strstr (str->str, "wrapped") != NULL) {
503                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
504                                                                      _("The old and new passwords are too similar"));
505                                         } else if (strstr (str->str, "recent") != NULL) {
506                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
507                                                                      _("The new password has already been used recently."));
508                                         } else if (strstr (str->str, "1 numeric or special") != NULL) {
509                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
510                                                                      _("The new password must contain numeric or special characters"));
511                                         } else if (strstr (str->str, "unchanged") != NULL ||
512                                                    strstr (str->str, "match") != NULL) {
513                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
514                                                                      _("The old and new passwords are the same"));
515                                         } else if (strstr (str->str, "failure") != NULL) {
516                                                 /* Authentication failure */
517                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
518                                                                      _("Your password has been changed since you initially authenticated!"));
519                                         }
520                                         else if (strstr (str->str, "DIFFERENT")) {
521                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
522                                                                      _("The new password does not contain enough different characters"));
523                                         }
524                                         else {
525                                                 error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
526                                                                      _("Unknown error"));
527                                         }
528 
529                                         /* At this point, passwd might have exited, in which case
530                                          * child_watch_cb should clean up for us and remove this watcher.
531                                          * On some error conditions though, passwd just re-prompts us
532                                          * for our new password. */
533                                         passwd_handler->backend_state = PASSWD_STATE_ERR;
534 
535                                         passwd_handler->changing_password = FALSE;
536 
537                                         /* Trigger callback to update status */
538                                         if (passwd_handler->chpasswd_cb)
539                                                 passwd_handler->chpasswd_cb (passwd_handler,
540                                                                              error,
541                                                                              passwd_handler->chpasswd_cb_data);
542                                 }
543 
544                                 reinit = TRUE;
545 
546                                 /* child_watch_cb should clean up for us now */
547                         }
548                         break;
549                 case PASSWD_STATE_NONE:
550                         /* Passwd is not asking for anything yet */
551                         if (is_string_complete (str->str, "assword: ", NULL)) {
552 
553                                 /* If the user does not have a password set,
554                                  * passwd will immediately ask for the new password,
555                                  * so skip the AUTH phase */
556                                 if (is_string_complete (str->str, "new", "New", NULL)) {
557                                         g_autofree gchar *pw = NULL;
558 
559                                         passwd_handler->backend_state = PASSWD_STATE_NEW;
560 
561                                         /* since passwd didn't ask for our old password
562                                          * in this case, simply remove it from the queue */
563                                         pw = g_queue_pop_head (passwd_handler->backend_stdin_queue);
564 
565                                         /* Pop the IO queue, i.e. send new password */
566                                         io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
567 
568                                 } else {
569 
570                                         passwd_handler->backend_state = PASSWD_STATE_AUTH;
571 
572                                         /* Pop the IO queue, i.e. send current password */
573                                         io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
574                                 }
575 
576                                 reinit = TRUE;
577                         }
578                         break;
579                 default:
580                         /* Passwd has returned an error */
581                         reinit = TRUE;
582                         break;
583         }
584 
585         if (reinit) {
586                 g_string_free (str, TRUE);
587                 str = NULL;
588         }
589 
590         /* Continue calling us */
591         return TRUE;
592 }
593 
594 /*
595  * }} Backend communication code
596  */
597 
598 /* Adds the current password to the IO queue */
599 static void
authenticate(PasswdHandler * passwd_handler)600 authenticate (PasswdHandler *passwd_handler)
601 {
602         gchar   *s;
603 
604         s = g_strdup_printf ("%s\n", passwd_handler->current_password);
605 
606         g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
607 }
608 
609 /* Adds the new password twice to the IO queue */
610 static void
update_password(PasswdHandler * passwd_handler)611 update_password (PasswdHandler *passwd_handler)
612 {
613         gchar   *s;
614 
615         s = g_strdup_printf ("%s\n", passwd_handler->new_password);
616 
617         g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
618         /* We need to allocate new space because io_queue_pop() g_free()s
619          * every element of the queue after it's done */
620         g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
621 }
622 
623 
624 PasswdHandler *
passwd_init(void)625 passwd_init (void)
626 {
627         PasswdHandler *passwd_handler;
628 
629         passwd_handler = g_new0 (PasswdHandler, 1);
630 
631         /* Initialize backend_pid. -1 means the backend is not running */
632         passwd_handler->backend_pid = -1;
633 
634         /* Initialize IO Channels */
635         passwd_handler->backend_stdin = NULL;
636         passwd_handler->backend_stdout = NULL;
637 
638         /* Initialize write queue */
639         passwd_handler->backend_stdin_queue = g_queue_new ();
640 
641         /* Initialize watchers */
642         passwd_handler->backend_child_watch_id = 0;
643         passwd_handler->backend_stdout_watch_id = 0;
644 
645         /* Initialize backend state */
646         passwd_handler->backend_state = PASSWD_STATE_NONE;
647         passwd_handler->changing_password = FALSE;
648 
649         return passwd_handler;
650 }
651 
652 void
passwd_destroy(PasswdHandler * passwd_handler)653 passwd_destroy (PasswdHandler *passwd_handler)
654 {
655         g_queue_free (passwd_handler->backend_stdin_queue);
656         stop_passwd (passwd_handler);
657         g_free (passwd_handler);
658 }
659 
660 void
passwd_authenticate(PasswdHandler * passwd_handler,const char * current_password,PasswdCallback cb,const gpointer user_data)661 passwd_authenticate (PasswdHandler *passwd_handler,
662                      const char    *current_password,
663                      PasswdCallback cb,
664                      const gpointer user_data)
665 {
666         g_autoptr(GError) error = NULL;
667 
668         /* Don't stop if we've already started changing password */
669         if (passwd_handler->changing_password)
670                 return;
671 
672         /* Clear data from possible previous attempts to change password */
673         passwd_handler->new_password = NULL;
674         passwd_handler->chpasswd_cb = NULL;
675         passwd_handler->chpasswd_cb_data = NULL;
676         g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
677         g_queue_clear (passwd_handler->backend_stdin_queue);
678 
679         passwd_handler->current_password = current_password;
680         passwd_handler->auth_cb = cb;
681         passwd_handler->auth_cb_data = user_data;
682 
683         /* Spawn backend */
684         stop_passwd (passwd_handler);
685 
686         if (!spawn_passwd (passwd_handler, &error)) {
687                 g_warning ("%s", error->message);
688                 return;
689         }
690 
691         authenticate (passwd_handler);
692 
693         /* Our IO watcher should now handle the rest */
694 }
695 
696 gboolean
passwd_change_password(PasswdHandler * passwd_handler,const char * new_password,PasswdCallback cb,const gpointer user_data)697 passwd_change_password (PasswdHandler *passwd_handler,
698                         const char    *new_password,
699                         PasswdCallback cb,
700                         const gpointer user_data)
701 {
702         passwd_handler->changing_password = TRUE;
703 
704         passwd_handler->new_password = new_password;
705         passwd_handler->chpasswd_cb = cb;
706         passwd_handler->chpasswd_cb_data = user_data;
707 
708         /* Stop passwd if an error occurred and it is still running */
709         if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
710 
711                 /* Stop passwd, free resources */
712                 stop_passwd (passwd_handler);
713         }
714 
715         /* Check that the backend is still running, or that an error
716          * has occurred but it has not yet exited */
717         if (passwd_handler->backend_pid == -1) {
718                 /* If it is not, re-run authentication */
719                 g_autoptr(GError) error = NULL;
720 
721                 /* Spawn backend */
722                 stop_passwd (passwd_handler);
723 
724                 if (!spawn_passwd (passwd_handler, &error)) {
725                         g_warning ("%s", error->message);
726                         return FALSE;
727                 }
728 
729                 /* Add current and new passwords to queue */
730                 authenticate (passwd_handler);
731                 update_password (passwd_handler);
732         } else {
733                 /* Only add new passwords to queue */
734                 update_password (passwd_handler);
735         }
736 
737         /* Pop new password through the backend.
738          * If user has no password, popping the queue would output current
739          * password, while 'passwd' is waiting for the new one. So wait for
740          * io_watch_stdout() to remove current password from the queue,
741          * and output the new one for us.
742          */
743         if (passwd_handler->current_password)
744                 io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
745 
746         /* Our IO watcher should now handle the rest */
747 
748         return TRUE;
749 }
750