xref: /netbsd-src/lib/libpam/modules/pam_ksu/pam_ksu.c (revision 83df26257c01073efac14ebb98c564a55b2ce551)
1*83df2625Sriastradh /*	$NetBSD: pam_ksu.c,v 1.11 2023/09/07 11:27:57 riastradh Exp $	*/
2e7d22a2eSchristos 
36f11bdf1Schristos /*-
46f11bdf1Schristos  * Copyright (c) 2002 Jacques A. Vidrine <nectar@FreeBSD.org>
56f11bdf1Schristos  * All rights reserved.
66f11bdf1Schristos  *
76f11bdf1Schristos  * Redistribution and use in source and binary forms, with or without
86f11bdf1Schristos  * modification, are permitted provided that the following conditions
96f11bdf1Schristos  * are met:
106f11bdf1Schristos  * 1. Redistributions of source code must retain the above copyright
116f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer.
126f11bdf1Schristos  * 2. Redistributions in binary form must reproduce the above copyright
136f11bdf1Schristos  *    notice, this list of conditions and the following disclaimer in the
146f11bdf1Schristos  *    documentation and/or other materials provided with the distribution.
156f11bdf1Schristos  *
166f11bdf1Schristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
176f11bdf1Schristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
186f11bdf1Schristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
196f11bdf1Schristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
206f11bdf1Schristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
216f11bdf1Schristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
226f11bdf1Schristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
236f11bdf1Schristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
246f11bdf1Schristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
256f11bdf1Schristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
266f11bdf1Schristos  * SUCH DAMAGE.
276f11bdf1Schristos  */
286f11bdf1Schristos #include <sys/cdefs.h>
29e7d22a2eSchristos #ifdef __FreeBSD__
306f11bdf1Schristos __FBSDID("$FreeBSD: src/lib/libpam/modules/pam_ksu/pam_ksu.c,v 1.5 2004/02/10 10:13:21 des Exp $");
31e7d22a2eSchristos #else
32*83df2625Sriastradh __RCSID("$NetBSD: pam_ksu.c,v 1.11 2023/09/07 11:27:57 riastradh Exp $");
33e7d22a2eSchristos #endif
346f11bdf1Schristos 
356f11bdf1Schristos #include <sys/param.h>
366f11bdf1Schristos #include <errno.h>
376f11bdf1Schristos #include <stdio.h>
386f11bdf1Schristos #include <stdlib.h>
396f11bdf1Schristos #include <string.h>
406f11bdf1Schristos #include <unistd.h>
416f11bdf1Schristos 
42e7d22a2eSchristos #include <krb5/krb5.h>
436f11bdf1Schristos 
446f11bdf1Schristos #define PAM_SM_AUTH
456f11bdf1Schristos #define PAM_SM_CRED
466f11bdf1Schristos #include <security/pam_appl.h>
476f11bdf1Schristos #include <security/pam_modules.h>
486f11bdf1Schristos #include <security/pam_mod_misc.h>
496f11bdf1Schristos 
506f11bdf1Schristos static const char superuser[] = "root";
516f11bdf1Schristos 
52ea6a01a6Schristos #define PASSWORD_PROMPT	"%s's password:"
53ea6a01a6Schristos 
544a04b195Schristos static void	log_krb5(krb5_context, krb5_error_code, const char *, ...)
554a04b195Schristos     __printflike(3, 4);
569778b180Schristos static krb5_error_code	get_su_principal(krb5_context, const char *,
579778b180Schristos     const char *, char **, krb5_principal *);
586f11bdf1Schristos static int	auth_krb5(pam_handle_t *, krb5_context, const char *,
596f11bdf1Schristos 		    krb5_principal);
606f11bdf1Schristos 
616f11bdf1Schristos PAM_EXTERN int
pam_sm_authenticate(pam_handle_t * pamh,int flags __unused,int argc __unused,const char * argv[]__unused)626f11bdf1Schristos pam_sm_authenticate(pam_handle_t *pamh, int flags __unused,
636f11bdf1Schristos     int argc __unused, const char *argv[] __unused)
646f11bdf1Schristos {
65f9fe2f8cSriastradh 	krb5_boolean	 allow_homedir;
666f11bdf1Schristos 	krb5_context	 context;
676f11bdf1Schristos 	krb5_principal	 su_principal;
686f11bdf1Schristos 	const char	*user;
696f11bdf1Schristos 	const void	*ruser;
706f11bdf1Schristos 	char		*su_principal_name;
719778b180Schristos 	krb5_error_code	 rv;
726f11bdf1Schristos 	int		 pamret;
736f11bdf1Schristos 
746f11bdf1Schristos 	pamret = pam_get_user(pamh, &user, NULL);
756f11bdf1Schristos 	if (pamret != PAM_SUCCESS)
766f11bdf1Schristos 		return (pamret);
776f11bdf1Schristos 	PAM_LOG("Got user: %s", user);
786f11bdf1Schristos 	pamret = pam_get_item(pamh, PAM_RUSER, &ruser);
796f11bdf1Schristos 	if (pamret != PAM_SUCCESS)
806f11bdf1Schristos 		return (pamret);
816f11bdf1Schristos 	PAM_LOG("Got ruser: %s", (const char *)ruser);
82f9fe2f8cSriastradh 	allow_homedir = krb5_set_home_dir_access(NULL, FALSE);
836f11bdf1Schristos 	rv = krb5_init_context(&context);
846f11bdf1Schristos 	if (rv != 0) {
854a04b195Schristos 		log_krb5(context, rv, "krb5_init_context failed");
86f9fe2f8cSriastradh 		pamret = PAM_SERVICE_ERR;
87f9fe2f8cSriastradh 		goto out;
886f11bdf1Schristos 	}
896f11bdf1Schristos 	rv = get_su_principal(context, user, ruser, &su_principal_name, &su_principal);
90f9fe2f8cSriastradh 	if (rv != 0) {
91f9fe2f8cSriastradh 		pamret = PAM_AUTH_ERR;
92f9fe2f8cSriastradh 		goto out;
93f9fe2f8cSriastradh 	}
946f11bdf1Schristos 	PAM_LOG("kuserok: %s -> %s", su_principal_name, user);
95*83df2625Sriastradh 	(void)krb5_set_home_dir_access(NULL, TRUE); /* ~user/.k5login */
966f11bdf1Schristos 	rv = krb5_kuserok(context, su_principal, user);
97*83df2625Sriastradh 	(void)krb5_set_home_dir_access(NULL, FALSE);
986f11bdf1Schristos 	pamret = rv ? auth_krb5(pamh, context, su_principal_name, su_principal) : PAM_AUTH_ERR;
996f11bdf1Schristos 	free(su_principal_name);
1006f11bdf1Schristos 	krb5_free_principal(context, su_principal);
1016f11bdf1Schristos 	krb5_free_context(context);
102f9fe2f8cSriastradh out:	(void)krb5_set_home_dir_access(NULL, allow_homedir);
1036f11bdf1Schristos 	return (pamret);
1046f11bdf1Schristos }
1056f11bdf1Schristos 
1066f11bdf1Schristos PAM_EXTERN int
pam_sm_setcred(pam_handle_t * pamh __unused,int flags __unused,int ac __unused,const char * av[]__unused)1076f11bdf1Schristos pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused,
1086f11bdf1Schristos     int ac __unused, const char *av[] __unused)
1096f11bdf1Schristos {
1106f11bdf1Schristos 
1116f11bdf1Schristos 	return (PAM_SUCCESS);
1126f11bdf1Schristos }
1136f11bdf1Schristos 
1146f11bdf1Schristos /* Authenticate using Kerberos 5.
1156f11bdf1Schristos  *   pamh              -- The PAM handle.
1166f11bdf1Schristos  *   context           -- An initialized krb5_context.
1176f11bdf1Schristos  *   su_principal_name -- The target principal name, used only for password prompts.
1186f11bdf1Schristos  *              If NULL, the password prompts will not include a principal
1196f11bdf1Schristos  *              name.
1206f11bdf1Schristos  *   su_principal      -- The target krb5_principal.
1216f11bdf1Schristos  * Note that a valid keytab in the default location with a host entry
1226f11bdf1Schristos  * must be available, and that the PAM application must have sufficient
1236f11bdf1Schristos  * privileges to access it.
1246f11bdf1Schristos  * Returns PAM_SUCCESS if authentication was successful, or an appropriate
1256f11bdf1Schristos  * PAM error code if it was not.
1266f11bdf1Schristos  */
1276f11bdf1Schristos static int
auth_krb5(pam_handle_t * pamh,krb5_context context,const char * su_principal_name,krb5_principal su_principal)1286f11bdf1Schristos auth_krb5(pam_handle_t *pamh, krb5_context context, const char *su_principal_name,
1296f11bdf1Schristos     krb5_principal su_principal)
1306f11bdf1Schristos {
1316f11bdf1Schristos 	krb5_creds	 creds;
1320fce8776Selric 	krb5_get_init_creds_opt *gic_opt;
1336f11bdf1Schristos 	krb5_verify_init_creds_opt vic_opt;
1346f11bdf1Schristos 	const char	*pass;
135ea6a01a6Schristos 	char		 prompt[80];
1369778b180Schristos 	krb5_error_code	 rv;
1376f11bdf1Schristos 	int		 pamret;
1386f11bdf1Schristos 
1390fce8776Selric 	rv = krb5_get_init_creds_opt_alloc(context, &gic_opt);
1400fce8776Selric 	if (rv != 0) {
1414a04b195Schristos 		log_krb5(context, rv, "krb5_get_init_creds_opt_alloc");
1420fce8776Selric 		return (PAM_SERVICE_ERR);
1430fce8776Selric 	}
1446f11bdf1Schristos 	krb5_verify_init_creds_opt_init(&vic_opt);
1456f11bdf1Schristos 	if (su_principal_name != NULL)
146ea6a01a6Schristos 		(void)snprintf(prompt, sizeof(prompt), PASSWORD_PROMPT,
147ea6a01a6Schristos 		    su_principal_name);
1486f11bdf1Schristos 	else
149ea6a01a6Schristos 		(void)snprintf(prompt, sizeof(prompt), "Password:");
1506f11bdf1Schristos 	pass = NULL;
1516f11bdf1Schristos 	pamret = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt);
1526f11bdf1Schristos 	if (pamret != PAM_SUCCESS)
1536f11bdf1Schristos 		return (pamret);
1546f11bdf1Schristos 	rv = krb5_get_init_creds_password(context, &creds, su_principal,
1550fce8776Selric 	    pass, NULL, NULL, 0, NULL, gic_opt);
1566f11bdf1Schristos 	if (rv != 0) {
1574a04b195Schristos 		log_krb5(context, rv, "krb5_get_init_creds_password");
1586f11bdf1Schristos 		return (PAM_AUTH_ERR);
1596f11bdf1Schristos 	}
1606f11bdf1Schristos 	krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1);
1616f11bdf1Schristos 	rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL,
1626f11bdf1Schristos 	    &vic_opt);
1636f11bdf1Schristos 	krb5_free_cred_contents(context, &creds);
1646f11bdf1Schristos 	if (rv != 0) {
1654a04b195Schristos 		log_krb5(context, rv, "krb5_verify_init_creds");
1666f11bdf1Schristos 		return (PAM_AUTH_ERR);
1676f11bdf1Schristos 	}
1686f11bdf1Schristos 	return (PAM_SUCCESS);
1696f11bdf1Schristos }
1706f11bdf1Schristos 
1710fce8776Selric static void
log_krb5(krb5_context ctx,krb5_error_code err,const char * fmt,...)1724a04b195Schristos log_krb5(krb5_context ctx, krb5_error_code err, const char *fmt, ...)
1730fce8776Selric {
1744a04b195Schristos 	char b1[1024], b2[1024];
1750fce8776Selric 	const char *errtxt;
1764a04b195Schristos 	va_list ap;
1770fce8776Selric 
1784a04b195Schristos 	va_start(ap, fmt);
1794a04b195Schristos 	vsnprintf(b1, sizeof(b1), fmt, ap);
1804a04b195Schristos 	va_end(ap);
1814a04b195Schristos 	if (ctx)
1820fce8776Selric 		errtxt = krb5_get_error_message(ctx, err);
1834a04b195Schristos 	else
1844a04b195Schristos 		errtxt = NULL;
1850fce8776Selric 	if (errtxt != NULL) {
1868cf33d7fSchristos 		snprintf(b2, sizeof(b2), "%s", errtxt);
1870fce8776Selric 		krb5_free_error_message(ctx, errtxt);
1880fce8776Selric 	} else {
1898cf33d7fSchristos 		snprintf(b2, sizeof(b2), "unknown %d", (int)err);
1900fce8776Selric 	}
1918cf33d7fSchristos 	PAM_LOG("%s (%s)", b1, b2);
1920fce8776Selric }
1930fce8776Selric 
1946f11bdf1Schristos /* Determine the target principal given the current user and the target user.
1956f11bdf1Schristos  *   context           -- An initialized krb5_context.
1966f11bdf1Schristos  *   target_user       -- The target username.
1976f11bdf1Schristos  *   current_user      -- The current username.
1986f11bdf1Schristos  *   su_principal_name -- (out) The target principal name.
1996f11bdf1Schristos  *   su_principal      -- (out) The target krb5_principal.
2006f11bdf1Schristos  * When the target user is `root', the target principal will be a `root
2016f11bdf1Schristos  * instance', e.g. `luser/root@REA.LM'.  Otherwise, the target principal
2026f11bdf1Schristos  * will simply be the current user's default principal name.  Note that
2036f11bdf1Schristos  * in any case, if KRB5CCNAME is set and a credentials cache exists, the
2046f11bdf1Schristos  * principal name found there will be the `starting point', rather than
2056f11bdf1Schristos  * the ruser parameter.
2066f11bdf1Schristos  *
2076f11bdf1Schristos  * Returns 0 for success, or a com_err error code on failure.
2086f11bdf1Schristos  */
2099778b180Schristos static krb5_error_code
get_su_principal(krb5_context context,const char * target_user,const char * current_user,char ** su_principal_name,krb5_principal * su_principal)2106f11bdf1Schristos get_su_principal(krb5_context context, const char *target_user, const char *current_user,
2116f11bdf1Schristos     char **su_principal_name, krb5_principal *su_principal)
2126f11bdf1Schristos {
2136f11bdf1Schristos 	krb5_principal	 default_principal;
2146f11bdf1Schristos 	krb5_ccache	 ccache;
2156f11bdf1Schristos 	char		*principal_name, *ccname, *p;
2169778b180Schristos 	krb5_error_code	 rv;
2176f11bdf1Schristos 	uid_t		 euid, ruid;
2186f11bdf1Schristos 
2196f11bdf1Schristos 	*su_principal = NULL;
2206f11bdf1Schristos 	default_principal = NULL;
2216f11bdf1Schristos 	/* Unless KRB5CCNAME was explicitly set, we won't really be able
2226f11bdf1Schristos 	 * to look at the credentials cache since krb5_cc_default will
2236f11bdf1Schristos 	 * look at getuid().
2246f11bdf1Schristos 	 */
2256f11bdf1Schristos 	ruid = getuid();
2266f11bdf1Schristos 	euid = geteuid();
2276f11bdf1Schristos 	rv = seteuid(ruid);
2286f11bdf1Schristos 	if (rv != 0)
2296f11bdf1Schristos 		return (errno);
2306f11bdf1Schristos 	p = getenv("KRB5CCNAME");
2316f11bdf1Schristos 	if (p != NULL)
2326f11bdf1Schristos 		ccname = strdup(p);
2336f11bdf1Schristos 	else
2346f11bdf1Schristos 		(void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT, (unsigned long)ruid);
2356f11bdf1Schristos 	if (ccname == NULL)
2366f11bdf1Schristos 		return (errno);
2376f11bdf1Schristos 	rv = krb5_cc_resolve(context, ccname, &ccache);
2386f11bdf1Schristos 	free(ccname);
2396f11bdf1Schristos 	if (rv == 0) {
2406f11bdf1Schristos 		rv = krb5_cc_get_principal(context, ccache, &default_principal);
2416f11bdf1Schristos 		krb5_cc_close(context, ccache);
2426f11bdf1Schristos 		if (rv != 0)
2436f11bdf1Schristos 			default_principal = NULL; /* just to be safe */
2446f11bdf1Schristos 	}
2456f11bdf1Schristos 	rv = seteuid(euid);
2466f11bdf1Schristos 	if (rv != 0)
2476f11bdf1Schristos 		return (errno);
2486f11bdf1Schristos 	if (default_principal == NULL) {
2496f11bdf1Schristos 		rv = krb5_make_principal(context, &default_principal, NULL, current_user, NULL);
2506f11bdf1Schristos 		if (rv != 0) {
2516f11bdf1Schristos 			PAM_LOG("Could not determine default principal name.");
2526f11bdf1Schristos 			return (rv);
2536f11bdf1Schristos 		}
2546f11bdf1Schristos 	}
2556f11bdf1Schristos 	/* Now that we have some principal, if the target account is
2566f11bdf1Schristos 	 * `root', then transform it into a `root' instance, e.g.
2576f11bdf1Schristos 	 * `user@REA.LM' -> `user/root@REA.LM'.
2586f11bdf1Schristos 	 */
2596f11bdf1Schristos 	rv = krb5_unparse_name(context, default_principal, &principal_name);
2606f11bdf1Schristos 	krb5_free_principal(context, default_principal);
2616f11bdf1Schristos 	if (rv != 0) {
2624a04b195Schristos 		log_krb5(context, rv, "krb5_unparse_name");
2636f11bdf1Schristos 		return (rv);
2646f11bdf1Schristos 	}
2656f11bdf1Schristos 	PAM_LOG("Default principal name: %s", principal_name);
2666f11bdf1Schristos 	if (strcmp(target_user, superuser) == 0) {
2676f11bdf1Schristos 		p = strrchr(principal_name, '@');
2686f11bdf1Schristos 		if (p == NULL) {
2696f11bdf1Schristos 			PAM_LOG("malformed principal name `%s'", principal_name);
2706f11bdf1Schristos 			free(principal_name);
2716f11bdf1Schristos 			return (rv);
2726f11bdf1Schristos 		}
2736f11bdf1Schristos 		*p++ = '\0';
2746f11bdf1Schristos 		*su_principal_name = NULL;
2756f11bdf1Schristos 		(void)asprintf(su_principal_name, "%s/%s@%s", principal_name, superuser, p);
2766f11bdf1Schristos 		free(principal_name);
2776f11bdf1Schristos 	} else
2786f11bdf1Schristos 		*su_principal_name = principal_name;
2796f11bdf1Schristos 
2806f11bdf1Schristos 	if (*su_principal_name == NULL)
2816f11bdf1Schristos 		return (errno);
2826f11bdf1Schristos 	rv = krb5_parse_name(context, *su_principal_name, &default_principal);
2836f11bdf1Schristos 	if (rv != 0) {
2844a04b195Schristos 		log_krb5(context, rv, "krb5_parse_name `%s'",
2854a04b195Schristos 		    *su_principal_name);
2866f11bdf1Schristos 		return (rv);
2876f11bdf1Schristos 	}
2886f11bdf1Schristos 	PAM_LOG("Target principal name: %s", *su_principal_name);
2896f11bdf1Schristos 	*su_principal = default_principal;
2906f11bdf1Schristos 	return (0);
2916f11bdf1Schristos }
2926f11bdf1Schristos 
2936f11bdf1Schristos PAM_MODULE_ENTRY("pam_ksu");
294