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 *
build_password_prompt(struct pam_args * args,const char * prefix)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
pamk5_get_password(struct pam_args * args,const char * prefix,char ** password)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
pamk5_conv(struct pam_args * args,const char * message,int type,char ** response)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 **
allocate_pam_message(size_t total_prompts)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
free_pam_message(struct pam_message ** msg,size_t total_prompts)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
free_pam_responses(struct pam_response * resp,size_t total_prompts)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
format_prompt(krb5_prompt * prompt,struct pam_message * message)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
record_prompt_answers(struct pam_response * resp,int num_prompts,krb5_prompt * prompts)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
pamk5_prompter_krb5(krb5_context context UNUSED,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt * prompts)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
pamk5_prompter_krb5_no_password(krb5_context context,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt * prompts)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
pamk5_prompter_krb5_no_password(krb5_context context,void * data,const char * name,const char * banner,int num_prompts,krb5_prompt * prompts)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