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