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
converse(pam_handle_t * pamh,int style,lo_const char * text,struct pam_response ** resp)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
say(pam_handle_t * pamh,int style,const char * format,...)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
check_max(params_t * params,pam_handle_t * pamh,const char * newpass)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
parse(params_t * params,pam_handle_t * pamh,int argc,const char ** argv)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
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,const char ** argv)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(¶ms, 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(¶ms, pamh, curpass) && enforce))
422 return PAM_AUTHTOK_ERR;
423 reason = _passwdqc_check(¶ms.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(¶ms.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(¶ms, 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(¶ms.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
569