1 /* 2 * Support for alternate authentication mapping. 3 * 4 * pam-krb5 supports a feature where the principal for authentication can be 5 * set via a PAM option and possibly based on the authenticating user. This 6 * can be used to, for example, require /root instances be used with sudo 7 * while still using normal instances for other system authentications. 8 * 9 * This file collects all the pieces related to that support. 10 * 11 * Original support written by Booker Bense <bbense@slac.stanford.edu> 12 * Further updates by Russ Allbery <eagle@eyrie.org> 13 * Copyright 2020 Russ Allbery <eagle@eyrie.org> 14 * Copyright 2008-2012 15 * The Board of Trustees of the Leland Stanford Junior University 16 * 17 * SPDX-License-Identifier: BSD-3-clause or GPL-1+ 18 */ 19 20 #include <config.h> 21 #include <portable/krb5.h> 22 #include <portable/pam.h> 23 #include <portable/system.h> 24 25 #include <errno.h> 26 27 #include <module/internal.h> 28 #include <pam-util/args.h> 29 #include <pam-util/logging.h> 30 31 32 /* 33 * Map the user to a Kerberos principal according to alt_auth_map. Returns 0 34 * on success, storing the mapped principal name in newly allocated memory in 35 * principal. The caller is responsible for freeing. Returns an errno value 36 * on any error. 37 */ 38 int 39 pamk5_map_principal(struct pam_args *args, const char *username, 40 char **principal) 41 { 42 char *realm; 43 char *new_user = NULL; 44 const char *user; 45 const char *p; 46 size_t needed, offset; 47 int oerrno; 48 49 /* Makes no sense if alt_auth_map isn't set. */ 50 if (args->config->alt_auth_map == NULL) 51 return EINVAL; 52 53 /* Need to split off the realm if it is present. */ 54 realm = strchr(username, '@'); 55 if (realm == NULL) 56 user = username; 57 else { 58 new_user = strdup(username); 59 if (new_user == NULL) 60 return errno; 61 realm = strchr(new_user, '@'); 62 if (realm == NULL) 63 goto fail; 64 *realm = '\0'; 65 realm++; 66 user = new_user; 67 } 68 69 /* Now, allocate a string and build the principal. */ 70 needed = 0; 71 for (p = args->config->alt_auth_map; *p != '\0'; p++) { 72 if (p[0] == '%' && p[1] == 's') { 73 needed += strlen(user); 74 p++; 75 } else { 76 needed++; 77 } 78 } 79 if (realm != NULL && strchr(args->config->alt_auth_map, '@') == NULL) 80 needed += 1 + strlen(realm); 81 needed++; 82 *principal = malloc(needed); 83 if (*principal == NULL) 84 goto fail; 85 offset = 0; 86 for (p = args->config->alt_auth_map; *p != '\0'; p++) { 87 if (p[0] == '%' && p[1] == 's') { 88 memcpy(*principal + offset, user, strlen(user)); 89 offset += strlen(user); 90 p++; 91 } else { 92 (*principal)[offset] = *p; 93 offset++; 94 } 95 } 96 if (realm != NULL && strchr(args->config->alt_auth_map, '@') == NULL) { 97 (*principal)[offset] = '@'; 98 offset++; 99 memcpy(*principal + offset, realm, strlen(realm)); 100 offset += strlen(realm); 101 } 102 (*principal)[offset] = '\0'; 103 free(new_user); 104 return 0; 105 106 fail: 107 if (new_user != NULL) { 108 oerrno = errno; 109 free(new_user); 110 errno = oerrno; 111 } 112 return errno; 113 } 114 115 116 /* 117 * Authenticate using an alternate principal mapping. 118 * 119 * Create a principal based on the principal mapping and the user, and use the 120 * provided password to try to authenticate as that user. If we succeed, fill 121 * out creds, set princ to the successful principal in the context, and return 122 * 0. Otherwise, return a Kerberos error code or an errno value. 123 */ 124 krb5_error_code 125 pamk5_alt_auth(struct pam_args *args, const char *service, 126 krb5_get_init_creds_opt *opts, const char *pass, 127 krb5_creds *creds) 128 { 129 struct context *ctx = args->config->ctx; 130 char *kuser; 131 krb5_principal princ; 132 krb5_error_code retval; 133 134 retval = pamk5_map_principal(args, ctx->name, &kuser); 135 if (retval != 0) 136 return retval; 137 retval = krb5_parse_name(ctx->context, kuser, &princ); 138 if (retval != 0) { 139 free(kuser); 140 return retval; 141 } 142 free(kuser); 143 144 /* Log the principal we're attempting to authenticate as. */ 145 if (args->debug) { 146 char *principal; 147 148 retval = krb5_unparse_name(ctx->context, princ, &principal); 149 if (retval != 0) 150 putil_debug_krb5(args, retval, "krb5_unparse_name failed"); 151 else { 152 putil_debug(args, "mapping %s to %s", ctx->name, principal); 153 krb5_free_unparsed_name(ctx->context, principal); 154 } 155 } 156 157 /* 158 * Now, attempt to authenticate as that user. On success, save the 159 * principal. Return the Kerberos status code. 160 */ 161 retval = krb5_get_init_creds_password(ctx->context, creds, princ, 162 (char *) pass, pamk5_prompter_krb5, 163 args, 0, (char *) service, opts); 164 if (retval != 0) { 165 putil_debug_krb5(args, retval, "alternate authentication failed"); 166 krb5_free_principal(ctx->context, princ); 167 return retval; 168 } else { 169 putil_debug(args, "alternate authentication successful"); 170 if (ctx->princ != NULL) 171 krb5_free_principal(ctx->context, ctx->princ); 172 ctx->princ = princ; 173 return 0; 174 } 175 } 176 177 178 /* 179 * Verify an alternate authentication. 180 * 181 * Meant to be called from pamk5_authorized, this checks that the principal in 182 * the context matches the alt_auth_map-derived identity of the user we're 183 * authenticating. Returns PAM_SUCCESS if they match, PAM_AUTH_ERR if they 184 * don't match, and PAM_SERVICE_ERR on an internal error. 185 */ 186 int 187 pamk5_alt_auth_verify(struct pam_args *args) 188 { 189 struct context *ctx; 190 char *name = NULL; 191 char *mapped = NULL; 192 char *authed = NULL; 193 krb5_principal princ = NULL; 194 krb5_error_code retval; 195 int status = PAM_SERVICE_ERR; 196 197 if (args == NULL || args->config == NULL || args->config->ctx == NULL) 198 return PAM_SERVICE_ERR; 199 ctx = args->config->ctx; 200 if (ctx->context == NULL || ctx->name == NULL) 201 return PAM_SERVICE_ERR; 202 if (pamk5_map_principal(args, ctx->name, &name) != 0) { 203 putil_err(args, "cannot map principal name"); 204 goto done; 205 } 206 retval = krb5_parse_name(ctx->context, name, &princ); 207 if (retval != 0) { 208 putil_err_krb5(args, retval, "cannot parse mapped principal name %s", 209 mapped); 210 goto done; 211 } 212 retval = krb5_unparse_name(ctx->context, princ, &mapped); 213 if (retval != 0) { 214 putil_err_krb5(args, retval, 215 "krb5_unparse_name on mapped principal failed"); 216 goto done; 217 } 218 retval = krb5_unparse_name(ctx->context, ctx->princ, &authed); 219 if (retval != 0) { 220 putil_err_krb5(args, retval, "krb5_unparse_name failed"); 221 goto done; 222 } 223 if (strcmp(authed, mapped) == 0) 224 status = PAM_SUCCESS; 225 else { 226 putil_debug(args, "mapped user %s does not match principal %s", mapped, 227 authed); 228 status = PAM_AUTH_ERR; 229 } 230 231 done: 232 free(name); 233 if (authed != NULL) 234 krb5_free_unparsed_name(ctx->context, authed); 235 if (mapped != NULL) 236 krb5_free_unparsed_name(ctx->context, mapped); 237 if (princ != NULL) 238 krb5_free_principal(ctx->context, princ); 239 return status; 240 } 241