1 /* 2 * Prompt users for information. 3 * 4 * Handles all interaction with the PAM conversation, either directly or 5 * indirectly through the Kerberos libraries. 6 * 7 * Copyright 2005-2007, 2009, 2014, 2017, 2020 Russ Allbery <eagle@eyrie.org> 8 * Copyright 2011-2012 9 * The Board of Trustees of the Leland Stanford Junior University 10 * Copyright 2005 Andres Salomon <dilinger@debian.org> 11 * Copyright 1999-2000 Frank Cusack <fcusack@fcusack.com> 12 * 13 * SPDX-License-Identifier: BSD-3-clause or GPL-1+ 14 */ 15 16 #include <config.h> 17 #include <portable/krb5.h> 18 #include <portable/pam.h> 19 #include <portable/system.h> 20 21 #include <assert.h> 22 #include <errno.h> 23 24 #include <module/internal.h> 25 #include <pam-util/args.h> 26 #include <pam-util/logging.h> 27 28 29 /* 30 * Build a password prompt. 31 * 32 * The default prompt is simply "Password:". Optionally, a string describing 33 * the type of password is passed in as prefix. In this case, the prompts is: 34 * 35 * <prefix> <banner> password: 36 * 37 * where <prefix> is the argument passed and <banner> is the value of 38 * args->banner (defaulting to "Kerberos"). 39 * 40 * If args->config->expose_account is set, we append the principal name (taken 41 * from args->config->ctx->princ) before the colon, so the prompts are: 42 * 43 * Password for <principal>: 44 * <prefix> <banner> password for <principal>: 45 * 46 * Normally this is not done because it exposes the realm and possibly any 47 * username to principal mappings, plus may confuse some ssh clients if sshd 48 * passes the prompt back to the client. 49 * 50 * Returns newly-allocated memory or NULL on failure. The caller is 51 * responsible for freeing. 52 */ 53 static char * 54 build_password_prompt(struct pam_args *args, const char *prefix) 55 { 56 struct context *ctx = args->config->ctx; 57 char *principal = NULL; 58 const char *banner, *bspace; 59 char *prompt, *tmp; 60 bool expose_account; 61 krb5_error_code k5_errno; 62 int retval; 63 64 /* If we're exposing the account, format the principal name. */ 65 if (args->config->expose_account || prefix != NULL) 66 if (ctx != NULL && ctx->context != NULL && ctx->princ != NULL) { 67 k5_errno = krb5_unparse_name(ctx->context, ctx->princ, &principal); 68 if (k5_errno != 0) 69 putil_debug_krb5(args, k5_errno, "krb5_unparse_name failed"); 70 } 71 72 /* Build the part of the prompt without the principal name. */ 73 if (prefix == NULL) 74 tmp = strdup("Password"); 75 else { 76 banner = (args->config->banner == NULL) ? "" : args->config->banner; 77 bspace = (args->config->banner == NULL) ? "" : " "; 78 retval = asprintf(&tmp, "%s%s%s password", prefix, bspace, banner); 79 if (retval < 0) 80 tmp = NULL; 81 } 82 if (tmp == NULL) 83 goto fail; 84 85 /* Add the principal, if desired, and the colon and space. */ 86 expose_account = args->config->expose_account && principal != NULL; 87 if (expose_account) 88 retval = asprintf(&prompt, "%s for %s: ", tmp, principal); 89 else 90 retval = asprintf(&prompt, "%s: ", tmp); 91 free(tmp); 92 if (retval < 0) 93 goto fail; 94 95 /* Clean up and return. */ 96 if (principal != NULL) 97 krb5_free_unparsed_name(ctx->context, principal); 98 return prompt; 99 100 fail: 101 if (principal != NULL) 102 krb5_free_unparsed_name(ctx->context, principal); 103 return NULL; 104 } 105 106 107 /* 108 * Prompt for a password. 109 * 110 * The entered password is stored in password. The memory is allocated by the 111 * application and returned as part of the PAM conversation. It must be freed 112 * by the caller. 113 * 114 * Returns a PAM success or error code. 115 */ 116 int 117 pamk5_get_password(struct pam_args *args, const char *prefix, char **password) 118 { 119 char *prompt; 120 int retval; 121 122 prompt = build_password_prompt(args, prefix); 123 if (prompt == NULL) 124 return PAM_BUF_ERR; 125 retval = pamk5_conv(args, prompt, PAM_PROMPT_ECHO_OFF, password); 126 free(prompt); 127 return retval; 128 } 129 130 131 /* 132 * Get information from the user or display a message to the user, as 133 * determined by type. If PAM_SILENT was given, don't pass any text or error 134 * messages to the application. 135 * 136 * The response variable is set to the response returned by the conversation 137 * function on a successful return if a response was desired. Caller is 138 * responsible for freeing it. 139 */ 140 int 141 pamk5_conv(struct pam_args *args, const char *message, int type, 142 char **response) 143 { 144 int pamret; 145 struct pam_message msg; 146 PAM_CONST struct pam_message *pmsg; 147 struct pam_response *resp = NULL; 148 struct pam_conv *conv; 149 int want_reply; 150 151 if (args->silent && (type == PAM_ERROR_MSG || type == PAM_TEXT_INFO)) 152 return PAM_SUCCESS; 153 pamret = pam_get_item(args->pamh, PAM_CONV, (PAM_CONST void **) &conv); 154 if (pamret != PAM_SUCCESS) 155 return pamret; 156 if (conv->conv == NULL) 157 return PAM_CONV_ERR; 158 pmsg = &msg; 159 msg.msg_style = type; 160 msg.msg = (PAM_CONST char *) message; 161 pamret = conv->conv(1, &pmsg, &resp, conv->appdata_ptr); 162 if (pamret != PAM_SUCCESS) 163 return pamret; 164 165 /* 166 * Only expect a response for PAM_PROMPT_ECHO_OFF or PAM_PROMPT_ECHO_ON 167 * message types. This mildly annoying logic makes sure that everything 168 * is freed properly (except the response itself, if wanted, which is 169 * returned for the caller to free) and that the success status is set 170 * based on whether the reply matched our expectations. 171 * 172 * If we got a reply even though we didn't want one, still overwrite the 173 * reply before freeing in case it was a password. 174 */ 175 want_reply = (type == PAM_PROMPT_ECHO_OFF || type == PAM_PROMPT_ECHO_ON); 176 if (resp == NULL || resp->resp == NULL) 177 pamret = want_reply ? PAM_CONV_ERR : PAM_SUCCESS; 178 else if (want_reply && response != NULL) { 179 *response = resp->resp; 180 pamret = PAM_SUCCESS; 181 } else { 182 explicit_bzero(resp->resp, strlen(resp->resp)); 183 free(resp->resp); 184 pamret = want_reply ? PAM_SUCCESS : PAM_CONV_ERR; 185 } 186 free(resp); 187 return pamret; 188 } 189 190 191 /* 192 * Allocate memory to copy all of the prompts into a pam_message. 193 * 194 * Linux PAM and Solaris PAM expect different things here. Solaris PAM 195 * expects to receive a pointer to a pointer to an array of pam_message 196 * structs. Linux PAM expects to receive a pointer to an array of pointers to 197 * pam_message structs. In order for the module to work with either PAM 198 * implementation, we need to set up a structure that is valid either way you 199 * look at it. 200 * 201 * We do this by making msg point to the array of struct pam_message pointers 202 * (what Linux PAM expects), and then make the first one of those pointers 203 * point to the array of pam_message structs. Solaris will then be happy, 204 * looking at only the first element of the outer array and finding it 205 * pointing to the inner array. Then, for Linux, we point the other elements 206 * of the outer array to the storage allocated in the inner array. 207 * 208 * All this also means we have to be careful how we free the resulting 209 * structure since it's double-linked in a subtle way. Thankfully, we get to 210 * free it ourselves. 211 */ 212 static struct pam_message ** 213 allocate_pam_message(size_t total_prompts) 214 { 215 struct pam_message **msg; 216 size_t i; 217 218 msg = calloc(total_prompts, sizeof(struct pam_message *)); 219 if (msg == NULL) 220 return NULL; 221 *msg = calloc(total_prompts, sizeof(struct pam_message)); 222 if (*msg == NULL) { 223 free(msg); 224 return NULL; 225 } 226 for (i = 1; i < total_prompts; i++) 227 msg[i] = msg[0] + i; 228 return msg; 229 } 230 231 232 /* 233 * Free the structure created by allocate_pam_message. 234 */ 235 static void 236 free_pam_message(struct pam_message **msg, size_t total_prompts) 237 { 238 size_t i; 239 240 for (i = 0; i < total_prompts; i++) 241 free((char *) msg[i]->msg); 242 free(*msg); 243 free(msg); 244 } 245 246 247 /* 248 * Free the responses returned by the conversation function. These may 249 * contain passwords, so we overwrite them before we free them. 250 */ 251 static void 252 free_pam_responses(struct pam_response *resp, size_t total_prompts) 253 { 254 size_t i; 255 256 if (resp == NULL) 257 return; 258 for (i = 0; i < total_prompts; i++) { 259 if (resp[i].resp != NULL) { 260 explicit_bzero(resp[i].resp, strlen(resp[i].resp)); 261 free(resp[i].resp); 262 } 263 } 264 free(resp); 265 } 266 267 268 /* 269 * Format a Kerberos prompt into a PAM prompt. Takes a krb5_prompt as input 270 * and writes the resulting PAM prompt into a struct pam_message. 271 */ 272 static krb5_error_code 273 format_prompt(krb5_prompt *prompt, struct pam_message *message) 274 { 275 size_t len = strlen(prompt->prompt); 276 bool has_colon; 277 const char *colon; 278 int retval, style; 279 280 /* 281 * Heimdal adds the trailing colon and space, while MIT does not. 282 * Work around the difference by looking to see if there's a trailing 283 * colon and space already and only adding it if there is not. 284 */ 285 has_colon = (len > 2 && memcmp(&prompt->prompt[len - 2], ": ", 2) == 0); 286 colon = has_colon ? "" : ": "; 287 retval = asprintf((char **) &message->msg, "%s%s", prompt->prompt, colon); 288 if (retval < 0) 289 return retval; 290 style = prompt->hidden ? PAM_PROMPT_ECHO_OFF : PAM_PROMPT_ECHO_ON; 291 message->msg_style = style; 292 return 0; 293 } 294 295 296 /* 297 * Given an array of struct pam_response elements, record the responses in the 298 * corresponding krb5_prompt structures. 299 */ 300 static krb5_error_code 301 record_prompt_answers(struct pam_response *resp, int num_prompts, 302 krb5_prompt *prompts) 303 { 304 int i; 305 306 for (i = 0; i < num_prompts; i++) { 307 size_t len, allowed; 308 309 if (resp[i].resp == NULL) 310 return KRB5_LIBOS_CANTREADPWD; 311 len = strlen(resp[i].resp); 312 allowed = prompts[i].reply->length; 313 if (allowed == 0 || len > allowed - 1) 314 return KRB5_LIBOS_CANTREADPWD; 315 316 /* 317 * Since the first version of this module, it has copied a nul 318 * character into the prompt data buffer for MIT Kerberos with the 319 * note that "other applications expect it to be there." I suspect 320 * this is incorrect and nothing cares about this nul, but have 321 * preserved this behavior out of an abundance of caution. 322 * 323 * Note that it shortens the maximum response length we're willing to 324 * accept by one (implemented above) and is the source of one prior 325 * security vulnerability. 326 */ 327 memcpy(prompts[i].reply->data, resp[i].resp, len + 1); 328 prompts[i].reply->length = (unsigned int) len; 329 } 330 return 0; 331 } 332 333 334 /* 335 * This is the generic prompting function called by both MIT Kerberos and 336 * Heimdal prompting implementations. 337 * 338 * There are a lot of structures and different layers of code at work here, 339 * making this code quite confusing. This function is a prompter function to 340 * pass into the Kerberos library, in particular krb5_get_init_creds_password. 341 * It is used by the Kerberos library to prompt for a password if need be, and 342 * also to prompt for password changes if the password was expired. 343 * 344 * The purpose of this function is to serve as glue between the Kerberos 345 * library and the application (by way of the PAM glue). PAM expects us to 346 * pass back to the conversation function an array of prompts and receive from 347 * the application an array of responses to those prompts. We pass the 348 * application an array of struct pam_message pointers, and the application 349 * passes us an array of struct pam_response pointers. 350 * 351 * Kerberos, meanwhile, passes us in an array of krb5_prompt structs. This 352 * struct contains the prompt, a flag saying whether to suppress echoing of 353 * what the user types for that prompt, and a buffer into which to store the 354 * response. 355 * 356 * Therefore, what we're doing here is copying the prompts from the 357 * krb5_prompt structs into pam_message structs, calling the conversation 358 * function, and then copying the responses back out of pam_response structs 359 * into the krb5_prompt structs to return to the Kerberos library. 360 */ 361 krb5_error_code 362 pamk5_prompter_krb5(krb5_context context UNUSED, void *data, const char *name, 363 const char *banner, int num_prompts, krb5_prompt *prompts) 364 { 365 struct pam_args *args = data; 366 int current_prompt, retval, pamret, i, offset; 367 int total_prompts = num_prompts; 368 struct pam_message **msg; 369 struct pam_response *resp = NULL; 370 struct pam_conv *conv; 371 372 /* Treat the name and banner as prompts that doesn't need input. */ 373 if (name != NULL && !args->silent) 374 total_prompts++; 375 if (banner != NULL && !args->silent) 376 total_prompts++; 377 378 /* If we have zero prompts, do nothing, silently. */ 379 if (total_prompts == 0) 380 return 0; 381 382 /* Obtain the conversation function from the application. */ 383 pamret = pam_get_item(args->pamh, PAM_CONV, (PAM_CONST void **) &conv); 384 if (pamret != 0) 385 return KRB5_LIBOS_CANTREADPWD; 386 if (conv->conv == NULL) 387 return KRB5_LIBOS_CANTREADPWD; 388 389 /* Allocate memory to copy all of the prompts into a pam_message. */ 390 msg = allocate_pam_message(total_prompts); 391 if (msg == NULL) 392 return ENOMEM; 393 394 /* current_prompt is an index into msg and a count when we're done. */ 395 current_prompt = 0; 396 if (name != NULL && !args->silent) { 397 msg[current_prompt]->msg = strdup(name); 398 if (msg[current_prompt]->msg == NULL) { 399 retval = ENOMEM; 400 goto cleanup; 401 } 402 msg[current_prompt]->msg_style = PAM_TEXT_INFO; 403 current_prompt++; 404 } 405 if (banner != NULL && !args->silent) { 406 assert(current_prompt < total_prompts); 407 msg[current_prompt]->msg = strdup(banner); 408 if (msg[current_prompt]->msg == NULL) { 409 retval = ENOMEM; 410 goto cleanup; 411 } 412 msg[current_prompt]->msg_style = PAM_TEXT_INFO; 413 current_prompt++; 414 } 415 for (i = 0; i < num_prompts; i++) { 416 assert(current_prompt < total_prompts); 417 retval = format_prompt(&prompts[i], msg[current_prompt]); 418 if (retval < 0) 419 goto cleanup; 420 current_prompt++; 421 } 422 423 /* Call into the application conversation function. */ 424 pamret = conv->conv(total_prompts, (PAM_CONST struct pam_message **) msg, 425 &resp, conv->appdata_ptr); 426 if (pamret != 0 || resp == NULL) { 427 retval = KRB5_LIBOS_CANTREADPWD; 428 goto cleanup; 429 } 430 431 /* 432 * Record the answers in the Kerberos data structure. If name or banner 433 * were provided, skip over the initial PAM responses that correspond to 434 * those messages. 435 */ 436 offset = 0; 437 if (name != NULL && !args->silent) 438 offset++; 439 if (banner != NULL && !args->silent) 440 offset++; 441 retval = record_prompt_answers(resp + offset, num_prompts, prompts); 442 443 cleanup: 444 free_pam_message(msg, total_prompts); 445 free_pam_responses(resp, total_prompts); 446 return retval; 447 } 448 449 450 /* 451 * This is a special version of krb5_prompter_krb5 that returns an error if 452 * the Kerberos library asks for a password. It is only used with MIT 453 * Kerberos as part of the implementation of try_pkinit and use_pkinit. 454 * (Heimdal has a different API for PKINIT authentication.) 455 */ 456 #ifdef HAVE_KRB5_GET_PROMPT_TYPES 457 krb5_error_code 458 pamk5_prompter_krb5_no_password(krb5_context context, void *data, 459 const char *name, const char *banner, 460 int num_prompts, krb5_prompt *prompts) 461 { 462 krb5_prompt_type *ptypes; 463 int i; 464 465 ptypes = krb5_get_prompt_types(context); 466 for (i = 0; i < num_prompts; i++) 467 if (ptypes != NULL && ptypes[i] == KRB5_PROMPT_TYPE_PASSWORD) 468 return KRB5_LIBOS_CANTREADPWD; 469 return pamk5_prompter_krb5(context, data, name, banner, num_prompts, 470 prompts); 471 } 472 #else /* !HAVE_KRB5_GET_PROMPT_TYPES */ 473 krb5_error_code 474 pamk5_prompter_krb5_no_password(krb5_context context, void *data, 475 const char *name, const char *banner, 476 int num_prompts, krb5_prompt *prompts) 477 { 478 return pamk5_prompter_krb5(context, data, name, banner, num_prompts, 479 prompts); 480 } 481 #endif /* !HAVE_KRB5_GET_PROMPT_TYPES */ 482