1 /*
2 * Copyright (c) 2008 Thorsten Kukuk <kukuk@suse.de>
3 * Copyright (c) 2013 Red Hat, Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, and the entire permission notice in its entirety,
10 * including the disclaimer of warranties.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote
15 * products derived from this software without specific prior
16 * written permission.
17 *
18 * ALTERNATIVELY, this product may be distributed under the terms of
19 * the GNU Public License, in which case the provisions of the GPL are
20 * required INSTEAD OF the above restrictions. (This clause is
21 * necessary due to a potential bad interaction between the GPL and
22 * the restrictions contained in a BSD-style copyright.)
23 *
24 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
25 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
27 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
28 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
32 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
34 * OF THE POSSIBILITY OF SUCH DAMAGE.
35 */
36
37 #if defined(HAVE_CONFIG_H)
38 #include <config.h>
39 #endif
40
41 #include <pwd.h>
42 #include <shadow.h>
43 #include <time.h>
44 #include <ctype.h>
45 #include <errno.h>
46 #include <fcntl.h>
47 #include <limits.h>
48 #include <stdio.h>
49 #include <unistd.h>
50 #include <string.h>
51 #include <stdlib.h>
52 #include <syslog.h>
53 #ifdef HELPER_COMPILE
54 #include <stdarg.h>
55 #endif
56 #include <sys/stat.h>
57
58 #ifdef HAVE_CRYPT_H
59 #include <crypt.h>
60 #endif
61
62 #ifdef HELPER_COMPILE
63 #define pam_modutil_getpwnam(h,n) getpwnam(n)
64 #define pam_modutil_getspnam(h,n) getspnam(n)
65 #define pam_syslog(h,a,...) helper_log_err(a,__VA_ARGS__)
66 #else
67 #include <security/pam_modutil.h>
68 #include <security/pam_ext.h>
69 #endif
70 #include <security/pam_modules.h>
71 #include "pam_inline.h"
72
73 #include "opasswd.h"
74
75 #ifndef RANDOM_DEVICE
76 #define RANDOM_DEVICE "/dev/urandom"
77 #endif
78
79 #define DEFAULT_OLD_PASSWORDS_FILE SCONFIGDIR "/opasswd"
80
81 #define DEFAULT_BUFLEN 4096
82
83 typedef struct {
84 char *user;
85 char *uid;
86 int count;
87 char *old_passwords;
88 } opwd;
89
90 #ifdef HELPER_COMPILE
91 PAM_FORMAT((printf, 2, 3))
92 void
93 helper_log_err(int err, const char *format, ...)
94 {
95 va_list args;
96
97 va_start(args, format);
98 openlog(HELPER_COMPILE, LOG_CONS | LOG_PID, LOG_AUTHPRIV);
99 vsyslog(err, format, args);
100 va_end(args);
101 closelog();
102 }
103 #endif
104
105 static int
106 parse_entry (char *line, opwd *data)
107 {
108 const char delimiters[] = ":";
109 char *endptr;
110 char *count;
111
112 data->user = strsep (&line, delimiters);
113 data->uid = strsep (&line, delimiters);
114 count = strsep (&line, delimiters);
115 if (count == NULL)
116 return 1;
117
118 data->count = strtol (count, &endptr, 10);
119 if (endptr != NULL && *endptr != '\0')
120 return 1;
121
122 data->old_passwords = strsep (&line, delimiters);
123
124 return 0;
125 }
126
127 static int
128 compare_password(const char *newpass, const char *oldpass)
129 {
130 char *outval;
131 #ifdef HAVE_CRYPT_R
132 struct crypt_data output;
133 int retval;
134
135 output.initialized = 0;
136
137 outval = crypt_r (newpass, oldpass, &output);
138 #else
139 outval = crypt (newpass, oldpass);
140 #endif
141
142 retval = outval != NULL && strcmp(outval, oldpass) == 0;
143 pam_overwrite_string(outval);
144 return retval;
145 }
146
147 /* Check, if the new password is already in the opasswd file. */
148 PAMH_ARG_DECL(int
149 check_old_pass, const char *user, const char *newpass, const char *filename, int debug)
150 {
151 int retval = PAM_SUCCESS;
152 FILE *oldpf;
153 char *buf = NULL;
154 size_t buflen = 0;
155 opwd entry;
156 int found = 0;
157
158 #ifndef HELPER_COMPILE
159 if (SELINUX_ENABLED)
160 return PAM_PWHISTORY_RUN_HELPER;
161 #endif
162
163 const char *opasswd_file =
164 (filename != NULL ? filename : DEFAULT_OLD_PASSWORDS_FILE);
165
166 if ((oldpf = fopen (opasswd_file, "r")) == NULL)
167 {
168 if (errno != ENOENT)
169 pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", opasswd_file);
170 return PAM_SUCCESS;
171 }
172
173 while (!feof (oldpf))
174 {
175 char *cp, *tmp;
176 #if defined(HAVE_GETLINE)
177 ssize_t n = getline (&buf, &buflen, oldpf);
178 #elif defined (HAVE_GETDELIM)
179 ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
180 #else
181 ssize_t n;
182
183 if (buf == NULL)
184 {
185 buflen = DEFAULT_BUFLEN;
186 buf = malloc (buflen);
187 if (buf == NULL)
188 return PAM_BUF_ERR;
189 }
190 buf[0] = '\0';
191 fgets (buf, buflen - 1, oldpf);
192 n = strlen (buf);
193 #endif /* HAVE_GETLINE / HAVE_GETDELIM */
194 cp = buf;
195
196 if (n < 1)
197 break;
198
199 tmp = strchr (cp, '#'); /* remove comments */
200 if (tmp)
201 *tmp = '\0';
202 while (isspace ((int)*cp)) /* remove spaces and tabs */
203 ++cp;
204 if (*cp == '\0') /* ignore empty lines */
205 continue;
206
207 if (cp[strlen (cp) - 1] == '\n')
208 cp[strlen (cp) - 1] = '\0';
209
210 if (strncmp (cp, user, strlen (user)) == 0 &&
211 cp[strlen (user)] == ':')
212 {
213 /* We found the line we needed */
214 if (parse_entry (cp, &entry) == 0)
215 {
216 found = 1;
217 break;
218 }
219 }
220 }
221
222 fclose (oldpf);
223
224 if (found && entry.old_passwords)
225 {
226 const char delimiters[] = ",";
227 char *running;
228 char *oldpass;
229
230 running = entry.old_passwords;
231
232 do {
233 oldpass = strsep (&running, delimiters);
234 if (oldpass && strlen (oldpass) > 0 &&
235 compare_password(newpass, oldpass) )
236 {
237 if (debug)
238 pam_syslog (pamh, LOG_DEBUG, "New password already used");
239 retval = PAM_AUTHTOK_ERR;
240 break;
241 }
242 } while (oldpass != NULL);
243 }
244
245 pam_overwrite_n(buf, buflen);
246 free (buf);
247
248 return retval;
249 }
250
251 PAMH_ARG_DECL(int
252 save_old_pass, const char *user, int howmany, const char *filename, int debug UNUSED)
253 {
254 struct stat opasswd_stat;
255 FILE *oldpf, *newpf;
256 int newpf_fd;
257 int do_create = 0;
258 int retval = PAM_SUCCESS;
259 char *buf = NULL;
260 size_t buflen = 0;
261 int found = 0;
262 struct passwd *pwd;
263 const char *oldpass;
264
265 /* Define opasswd file and temp file for opasswd */
266 const char *opasswd_file =
267 (filename != NULL ? filename : DEFAULT_OLD_PASSWORDS_FILE);
268 char opasswd_tmp[PATH_MAX];
269
270 if ((size_t) snprintf (opasswd_tmp, sizeof (opasswd_tmp), "%s.tmpXXXXXX",
271 opasswd_file) >= sizeof (opasswd_tmp))
272 return PAM_BUF_ERR;
273
274 pwd = pam_modutil_getpwnam (pamh, user);
275 if (pwd == NULL)
276 return PAM_USER_UNKNOWN;
277
278 if (howmany <= 0)
279 return PAM_SUCCESS;
280
281 #ifndef HELPER_COMPILE
282 if (SELINUX_ENABLED)
283 return PAM_PWHISTORY_RUN_HELPER;
284 #endif
285
286 if ((strcmp(pwd->pw_passwd, "x") == 0) ||
287 ((pwd->pw_passwd[0] == '#') &&
288 (pwd->pw_passwd[1] == '#') &&
289 (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0)))
290 {
291 struct spwd *spw = pam_modutil_getspnam (pamh, user);
292
293 if (spw == NULL)
294 return PAM_USER_UNKNOWN;
295 oldpass = spw->sp_pwdp;
296 }
297 else
298 oldpass = pwd->pw_passwd;
299
300 if (oldpass == NULL || *oldpass == '\0')
301 return PAM_SUCCESS;
302
303 if ((oldpf = fopen (opasswd_file, "r")) == NULL)
304 {
305 if (errno == ENOENT)
306 {
307 pam_syslog (pamh, LOG_NOTICE, "Creating %s", opasswd_file);
308 do_create = 1;
309 }
310 else
311 {
312 pam_syslog (pamh, LOG_ERR, "Cannot open %s: %m", opasswd_file);
313 return PAM_AUTHTOK_ERR;
314 }
315 }
316 else if (fstat (fileno (oldpf), &opasswd_stat) < 0)
317 {
318 pam_syslog (pamh, LOG_ERR, "Cannot stat %s: %m", opasswd_file);
319 fclose (oldpf);
320 return PAM_AUTHTOK_ERR;
321 }
322
323 /* Open a temp passwd file */
324 newpf_fd = mkstemp (opasswd_tmp);
325 if (newpf_fd == -1)
326 {
327 pam_syslog (pamh, LOG_ERR, "Cannot create %s temp file: %m",
328 opasswd_file);
329 if (oldpf)
330 fclose (oldpf);
331 return PAM_AUTHTOK_ERR;
332 }
333 if (do_create)
334 {
335 if (fchmod (newpf_fd, S_IRUSR|S_IWUSR) != 0)
336 pam_syslog (pamh, LOG_ERR,
337 "Cannot set permissions of %s temp file: %m", opasswd_file);
338 if (fchown (newpf_fd, 0, 0) != 0)
339 pam_syslog (pamh, LOG_ERR,
340 "Cannot set owner/group of %s temp file: %m", opasswd_file);
341 }
342 else
343 {
344 if (fchmod (newpf_fd, opasswd_stat.st_mode) != 0)
345 pam_syslog (pamh, LOG_ERR,
346 "Cannot set permissions of %s temp file: %m", opasswd_file);
347 if (fchown (newpf_fd, opasswd_stat.st_uid, opasswd_stat.st_gid) != 0)
348 pam_syslog (pamh, LOG_ERR,
349 "Cannot set owner/group of %s temp file: %m", opasswd_file);
350 }
351 newpf = fdopen (newpf_fd, "w+");
352 if (newpf == NULL)
353 {
354 pam_syslog (pamh, LOG_ERR, "Cannot fdopen %s: %m", opasswd_tmp);
355 if (oldpf)
356 fclose (oldpf);
357 close (newpf_fd);
358 retval = PAM_AUTHTOK_ERR;
359 goto error_opasswd;
360 }
361
362 if (!do_create)
363 while (!feof (oldpf))
364 {
365 char *cp, *tmp, *save;
366 #if defined(HAVE_GETLINE)
367 ssize_t n = getline (&buf, &buflen, oldpf);
368 #elif defined (HAVE_GETDELIM)
369 ssize_t n = getdelim (&buf, &buflen, '\n', oldpf);
370 #else
371 ssize_t n;
372
373 if (buf == NULL)
374 {
375 buflen = DEFAULT_BUFLEN;
376 buf = malloc (buflen);
377 if (buf == NULL)
378 {
379 fclose (oldpf);
380 fclose (newpf);
381 retval = PAM_BUF_ERR;
382 goto error_opasswd;
383 }
384 }
385 buf[0] = '\0';
386 fgets (buf, buflen - 1, oldpf);
387 n = strlen (buf);
388 #endif /* HAVE_GETLINE / HAVE_GETDELIM */
389
390 if (n < 1)
391 break;
392
393 cp = buf;
394 save = strdup (buf); /* Copy to write the original data back. */
395 if (save == NULL)
396 {
397 fclose (oldpf);
398 fclose (newpf);
399 retval = PAM_BUF_ERR;
400 goto error_opasswd;
401 }
402
403 tmp = strchr (cp, '#'); /* remove comments */
404 if (tmp)
405 *tmp = '\0';
406 while (isspace ((int)*cp)) /* remove spaces and tabs */
407 ++cp;
408 if (*cp == '\0') /* ignore empty lines */
409 goto write_old_data;
410
411 if (cp[strlen (cp) - 1] == '\n')
412 cp[strlen (cp) - 1] = '\0';
413
414 if (strncmp (cp, user, strlen (user)) == 0 &&
415 cp[strlen (user)] == ':')
416 {
417 /* We found the line we needed */
418 opwd entry;
419
420 if (parse_entry (cp, &entry) == 0)
421 {
422 char *out = NULL;
423
424 found = 1;
425
426 /* Don't save the current password twice */
427 if (entry.old_passwords && entry.old_passwords[0] != '\0')
428 {
429 char *last = entry.old_passwords;
430
431 cp = entry.old_passwords;
432 entry.count = 1; /* Don't believe the count */
433 while ((cp = strchr (cp, ',')) != NULL)
434 {
435 entry.count++;
436 last = ++cp;
437 }
438
439 /* compare the last password */
440 if (strcmp (last, oldpass) == 0)
441 goto write_old_data;
442 }
443 else
444 entry.count = 0;
445
446 /* increase count. */
447 entry.count++;
448
449 /* check that we don't remember to many passwords. */
450 while (entry.count > howmany && entry.count > 1)
451 {
452 char *p = strpbrk (entry.old_passwords, ",");
453 if (p != NULL)
454 entry.old_passwords = ++p;
455 entry.count--;
456 }
457
458 if (entry.count == 1)
459 {
460 if (asprintf (&out, "%s:%s:%d:%s\n",
461 entry.user, entry.uid, entry.count,
462 oldpass) < 0)
463 {
464 free (save);
465 retval = PAM_AUTHTOK_ERR;
466 fclose (oldpf);
467 fclose (newpf);
468 goto error_opasswd;
469 }
470 }
471 else
472 {
473 if (asprintf (&out, "%s:%s:%d:%s,%s\n",
474 entry.user, entry.uid, entry.count,
475 entry.old_passwords, oldpass) < 0)
476 {
477 free (save);
478 retval = PAM_AUTHTOK_ERR;
479 fclose (oldpf);
480 fclose (newpf);
481 goto error_opasswd;
482 }
483 }
484
485 if (fputs (out, newpf) < 0)
486 {
487 free (out);
488 free (save);
489 retval = PAM_AUTHTOK_ERR;
490 fclose (oldpf);
491 fclose (newpf);
492 goto error_opasswd;
493 }
494 free (out);
495 }
496 }
497 else
498 {
499 write_old_data:
500 if (fputs (save, newpf) < 0)
501 {
502 free (save);
503 retval = PAM_AUTHTOK_ERR;
504 fclose (oldpf);
505 fclose (newpf);
506 goto error_opasswd;
507 }
508 }
509 free (save);
510 }
511
512 if (!found)
513 {
514 char *out;
515
516 if (asprintf (&out, "%s:%d:1:%s\n", user, pwd->pw_uid, oldpass) < 0)
517 {
518 retval = PAM_AUTHTOK_ERR;
519 if (oldpf)
520 fclose (oldpf);
521 fclose (newpf);
522 goto error_opasswd;
523 }
524 if (fputs (out, newpf) < 0)
525 {
526 pam_overwrite_string(out);
527 free (out);
528 retval = PAM_AUTHTOK_ERR;
529 if (oldpf)
530 fclose (oldpf);
531 fclose (newpf);
532 goto error_opasswd;
533 }
534 pam_overwrite_string(out);
535 free (out);
536 }
537
538 if (oldpf)
539 if (fclose (oldpf) != 0)
540 {
541 pam_syslog (pamh, LOG_ERR, "Error while closing old opasswd file: %m");
542 retval = PAM_AUTHTOK_ERR;
543 fclose (newpf);
544 goto error_opasswd;
545 }
546
547 if (fflush (newpf) != 0 || fsync (fileno (newpf)) != 0)
548 {
549 pam_syslog (pamh, LOG_ERR,
550 "Error while syncing temporary opasswd file: %m");
551 retval = PAM_AUTHTOK_ERR;
552 fclose (newpf);
553 goto error_opasswd;
554 }
555
556 if (fclose (newpf) != 0)
557 {
558 pam_syslog (pamh, LOG_ERR,
559 "Error while closing temporary opasswd file: %m");
560 retval = PAM_AUTHTOK_ERR;
561 goto error_opasswd;
562 }
563
564 char opasswd_backup[PATH_MAX];
565 if ((size_t) snprintf (opasswd_backup, sizeof (opasswd_backup), "%s.old",
566 opasswd_file) >= sizeof (opasswd_backup))
567 {
568 retval = PAM_BUF_ERR;
569 goto error_opasswd;
570 }
571
572 unlink (opasswd_backup);
573 if (link (opasswd_file, opasswd_backup) != 0 &&
574 errno != ENOENT)
575 pam_syslog (pamh, LOG_ERR, "Cannot create backup file of %s: %m",
576 opasswd_file);
577 rename (opasswd_tmp, opasswd_file);
578 error_opasswd:
579 unlink (opasswd_tmp);
580 pam_overwrite_n(buf, buflen);
581 free (buf);
582
583 return retval;
584 }