1 /* $NetBSD: pam_unix.c,v 1.13 2009/06/14 23:23:54 tonnerre Exp $ */
4 * Copyright 1998 Juniper Networks, Inc.
6 * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
9 * Portions of this software was developed for the FreeBSD Project by
10 * ThinkSec AS and NAI Labs, the Security Research Division of Network
11 * Associates, Inc. under DARPA/SPAWAR contract N66001-01-C-8035
12 * ("CBOSS"), as part of the DARPA CHATS research program.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * 3. The name of the author may not be used to endorse or promote
23 * products derived from this software without specific prior written
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
39 #include <sys/cdefs.h>
41 __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_unix/pam_unix.c,v 1.49 2004/02/10 10:13:21 des Exp $");
43 __RCSID("$NetBSD: pam_unix.c,v 1.13 2009/06/14 23:23:54 tonnerre Exp $");
47 #include <sys/types.h>
58 #include <login_cap.h>
67 #include <rpcsvc/ypclnt.h>
68 #include <rpcsvc/yppasswd.h>
72 #define PAM_SM_ACCOUNT
73 #define PAM_SM_PASSWORD
75 #include <security/pam_appl.h>
76 #include <security/pam_modules.h>
77 #include <security/pam_mod_misc.h>
80 * authentication management
84 pam_sm_authenticate(pam_handle_t
*pamh
, int flags __unused
,
85 int argc __unused
, const char *argv
[] __unused
)
88 struct passwd
*pwd
, pwres
;
90 const char *pass
, *user
, *realpw
;
94 if (openpam_get_option(pamh
, PAM_OPT_AUTH_AS_SELF
)) {
95 (void) getpwnam_r(getlogin(), &pwres
, pwbuf
, sizeof(pwbuf
),
98 retval
= pam_get_user(pamh
, &user
, NULL
);
99 if (retval
!= PAM_SUCCESS
)
101 PAM_LOG("Got user: %s", user
);
102 (void) getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
);
106 PAM_LOG("Doing real authentication");
107 realpw
= pwd
->pw_passwd
;
108 if (realpw
[0] == '\0') {
109 if (!(flags
& PAM_DISALLOW_NULL_AUTHTOK
) &&
110 openpam_get_option(pamh
, PAM_OPT_NULLOK
))
111 return (PAM_SUCCESS
);
114 lc
= login_getpwclass(pwd
);
116 PAM_LOG("Doing dummy authentication");
118 lc
= login_getclass(NULL
);
120 retval
= pam_get_authtok(pamh
, PAM_AUTHTOK
, &pass
, NULL
);
122 if (retval
!= PAM_SUCCESS
)
124 PAM_LOG("Got password");
125 if (strcmp(crypt(pass
, realpw
), realpw
) == 0)
126 return (PAM_SUCCESS
);
128 PAM_VERBOSE_ERROR("UNIX authentication refused");
129 return (PAM_AUTH_ERR
);
134 pam_sm_setcred(pam_handle_t
*pamh __unused
, int flags __unused
,
135 int argc __unused
, const char *argv
[] __unused
)
138 return (PAM_SUCCESS
);
146 pam_sm_acct_mgmt(pam_handle_t
*pamh
, int flags __unused
,
147 int argc __unused
, const char *argv
[] __unused
)
149 struct passwd
*pwd
, pwres
;
157 retval
= pam_get_user(pamh
, &user
, NULL
);
158 if (retval
!= PAM_SUCCESS
)
162 getpwnam_r(user
, &pwres
, pwbuf
, sizeof(pwbuf
), &pwd
) != 0 ||
164 return (PAM_SERVICE_ERR
);
166 PAM_LOG("Got user: %s", user
);
168 if (*pwd
->pw_passwd
== '\0' &&
169 (flags
& PAM_DISALLOW_NULL_AUTHTOK
) != 0)
170 return (PAM_NEW_AUTHTOK_REQD
);
172 lc
= login_getpwclass(pwd
);
174 PAM_LOG("Unable to get login class for user %s", user
);
175 return (PAM_SERVICE_ERR
);
178 PAM_LOG("Got login_cap");
180 if (pwd
->pw_change
|| pwd
->pw_expire
)
181 (void) gettimeofday(&now
, NULL
);
183 warntime
= (time_t)login_getcaptime(lc
, "password-warn",
184 (quad_t
)(_PASSWORD_WARNDAYS
* SECSPERDAY
),
185 (quad_t
)(_PASSWORD_WARNDAYS
* SECSPERDAY
));
188 * Check pw_expire before pw_change - no point in letting the
189 * user change the password on an expired account.
192 if (pwd
->pw_expire
) {
193 if (now
.tv_sec
>= pwd
->pw_expire
) {
195 return (PAM_ACCT_EXPIRED
);
196 } else if (pwd
->pw_expire
- now
.tv_sec
< warntime
&&
197 (flags
& PAM_SILENT
) == 0) {
198 pam_error(pamh
, "Warning: your account expires on %s",
199 ctime(&pwd
->pw_expire
));
203 if (pwd
->pw_change
) {
204 /* XXX How to handle _PASSWORD_CHGNOW? --thorpej */
205 if (now
.tv_sec
>= pwd
->pw_change
) {
207 return (PAM_NEW_AUTHTOK_REQD
);
208 } else if (pwd
->pw_change
- now
.tv_sec
< warntime
&&
209 (flags
& PAM_SILENT
) == 0) {
210 pam_error(pamh
, "Warning: your password expires on %s",
211 ctime(&pwd
->pw_change
));
217 return (PAM_SUCCESS
);
224 * Helper function; check that a user exists in the NIS
228 yp_check_user(const char *domain
, const char *user
)
234 reason
= yp_match(domain
, "passwd.byname", user
, (int)strlen(user
),
247 yp_set_password(pam_handle_t
*pamh
, struct passwd
*opwd
,
248 struct passwd
*pwd
, const char *old_pass
, const char *domain
)
251 int r
, rpcport
, status
;
252 struct yppasswd yppwd
;
255 int retval
= PAM_SERVICE_ERR
;
259 * Find the master for the passwd map; it should be running
262 if ((r
= yp_master(domain
, "passwd.byname", &master
)) != 0) {
263 pam_error(pamh
, "Can't find master NIS server. Reason: %s",
265 return (PAM_SERVICE_ERR
);
269 * Ask the portmapper for the port of rpc.yppasswdd.
271 if ((rpcport
= getrpcport(master
, YPPASSWDPROG
,
272 YPPASSWDPROC_UPDATE
, IPPROTO_UDP
)) == 0) {
274 "Master NIS server not runing yppasswd daemon.\n\t"
275 "Can't change NIS password.");
276 return (PAM_SERVICE_ERR
);
280 * Be sure the port is privileged.
282 if (rpcport
>= IPPORT_RESERVED
) {
283 pam_error(pamh
, "yppasswd daemon is on an invalid port.");
284 return (PAM_SERVICE_ERR
);
288 if (uid
!= 0 && uid
!= pwd
->pw_uid
) {
289 pam_error(pamh
, "You may only change your own password: %s",
291 return (PAM_SERVICE_ERR
);
295 * Fill in the yppasswd structure for yppasswdd.
297 memset(&yppwd
, 0, sizeof(yppwd
));
298 yppwd
.oldpass
= strdup(old_pass
);
299 if ((yppwd
.newpw
.pw_passwd
= strdup(pwd
->pw_passwd
)) == NULL
)
301 if ((yppwd
.newpw
.pw_name
= strdup(pwd
->pw_name
)) == NULL
)
303 yppwd
.newpw
.pw_uid
= pwd
->pw_uid
;
304 yppwd
.newpw
.pw_gid
= pwd
->pw_gid
;
305 if ((yppwd
.newpw
.pw_gecos
= strdup(pwd
->pw_gecos
)) == NULL
)
307 if ((yppwd
.newpw
.pw_dir
= strdup(pwd
->pw_dir
)) == NULL
)
309 if ((yppwd
.newpw
.pw_shell
= strdup(pwd
->pw_shell
)) == NULL
)
312 client
= clnt_create(master
, YPPASSWDPROG
, YPPASSWDVERS
, "udp");
313 if (client
== NULL
) {
314 pam_error(pamh
, "Can't contact yppasswdd on %s: Reason: %s",
315 master
, yperr_string(YPERR_YPBIND
));
319 client
->cl_auth
= authunix_create_default();
322 r
= clnt_call(client
, YPPASSWDPROC_UPDATE
,
323 xdr_yppasswd
, &yppwd
, xdr_int
, &status
, tv
);
325 pam_error(pamh
, "RPC to yppasswdd failed.");
327 pam_error(pamh
, "Couldn't change NIS password.");
329 pam_info(pamh
, "The NIS password has been changed on %s, "
330 "the master NIS passwd server.", master
);
331 retval
= PAM_SUCCESS
;
335 if (yppwd
.oldpass
!= NULL
)
337 if (yppwd
.newpw
.pw_passwd
!= NULL
)
338 free(yppwd
.newpw
.pw_passwd
);
339 if (yppwd
.newpw
.pw_name
!= NULL
)
340 free(yppwd
.newpw
.pw_name
);
341 if (yppwd
.newpw
.pw_gecos
!= NULL
)
342 free(yppwd
.newpw
.pw_gecos
);
343 if (yppwd
.newpw
.pw_dir
!= NULL
)
344 free(yppwd
.newpw
.pw_dir
);
345 if (yppwd
.newpw
.pw_shell
!= NULL
)
346 free(yppwd
.newpw
.pw_shell
);
350 pam_error(pamh
, "memory allocation failure");
356 local_set_password(pam_handle_t
*pamh
, struct passwd
*opwd
,
365 pam_error(pamh
, "The password file is busy, waiting...");
368 pam_error(pamh
, "The password file is still busy, "
370 return (PAM_SERVICE_ERR
);
374 pfd
= open(_PATH_MASTERPASSWD
, O_RDONLY
, 0);
376 pam_error(pamh
, "%s: %s", _PATH_MASTERPASSWD
, strerror(errno
));
378 return (PAM_SERVICE_ERR
);
381 if (pw_copyx(pfd
, tfd
, pwd
, opwd
, errbuf
, sizeof(errbuf
)) == 0) {
382 pam_error(pamh
, "Unable to update password entry: %s",
385 return (PAM_SERVICE_ERR
);
388 if (pw_mkdb(pwd
->pw_name
, opwd
->pw_change
== pwd
->pw_change
) < 0) {
389 pam_error(pamh
, "Unable to rebuild local password database.");
391 return (PAM_SERVICE_ERR
);
394 return (PAM_SUCCESS
);
398 * password management
400 * standard Unix and NIS password changing
404 pam_sm_chauthtok(pam_handle_t
*pamh
, int flags
,
405 int argc __unused
, const char *argv
[] __unused
)
407 struct passwd
*pwd
, new_pwd
, old_pwd
;
409 const char *user
, *passwd_db
, *new_pass
, *old_pass
, *p
;
410 int retval
, tries
, min_pw_len
= 0, pw_expiry
= 0;
411 char salt
[_PASSWORD_LEN
+1];
412 char old_pwbuf
[1024];
419 if (openpam_get_option(pamh
, PAM_OPT_AUTH_AS_SELF
)) {
420 if ((user
= getlogin()) == NULL
) {
421 pam_error(pamh
, "Unable to determine user.");
422 return (PAM_SERVICE_ERR
);
424 (void) getpwnam_r(user
, &old_pwd
, old_pwbuf
,
425 sizeof(old_pwbuf
), &pwd
);
427 retval
= pam_get_user(pamh
, &user
, NULL
);
428 if (retval
!= PAM_SUCCESS
)
430 (void) getpwnam_r(user
, &old_pwd
, old_pwbuf
,
431 sizeof(old_pwbuf
), &pwd
);
435 return (PAM_AUTHTOK_RECOVERY_ERR
);
437 PAM_LOG("Got user: %s", user
);
440 * Determine which password type we're going to change, and
443 * NOTE: domain does not need to be freed; its storage is
444 * allocated statically in libc.
446 passwd_db
= openpam_get_option(pamh
, "passwd_db");
447 if (passwd_db
== NULL
) {
449 /* Prefer YP, if configured. */
450 if (_yp_check(NULL
)) {
451 /* If _yp_check() succeeded, then this must. */
452 if ((r
= yp_get_default_domain(&domain
)) != 0) {
454 "Unable to get NIS domain, reason: %s",
456 return (PAM_SERVICE_ERR
);
458 if (yp_check_user(domain
, user
))
462 /* Otherwise we always use local files. */
463 if (passwd_db
== NULL
) {
464 /* XXX Any validation to do here? */
468 if ((retval
= openpam_set_option(pamh
, "passwd_db",
469 passwd_db
)) != PAM_SUCCESS
) {
473 /* Check to see if the specified password DB is usable. */
475 if (strcmp(passwd_db
, "nis") == 0) {
476 if (_yp_check(NULL
) == 0) {
477 pam_error(pamh
, "NIS not in use.");
478 return (PAM_SERVICE_ERR
);
480 if ((r
= yp_get_default_domain(&domain
)) != 0) {
482 "Unable to get NIS domain, reason: %s",
484 return (PAM_SERVICE_ERR
);
486 if (yp_check_user(domain
, user
) == 0) {
488 "User %s does not exist in NIS.", user
);
489 return (PAM_USER_UNKNOWN
);
491 goto known_passwd_db
;
494 if (strcmp(passwd_db
, "files") == 0) {
495 /* XXX Any validation to do here? */
496 goto known_passwd_db
;
498 pam_error(pamh
, "Unknown Unix password DB: %s", passwd_db
);
499 return (PAM_SERVICE_ERR
);
503 if (flags
& PAM_PRELIM_CHECK
) {
504 PAM_LOG("PRELIM round");
506 if (strcmp(passwd_db
, "files") == 0) {
508 /* Root doesn't need the old password. */
509 return (pam_set_item(pamh
, PAM_OLDAUTHTOK
, ""));
512 * Apparently we're not root, so let's forbid editing
514 * XXX Check for some flag to indicate if this
515 * XXX is the desired behavior.
517 if (pwd
->pw_uid
== 0)
518 return (PAM_PERM_DENIED
);
521 if (pwd
->pw_passwd
[0] == '\0') {
524 * XXX Are we giviing too much away by not prompting
525 * XXX for a password?
526 * XXX Check PAM_DISALLOW_NULL_AUTHTOK
528 return (pam_set_item(pamh
, PAM_OLDAUTHTOK
, ""));
530 retval
= pam_get_authtok(pamh
, PAM_OLDAUTHTOK
,
532 if (retval
!= PAM_SUCCESS
)
534 if (strcmp(crypt(old_pass
, pwd
->pw_passwd
),
535 pwd
->pw_passwd
) != 0)
536 return (PAM_PERM_DENIED
);
537 return (PAM_SUCCESS
);
541 if (flags
& PAM_UPDATE_AUTHTOK
) {
542 char option
[LINE_MAX
], *key
, *opt
;
544 PAM_LOG("UPDATE round");
546 if ((lc
= login_getclass(pwd
->pw_class
)) != NULL
) {
547 min_pw_len
= (int) login_getcapnum(lc
,
548 "minpasswordlen", (quad_t
)0, (quad_t
)0);
549 pw_expiry
= (int) login_getcapnum(lc
,
550 "passwordtime", (quad_t
)0, (quad_t
)0);
554 retval
= pam_get_authtok(pamh
, PAM_OLDAUTHTOK
, &old_pass
, NULL
);
555 if (retval
!= PAM_SUCCESS
)
558 /* Get the new password. */
560 retval
= pam_get_authtok(pamh
, PAM_AUTHTOK
, &new_pass
,
562 if (retval
== PAM_TRY_AGAIN
) {
564 "Mismatch; try again, EOF to quit.");
567 if (retval
!= PAM_SUCCESS
) {
568 PAM_VERBOSE_ERROR("Unable to get new password");
571 /* Successfully got new password. */
572 if (new_pass
[0] == '\0') {
573 pam_info(pamh
, "Password unchanged.");
574 return (PAM_SUCCESS
);
576 if (min_pw_len
> 0 && strlen(new_pass
) < (size_t)min_pw_len
) {
577 pam_error(pamh
, "Password is too short.");
580 if (strlen(new_pass
) <= 5 && ++tries
< 2) {
582 "Please enter a longer password.");
585 for (p
= new_pass
; *p
&& islower((unsigned char)*p
); ++p
);
586 if (!*p
&& ++tries
< 2) {
588 "Please don't use an all-lower case "
589 "password.\nUnusual capitalization, "
590 "control characters or digits are "
594 /* Password is OK. */
597 pam_set_item(pamh
, PAM_AUTHTOK
, NULL
);
599 pw_getpwconf(option
, sizeof(option
), pwd
,
601 strcmp(passwd_db
, "nis") == 0 ? "ypcipher" :
605 key
= strsep(&opt
, ",");
607 if (pw_gensalt(salt
, _PASSWORD_LEN
, key
, opt
) == -1) {
608 pam_error(pamh
, "Couldn't generate salt.");
609 return (PAM_SERVICE_ERR
);
614 pwd
->pw_passwd
= crypt(new_pass
, salt
);
615 pwd
->pw_change
= pw_expiry
? pw_expiry
+ time(NULL
) : 0;
617 retval
= PAM_SERVICE_ERR
;
618 if (strcmp(passwd_db
, "files") == 0)
619 retval
= local_set_password(pamh
, &old_pwd
, pwd
);
621 if (strcmp(passwd_db
, "nis") == 0)
622 retval
= yp_set_password(pamh
, &old_pwd
, pwd
, old_pass
,
628 PAM_LOG("Illegal flags argument");
632 PAM_MODULE_ENTRY("pam_unix");