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