1 /*
2 * Copyright 2003,2004,2005,2006,2007,2008,2009,2010,2011,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 <sys/stat.h>
37
38 #ifdef HAVE_SECURITY_PAM_APPL_H
39 #include <security/pam_appl.h>
40 #endif
41
42 #ifdef HAVE_SECURITY_PAM_MODULES_H
43 #define PAM_SM_PASSWORD
44 #include <security/pam_modules.h>
45 #endif
46
47 #include <limits.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51
52 #include KRB5_H
53
54 #include "conv.h"
55 #include "init.h"
56 #include "initopts.h"
57 #include "items.h"
58 #include "log.h"
59 #include "options.h"
60 #include "prompter.h"
61 #include "stash.h"
62 #include "userinfo.h"
63 #include "v5.h"
64 #include "xstr.h"
65
66 int
pam_sm_chauthtok(pam_handle_t * pamh,int flags,int argc,PAM_KRB5_MAYBE_CONST char ** argv)67 pam_sm_chauthtok(pam_handle_t *pamh, int flags,
68 int argc, PAM_KRB5_MAYBE_CONST char **argv)
69 {
70 PAM_KRB5_MAYBE_CONST char *user;
71 char prompt[LINE_MAX], prompt2[LINE_MAX], *password, *password2;
72 krb5_context ctx;
73 krb5_creds pwc_creds;
74 struct _pam_krb5_options *options;
75 struct _pam_krb5_user_info *userinfo;
76 struct _pam_krb5_stash *stash;
77 krb5_get_init_creds_opt *gic_options, *tmp_gicopts;
78 int tmp_result, prelim_attempted;
79 int i, retval, use_third_pass;
80 char *pwhelp;
81 struct stat st;
82 FILE *fp;
83 struct pam_message message;
84
85 /* Initialize Kerberos. */
86 if (_pam_krb5_init_ctx(&ctx, argc, argv) != 0) {
87 warn("error initializing Kerberos");
88 return PAM_SERVICE_ERR;
89 }
90
91 /* Get the user's name. */
92 i = pam_get_user(pamh, &user, NULL);
93 if ((i != PAM_SUCCESS) || (user == NULL)) {
94 warn("could not identify user name");
95 _pam_krb5_free_ctx(ctx);
96 return i;
97 }
98
99 /* Read our options. */
100 i = v5_alloc_get_init_creds_opt(ctx, &gic_options);
101 if (i != 0) {
102 warn("error initializing options (shouldn't happen)");
103 _pam_krb5_free_ctx(ctx);
104 return PAM_SERVICE_ERR;
105 }
106 options = _pam_krb5_options_init(pamh, argc, argv, ctx,
107 _pam_krb5_option_role_chauthtok);
108 if (options == NULL) {
109 warn("error parsing options (shouldn't happen)");
110 v5_free_get_init_creds_opt(ctx, gic_options);
111 _pam_krb5_free_ctx(ctx);
112 return PAM_SERVICE_ERR;
113 }
114 _pam_krb5_set_init_opts(ctx, gic_options, options);
115
116 /* Get information about the user and the user's principal name. */
117 userinfo = _pam_krb5_user_info_init(ctx, user, options);
118 if (userinfo == NULL) {
119 if (options->ignore_unknown_principals) {
120 retval = PAM_IGNORE;
121 } else {
122 warn("error getting information about '%s'", user);
123 retval = PAM_USER_UNKNOWN;
124 }
125 _pam_krb5_options_free(pamh, ctx, options);
126 v5_free_get_init_creds_opt(ctx, gic_options);
127 _pam_krb5_free_ctx(ctx);
128 return retval;
129 }
130
131 /* Check the minimum UID argument. */
132 if ((options->user_check) &&
133 (options->minimum_uid != (uid_t)-1) &&
134 (userinfo->uid < options->minimum_uid)) {
135 if (options->debug) {
136 debug("ignoring '%s' -- uid below minimum = %lu", user,
137 (unsigned long) options->minimum_uid);
138 }
139 _pam_krb5_user_info_free(ctx, userinfo);
140 _pam_krb5_options_free(pamh, ctx, options);
141 v5_free_get_init_creds_opt(ctx, gic_options);
142 _pam_krb5_free_ctx(ctx);
143 return PAM_IGNORE;
144 }
145
146 /* Get the stash of credentials. If we are interactively prompting
147 * the user for information, we're not expected to ask for the user's
148 * current password more than once, so we use it to get a changepw
149 * ticket during the first pass, and we store that for use in the
150 * second pass. It should have a low lifetime, so we needn't free it
151 * just now. */
152 retval = PAM_AUTH_ERR;
153 stash = _pam_krb5_stash_get(pamh, user, userinfo, options);
154
155 /* If this is the first pass, just check the user's password by
156 * obtaining a password-changing initial ticket. */
157 if (flags & PAM_PRELIM_CHECK) {
158 retval = PAM_AUTH_ERR;
159 prelim_attempted = 0;
160
161 /* Ideally we're only going to let libkrb5 ask questions once,
162 * and after that we intend to lie to it. */
163 use_third_pass = options->use_third_pass;
164
165 /* Set up options for getting password-changing creds. */
166 i = v5_alloc_get_init_creds_opt(ctx, &tmp_gicopts);
167 if (i == 0) {
168 /* Set hard-coded defaults for password-changing creds
169 * which might not match generally-used options. */
170 _pam_krb5_set_init_opts_for_pwchange(ctx,
171 tmp_gicopts,
172 options);
173 } else {
174 /* Try library defaults. */
175 tmp_gicopts = NULL;
176 }
177
178 /* Display password help text. */
179 if ((options->pwhelp != NULL) && (options->pwhelp[0] != '\0')) {
180 fp = fopen(options->pwhelp, "r");
181 if (fp != NULL) {
182 if (options->debug) {
183 debug("opened help file '%s'",
184 options->pwhelp);
185 }
186 if (fstat(fileno(fp), &st) != -1) {
187 pwhelp = malloc(st.st_size + 1);
188 if (pwhelp == NULL) {
189 memset(prompt, '\0',
190 sizeof(prompt));
191 i = fread(prompt, 1,
192 sizeof(prompt) -1,
193 fp);
194 pwhelp = prompt;
195 } else {
196 memset(pwhelp, '\0',
197 st.st_size + 1);
198 i = fread(pwhelp, 1,
199 st.st_size, fp);
200 if (options->debug) {
201 debug("read %d bytes",
202 (int) st.st_size);
203 }
204 }
205 } else {
206 memset(prompt, '\0', sizeof(prompt));
207 i = fread(prompt, 1,
208 sizeof(prompt) - 1, fp);
209 pwhelp = prompt;
210 }
211 if (i > 0) {
212 message.msg = pwhelp;
213 message.msg_style = PAM_TEXT_INFO;
214 _pam_krb5_conv_call(pamh, &message, 1,
215 NULL);
216 }
217 if (pwhelp != prompt) {
218 xstrfree(pwhelp);
219 }
220 fclose(fp);
221 } else {
222 if (options->debug) {
223 debug("failed to open help file '%s'",
224 options->pwhelp);
225 }
226 }
227 }
228
229 /* Obtain the current password. */
230 password = NULL;
231 if (options->use_first_pass) {
232 /* Read the stored password. */
233 password = NULL;
234 i = _pam_krb5_get_item_text(pamh, PAM_OLDAUTHTOK,
235 &password);
236 /* Duplicate the password so that we can free it later
237 * without corrupting the heap. */
238 if ((password != NULL) && (i == PAM_SUCCESS)) {
239 password = xstrdup(password);
240 }
241 }
242 if ((password != NULL) && (i == PAM_SUCCESS)) {
243 if (options->debug) {
244 if (use_third_pass) {
245 debug("trying previously-entered "
246 "password for '%s', allowing "
247 "libkrb5 to prompt for more",
248 user);
249 } else {
250 debug("trying previously-entered "
251 "password for '%s'", user);
252 }
253 }
254 /* We have a password, so try to obtain initial
255 * credentials using the password. */
256 i = v5_get_creds(stash->v5ctx, pamh,
257 &stash->v5ccache,
258 &stash->v5armorccache,
259 user, userinfo,
260 options,
261 PASSWORD_CHANGE_PRINCIPAL,
262 password, tmp_gicopts,
263 use_third_pass ?
264 _pam_krb5_normal_prompter :
265 _pam_krb5_previous_prompter,
266 NULL,
267 &tmp_result);
268 prelim_attempted = 1;
269 use_third_pass = 0;
270 if (options->debug) {
271 debug("Got %d (%s) acquiring credentials for "
272 "%s: %s.",
273 tmp_result, v5_error_message(tmp_result),
274 PASSWORD_CHANGE_PRINCIPAL,
275 pam_strerror(pamh, i));
276 }
277 if (i != PAM_SUCCESS) {
278 /* No joy. */
279 xstrfree(password);
280 password = NULL;
281 }
282 retval = i;
283 }
284 if ((retval != PAM_SUCCESS) &&
285 (password == NULL) &&
286 options->use_second_pass) {
287 /* Ask the user for a password. */
288 sprintf(prompt, Y_("%s%sPassword: "),
289 options->banner,
290 strlen(options->banner) > 0 ? " " : "");
291 i = _pam_krb5_prompt_for(pamh, prompt, &password);
292 /* Save the old password for possible use by other
293 * modules. */
294 if ((password != NULL) && (i == PAM_SUCCESS)) {
295 pam_set_item(pamh, PAM_OLDAUTHTOK, password);
296 }
297 }
298 /* We have a second password, so try to obtain initial
299 * credentials using the password. */
300 if ((retval != PAM_SUCCESS) &&
301 ((password != NULL) && (i == PAM_SUCCESS))) {
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 i = v5_get_creds(stash->v5ctx, pamh,
314 &stash->v5ccache,
315 &stash->v5armorccache,
316 user, userinfo,
317 options,
318 PASSWORD_CHANGE_PRINCIPAL,
319 password, tmp_gicopts,
320 use_third_pass ?
321 _pam_krb5_normal_prompter :
322 _pam_krb5_always_fail_prompter,
323 NULL,
324 &tmp_result);
325 prelim_attempted = 1;
326 use_third_pass = 0;
327 if (options->debug) {
328 debug("Got %d (%s) acquiring credentials for "
329 "%s.",
330 tmp_result, v5_error_message(tmp_result),
331 PASSWORD_CHANGE_PRINCIPAL);
332 }
333 retval = i;
334 }
335 /* We haven't tried anything yet, so if it's allowed, try to
336 * obtain initial credentials, letting libkrb5 ask the
337 * questions. */
338 if ((retval != PAM_SUCCESS) &&
339 (prelim_attempted == 0) &&
340 use_third_pass) {
341 if (options->debug) {
342 debug("not using an entered password for '%s', "
343 "allowing libkrb5 to prompt", user);
344 }
345 i = v5_get_creds(stash->v5ctx, pamh,
346 &stash->v5ccache,
347 &stash->v5armorccache,
348 user, userinfo,
349 options,
350 PASSWORD_CHANGE_PRINCIPAL,
351 NULL, tmp_gicopts,
352 options->permit_password_callback ?
353 _pam_krb5_always_prompter :
354 _pam_krb5_normal_prompter,
355 NULL,
356 &tmp_result);
357 prelim_attempted = 1;
358 use_third_pass = 0;
359 if (options->debug) {
360 debug("Got %d (%s) acquiring credentials for "
361 "%s.",
362 tmp_result, v5_error_message(tmp_result),
363 PASSWORD_CHANGE_PRINCIPAL);
364 }
365 retval = i;
366 }
367
368 /* Clean up the password-changing options. */
369 v5_free_get_init_creds_opt(ctx, tmp_gicopts);
370 /* Free [the copy of] the password. */
371 xstrfree(password);
372 }
373
374 /* If this is the second pass, get the new password, use the
375 * credentials which we obtained and stashed in the first pass to set
376 * the user's password, and then use the new password to obtain a TGT.
377 * (If we're changing an expired password, then we'll need it to create
378 * a ccache file later.) */
379 if (flags & PAM_UPDATE_AUTHTOK) {
380 retval = PAM_AUTHTOK_ERR;
381 password = NULL;
382
383 /* If our preliminary check succeeded, then we'll have
384 * password-changing credentials. */
385 if (v5_ccache_has_pwc(ctx, stash->v5ccache, NULL) == 0) {
386 /* The new password (if it's already been requested by
387 * a previously-called module) is stored as the
388 * PAM_AUTHTOK item. The old one is stored as the
389 * PAM_OLDAUTHTOK item, but we don't use it here. */
390 i = _pam_krb5_get_item_text(pamh, PAM_AUTHTOK,
391 &password);
392
393 /* Duplicate the password, as above. */
394 if ((password != NULL) && (i == PAM_SUCCESS)) {
395 password = xstrdup(password);
396 } else {
397 /* Indicate that we didn't get a satisfactory
398 * password, but we can ask the user. */
399 retval = PAM_AUTHTOK_ERR;
400 password = NULL;
401 }
402
403 /* If there wasn't a previously-entered password, and
404 * we can't ask the user, then return an error. */
405 if ((password == NULL) && (options->use_authtok)) {
406 retval = PAM_AUTHTOK_RECOVERY_ERR;
407 }
408 } else {
409 /* If we didn't pass the preliminary check, then stand
410 * back and let whichever module it was that told the
411 * calling application it was okay to continue to do
412 * its thing. */
413 if (options->ignore_unknown_principals) {
414 debug("no password-changing credentials for "
415 "'%s' obtained, ignoring user",
416 userinfo->unparsed_name);
417 retval = PAM_IGNORE;
418 } else {
419 debug("no password-changing credentials for "
420 "'%s' obtained, user not known",
421 userinfo->unparsed_name);
422 retval = PAM_USER_UNKNOWN;
423 }
424 }
425
426 /* If there wasn't a previously-entered password, and we are
427 * okay with that, ask for one. */
428 if ((password == NULL) && (retval == PAM_AUTHTOK_ERR)) {
429 /* Ask for the new password twice. */
430 sprintf(prompt, Y_("New %s%sPassword: "),
431 options->banner,
432 strlen(options->banner) > 0 ? " " : "");
433 sprintf(prompt2, Y_("Repeat New %s%sPassword: "),
434 options->banner,
435 strlen(options->banner) > 0 ? " " : "");
436 i = _pam_krb5_prompt_for_2(pamh, prompt, &password,
437 prompt2, &password2);
438 /* If they're not the same, return PAM_TRY_AGAIN. */
439 if (strcmp(password, password2) != 0) {
440 i = PAM_TRY_AGAIN;
441 retval = PAM_TRY_AGAIN;
442 }
443 /* Save the password for possible use by other
444 * modules. */
445 if (i == PAM_SUCCESS) {
446 pam_set_item(pamh, PAM_AUTHTOK, password);
447 }
448 /* Free the second password, we only need one copy. */
449 xstrfree(password2);
450 password2 = NULL;
451 }
452
453 /* We have the new password, so attempt to change the user's
454 * password using the previously-acquired password-changing
455 * ticket. */
456 memset(&pwc_creds, 0, sizeof(pwc_creds));
457 if ((password != NULL) &&
458 (retval == PAM_AUTHTOK_ERR) &&
459 (v5_ccache_has_pwc(ctx, stash->v5ccache, &pwc_creds) == 0)) {
460 int result_code;
461 krb5_data result_code_string, result_string;
462 result_code = -1;
463 result_string.length = 0;
464 result_string.data = NULL;
465 result_code_string.length = 0;
466 result_code_string.data = NULL;
467 i = v5_change_password(ctx, &pwc_creds, password,
468 &result_code,
469 &result_code_string,
470 &result_string);
471 krb5_free_cred_contents(ctx, &pwc_creds);
472 if ((i == 0) && (result_code == 0)) {
473 notice("password changed for %s",
474 userinfo->unparsed_name);
475 retval = PAM_SUCCESS;
476 } else {
477 if (i != 0) {
478 notice("password change failed for "
479 "%s: %s",
480 userinfo->unparsed_name,
481 v5_error_message(i));
482 } else {
483 notice("password change failed for "
484 "%s: %s: %.*s %s%.*s%s",
485 userinfo->unparsed_name,
486 v5_passwd_error_message(result_code),
487 (int) result_code_string.length,
488 (char *) result_code_string.data,
489 result_string.length ? "(" : "",
490 (int) result_string.length,
491 (char *) result_string.data,
492 result_string.length ? ")" : "");
493 }
494 if ((result_string.length > 0) ||
495 (result_code_string.length > 0)) {
496 notice_user(pamh, "%s: %.*s %s%.*s%s\n",
497 v5_passwd_error_message(result_code),
498 (int) result_code_string.length,
499 (const char *) result_code_string.data,
500 result_string.length ?
501 "(" : "",
502 (int) result_string.length,
503 (const char *) result_string.data,
504 result_string.length ?
505 ")" : "");
506 }
507 }
508 }
509
510 /* If we succeeded, obtain a new TGT using the new password. */
511 if (retval == PAM_SUCCESS) {
512 if (options->debug) {
513 debug("obtaining credentials using new "
514 "password for '%s'",
515 userinfo->unparsed_name);
516 }
517 i = v5_get_creds(stash->v5ctx, pamh, &stash->v5ccache,
518 &stash->v5armorccache,
519 user, userinfo, options,
520 KRB5_TGS_NAME,
521 password, gic_options,
522 _pam_krb5_always_fail_prompter,
523 NULL,
524 &stash->v5result);
525 stash->v5attempted = 1;
526 if (i == PAM_SUCCESS) {
527 if (options->use_shmem) {
528 _pam_krb5_stash_shm_write(pamh, stash,
529 options,
530 user, userinfo);
531 }
532 }
533 }
534
535 /* Free the new password. */
536 if (password != NULL) {
537 xstrfree(password);
538 }
539 }
540
541 /* Clean up. */
542 if (options->debug) {
543 debug("pam_chauthtok (%s) returning %d (%s)",
544 (flags & PAM_PRELIM_CHECK) ?
545 "preliminary check" :
546 ((flags & PAM_UPDATE_AUTHTOK) ?
547 "updating authtok":
548 "unknown phase"),
549 retval, pam_strerror(pamh, retval));
550 }
551 _pam_krb5_user_info_free(ctx, userinfo);
552 _pam_krb5_options_free(pamh, ctx, options);
553 v5_free_get_init_creds_opt(ctx, gic_options);
554 _pam_krb5_free_ctx(ctx);
555 return retval;
556 }
557