1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2002 Jacques A. Vidrine <nectar@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/param.h> 30 #include <errno.h> 31 #include <stdio.h> 32 #include <stdlib.h> 33 #include <string.h> 34 #include <unistd.h> 35 36 #include <krb5.h> 37 38 #define PAM_SM_AUTH 39 #define PAM_SM_CRED 40 #include <security/pam_appl.h> 41 #include <security/pam_modules.h> 42 #include <security/pam_mod_misc.h> 43 44 static const char superuser[] = "root"; 45 46 static long get_su_principal(krb5_context, const char *, const char *, 47 char **, krb5_principal *); 48 static int auth_krb5(pam_handle_t *, krb5_context, const char *, 49 krb5_principal); 50 51 #ifdef MK_MITKRB5 52 /* For MIT KRB5 only. */ 53 54 /* 55 * XXX This entire module will need to be rewritten when heimdal 56 * XXX compatidibility is no longer needed. 57 */ 58 #define KRB5_DEFAULT_CCFILE_ROOT "/tmp/krb5cc_" 59 #define KRB5_DEFAULT_CCROOT "FILE:" KRB5_DEFAULT_CCFILE_ROOT 60 61 /* 62 * XXX We will replace krb5_build_principal_va() with 63 * XXX krb5_build_principal_alloc_va() when Heimdal is finally 64 * XXX removed. 65 */ 66 krb5_error_code KRB5_CALLCONV 67 krb5_build_principal_va(krb5_context context, 68 krb5_principal princ, 69 unsigned int rlen, 70 const char *realm, 71 va_list ap); 72 typedef char *heim_general_string; 73 typedef heim_general_string Realm; 74 typedef Realm krb5_realm; 75 typedef const char *krb5_const_realm; 76 77 static krb5_error_code 78 krb5_make_principal(krb5_context context, krb5_principal principal, 79 krb5_const_realm realm, ...) 80 { 81 krb5_realm temp_realm = NULL; 82 krb5_error_code rc; 83 va_list ap; 84 85 if (realm == NULL) { 86 if ((rc = krb5_get_default_realm(context, &temp_realm))) 87 return (rc); 88 realm=temp_realm; 89 } 90 va_start(ap, realm); 91 /* 92 * XXX Ideally we should be using krb5_build_principal_alloc_va() 93 * XXX here because krb5_build_principal_va() is deprecated. But, 94 * XXX this would require changes elsewhere in the calling code 95 * XXX to call krb5_free_principal() elsewhere to free the 96 * XXX principal. We can do that after Heimdal is removed from 97 * XXX our tree. 98 */ 99 rc = krb5_build_principal_va(context, principal, strlen(realm), realm, ap); 100 va_end(ap); 101 if (temp_realm) 102 free(temp_realm); 103 return (rc); 104 } 105 #endif 106 107 PAM_EXTERN int 108 pam_sm_authenticate(pam_handle_t *pamh, int flags __unused, 109 int argc __unused, const char *argv[] __unused) 110 { 111 krb5_context context; 112 krb5_principal su_principal; 113 const char *user; 114 const void *ruser; 115 char *su_principal_name; 116 long rv; 117 int pamret; 118 119 pamret = pam_get_user(pamh, &user, NULL); 120 if (pamret != PAM_SUCCESS) 121 return (pamret); 122 PAM_LOG("Got user: %s", user); 123 pamret = pam_get_item(pamh, PAM_RUSER, &ruser); 124 if (pamret != PAM_SUCCESS) 125 return (pamret); 126 PAM_LOG("Got ruser: %s", (const char *)ruser); 127 rv = krb5_init_context(&context); 128 if (rv != 0) { 129 const char *msg = krb5_get_error_message(context, rv); 130 PAM_LOG("krb5_init_context failed: %s", msg); 131 krb5_free_error_message(context, msg); 132 return (PAM_SERVICE_ERR); 133 } 134 rv = get_su_principal(context, user, ruser, &su_principal_name, &su_principal); 135 if (rv != 0) 136 return (PAM_AUTH_ERR); 137 PAM_LOG("kuserok: %s -> %s", su_principal_name, user); 138 rv = krb5_kuserok(context, su_principal, user); 139 pamret = rv ? auth_krb5(pamh, context, su_principal_name, su_principal) : PAM_AUTH_ERR; 140 free(su_principal_name); 141 krb5_free_principal(context, su_principal); 142 krb5_free_context(context); 143 return (pamret); 144 } 145 146 PAM_EXTERN int 147 pam_sm_setcred(pam_handle_t *pamh __unused, int flags __unused, 148 int ac __unused, const char *av[] __unused) 149 { 150 151 return (PAM_SUCCESS); 152 } 153 154 /* Authenticate using Kerberos 5. 155 * pamh -- The PAM handle. 156 * context -- An initialized krb5_context. 157 * su_principal_name -- The target principal name, used only for password prompts. 158 * If NULL, the password prompts will not include a principal 159 * name. 160 * su_principal -- The target krb5_principal. 161 * Note that a valid keytab in the default location with a host entry 162 * must be available, and that the PAM application must have sufficient 163 * privileges to access it. 164 * Returns PAM_SUCCESS if authentication was successful, or an appropriate 165 * PAM error code if it was not. 166 */ 167 static int 168 auth_krb5(pam_handle_t *pamh, krb5_context context, const char *su_principal_name, 169 krb5_principal su_principal) 170 { 171 krb5_creds creds; 172 krb5_get_init_creds_opt *gic_opt; 173 krb5_verify_init_creds_opt vic_opt; 174 const char *pass; 175 char *prompt; 176 long rv; 177 int pamret; 178 179 prompt = NULL; 180 krb5_verify_init_creds_opt_init(&vic_opt); 181 if (su_principal_name != NULL) 182 (void)asprintf(&prompt, "Password for %s:", su_principal_name); 183 else 184 (void)asprintf(&prompt, "Password:"); 185 if (prompt == NULL) 186 return (PAM_BUF_ERR); 187 pass = NULL; 188 pamret = pam_get_authtok(pamh, PAM_AUTHTOK, &pass, prompt); 189 free(prompt); 190 if (pamret != PAM_SUCCESS) 191 return (pamret); 192 rv = krb5_get_init_creds_opt_alloc(context, &gic_opt); 193 if (rv != 0) { 194 const char *msg = krb5_get_error_message(context, rv); 195 PAM_LOG("krb5_get_init_creds_opt_alloc: %s", msg); 196 krb5_free_error_message(context, msg); 197 return (PAM_AUTH_ERR); 198 } 199 rv = krb5_get_init_creds_password(context, &creds, su_principal, 200 pass, NULL, NULL, 0, NULL, gic_opt); 201 krb5_get_init_creds_opt_free(context, gic_opt); 202 if (rv != 0) { 203 const char *msg = krb5_get_error_message(context, rv); 204 PAM_LOG("krb5_get_init_creds_password: %s", msg); 205 krb5_free_error_message(context, msg); 206 return (PAM_AUTH_ERR); 207 } 208 krb5_verify_init_creds_opt_set_ap_req_nofail(&vic_opt, 1); 209 rv = krb5_verify_init_creds(context, &creds, NULL, NULL, NULL, 210 &vic_opt); 211 krb5_free_cred_contents(context, &creds); 212 if (rv != 0) { 213 const char *msg = krb5_get_error_message(context, rv); 214 PAM_LOG("krb5_verify_init_creds: %s", msg); 215 krb5_free_error_message(context, msg); 216 return (PAM_AUTH_ERR); 217 } 218 return (PAM_SUCCESS); 219 } 220 221 /* Determine the target principal given the current user and the target user. 222 * context -- An initialized krb5_context. 223 * target_user -- The target username. 224 * current_user -- The current username. 225 * su_principal_name -- (out) The target principal name. 226 * su_principal -- (out) The target krb5_principal. 227 * When the target user is `root', the target principal will be a `root 228 * instance', e.g. `luser/root@REA.LM'. Otherwise, the target principal 229 * will simply be the current user's default principal name. Note that 230 * in any case, if KRB5CCNAME is set and a credentials cache exists, the 231 * principal name found there will be the `starting point', rather than 232 * the ruser parameter. 233 * 234 * Returns 0 for success, or a com_err error code on failure. 235 */ 236 static long 237 get_su_principal(krb5_context context, const char *target_user, const char *current_user, 238 char **su_principal_name, krb5_principal *su_principal) 239 { 240 krb5_principal default_principal; 241 krb5_ccache ccache; 242 char *principal_name, *ccname, *p; 243 long rv; 244 uid_t euid, ruid; 245 246 *su_principal = NULL; 247 default_principal = NULL; 248 /* Unless KRB5CCNAME was explicitly set, we won't really be able 249 * to look at the credentials cache since krb5_cc_default will 250 * look at getuid(). 251 */ 252 ruid = getuid(); 253 euid = geteuid(); 254 rv = seteuid(ruid); 255 if (rv != 0) 256 return (errno); 257 p = getenv("KRB5CCNAME"); 258 if (p != NULL) 259 ccname = strdup(p); 260 else 261 (void)asprintf(&ccname, "%s%lu", KRB5_DEFAULT_CCROOT, (unsigned long)ruid); 262 if (ccname == NULL) 263 return (errno); 264 rv = krb5_cc_resolve(context, ccname, &ccache); 265 free(ccname); 266 if (rv == 0) { 267 rv = krb5_cc_get_principal(context, ccache, &default_principal); 268 krb5_cc_close(context, ccache); 269 if (rv != 0) 270 default_principal = NULL; /* just to be safe */ 271 } 272 rv = seteuid(euid); 273 if (rv != 0) 274 return (errno); 275 if (default_principal == NULL) { 276 #ifdef MK_MITKRB5 277 /* For MIT KRB5. */ 278 rv = krb5_make_principal(context, default_principal, NULL, current_user, NULL); 279 #else 280 /* For Heimdal. */ 281 rv = krb5_make_principal(context, &default_principal, NULL, current_user, NULL); 282 #endif 283 if (rv != 0) { 284 PAM_LOG("Could not determine default principal name."); 285 return (rv); 286 } 287 } 288 /* Now that we have some principal, if the target account is 289 * `root', then transform it into a `root' instance, e.g. 290 * `user@REA.LM' -> `user/root@REA.LM'. 291 */ 292 rv = krb5_unparse_name(context, default_principal, &principal_name); 293 krb5_free_principal(context, default_principal); 294 if (rv != 0) { 295 const char *msg = krb5_get_error_message(context, rv); 296 PAM_LOG("krb5_unparse_name: %s", msg); 297 krb5_free_error_message(context, msg); 298 return (rv); 299 } 300 PAM_LOG("Default principal name: %s", principal_name); 301 if (strcmp(target_user, superuser) == 0) { 302 p = strrchr(principal_name, '@'); 303 if (p == NULL) { 304 PAM_LOG("malformed principal name `%s'", principal_name); 305 free(principal_name); 306 return (rv); 307 } 308 *p++ = '\0'; 309 *su_principal_name = NULL; 310 (void)asprintf(su_principal_name, "%s/%s@%s", principal_name, superuser, p); 311 free(principal_name); 312 } else 313 *su_principal_name = principal_name; 314 315 if (*su_principal_name == NULL) 316 return (errno); 317 rv = krb5_parse_name(context, *su_principal_name, &default_principal); 318 if (rv != 0) { 319 const char *msg = krb5_get_error_message(context, rv); 320 PAM_LOG("krb5_parse_name `%s': %s", *su_principal_name, msg); 321 krb5_free_error_message(context, msg); 322 free(*su_principal_name); 323 return (rv); 324 } 325 PAM_LOG("Target principal name: %s", *su_principal_name); 326 *su_principal = default_principal; 327 return (0); 328 } 329 330 PAM_MODULE_ENTRY("pam_ksu"); 331