xref: /freebsd/contrib/pam-krb5/module/auth.c (revision fe5c8baf25a5b40285c3ef85b69391d591e4a76c)
1 /*
2  * Core authentication routines for pam_krb5.
3  *
4  * The actual authentication work is done here, either via password or via
5  * PKINIT.  The only external interface is pamk5_password_auth, which calls
6  * the appropriate internal functions.  This interface is used by both the
7  * authentication and the password groups.
8  *
9  * Copyright 2005-2010, 2014-2015, 2017, 2020
10  *     Russ Allbery <eagle@eyrie.org>
11  * Copyright 2010-2012, 2014
12  *     The Board of Trustees of the Leland Stanford Junior University
13  * Copyright 2005 Andres Salomon <dilinger@debian.org>
14  * Copyright 1999-2000 Frank Cusack <fcusack@fcusack.com>
15  *
16  * SPDX-License-Identifier: BSD-3-clause or GPL-1+
17  */
18 
19 #include <config.h>
20 #include <portable/krb5.h>
21 #include <portable/pam.h>
22 #include <portable/system.h>
23 
24 #include <errno.h>
25 #ifdef HAVE_HX509_ERR_H
26 #    include <hx509_err.h>
27 #endif
28 #include <pwd.h>
29 #include <sys/stat.h>
30 
31 #include <module/internal.h>
32 #include <pam-util/args.h>
33 #include <pam-util/logging.h>
34 #include <pam-util/vector.h>
35 
36 /*
37  * If the PKINIT smart card error statuses aren't defined, define them to 0.
38  * This will cause the right thing to happen with the logic around PKINIT.
39  */
40 #ifndef HX509_PKCS11_NO_TOKEN
41 #    define HX509_PKCS11_NO_TOKEN 0
42 #endif
43 #ifndef HX509_PKCS11_NO_SLOT
44 #    define HX509_PKCS11_NO_SLOT 0
45 #endif
46 
47 
48 /*
49  * Fill in ctx->princ from the value of ctx->name or (if configured) from
50  * prompting.  If we don't prompt and ctx->name contains an @-sign,
51  * canonicalize it to a local account name unless no_update_user is set.  If
52  * the canonicalization fails, don't worry about it.  It may be that the
53  * application doesn't care.
54  */
55 static krb5_error_code
parse_name(struct pam_args * args)56 parse_name(struct pam_args *args)
57 {
58     struct context *ctx = args->config->ctx;
59     krb5_context c = ctx->context;
60     char *user_realm;
61     char *user = ctx->name;
62     char *newuser = NULL;
63     char kuser[65] = ""; /* MAX_USERNAME == 65 (MIT Kerberos 1.4.1). */
64     krb5_error_code k5_errno;
65     int retval;
66 
67     /*
68      * If configured to prompt for the principal, do that first.  Fall back on
69      * using the local username as normal if prompting fails or if the user
70      * just presses Enter.
71      */
72     if (args->config->prompt_principal) {
73         retval = pamk5_conv(args, "Principal: ", PAM_PROMPT_ECHO_ON, &user);
74         if (retval != PAM_SUCCESS)
75             putil_err_pam(args, retval, "error getting principal");
76         if (*user == '\0') {
77             free(user);
78             user = ctx->name;
79         }
80     }
81 
82     /*
83      * We don't just call krb5_parse_name so that we can work around a bug in
84      * MIT Kerberos versions prior to 1.4, which store the realm in a static
85      * variable inside the library and don't notice changes.  If no realm is
86      * specified and a realm is set in our arguments, append the realm to
87      * force krb5_parse_name to do the right thing.
88      */
89     user_realm = args->realm;
90     if (args->config->user_realm)
91         user_realm = args->config->user_realm;
92     if (user_realm != NULL && strchr(user, '@') == NULL) {
93         if (asprintf(&newuser, "%s@%s", user, user_realm) < 0) {
94             if (user != ctx->name)
95                 free(user);
96             return KRB5_CC_NOMEM;
97         }
98         if (user != ctx->name)
99             free(user);
100         user = newuser;
101     }
102     k5_errno = krb5_parse_name(c, user, &ctx->princ);
103     if (user != ctx->name)
104         free(user);
105     if (k5_errno != 0)
106         return k5_errno;
107 
108     /*
109      * Now that we have a principal to call krb5_aname_to_localname, we can
110      * canonicalize ctx->name to a local name.  We do this even if we were
111      * explicitly prompting for a principal, but we use ctx->name to generate
112      * the local username, not the principal name.  It's unlikely, and would
113      * be rather weird, if the user were to specify a principal name for the
114      * username and then enter a different username at the principal prompt,
115      * but this behavior seems to make the most sense.
116      *
117      * Skip canonicalization if no_update_user was set.  In that case,
118      * continue to use the initial authentication identity everywhere.
119      */
120     if (strchr(ctx->name, '@') != NULL && !args->config->no_update_user) {
121         if (krb5_aname_to_localname(c, ctx->princ, sizeof(kuser), kuser) != 0)
122             return 0;
123         user = strdup(kuser);
124         if (user == NULL) {
125             putil_crit(args, "cannot allocate memory: %s", strerror(errno));
126             return 0;
127         }
128         free(ctx->name);
129         ctx->name = user;
130         args->user = user;
131     }
132     return k5_errno;
133 }
134 
135 
136 /*
137  * Set initial credential options based on our configuration information, and
138  * using the Heimdal call to set initial credential options if it's available.
139  * This function is used both for regular password authentication and for
140  * PKINIT.  It also configures FAST if requested and the Kerberos libraries
141  * support it.
142  *
143  * Takes a flag indicating whether we're getting tickets for a specific
144  * service.  If so, we don't try to get forwardable, renewable, or proxiable
145  * tickets.
146  */
147 static void
set_credential_options(struct pam_args * args,krb5_get_init_creds_opt * opts,int service)148 set_credential_options(struct pam_args *args, krb5_get_init_creds_opt *opts,
149                        int service)
150 {
151     struct pam_config *config = args->config;
152     krb5_context c = config->ctx->context;
153 
154     krb5_get_init_creds_opt_set_default_flags(c, "pam", args->realm, opts);
155     if (!service) {
156         if (config->forwardable)
157             krb5_get_init_creds_opt_set_forwardable(opts, 1);
158         if (config->ticket_lifetime != 0)
159             krb5_get_init_creds_opt_set_tkt_life(opts,
160                                                  config->ticket_lifetime);
161         if (config->renew_lifetime != 0)
162             krb5_get_init_creds_opt_set_renew_life(opts,
163                                                    config->renew_lifetime);
164         krb5_get_init_creds_opt_set_change_password_prompt(
165             opts, (config->defer_pwchange || config->fail_pwchange) ? 0 : 1);
166     } else {
167         krb5_get_init_creds_opt_set_forwardable(opts, 0);
168         krb5_get_init_creds_opt_set_proxiable(opts, 0);
169         krb5_get_init_creds_opt_set_renew_life(opts, 0);
170     }
171     pamk5_fast_setup(args, opts);
172 
173     /*
174      * Set options for PKINIT.  Only used with MIT Kerberos; Heimdal's
175      * implementation of PKINIT uses a separate API instead of setting
176      * get_init_creds options.
177      */
178 #ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PA
179     if (config->use_pkinit || config->try_pkinit) {
180         if (config->pkinit_user != NULL)
181             krb5_get_init_creds_opt_set_pa(c, opts, "X509_user_identity",
182                                            config->pkinit_user);
183         if (config->pkinit_anchors != NULL)
184             krb5_get_init_creds_opt_set_pa(c, opts, "X509_anchors",
185                                            config->pkinit_anchors);
186         if (config->preauth_opt != NULL && config->preauth_opt->count > 0) {
187             size_t i;
188             char *name, *value;
189             char save = '\0';
190 
191             for (i = 0; i < config->preauth_opt->count; i++) {
192                 name = config->preauth_opt->strings[i];
193                 if (name == NULL)
194                     continue;
195                 value = strchr(name, '=');
196                 if (value != NULL) {
197                     save = *value;
198                     *value = '\0';
199                     value++;
200                 }
201                 krb5_get_init_creds_opt_set_pa(
202                     c, opts, name, (value != NULL) ? value : "yes");
203                 if (value != NULL)
204                     value[-1] = save;
205             }
206         }
207     }
208 #endif /* HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PA */
209 }
210 
211 
212 /*
213  * Retrieve the existing password (authtok) stored in the PAM data if
214  * appropriate and if available.  We decide whether to retrieve it based on
215  * the PAM configuration, and also decied whether failing to retrieve it is a
216  * fatal error.  Takes the PAM arguments, the PAM authtok code to retrieve
217  * (may be PAM_AUTHTOK or PAM_OLDAUTHTOK depending on whether we're
218  * authenticating or changing the password), and the place to store the
219  * password.  Returns a PAM status code.
220  *
221  * If try_first_pass, use_first_pass, or force_first_pass is set, grab the old
222  * password (if set).  If force_first_pass is set, fail if the password is not
223  * already set.
224  *
225  * The empty password has to be handled separately, since the Kerberos
226  * libraries may treat it as equivalent to no password and prompt when we
227  * don't want them to.  We make the assumption here that the empty password is
228  * always invalid and is an authentication failure.
229  */
230 static int
maybe_retrieve_password(struct pam_args * args,int authtok,const char ** pass)231 maybe_retrieve_password(struct pam_args *args, int authtok, const char **pass)
232 {
233     int status;
234     const bool try_first = args->config->try_first_pass;
235     const bool use = args->config->use_first_pass;
236     const bool force = args->config->force_first_pass;
237 
238     *pass = NULL;
239     if (!try_first && !use && !force)
240         return PAM_SUCCESS;
241     status = pam_get_item(args->pamh, authtok, (PAM_CONST void **) pass);
242     if (*pass != NULL && **pass == '\0') {
243         if (use || force) {
244             putil_debug(args, "rejecting empty password");
245             return PAM_AUTH_ERR;
246         }
247         *pass = NULL;
248     }
249     if (*pass != NULL && strlen(*pass) > PAM_MAX_RESP_SIZE - 1) {
250         putil_debug(args, "rejecting password longer than %d",
251                     PAM_MAX_RESP_SIZE - 1);
252         return PAM_AUTH_ERR;
253     }
254     if (force && (status != PAM_SUCCESS || *pass == NULL)) {
255         putil_debug_pam(args, status, "no stored password");
256         return PAM_AUTH_ERR;
257     }
258     return PAM_SUCCESS;
259 }
260 
261 
262 /*
263  * Prompt for the password.  Takes the PAM arguments, the authtok for which
264  * we're prompting (may be PAM_AUTHTOK or PAM_OLDAUTHTOK depending on whether
265  * we're authenticating or changing the password), and the place to store the
266  * password.  Returns a PAM status code.
267  *
268  * If we successfully get a password, store it in the PAM data, free it, and
269  * then return the password as retrieved from the PAM data so that we don't
270  * have to worry about memory allocation later.
271  *
272  * The empty password has to be handled separately, since the Kerberos
273  * libraries may treat it as equivalent to no password and prompt when we
274  * don't want them to.  We make the assumption here that the empty password is
275  * always invalid and is an authentication failure.
276  */
277 static int
prompt_password(struct pam_args * args,int authtok,const char ** pass)278 prompt_password(struct pam_args *args, int authtok, const char **pass)
279 {
280     char *password;
281     int status;
282     const char *prompt = (authtok == PAM_AUTHTOK) ? NULL : "Current";
283 
284     *pass = NULL;
285     status = pamk5_get_password(args, prompt, &password);
286     if (status != PAM_SUCCESS) {
287         putil_debug_pam(args, status, "error getting password");
288         return PAM_AUTH_ERR;
289     }
290     if (password[0] == '\0') {
291         putil_debug(args, "rejecting empty password");
292         free(password);
293         return PAM_AUTH_ERR;
294     }
295     if (strlen(password) > PAM_MAX_RESP_SIZE - 1) {
296         putil_debug(args, "rejecting password longer than %d",
297                     PAM_MAX_RESP_SIZE - 1);
298         explicit_bzero(password, strlen(password));
299         free(password);
300         return PAM_AUTH_ERR;
301     }
302 
303     /* Set this for the next PAM module. */
304     status = pam_set_item(args->pamh, authtok, password);
305     explicit_bzero(password, strlen(password));
306     free(password);
307     if (status != PAM_SUCCESS) {
308         putil_err_pam(args, status, "error storing password");
309         return PAM_AUTH_ERR;
310     }
311 
312     /* Return the password retrieved from PAM. */
313     status = pam_get_item(args->pamh, authtok, (PAM_CONST void **) pass);
314     if (status != PAM_SUCCESS) {
315         putil_err_pam(args, status, "error retrieving password");
316         status = PAM_AUTH_ERR;
317     }
318     return status;
319 }
320 
321 
322 /*
323  * Authenticate via password.
324  *
325  * This is our basic authentication function.  Log what principal we're
326  * attempting to authenticate with and then attempt password authentication.
327  * Returns 0 on success or a Kerberos error on failure.
328  */
329 static krb5_error_code
password_auth(struct pam_args * args,krb5_creds * creds,krb5_get_init_creds_opt * opts,const char * service,const char * pass)330 password_auth(struct pam_args *args, krb5_creds *creds,
331               krb5_get_init_creds_opt *opts, const char *service,
332               const char *pass)
333 {
334     struct context *ctx = args->config->ctx;
335     krb5_error_code retval;
336 
337     /* Log the principal as which we're attempting authentication. */
338     if (args->debug) {
339         char *principal;
340 
341         retval = krb5_unparse_name(ctx->context, ctx->princ, &principal);
342         if (retval != 0)
343             putil_debug_krb5(args, retval, "krb5_unparse_name failed");
344         else {
345             if (service == NULL)
346                 putil_debug(args, "attempting authentication as %s",
347                             principal);
348             else
349                 putil_debug(args, "attempting authentication as %s for %s",
350                             principal, service);
351             free(principal);
352         }
353     }
354 
355     /* Do the authentication. */
356     retval = krb5_get_init_creds_password(ctx->context, creds, ctx->princ,
357                                           (char *) pass, pamk5_prompter_krb5,
358                                           args, 0, (char *) service, opts);
359 
360     /*
361      * Heimdal may return an expired key error even if the password is
362      * incorrect.  To avoid accepting any incorrect password for the user
363      * in the fully correct password change case, confirm that we can get
364      * a password change ticket for the user using this password, and
365      * otherwise change the error to invalid password.
366      */
367     if (retval == KRB5KDC_ERR_KEY_EXP) {
368         krb5_get_init_creds_opt *heimdal_opts = NULL;
369 
370         retval = krb5_get_init_creds_opt_alloc(ctx->context, &heimdal_opts);
371         if (retval == 0) {
372             set_credential_options(args, opts, 1);
373             retval = krb5_get_init_creds_password(
374                 ctx->context, creds, ctx->princ, (char *) pass,
375                 pamk5_prompter_krb5, args, 0, (char *) "kadmin/changepw",
376                 heimdal_opts);
377             krb5_get_init_creds_opt_free(ctx->context, heimdal_opts);
378         }
379         if (retval == 0) {
380             retval = KRB5KDC_ERR_KEY_EXP;
381             krb5_free_cred_contents(ctx->context, creds);
382             explicit_bzero(creds, sizeof(krb5_creds));
383         }
384     }
385     return retval;
386 }
387 
388 
389 /*
390  * Authenticate by trying each principal in the .k5login file.
391  *
392  * Read through each line that parses correctly as a principal and use the
393  * provided password to try to authenticate as that user.  If at any point we
394  * succeed, fill out creds, set princ to the successful principal in the
395  * context, and return 0.  Otherwise, return either a Kerberos error code or
396  * errno for a system error.
397  */
398 static krb5_error_code
k5login_password_auth(struct pam_args * args,krb5_creds * creds,krb5_get_init_creds_opt * opts,const char * service,const char * pass)399 k5login_password_auth(struct pam_args *args, krb5_creds *creds,
400                       krb5_get_init_creds_opt *opts, const char *service,
401                       const char *pass)
402 {
403     struct context *ctx = args->config->ctx;
404     char *filename = NULL;
405     char line[BUFSIZ];
406     size_t len;
407     FILE *k5login;
408     struct passwd *pwd;
409     struct stat st;
410     krb5_error_code k5_errno, retval;
411     krb5_principal princ;
412 
413     /*
414      * C sucks at string manipulation.  Generate the filename for the user's
415      * .k5login file.  If the user doesn't exist, the .k5login file doesn't
416      * exist, or the .k5login file cannot be read, fall back on the easy way
417      * and assume ctx->princ is already set properly.
418      */
419     pwd = pam_modutil_getpwnam(args->pamh, ctx->name);
420     if (pwd != NULL)
421         if (asprintf(&filename, "%s/.k5login", pwd->pw_dir) < 0) {
422             putil_crit(args, "malloc failure: %s", strerror(errno));
423             return errno;
424         }
425     if (pwd == NULL || filename == NULL || access(filename, R_OK) != 0) {
426         free(filename);
427         return krb5_get_init_creds_password(ctx->context, creds, ctx->princ,
428                                             (char *) pass, pamk5_prompter_krb5,
429                                             args, 0, (char *) service, opts);
430     }
431 
432     /*
433      * Make sure the ownership on .k5login is okay.  The user must own their
434      * own .k5login or it must be owned by root.  If that fails, set the
435      * Kerberos error code to errno.
436      */
437     k5login = fopen(filename, "r");
438     if (k5login == NULL) {
439         retval = errno;
440         free(filename);
441         return retval;
442     }
443     free(filename);
444     if (fstat(fileno(k5login), &st) != 0) {
445         retval = errno;
446         goto fail;
447     }
448     if (st.st_uid != 0 && (st.st_uid != pwd->pw_uid)) {
449         retval = EACCES;
450         putil_err(args, "unsafe .k5login ownership (saw %lu, expected %lu)",
451                   (unsigned long) st.st_uid, (unsigned long) pwd->pw_uid);
452         goto fail;
453     }
454 
455     /*
456      * Parse the .k5login file and attempt authentication for each principal.
457      * Ignore any lines that are too long or that don't parse into a Kerberos
458      * principal.  Assume an invalid password error if there are no valid
459      * lines in .k5login.
460      */
461     retval = KRB5KRB_AP_ERR_BAD_INTEGRITY;
462     while (fgets(line, BUFSIZ, k5login) != NULL) {
463         len = strlen(line);
464         if (line[len - 1] != '\n') {
465             while (fgets(line, BUFSIZ, k5login) != NULL) {
466                 len = strlen(line);
467                 if (line[len - 1] == '\n')
468                     break;
469             }
470             continue;
471         }
472         line[len - 1] = '\0';
473         k5_errno = krb5_parse_name(ctx->context, line, &princ);
474         if (k5_errno != 0)
475             continue;
476 
477         /* Now, attempt to authenticate as that user. */
478         if (service == NULL)
479             putil_debug(args, "attempting authentication as %s", line);
480         else
481             putil_debug(args, "attempting authentication as %s for %s", line,
482                         service);
483         retval = krb5_get_init_creds_password(
484             ctx->context, creds, princ, (char *) pass, pamk5_prompter_krb5,
485             args, 0, (char *) service, opts);
486 
487         /*
488          * If that worked, update ctx->princ and return success.  Otherwise,
489          * continue on to the next line.
490          */
491         if (retval == 0) {
492             if (ctx->princ != NULL)
493                 krb5_free_principal(ctx->context, ctx->princ);
494             ctx->princ = princ;
495             fclose(k5login);
496             return 0;
497         }
498         krb5_free_principal(ctx->context, princ);
499     }
500 
501 fail:
502     fclose(k5login);
503     return retval;
504 }
505 
506 
507 #if (defined(HAVE_KRB5_HEIMDAL)                           \
508      && defined(HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PKINIT)) \
509     || defined(HAVE_KRB5_GET_PROMPT_TYPES)
510 /*
511  * Attempt authentication via PKINIT.  Currently, this uses an API specific to
512  * Heimdal.  Once MIT Kerberos supports PKINIT, some of the details may need
513  * to move into the compat layer.
514  *
515  * Some smart card readers require the user to enter the PIN at the keyboard
516  * after inserting the smart card.  Others have a pad on the card and no
517  * prompting by PAM is required.  The Kerberos library prompting functions
518  * should be able to work out which is required.
519  *
520  * PKINIT is just one of many pre-authentication mechanisms that could be
521  * used.  It's handled separately because of possible smart card interactions
522  * and the possibility that some users may be authenticated via PKINIT and
523  * others may not.
524  *
525  * Takes the same arguments as pamk5_password_auth and returns a
526  * krb5_error_code.  If successful, the credentials will be stored in creds.
527  */
528 static krb5_error_code
pkinit_auth(struct pam_args * args,const char * service,krb5_creds ** creds)529 pkinit_auth(struct pam_args *args, const char *service, krb5_creds **creds)
530 {
531     struct context *ctx = args->config->ctx;
532     krb5_get_init_creds_opt *opts = NULL;
533     krb5_error_code retval;
534     char *dummy = NULL;
535 
536     /*
537      * We may not be able to dive directly into the PKINIT functions because
538      * the user may not have a chance to enter the smart card.  For example,
539      * gnome-screensaver jumps into PAM as soon as the mouse is moved and
540      * expects to be prompted for a password, which may not happen if the
541      * smart card is the type that has a pad for the PIN on the card.
542      *
543      * Allow the user to set pkinit_prompt as an option.  If set, we tell the
544      * user they need to insert the card.
545      *
546      * We always ignore the input.  If the user wants to use a password
547      * instead, they'll be prompted later when the PKINIT code discovers that
548      * no smart card is available.
549      */
550     if (args->config->pkinit_prompt) {
551         pamk5_conv(args,
552                    args->config->use_pkinit
553                        ? "Insert smart card and press Enter: "
554                        : "Insert smart card if desired, then press Enter: ",
555                    PAM_PROMPT_ECHO_OFF, &dummy);
556     }
557 
558     /*
559      * Set credential options.  We have to use the allocated version of the
560      * credential option struct to store the PKINIT options.
561      */
562     *creds = calloc(1, sizeof(krb5_creds));
563     if (*creds == NULL)
564         return ENOMEM;
565     retval = krb5_get_init_creds_opt_alloc(ctx->context, &opts);
566     if (retval != 0)
567         return retval;
568     set_credential_options(args, opts, service != NULL);
569 
570     /* Finally, do the actual work and return the results. */
571 #    ifdef HAVE_KRB5_HEIMDAL
572     retval = krb5_get_init_creds_opt_set_pkinit(
573         ctx->context, opts, ctx->princ, args->config->pkinit_user,
574         args->config->pkinit_anchors, NULL, NULL, 0, pamk5_prompter_krb5, args,
575         NULL);
576     if (retval == 0)
577         retval = krb5_get_init_creds_password(ctx->context, *creds, ctx->princ,
578                                               NULL, NULL, args, 0,
579                                               (char *) service, opts);
580 #    else  /* !HAVE_KRB5_HEIMDAL */
581     retval = krb5_get_init_creds_password(
582         ctx->context, *creds, ctx->princ, NULL,
583         pamk5_prompter_krb5_no_password, args, 0, (char *) service, opts);
584 #    endif /* !HAVE_KRB5_HEIMDAL */
585 
586     krb5_get_init_creds_opt_free(ctx->context, opts);
587     if (retval != 0) {
588         krb5_free_cred_contents(ctx->context, *creds);
589         free(*creds);
590         *creds = NULL;
591     }
592     return retval;
593 }
594 #endif
595 
596 
597 /*
598  * Attempt authentication once with a given password.  This is the core of the
599  * authentication loop, and handles alt_auth_map and search_k5login.  It takes
600  * the PAM arguments, the service for which to get tickets (NULL for the
601  * default TGT), the initial credential options, and the password, and returns
602  * a Kerberos status code or errno.  On success (return status 0), it stores
603  * the obtained credentials in the provided creds argument.
604  */
605 static krb5_error_code
password_auth_attempt(struct pam_args * args,const char * service,krb5_get_init_creds_opt * opts,const char * pass,krb5_creds * creds)606 password_auth_attempt(struct pam_args *args, const char *service,
607                       krb5_get_init_creds_opt *opts, const char *pass,
608                       krb5_creds *creds)
609 {
610     krb5_error_code retval;
611 
612     /*
613      * First, try authenticating as the alternate principal if one were
614      * configured.  If that fails or wasn't configured, continue on to trying
615      * search_k5login or a regular authentication unless configuration
616      * indicates that regular authentication should not be attempted.
617      */
618     if (args->config->alt_auth_map != NULL) {
619         retval = pamk5_alt_auth(args, service, opts, pass, creds);
620         if (retval == 0)
621             return retval;
622 
623         /* If only_alt_auth is set, we cannot continue. */
624         if (args->config->only_alt_auth)
625             return retval;
626 
627         /*
628          * If force_alt_auth is set, skip attempting normal authentication iff
629          * the alternate principal exists.
630          */
631         if (args->config->force_alt_auth)
632             if (retval != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN)
633                 return retval;
634     }
635 
636     /* Attempt regular authentication, via either search_k5login or normal. */
637     if (args->config->search_k5login)
638         retval = k5login_password_auth(args, creds, opts, service, pass);
639     else
640         retval = password_auth(args, creds, opts, service, pass);
641     if (retval != 0)
642         putil_debug_krb5(args, retval, "krb5_get_init_creds_password");
643     return retval;
644 }
645 
646 
647 /*
648  * Try to verify credentials by obtaining and checking a service ticket.  This
649  * is required to verify that no one is spoofing the KDC, but requires read
650  * access to a keytab with a valid key.  By default, the Kerberos library will
651  * silently succeed if no verification keys are available, but the user can
652  * change this by setting verify_ap_req_nofail in [libdefaults] in
653  * /etc/krb5.conf.
654  *
655  * The MIT Kerberos implementation of krb5_verify_init_creds hardwires the
656  * host key for the local system as the desired principal if no principal is
657  * given.  If we have an explicitly configured keytab, instead read that
658  * keytab, find the first principal in that keytab, and use that.
659  *
660  * Returns a Kerberos status code (0 for success).
661  */
662 static krb5_error_code
verify_creds(struct pam_args * args,krb5_creds * creds)663 verify_creds(struct pam_args *args, krb5_creds *creds)
664 {
665     krb5_verify_init_creds_opt opts;
666     krb5_keytab keytab = NULL;
667     krb5_kt_cursor cursor;
668     int cursor_valid = 0;
669     krb5_keytab_entry entry;
670     krb5_principal princ = NULL;
671     krb5_error_code retval;
672     krb5_context c = args->config->ctx->context;
673 
674     memset(&entry, 0, sizeof(entry));
675     krb5_verify_init_creds_opt_init(&opts);
676     if (args->config->keytab) {
677         retval = krb5_kt_resolve(c, args->config->keytab, &keytab);
678         if (retval != 0) {
679             putil_err_krb5(args, retval, "cannot open keytab %s",
680                            args->config->keytab);
681             keytab = NULL;
682         }
683         if (retval == 0)
684             retval = krb5_kt_start_seq_get(c, keytab, &cursor);
685         if (retval == 0) {
686             cursor_valid = 1;
687             retval = krb5_kt_next_entry(c, keytab, &entry, &cursor);
688         }
689         if (retval == 0)
690             retval = krb5_copy_principal(c, entry.principal, &princ);
691         if (retval != 0)
692             putil_err_krb5(args, retval, "error reading keytab %s",
693                            args->config->keytab);
694         if (entry.principal != NULL)
695             krb5_kt_free_entry(c, &entry);
696         if (cursor_valid)
697             krb5_kt_end_seq_get(c, keytab, &cursor);
698     }
699 #ifdef __FreeBSD__
700     if (args->config->allow_kdc_spoof)
701 	opts.flags &= ~KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL;
702     else
703 	opts.flags |= KRB5_VERIFY_INIT_CREDS_OPT_AP_REQ_NOFAIL;
704 #endif /* __FreeBSD__ */
705     retval = krb5_verify_init_creds(c, creds, princ, keytab, NULL, &opts);
706     if (retval != 0)
707         putil_err_krb5(args, retval, "credential verification failed");
708     if (princ != NULL)
709         krb5_free_principal(c, princ);
710     if (keytab != NULL)
711         krb5_kt_close(c, keytab);
712     return retval;
713 }
714 
715 
716 /*
717  * Give the user a nicer error message when we've attempted PKINIT without
718  * success.  We can only do this if the rich status codes are available.
719  * Currently, this only works with Heimdal.
720  */
721 static void UNUSED
report_pkinit_error(struct pam_args * args,krb5_error_code retval UNUSED)722 report_pkinit_error(struct pam_args *args, krb5_error_code retval UNUSED)
723 {
724     const char *message;
725 
726 #ifdef HAVE_HX509_ERR_H
727     switch (retval) {
728 #    ifdef HX509_PKCS11_PIN_LOCKED
729     case HX509_PKCS11_PIN_LOCKED:
730         message = "PKINIT failed: user PIN locked";
731         break;
732 #    endif
733 #    ifdef HX509_PKCS11_PIN_EXPIRED
734     case HX509_PKCS11_PIN_EXPIRED:
735         message = "PKINIT failed: user PIN expired";
736         break;
737 #    endif
738 #    ifdef HX509_PKCS11_PIN_INCORRECT
739     case HX509_PKCS11_PIN_INCORRECT:
740         message = "PKINIT failed: user PIN incorrect";
741         break;
742 #    endif
743 #    ifdef HX509_PKCS11_PIN_NOT_INITIALIZED
744     case HX509_PKCS11_PIN_NOT_INITIALIZED:
745         message = "PKINIT fialed: user PIN not initialized";
746         break;
747 #    endif
748     default:
749         message = "PKINIT failed";
750         break;
751     }
752 #else
753     message = "PKINIT failed";
754 #endif
755     pamk5_conv(args, message, PAM_TEXT_INFO, NULL);
756 }
757 
758 
759 /*
760  * Prompt the user for a password and authenticate the password with the KDC.
761  * If correct, fill in creds with the obtained TGT or ticket.  service, if
762  * non-NULL, specifies the service to get tickets for; the only interesting
763  * non-null case is kadmin/changepw for changing passwords.  Therefore, if it
764  * is non-null, we look for the password in PAM_OLDAUTHOK and save it there
765  * instead of using PAM_AUTHTOK.
766  */
767 int
pamk5_password_auth(struct pam_args * args,const char * service,krb5_creds ** creds)768 pamk5_password_auth(struct pam_args *args, const char *service,
769                     krb5_creds **creds)
770 {
771     struct context *ctx;
772     krb5_get_init_creds_opt *opts = NULL;
773     krb5_error_code retval = 0;
774     int status = PAM_SUCCESS;
775     bool retry, prompt;
776     bool creds_valid = false;
777     const char *pass = NULL;
778     int authtok = (service == NULL) ? PAM_AUTHTOK : PAM_OLDAUTHTOK;
779 
780     /* Sanity check and initialization. */
781     if (args->config->ctx == NULL)
782         return PAM_SERVICE_ERR;
783     ctx = args->config->ctx;
784 
785     /*
786      * Fill in the default principal to authenticate as.  alt_auth_map or
787      * search_k5login may change this later.
788      */
789     if (ctx->princ == NULL) {
790         retval = parse_name(args);
791         if (retval != 0) {
792             putil_err_krb5(args, retval, "parse_name failed");
793             return PAM_SERVICE_ERR;
794         }
795     }
796 
797     /*
798      * If PKINIT is available and we were configured to attempt it, try
799      * authenticating with PKINIT first.  Otherwise, fail all authentication
800      * if PKINIT is not available and use_pkinit was set.  Fake an error code
801      * that gives an approximately correct error message.
802      */
803 #if defined(HAVE_KRB5_HEIMDAL) \
804     && defined(HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PKINIT)
805     if (args->config->use_pkinit || args->config->try_pkinit) {
806         retval = pkinit_auth(args, service, creds);
807         if (retval == 0)
808             goto verify;
809         putil_debug_krb5(args, retval, "PKINIT failed");
810         if (retval != HX509_PKCS11_NO_TOKEN && retval != HX509_PKCS11_NO_SLOT)
811             goto done;
812         if (retval != 0) {
813             report_pkinit_error(args, retval);
814             if (args->config->use_pkinit)
815                 goto done;
816         }
817     }
818 #elif defined(HAVE_KRB5_GET_PROMPT_TYPES)
819     if (args->config->use_pkinit) {
820         retval = pkinit_auth(args, service, creds);
821         if (retval == 0)
822             goto verify;
823         putil_debug_krb5(args, retval, "PKINIT failed");
824         report_pkinit_error(args, retval);
825         goto done;
826     }
827 #endif
828 
829     /* Allocate cred structure and set credential options. */
830     *creds = calloc(1, sizeof(krb5_creds));
831     if (*creds == NULL) {
832         putil_crit(args, "cannot allocate memory: %s", strerror(errno));
833         status = PAM_SERVICE_ERR;
834         goto done;
835     }
836     retval = krb5_get_init_creds_opt_alloc(ctx->context, &opts);
837     if (retval != 0) {
838         putil_crit_krb5(args, retval, "cannot allocate credential options");
839         goto done;
840     }
841     set_credential_options(args, opts, service != NULL);
842 
843     /*
844      * Obtain the saved password, if appropriate and available, and determine
845      * our retry strategy.  If try_first_pass is set, we will prompt for a
846      * password and retry the authentication if the stored password didn't
847      * work.
848      */
849     status = maybe_retrieve_password(args, authtok, &pass);
850     if (status != PAM_SUCCESS)
851         goto done;
852 
853     /*
854      * Main authentication loop.
855      *
856      * If we had no stored password, we prompt for a password the first time
857      * through.  If try_first_pass is set and we had an old password, we try
858      * with it.  If the old password doesn't work, we loop once, prompt for a
859      * password, and retry.  If use_first_pass is set, we'll prompt once if
860      * the password isn't already set but won't retry.
861      *
862      * If we don't have a password but try_pkinit or no_prompt are true, we
863      * don't attempt to prompt for a password and we go into the Kerberos
864      * libraries with no password.  We rely on the Kerberos libraries to do
865      * the prompting if PKINIT fails.  In this case, make sure we don't retry.
866      * Be aware that in this case, we also have no way of saving whatever
867      * password or other credentials the user might enter, so subsequent PAM
868      * modules will not see a stored authtok.
869      *
870      * We've already handled empty passwords in our other functions.
871      */
872     retry = args->config->try_first_pass;
873     prompt = !(args->config->try_pkinit || args->config->no_prompt);
874     do {
875         if (pass == NULL)
876             retry = false;
877         if (pass == NULL && prompt) {
878             status = prompt_password(args, authtok, &pass);
879             if (status != PAM_SUCCESS)
880                 goto done;
881         }
882 
883         /*
884          * Attempt authentication.  If we succeeded, we're done.  Otherwise,
885          * clear the password and then see if we should try again after
886          * prompting for a password.
887          */
888         retval = password_auth_attempt(args, service, opts, pass, *creds);
889         if (retval == 0) {
890             creds_valid = true;
891             break;
892         }
893         pass = NULL;
894     } while (retry
895              && (retval == KRB5KRB_AP_ERR_BAD_INTEGRITY
896                  || retval == KRB5KRB_AP_ERR_MODIFIED
897                  || retval == KRB5KDC_ERR_PREAUTH_FAILED
898                  || retval == KRB5_GET_IN_TKT_LOOP
899                  || retval == KRB5_BAD_ENCTYPE));
900 
901 verify:
902     UNUSED
903     /*
904      * If we think we succeeded, whether through the regular path or via
905      * PKINIT, try to verify the credentials.  Don't do this if we're
906      * authenticating for password changes (or any other case where we're not
907      * getting a TGT).  We can't get a service ticket from a kadmin/changepw
908      * ticket.
909      */
910     if (retval == 0 && service == NULL)
911         retval = verify_creds(args, *creds);
912 
913 done:
914     /*
915      * Free resources, including any credentials we have sitting around if we
916      * failed, and return the appropriate PAM error code.  If status is
917      * already set to something other than PAM_SUCCESS, we encountered a PAM
918      * error and will just return that code.  Otherwise, we need to map the
919      * Kerberos status code in retval to a PAM error code.
920      */
921     if (status == PAM_SUCCESS) {
922         switch (retval) {
923         case 0:
924             status = PAM_SUCCESS;
925             break;
926         case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
927             status = PAM_USER_UNKNOWN;
928             break;
929         case KRB5KDC_ERR_KEY_EXP:
930             status = PAM_NEW_AUTHTOK_REQD;
931             break;
932         case KRB5KDC_ERR_NAME_EXP:
933             status = PAM_ACCT_EXPIRED;
934             break;
935         case KRB5_KDC_UNREACH:
936         case KRB5_LIBOS_CANTREADPWD:
937         case KRB5_REALM_CANT_RESOLVE:
938         case KRB5_REALM_UNKNOWN:
939             status = PAM_AUTHINFO_UNAVAIL;
940             break;
941         default:
942             status = PAM_AUTH_ERR;
943             break;
944         }
945     }
946     if (status != PAM_SUCCESS && *creds != NULL) {
947         if (creds_valid)
948             krb5_free_cred_contents(ctx->context, *creds);
949         free(*creds);
950         *creds = NULL;
951     }
952     if (opts != NULL)
953         krb5_get_init_creds_opt_free(ctx->context, opts);
954 
955     /* Whatever the results, destroy the anonymous FAST cache. */
956     if (ctx->fast_cache != NULL) {
957         krb5_cc_destroy(ctx->context, ctx->fast_cache);
958         ctx->fast_cache = NULL;
959     }
960     return status;
961 }
962 
963 
964 /*
965  * Authenticate a user via Kerberos.
966  *
967  * It would be nice to be able to save the ticket cache temporarily as a
968  * memory cache and then only write it out to disk during the session
969  * initialization.  Unfortunately, OpenSSH 4.2 and later do PAM authentication
970  * in a subprocess and therefore has no saved module-specific data available
971  * once it opens a session, so we have to save the ticket cache to disk and
972  * store in the environment where it is.  The alternative is to use something
973  * like System V shared memory, which seems like more trouble than it's worth.
974  */
975 int
pamk5_authenticate(struct pam_args * args)976 pamk5_authenticate(struct pam_args *args)
977 {
978     struct context *ctx = NULL;
979     krb5_creds *creds = NULL;
980     char *pass = NULL;
981     char *principal;
982     int pamret;
983     bool set_context = false;
984     krb5_error_code retval;
985 
986     /* Temporary backward compatibility. */
987     if (args->config->use_authtok && !args->config->force_first_pass) {
988         putil_err(args, "use_authtok option in authentication group should"
989                         " be changed to force_first_pass");
990         args->config->force_first_pass = true;
991     }
992 
993     /* Create a context and obtain the user. */
994     pamret = pamk5_context_new(args);
995     if (pamret != PAM_SUCCESS)
996         goto done;
997     ctx = args->config->ctx;
998 
999     /* Check whether we should ignore this user. */
1000     if (pamk5_should_ignore(args, ctx->name)) {
1001         pamret = PAM_USER_UNKNOWN;
1002         goto done;
1003     }
1004 
1005     /*
1006      * Do the actual authentication.
1007      *
1008      * The complexity arises if the password was expired (which means the
1009      * Kerberos library was also unable to prompt for the password change
1010      * internally).  In that case, there are three possibilities:
1011      * fail_pwchange says we treat that as an authentication failure and stop,
1012      * defer_pwchange says to set a flag that will result in an error at the
1013      * acct_mgmt step, and force_pwchange says that we should change the
1014      * password here and now.
1015      *
1016      * defer_pwchange is the formally correct behavior.  Set a flag in the
1017      * context and return success.  That flag will later be checked by
1018      * pam_sm_acct_mgmt.  We need to set the context as PAM data in the
1019      * defer_pwchange case, but we don't want to set the PAM data until we've
1020      * checked .k5login.  If we've stacked multiple pam-krb5 invocations in
1021      * different realms as optional, we don't want to override a previous
1022      * successful authentication.
1023      *
1024      * Note this means that, if the user can authenticate with multiple realms
1025      * and authentication succeeds in one realm and is then expired in a later
1026      * realm, the expiration in the latter realm wins.  This isn't ideal, but
1027      * avoiding that case is more complicated than it's worth.
1028      *
1029      * We would like to set the current password as PAM_OLDAUTHTOK so that
1030      * when the application subsequently calls pam_chauthtok, the user won't
1031      * be reprompted.  However, the PAM library clears all the auth tokens
1032      * when pam_authenticate exits, so this isn't possible.
1033      *
1034      * In the force_pwchange case, try to use the password the user just
1035      * entered to authenticate to the password changing service, but don't
1036      * throw an error if that doesn't work.  We have to move it from
1037      * PAM_AUTHTOK to PAM_OLDAUTHTOK to be in the place where password
1038      * changing expects, and have to unset PAM_AUTHTOK or we'll just change
1039      * the password to the same thing it was.
1040      */
1041     pamret = pamk5_password_auth(args, NULL, &creds);
1042     if (pamret == PAM_NEW_AUTHTOK_REQD) {
1043         if (args->config->fail_pwchange)
1044             pamret = PAM_AUTH_ERR;
1045         else if (args->config->defer_pwchange) {
1046             putil_debug(args, "expired account, deferring failure");
1047             ctx->expired = 1;
1048             pamret = PAM_SUCCESS;
1049         } else if (args->config->force_pwchange) {
1050             pam_syslog(args->pamh, LOG_INFO,
1051                        "user %s password expired, forcing password change",
1052                        ctx->name);
1053             pamk5_conv(args, "Password expired.  You must change it now.",
1054                        PAM_TEXT_INFO, NULL);
1055             pamret = pam_get_item(args->pamh, PAM_AUTHTOK,
1056                                   (PAM_CONST void **) &pass);
1057             if (pamret == PAM_SUCCESS && pass != NULL)
1058                 pam_set_item(args->pamh, PAM_OLDAUTHTOK, pass);
1059             pam_set_item(args->pamh, PAM_AUTHTOK, NULL);
1060             args->config->use_first_pass = true;
1061             pamret = pamk5_password_change(args, false);
1062             if (pamret == PAM_SUCCESS)
1063                 putil_debug(args, "successfully changed expired password");
1064         }
1065     }
1066     if (pamret != PAM_SUCCESS) {
1067         putil_log_failure(args, "authentication failure");
1068         goto done;
1069     }
1070 
1071     /* Check .k5login and alt_auth_map. */
1072     pamret = pamk5_authorized(args);
1073     if (pamret != PAM_SUCCESS) {
1074         putil_log_failure(args, "failed authorization check");
1075         goto done;
1076     }
1077 
1078     /* Reset PAM_USER in case we canonicalized, but ignore errors. */
1079     if (!ctx->expired && !args->config->no_update_user) {
1080         pamret = pam_set_item(args->pamh, PAM_USER, ctx->name);
1081         if (pamret != PAM_SUCCESS)
1082             putil_err_pam(args, pamret, "cannot set PAM_USER");
1083     }
1084 
1085     /* Log the successful authentication. */
1086     retval = krb5_unparse_name(ctx->context, ctx->princ, &principal);
1087     if (retval != 0) {
1088         putil_err_krb5(args, retval, "krb5_unparse_name failed");
1089         pam_syslog(args->pamh, LOG_INFO, "user %s authenticated as UNKNOWN",
1090                    ctx->name);
1091     } else {
1092         pam_syslog(args->pamh, LOG_INFO, "user %s authenticated as %s%s",
1093                    ctx->name, principal, ctx->expired ? " (expired)" : "");
1094         krb5_free_unparsed_name(ctx->context, principal);
1095     }
1096 
1097     /* Now that we know we're successful, we can store the context. */
1098     pamret = pam_set_data(args->pamh, "pam_krb5", ctx, pamk5_context_destroy);
1099     if (pamret != PAM_SUCCESS) {
1100         putil_err_pam(args, pamret, "cannot set context data");
1101         pamk5_context_free(args);
1102         pamret = PAM_SERVICE_ERR;
1103         goto done;
1104     }
1105     set_context = true;
1106 
1107     /*
1108      * If we have an expired account or if we're not creating a ticket cache,
1109      * we're done.  Otherwise, store the obtained credentials in a temporary
1110      * cache.
1111      */
1112     if (!args->config->no_ccache && !ctx->expired)
1113         pamret = pamk5_cache_init_random(args, creds);
1114 
1115 done:
1116     if (creds != NULL && ctx != NULL) {
1117         krb5_free_cred_contents(ctx->context, creds);
1118         free(creds);
1119     }
1120 
1121     /*
1122      * Don't free our Kerberos context if we set a context, since the context
1123      * will take care of that.
1124      */
1125     if (set_context)
1126         args->ctx = NULL;
1127 
1128     /*
1129      * Clear the context on failure so that the account management module
1130      * knows that we didn't authenticate with Kerberos.  Only clear the
1131      * context if we set it.  Otherwise, we may be blowing away the context of
1132      * a previous successful authentication.
1133      */
1134     if (pamret != PAM_SUCCESS) {
1135         if (set_context)
1136             pam_set_data(args->pamh, "pam_krb5", NULL, NULL);
1137         else
1138             pamk5_context_free(args);
1139     }
1140     return pamret;
1141 }
1142