]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/pam_modules/pam_passwdqc/pam_passwdqc.c
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.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 500
6 #define _XOPEN_SOURCE_EXTENDED
7 #define _XOPEN_VERSION 500
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         struct pam_conv *conv;
136         struct pam_message msg, *pmsg;
137         int status;
138
139         status = pam_get_item(pamh, PAM_CONV, (pam_item_t *)&conv);
140         if (status != PAM_SUCCESS)
141                 return status;
142
143         pmsg = &msg;
144         msg.msg_style = style;
145         msg.msg = text;
146
147         *resp = NULL;
148         return conv->conv(1, (lo_const struct pam_message **)&pmsg, resp,
149             conv->appdata_ptr);
150 }
151
152 #ifdef __GNUC__
153 __attribute__ ((format (printf, 3, 4)))
154 #endif
155 static int say(pam_handle_t *pamh, int style, const char *format, ...)
156 {
157         va_list args;
158         char buffer[0x800];
159         int needed;
160         struct pam_response *resp;
161         int status;
162
163         va_start(args, format);
164         needed = vsnprintf(buffer, sizeof(buffer), format, args);
165         va_end(args);
166
167         if ((unsigned int)needed < sizeof(buffer)) {
168                 status = converse(pamh, style, buffer, &resp);
169                 _pam_overwrite(buffer);
170         } else {
171                 status = PAM_ABORT;
172                 memset(buffer, 0, sizeof(buffer));
173         }
174
175         return status;
176 }
177
178 static int check_max(params_t *params, pam_handle_t *pamh, const char *newpass)
179 {
180         if ((int)strlen(newpass) > params->qc.max) {
181                 if (params->qc.max != 8) {
182                         say(pamh, PAM_ERROR_MSG, MESSAGE_TOOLONG);
183                         return -1;
184                 }
185                 say(pamh, PAM_TEXT_INFO, MESSAGE_TRUNCATED);
186         }
187
188         return 0;
189 }
190
191 static int parse(params_t *params, pam_handle_t *pamh,
192     int argc, const char **argv)
193 {
194         const char *p;
195         char *e;
196         int i;
197         unsigned long v;
198
199         while (argc) {
200                 if (!strncmp(*argv, "min=", 4)) {
201                         p = *argv + 4;
202                         for (i = 0; i < 5; i++) {
203                                 if (!strncmp(p, "disabled", 8)) {
204                                         v = INT_MAX;
205                                         p += 8;
206                                 } else {
207                                         v = strtoul(p, &e, 10);
208                                         p = e;
209                                 }
210                                 if (i < 4 && *p++ != ',') break;
211                                 if (v > INT_MAX) break;
212                                 if (i && (int)v > params->qc.min[i - 1]) break;
213                                 params->qc.min[i] = v;
214                         }
215                         if (*p) break;
216                 } else
217                 if (!strncmp(*argv, "max=", 4)) {
218                         v = strtoul(*argv + 4, &e, 10);
219                         if (*e || v < 8 || v > INT_MAX) break;
220                         params->qc.max = v;
221                 } else
222                 if (!strncmp(*argv, "passphrase=", 11)) {
223                         v = strtoul(*argv + 11, &e, 10);
224                         if (*e || v > INT_MAX) break;
225                         params->qc.passphrase_words = v;
226                 } else
227                 if (!strncmp(*argv, "match=", 6)) {
228                         v = strtoul(*argv + 6, &e, 10);
229                         if (*e || v > INT_MAX) break;
230                         params->qc.match_length = v;
231                 } else
232                 if (!strncmp(*argv, "similar=", 8)) {
233                         if (!strcmp(*argv + 8, "permit"))
234                                 params->qc.similar_deny = 0;
235                         else
236                         if (!strcmp(*argv + 8, "deny"))
237                                 params->qc.similar_deny = 1;
238                         else
239                                 break;
240                 } else
241                 if (!strncmp(*argv, "random=", 7)) {
242                         v = strtoul(*argv + 7, &e, 10);
243                         if (!strcmp(e, ",only")) {
244                                 e += 5;
245                                 params->qc.min[4] = INT_MAX;
246                         }
247                         if (*e || v > INT_MAX) break;
248                         params->qc.random_bits = v;
249                 } else
250                 if (!strncmp(*argv, "enforce=", 8)) {
251                         params->flags &= ~F_ENFORCE_MASK;
252                         if (!strcmp(*argv + 8, "users"))
253                                 params->flags |= F_ENFORCE_USERS;
254                         else
255                         if (!strcmp(*argv + 8, "everyone"))
256                                 params->flags |= F_ENFORCE_EVERYONE;
257                         else
258                         if (strcmp(*argv + 8, "none"))
259                                 break;
260                 } else
261                 if (!strcmp(*argv, "non-unix")) {
262                         if (params->flags & F_CHECK_OLDAUTHTOK) break;
263                         params->flags |= F_NON_UNIX;
264                 } else
265                 if (!strncmp(*argv, "retry=", 6)) {
266                         v = strtoul(*argv + 6, &e, 10);
267                         if (*e || v > INT_MAX) break;
268                         params->retry = v;
269                 } else
270                 if (!strncmp(*argv, "ask_oldauthtok", 14)) {
271                         params->flags &= ~F_ASK_OLDAUTHTOK_MASK;
272                         if (params->flags & F_USE_FIRST_PASS) break;
273                         if (!strcmp(*argv + 14, "=update"))
274                                 params->flags |= F_ASK_OLDAUTHTOK_UPDATE;
275                         else
276                         if (!(*argv)[14])
277                                 params->flags |= F_ASK_OLDAUTHTOK_PRELIM;
278                         else
279                                 break;
280                 } else
281                 if (!strcmp(*argv, "check_oldauthtok")) {
282                         if (params->flags & F_NON_UNIX) break;
283                         params->flags |= F_CHECK_OLDAUTHTOK;
284                 } else
285                 if (!strcmp(*argv, "use_first_pass")) {
286                         if (params->flags & F_ASK_OLDAUTHTOK_MASK) break;
287                         params->flags |= F_USE_FIRST_PASS | F_USE_AUTHTOK;
288                 } else
289                 if (!strcmp(*argv, "use_authtok")) {
290                         params->flags |= F_USE_AUTHTOK;
291                 } else
292                         break;
293                 argc--; argv++;
294         }
295
296         if (argc) {
297                 say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
298                     MESSAGE_MISCONFIGURED : MESSAGE_INVALID_OPTION, *argv);
299                 return PAM_ABORT;
300         }
301
302         return PAM_SUCCESS;
303 }
304
305 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags,
306     int argc, const char **argv)
307 {
308         params_t params;
309         struct pam_response *resp;
310         struct passwd *pw, fake_pw;
311 #ifdef HAVE_SHADOW
312         struct spwd *spw;
313 #endif
314         char *user, *oldpass, *newpass, *randompass;
315         const char *reason;
316         int ask_oldauthtok;
317         int randomonly, enforce, retries_left, retry_wanted;
318         int status;
319
320         params = defaults;
321         status = parse(&params, pamh, argc, argv);
322         if (status != PAM_SUCCESS)
323                 return status;
324
325         ask_oldauthtok = 0;
326         if (flags & PAM_PRELIM_CHECK) {
327                 if (params.flags & F_ASK_OLDAUTHTOK_PRELIM)
328                         ask_oldauthtok = 1;
329         } else
330         if (flags & PAM_UPDATE_AUTHTOK) {
331                 if (params.flags & F_ASK_OLDAUTHTOK_UPDATE)
332                         ask_oldauthtok = 1;
333         } else
334                 return PAM_SERVICE_ERR;
335
336         if (ask_oldauthtok && getuid() != 0) {
337                 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
338                     PROMPT_OLDPASS, &resp);
339
340                 if (status == PAM_SUCCESS) {
341                         if (resp && resp->resp) {
342                                 status = pam_set_item(pamh,
343                                     PAM_OLDAUTHTOK, resp->resp);
344                                 _pam_drop_reply(resp, 1);
345                         } else
346                                 status = PAM_AUTHTOK_RECOVERY_ERR;
347                 }
348
349                 if (status != PAM_SUCCESS)
350                         return status;
351         }
352
353         if (flags & PAM_PRELIM_CHECK)
354                 return status;
355
356         status = pam_get_item(pamh, PAM_USER, (pam_item_t *)&user);
357         if (status != PAM_SUCCESS)
358                 return status;
359
360         status = pam_get_item(pamh, PAM_OLDAUTHTOK, (pam_item_t *)&oldpass);
361         if (status != PAM_SUCCESS)
362                 return status;
363
364         if (params.flags & F_NON_UNIX) {
365                 pw = &fake_pw;
366                 pw->pw_name = user;
367                 pw->pw_gecos = "";
368         } else {
369                 pw = getpwnam(user);
370                 endpwent();
371                 if (!pw)
372                         return PAM_USER_UNKNOWN;
373                 if ((params.flags & F_CHECK_OLDAUTHTOK) && getuid() != 0) {
374                         if (!oldpass)
375                                 status = PAM_AUTH_ERR;
376                         else
377 #ifdef HAVE_SHADOW
378                         if (!strcmp(pw->pw_passwd, "x")) {
379                                 spw = getspnam(user);
380                                 endspent();
381                                 if (spw) {
382                                         if (strcmp(crypt(oldpass, spw->sp_pwdp),
383                                             spw->sp_pwdp))
384                                                 status = PAM_AUTH_ERR;
385                                         memset(spw->sp_pwdp, 0,
386                                             strlen(spw->sp_pwdp));
387                                 } else
388                                         status = PAM_AUTH_ERR;
389                         } else
390 #endif
391                         if (strcmp(crypt(oldpass, pw->pw_passwd),
392                             pw->pw_passwd))
393                                 status = PAM_AUTH_ERR;
394                 }
395                 memset(pw->pw_passwd, 0, strlen(pw->pw_passwd));
396                 if (status != PAM_SUCCESS)
397                         return status;
398         }
399
400         randomonly = params.qc.min[4] > params.qc.max;
401
402         if (getuid() != 0)
403                 enforce = params.flags & F_ENFORCE_USERS;
404         else
405                 enforce = params.flags & F_ENFORCE_ROOT;
406
407         if (params.flags & F_USE_AUTHTOK) {
408                 status = pam_get_item(pamh, PAM_AUTHTOK,
409                     (pam_item_t *)&newpass);
410                 if (status != PAM_SUCCESS)
411                         return status;
412                 if (!newpass || (check_max(&params, pamh, newpass) && enforce))
413                         return PAM_AUTHTOK_ERR;
414                 reason = _passwdqc_check(&params.qc, newpass, oldpass, pw);
415                 if (reason) {
416                         say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
417                         if (enforce)
418                                 status = PAM_AUTHTOK_ERR;
419                 }
420                 return status;
421         }
422
423         retries_left = params.retry;
424
425 retry:
426         retry_wanted = 0;
427
428         if (!randomonly &&
429             params.qc.passphrase_words && params.qc.min[2] <= params.qc.max)
430                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_BOTH);
431         else
432                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_INTRO_PASSWORD);
433         if (status != PAM_SUCCESS)
434                 return status;
435
436         if (!randomonly && params.qc.min[3] <= params.qc.min[4])
437                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_1,
438                     params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
439                     params.qc.min[3]);
440         else
441         if (!randomonly)
442                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSWORD_2,
443                     params.qc.min[3] == 8 || params.qc.min[3] == 11 ? "n" : "",
444                     params.qc.min[3],
445                     params.qc.min[4] == 8 || params.qc.min[4] == 11 ? "n" : "",
446                     params.qc.min[4]);
447         if (status != PAM_SUCCESS)
448                 return status;
449
450         if (!randomonly &&
451             params.qc.passphrase_words &&
452             params.qc.min[2] <= params.qc.max) {
453                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_EXPLAIN_PASSPHRASE,
454                     params.qc.passphrase_words,
455                     params.qc.min[2], params.qc.max);
456                 if (status != PAM_SUCCESS)
457                         return status;
458         }
459
460         randompass = _passwdqc_random(&params.qc);
461         if (randompass) {
462                 status = say(pamh, PAM_TEXT_INFO, randomonly ?
463                     MESSAGE_RANDOMONLY : MESSAGE_RANDOM, randompass);
464                 if (status != PAM_SUCCESS) {
465                         _pam_overwrite(randompass);
466                         randompass = NULL;
467                 }
468         } else
469         if (randomonly) {
470                 say(pamh, PAM_ERROR_MSG, getuid() != 0 ?
471                     MESSAGE_MISCONFIGURED : MESSAGE_RANDOMFAILED);
472                 return PAM_AUTHTOK_ERR;
473         }
474
475         status = converse(pamh, PAM_PROMPT_ECHO_OFF, PROMPT_NEWPASS1, &resp);
476         if (status == PAM_SUCCESS && (!resp || !resp->resp))
477                 status = PAM_AUTHTOK_ERR;
478
479         if (status != PAM_SUCCESS) {
480                 if (randompass) _pam_overwrite(randompass);
481                 return status;
482         }
483
484         newpass = strdup(resp->resp);
485
486         _pam_drop_reply(resp, 1);
487
488         if (!newpass) {
489                 if (randompass) _pam_overwrite(randompass);
490                 return PAM_AUTHTOK_ERR;
491         }
492
493         if (check_max(&params, pamh, newpass) && enforce) {
494                 status = PAM_AUTHTOK_ERR;
495                 retry_wanted = 1;
496         }
497
498         reason = NULL;
499         if (status == PAM_SUCCESS &&
500             (!randompass || !strstr(newpass, randompass)) &&
501             (randomonly ||
502             (reason = _passwdqc_check(&params.qc, newpass, oldpass, pw)))) {
503                 if (randomonly)
504                         say(pamh, PAM_ERROR_MSG, MESSAGE_NOTRANDOM);
505                 else
506                         say(pamh, PAM_ERROR_MSG, MESSAGE_WEAKPASS, reason);
507                 if (enforce) {
508                         status = PAM_AUTHTOK_ERR;
509                         retry_wanted = 1;
510                 }
511         }
512
513         if (status == PAM_SUCCESS)
514                 status = converse(pamh, PAM_PROMPT_ECHO_OFF,
515                     PROMPT_NEWPASS2, &resp);
516         if (status == PAM_SUCCESS) {
517                 if (resp && resp->resp) {
518                         if (strcmp(newpass, resp->resp)) {
519                                 status = say(pamh,
520                                     PAM_ERROR_MSG, MESSAGE_MISTYPED);
521                                 if (status == PAM_SUCCESS) {
522                                         status = PAM_AUTHTOK_ERR;
523                                         retry_wanted = 1;
524                                 }
525                         }
526                         _pam_drop_reply(resp, 1);
527                 } else
528                         status = PAM_AUTHTOK_ERR;
529         }
530
531         if (status == PAM_SUCCESS)
532                 status = pam_set_item(pamh, PAM_AUTHTOK, newpass);
533
534         if (randompass) _pam_overwrite(randompass);
535         _pam_overwrite(newpass);
536         free(newpass);
537
538         if (retry_wanted && --retries_left > 0) {
539                 status = say(pamh, PAM_TEXT_INFO, MESSAGE_RETRY);
540                 if (status == PAM_SUCCESS)
541                         goto retry;
542         }
543
544         return status;
545 }
546
547 #ifdef PAM_MODULE_ENTRY
548 PAM_MODULE_ENTRY("pam_passwdqc");
549 #elif defined(PAM_STATIC)
550 struct pam_module _pam_passwdqc_modstruct = {
551         "pam_passwdqc",
552         NULL,
553         NULL,
554         NULL,
555         NULL,
556         NULL,
557         pam_sm_chauthtok
558 };
559 #endif