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