xref: /illumos-gate/usr/src/lib/pam_modules/dhkeys/dhkeys.c (revision 1a2d662a91cee3bf82f41cd47c7ae6f3825d9db2)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2023 OmniOS Community Edition (OmniOSce) Association.
26  */
27 
28 #include <stdlib.h>
29 #include <syslog.h>
30 #include <errno.h>
31 #include <string.h>
32 #include <rpc/rpc.h>
33 #include <unistd.h>
34 #include <assert.h>
35 #include <stdarg.h>
36 #include <sys/types.h>
37 #include <sys/wait.h>
38 #include <limits.h>
39 #include <signal.h>
40 #include <pthread.h>
41 #include <synch.h>
42 
43 #include <rpcsvc/nis.h>
44 #include <rpcsvc/yppasswd.h>
45 #include <rpcsvc/ypclnt.h>
46 #include <rpc/key_prot.h>
47 #include <rpc/rpc.h>
48 #include <nfs/nfs.h>
49 #include <nfs/nfssys.h>
50 #include <nss_dbdefs.h>
51 #include <nsswitch.h>
52 #include <rpcsvc/nis_dhext.h>
53 
54 #include <security/pam_appl.h>
55 #include <security/pam_modules.h>
56 #include <security/pam_impl.h>
57 
58 #include <libintl.h>
59 
60 #include <sys/mman.h>
61 
62 #include <passwdutil.h>
63 
64 #include "key_call_uid.h"
65 #include <shadow.h>
66 
67 extern	int	_nfssys(int, void *);
68 
69 /*
70  * int msg(pamh, ...)
71  *
72  * display message to the user
73  */
74 /*PRINTFLIKE2*/
75 static int
76 msg(pam_handle_t *pamh, char *fmt, ...)
77 {
78 	va_list	ap;
79 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
80 
81 	va_start(ap, fmt);
82 	(void) vsnprintf(messages[0], sizeof (messages[0]), fmt, ap);
83 	va_end(ap);
84 
85 	return (__pam_display_msg(pamh, PAM_ERROR_MSG, 1, messages, NULL));
86 }
87 
88 
89 /*
90  * Get the secret key for the given netname, key length, and algorithm
91  * type and send it to keyserv if the given pw decrypts it.  Update the
92  * following counter args as necessary: get_seckey_cnt, good_pw_cnt, and
93  * set_seckey_cnt.
94  *
95  * Returns 0 on malloc failure, else 1.
96  */
97 static int
98 get_and_set_seckey(
99 	pam_handle_t	*pamh,			/* in */
100 	const char	*netname,		/* in */
101 	keylen_t	keylen,			/* in */
102 	algtype_t	algtype,		/* in */
103 	const char	*pw,			/* in */
104 	uid_t		uid,			/* in */
105 	gid_t		gid,			/* in */
106 	int		*get_seckey_cnt,	/* out */
107 	int		*good_pw_cnt,		/* out */
108 	int		*set_seckey_cnt,	/* out */
109 	int		flags,			/* in */
110 	int		debug)			/* in */
111 {
112 	char	*skey;
113 	int	skeylen;
114 	char	messages[PAM_MAX_NUM_MSG][PAM_MAX_MSG_SIZE];
115 
116 	skeylen = BITS2NIBBLES(keylen) + 1;
117 
118 	if ((skey = malloc(skeylen)) == NULL) {
119 		return (0);
120 	}
121 
122 	if (getsecretkey_g(netname, keylen, algtype, skey, skeylen, pw)) {
123 		(*get_seckey_cnt)++;
124 
125 		if (skey[0]) {
126 			/* password does decrypt secret key */
127 			(*good_pw_cnt)++;
128 			if (key_setnet_g_uid(netname, skey, keylen, NULL, 0,
129 			    algtype, uid, gid) >= 0) {
130 				(*set_seckey_cnt)++;
131 			} else {
132 				if (debug)
133 					syslog(LOG_DEBUG, "pam_dhkeys: "
134 					    "get_and_set_seckey: could not "
135 					    "set secret key for keytype "
136 					    "%d-%d", keylen, algtype);
137 			}
138 		} else {
139 			if (pamh && !(flags & PAM_SILENT)) {
140 				(void) snprintf(messages[0],
141 				    sizeof (messages[0]),
142 				    dgettext(TEXT_DOMAIN,
143 				    "Password does not "
144 				    "decrypt secret key (type = %d-%d) "
145 				    "for '%s'."), keylen, algtype, netname);
146 				(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 1,
147 				    messages, NULL);
148 			}
149 		}
150 	} else {
151 		if (debug)
152 			syslog(LOG_DEBUG, "pam_dhkeys: get_and_set_seckey: "
153 			    "could not get secret key for keytype %d-%d",
154 			    keylen, algtype);
155 	}
156 
157 	free(skey);
158 
159 	return (1);
160 }
161 
162 /*
163  * int establish_key(pamh, flags, debug, netname)
164  *
165  * This routine establishes the Secure RPC Credentials for the
166  * user specified in PAM_USER, using the password in PAM_AUTHTOK.
167  *
168  * Establishing RPC credentials is considered a "helper" function for the PAM
169  * stack so we should only return failures or PAM_IGNORE. Returning PAM_SUCCESS
170  * may short circuit the stack and circumvent later critical checks.
171  *
172  * we are called from pam_sm_setcred:
173  *	1. if we are root (uid == 0), we do nothing and return
174  *	   PAM_IGNORE.
175  *	2. else, we try to establish credentials.
176  *
177  * We return framework errors as appropriate such as PAM_USER_UNKNOWN,
178  * PAM_BUF_ERR, PAM_PERM_DENIED.
179  *
180  * If we succeed in establishing credentials we return PAM_IGNORE.
181  *
182  * If we fail to establish credentials then we return:
183  *    - PAM_SERVICE_ERR (credentials needed) or PAM_SYSTEM_ERR
184  *      (credentials not needed) if netname could not be created;
185  *    - PAM_AUTH_ERR (credentials needed) or PAM_IGNORE (credentials
186  *      not needed) if no credentials were retrieved;
187  *    - PAM_AUTH_ERR if the password didn't decrypt the cred;
188  *    - PAM_SYSTEM_ERR if the cred's could not be stored.
189  *
190  * This routine returns the user's netname in "netname".
191  *
192  * All tools--but the PAM stack--currently use getpass() to obtain
193  * the user's secure RPC password. We must make sure we don't use more than
194  * the first des_block (eight) characters of whatever is handed down to us.
195  * Therefore, we use a local variable "short_pass" to hold those 8 char's.
196  */
197 static int
198 establish_key(pam_handle_t *pamh, int flags, int debug, char *netname)
199 {
200 	const char *user;
201 	const char *passwd;
202 	char short_pass[sizeof (des_block)+1], *short_passp;
203 	int result;
204 	uid_t uid;
205 	gid_t gid;
206 	int err;
207 
208 	struct passwd pw;	/* Needed to obtain uid */
209 	char *scratch;
210 	int scratchlen;
211 
212 	mechanism_t **mechs;
213 	mechanism_t **mpp;
214 	int get_seckey_cnt = 0;
215 	int set_seckey_cnt = 0;
216 	int good_pw_cnt = 0;
217 	int valid_mech_cnt = 0;
218 
219 	(void) pam_get_item(pamh, PAM_USER, (const void **)&user);
220 
221 	if (user == NULL || *user == '\0') {
222 		if (debug)
223 			syslog(LOG_DEBUG, "pam_dhkeys: user NULL or empty");
224 		return (PAM_USER_UNKNOWN);
225 	}
226 
227 	(void) pam_get_item(pamh, PAM_AUTHTOK, (const void **)&passwd);
228 
229 	scratchlen = sysconf(_SC_GETPW_R_SIZE_MAX);
230 	if ((scratch = malloc(scratchlen)) == NULL)
231 		return (PAM_BUF_ERR);
232 
233 	if (getpwnam_r(user, &pw, scratch, scratchlen) == NULL) {
234 		result = PAM_USER_UNKNOWN;
235 		goto out;
236 	}
237 
238 	uid = pw.pw_uid;
239 	gid = pw.pw_gid;
240 
241 	/*
242 	 * We don't set credentials when root logs in.
243 	 */
244 	if (uid == 0) {
245 		result = PAM_IGNORE;
246 		goto out;
247 	}
248 
249 	err = user2netname(netname, uid, NULL);
250 
251 	if (err != 1) {
252 		if (debug)
253 			syslog(LOG_DEBUG, "pam_dhkeys: user2netname failed");
254 		result = PAM_SYSTEM_ERR;
255 		goto out;
256 	}
257 
258 	/* passwd can be NULL (no passwd or su as root) */
259 	if (passwd) {
260 		(void) strlcpy(short_pass, passwd, sizeof (short_pass));
261 		short_passp = short_pass;
262 	} else {
263 		short_passp = NULL;
264 	}
265 
266 	if (mechs = __nis_get_mechanisms(FALSE)) {
267 
268 		for (mpp = mechs; *mpp; mpp++) {
269 			mechanism_t *mp = *mpp;
270 
271 			if (AUTH_DES_COMPAT_CHK(mp))
272 				break;	/* fall through to AUTH_DES below */
273 
274 			if (!VALID_MECH_ENTRY(mp))
275 				continue;
276 
277 			if (debug)
278 				syslog(LOG_DEBUG, "pam_dhkeys: trying "
279 				    "key type = %d-%d", mp->keylen,
280 				    mp->algtype);
281 			valid_mech_cnt++;
282 			if (!get_and_set_seckey(pamh, netname, mp->keylen,
283 			    mp->algtype, short_passp, uid, gid,
284 			    &get_seckey_cnt, &good_pw_cnt, &set_seckey_cnt,
285 			    flags, debug)) {
286 				result = PAM_BUF_ERR;
287 				goto out;
288 			}
289 		}
290 		__nis_release_mechanisms(mechs);
291 		/* fall through to AUTH_DES below */
292 	} else {
293 		/*
294 		 * No usable mechs found in security congifuration file thus
295 		 * fallback to AUTH_DES compat.
296 		 */
297 		if (debug)
298 			syslog(LOG_DEBUG, "pam_dhkeys: no valid mechs "
299 			    "found. Trying AUTH_DES.");
300 	}
301 
302 	/*
303 	 * We always perform AUTH_DES for the benefit of services like NFS
304 	 * that may depend on the classic des 192bit key being set.
305 	 */
306 	if (!get_and_set_seckey(pamh, netname, AUTH_DES_KEYLEN,
307 	    AUTH_DES_ALGTYPE, short_passp, uid, gid, &get_seckey_cnt,
308 	    &good_pw_cnt, &set_seckey_cnt, flags, debug)) {
309 		result = PAM_BUF_ERR;
310 		goto out;
311 	}
312 
313 	if (debug) {
314 		syslog(LOG_DEBUG, "pam_dhkeys: mech key totals:\n");
315 		syslog(LOG_DEBUG, "pam_dhkeys: %d valid mechanism(s)",
316 		    valid_mech_cnt);
317 		syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) retrieved",
318 		    get_seckey_cnt);
319 		syslog(LOG_DEBUG, "pam_dhkeys: %d passwd decrypt successes",
320 		    good_pw_cnt);
321 		syslog(LOG_DEBUG, "pam_dhkeys: %d secret key(s) set",
322 		    set_seckey_cnt);
323 	}
324 
325 	if (get_seckey_cnt == 0) {		/* No credentials */
326 		result = PAM_IGNORE;
327 		goto out;
328 	}
329 
330 	if (good_pw_cnt == 0) {			/* wrong password */
331 		result = PAM_AUTH_ERR;
332 		goto out;
333 	}
334 
335 	if (set_seckey_cnt == 0) {
336 		result = PAM_SYSTEM_ERR;
337 		goto out;
338 	}
339 	/* Credentials have been successfully established, return PAM_IGNORE */
340 	result = PAM_IGNORE;
341 out:
342 	/*
343 	 * If we are authenticating we attempt to establish credentials
344 	 * where appropriate. Failure to do so is only an error if we
345 	 * definitely needed them. Thus always return PAM_IGNORE
346 	 * if we are authenticating and credentials were not needed.
347 	 */
348 	free(scratch);
349 
350 	(void) memset(short_pass, '\0', sizeof (short_pass));
351 
352 	return (result);
353 }
354 
355 /*ARGSUSED*/
356 int
357 pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv)
358 {
359 	return (PAM_IGNORE);
360 }
361 
362 
363 typedef struct argres {
364 	uid_t uid;
365 	int result;
366 } argres_t;
367 
368 /*
369  * Revoke NFS DES credentials.
370  * NFS may not be installed so we need to deal with SIGSYS
371  * when we call _nfssys(); we thus call _nfssys() in a seperate thread that
372  * is created specifically for this call. The thread specific signalmask
373  * is set to ignore SIGSYS. After the call to _nfssys(), the thread
374  * ceases to exist.
375  */
376 static void *
377 revoke_nfs_cred(void *ap)
378 {
379 	struct nfs_revauth_args nra;
380 	sigset_t isigset;
381 	argres_t *argres = (argres_t *)ap;
382 
383 	nra.authtype = AUTH_DES;
384 	nra.uid = argres->uid;
385 
386 	(void) sigemptyset(&isigset);
387 	(void) sigaddset(&isigset, SIGSYS);
388 
389 	if (pthread_sigmask(SIG_BLOCK, &isigset, NULL) == 0) {
390 		argres->result = _nfssys(NFS_REVAUTH, &nra);
391 		if (argres->result < 0 && errno == ENOSYS) {
392 			argres->result = 0;
393 		}
394 	} else {
395 		argres->result = -1;
396 	}
397 	return (NULL);
398 }
399 
400 static int
401 remove_key(pam_handle_t *pamh, int flags, int debug)
402 {
403 	int result;
404 	const char *uname;
405 	attrlist attr_pw[2];
406 	const struct pam_repository *auth_rep = NULL;
407 	pwu_repository_t *pwu_rep;
408 	uid_t uid;
409 	gid_t gid;
410 	argres_t argres;
411 	pthread_t tid;
412 
413 	(void) pam_get_item(pamh, PAM_USER, (const void **)&uname);
414 	if (uname == NULL || *uname == '\0') {
415 		if (debug)
416 			syslog(LOG_DEBUG,
417 			    "pam_dhkeys: user NULL or empty in remove_key()");
418 		return (PAM_USER_UNKNOWN);
419 	}
420 
421 	if (strcmp(uname, "root") == 0) {
422 		if ((flags & PAM_SILENT) == 0) {
423 			char msg[3][PAM_MAX_MSG_SIZE];
424 			(void) snprintf(msg[0], sizeof (msg[0]),
425 			    dgettext(TEXT_DOMAIN,
426 			    "removing root credentials would"
427 			    " break the rpc services that"));
428 			(void) snprintf(msg[1], sizeof (msg[1]),
429 			    dgettext(TEXT_DOMAIN,
430 			    "use secure rpc on this host!"));
431 			(void) snprintf(msg[2], sizeof (msg[2]),
432 			    dgettext(TEXT_DOMAIN,
433 			    "root may use keylogout -f to do"
434 			    " this (at your own risk)!"));
435 			(void) __pam_display_msg(pamh, PAM_ERROR_MSG, 3,
436 			    msg, NULL);
437 		}
438 		return (PAM_PERM_DENIED);
439 	}
440 
441 	(void) pam_get_item(pamh, PAM_REPOSITORY, (const void **)&auth_rep);
442 	if (auth_rep != NULL) {
443 		if ((pwu_rep = calloc(1, sizeof (*pwu_rep))) == NULL)
444 			return (PAM_BUF_ERR);
445 		pwu_rep->type = auth_rep->type;
446 		pwu_rep->scope = auth_rep->scope;
447 		pwu_rep->scope_len = auth_rep->scope_len;
448 	} else {
449 		pwu_rep = PWU_DEFAULT_REP;
450 	}
451 
452 	/* Retrieve user's uid/gid from the password repository */
453 	attr_pw[0].type = ATTR_UID; attr_pw[0].next = &attr_pw[1];
454 	attr_pw[1].type = ATTR_GID; attr_pw[1].next = NULL;
455 
456 	result = __get_authtoken_attr(uname, pwu_rep, attr_pw);
457 
458 	if (pwu_rep != PWU_DEFAULT_REP)
459 		free(pwu_rep);
460 
461 	if (result == PWU_NOT_FOUND)
462 		return (PAM_USER_UNKNOWN);
463 	if (result == PWU_DENIED)
464 		return (PAM_PERM_DENIED);
465 	if (result != PWU_SUCCESS)
466 		return (PAM_SYSTEM_ERR);
467 
468 	uid = (uid_t)attr_pw[0].data.val_i;
469 	gid = (gid_t)attr_pw[1].data.val_i;
470 
471 	(void) key_removesecret_g_uid(uid, gid);
472 
473 	argres.uid = uid;
474 	argres.result = -1;
475 
476 	if (pthread_create(&tid, NULL, revoke_nfs_cred, (void *)&argres) == 0)
477 		(void) pthread_join(tid, NULL);
478 
479 	if (argres.result < 0) {
480 		if ((flags & PAM_SILENT) == 0) {
481 			(void) msg(pamh, dgettext(TEXT_DOMAIN,
482 			    "Warning: NFS credentials not destroyed"));
483 		}
484 		return (PAM_AUTH_ERR);
485 	}
486 
487 	return (PAM_IGNORE);
488 }
489 
490 int
491 pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
492 {
493 	int	i;
494 	int	debug = 0;
495 	int	result;
496 	char	netname[MAXNETNAMELEN + 1];
497 
498 	for (i = 0; i < argc; i++) {
499 		if (strcmp(argv[i], "debug") == 0)
500 			debug = 1;
501 		else if (strcmp(argv[i], "nowarn") == 0)
502 			flags |= PAM_SILENT;
503 	}
504 
505 	/* Check for invalid flags */
506 	if (flags && (flags & PAM_ESTABLISH_CRED) == 0 &&
507 	    (flags & PAM_REINITIALIZE_CRED) == 0 &&
508 	    (flags & PAM_REFRESH_CRED) == 0 &&
509 	    (flags & PAM_DELETE_CRED) == 0 &&
510 	    (flags & PAM_SILENT) == 0) {
511 		syslog(LOG_ERR, "pam_dhkeys: pam_setcred: illegal flags %d",
512 		    flags);
513 		return (PAM_SYSTEM_ERR);
514 	}
515 
516 
517 	if ((flags & PAM_REINITIALIZE_CRED) || (flags & PAM_REFRESH_CRED)) {
518 		/* doesn't apply to UNIX */
519 		if (debug)
520 			syslog(LOG_DEBUG, "pam_dhkeys: cred reinit/refresh "
521 			    "ignored\n");
522 		return (PAM_IGNORE);
523 	}
524 
525 	if (flags & PAM_DELETE_CRED) {
526 		if (debug)
527 			syslog(LOG_DEBUG, "pam_dhkeys: removing creds\n");
528 		result = remove_key(pamh, flags, debug);
529 	} else {
530 		result = establish_key(pamh, flags, debug, netname);
531 		/* Some diagnostics */
532 		if ((flags & PAM_SILENT) == 0) {
533 			if (result == PAM_AUTH_ERR)
534 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
535 				    "Password does not decrypt any secret "
536 				    "keys for %s."), netname);
537 			else if (result == PAM_SYSTEM_ERR && netname[0])
538 				(void) msg(pamh, dgettext(TEXT_DOMAIN,
539 				    "Could not set secret key(s) for %s. "
540 				    "The key server may be down."), netname);
541 		}
542 
543 		/* Not having credentials set is not an error... */
544 		result = PAM_IGNORE;
545 	}
546 
547 	return (result);
548 }
549 
550 /*ARGSUSED*/
551 void
552 rpc_cleanup(pam_handle_t *pamh, void *data, int pam_status)
553 {
554 	if (data) {
555 		(void) memset(data, 0, strlen(data));
556 		free(data);
557 	}
558 }
559 
560 /*ARGSUSED*/
561 int
562 pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
563 {
564 	return (PAM_IGNORE);
565 }
566