xref: /freebsd/contrib/pam-krb5/module/alt-auth.c (revision bf6873c5786e333d679a7838d28812febf479a8a)
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
pamk5_map_principal(struct pam_args * args,const char * username,char ** principal)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
pamk5_alt_auth(struct pam_args * args,const char * service,krb5_get_init_creds_opt * opts,const char * pass,krb5_creds * creds)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
pamk5_alt_auth_verify(struct pam_args * args)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