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