xref: /netbsd-src/lib/libpam/modules/pam_unix/pam_unix.c (revision 730fdfa60117e6ea810248d91452b941a29fb7a7)
1*730fdfa6Sandvar /*	$NetBSD: pam_unix.c,v 1.19 2022/10/26 22:09:37 andvar Exp $	*/
2bb62ec41Schristos 
36f11bdf1Schristos /*-
46f11bdf1Schristos  * Copyright 1998 Juniper Networks, Inc.
56f11bdf1Schristos  * All rights reserved.
66f11bdf1Schristos  * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
76f11bdf1Schristos  * All rights reserved.
86f11bdf1Schristos  *
96f11bdf1Schristos  * Portions of this software was developed for the FreeBSD Project by
106f11bdf1Schristos  * ThinkSec AS and NAI Labs, the Security Research Division of Network
116f11bdf1Schristos  * Associates, Inc.  under DARPA/SPAWAR contract N66001-01-C-8035
126f11bdf1Schristos  * ("CBOSS"), as part of the DARPA CHATS research program.
136f11bdf1Schristos  *
146f11bdf1Schristos  * Redistribution and use in source and binary forms, with or without
156f11bdf1Schristos  * modification, are permitted provided that the following conditions
166f11bdf1Schristos  * are met:
176f11bdf1Schristos  * 1. Redistributions of source code must retain the above copyright
186f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer.
196f11bdf1Schristos  * 2. Redistributions in binary form must reproduce the above copyright
206f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer in the
216f11bdf1Schristos  *    documentation and/or other materials provided with the distribution.
226f11bdf1Schristos  * 3. The name of the author may not be used to endorse or promote
236f11bdf1Schristos  *    products derived from this software without specific prior written
246f11bdf1Schristos  *    permission.
256f11bdf1Schristos  *
266f11bdf1Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
276f11bdf1Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
286f11bdf1Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
296f11bdf1Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
306f11bdf1Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
316f11bdf1Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
326f11bdf1Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
336f11bdf1Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
346f11bdf1Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
356f11bdf1Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
366f11bdf1Schristos  * SUCH DAMAGE.
376f11bdf1Schristos  */
386f11bdf1Schristos 
396f11bdf1Schristos #include <sys/cdefs.h>
40bb62ec41Schristos #ifdef __FreeBSD__
416f11bdf1Schristos __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_unix/pam_unix.c,v 1.49 2004/02/10 10:13:21 des Exp $");
42bb62ec41Schristos #else
43*730fdfa6Sandvar __RCSID("$NetBSD: pam_unix.c,v 1.19 2022/10/26 22:09:37 andvar Exp $");
44bb62ec41Schristos #endif
456f11bdf1Schristos 
466f11bdf1Schristos 
47bb62ec41Schristos #include <sys/types.h>
48bb62ec41Schristos 
49bb62ec41Schristos #include <ctype.h>
50bb62ec41Schristos #include <errno.h>
51bb62ec41Schristos #include <fcntl.h>
526f11bdf1Schristos #include <pwd.h>
53bb62ec41Schristos #include <grp.h>
54bb62ec41Schristos #include <limits.h>
556f11bdf1Schristos #include <stdlib.h>
566f11bdf1Schristos #include <string.h>
576f11bdf1Schristos #include <stdio.h>
58bb62ec41Schristos #include <login_cap.h>
59bb62ec41Schristos #include <time.h>
60bb62ec41Schristos #include <tzfile.h>
616f11bdf1Schristos #include <unistd.h>
626f11bdf1Schristos 
63bb62ec41Schristos #include <util.h>
646f11bdf1Schristos 
656f11bdf1Schristos #ifdef YP
66bb62ec41Schristos #include <rpc/rpc.h>
67bb62ec41Schristos #include <rpcsvc/ypclnt.h>
68bb62ec41Schristos #include <rpcsvc/yppasswd.h>
696f11bdf1Schristos #endif
706f11bdf1Schristos 
716f11bdf1Schristos #define PAM_SM_AUTH
726f11bdf1Schristos #define PAM_SM_ACCOUNT
736f11bdf1Schristos #define	PAM_SM_PASSWORD
746f11bdf1Schristos 
756f11bdf1Schristos #include <security/pam_appl.h>
766f11bdf1Schristos #include <security/pam_modules.h>
776f11bdf1Schristos #include <security/pam_mod_misc.h>
786f11bdf1Schristos 
796f11bdf1Schristos /*
806f11bdf1Schristos  * authentication management
816f11bdf1Schristos  */
826f11bdf1Schristos PAM_EXTERN int
83bb62ec41Schristos /*ARGSUSED*/
pam_sm_authenticate(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)846f11bdf1Schristos pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
856f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
866f11bdf1Schristos {
876f11bdf1Schristos 	login_cap_t *lc;
8859cbc9e2Sthorpej 	struct passwd *pwd, pwres;
896f11bdf1Schristos 	int retval;
90bb62ec41Schristos 	const char *pass, *user, *realpw;
9159cbc9e2Sthorpej 	char pwbuf[1024];
926f11bdf1Schristos 
9301cf9d02Slukem 	pwd = NULL;
946f11bdf1Schristos 	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
9559cbc9e2Sthorpej 		(void) getpwnam_r(getlogin(), &pwres, pwbuf, sizeof(pwbuf),
9659cbc9e2Sthorpej 				  &pwd);
976f11bdf1Schristos 	} else {
986f11bdf1Schristos 		retval = pam_get_user(pamh, &user, NULL);
996f11bdf1Schristos 		if (retval != PAM_SUCCESS)
1006f11bdf1Schristos 			return (retval);
101bb62ec41Schristos 		PAM_LOG("Got user: %s", user);
10259cbc9e2Sthorpej 		(void) getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd);
1036f11bdf1Schristos 	}
1046f11bdf1Schristos 
1056f11bdf1Schristos 	if (pwd != NULL) {
1066f11bdf1Schristos 		PAM_LOG("Doing real authentication");
1076f11bdf1Schristos 		realpw = pwd->pw_passwd;
1086f11bdf1Schristos 		if (realpw[0] == '\0') {
1096f11bdf1Schristos 			if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
1106f11bdf1Schristos 			    openpam_get_option(pamh, PAM_OPT_NULLOK))
1116f11bdf1Schristos 				return (PAM_SUCCESS);
1126f11bdf1Schristos 			realpw = "*";
1136f11bdf1Schristos 		}
1146f11bdf1Schristos 	} else {
1156f11bdf1Schristos 		PAM_LOG("Doing dummy authentication");
1166f11bdf1Schristos 		realpw = "*";
1176f11bdf1Schristos 	}
118fd65ca01Schristos 	lc = login_getpwclass(pwd);
119bb62ec41Schristos 	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, NULL);
1206f11bdf1Schristos 	login_close(lc);
1216f11bdf1Schristos 	if (retval != PAM_SUCCESS)
1226f11bdf1Schristos 		return (retval);
1236f11bdf1Schristos 	PAM_LOG("Got password");
1246f11bdf1Schristos 	if (strcmp(crypt(pass, realpw), realpw) == 0)
1256f11bdf1Schristos 		return (PAM_SUCCESS);
1266f11bdf1Schristos 
1276f11bdf1Schristos 	PAM_VERBOSE_ERROR("UNIX authentication refused");
1286f11bdf1Schristos 	return (PAM_AUTH_ERR);
1296f11bdf1Schristos }
1306f11bdf1Schristos 
1316f11bdf1Schristos PAM_EXTERN int
132bb62ec41Schristos /*ARGSUSED*/
pam_sm_setcred(pam_handle_t * pamh __unused,int flags __unused,int argc __unused,const char * argv[]__unused)1336f11bdf1Schristos pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
1346f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
1356f11bdf1Schristos {
1366f11bdf1Schristos 
1376f11bdf1Schristos 	return (PAM_SUCCESS);
1386f11bdf1Schristos }
1396f11bdf1Schristos 
1406f11bdf1Schristos /*
1416f11bdf1Schristos  * account management
1426f11bdf1Schristos  */
1436f11bdf1Schristos PAM_EXTERN int
144bb62ec41Schristos /*ARGSUSED*/
pam_sm_acct_mgmt(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)1456f11bdf1Schristos pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
1466f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
1476f11bdf1Schristos {
14859cbc9e2Sthorpej 	struct passwd *pwd, pwres;
149bb62ec41Schristos 	struct timeval now;
1506f11bdf1Schristos 	login_cap_t *lc;
1516f11bdf1Schristos 	time_t warntime;
1526f11bdf1Schristos 	int retval;
1536f11bdf1Schristos 	const char *user;
15459cbc9e2Sthorpej 	char pwbuf[1024];
1556f11bdf1Schristos 
1566f11bdf1Schristos 	retval = pam_get_user(pamh, &user, NULL);
1576f11bdf1Schristos 	if (retval != PAM_SUCCESS)
1586f11bdf1Schristos 		return (retval);
1596f11bdf1Schristos 
16059cbc9e2Sthorpej 	if (user == NULL ||
1612a62e4e1Schristos 	    getpwnam_r(user, &pwres, pwbuf, sizeof(pwbuf), &pwd) != 0 ||
1622a62e4e1Schristos 	    pwd == NULL)
1636f11bdf1Schristos 		return (PAM_SERVICE_ERR);
1646f11bdf1Schristos 
1656f11bdf1Schristos 	PAM_LOG("Got user: %s", user);
1666f11bdf1Schristos 
1676f11bdf1Schristos 	if (*pwd->pw_passwd == '\0' &&
1686f11bdf1Schristos 	    (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
1696f11bdf1Schristos 		return (PAM_NEW_AUTHTOK_REQD);
1706f11bdf1Schristos 
1716f11bdf1Schristos 	lc = login_getpwclass(pwd);
1726f11bdf1Schristos 	if (lc == NULL) {
1736f11bdf1Schristos 		PAM_LOG("Unable to get login class for user %s", user);
1746f11bdf1Schristos 		return (PAM_SERVICE_ERR);
1756f11bdf1Schristos 	}
1766f11bdf1Schristos 
1776f11bdf1Schristos 	PAM_LOG("Got login_cap");
1786f11bdf1Schristos 
1796f11bdf1Schristos 	if (pwd->pw_change || pwd->pw_expire)
180bb62ec41Schristos 		(void) gettimeofday(&now, NULL);
181bb62ec41Schristos 
182bb62ec41Schristos 	warntime = (time_t)login_getcaptime(lc, "password-warn",
183bb62ec41Schristos 	    (quad_t)(_PASSWORD_WARNDAYS * SECSPERDAY),
184bb62ec41Schristos 	    (quad_t)(_PASSWORD_WARNDAYS * SECSPERDAY));
1856f11bdf1Schristos 
1866f11bdf1Schristos 	/*
1876f11bdf1Schristos 	 * Check pw_expire before pw_change - no point in letting the
1886f11bdf1Schristos 	 * user change the password on an expired account.
1896f11bdf1Schristos 	 */
1906f11bdf1Schristos 
1916f11bdf1Schristos 	if (pwd->pw_expire) {
192bb62ec41Schristos 		if (now.tv_sec >= pwd->pw_expire) {
1936f11bdf1Schristos 			login_close(lc);
1946f11bdf1Schristos 			return (PAM_ACCT_EXPIRED);
195bb62ec41Schristos 		} else if (pwd->pw_expire - now.tv_sec < warntime &&
1966f11bdf1Schristos 		    (flags & PAM_SILENT) == 0) {
1976f11bdf1Schristos 			pam_error(pamh, "Warning: your account expires on %s",
1986f11bdf1Schristos 			    ctime(&pwd->pw_expire));
1996f11bdf1Schristos 		}
2006f11bdf1Schristos 	}
2016f11bdf1Schristos 
2026f11bdf1Schristos 	if (pwd->pw_change) {
203bb62ec41Schristos 		/* XXX How to handle _PASSWORD_CHGNOW?  --thorpej */
204bb62ec41Schristos 		if (now.tv_sec >= pwd->pw_change) {
205bb62ec41Schristos 			login_close(lc);
206bb62ec41Schristos 			return (PAM_NEW_AUTHTOK_REQD);
207bb62ec41Schristos 		} else if (pwd->pw_change - now.tv_sec < warntime &&
2086f11bdf1Schristos 		    (flags & PAM_SILENT) == 0) {
2096f11bdf1Schristos 			pam_error(pamh, "Warning: your password expires on %s",
2106f11bdf1Schristos 			    ctime(&pwd->pw_change));
2116f11bdf1Schristos 		}
2126f11bdf1Schristos 	}
2136f11bdf1Schristos 
2146f11bdf1Schristos 	login_close(lc);
2156f11bdf1Schristos 
216bb62ec41Schristos 	return (PAM_SUCCESS);
217bb62ec41Schristos }
218bb62ec41Schristos 
219bb62ec41Schristos #ifdef YP
220bb62ec41Schristos /*
221bb62ec41Schristos  * yp_check_user:
222bb62ec41Schristos  *
223bb62ec41Schristos  *	Helper function; check that a user exists in the NIS
224bb62ec41Schristos  *	password map.
225bb62ec41Schristos  */
226bb62ec41Schristos static int
yp_check_user(const char * domain,const char * user)227bb62ec41Schristos yp_check_user(const char *domain, const char *user)
228bb62ec41Schristos {
229bb62ec41Schristos 	char *val;
230bb62ec41Schristos 	int reason, vallen;
231bb62ec41Schristos 
232bb62ec41Schristos 	val = NULL;
233bb62ec41Schristos 	reason = yp_match(domain, "passwd.byname", user, (int)strlen(user),
234bb62ec41Schristos 	    &val, &vallen);
235bb62ec41Schristos 	if (reason != 0) {
236bb62ec41Schristos 		if (val != NULL)
237bb62ec41Schristos 			free(val);
238bb62ec41Schristos 		return (0);
239bb62ec41Schristos 	}
240bb62ec41Schristos 	free(val);
241bb62ec41Schristos 	return (1);
242bb62ec41Schristos }
243bb62ec41Schristos 
244bb62ec41Schristos static int
245bb62ec41Schristos /*ARGSUSED*/
yp_set_password(pam_handle_t * pamh,struct passwd * opwd,struct passwd * pwd,const char * old_pass,const char * domain)246bb62ec41Schristos yp_set_password(pam_handle_t *pamh, struct passwd *opwd,
247bb62ec41Schristos     struct passwd *pwd, const char *old_pass, const char *domain)
248bb62ec41Schristos {
249bb62ec41Schristos 	char *master;
250bb62ec41Schristos 	int r, rpcport, status;
251ed9315c5Sjoerg 	enum clnt_stat r2;
252bb62ec41Schristos 	struct yppasswd yppwd;
253bb62ec41Schristos 	CLIENT *client;
254bb62ec41Schristos 	uid_t uid;
255bb62ec41Schristos 	int retval = PAM_SERVICE_ERR;
256bb62ec41Schristos 	struct timeval tv;
257bb62ec41Schristos 
258bb62ec41Schristos 	/*
259bb62ec41Schristos 	 * Find the master for the passwd map; it should be running
260bb62ec41Schristos 	 * rpc.yppasswdd.
261bb62ec41Schristos 	 */
262bb62ec41Schristos 	if ((r = yp_master(domain, "passwd.byname", &master)) != 0) {
263bb62ec41Schristos 		pam_error(pamh, "Can't find master NIS server.  Reason: %s",
264bb62ec41Schristos 		    yperr_string(r));
265bb62ec41Schristos 		return (PAM_SERVICE_ERR);
266bb62ec41Schristos 	}
267bb62ec41Schristos 
268bb62ec41Schristos 	/*
269bb62ec41Schristos 	 * Ask the portmapper for the port of rpc.yppasswdd.
270bb62ec41Schristos 	 */
271bb62ec41Schristos 	if ((rpcport = getrpcport(master, YPPASSWDPROG,
272bb62ec41Schristos 				  YPPASSWDPROC_UPDATE, IPPROTO_UDP)) == 0) {
273bb62ec41Schristos 		pam_error(pamh,
27475d2abaeSandvar 		    "Master NIS server not running yppasswd daemon.\n\t"
275bb62ec41Schristos 		    "Can't change NIS password.");
276bb62ec41Schristos 		return (PAM_SERVICE_ERR);
277bb62ec41Schristos 	}
278bb62ec41Schristos 
279bb62ec41Schristos 	/*
280bb62ec41Schristos 	 * Be sure the port is privileged.
281bb62ec41Schristos 	 */
282bb62ec41Schristos 	if (rpcport >= IPPORT_RESERVED) {
283bb62ec41Schristos 		pam_error(pamh, "yppasswd daemon is on an invalid port.");
284bb62ec41Schristos 		return (PAM_SERVICE_ERR);
285bb62ec41Schristos 	}
286bb62ec41Schristos 
287bb62ec41Schristos 	uid = getuid();
288bb62ec41Schristos 	if (uid != 0 && uid != pwd->pw_uid) {
289bb62ec41Schristos 		pam_error(pamh, "You may only change your own password: %s",
290bb62ec41Schristos 		    strerror(EACCES));
291bb62ec41Schristos 		return (PAM_SERVICE_ERR);
292bb62ec41Schristos 	}
293bb62ec41Schristos 
294bb62ec41Schristos 	/*
295bb62ec41Schristos 	 * Fill in the yppasswd structure for yppasswdd.
296bb62ec41Schristos 	 */
297bb62ec41Schristos 	memset(&yppwd, 0, sizeof(yppwd));
2980a40f744Sthorpej 	yppwd.oldpass = strdup(old_pass);
299bb62ec41Schristos 	if ((yppwd.newpw.pw_passwd = strdup(pwd->pw_passwd)) == NULL)
300bb62ec41Schristos 		goto malloc_failure;
301bb62ec41Schristos 	if ((yppwd.newpw.pw_name = strdup(pwd->pw_name)) == NULL)
302bb62ec41Schristos 		goto malloc_failure;
3039778b180Schristos 	yppwd.newpw.pw_uid = (int)pwd->pw_uid;
3049778b180Schristos 	yppwd.newpw.pw_gid = (int)pwd->pw_gid;
305bb62ec41Schristos 	if ((yppwd.newpw.pw_gecos = strdup(pwd->pw_gecos)) == NULL)
306bb62ec41Schristos 		goto malloc_failure;
307bb62ec41Schristos 	if ((yppwd.newpw.pw_dir = strdup(pwd->pw_dir)) == NULL)
308bb62ec41Schristos 		goto malloc_failure;
309bb62ec41Schristos 	if ((yppwd.newpw.pw_shell = strdup(pwd->pw_shell)) == NULL)
310bb62ec41Schristos 		goto malloc_failure;
311bb62ec41Schristos 
312bb62ec41Schristos 	client = clnt_create(master, YPPASSWDPROG, YPPASSWDVERS, "udp");
313bb62ec41Schristos 	if (client == NULL) {
314bb62ec41Schristos 		pam_error(pamh, "Can't contact yppasswdd on %s: Reason: %s",
315bb62ec41Schristos 		    master, yperr_string(YPERR_YPBIND));
316bb62ec41Schristos 		goto out;
317bb62ec41Schristos 	}
318bb62ec41Schristos 
319bb62ec41Schristos 	client->cl_auth = authunix_create_default();
320bb62ec41Schristos 	tv.tv_sec = 2;
321bb62ec41Schristos 	tv.tv_usec = 0;
322ed9315c5Sjoerg 	r2 = clnt_call(client, YPPASSWDPROC_UPDATE,
323bb62ec41Schristos 	    xdr_yppasswd, &yppwd, xdr_int, &status, tv);
324ed9315c5Sjoerg 	if (r2 != RPC_SUCCESS)
325bb62ec41Schristos 		pam_error(pamh, "RPC to yppasswdd failed.");
326bb62ec41Schristos 	else if (status)
327bb62ec41Schristos 		pam_error(pamh, "Couldn't change NIS password.");
328bb62ec41Schristos 	else {
329bb62ec41Schristos 		pam_info(pamh, "The NIS password has been changed on %s, "
330bb62ec41Schristos 		    "the master NIS passwd server.", master);
331bb62ec41Schristos 		retval = PAM_SUCCESS;
332bb62ec41Schristos 	}
333bb62ec41Schristos 
334bb62ec41Schristos  out:
3350a40f744Sthorpej 	if (yppwd.oldpass != NULL)
3360a40f744Sthorpej 		free(yppwd.oldpass);
337bb62ec41Schristos 	if (yppwd.newpw.pw_passwd != NULL)
338bb62ec41Schristos 		free(yppwd.newpw.pw_passwd);
339bb62ec41Schristos 	if (yppwd.newpw.pw_name != NULL)
340bb62ec41Schristos 		free(yppwd.newpw.pw_name);
341bb62ec41Schristos 	if (yppwd.newpw.pw_gecos != NULL)
342bb62ec41Schristos 		free(yppwd.newpw.pw_gecos);
343bb62ec41Schristos 	if (yppwd.newpw.pw_dir != NULL)
344bb62ec41Schristos 		free(yppwd.newpw.pw_dir);
345bb62ec41Schristos 	if (yppwd.newpw.pw_shell != NULL)
346bb62ec41Schristos 		free(yppwd.newpw.pw_shell);
3476f11bdf1Schristos 	return (retval);
348bb62ec41Schristos 
349bb62ec41Schristos  malloc_failure:
350bb62ec41Schristos 	pam_error(pamh, "memory allocation failure");
351bb62ec41Schristos 	goto out;
352bb62ec41Schristos }
353bb62ec41Schristos #endif /* YP */
354bb62ec41Schristos 
355bb62ec41Schristos static int
local_set_password(pam_handle_t * pamh,struct passwd * opwd,struct passwd * pwd)356bb62ec41Schristos local_set_password(pam_handle_t *pamh, struct passwd *opwd,
357bb62ec41Schristos     struct passwd *pwd)
358bb62ec41Schristos {
359bb62ec41Schristos 	char errbuf[200];
360bb62ec41Schristos 	int tfd, pfd;
361bb62ec41Schristos 
362bb62ec41Schristos 	pw_init();
363bb62ec41Schristos 	tfd = pw_lock(0);
364bb62ec41Schristos 	if (tfd < 0) {
365bb62ec41Schristos 		pam_error(pamh, "The password file is busy, waiting...");
366bb62ec41Schristos 		tfd = pw_lock(10);
367bb62ec41Schristos 		if (tfd < 0) {
368bb62ec41Schristos 			pam_error(pamh, "The password file is still busy, "
369bb62ec41Schristos 			    "try again later.");
370bb62ec41Schristos 			return (PAM_SERVICE_ERR);
371bb62ec41Schristos 		}
372bb62ec41Schristos 	}
373bb62ec41Schristos 
374bb62ec41Schristos 	pfd = open(_PATH_MASTERPASSWD, O_RDONLY, 0);
375bb62ec41Schristos 	if (pfd < 0) {
376bb62ec41Schristos 		pam_error(pamh, "%s: %s", _PATH_MASTERPASSWD, strerror(errno));
377bb62ec41Schristos 		pw_abort();
378bb62ec41Schristos 		return (PAM_SERVICE_ERR);
379bb62ec41Schristos 	}
380bb62ec41Schristos 
381bb62ec41Schristos 	if (pw_copyx(pfd, tfd, pwd, opwd, errbuf, sizeof(errbuf)) == 0) {
382bb62ec41Schristos 		pam_error(pamh, "Unable to update password entry: %s",
383bb62ec41Schristos 		    errbuf);
384bb62ec41Schristos 		pw_abort();
385bb62ec41Schristos 		return (PAM_SERVICE_ERR);
386bb62ec41Schristos 	}
387bb62ec41Schristos 
388bb62ec41Schristos 	if (pw_mkdb(pwd->pw_name, opwd->pw_change == pwd->pw_change) < 0) {
389bb62ec41Schristos 		pam_error(pamh, "Unable to rebuild local password database.");
390bb62ec41Schristos 		pw_abort();
391bb62ec41Schristos 		return (PAM_SERVICE_ERR);
392bb62ec41Schristos 	}
393bb62ec41Schristos 
394bb62ec41Schristos 	return (PAM_SUCCESS);
3956f11bdf1Schristos }
3966f11bdf1Schristos 
3976f11bdf1Schristos /*
3986f11bdf1Schristos  * password management
3996f11bdf1Schristos  *
4006f11bdf1Schristos  * standard Unix and NIS password changing
4016f11bdf1Schristos  */
4026f11bdf1Schristos PAM_EXTERN int
403bb62ec41Schristos /*ARGSUSED*/
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc __unused,const char * argv[]__unused)4046f11bdf1Schristos pam_sm_chauthtok(pam_handle_t *pamh, int flags,
4056f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
4066f11bdf1Schristos {
40774cf7887Sjnemeth 	struct passwd *pwd, new_pwd, old_pwd;
4086f11bdf1Schristos 	login_cap_t *lc;
409bb62ec41Schristos 	const char *user, *passwd_db, *new_pass, *old_pass, *p;
410bb62ec41Schristos 	int retval, tries, min_pw_len = 0, pw_expiry = 0;
411bb62ec41Schristos 	char salt[_PASSWORD_LEN+1];
41259cbc9e2Sthorpej 	char old_pwbuf[1024];
413bb62ec41Schristos #ifdef YP
414bb62ec41Schristos 	char *domain;
415bb62ec41Schristos 	int r;
416bb62ec41Schristos #endif
4176f11bdf1Schristos 
41801cf9d02Slukem 	pwd = NULL;
419f3587062Sjnemeth 	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
420f3587062Sjnemeth 		if ((user = getlogin()) == NULL) {
421f3587062Sjnemeth 			pam_error(pamh, "Unable to determine user.");
422f3587062Sjnemeth 			return (PAM_SERVICE_ERR);
423f3587062Sjnemeth 		}
424f3587062Sjnemeth 		(void) getpwnam_r(user, &old_pwd, old_pwbuf,
42559cbc9e2Sthorpej 				  sizeof(old_pwbuf), &pwd);
426f3587062Sjnemeth 	} else {
4276f11bdf1Schristos 		retval = pam_get_user(pamh, &user, NULL);
4286f11bdf1Schristos 		if (retval != PAM_SUCCESS)
4296f11bdf1Schristos 			return (retval);
43059cbc9e2Sthorpej 		(void) getpwnam_r(user, &old_pwd, old_pwbuf,
43159cbc9e2Sthorpej 				  sizeof(old_pwbuf), &pwd);
4326f11bdf1Schristos 	}
4336f11bdf1Schristos 
4346f11bdf1Schristos 	if (pwd == NULL)
4356f11bdf1Schristos 		return (PAM_AUTHTOK_RECOVERY_ERR);
4366f11bdf1Schristos 
4376f11bdf1Schristos 	PAM_LOG("Got user: %s", user);
4386f11bdf1Schristos 
439bb62ec41Schristos 	/*
440bb62ec41Schristos 	 * Determine which password type we're going to change, and
441bb62ec41Schristos 	 * remember it.
442bb62ec41Schristos 	 *
443bb62ec41Schristos 	 * NOTE: domain does not need to be freed; its storage is
444bb62ec41Schristos 	 * allocated statically in libc.
445bb62ec41Schristos 	 */
446bb62ec41Schristos 	passwd_db = openpam_get_option(pamh, "passwd_db");
447bb62ec41Schristos 	if (passwd_db == NULL) {
4486f11bdf1Schristos #ifdef YP
449bb62ec41Schristos 		/* Prefer YP, if configured. */
450bb62ec41Schristos 		if (_yp_check(NULL)) {
451bb62ec41Schristos 			/* If _yp_check() succeeded, then this must. */
452bb62ec41Schristos 			if ((r = yp_get_default_domain(&domain)) != 0) {
453bb62ec41Schristos 				pam_error(pamh,
454bb62ec41Schristos 				    "Unable to get NIS domain, reason: %s",
455bb62ec41Schristos 				    yperr_string(r));
4566f11bdf1Schristos 				return (PAM_SERVICE_ERR);
4576f11bdf1Schristos 			}
458bb62ec41Schristos 			if (yp_check_user(domain, user))
459bb62ec41Schristos 				passwd_db = "nis";
4606f11bdf1Schristos 		}
4616f11bdf1Schristos #endif
462bb62ec41Schristos 		/* Otherwise we always use local files. */
463bb62ec41Schristos 		if (passwd_db == NULL) {
464bb62ec41Schristos 			/* XXX Any validation to do here? */
465bb62ec41Schristos 			passwd_db = "files";
466bb62ec41Schristos 		}
467bb62ec41Schristos 
468f3587062Sjnemeth 		if ((retval = openpam_set_option(pamh, "passwd_db",
469bb62ec41Schristos 		    passwd_db)) != PAM_SUCCESS) {
4706f11bdf1Schristos 			return (retval);
4716f11bdf1Schristos 		}
472bb62ec41Schristos 	} else {
473bb62ec41Schristos 		/* Check to see if the specified password DB is usable. */
474bb62ec41Schristos #ifdef YP
475bb62ec41Schristos 		if (strcmp(passwd_db, "nis") == 0) {
476bb62ec41Schristos 			if (_yp_check(NULL) == 0) {
477bb62ec41Schristos 				pam_error(pamh, "NIS not in use.");
478bb62ec41Schristos 				return (PAM_SERVICE_ERR);
4796f11bdf1Schristos 			}
480bb62ec41Schristos 			if ((r = yp_get_default_domain(&domain)) != 0) {
481bb62ec41Schristos 				pam_error(pamh,
482bb62ec41Schristos 				    "Unable to get NIS domain, reason: %s",
483bb62ec41Schristos 				    yperr_string(r));
484bb62ec41Schristos 				return (PAM_SERVICE_ERR);
485bb62ec41Schristos 			}
486bb62ec41Schristos 			if (yp_check_user(domain, user) == 0) {
487bb62ec41Schristos 				pam_error(pamh,
488bb62ec41Schristos 				    "User %s does not exist in NIS.", user);
489bb62ec41Schristos 				return (PAM_USER_UNKNOWN);
490bb62ec41Schristos 			}
491bb62ec41Schristos 			goto known_passwd_db;
492bb62ec41Schristos 		}
493bb62ec41Schristos #endif
494bb62ec41Schristos 		if (strcmp(passwd_db, "files") == 0) {
495bb62ec41Schristos 			/* XXX Any validation to do here? */
496bb62ec41Schristos 			goto known_passwd_db;
497bb62ec41Schristos 		}
498bb62ec41Schristos 		pam_error(pamh, "Unknown Unix password DB: %s", passwd_db);
499bb62ec41Schristos 		return (PAM_SERVICE_ERR);
500bb62ec41Schristos 	}
501bb62ec41Schristos  known_passwd_db:
502bb62ec41Schristos 
503bb62ec41Schristos 	if (flags & PAM_PRELIM_CHECK) {
504bb62ec41Schristos 		PAM_LOG("PRELIM round");
505bb62ec41Schristos 
506bb62ec41Schristos 		if (strcmp(passwd_db, "files") == 0) {
507bb62ec41Schristos 			if (getuid() == 0) {
508bb62ec41Schristos 				/* Root doesn't need the old password. */
509bb62ec41Schristos 				return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
510bb62ec41Schristos 			}
511c08078c2Stonnerre 			/*
512c08078c2Stonnerre 			 * Apparently we're not root, so let's forbid editing
513c08078c2Stonnerre 			 * root.
514c08078c2Stonnerre 			 * XXX Check for some flag to indicate if this
515c08078c2Stonnerre 			 * XXX is the desired behavior.
516c08078c2Stonnerre 			 */
517c08078c2Stonnerre 			if (pwd->pw_uid == 0)
518c08078c2Stonnerre 				return (PAM_PERM_DENIED);
519bb62ec41Schristos 		}
520bb62ec41Schristos 
521bb62ec41Schristos 		if (pwd->pw_passwd[0] == '\0') {
522bb62ec41Schristos 			/*
523bb62ec41Schristos 			 * No password case.
524*730fdfa6Sandvar 			 * XXX Are we giving too much away by not prompting
525bb62ec41Schristos 			 * XXX for a password?
526bb62ec41Schristos 			 * XXX Check PAM_DISALLOW_NULL_AUTHTOK
527bb62ec41Schristos 			 */
528bb62ec41Schristos 			return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
529bb62ec41Schristos 		} else {
530bb62ec41Schristos 			retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK,
531bb62ec41Schristos 			    &old_pass, NULL);
532bb62ec41Schristos 			if (retval != PAM_SUCCESS)
533bb62ec41Schristos 				return (retval);
534bb62ec41Schristos 			if (strcmp(crypt(old_pass, pwd->pw_passwd),
535bb62ec41Schristos 				   pwd->pw_passwd) != 0)
536bb62ec41Schristos 				return (PAM_PERM_DENIED);
537bb62ec41Schristos 			return (PAM_SUCCESS);
538bb62ec41Schristos 		}
539bb62ec41Schristos 	}
540bb62ec41Schristos 
541bb62ec41Schristos 	if (flags & PAM_UPDATE_AUTHTOK) {
542c9cb0c3bSchristos 		char option[LINE_MAX], *key, *opt;
543c9cb0c3bSchristos 
5446f11bdf1Schristos 		PAM_LOG("UPDATE round");
5456f11bdf1Schristos 
546fd65ca01Schristos 		if ((lc = login_getpwclass(pwd)) != NULL) {
547bb62ec41Schristos 			min_pw_len = (int) login_getcapnum(lc,
548bb62ec41Schristos 			    "minpasswordlen", (quad_t)0, (quad_t)0);
549bb62ec41Schristos 			pw_expiry = (int) login_getcapnum(lc,
550bb62ec41Schristos 			    "passwordtime", (quad_t)0, (quad_t)0);
551bb62ec41Schristos 			login_close(lc);
552bb62ec41Schristos 		}
553bb62ec41Schristos 
554bb62ec41Schristos 		retval = pam_get_authtok(pamh, PAM_OLDAUTHTOK, &old_pass, NULL);
5556f11bdf1Schristos 		if (retval != PAM_SUCCESS)
5566f11bdf1Schristos 			return (retval);
5576f11bdf1Schristos 
558bb62ec41Schristos 		/* Get the new password. */
559bb62ec41Schristos 		for (tries = 0;;) {
560bb62ec41Schristos 			retval = pam_get_authtok(pamh, PAM_AUTHTOK, &new_pass,
561bb62ec41Schristos 			    NULL);
562bb62ec41Schristos 			if (retval == PAM_TRY_AGAIN) {
563bb62ec41Schristos 				pam_error(pamh,
564bb62ec41Schristos 				    "Mismatch; try again, EOF to quit.");
565bb62ec41Schristos 				continue;
5666f11bdf1Schristos 			}
5676f11bdf1Schristos 			if (retval != PAM_SUCCESS) {
5686f11bdf1Schristos 				PAM_VERBOSE_ERROR("Unable to get new password");
5696f11bdf1Schristos 				return (retval);
5706f11bdf1Schristos 			}
571bb62ec41Schristos 			/* Successfully got new password. */
572bb62ec41Schristos 			if (new_pass[0] == '\0') {
573bb62ec41Schristos 				pam_info(pamh, "Password unchanged.");
574bb62ec41Schristos 				return (PAM_SUCCESS);
575bb62ec41Schristos 			}
576ee7c6ab3Slukem 			if (min_pw_len > 0 && strlen(new_pass) < (size_t)min_pw_len) {
577bb62ec41Schristos 				pam_error(pamh, "Password is too short.");
578fa423103Sdrochner 				goto retry;
579bb62ec41Schristos 			}
580bb62ec41Schristos 			if (strlen(new_pass) <= 5 && ++tries < 2) {
581bb62ec41Schristos 				pam_error(pamh,
582bb62ec41Schristos 				    "Please enter a longer password.");
583fa423103Sdrochner 				goto retry;
584bb62ec41Schristos 			}
585bb62ec41Schristos 			for (p = new_pass; *p && islower((unsigned char)*p); ++p);
586bb62ec41Schristos 			if (!*p && ++tries < 2) {
587bb62ec41Schristos 				pam_error(pamh,
588bb62ec41Schristos 				    "Please don't use an all-lower case "
589bb62ec41Schristos 				    "password.\nUnusual capitalization, "
590bb62ec41Schristos 				    "control characters or digits are "
591bb62ec41Schristos 				    "suggested.");
592fa423103Sdrochner 				goto retry;
593bb62ec41Schristos 			}
594bb62ec41Schristos 			/* Password is OK. */
595bb62ec41Schristos 			break;
596fa423103Sdrochner retry:
597fa423103Sdrochner 			pam_set_item(pamh, PAM_AUTHTOK, NULL);
598bb62ec41Schristos 		}
599c9cb0c3bSchristos 		pw_getpwconf(option, sizeof(option), pwd,
600bb62ec41Schristos #ifdef YP
601c9cb0c3bSchristos 		    strcmp(passwd_db, "nis") == 0 ? "ypcipher" :
602bb62ec41Schristos #endif
603c9cb0c3bSchristos 		    "localcipher");
604c9cb0c3bSchristos 		opt = option;
605c9cb0c3bSchristos 		key = strsep(&opt, ",");
606c9cb0c3bSchristos 
607c9cb0c3bSchristos 		if (pw_gensalt(salt, _PASSWORD_LEN, key, opt) == -1) {
608bb62ec41Schristos 			pam_error(pamh, "Couldn't generate salt.");
609bb62ec41Schristos 			return (PAM_SERVICE_ERR);
610bb62ec41Schristos 		}
6116f11bdf1Schristos 
61274cf7887Sjnemeth 		new_pwd = old_pwd;
61374cf7887Sjnemeth 		pwd = &new_pwd;
6146f11bdf1Schristos 		pwd->pw_passwd = crypt(new_pass, salt);
615bb62ec41Schristos 		pwd->pw_change = pw_expiry ? pw_expiry + time(NULL) : 0;
6166f11bdf1Schristos 
617bb62ec41Schristos 		retval = PAM_SERVICE_ERR;
618bb62ec41Schristos 		if (strcmp(passwd_db, "files") == 0)
619bb62ec41Schristos 			retval = local_set_password(pamh, &old_pwd, pwd);
620bb62ec41Schristos #ifdef YP
621bb62ec41Schristos 		if (strcmp(passwd_db, "nis") == 0)
622bb62ec41Schristos 			retval = yp_set_password(pamh, &old_pwd, pwd, old_pass,
623bb62ec41Schristos 			    domain);
624bb62ec41Schristos #endif
6256f11bdf1Schristos 		return (retval);
6266f11bdf1Schristos 	}
6276f11bdf1Schristos 
628bb62ec41Schristos 	PAM_LOG("Illegal flags argument");
629bb62ec41Schristos 	return (PAM_ABORT);
630bb62ec41Schristos }
6316f11bdf1Schristos 
632bb62ec41Schristos PAM_MODULE_ENTRY("pam_unix");
633