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