13a271666SJacques Vidrine /*- 23a271666SJacques Vidrine * Copyright (c) 2002 Jacques A. Vidrine <nectar@FreeBSD.org> 33a271666SJacques Vidrine * All rights reserved. 43a271666SJacques Vidrine * 53a271666SJacques Vidrine * Redistribution and use in source and binary forms, with or without 63a271666SJacques Vidrine * modification, are permitted provided that the following conditions 73a271666SJacques Vidrine * are met: 83a271666SJacques Vidrine * 1. Redistributions of source code must retain the above copyright 93a271666SJacques Vidrine * notice, this list of conditions and the following disclaimer. 103a271666SJacques Vidrine * 2. Redistributions in binary form must reproduce the above copyright 113a271666SJacques Vidrine * notice, this list of conditions and the following disclaimer in the 123a271666SJacques Vidrine * documentation and/or other materials provided with the distribution. 133a271666SJacques Vidrine * 143a271666SJacques Vidrine * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 153a271666SJacques Vidrine * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 163a271666SJacques Vidrine * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 173a271666SJacques Vidrine * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 183a271666SJacques Vidrine * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 193a271666SJacques Vidrine * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 203a271666SJacques Vidrine * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 213a271666SJacques Vidrine * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 223a271666SJacques Vidrine * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 233a271666SJacques Vidrine * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 243a271666SJacques Vidrine * SUCH DAMAGE. 253a271666SJacques Vidrine */ 263a271666SJacques Vidrine #include <sys/cdefs.h> 273a271666SJacques Vidrine __FBSDID("$FreeBSD$"); 283a271666SJacques Vidrine 293a271666SJacques Vidrine #include <sys/param.h> 303a271666SJacques Vidrine #include <errno.h> 313a271666SJacques Vidrine #include <stdio.h> 323a271666SJacques Vidrine #include <stdlib.h> 333a271666SJacques Vidrine #include <string.h> 343a271666SJacques Vidrine #include <unistd.h> 353a271666SJacques Vidrine 363a271666SJacques Vidrine #include <krb5.h> 373a271666SJacques Vidrine 383a271666SJacques Vidrine #define PAM_SM_AUTH 393a271666SJacques Vidrine #define PAM_SM_CRED 403a271666SJacques Vidrine #include <security/pam_appl.h> 413a271666SJacques Vidrine #include <security/pam_modules.h> 423a271666SJacques Vidrine #include <security/pam_mod_misc.h> 433a271666SJacques Vidrine 443a271666SJacques Vidrine static const char superuser[] = "root"; 453a271666SJacques Vidrine 463a271666SJacques Vidrine static long get_su_principal(krb5_context, const char *, const char *, 473a271666SJacques Vidrine char **, krb5_principal *); 483a271666SJacques Vidrine static int auth_krb5(pam_handle_t *, krb5_context, const char *, 493a271666SJacques Vidrine krb5_principal); 503a271666SJacques Vidrine 513a271666SJacques Vidrine PAM_EXTERN int 523a271666SJacques Vidrine pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 533a271666SJacques Vidrine int argc __unused, const char *argv[] __unused) 543a271666SJacques Vidrine { 553a271666SJacques Vidrine krb5_context context; 563a271666SJacques Vidrine krb5_principal su_principal; 5733b7c0d9SDag-Erling Smørgrav const char *user; 5833b7c0d9SDag-Erling Smørgrav const void *ruser; 593a271666SJacques Vidrine char *su_principal_name; 603a271666SJacques Vidrine long rv; 613a271666SJacques Vidrine int pamret; 623a271666SJacques Vidrine 633a271666SJacques Vidrine pamret = pam_get_user(pamh, &user, NULL); 643a271666SJacques Vidrine if (pamret != PAM_SUCCESS) 653a271666SJacques Vidrine return (pamret); 663a271666SJacques Vidrine PAM_LOG("Got user: %s", user); 6733b7c0d9SDag-Erling Smørgrav pamret = pam_get_item(pamh, PAM_RUSER, &ruser); 683a271666SJacques Vidrine if (pamret != PAM_SUCCESS) 693a271666SJacques Vidrine return (pamret); 70af9b4074SDag-Erling Smørgrav PAM_LOG("Got ruser: %s", (const char *)ruser); 713a271666SJacques Vidrine rv = krb5_init_context(&context); 723a271666SJacques Vidrine if (rv != 0) { 73*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 74*bbbc13f8SStanislav Sedov PAM_LOG("krb5_init_context failed: %s", msg); 75*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 763a271666SJacques Vidrine return (PAM_SERVICE_ERR); 773a271666SJacques Vidrine } 783a271666SJacques Vidrine rv = get_su_principal(context, user, ruser, &su_principal_name, &su_principal); 793a271666SJacques Vidrine if (rv != 0) 803a271666SJacques Vidrine return (PAM_AUTH_ERR); 813a271666SJacques Vidrine PAM_LOG("kuserok: %s -> %s", su_principal_name, user); 823a271666SJacques Vidrine rv = krb5_kuserok(context, su_principal, user); 833a271666SJacques Vidrine pamret = rv ? auth_krb5(pamh, context, su_principal_name, su_principal) : PAM_AUTH_ERR; 843a271666SJacques Vidrine free(su_principal_name); 853a271666SJacques Vidrine krb5_free_principal(context, su_principal); 863a271666SJacques Vidrine krb5_free_context(context); 873a271666SJacques Vidrine return (pamret); 883a271666SJacques Vidrine } 893a271666SJacques Vidrine 903a271666SJacques Vidrine PAM_EXTERN int 913a271666SJacques Vidrine pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 923a271666SJacques Vidrine int ac __unused, const char *av[] __unused) 933a271666SJacques Vidrine { 943a271666SJacques Vidrine 953a271666SJacques Vidrine return (PAM_SUCCESS); 963a271666SJacques Vidrine } 973a271666SJacques Vidrine 983a271666SJacques Vidrine /* Authenticate using Kerberos 5. 993a271666SJacques Vidrine * pamh -- The PAM handle. 1003a271666SJacques Vidrine * context -- An initialized krb5_context. 1013a271666SJacques Vidrine * su_principal_name -- The target principal name, used only for password prompts. 1023a271666SJacques Vidrine * If NULL, the password prompts will not include a principal 1033a271666SJacques Vidrine * name. 1043a271666SJacques Vidrine * su_principal -- The target krb5_principal. 1053a271666SJacques Vidrine * Note that a valid keytab in the default location with a host entry 1063a271666SJacques Vidrine * must be available, and that the PAM application must have sufficient 1073a271666SJacques Vidrine * privileges to access it. 1083a271666SJacques Vidrine * Returns PAM_SUCCESS if authentication was successful, or an appropriate 1093a271666SJacques Vidrine * PAM error code if it was not. 1103a271666SJacques Vidrine */ 1113a271666SJacques Vidrine static int 1123a271666SJacques Vidrine auth_krb5(pam_handle_t *pamh, krb5_context context, const char *su_principal_name, 1133a271666SJacques Vidrine krb5_principal su_principal) 1143a271666SJacques Vidrine { 1153a271666SJacques Vidrine krb5_creds creds; 116*bbbc13f8SStanislav Sedov krb5_get_init_creds_opt *gic_opt; 1173a271666SJacques Vidrine krb5_verify_init_creds_opt vic_opt; 1183a271666SJacques Vidrine const char *pass; 1193a271666SJacques Vidrine char *prompt; 1203a271666SJacques Vidrine long rv; 1213a271666SJacques Vidrine int pamret; 1223a271666SJacques Vidrine 1233a271666SJacques Vidrine prompt = NULL; 1243a271666SJacques Vidrine krb5_verify_init_creds_opt_init(&vic_opt); 1253a271666SJacques Vidrine if (su_principal_name != NULL) 1263a271666SJacques Vidrine (void)asprintf(&prompt, "Password for %s:", su_principal_name); 1273a271666SJacques Vidrine else 1283a271666SJacques Vidrine (void)asprintf(&prompt, "Password:"); 1293a271666SJacques Vidrine if (prompt == NULL) 1303a271666SJacques Vidrine return (PAM_BUF_ERR); 1313a271666SJacques Vidrine pass = NULL; 1323a271666SJacques Vidrine pamret = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt); 13333b7c0d9SDag-Erling Smørgrav free(prompt); 1343a271666SJacques Vidrine if (pamret != PAM_SUCCESS) 1353a271666SJacques Vidrine return (pamret); 136*bbbc13f8SStanislav Sedov rv = krb5_get_init_creds_opt_alloc(context, &gic_opt); 1373a271666SJacques Vidrine if (rv != 0) { 138*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 139*bbbc13f8SStanislav Sedov PAM_LOG("krb5_get_init_creds_opt_alloc: %s", msg); 140*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 141*bbbc13f8SStanislav Sedov return (PAM_AUTH_ERR); 142*bbbc13f8SStanislav Sedov } 143*bbbc13f8SStanislav Sedov rv = krb5_get_init_creds_password(context, &creds, su_principal, 144*bbbc13f8SStanislav Sedov pass, NULL, NULL, 0, NULL, gic_opt); 145*bbbc13f8SStanislav Sedov krb5_get_init_creds_opt_free(context, gic_opt); 146*bbbc13f8SStanislav Sedov if (rv != 0) { 147*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 148*bbbc13f8SStanislav Sedov PAM_LOG("krb5_get_init_creds_password: %s", msg); 149*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 1503a271666SJacques Vidrine return (PAM_AUTH_ERR); 1513a271666SJacques Vidrine } 1523a271666SJacques Vidrine krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1); 1533a271666SJacques Vidrine rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL, 1543a271666SJacques Vidrine &vic_opt); 1553a271666SJacques Vidrine krb5_free_cred_contents(context, &creds); 1563a271666SJacques Vidrine if (rv != 0) { 157*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 158*bbbc13f8SStanislav Sedov PAM_LOG("krb5_verify_init_creds: %s", msg); 159*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 1603a271666SJacques Vidrine return (PAM_AUTH_ERR); 1613a271666SJacques Vidrine } 1623a271666SJacques Vidrine return (PAM_SUCCESS); 1633a271666SJacques Vidrine } 1643a271666SJacques Vidrine 1653a271666SJacques Vidrine /* Determine the target principal given the current user and the target user. 1663a271666SJacques Vidrine * context -- An initialized krb5_context. 1673a271666SJacques Vidrine * target_user -- The target username. 1683a271666SJacques Vidrine * current_user -- The current username. 1693a271666SJacques Vidrine * su_principal_name -- (out) The target principal name. 1703a271666SJacques Vidrine * su_principal -- (out) The target krb5_principal. 1713a271666SJacques Vidrine * When the target user is `root', the target principal will be a `root 1723a271666SJacques Vidrine * instance', e.g. `luser/root@REA.LM'. Otherwise, the target principal 1733a271666SJacques Vidrine * will simply be the current user's default principal name. Note that 1743a271666SJacques Vidrine * in any case, if KRB5CCNAME is set and a credentials cache exists, the 1753a271666SJacques Vidrine * principal name found there will be the `starting point', rather than 1763a271666SJacques Vidrine * the ruser parameter. 1773a271666SJacques Vidrine * 1783a271666SJacques Vidrine * Returns 0 for success, or a com_err error code on failure. 1793a271666SJacques Vidrine */ 1803a271666SJacques Vidrine static long 1813a271666SJacques Vidrine get_su_principal(krb5_context context, const char *target_user, const char *current_user, 1823a271666SJacques Vidrine char **su_principal_name, krb5_principal *su_principal) 1833a271666SJacques Vidrine { 1843a271666SJacques Vidrine krb5_principal default_principal; 1853a271666SJacques Vidrine krb5_ccache ccache; 1863a271666SJacques Vidrine char *principal_name, *ccname, *p; 1873a271666SJacques Vidrine long rv; 1883a271666SJacques Vidrine uid_t euid, ruid; 1893a271666SJacques Vidrine 1903a271666SJacques Vidrine *su_principal = NULL; 1913a271666SJacques Vidrine default_principal = NULL; 1923a271666SJacques Vidrine /* Unless KRB5CCNAME was explicitly set, we won't really be able 1933a271666SJacques Vidrine * to look at the credentials cache since krb5_cc_default will 1943a271666SJacques Vidrine * look at getuid(). 1953a271666SJacques Vidrine */ 1963a271666SJacques Vidrine ruid = getuid(); 1973a271666SJacques Vidrine euid = geteuid(); 1983a271666SJacques Vidrine rv = seteuid(ruid); 1993a271666SJacques Vidrine if (rv != 0) 2003a271666SJacques Vidrine return (errno); 2013a271666SJacques Vidrine p = getenv("KRB5CCNAME"); 2023a271666SJacques Vidrine if (p != NULL) 2033a271666SJacques Vidrine ccname = strdup(p); 2043a271666SJacques Vidrine else 2053a271666SJacques Vidrine (void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT, (unsigned long)ruid); 2063a271666SJacques Vidrine if (ccname == NULL) 2073a271666SJacques Vidrine return (errno); 2083a271666SJacques Vidrine rv = krb5_cc_resolve(context, ccname, &ccache); 2093a271666SJacques Vidrine free(ccname); 2103a271666SJacques Vidrine if (rv == 0) { 2113a271666SJacques Vidrine rv = krb5_cc_get_principal(context, ccache, &default_principal); 2123a271666SJacques Vidrine krb5_cc_close(context, ccache); 2133a271666SJacques Vidrine if (rv != 0) 2143a271666SJacques Vidrine default_principal = NULL; /* just to be safe */ 2153a271666SJacques Vidrine } 2163a271666SJacques Vidrine rv = seteuid(euid); 2173a271666SJacques Vidrine if (rv != 0) 2183a271666SJacques Vidrine return (errno); 2193a271666SJacques Vidrine if (default_principal == NULL) { 2203a271666SJacques Vidrine rv = krb5_make_principal(context, &default_principal, NULL, current_user, NULL); 2213a271666SJacques Vidrine if (rv != 0) { 2223a271666SJacques Vidrine PAM_LOG("Could not determine default principal name."); 2233a271666SJacques Vidrine return (rv); 2243a271666SJacques Vidrine } 2253a271666SJacques Vidrine } 2263a271666SJacques Vidrine /* Now that we have some principal, if the target account is 2273a271666SJacques Vidrine * `root', then transform it into a `root' instance, e.g. 2283a271666SJacques Vidrine * `user@REA.LM' -> `user/root@REA.LM'. 2293a271666SJacques Vidrine */ 2303a271666SJacques Vidrine rv = krb5_unparse_name(context, default_principal, &principal_name); 2313a271666SJacques Vidrine krb5_free_principal(context, default_principal); 2323a271666SJacques Vidrine if (rv != 0) { 233*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 234*bbbc13f8SStanislav Sedov PAM_LOG("krb5_unparse_name: %s", msg); 235*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 2363a271666SJacques Vidrine return (rv); 2373a271666SJacques Vidrine } 2383a271666SJacques Vidrine PAM_LOG("Default principal name: %s", principal_name); 2393a271666SJacques Vidrine if (strcmp(target_user, superuser) == 0) { 2403a271666SJacques Vidrine p = strrchr(principal_name, '@'); 2413a271666SJacques Vidrine if (p == NULL) { 2423a271666SJacques Vidrine PAM_LOG("malformed principal name `%s'", principal_name); 2433a271666SJacques Vidrine free(principal_name); 2443a271666SJacques Vidrine return (rv); 2453a271666SJacques Vidrine } 2463a271666SJacques Vidrine *p++ = '\0'; 2473a271666SJacques Vidrine *su_principal_name = NULL; 2483a271666SJacques Vidrine (void)asprintf(su_principal_name, "%s/%s@%s", principal_name, superuser, p); 2493a271666SJacques Vidrine free(principal_name); 2503a271666SJacques Vidrine } else 2513a271666SJacques Vidrine *su_principal_name = principal_name; 2523a271666SJacques Vidrine 2533a271666SJacques Vidrine if (*su_principal_name == NULL) 2543a271666SJacques Vidrine return (errno); 2553a271666SJacques Vidrine rv = krb5_parse_name(context, *su_principal_name, &default_principal); 2563a271666SJacques Vidrine if (rv != 0) { 257*bbbc13f8SStanislav Sedov const char *msg = krb5_get_error_message(context, rv); 258*bbbc13f8SStanislav Sedov PAM_LOG("krb5_parse_name `%s': %s", *su_principal_name, msg); 259*bbbc13f8SStanislav Sedov krb5_free_error_message(context, msg); 2603a271666SJacques Vidrine free(*su_principal_name); 2613a271666SJacques Vidrine return (rv); 2623a271666SJacques Vidrine } 2633a271666SJacques Vidrine PAM_LOG("Target principal name: %s", *su_principal_name); 2643a271666SJacques Vidrine *su_principal = default_principal; 2653a271666SJacques Vidrine return (0); 2663a271666SJacques Vidrine } 2673fdd8a40SDag-Erling Smørgrav 2683fdd8a40SDag-Erling Smørgrav PAM_MODULE_ENTRY("pam_ksu"); 269