1/*-
2 * SPDX-License-Identifier: BSD-3-Clause
3 *
4 * Copyright 1998 Juniper Networks, Inc.
5 * All rights reserved.
6 * Copyright (c) 2002-2003 Networks Associates Technology, Inc.
7 * All rights reserved.
8 *
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.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
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
24 *    permission.
25 *
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
36 * SUCH DAMAGE.
37 */
38
39#include <sys/cdefs.h>
40__FBSDID("$FreeBSD$");
41
42#include <sys/param.h>
43#include <sys/socket.h>
44#include <sys/time.h>
45#include <netinet/in.h>
46#include <arpa/inet.h>
47
48#include <login_cap.h>
49#include <netdb.h>
50#include <pwd.h>
51#include <stdlib.h>
52#include <string.h>
53#include <stdio.h>
54#include <syslog.h>
55#include <time.h>
56#include <unistd.h>
57
58#include <libutil.h>
59
60#ifdef YP
61#include <ypclnt.h>
62#endif
63
64#define PAM_SM_AUTH
65#define PAM_SM_ACCOUNT
66#define	PAM_SM_PASSWORD
67
68#include <security/pam_appl.h>
69#include <security/pam_modules.h>
70#include <security/pam_mod_misc.h>
71
72#define PASSWORD_HASH		"md5"
73#define DEFAULT_WARN		(2L * 7L * 86400L)  /* Two weeks */
74#define	SALTSIZE		32
75
76#define	LOCKED_PREFIX		"*LOCKED*"
77#define	LOCKED_PREFIX_LEN	(sizeof(LOCKED_PREFIX) - 1)
78
79static void makesalt(char []);
80
81static char password_hash[] =		PASSWORD_HASH;
82
83#define PAM_OPT_LOCAL_PASS	"local_pass"
84#define PAM_OPT_NIS_PASS	"nis_pass"
85
86/*
87 * authentication management
88 */
89PAM_EXTERN int
90pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
91    int argc __unused, const char *argv[] __unused)
92{
93	login_cap_t *lc;
94	struct passwd *pwd;
95	int retval;
96	const char *pass, *user, *realpw, *prompt;
97
98	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF)) {
99		user = getlogin();
100	} else {
101		retval = pam_get_user(pamh, &user, NULL);
102		if (retval != PAM_SUCCESS)
103			return (retval);
104	}
105	pwd = getpwnam(user);
106
107	PAM_LOG("Got user: %s", user);
108
109	if (pwd != NULL) {
110		PAM_LOG("Doing real authentication");
111		realpw = pwd->pw_passwd;
112		if (realpw[0] == '\0') {
113			if (!(flags & PAM_DISALLOW_NULL_AUTHTOK) &&
114			    openpam_get_option(pamh, PAM_OPT_NULLOK))
115				return (PAM_SUCCESS);
116			PAM_LOG("Password is empty, using fake password");
117			realpw = "*";
118		}
119		lc = login_getpwclass(pwd);
120	} else {
121		PAM_LOG("Doing dummy authentication");
122		realpw = "*";
123		lc = login_getclass(NULL);
124	}
125	prompt = login_getcapstr(lc, "passwd_prompt", NULL, NULL);
126	retval = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt);
127	login_close(lc);
128	if (retval != PAM_SUCCESS)
129		return (retval);
130	PAM_LOG("Got password");
131	if (strnlen(pass, _PASSWORD_LEN + 1) > _PASSWORD_LEN) {
132		PAM_LOG("Password is too long, using fake password");
133		realpw = "*";
134	}
135	if (strcmp(crypt(pass, realpw), realpw) == 0)
136		return (PAM_SUCCESS);
137
138	PAM_VERBOSE_ERROR("UNIX authentication refused");
139	return (PAM_AUTH_ERR);
140}
141
142PAM_EXTERN int
143pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
144    int argc __unused, const char *argv[] __unused)
145{
146
147	return (PAM_SUCCESS);
148}
149
150/*
151 * account management
152 */
153PAM_EXTERN int
154pam_sm_acct_mgmt(pam_handle_t *pamh, int flags __unused,
155    int argc __unused, const char *argv[] __unused)
156{
157	struct addrinfo hints, *res;
158	struct passwd *pwd;
159	struct timeval tp;
160	login_cap_t *lc;
161	time_t warntime;
162	int retval;
163	const char *user;
164	const void *rhost, *tty;
165	char rhostip[MAXHOSTNAMELEN] = "";
166
167	retval = pam_get_user(pamh, &user, NULL);
168	if (retval != PAM_SUCCESS)
169		return (retval);
170
171	if (user == NULL || (pwd = getpwnam(user)) == NULL)
172		return (PAM_SERVICE_ERR);
173
174	PAM_LOG("Got user: %s", user);
175
176	retval = pam_get_item(pamh, PAM_RHOST, &rhost);
177	if (retval != PAM_SUCCESS)
178		return (retval);
179
180	retval = pam_get_item(pamh, PAM_TTY, &tty);
181	if (retval != PAM_SUCCESS)
182		return (retval);
183
184	if (*pwd->pw_passwd == '\0' &&
185	    (flags & PAM_DISALLOW_NULL_AUTHTOK) != 0)
186		return (PAM_NEW_AUTHTOK_REQD);
187
188	if (strncmp(pwd->pw_passwd, LOCKED_PREFIX, LOCKED_PREFIX_LEN) == 0)
189		return (PAM_AUTH_ERR);
190
191	lc = login_getpwclass(pwd);
192	if (lc == NULL) {
193		PAM_LOG("Unable to get login class for user %s", user);
194		return (PAM_SERVICE_ERR);
195	}
196
197	PAM_LOG("Got login_cap");
198
199	if (pwd->pw_change || pwd->pw_expire)
200		gettimeofday(&tp, NULL);
201
202	/*
203	 * Check pw_expire before pw_change - no point in letting the
204	 * user change the password on an expired account.
205	 */
206
207	if (pwd->pw_expire) {
208		warntime = login_getcaptime(lc, "warnexpire",
209		    DEFAULT_WARN, DEFAULT_WARN);
210		if (tp.tv_sec >= pwd->pw_expire) {
211			login_close(lc);
212			return (PAM_ACCT_EXPIRED);
213		} else if (pwd->pw_expire - tp.tv_sec < warntime &&
214		    (flags & PAM_SILENT) == 0) {
215			pam_error(pamh, "Warning: your account expires on %s",
216			    ctime(&pwd->pw_expire));
217		}
218	}
219
220	retval = PAM_SUCCESS;
221	if (pwd->pw_change) {
222		warntime = login_getcaptime(lc, "warnpassword",
223		    DEFAULT_WARN, DEFAULT_WARN);
224		if (tp.tv_sec >= pwd->pw_change) {
225			retval = PAM_NEW_AUTHTOK_REQD;
226		} else if (pwd->pw_change - tp.tv_sec < warntime &&
227		    (flags & PAM_SILENT) == 0) {
228			pam_error(pamh, "Warning: your password expires on %s",
229			    ctime(&pwd->pw_change));
230		}
231	}
232
233	/*
234	 * From here on, we must leave retval untouched (unless we
235	 * know we're going to fail), because we need to remember
236	 * whether we're supposed to return PAM_SUCCESS or
237	 * PAM_NEW_AUTHTOK_REQD.
238	 */
239
240	if (rhost && *(const char *)rhost != '\0') {
241		memset(&hints, 0, sizeof(hints));
242		hints.ai_family = AF_UNSPEC;
243		if (getaddrinfo(rhost, NULL, &hints, &res) == 0) {
244			getnameinfo(res->ai_addr, res->ai_addrlen,
245			    rhostip, sizeof(rhostip), NULL, 0,
246			    NI_NUMERICHOST);
247		}
248		if (res != NULL)
249			freeaddrinfo(res);
250	}
251
252	/*
253	 * Check host / tty / time-of-day restrictions
254	 */
255
256	if (!auth_hostok(lc, rhost, rhostip) ||
257	    !auth_ttyok(lc, tty) ||
258	    !auth_timeok(lc, time(NULL)))
259		retval = PAM_AUTH_ERR;
260
261	login_close(lc);
262
263	return (retval);
264}
265
266/*
267 * password management
268 *
269 * standard Unix and NIS password changing
270 */
271PAM_EXTERN int
272pam_sm_chauthtok(pam_handle_t *pamh, int flags,
273    int argc __unused, const char *argv[] __unused)
274{
275#ifdef YP
276	struct ypclnt *ypclnt;
277	const void *yp_domain, *yp_server;
278#endif
279	char salt[SALTSIZE + 1];
280	login_cap_t *lc;
281	struct passwd *pwd, *old_pwd;
282	const char *user, *old_pass, *new_pass;
283	char *encrypted;
284	time_t passwordtime;
285	int pfd, tfd, retval;
286
287	if (openpam_get_option(pamh, PAM_OPT_AUTH_AS_SELF))
288		user = getlogin();
289	else {
290		retval = pam_get_user(pamh, &user, NULL);
291		if (retval != PAM_SUCCESS)
292			return (retval);
293	}
294	pwd = getpwnam(user);
295
296	if (pwd == NULL)
297		return (PAM_AUTHTOK_RECOVERY_ERR);
298
299	PAM_LOG("Got user: %s", user);
300
301	if (flags & PAM_PRELIM_CHECK) {
302
303		PAM_LOG("PRELIM round");
304
305		if (getuid() == 0 &&
306		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_FILES)
307			/* root doesn't need the old password */
308			return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
309#ifdef YP
310		if (getuid() == 0 &&
311		    (pwd->pw_fields & _PWF_SOURCE) == _PWF_NIS) {
312
313			yp_domain = yp_server = NULL;
314			(void)pam_get_data(pamh, "yp_domain", &yp_domain);
315			(void)pam_get_data(pamh, "yp_server", &yp_server);
316
317			ypclnt = ypclnt_new(yp_domain, "passwd.byname", yp_server);
318			if (ypclnt == NULL)
319				return (PAM_BUF_ERR);
320
321			if (ypclnt_connect(ypclnt) == -1) {
322				ypclnt_free(ypclnt);
323				return (PAM_SERVICE_ERR);
324			}
325
326			retval = ypclnt_havepasswdd(ypclnt);
327			ypclnt_free(ypclnt);
328			if (retval == 1)
329				return (pam_set_item(pamh, PAM_OLDAUTHTOK, ""));
330			else if (retval == -1)
331				return (PAM_SERVICE_ERR);
332		}
333#endif
334		if (pwd->pw_passwd[0] == '\0'
335		    && openpam_get_option(pamh, PAM_OPT_NULLOK)) {
336			/*
337			 * No password case. XXX Are we giving too much away
338			 * by not prompting for a password?
339			 * XXX check PAM_DISALLOW_NULL_AUTHTOK
340			 */
341			old_pass = "";
342			retval = PAM_SUCCESS;
343		} else {
344			retval = pam_get_authtok(pamh,
345			    PAM_OLDAUTHTOK, &old_pass, NULL);
346			if (retval != PAM_SUCCESS)
347				return (retval);
348		}
349		PAM_LOG("Got old password");
350		/* always encrypt first */
351		encrypted = crypt(old_pass, pwd->pw_passwd);
352		if (old_pass[0] == '\0' &&
353		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
354			return (PAM_PERM_DENIED);
355		if (strcmp(encrypted, pwd->pw_passwd) != 0)
356			return (PAM_PERM_DENIED);
357	}
358	else if (flags & PAM_UPDATE_AUTHTOK) {
359		PAM_LOG("UPDATE round");
360
361		retval = pam_get_authtok(pamh,
362		    PAM_OLDAUTHTOK, &old_pass, NULL);
363		if (retval != PAM_SUCCESS)
364			return (retval);
365		PAM_LOG("Got old password");
366
367		/* get new password */
368		for (;;) {
369			retval = pam_get_authtok(pamh,
370			    PAM_AUTHTOK, &new_pass, NULL);
371			if (retval != PAM_TRY_AGAIN)
372				break;
373			pam_error(pamh, "Mismatch; try again, EOF to quit.");
374		}
375		PAM_LOG("Got new password");
376		if (retval != PAM_SUCCESS) {
377			PAM_VERBOSE_ERROR("Unable to get new password");
378			return (retval);
379		}
380
381		if (getuid() != 0 && new_pass[0] == '\0' &&
382		    !openpam_get_option(pamh, PAM_OPT_NULLOK))
383			return (PAM_PERM_DENIED);
384
385		if ((old_pwd = pw_dup(pwd)) == NULL)
386			return (PAM_BUF_ERR);
387
388		lc = login_getclass(pwd->pw_class);
389		if (login_setcryptfmt(lc, password_hash, NULL) == NULL)
390			openpam_log(PAM_LOG_ERROR,
391			    "can't set password cipher, relying on default");
392
393		/* set password expiry date */
394		pwd->pw_change = 0;
395		passwordtime = login_getcaptime(lc, "passwordtime", 0, 0);
396		if (passwordtime > 0)
397			pwd->pw_change = time(NULL) + passwordtime;
398
399		login_close(lc);
400		makesalt(salt);
401		pwd->pw_passwd = crypt(new_pass, salt);
402#ifdef YP
403		switch (old_pwd->pw_fields & _PWF_SOURCE) {
404		case _PWF_FILES:
405#endif
406			retval = PAM_SERVICE_ERR;
407			if (pw_init(NULL, NULL))
408				openpam_log(PAM_LOG_ERROR, "pw_init() failed");
409			else if ((pfd = pw_lock()) == -1)
410				openpam_log(PAM_LOG_ERROR, "pw_lock() failed");
411			else if ((tfd = pw_tmp(-1)) == -1)
412				openpam_log(PAM_LOG_ERROR, "pw_tmp() failed");
413			else if (pw_copy(pfd, tfd, pwd, old_pwd) == -1)
414				openpam_log(PAM_LOG_ERROR, "pw_copy() failed");
415			else if (pw_mkdb(pwd->pw_name) == -1)
416				openpam_log(PAM_LOG_ERROR, "pw_mkdb() failed");
417			else
418				retval = PAM_SUCCESS;
419			pw_fini();
420#ifdef YP
421			break;
422		case _PWF_NIS:
423			yp_domain = yp_server = NULL;
424			(void)pam_get_data(pamh, "yp_domain", &yp_domain);
425			(void)pam_get_data(pamh, "yp_server", &yp_server);
426			ypclnt = ypclnt_new(yp_domain,
427			    "passwd.byname", yp_server);
428			if (ypclnt == NULL) {
429				retval = PAM_BUF_ERR;
430			} else if (ypclnt_connect(ypclnt) == -1 ||
431			    ypclnt_passwd(ypclnt, pwd, old_pass) == -1) {
432				openpam_log(PAM_LOG_ERROR, "%s", ypclnt->error);
433				retval = PAM_SERVICE_ERR;
434			} else {
435				retval = PAM_SUCCESS;
436			}
437			ypclnt_free(ypclnt);
438			break;
439		default:
440			openpam_log(PAM_LOG_ERROR, "unsupported source 0x%x",
441			    pwd->pw_fields & _PWF_SOURCE);
442			retval = PAM_SERVICE_ERR;
443		}
444#endif
445		free(old_pwd);
446	}
447	else {
448		/* Very bad juju */
449		retval = PAM_ABORT;
450		PAM_LOG("Illegal 'flags'");
451	}
452
453	return (retval);
454}
455
456/* Mostly stolen from passwd(1)'s local_passwd.c - markm */
457
458static unsigned char itoa64[] =		/* 0 ... 63 => ascii - 64 */
459	"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
460
461static void
462to64(char *s, long v, int n)
463{
464	while (--n >= 0) {
465		*s++ = itoa64[v&0x3f];
466		v >>= 6;
467	}
468}
469
470/* Salt suitable for traditional DES and MD5 */
471static void
472makesalt(char salt[SALTSIZE + 1])
473{
474	int i;
475
476	/* These are not really random numbers, they are just
477	 * numbers that change to thwart construction of a
478	 * dictionary.
479	 */
480	for (i = 0; i < SALTSIZE; i += 4)
481		to64(&salt[i], arc4random(), 4);
482	salt[SALTSIZE] = '\0';
483}
484
485PAM_MODULE_ENTRY("pam_unix");
486