1 /*
2 * Copyright 2003,2004,2005,2006,2007,2008,2009,2010,2012,2013,2014 Red Hat, Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, and the entire permission notice in its entirety,
9 * including the disclaimer of warranties.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote
14 * products derived from this software without specific prior
15 * written permission.
16 *
17 * ALTERNATIVELY, this product may be distributed under the terms of the
18 * GNU Lesser General Public License, in which case the provisions of the
19 * LGPL are required INSTEAD OF the above restrictions.
20 *
21 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
22 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
23 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
24 * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
28 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 #include "../config.h"
34
35 #include <sys/types.h>
36 #include <errno.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40
41 #ifdef HAVE_SECURITY_PAM_APPL_H
42 #include <security/pam_appl.h>
43 #endif
44
45 #ifdef HAVE_SECURITY_PAM_MODULES_H
46 #define PAM_SM_AUTH
47 #define PAM_SM_SESSION
48 #include <security/pam_modules.h>
49 #endif
50
51 #include KRB5_H
52
53 #include "conv.h"
54 #include "init.h"
55 #include "initopts.h"
56 #include "items.h"
57 #include "kuserok.h"
58 #include "log.h"
59 #include "options.h"
60 #include "prompter.h"
61 #include "session.h"
62 #include "sly.h"
63 #include "stash.h"
64 #include "tokens.h"
65 #include "userinfo.h"
66 #include "v5.h"
67 #include "xstr.h"
68
69 int
pam_sm_authenticate(pam_handle_t * pamh,int flags,int argc,PAM_KRB5_MAYBE_CONST char ** argv)70 pam_sm_authenticate(pam_handle_t *pamh, int flags,
71 int argc, PAM_KRB5_MAYBE_CONST char **argv)
72 {
73 PAM_KRB5_MAYBE_CONST char *user;
74 krb5_context ctx;
75 struct _pam_krb5_options *options;
76 struct _pam_krb5_user_info *userinfo;
77 struct _pam_krb5_stash *stash;
78 krb5_get_init_creds_opt *gic_options;
79 int i, retval, use_third_pass, prompted, prompt_result;
80 char *first_pass, *second_pass;
81
82 /* Initialize Kerberos. */
83 if (_pam_krb5_init_ctx(&ctx, argc, argv) != 0) {
84 warn("error initializing Kerberos");
85 return PAM_SERVICE_ERR;
86 }
87
88 /* Get the user's name. */
89 i = pam_get_user(pamh, &user, NULL);
90 if ((i != PAM_SUCCESS) || (user == NULL)) {
91 warn("could not identify user name");
92 _pam_krb5_free_ctx(ctx);
93 return i;
94 }
95
96 /* Read our options. */
97 i = v5_alloc_get_init_creds_opt(ctx, &gic_options);
98 if (i != 0) {
99 warn("error initializing options (shouldn't happen)");
100 _pam_krb5_free_ctx(ctx);
101 return PAM_SERVICE_ERR;
102 }
103 options = _pam_krb5_options_init(pamh, argc, argv, ctx,
104 _pam_krb5_option_role_general);
105 if (options == NULL) {
106 warn("error parsing options (shouldn't happen)");
107 v5_free_get_init_creds_opt(ctx, gic_options);
108 _pam_krb5_free_ctx(ctx);
109 return PAM_SERVICE_ERR;
110 }
111 if (options->debug) {
112 debug("called to authenticate '%s', configured realm '%s'",
113 user, options->realm);
114 }
115 _pam_krb5_set_init_opts(ctx, gic_options, options);
116
117 /* Prompt for the password, as we might need to. */
118 prompted = 0;
119 prompt_result = PAM_ABORT;
120 second_pass = NULL;
121 if (options->use_second_pass) {
122 first_pass = NULL;
123 i = _pam_krb5_get_item_text(pamh, PAM_AUTHTOK, &first_pass);
124 if ((i != PAM_SUCCESS) || (first_pass == NULL)) {
125 /* Nobody's asked for a password yet. */
126 prompt_result = _pam_krb5_prompt_for(pamh,
127 Y_("Password: "),
128 &second_pass);
129 prompted = 1;
130 }
131 }
132
133 /* Get information about the user and the user's principal name. */
134 userinfo = _pam_krb5_user_info_init(ctx, user, options);
135 if (userinfo == NULL) {
136 if (options->ignore_unknown_principals) {
137 retval = PAM_IGNORE;
138 } else {
139 warn("error getting information about '%s'", user);
140 retval = PAM_USER_UNKNOWN;
141 }
142 if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
143 if (options->debug) {
144 debug("saving newly-entered "
145 "password for use by "
146 "other modules");
147 }
148 pam_set_item(pamh, PAM_AUTHTOK, second_pass);
149 }
150 /* Clean up and return. */
151 _pam_krb5_options_free(pamh, ctx, options);
152 v5_free_get_init_creds_opt(ctx, gic_options);
153 _pam_krb5_free_ctx(ctx);
154 return retval;
155 }
156 if (options->debug) {
157 debug("authenticating '%s'", userinfo->unparsed_name);
158 }
159
160 /* Check the minimum UID argument. */
161 if ((options->user_check) &&
162 (options->minimum_uid != (uid_t) -1) &&
163 (userinfo->uid < options->minimum_uid)) {
164 if (options->debug) {
165 debug("ignoring '%s' -- uid below minimum = %lu", user,
166 (unsigned long) options->minimum_uid);
167 }
168 _pam_krb5_user_info_free(ctx, userinfo);
169 if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
170 if (options->debug) {
171 debug("saving newly-entered "
172 "password for use by "
173 "other modules");
174 }
175 pam_set_item(pamh, PAM_AUTHTOK, second_pass);
176 }
177 _pam_krb5_options_free(pamh, ctx, options);
178 v5_free_get_init_creds_opt(ctx, gic_options);
179 _pam_krb5_free_ctx(ctx);
180 return PAM_IGNORE;
181 }
182
183 /* Get the stash for this user. */
184 stash = _pam_krb5_stash_get(pamh, user, userinfo, options);
185 if (stash == NULL) {
186 warn("error retrieving stash for '%s' (shouldn't happen)",
187 user);
188 _pam_krb5_user_info_free(ctx, userinfo);
189 if (prompted && (prompt_result == 0) && (second_pass != NULL)) {
190 if (options->debug) {
191 debug("saving newly-entered "
192 "password for use by "
193 "other modules");
194 }
195 pam_set_item(pamh, PAM_AUTHTOK, second_pass);
196 }
197 _pam_krb5_options_free(pamh, ctx, options);
198 v5_free_get_init_creds_opt(ctx, gic_options);
199 _pam_krb5_free_ctx(ctx);
200 return PAM_SERVICE_ERR;
201 }
202
203 /* If we've been called before, then the stash is more or less stale,
204 * so reset things for applications which call pam_authenticate() more
205 * than once with the same library context. */
206 stash->v5attempted = 0;
207
208 retval = PAM_AUTH_ERR;
209
210 /* Ideally we're only going to let libkrb5 ask questions once, and
211 * after that we intend to lie to it. */
212 use_third_pass = options->use_third_pass;
213
214 /* Try with the stored password, if we've been told to use just that
215 * value. */
216 first_pass = NULL;
217 if ((retval != PAM_SUCCESS) && options->use_first_pass) {
218 i = _pam_krb5_get_item_text(pamh, PAM_AUTHTOK, &first_pass);
219 if ((i == PAM_SUCCESS) &&
220 (flags & PAM_DISALLOW_NULL_AUTHTOK) &&
221 (first_pass != NULL) &&
222 (strlen(first_pass) == 0)) {
223 warn("disallowing NULL authtok for '%s'", user);
224 retval = PAM_AUTH_ERR;
225 i = PAM_AUTH_ERR;
226 }
227 if ((i == PAM_SUCCESS) &&
228 (first_pass != NULL) &&
229 (strlen(first_pass) > 0)) {
230 if (options->debug) {
231 if (use_third_pass) {
232 debug("trying previously-entered "
233 "password for '%s', allowing "
234 "libkrb5 to prompt for more",
235 user);
236 } else {
237 debug("trying previously-entered "
238 "password for '%s'", user);
239 }
240 }
241 retval = v5_get_creds(stash->v5ctx, pamh,
242 &stash->v5ccache,
243 &stash->v5armorccache,
244 user, userinfo,
245 options,
246 KRB5_TGS_NAME,
247 first_pass,
248 gic_options,
249 use_third_pass ?
250 _pam_krb5_normal_prompter :
251 _pam_krb5_previous_prompter,
252 &stash->v5expired,
253 &stash->v5result);
254 use_third_pass = 0;
255 stash->v5external = 0;
256 stash->v5attempted = 1;
257 if (options->debug) {
258 debug("got result %d (%s)", stash->v5result,
259 v5_error_message(stash->v5result));
260 }
261 }
262 if ((retval == PAM_SUCCESS) &&
263 (options->ignore_afs == 0) &&
264 (options->tokens == 1) &&
265 tokens_useful()) {
266 tokens_obtain(ctx, stash, options, userinfo, 1);
267 }
268 }
269
270 /* If that didn't work, and we're allowed to ask for a new password, do
271 * so in preparation for another attempt. */
272 if ((retval != PAM_SUCCESS) &&
273 (retval != PAM_USER_UNKNOWN) &&
274 options->use_second_pass) {
275 /* The "second_pass" variable already contains a value if we
276 * asked for one. */
277 if (!prompted) {
278 prompt_result = _pam_krb5_prompt_for(pamh,
279 Y_("Password: "),
280 &second_pass);
281 prompted = 1;
282 }
283 i = prompt_result;
284 if ((i == PAM_SUCCESS) &&
285 (flags & PAM_DISALLOW_NULL_AUTHTOK) &&
286 (second_pass != NULL) &&
287 (strlen(second_pass) == 0)) {
288 warn("disallowing NULL authtok for '%s'", user);
289 retval = PAM_AUTH_ERR;
290 i = PAM_AUTH_ERR;
291 }
292 if ((i == PAM_SUCCESS) &&
293 (second_pass != NULL) &&
294 (strlen(second_pass) > 0)) {
295 /* Save the password for the next module. */
296 if (options->debug) {
297 debug("saving newly-entered "
298 "password for use by "
299 "other modules");
300 }
301 pam_set_item(pamh, PAM_AUTHTOK, second_pass);
302 if (options->debug) {
303 if (use_third_pass) {
304 debug("trying newly-entered "
305 "password for '%s', allowing "
306 "libkrb5 to prompt for more",
307 user);
308 } else {
309 debug("trying newly-entered "
310 "password for '%s'", user);
311 }
312 }
313 retval = v5_get_creds(stash->v5ctx, pamh,
314 &stash->v5ccache,
315 &stash->v5armorccache,
316 user, userinfo,
317 options,
318 KRB5_TGS_NAME,
319 second_pass,
320 gic_options,
321 use_third_pass ?
322 _pam_krb5_normal_prompter :
323 _pam_krb5_always_fail_prompter,
324 &stash->v5expired,
325 &stash->v5result);
326 use_third_pass = 0;
327 stash->v5external = 0;
328 stash->v5attempted = 1;
329 if (options->debug) {
330 debug("got result %d (%s)", stash->v5result,
331 v5_error_message(stash->v5result));
332 }
333 }
334 if ((retval == PAM_SUCCESS) &&
335 (options->ignore_afs == 0) &&
336 (options->tokens == 1) &&
337 tokens_useful()) {
338 tokens_obtain(ctx, stash, options, userinfo, 1);
339 }
340 }
341
342 /* If we didn't use the first password (because it wasn't set), and we
343 * didn't ask for a password (due to the "no_initial_prompt" flag,
344 * probably), and we can let libkrb5 ask questions (no
345 * "no_subsequent_prompt"), then let libkrb5 have another go. */
346 if ((retval != PAM_SUCCESS) &&
347 (retval != PAM_USER_UNKNOWN) &&
348 use_third_pass) {
349 if (options->debug) {
350 debug("not using an entered password for '%s', "
351 "allowing libkrb5 to prompt for more", user);
352 }
353 retval = v5_get_creds(stash->v5ctx, pamh,
354 &stash->v5ccache,
355 &stash->v5armorccache,
356 user, userinfo,
357 options,
358 KRB5_TGS_NAME,
359 NULL,
360 gic_options,
361 options->permit_password_callback ?
362 _pam_krb5_always_prompter :
363 _pam_krb5_normal_prompter,
364 &stash->v5expired,
365 &stash->v5result);
366 stash->v5external = 0;
367 stash->v5attempted = 1;
368 if (options->debug) {
369 debug("got result %d (%s)", stash->v5result,
370 v5_error_message(stash->v5result));
371 }
372 if ((retval == PAM_SUCCESS) &&
373 (options->ignore_afs == 0) &&
374 (options->tokens == 1) &&
375 tokens_useful()) {
376 tokens_obtain(ctx, stash, options, userinfo, 1);
377 }
378 }
379
380 /* If we got this far, check the target user's .k5login file. */
381 if ((retval == PAM_SUCCESS) && options->user_check &&
382 (options->ignore_k5login == 0)) {
383 if (_pam_krb5_kuserok(ctx, stash, options, userinfo, user,
384 userinfo->uid, userinfo->gid) != TRUE) {
385 notice("account checks fail for '%s': user disallowed "
386 "by .k5login file for '%s'",
387 userinfo->unparsed_name, user);
388 retval = PAM_PERM_DENIED;
389 } else {
390 if (options->debug) {
391 debug("'%s' passes .k5login check for '%s'",
392 userinfo->unparsed_name, user);
393 }
394 }
395 }
396
397 /* Log the authentication status, optionally saving the credentials in
398 * a piece of shared memory. */
399 if (retval == PAM_SUCCESS) {
400 if (options->use_shmem) {
401 _pam_krb5_stash_shm_write(pamh, stash, options,
402 user, userinfo);
403 }
404 notice("authentication succeeds for '%s' (%s)", user,
405 userinfo->unparsed_name);
406 } else {
407 if ((retval == PAM_USER_UNKNOWN) &&
408 options->ignore_unknown_principals) {
409 retval = PAM_IGNORE;
410 } else {
411 notice("authentication fails for '%s' (%s): %s (%s)",
412 user,
413 userinfo->unparsed_name,
414 pam_strerror(pamh, retval),
415 v5_error_message(stash->v5result));
416 }
417 }
418
419 /* Clean up. */
420 if (options->debug) {
421 debug("pam_authenticate returning %d (%s)", retval,
422 pam_strerror(pamh, retval));
423 }
424 v5_free_get_init_creds_opt(ctx, gic_options);
425 _pam_krb5_options_free(pamh, ctx, options);
426 _pam_krb5_user_info_free(ctx, userinfo);
427 _pam_krb5_free_ctx(ctx);
428
429 return retval;
430 }
431
432 int
pam_sm_setcred(pam_handle_t * pamh,int flags,int argc,PAM_KRB5_MAYBE_CONST char ** argv)433 pam_sm_setcred(pam_handle_t *pamh, int flags,
434 int argc, PAM_KRB5_MAYBE_CONST char **argv)
435 {
436 const char *why = "";
437 if (flags & PAM_ESTABLISH_CRED) {
438 return _pam_krb5_open_session(pamh, flags, argc, argv,
439 "pam_setcred(PAM_ESTABLISH_CRED)",
440 _pam_krb5_session_caller_setcred);
441 }
442 if (flags & (PAM_REINITIALIZE_CRED | PAM_REFRESH_CRED)) {
443 if (flags & PAM_REINITIALIZE_CRED) {
444 why = "pam_setcred(PAM_REINITIALIZE_CRED)";
445 if (flags & PAM_REFRESH_CRED) {
446 why = "pam_setcred(PAM_REINITIALIZE_CRED|PAM_REFRESH_CRED)";
447 }
448 } else {
449 why = "pam_setcred(PAM_REFRESH_CRED)";
450 }
451 if (_pam_krb5_sly_looks_unsafe() == 0) {
452 return _pam_krb5_sly_maybe_refresh(pamh, flags, why,
453 argc, argv);
454 } else {
455 return PAM_IGNORE;
456 }
457 }
458 if (flags & PAM_DELETE_CRED) {
459 return _pam_krb5_close_session(pamh, flags, argc, argv,
460 "pam_setcred(PAM_DELETE_CRED)",
461 _pam_krb5_session_caller_setcred);
462 }
463 warn("pam_setcred() called with no flags");
464 return PAM_SERVICE_ERR;
465 }
466