Merge branch 'vendor/DHCPCD'
[dragonfly.git] / contrib / pam_passwdqc / pam_passwdqc.c
1 /*
2  * Copyright (c) 2000-2003,2005 by Solar Designer.  See LICENSE.
3  */
4
5 #if defined(__FreeBSD__) || defined(__DragonFly__)
6 /* For vsnprintf(3) */
7 #define _XOPEN_SOURCE 600
8 #else
9 #define _XOPEN_SOURCE 500
10 #define _XOPEN_SOURCE_EXTENDED
11 #define _XOPEN_VERSION 500
12 #endif
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <stdarg.h>
16 #include <string.h>
17 #include <limits.h>
18 #include <unistd.h>
19 #include <pwd.h>
20 #ifdef HAVE_SHADOW
21 #include <shadow.h>
22 #endif
23
24 #define PAM_SM_PASSWORD
25 #ifndef LINUX_PAM
26 #include <security/pam_appl.h>
27 #endif
28 #include <security/pam_modules.h>
29
30 #include "pam_macros.h"
31
32 #if !defined(PAM_EXTERN) && !defined(PAM_STATIC)
33 #define PAM_EXTERN                      extern
34 #endif
35
36 #if !defined(PAM_AUTHTOK_RECOVERY_ERR) && defined(PAM_AUTHTOK_RECOVER_ERR)
37 #define PAM_AUTHTOK_RECOVERY_ERR        PAM_AUTHTOK_RECOVER_ERR
38 #endif
39
40 #if (defined(__sun) || defined(__hpux)) && \
41     !defined(LINUX_PAM) && !defined(_OPENPAM)
42 /* Sun's PAM doesn't use const here, while Linux-PAM and OpenPAM do */
43 #define lo_const
44 #else
45 #define lo_const                        const
46 #endif
47 #ifdef _OPENPAM
48 /* OpenPAM doesn't use const here, while Linux-PAM does */
49 #define l_const
50 #else
51 #define l_const                         lo_const
52 #endif
53 typedef lo_const void *pam_item_t;
54
55 #include "passwdqc.h"
56
57 #define F_ENFORCE_MASK                  0x00000003
58 #define F_ENFORCE_USERS                 0x00000001
59 #define F_ENFORCE_ROOT                  0x00000002
60 #define F_ENFORCE_EVERYONE              F_ENFORCE_MASK
61 #define F_NON_UNIX                      0x00000004
62 #define F_ASK_OLDAUTHTOK_MASK           0x00000030
63 #define F_ASK_OLDAUTHTOK_PRELIM         0x00000010
64 #define F_ASK_OLDAUTHTOK_UPDATE         0x00000020
65 #define F_CHECK_OLDAUTHTOK              0x00000040
66 #define F_USE_FIRST_PASS                0x00000100
67 #define F_USE_AUTHTOK                   0x00000200
68
69 typedef struct {
70         passwdqc_params_t qc;
71         int flags;
72         int retry;
73 } params_t;
74
75 static params_t defaults = {
76         {
77                 {INT_MAX, 24, 11, 8, 7},        /* min */
78                 40,                             /* max */
79                 3,                              /* passphrase_words */
80                 4,                              /* match_length */
81                 1,                              /* similar_deny */
82                 42                              /* random_bits */
83         },
84         F_ENFORCE_EVERYONE,                     /* flags */
85         3                                       /* retry */
86 };
87
88 #define PROMPT_OLDPASS \
89         "Enter current password: "
90 #define PROMPT_NEWPASS1 \
91         "Enter new password: "
92 #define PROMPT_NEWPASS2 \
93         "Re-type new password: "
94
95 #define MESSAGE_MISCONFIGURED \
96         "System configuration error.  Please contact your administrator."
97 #define MESSAGE_INVALID_OPTION \
98         "pam_passwdqc: Invalid option: \"%s\"."
99 #define MESSAGE_INTRO_PASSWORD \
100         "\nYou can now choose the new password.\n"
101 #define MESSAGE_INTRO_BOTH \
102         "\nYou can now choose the new password or passphrase.\n"
103 #define MESSAGE_EXPLAIN_PASSWORD_1CLASS \
104         "A good password should be a mix of upper and lower case letters,\n" \
105         "digits, and other characters.  You can use a%s %d character long\n" \
106         "password.\n"
107 #define MESSAGE_EXPLAIN_PASSWORD_CLASSES \
108         "A valid password should be a mix of upper and lower case letters,\n" \
109         "digits, and other characters.  You can use a%s %d character long\n" \
110         "password with characters from at least %d of these 4 classes.\n" \
111         "An upper case letter that begins the password and a digit that\n" \
112         "ends it do not count towards the number of character classes used.\n"
113 #define MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES \
114         "A valid password should be a mix of upper and lower case letters,\n" \
115         "digits, and other characters.  You can use a%s %d character long\n" \
116         "password with characters from all of these classes.  An upper\n" \
117         "case letter that begins the password and a digit that ends it do\n" \
118         "not count towards the number of character classes used.\n"
119 #define MESSAGE_EXPLAIN_PASSWORD_ALT \
120         "A valid password should be a mix of upper and lower case letters,\n" \
121         "digits, and other characters.  You can use a%s %d character long\n" \
122         "password with characters from at least 3 of these 4 classes, or\n" \
123         "a%s %d character long password containing characters from all the\n" \
124         "classes.  An upper case letter that begins the password and a\n" \
125         "digit that ends it do not count towards the number of character\n" \
126         "classes used.\n"
127 #define MESSAGE_EXPLAIN_PASSPHRASE \
128         "A passphrase should be of at least %d words, %d to %d characters\n" \
129         "long, and contain enough different characters.\n"
130 #define MESSAGE_RANDOM \
131         "Alternatively, if noone else can see your terminal now, you can\n" \
132         "pick this as your password: \"%s\".\n"
133 #define MESSAGE_RANDOMONLY \
134         "This system is configured to permit randomly generated passwords\n" \
135         "only.  If noone else can see your terminal now, you can pick this\n" \
136         "as your password: \"%s\".  Otherwise, come back later.\n"
137 #define MESSAGE_RANDOMFAILED \
138         "This system is configured to use randomly generated passwords\n" \
139         "only, but the attempt to generate a password has failed.  This\n" \
140         "could happen for a number of reasons: you could have requested\n" \
141         "an impossible password length, or the access to kernel random\n" \
142         "number pool could have failed."
143 #define MESSAGE_TOOLONG \
144         "This password may be too long for some services.  Choose another."
145 #define MESSAGE_TRUNCATED \
146         "Warning: your longer password will be truncated to 8 characters."
147 #define MESSAGE_WEAKPASS \
148         "Weak password: %s."
149 #define MESSAGE_NOTRANDOM \
150         "Sorry, you've mistyped the password that was generated for you."
151 #define MESSAGE_MISTYPED \
152         "Sorry, passwords do not match."
153 #define MESSAGE_RETRY \
154         "Try again."
155
156 static int converse(pam_handle_t *pamh, int style, l_const char *text,
157     struct pam_response **resp)
158 {
159         pam_item_t item;
160         const struct pam_conv *conv;
161         struct pam_message msg, *pmsg;
162         int status;
163
164         *resp = NULL;
165         status = pam_get_item(pamh, PAM_CONV, &item);
166         if (status != PAM_SUCCESS)
167                 return status;
168         conv = item;
169
170         pmsg = &msg;
171         msg.msg_style = style;
172         msg.msg = (char *)text;
173
174         return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
175             conv->appdata_ptr);
176 }
177
178 #ifdef __GNUC__
179 __attribute__ ((format (printf, 3, 4)))
180 #endif
181 static int say(pam_handle_t *pamh, int style, const char *format, ...)
182 {
183         va_list args;
184         char buffer[0x800];
185         int needed;
186         struct pam_response *resp;
187         int status;
188
189         va_start(args, format);
190         needed = vsnprintf(buffer, sizeof(buffer), format, args);
191         va_end(args);
192
193         if ((unsigned int)needed < sizeof(buffer)) {
194                 status = converse(pamh, style, buffer, &resp);
195                 pwqc_overwrite_string(buffer);
196                 pwqc_drop_pam_reply(resp, 1);
197         } else {
198                 status = PAM_ABORT;
199                 memset(buffer, 0, sizeof(buffer));
200         }
201
202         return status;
203 }
204
205 static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass)
206 {
207         if ((int)strlen(newpass) > params->qc.max) {
208                 if (params->qc.max != 8) {
209                         say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
210                         return -1;
211                 }
212                 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
213         }
214
215         return 0;
216 }
217
218 static int check_pass(struct passwd *pw, const char *pass)
219 {
220         char *cryptpw;
221 #ifdef HAVE_SHADOW
222         struct spwd *spw;
223         const char *hash;
224         int retval;
225
226 #ifdef __hpux
227         if (iscomsec()) {
228 #else
229         if (!strcmp(pw->pw_passwd, "x")) {
230 #endif
231                 spw = getspnam(pw->pw_name);
232                 endspent();
233                 if (!spw)
234                         return -1;
235 #ifdef __hpux
236                 hash = bigcrypt(pass, spw->sp_pwdp);
237 #else
238                 hash = crypt(pass, spw->sp_pwdp);
239 #endif
240                 retval = (hash == NULL || strcmp(hash, spw->sp_pwdp)) ? -1 : 0;
241                 memset(spw->sp_pwdp, 0, strlen(spw->sp_pwdp));
242                 return retval;
243         }
244 #endif
245         cryptpw = crypt(pass, pw->pw_passwd);
246         return (cryptpw == NULL || strcmp(cryptpw, pw->pw_passwd)) ? -1 : 0;
247 }
248
249 static int am_root(pam_handle_t *pamh)
250 {
251         pam_item_t item;
252         const char *service;
253
254         if (getuid() != 0)
255                 return 0;
256
257         if (pam_get_item(pamh, PAM_SERVICE, &item) != PAM_SUCCESS)
258                 return 0;
259         service = item;
260
261         return !strcmp(service, "passwd");
262 }
263
264 static int parse(params_t *params, pam_handle_t *pamh,
265     int argc, const char **argv)
266 {
267         const char *p;
268         char *e;
269         int i;
270         unsigned long v;
271
272         while (argc) {
273                 if (!strncmp(*argv, "min=", 4)) {
274                         p = *argv + 4;
275                         for (i = 0; i < 5; i++) {
276                                 if (!strncmp(p, "disabled", 8)) {
277                                         v = INT_MAX;
278                                         p += 8;
279                                 } else {
280                                         v = strtoul(p, &e, 10);
281                                         p = e;
282                                 }
283                                 if (i < 4 && *p++ != ',') break;
284                                 if (v > INT_MAX) break;
285                                 if (i && (int)v > params->qc.min[i - 1]) break;
286                                 params->qc.min[i] = v;
287                         }
288                         if (*p) break;
289                 } else
290                 if (!strncmp(*argv, "max=", 4)) {
291                         v = strtoul(*argv + 4, &e, 10);
292                         if (*e || v < 8 || v > INT_MAX) break;
293                         params->qc.max = v;
294                 } else
295                 if (!strncmp(*argv, "passphrase=", 11)) {
296                         v = strtoul(*argv + 11, &e, 10);
297                         if (*e || v > INT_MAX) break;
298                         params->qc.passphrase_words = v;
299                 } else
300                 if (!strncmp(*argv, "match=", 6)) {
301                         v = strtoul(*argv + 6, &e, 10);
302                         if (*e || v > INT_MAX) break;
303                         params->qc.match_length = v;
304                 } else
305                 if (!strncmp(*argv, "similar=", 8)) {
306                         if (!strcmp(*argv + 8, "permit"))
307                                 params->qc.similar_deny = 0;
308                         else
309                         if (!strcmp(*argv + 8, "deny"))
310                                 params->qc.similar_deny = 1;
311                         else
312                                 break;
313                 } else
314                 if (!strncmp(*argv, "random=", 7)) {
315                         v = strtoul(*argv + 7, &e, 10);
316                         if (!strcmp(e, ",only")) {
317                                 e += 5;
318                                 params->qc.min[4] = INT_MAX;
319                         }
320                         if (*e || (v && v < 24) || v > 72) break;
321                         params->qc.random_bits = v;
322                 } else
323                 if (!strncmp(*argv, "enforce=", 8)) {
324                         params->flags &= ~F_ENFORCE_MASK;
325                         if (!strcmp(*argv + 8, "users"))
326                                 params->flags |= F_ENFORCE_USERS;
327                         else
328                         if (!strcmp(*argv + 8, "everyone"))
329                                 params->flags |= F_ENFORCE_EVERYONE;
330                         else
331                         if (strcmp(*argv + 8, "none"))
332                                 break;
333                 } else
334                 if (!strcmp(*argv, "non-unix")) {
335                         if (params->flags & F_CHECK_OLDAUTHTOK) break;
336                         params->flags |= F_NON_UNIX;
337                 } else
338                 if (!strncmp(*argv, "retry=", 6)) {
339                         v = strtoul(*argv + 6, &e, 10);
340                         if (*e || v > INT_MAX) break;
341                         params->retry = v;
342                 } else
343                 if (!strncmp(*argv, "ask_oldauthtok", 14)) {
344                         params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
345                         if (params->flags & F_USE_FIRST_PASS) break;
346                         if (!strcmp(*argv + 14, "=update"))
347                                 params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
348                         else
349                         if (!(*argv)[14])
350                                 params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
351                         else
352                                 break;
353                 } else
354                 if (!strcmp(*argv, "check_oldauthtok")) {
355                         if (params->flags & F_NON_UNIX) break;
356                         params->flags |= F_CHECK_OLDAUTHTOK;
357                 } else
358                 if (!strcmp(*argv, "use_first_pass")) {
359                         if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
360                         params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
361                 } else
362                 if (!strcmp(*argv, "use_authtok")) {
363                         params->flags |= F_USE_AUTHTOK;
364                 } else
365                         break;
366                 argc--; argv++;
367         }
368
369         if (argc) {
370                 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
371                     MESSAGE_INVALID_OPTION : MESSAGE_MISCONFIGURED, *argv);
372                 return PAM_ABORT;
373         }
374
375         return PAM_SUCCESS;
376 }
377
378 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
379     int argc, const char **argv)
380 {
381         params_t params;
382         struct pam_response *resp;
383         struct passwd *pw, fake_pw;
384         pam_item_t item;
385         const char *user, *oldpass, *newpass;
386         char *trypass, *randompass;
387         const char *reason;
388         int ask_oldauthtok;
389         int randomonly, enforce, retries_left, retry_wanted;
390         int status;
391
392         params = defaults;
393         status = parse(&params, pamh, argc, argv);
394         if (status != PAM_SUCCESS)
395                 return status;
396
397         ask_oldauthtok = 0;
398         if (flags & PAM_PRELIM_CHECK) {
399                 if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
400                         ask_oldauthtok = 1;
401         } else
402         if (flags & PAM_UPDATE_AUTHTOK) {
403                 if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
404                         ask_oldauthtok = 1;
405         } else
406                 return PAM_SERVICE_ERR;
407
408         if (ask_oldauthtok && !am_root(pamh)) {
409                 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
410                     PROMPT_OLDPASS, &resp);
411
412                 if (status == PAM_SUCCESS) {
413                         if (resp && resp->resp) {
414                                 status = pam_set_item(pamh,
415                                     PAM_OLDAUTHTOK, resp->resp);
416                                 pwqc_drop_pam_reply(resp, 1);
417                         } else
418                                 status = PAM_AUTHTOK_RECOVERY_ERR;
419                 }
420
421                 if (status != PAM_SUCCESS)
422                         return status;
423         }
424
425         if (flags & PAM_PRELIM_CHECK)
426                 return status;
427
428         status = pam_get_item(pamh, PAM_USER, &item);
429         if (status != PAM_SUCCESS)
430                 return status;
431         user = item;
432
433         status = pam_get_item(pamh, PAM_OLDAUTHTOK, &item);
434         if (status != PAM_SUCCESS)
435                 return status;
436         oldpass = item;
437
438         if (params.flags & F_NON_UNIX) {
439                 pw = &fake_pw;
440                 pw->pw_name = (char *)user;
441                 pw->pw_gecos = "";
442         } else {
443                 pw = getpwnam(user);
444                 endpwent();
445                 if (!pw)
446                         return PAM_USER_UNKNOWN;
447                 if ((params.flags & F_CHECK_OLDAUTHTOK) && !am_root(pamh) &&
448                     (!oldpass || check_pass(pw, oldpass)))
449                         status = PAM_AUTH_ERR;
450                 memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
451                 if (status != PAM_SUCCESS)
452                         return status;
453         }
454
455         randomonly = params.qc.min[4] > params.qc.max;
456
457         if (am_root(pamh))
458                 enforce = params.flags & F_ENFORCE_ROOT;
459         else
460                 enforce = params.flags & F_ENFORCE_USERS;
461
462         if (params.flags & F_USE_AUTHTOK) {
463                 status = pam_get_item(pamh, PAM_AUTHTOK, &item);
464                 if (status != PAM_SUCCESS)
465                         return status;
466                 newpass = item;
467                 if (!newpass || (check_max(&params, pamh, newpass) && enforce))
468                         return PAM_AUTHTOK_ERR;
469                 reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
470                 if (reason) {
471                         say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
472                         if (enforce)
473                                 status = PAM_AUTHTOK_ERR;
474                 }
475                 return status;
476         }
477
478         retries_left = params.retry;
479
480 retry:
481         retry_wanted = 0;
482
483         if (!randomonly &&
484             params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
485                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
486         else
487                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
488         if (status != PAM_SUCCESS)
489                 return status;
490
491         if (!randomonly && params.qc.min[0] == params.qc.min[4])
492                 status = say(pamh, PAM_TEXT_INFO,
493                     MESSAGE_EXPLAIN_PASSWORD_1CLASS,
494                     params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
495                     params.qc.min[4]);
496         else
497         if (!randomonly && params.qc.min[3] == params.qc.min[4])
498                 status = say(pamh, PAM_TEXT_INFO,
499                     MESSAGE_EXPLAIN_PASSWORD_CLASSES,
500                     params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
501                     params.qc.min[4],
502                     params.qc.min[1] != params.qc.min[3] ? 3 : 2);
503         else
504         if (!randomonly && params.qc.min[3] == INT_MAX)
505                 status = say(pamh, PAM_TEXT_INFO,
506                     MESSAGE_EXPLAIN_PASSWORD_ALL_CLASSES,
507                     params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
508                     params.qc.min[4]);
509         else
510         if (!randomonly)
511                 status = say(pamh, PAM_TEXT_INFO,
512                     MESSAGE_EXPLAIN_PASSWORD_ALT,
513                     params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
514                     params.qc.min[3],
515                     params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
516                     params.qc.min[4]);
517         if (status != PAM_SUCCESS)
518                 return status;
519
520         if (!randomonly &&
521             params.qc.passphrase_words &&
522             params.qc.min[2] <= params.qc.max) {
523                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE,
524                     params.qc.passphrase_words,
525                     params.qc.min[2], params.qc.max);
526                 if (status != PAM_SUCCESS)
527                         return status;
528         }
529
530         randompass = _passwdqc_random(&params.qc);
531         if (randompass) {
532                 status = say(pamh, PAM_TEXT_INFO, randomonly ?
533                     MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
534                 if (status != PAM_SUCCESS) {
535                         pwqc_overwrite_string(randompass);
536                         randompass = NULL;
537                 }
538         } else
539         if (randomonly) {
540                 say(pamh, PAM_ERROR_MSG, am_root(pamh) ?
541                     MESSAGE_RANDOMFAILED : MESSAGE_MISCONFIGURED);
542                 return PAM_AUTHTOK_ERR;
543         }
544
545         status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
546         if (status == PAM_SUCCESS && (!resp || !resp->resp))
547                 status = PAM_AUTHTOK_ERR;
548
549         if (status != PAM_SUCCESS) {
550                 pwqc_overwrite_string(randompass);
551                 return status;
552         }
553
554         trypass = strdup(resp->resp);
555
556         pwqc_drop_pam_reply(resp, 1);
557
558         if (!trypass) {
559                 pwqc_overwrite_string(randompass);
560                 return PAM_AUTHTOK_ERR;
561         }
562
563         if (check_max(&params, pamh, trypass) && enforce) {
564                 status = PAM_AUTHTOK_ERR;
565                 retry_wanted = 1;
566         }
567
568         reason = NULL;
569         if (status == PAM_SUCCESS &&
570             (!randompass || !strstr(trypass, randompass)) &&
571             (randomonly ||
572             (reason = _passwdqc_check(&params.qc, trypass, oldpass, pw)))) {
573                 if (randomonly)
574                         say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
575                 else
576                         say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
577                 if (enforce) {
578                         status = PAM_AUTHTOK_ERR;
579                         retry_wanted = 1;
580                 }
581         }
582
583         if (status == PAM_SUCCESS)
584                 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
585                     PROMPT_NEWPASS2, &resp);
586         if (status == PAM_SUCCESS) {
587                 if (resp && resp->resp) {
588                         if (strcmp(trypass, resp->resp)) {
589                                 status = say(pamh,
590                                     PAM_ERROR_MSG, MESSAGE_MISTYPED);
591                                 if (status == PAM_SUCCESS) {
592                                         status = PAM_AUTHTOK_ERR;
593                                         retry_wanted = 1;
594                                 }
595                         }
596                         pwqc_drop_pam_reply(resp, 1);
597                 } else
598                         status = PAM_AUTHTOK_ERR;
599         }
600
601         if (status == PAM_SUCCESS)
602                 status = pam_set_item(pamh, PAM_AUTHTOK, trypass);
603
604         pwqc_overwrite_string(randompass);
605         pwqc_overwrite_string(trypass);
606         pwqc_drop_mem(trypass);
607
608         if (retry_wanted && --retries_left > 0) {
609                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
610                 if (status == PAM_SUCCESS)
611                         goto retry;
612         }
613
614         return status;
615 }
616
617 #ifdef PAM_MODULE_ENTRY
618 PAM_MODULE_ENTRY("pam_passwdqc");
619 #elif defined(PAM_STATIC)
620 struct pam_module _pam_passwdqc_modstruct = {
621         "pam_passwdqc",
622         NULL,
623         NULL,
624         NULL,
625         NULL,
626         NULL,
627         pam_sm_chauthtok
628 };
629 #endif