Usually, passwd exits with an error if the current password is incorrect, however, when using LDAP, it askes for password again. It causes hangs as the passwd helper assumes that the password is correct. Let's prevent this hangs using additional check for "incorrect" keyword. Based on a patch from https://launchpad.net/bugs/607357 made by Ryan Tandy. https://bugzilla.gnome.org/show_bug.cgi?id=786530
768 lines
31 KiB
C
768 lines
31 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
|
|
/* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend
|
|
* for user administration.
|
|
*
|
|
* Copyright (C) 2002 Diego Gonzalez
|
|
* Copyright (C) 2006 Johannes H. Jensen
|
|
* Copyright (C) 2010 Milan Bouchet-Valat
|
|
*
|
|
* Written by: Diego Gonzalez <diego@pemas.net>
|
|
* Modified by: Johannes H. Jensen <joh@deworks.net>,
|
|
* Milan Bouchet-Valat <nalimilan@club.fr>.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2, or (at your option)
|
|
* any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Most of this code originally comes from gnome-about-me-password.c,
|
|
* from gnome-control-center.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <glib/gi18n.h>
|
|
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/wait.h>
|
|
|
|
#if __sun
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
#endif
|
|
|
|
#include "run-passwd.h"
|
|
|
|
/* Passwd states */
|
|
typedef enum {
|
|
PASSWD_STATE_NONE, /* Passwd is not asking for anything */
|
|
PASSWD_STATE_AUTH, /* Passwd is asking for our current password */
|
|
PASSWD_STATE_NEW, /* Passwd is asking for our new password */
|
|
PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */
|
|
PASSWD_STATE_DONE, /* Passwd succeeded but has not yet exited */
|
|
PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
|
|
} PasswdState;
|
|
|
|
struct PasswdHandler {
|
|
const char *current_password;
|
|
const char *new_password;
|
|
|
|
/* Communication with the passwd program */
|
|
GPid backend_pid;
|
|
|
|
GIOChannel *backend_stdin;
|
|
GIOChannel *backend_stdout;
|
|
|
|
GQueue *backend_stdin_queue; /* Write queue to backend_stdin */
|
|
|
|
/* GMainLoop IDs */
|
|
guint backend_child_watch_id; /* g_child_watch_add (PID) */
|
|
guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */
|
|
|
|
/* State of the passwd program */
|
|
PasswdState backend_state;
|
|
gboolean changing_password;
|
|
|
|
PasswdCallback auth_cb;
|
|
gpointer auth_cb_data;
|
|
|
|
PasswdCallback chpasswd_cb;
|
|
gpointer chpasswd_cb_data;
|
|
};
|
|
|
|
/* Buffer size for backend output */
|
|
#define BUFSIZE 64
|
|
|
|
|
|
static GQuark
|
|
passwd_error_quark (void)
|
|
{
|
|
static GQuark q = 0;
|
|
|
|
if (q == 0) {
|
|
q = g_quark_from_static_string("passwd_error");
|
|
}
|
|
|
|
return q;
|
|
}
|
|
|
|
/* Error handling */
|
|
#define PASSWD_ERROR (passwd_error_quark ())
|
|
|
|
|
|
static void
|
|
stop_passwd (PasswdHandler *passwd_handler);
|
|
|
|
static void
|
|
free_passwd_resources (PasswdHandler *passwd_handler);
|
|
|
|
static gboolean
|
|
io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
|
|
|
|
|
|
/*
|
|
* Spawning and closing of backend {{
|
|
*/
|
|
|
|
/* Child watcher */
|
|
static void
|
|
child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
|
|
{
|
|
if (WIFEXITED (status)) {
|
|
if (WEXITSTATUS (status) >= 255) {
|
|
g_warning ("Child exited unexpectedly");
|
|
}
|
|
if (WEXITSTATUS (status) == 0) {
|
|
if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
|
|
passwd_handler->backend_state = PASSWD_STATE_DONE;
|
|
if (passwd_handler->chpasswd_cb)
|
|
passwd_handler->chpasswd_cb (passwd_handler,
|
|
NULL,
|
|
passwd_handler->chpasswd_cb_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
free_passwd_resources (passwd_handler);
|
|
}
|
|
|
|
static void
|
|
ignore_sigpipe (gpointer data)
|
|
{
|
|
signal (SIGPIPE, SIG_IGN);
|
|
}
|
|
|
|
/* Spawn passwd backend
|
|
* Returns: TRUE on success, FALSE otherwise and sets error appropriately */
|
|
static gboolean
|
|
spawn_passwd (PasswdHandler *passwd_handler, GError **error)
|
|
{
|
|
gchar *argv[2];
|
|
gchar **envp;
|
|
gint my_stdin, my_stdout, my_stderr;
|
|
|
|
argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
|
|
argv[1] = NULL;
|
|
|
|
envp = g_get_environ ();
|
|
envp = g_environ_setenv (envp, "LC_ALL", "C", TRUE);
|
|
|
|
if (!g_spawn_async_with_pipes (NULL, /* Working directory */
|
|
argv, /* Argument vector */
|
|
envp, /* Environment */
|
|
G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
|
|
ignore_sigpipe, /* Child setup */
|
|
NULL, /* Data to child setup */
|
|
&passwd_handler->backend_pid, /* PID */
|
|
&my_stdin, /* Stdin */
|
|
&my_stdout, /* Stdout */
|
|
&my_stderr, /* Stderr */
|
|
error)) { /* GError */
|
|
|
|
/* An error occured */
|
|
free_passwd_resources (passwd_handler);
|
|
|
|
g_strfreev (envp);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_strfreev (envp);
|
|
|
|
/* 2>&1 */
|
|
if (dup2 (my_stderr, my_stdout) == -1) {
|
|
/* Failed! */
|
|
g_set_error_literal (error,
|
|
PASSWD_ERROR,
|
|
PASSWD_ERROR_BACKEND,
|
|
strerror (errno));
|
|
|
|
/* Clean up */
|
|
stop_passwd (passwd_handler);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Open IO Channels */
|
|
passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
|
|
passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
|
|
|
|
/* Set raw encoding */
|
|
/* Set nonblocking mode */
|
|
if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
|
|
g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
|
|
g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
|
|
g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
|
|
|
|
/* Clean up */
|
|
stop_passwd (passwd_handler);
|
|
return FALSE;
|
|
}
|
|
|
|
/* Turn off buffering */
|
|
g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
|
|
g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
|
|
|
|
/* Add IO Channel watcher */
|
|
passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
|
|
G_IO_IN | G_IO_PRI,
|
|
(GIOFunc) io_watch_stdout, passwd_handler);
|
|
|
|
/* Add child watcher */
|
|
passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
|
|
|
|
/* Success! */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* Stop passwd backend */
|
|
static void
|
|
stop_passwd (PasswdHandler *passwd_handler)
|
|
{
|
|
/* This is the standard way of returning from the dialog with passwd.
|
|
* If we return this way we can safely kill passwd as it has completed
|
|
* its task.
|
|
*/
|
|
|
|
if (passwd_handler->backend_pid != -1) {
|
|
kill (passwd_handler->backend_pid, 9);
|
|
}
|
|
|
|
/* We must run free_passwd_resources here and not let our child
|
|
* watcher do it, since it will access invalid memory after the
|
|
* dialog has been closed and cleaned up.
|
|
*
|
|
* If we had more than a single thread we'd need to remove
|
|
* the child watch before trying to kill the child.
|
|
*/
|
|
free_passwd_resources (passwd_handler);
|
|
}
|
|
|
|
/* Clean up passwd resources */
|
|
static void
|
|
free_passwd_resources (PasswdHandler *passwd_handler)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
/* Remove the child watcher */
|
|
if (passwd_handler->backend_child_watch_id != 0) {
|
|
|
|
g_source_remove (passwd_handler->backend_child_watch_id);
|
|
|
|
passwd_handler->backend_child_watch_id = 0;
|
|
}
|
|
|
|
|
|
/* Close IO channels (internal file descriptors are automatically closed) */
|
|
if (passwd_handler->backend_stdin != NULL) {
|
|
|
|
if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
|
|
g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
|
|
g_error_free (error);
|
|
error = NULL;
|
|
}
|
|
|
|
g_io_channel_unref (passwd_handler->backend_stdin);
|
|
passwd_handler->backend_stdin = NULL;
|
|
}
|
|
|
|
if (passwd_handler->backend_stdout != NULL) {
|
|
|
|
if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
|
|
g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
|
|
g_error_free (error);
|
|
error = NULL;
|
|
}
|
|
|
|
g_io_channel_unref (passwd_handler->backend_stdout);
|
|
|
|
passwd_handler->backend_stdout = NULL;
|
|
}
|
|
|
|
/* Remove IO watcher */
|
|
if (passwd_handler->backend_stdout_watch_id != 0) {
|
|
|
|
g_source_remove (passwd_handler->backend_stdout_watch_id);
|
|
|
|
passwd_handler->backend_stdout_watch_id = 0;
|
|
}
|
|
|
|
/* Close PID */
|
|
if (passwd_handler->backend_pid != -1) {
|
|
|
|
g_spawn_close_pid (passwd_handler->backend_pid);
|
|
|
|
passwd_handler->backend_pid = -1;
|
|
}
|
|
|
|
/* Clear backend state */
|
|
passwd_handler->backend_state = PASSWD_STATE_NONE;
|
|
}
|
|
|
|
/*
|
|
* }} Spawning and closing of backend
|
|
*/
|
|
|
|
/*
|
|
* Backend communication code {{
|
|
*/
|
|
|
|
/* Write the first element of queue through channel */
|
|
static void
|
|
io_queue_pop (GQueue *queue, GIOChannel *channel)
|
|
{
|
|
gchar *buf;
|
|
gsize bytes_written;
|
|
GError *error = NULL;
|
|
|
|
buf = g_queue_pop_head (queue);
|
|
|
|
if (buf != NULL) {
|
|
|
|
if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
|
|
g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
|
|
g_error_free (error);
|
|
}
|
|
|
|
/* Ensure passwords are cleared from memory */
|
|
memset (buf, 0, strlen (buf));
|
|
g_free (buf);
|
|
}
|
|
}
|
|
|
|
/* Goes through the argument list, checking if one of them occurs in str
|
|
* Returns: TRUE as soon as an element is found to match, FALSE otherwise */
|
|
static gboolean
|
|
is_string_complete (gchar *str, ...)
|
|
{
|
|
va_list ap;
|
|
gchar *arg;
|
|
|
|
if (strlen (str) == 0) {
|
|
return FALSE;
|
|
}
|
|
|
|
va_start (ap, str);
|
|
|
|
while ((arg = va_arg (ap, char *)) != NULL) {
|
|
if (strstr (str, arg) != NULL) {
|
|
va_end (ap);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
va_end (ap);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* IO watcher for stdout, called whenever there is data to read from the backend.
|
|
* This is where most of the actual IO handling happens.
|
|
*/
|
|
static gboolean
|
|
io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
|
|
{
|
|
static GString *str = NULL; /* Persistent buffer */
|
|
|
|
gchar buf[BUFSIZE]; /* Temporary buffer */
|
|
gsize bytes_read;
|
|
GError *gio_error = NULL; /* Error returned by functions */
|
|
GError *error = NULL; /* Error sent to callbacks */
|
|
|
|
gboolean reinit = FALSE;
|
|
|
|
/* Initialize buffer */
|
|
if (str == NULL) {
|
|
str = g_string_new ("");
|
|
}
|
|
|
|
if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
|
|
!= G_IO_STATUS_NORMAL) {
|
|
g_warning ("IO Channel read error: %s", gio_error->message);
|
|
g_error_free (gio_error);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
str = g_string_append_len (str, buf, bytes_read);
|
|
|
|
/* In which state is the backend? */
|
|
switch (passwd_handler->backend_state) {
|
|
case PASSWD_STATE_AUTH:
|
|
/* Passwd is asking for our current password */
|
|
|
|
if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
|
|
|
|
if (strstr (str->str, "assword: ") != NULL && strstr (str->str, "incorrect") == NULL) {
|
|
/* Authentication successful */
|
|
|
|
passwd_handler->backend_state = PASSWD_STATE_NEW;
|
|
|
|
/* Trigger callback to update authentication status */
|
|
if (passwd_handler->auth_cb)
|
|
passwd_handler->auth_cb (passwd_handler,
|
|
NULL,
|
|
passwd_handler->auth_cb_data);
|
|
|
|
} else {
|
|
/* Authentication failed */
|
|
|
|
error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
|
|
_("Authentication failed"));
|
|
|
|
passwd_handler->changing_password = FALSE;
|
|
|
|
/* This error can happen both while authenticating or while changing password:
|
|
* if chpasswd_cb is set, this means we're already changing password */
|
|
if (passwd_handler->chpasswd_cb)
|
|
passwd_handler->chpasswd_cb (passwd_handler,
|
|
error,
|
|
passwd_handler->chpasswd_cb_data);
|
|
else if (passwd_handler->auth_cb)
|
|
passwd_handler->auth_cb (passwd_handler,
|
|
error,
|
|
passwd_handler->auth_cb_data);
|
|
|
|
g_error_free (error);
|
|
}
|
|
|
|
reinit = TRUE;
|
|
}
|
|
break;
|
|
case PASSWD_STATE_NEW:
|
|
/* Passwd is asking for our new password */
|
|
|
|
if (is_string_complete (str->str, "assword: ", NULL)) {
|
|
/* Advance to next state */
|
|
passwd_handler->backend_state = PASSWD_STATE_RETYPE;
|
|
|
|
/* Pop retyped password from queue and into IO channel */
|
|
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
|
|
|
|
reinit = TRUE;
|
|
}
|
|
break;
|
|
case PASSWD_STATE_RETYPE:
|
|
/* Passwd is asking for our retyped new password */
|
|
|
|
if (is_string_complete (str->str,
|
|
"successfully",
|
|
"short",
|
|
"longer",
|
|
"palindrome",
|
|
"dictionary",
|
|
"simple",
|
|
"simplistic",
|
|
"similar",
|
|
"case",
|
|
"different",
|
|
"wrapped",
|
|
"recovered",
|
|
"recent",
|
|
"unchanged",
|
|
"match",
|
|
"1 numeric or special",
|
|
"failure",
|
|
"DIFFERENT",
|
|
"BAD PASSWORD",
|
|
NULL)) {
|
|
|
|
if (strstr (str->str, "successfully") != NULL) {
|
|
/* Hooray! */
|
|
|
|
passwd_handler->backend_state = PASSWD_STATE_DONE;
|
|
/* Trigger callback to update status */
|
|
if (passwd_handler->chpasswd_cb)
|
|
passwd_handler->chpasswd_cb (passwd_handler,
|
|
NULL,
|
|
passwd_handler->chpasswd_cb_data);
|
|
}
|
|
else {
|
|
/* Ohnoes! */
|
|
|
|
if (strstr (str->str, "recovered") != NULL) {
|
|
/* What does this indicate?
|
|
* "Authentication information cannot be recovered?" from libpam? */
|
|
error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
|
|
str->str);
|
|
} else if (strstr (str->str, "short") != NULL ||
|
|
strstr (str->str, "longer") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The new password is too short"));
|
|
} else if (strstr (str->str, "palindrome") != NULL ||
|
|
strstr (str->str, "simple") != NULL ||
|
|
strstr (str->str, "simplistic") != NULL ||
|
|
strstr (str->str, "dictionary") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The new password is too simple"));
|
|
} else if (strstr (str->str, "similar") != NULL ||
|
|
strstr (str->str, "different") != NULL ||
|
|
strstr (str->str, "case") != NULL ||
|
|
strstr (str->str, "wrapped") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The old and new passwords are too similar"));
|
|
} else if (strstr (str->str, "recent") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The new password has already been used recently."));
|
|
} else if (strstr (str->str, "1 numeric or special") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The new password must contain numeric or special characters"));
|
|
} else if (strstr (str->str, "unchanged") != NULL ||
|
|
strstr (str->str, "match") != NULL) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The old and new passwords are the same"));
|
|
} else if (strstr (str->str, "failure") != NULL) {
|
|
/* Authentication failure */
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
|
|
_("Your password has been changed since you initially authenticated!"));
|
|
}
|
|
else if (strstr (str->str, "DIFFERENT")) {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
|
|
_("The new password does not contain enough different characters"));
|
|
}
|
|
else {
|
|
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
|
|
_("Unknown error"));
|
|
}
|
|
|
|
/* At this point, passwd might have exited, in which case
|
|
* child_watch_cb should clean up for us and remove this watcher.
|
|
* On some error conditions though, passwd just re-prompts us
|
|
* for our new password. */
|
|
passwd_handler->backend_state = PASSWD_STATE_ERR;
|
|
|
|
passwd_handler->changing_password = FALSE;
|
|
|
|
/* Trigger callback to update status */
|
|
if (passwd_handler->chpasswd_cb)
|
|
passwd_handler->chpasswd_cb (passwd_handler,
|
|
error,
|
|
passwd_handler->chpasswd_cb_data);
|
|
|
|
g_error_free (error);
|
|
|
|
}
|
|
|
|
reinit = TRUE;
|
|
|
|
/* child_watch_cb should clean up for us now */
|
|
}
|
|
break;
|
|
case PASSWD_STATE_NONE:
|
|
/* Passwd is not asking for anything yet */
|
|
if (is_string_complete (str->str, "assword: ", NULL)) {
|
|
|
|
/* If the user does not have a password set,
|
|
* passwd will immediately ask for the new password,
|
|
* so skip the AUTH phase */
|
|
if (is_string_complete (str->str, "new", "New", NULL)) {
|
|
gchar *pw;
|
|
|
|
passwd_handler->backend_state = PASSWD_STATE_NEW;
|
|
|
|
/* since passwd didn't ask for our old password
|
|
* in this case, simply remove it from the queue */
|
|
pw = g_queue_pop_head (passwd_handler->backend_stdin_queue);
|
|
g_free (pw);
|
|
|
|
/* Pop the IO queue, i.e. send new password */
|
|
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
|
|
|
|
} else {
|
|
|
|
passwd_handler->backend_state = PASSWD_STATE_AUTH;
|
|
|
|
/* Pop the IO queue, i.e. send current password */
|
|
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
|
|
}
|
|
|
|
reinit = TRUE;
|
|
}
|
|
break;
|
|
default:
|
|
/* Passwd has returned an error */
|
|
reinit = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (reinit) {
|
|
g_string_free (str, TRUE);
|
|
str = NULL;
|
|
}
|
|
|
|
/* Continue calling us */
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* }} Backend communication code
|
|
*/
|
|
|
|
/* Adds the current password to the IO queue */
|
|
static void
|
|
authenticate (PasswdHandler *passwd_handler)
|
|
{
|
|
gchar *s;
|
|
|
|
s = g_strdup_printf ("%s\n", passwd_handler->current_password);
|
|
|
|
g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
|
|
}
|
|
|
|
/* Adds the new password twice to the IO queue */
|
|
static void
|
|
update_password (PasswdHandler *passwd_handler)
|
|
{
|
|
gchar *s;
|
|
|
|
s = g_strdup_printf ("%s\n", passwd_handler->new_password);
|
|
|
|
g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
|
|
/* We need to allocate new space because io_queue_pop() g_free()s
|
|
* every element of the queue after it's done */
|
|
g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
|
|
}
|
|
|
|
|
|
PasswdHandler *
|
|
passwd_init (void)
|
|
{
|
|
PasswdHandler *passwd_handler;
|
|
|
|
passwd_handler = g_new0 (PasswdHandler, 1);
|
|
|
|
/* Initialize backend_pid. -1 means the backend is not running */
|
|
passwd_handler->backend_pid = -1;
|
|
|
|
/* Initialize IO Channels */
|
|
passwd_handler->backend_stdin = NULL;
|
|
passwd_handler->backend_stdout = NULL;
|
|
|
|
/* Initialize write queue */
|
|
passwd_handler->backend_stdin_queue = g_queue_new ();
|
|
|
|
/* Initialize watchers */
|
|
passwd_handler->backend_child_watch_id = 0;
|
|
passwd_handler->backend_stdout_watch_id = 0;
|
|
|
|
/* Initialize backend state */
|
|
passwd_handler->backend_state = PASSWD_STATE_NONE;
|
|
passwd_handler->changing_password = FALSE;
|
|
|
|
return passwd_handler;
|
|
}
|
|
|
|
void
|
|
passwd_destroy (PasswdHandler *passwd_handler)
|
|
{
|
|
g_queue_free (passwd_handler->backend_stdin_queue);
|
|
stop_passwd (passwd_handler);
|
|
g_free (passwd_handler);
|
|
}
|
|
|
|
void
|
|
passwd_authenticate (PasswdHandler *passwd_handler,
|
|
const char *current_password,
|
|
PasswdCallback cb,
|
|
const gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
/* Don't stop if we've already started chaging password */
|
|
if (passwd_handler->changing_password)
|
|
return;
|
|
|
|
/* Clear data from possible previous attempts to change password */
|
|
passwd_handler->new_password = NULL;
|
|
passwd_handler->chpasswd_cb = NULL;
|
|
passwd_handler->chpasswd_cb_data = NULL;
|
|
g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
|
|
g_queue_clear (passwd_handler->backend_stdin_queue);
|
|
|
|
passwd_handler->current_password = current_password;
|
|
passwd_handler->auth_cb = cb;
|
|
passwd_handler->auth_cb_data = user_data;
|
|
|
|
/* Spawn backend */
|
|
stop_passwd (passwd_handler);
|
|
|
|
if (!spawn_passwd (passwd_handler, &error)) {
|
|
g_warning ("%s", error->message);
|
|
g_error_free (error);
|
|
|
|
return;
|
|
}
|
|
|
|
authenticate (passwd_handler);
|
|
|
|
/* Our IO watcher should now handle the rest */
|
|
}
|
|
|
|
gboolean
|
|
passwd_change_password (PasswdHandler *passwd_handler,
|
|
const char *new_password,
|
|
PasswdCallback cb,
|
|
const gpointer user_data)
|
|
{
|
|
GError *error = NULL;
|
|
|
|
passwd_handler->changing_password = TRUE;
|
|
|
|
passwd_handler->new_password = new_password;
|
|
passwd_handler->chpasswd_cb = cb;
|
|
passwd_handler->chpasswd_cb_data = user_data;
|
|
|
|
/* Stop passwd if an error occured and it is still running */
|
|
if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
|
|
|
|
/* Stop passwd, free resources */
|
|
stop_passwd (passwd_handler);
|
|
}
|
|
|
|
/* Check that the backend is still running, or that an error
|
|
* has occured but it has not yet exited */
|
|
if (passwd_handler->backend_pid == -1) {
|
|
/* If it is not, re-run authentication */
|
|
|
|
/* Spawn backend */
|
|
stop_passwd (passwd_handler);
|
|
|
|
if (!spawn_passwd (passwd_handler, &error)) {
|
|
g_warning ("%s", error->message);
|
|
g_error_free (error);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* Add current and new passwords to queue */
|
|
authenticate (passwd_handler);
|
|
update_password (passwd_handler);
|
|
} else {
|
|
/* Only add new passwords to queue */
|
|
update_password (passwd_handler);
|
|
}
|
|
|
|
/* Pop new password through the backend.
|
|
* If user has no password, popping the queue would output current
|
|
* password, while 'passwd' is waiting for the new one. So wait for
|
|
* io_watch_stdout() to remove current password from the queue,
|
|
* and output the new one for us.
|
|
*/
|
|
if (passwd_handler->current_password)
|
|
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
|
|
|
|
/* Our IO watcher should now handle the rest */
|
|
|
|
return TRUE;
|
|
}
|