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
630