1 /*
2 * pam_pwhistory module
3 *
4 * Copyright (c) 2008, 2012 Thorsten Kukuk
5 * Author: Thorsten Kukuk <kukuk@thkukuk.de>
6 * Copyright (c) 2013 Red Hat, Inc.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, and the entire permission notice in its entirety,
13 * including the disclaimer of warranties.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. The name of the author may not be used to endorse or promote
18 * products derived from this software without specific prior
19 * written permission.
20 *
21 * ALTERNATIVELY, this product may be distributed under the terms of
22 * the GNU Public License, in which case the provisions of the GPL are
23 * required INSTEAD OF the above restrictions. (This clause is
24 * necessary due to a potential bad interaction between the GPL and
25 * the restrictions contained in a BSD-style copyright.)
26 *
27 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
28 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
30 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
31 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
33 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
37 * OF THE POSSIBILITY OF SUCH DAMAGE.
38 */
39
40 #if defined(HAVE_CONFIG_H)
41 #include <config.h>
42 #endif
43
44 #include <pwd.h>
45 #include <errno.h>
46 #include <stdio.h>
47 #include <stdlib.h>
48 #include <string.h>
49 #include <unistd.h>
50 #include <syslog.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <sys/time.h>
54 #include <sys/resource.h>
55 #include <sys/wait.h>
56 #include <signal.h>
57 #include <fcntl.h>
58
59 #include <security/pam_modules.h>
60 #include <security/pam_modutil.h>
61 #include <security/pam_ext.h>
62 #include <security/_pam_macros.h>
63
64 #include "opasswd.h"
65 #include "pam_inline.h"
66 #include "pwhistory_config.h"
67
68
69
70 static void
71 parse_option (pam_handle_t *pamh, const char *argv, options_t *options)
72 {
73 const char *str;
74
75 if (strcasecmp (argv, "try_first_pass") == 0)
76 /* ignore */;
77 else if (strcasecmp (argv, "use_first_pass") == 0)
78 /* ignore */;
79 else if (strcasecmp (argv, "use_authtok") == 0)
80 /* ignore, handled by pam_get_authtok */;
81 else if (strcasecmp (argv, "debug") == 0)
82 options->debug = 1;
83 else if ((str = pam_str_skip_icase_prefix(argv, "remember=")) != NULL)
84 {
85 options->remember = strtol(str, NULL, 10);
86 if (options->remember < 0)
87 options->remember = 0;
88 if (options->remember > 400)
89 options->remember = 400;
90 }
91 else if ((str = pam_str_skip_icase_prefix(argv, "retry=")) != NULL)
92 {
93 options->tries = strtol(str, NULL, 10);
94 if (options->tries < 0)
95 options->tries = 1;
96 }
97 else if (strcasecmp (argv, "enforce_for_root") == 0)
98 options->enforce_for_root = 1;
99 else if (pam_str_skip_icase_prefix(argv, "authtok_type=") != NULL)
100 { /* ignore, for pam_get_authtok */; }
101 else if ((str = pam_str_skip_icase_prefix(argv, "file=")) != NULL)
102 {
103 if (*str != '/')
104 {
105 pam_syslog (pamh, LOG_ERR,
106 "pam_pwhistory: file path should be absolute: %s", argv);
107 }
108 else
109 options->filename = str;
110 }
111 else
112 pam_syslog (pamh, LOG_ERR, "pam_pwhistory: unknown option: %s", argv);
113 }
114
115 static int
116 run_save_helper(pam_handle_t *pamh, const char *user,
117 int howmany, const char *filename, int debug)
118 {
119 int retval, child;
120 struct sigaction newsa, oldsa;
121
122 memset(&newsa, '\0', sizeof(newsa));
123 newsa.sa_handler = SIG_DFL;
124 sigaction(SIGCHLD, &newsa, &oldsa);
125
126 child = fork();
127 if (child == 0)
128 {
129 static char *envp[] = { NULL };
130 char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL };
131
132 if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_PIPE_FD,
133 PAM_MODUTIL_PIPE_FD,
134 PAM_MODUTIL_PIPE_FD) < 0)
135 {
136 _exit(PAM_SYSTEM_ERR);
137 }
138
139 /* exec binary helper */
140 DIAG_PUSH_IGNORE_CAST_QUAL;
141 args[0] = (char *)PWHISTORY_HELPER;
142 args[1] = (char *)"save";
143 args[2] = (char *)user;
144 args[3] = (char *)filename;
145 DIAG_POP_IGNORE_CAST_QUAL;
146 if (asprintf(&args[4], "%d", howmany) < 0 ||
147 asprintf(&args[5], "%d", debug) < 0)
148 {
149 pam_syslog(pamh, LOG_ERR, "asprintf: %m");
150 _exit(PAM_SYSTEM_ERR);
151 }
152
153 execve(args[0], args, envp);
154
155 pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
156
157 _exit(PAM_SYSTEM_ERR);
158 }
159 else if (child > 0)
160 {
161 /* wait for child */
162 int rc = 0;
163 while ((rc = waitpid (child, &retval, 0)) == -1 &&
164 errno == EINTR);
165 if (rc < 0)
166 {
167 pam_syslog(pamh, LOG_ERR, "pwhistory_helper save: waitpid: %m");
168 retval = PAM_SYSTEM_ERR;
169 }
170 else if (!WIFEXITED(retval))
171 {
172 pam_syslog(pamh, LOG_ERR, "pwhistory_helper save abnormal exit: %d", retval);
173 retval = PAM_SYSTEM_ERR;
174 }
175 else
176 {
177 retval = WEXITSTATUS(retval);
178 }
179 }
180 else
181 {
182 pam_syslog(pamh, LOG_ERR, "fork failed: %m");
183 retval = PAM_SYSTEM_ERR;
184 }
185
186 sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
187
188 return retval;
189 }
190
191 static int
192 run_check_helper(pam_handle_t *pamh, const char *user,
193 const char *newpass, const char *filename, int debug)
194 {
195 int retval, child, fds[2];
196 struct sigaction newsa, oldsa;
197
198 /* create a pipe for the password */
199 if (pipe(fds) != 0)
200 return PAM_SYSTEM_ERR;
201
202 memset(&newsa, '\0', sizeof(newsa));
203 newsa.sa_handler = SIG_DFL;
204 sigaction(SIGCHLD, &newsa, &oldsa);
205
206 child = fork();
207 if (child == 0)
208 {
209 static char *envp[] = { NULL };
210 char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL };
211
212 /* reopen stdin as pipe */
213 if (dup2(fds[0], STDIN_FILENO) != STDIN_FILENO)
214 {
215 pam_syslog(pamh, LOG_ERR, "dup2 of %s failed: %m", "stdin");
216 _exit(PAM_SYSTEM_ERR);
217 }
218
219 if (pam_modutil_sanitize_helper_fds(pamh, PAM_MODUTIL_IGNORE_FD,
220 PAM_MODUTIL_PIPE_FD,
221 PAM_MODUTIL_PIPE_FD) < 0)
222 {
223 _exit(PAM_SYSTEM_ERR);
224 }
225
226 /* exec binary helper */
227 DIAG_PUSH_IGNORE_CAST_QUAL;
228 args[0] = (char *)PWHISTORY_HELPER;
229 args[1] = (char *)"check";
230 args[2] = (char *)user;
231 args[3] = (char *)filename;
232 DIAG_POP_IGNORE_CAST_QUAL;
233 if (asprintf(&args[4], "%d", debug) < 0)
234 {
235 pam_syslog(pamh, LOG_ERR, "asprintf: %m");
236 _exit(PAM_SYSTEM_ERR);
237 }
238
239 execve(args[0], args, envp);
240
241 pam_syslog(pamh, LOG_ERR, "helper binary execve failed: %s: %m", args[0]);
242
243 _exit(PAM_SYSTEM_ERR);
244 }
245 else if (child > 0)
246 {
247 /* wait for child */
248 int rc = 0;
249 if (newpass == NULL)
250 newpass = "";
251
252 /* send the password to the child */
253 if (write(fds[1], newpass, strlen(newpass)+1) == -1)
254 {
255 pam_syslog(pamh, LOG_ERR, "Cannot send password to helper: %m");
256 retval = PAM_SYSTEM_ERR;
257 }
258 newpass = NULL;
259 close(fds[0]); /* close here to avoid possible SIGPIPE above */
260 close(fds[1]);
261 while ((rc = waitpid (child, &retval, 0)) == -1 &&
262 errno == EINTR);
263 if (rc < 0)
264 {
265 pam_syslog(pamh, LOG_ERR, "pwhistory_helper check: waitpid: %m");
266 retval = PAM_SYSTEM_ERR;
267 }
268 else if (!WIFEXITED(retval))
269 {
270 pam_syslog(pamh, LOG_ERR, "pwhistory_helper check abnormal exit: %d", retval);
271 retval = PAM_SYSTEM_ERR;
272 }
273 else
274 {
275 retval = WEXITSTATUS(retval);
276 }
277 }
278 else
279 {
280 pam_syslog(pamh, LOG_ERR, "fork failed: %m");
281 close(fds[0]);
282 close(fds[1]);
283 retval = PAM_SYSTEM_ERR;
284 }
285
286 sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */
287
288 return retval;
289 }
290
291 /* This module saves the current hashed password in /etc/security/opasswd
292 and then compares the new password with all entries in this file. */
293
294 int
295 pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv)
296 {
297 const char *newpass;
298 const char *user;
299 int retval, tries;
300 options_t options;
301
302 memset (&options, 0, sizeof (options));
303
304 /* Set some default values, which could be overwritten later. */
305 options.remember = 10;
306 options.tries = 1;
307
308 parse_config_file(pamh, argc, argv, &options);
309
310 /* Parse parameters for module */
311 for ( ; argc-- > 0; argv++)
312 parse_option (pamh, *argv, &options);
313
314 if (options.debug)
315 pam_syslog (pamh, LOG_DEBUG, "pam_sm_chauthtok entered");
316
317 if (options.remember == 0)
318 return PAM_IGNORE;
319
320 retval = pam_get_user (pamh, &user, NULL);
321 if (retval != PAM_SUCCESS)
322 return retval;
323
324 if (flags & PAM_PRELIM_CHECK)
325 {
326 if (options.debug)
327 pam_syslog (pamh, LOG_DEBUG,
328 "pam_sm_chauthtok(PAM_PRELIM_CHECK)");
329
330 return PAM_SUCCESS;
331 }
332
333 retval = save_old_pass (pamh, user, options.remember, options.filename, options.debug);
334
335 if (retval == PAM_PWHISTORY_RUN_HELPER)
336 retval = run_save_helper(pamh, user, options.remember, options.filename, options.debug);
337
338 if (retval != PAM_SUCCESS)
339 return retval;
340
341 newpass = NULL;
342 tries = 0;
343 while ((newpass == NULL) && (tries < options.tries))
344 {
345 retval = pam_get_authtok (pamh, PAM_AUTHTOK, &newpass, NULL);
346 if (retval != PAM_SUCCESS && retval != PAM_TRY_AGAIN)
347 {
348 if (retval == PAM_CONV_AGAIN)
349 retval = PAM_INCOMPLETE;
350 return retval;
351 }
352 tries++;
353
354 if (options.debug)
355 {
356 if (newpass)
357 pam_syslog (pamh, LOG_DEBUG, "got new auth token");
358 else
359 pam_syslog (pamh, LOG_DEBUG, "got no auth token");
360 }
361
362 if (newpass == NULL || retval == PAM_TRY_AGAIN)
363 continue;
364
365 if (options.debug)
366 pam_syslog (pamh, LOG_DEBUG, "check against old password file");
367
368 retval = check_old_pass (pamh, user, newpass, options.filename, options.debug);
369 if (retval == PAM_PWHISTORY_RUN_HELPER)
370 retval = run_check_helper(pamh, user, newpass, options.filename, options.debug);
371
372 if (retval != PAM_SUCCESS)
373 {
374 if (getuid() || options.enforce_for_root ||
375 (flags & PAM_CHANGE_EXPIRED_AUTHTOK))
376 {
377 pam_error (pamh,
378 _("Password has been already used. Choose another."));
379 newpass = NULL;
380 /* Remove password item, else following module will use it */
381 pam_set_item (pamh, PAM_AUTHTOK, (void *) NULL);
382 }
383 else
384 pam_info (pamh,
385 _("Password has been already used."));
386 }
387 }
388
389 if (newpass == NULL && tries >= options.tries)
390 {
391 if (options.debug)
392 pam_syslog (pamh, LOG_DEBUG, "Aborted, too many tries");
393 return PAM_MAXTRIES;
394 }
395
396 return PAM_SUCCESS;
397 }