1 /*
2 SSSD
3
4 Kerberos 5 Backend Module -- tgt_req and changepw child
5
6 Authors:
7 Sumit Bose <sbose@redhat.com>
8
9 Copyright (C) 2009-2010 Red Hat
10
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 3 of the License, or
14 (at your option) any later version.
15
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License
22 along with this program. If not, see <http://www.gnu.org/licenses/>.
23 */
24
25 #include <sys/types.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <popt.h>
29
30 #include <security/pam_modules.h>
31
32 #include "util/util.h"
33 #include "util/user_info_msg.h"
34 #include "providers/child_common.h"
35 #include "providers/dp_backend.h"
36 #include "providers/krb5/krb5_auth.h"
37 #include "providers/krb5/krb5_utils.h"
38
39 struct krb5_child_ctx {
40 /* opts taken from kinit */
41 /* in seconds */
42 krb5_deltat starttime;
43 krb5_deltat lifetime;
44 krb5_deltat rlife;
45
46 int forwardable;
47 int proxiable;
48 int addresses;
49
50 int not_forwardable;
51 int not_proxiable;
52 int no_addresses;
53
54 int verbose;
55
56 char* principal_name;
57 char* service_name;
58 char* keytab_name;
59 char* k5_cache_name;
60 char* k4_cache_name;
61
62 action_type action;
63
64 char *kdcip;
65 char *realm;
66 char *changepw_principle;
67 char *ccache_dir;
68 char *ccname_template;
69 int auth_timeout;
70
71 int child_debug_fd;
72 };
73
74 struct krb5_req {
75 krb5_context ctx;
76 krb5_principal princ;
77 char* name;
78 krb5_creds *creds;
79 krb5_get_init_creds_opt *options;
80 pid_t child_pid;
81 int read_from_child_fd;
82 int write_to_child_fd;
83
84 struct be_req *req;
85 struct pam_data *pd;
86 struct krb5_child_ctx *krb5_ctx;
87 errno_t (*child_req)(int fd, struct krb5_req *kr);
88
89 char *ccname;
90 char *keytab;
91 bool validate;
92
93 const char *upn;
94 uid_t uid;
95 gid_t gid;
96 };
97
98 static krb5_context krb5_error_ctx;
99 static const char *__krb5_error_msg;
100 #define KRB5_DEBUG(level, krb5_error) do { \
101 __krb5_error_msg = sss_krb5_get_error_message(krb5_error_ctx, krb5_error); \
102 DEBUG(level, ("%d: [%d][%s]\n", __LINE__, krb5_error, __krb5_error_msg)); \
103 sss_krb5_free_error_message(krb5_error_ctx, __krb5_error_msg); \
104 } while(0);
105
106
107 static krb5_error_code sss_krb5_prompter(krb5_context context, void *data,
108 const char *name, const char *banner,
109 int num_prompts, krb5_prompt prompts[])
110 {
111 int ret;
112 struct krb5_req *kr = talloc_get_type(data, struct krb5_req);
113
114 if (num_prompts != 0) {
115 DEBUG(1, ("Cannot handle password prompts.\n"));
116 return KRB5_LIBOS_CANTREADPWD;
117 }
118
119 if (banner == NULL || *banner == '\0') {
120 DEBUG(5, ("Prompter called with empty banner, nothing to do.\n"));
121 return EOK;
122 }
123
124 DEBUG(9, ("Prompter called with [%s].\n", banner));
125
126 ret = pam_add_response(kr->pd, SSS_PAM_TEXT_MSG, strlen(banner)+1,
127 (const uint8_t *) banner);
128 if (ret != EOK) {
129 DEBUG(1, ("pam_add_response failed.\n"));
130 }
131
132 return EOK;
133 }
134
135
136 static krb5_error_code create_empty_cred(struct krb5_req *kr, krb5_creds **_cred)
137 {
138 krb5_error_code kerr;
139 krb5_creds *cred = NULL;
140 krb5_data *krb5_realm;
141
142 cred = calloc(sizeof(krb5_creds), 1);
143 if (cred == NULL) {
144 DEBUG(1, ("calloc failed.\n"));
145 return ENOMEM;
146 }
147
148 kerr = krb5_copy_principal(kr->ctx, kr->princ, &cred->client);
149 if (kerr != 0) {
150 DEBUG(1, ("krb5_copy_principal failed.\n"));
151 goto done;
152 }
153
154 krb5_realm = krb5_princ_realm(kr->ctx, kr->princ);
155
156 kerr = krb5_build_principal_ext(kr->ctx, &cred->server,
157 krb5_realm->length, krb5_realm->data,
158 KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME,
159 krb5_realm->length, krb5_realm->data, 0);
160 if (kerr != 0) {
161 DEBUG(1, ("krb5_build_principal_ext failed.\n"));
162 goto done;
163 }
164
165 done:
166 if (kerr != 0) {
167 if (cred != NULL && cred->client != NULL) {
168 krb5_free_principal(kr->ctx, cred->client);
169 }
170
171 free(cred);
172 } else {
173 *_cred = cred;
174 }
175
176 return kerr;
177 }
178
179 static krb5_error_code create_ccache_file(struct krb5_req *kr, krb5_creds *creds)
180 {
181 krb5_error_code kerr;
182 krb5_ccache tmp_cc = NULL;
183 char *cc_file_name;
184 int fd = -1;
185 size_t ccname_len;
186 char *dummy;
187 char *tmp_ccname;
188 krb5_creds *l_cred;
189
190 if (strncmp(kr->ccname, "FILE:", 5) == 0) {
191 cc_file_name = kr->ccname + 5;
192 } else {
193 cc_file_name = kr->ccname;
194 }
195
196 if (cc_file_name[0] != '/') {
197 DEBUG(1, ("Ccache filename is not an absolute path.\n"));
198 return EINVAL;
199 }
200
201 dummy = strrchr(cc_file_name, '/');
202 tmp_ccname = talloc_strndup(kr, cc_file_name, (size_t) (dummy-cc_file_name));
203 if (tmp_ccname == NULL) {
204 DEBUG(1, ("talloc_strdup failed.\n"));
205 return ENOMEM;
206 }
207 tmp_ccname = talloc_asprintf_append(tmp_ccname, "/.krb5cc_dummy_XXXXXX");
208
209 fd = mkstemp(tmp_ccname);
210 if (fd == -1) {
211 DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
212 return errno;
213 }
214
215 kerr = krb5_cc_resolve(kr->ctx, tmp_ccname, &tmp_cc);
216 if (kerr != 0) {
217 KRB5_DEBUG(1, kerr);
218 goto done;
219 }
220
221 kerr = krb5_cc_initialize(kr->ctx, tmp_cc, kr->princ);
222 if (kerr != 0) {
223 KRB5_DEBUG(1, kerr);
224 goto done;
225 }
226 if (fd != -1) {
227 close(fd);
228 fd = -1;
229 }
230
231 if (creds == NULL) {
232 kerr = create_empty_cred(kr, &l_cred);
233 if (kerr != 0) {
234 KRB5_DEBUG(1, kerr);
235 goto done;
236 }
237 } else {
238 l_cred = creds;
239 }
240
241 kerr = krb5_cc_store_cred(kr->ctx, tmp_cc, l_cred);
242 if (kerr != 0) {
243 KRB5_DEBUG(1, kerr);
244 goto done;
245 }
246
247 kerr = krb5_cc_close(kr->ctx, tmp_cc);
248 if (kerr != 0) {
249 KRB5_DEBUG(1, kerr);
250 goto done;
251 }
252 tmp_cc = NULL;
253
254 ccname_len = strlen(cc_file_name);
255 if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) {
256 fd = mkstemp(cc_file_name);
257 if (fd == -1) {
258 DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
259 kerr = errno;
260 goto done;
261 }
262 }
263
264 kerr = rename(tmp_ccname, cc_file_name);
265 if (kerr == -1) {
266 DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
267 }
268
269 done:
270 if (fd != -1) {
271 close(fd);
272 fd = -1;
273 }
274 if (kerr != 0 && tmp_cc != NULL) {
275 krb5_cc_destroy(kr->ctx, tmp_cc);
276 }
277 return kerr;
278 }
279
280 static errno_t pack_response_packet(struct response *resp, int status,
281 struct pam_data *pd)
282 {
283 size_t size = 0;
284 size_t p = 0;
285 struct response_data *pdr;
286
287 /* A buffer with the following structure must be created:
288 * int32_t status of the request (required)
289 * message (zero or more)
290 *
291 * A message consists of:
292 * int32_t type of the message
293 * int32_t length of the following data
294 * uint8_t[len] data
295 */
296
297 size = sizeof(int32_t);
298
299 pdr = pd->resp_list;
300 while (pdr != NULL) {
301 size += 2*sizeof(int32_t) + pdr->len;
302 pdr = pdr->next;
303 }
304
305
306 resp->buf = talloc_array(resp, uint8_t, size);
307 if (!resp->buf) {
308 DEBUG(1, ("Insufficient memory to create message.\n"));
309 return ENOMEM;
310 }
311
312 SAFEALIGN_SET_INT32(&resp->buf[p], status, &p);
313
314 pdr = pd->resp_list;
315 while(pdr != NULL) {
316 SAFEALIGN_SET_INT32(&resp->buf[p], pdr->type, &p);
317 SAFEALIGN_SET_INT32(&resp->buf[p], pdr->len, &p);
318 safealign_memcpy(&resp->buf[p], pdr->data, pdr->len, &p);
319
320 pdr = pdr->next;
321 }
322
323
324 resp->size = p;
325
326 return EOK;
327 }
328
329 static struct response *prepare_response_message(struct krb5_req *kr,
330 krb5_error_code kerr,
331 int pam_status)
332 {
333 char *msg = NULL;
334 const char *krb5_msg = NULL;
335 int ret;
336 struct response *resp;
337
338 resp = talloc_zero(kr, struct response);
339 if (resp == NULL) {
340 DEBUG(1, ("Initializing response failed.\n"));
341 return NULL;
342 }
343
344 if (kerr == 0) {
345 if(kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
346 pam_status = PAM_SUCCESS;
347 ret = EOK;
348 } else {
349 if (kr->ccname == NULL) {
350 DEBUG(1, ("Error obtaining ccname.\n"));
351 return NULL;
352 }
353
354 msg = talloc_asprintf(kr, "%s=%s",CCACHE_ENV_NAME, kr->ccname);
355 if (msg == NULL) {
356 DEBUG(1, ("talloc_asprintf failed.\n"));
357 return NULL;
358 }
359
360 pam_status = PAM_SUCCESS;
361 ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1,
362 (uint8_t *) msg);
363 talloc_zfree(msg);
364 }
365 } else {
366 krb5_msg = sss_krb5_get_error_message(krb5_error_ctx, kerr);
367 if (krb5_msg == NULL) {
368 DEBUG(1, ("sss_krb5_get_error_message failed.\n"));
369 return NULL;
370 }
371
372 ret = pam_add_response(kr->pd, SSS_PAM_SYSTEM_INFO,
373 strlen(krb5_msg) + 1,
374 (const uint8_t *) krb5_msg);
375 sss_krb5_free_error_message(krb5_error_ctx, krb5_msg);
376 }
377 if (ret != EOK) {
378 DEBUG(1, ("pam_add_response failed.\n"));
379 }
380
381 ret = pack_response_packet(resp, pam_status, kr->pd);
382 if (ret != EOK) {
383 DEBUG(1, ("pack_response_packet failed.\n"));
384 return NULL;
385 }
386
387 return resp;
388 }
389
390 static errno_t sendresponse(int fd, krb5_error_code kerr, int pam_status,
391 struct krb5_req *kr)
392 {
393 struct response *resp;
394 size_t written;
395 int ret;
396
397 resp = prepare_response_message(kr, kerr, pam_status);
398 if (resp == NULL) {
399 DEBUG(1, ("prepare_response_message failed.\n"));
400 return ENOMEM;
401 }
402
403 written = 0;
404 while (written < resp->size) {
405 ret = write(fd, resp->buf + written, resp->size - written);
406 if (ret == -1) {
407 if (errno == EAGAIN || errno == EINTR) {
408 continue;
409 }
410 ret = errno;
411 DEBUG(1, ("write failed [%d][%s].\n", ret, strerror(ret)));
412 return ret;
413 }
414 written += ret;
415 }
416
417 return EOK;
418 }
419
420 static krb5_error_code validate_tgt(struct krb5_req *kr)
421 {
422 krb5_error_code kerr;
423 krb5_error_code kt_err;
Event var_decl: Declaring variable "principal" without initializer.
Also see events: [uninit_use] | |
424 char *principal;
425 krb5_keytab keytab;
426 krb5_kt_cursor cursor;
427 krb5_keytab_entry entry;
428 krb5_verify_init_creds_opt opt;
429
430 memset(&keytab, 0, sizeof(keytab));
431 kerr = krb5_kt_resolve(kr->ctx, kr->keytab, &keytab);
At conditional (1): "kerr != 0": Taking false branch.
|
432 if (kerr != 0) {
433 DEBUG(1, ("error resolving keytab [%s], not verifying TGT.\n",
434 kr->keytab));
435 return kerr;
436 }
437
438 memset(&cursor, 0, sizeof(cursor));
439 kerr = krb5_kt_start_seq_get(kr->ctx, keytab, &cursor);
At conditional (2): "kerr != 0": Taking false branch.
|
440 if (kerr != 0) {
441 DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
442 kr->keytab));
443 return kerr;
444 }
445
446 /* We look for the first entry from our realm or take the last one */
447 memset(&entry, 0, sizeof(entry));
At conditional (3): "(kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0": Taking true branch.
|
448 while ((kt_err = krb5_kt_next_entry(kr->ctx, keytab, &entry, &cursor)) == 0) {
At conditional (4): "krb5_realm_compare(kr->ctx, entry.principal, kr->princ)": Taking true branch.
|
449 if (krb5_realm_compare(kr->ctx, entry.principal, kr->princ)) {
At conditional (5): "9 <= debug_level": Taking true branch.
At conditional (6): "debug_timestamps": Taking true branch.
| |
450 DEBUG(9, ("Found keytab entry with the realm of the credential.\n"));
451 break;
452 }
453
454 kerr = krb5_free_keytab_entry_contents(kr->ctx, &entry);
455 if (kerr != 0) {
456 DEBUG(1, ("Failed to free keytab entry.\n"));
457 }
458 memset(&entry, 0, sizeof(entry));
459 }
460
461 /* Close the keytab here. Even though we're using cursors, the file
462 * handle is stored in the krb5_keytab structure, and it gets
463 * overwritten when the verify_init_creds() call below creates its own
464 * cursor, creating a leak. */
465 kerr = krb5_kt_end_seq_get(kr->ctx, keytab, &cursor);
At conditional (7): "kerr != 0": Taking true branch.
|
466 if (kerr != 0) {
At conditional (8): "1 <= debug_level": Taking true branch.
At conditional (9): "debug_timestamps": Taking true branch.
| |
467 DEBUG(1, ("krb5_kt_end_seq_get failed, not verifying TGT.\n"));
468 goto done;
469 }
470
471 /* check if we got any errors from krb5_kt_next_entry */
472 if (kt_err != 0 && kt_err != KRB5_KT_END) {
473 DEBUG(1, ("error reading keytab [%s], not verifying TGT.\n",
474 kr->keytab));
475 goto done;
476 }
477
478 /* Get the principal to which the key belongs, for logging purposes. */
479 principal = NULL;
480 kerr = krb5_unparse_name(kr->ctx, entry.principal, &principal);
481 if (kerr != 0) {
482 DEBUG(1, ("internal error parsing principal name, "
483 "not verifying TGT.\n"));
484 goto done;
485 }
486
487
488 krb5_verify_init_creds_opt_init(&opt);
489 kerr = krb5_verify_init_creds(kr->ctx, kr->creds, entry.principal, keytab,
490 NULL, &opt);
491
492 if (kerr == 0) {
493 DEBUG(5, ("TGT verified using key for [%s].\n", principal));
494 } else {
495 DEBUG(1 ,("TGT failed verification using key for [%s].\n", principal));
496 }
497
498 done:
At conditional (10): "krb5_kt_close(kr->ctx, keytab) != 0": Taking true branch.
|
499 if (krb5_kt_close(kr->ctx, keytab) != 0) {
At conditional (11): "1 <= debug_level": Taking true branch.
At conditional (12): "debug_timestamps": Taking true branch.
| |
500 DEBUG(1, ("krb5_kt_close failed"));
501 }
At conditional (13): "krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0": Taking true branch.
|
502 if (krb5_free_keytab_entry_contents(kr->ctx, &entry) != 0) {
At conditional (14): "1 <= debug_level": Taking true branch.
At conditional (15): "debug_timestamps": Taking true branch.
| |
503 DEBUG(1, ("Failed to free keytab entry.\n"));
504 }
Event uninit_use: Using uninitialized value "principal".
Also see events: [var_decl] | |
505 if (principal != NULL) {
506 sss_krb5_free_unparsed_name(kr->ctx, principal);
507 }
508
509 return kerr;
510
511 }
512
513 static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
514 char *password)
515 {
516 krb5_error_code kerr = 0;
517 int ret;
518
519 kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
520 password, sss_krb5_prompter, kr, 0,
521 NULL, kr->options);
522 if (kerr != 0) {
523 KRB5_DEBUG(1, kerr);
524 return kerr;
525 }
526
527 if (kr->validate) {
528 kerr = validate_tgt(kr);
529 if (kerr != 0) {
530 KRB5_DEBUG(1, kerr);
531 return kerr;
532 }
533
534 /* We drop root privileges which were needed to read the keytab file
535 * for the validation validation of the credentials here to run the
536 * ccache I/O operations with user privileges. */
537 ret = become_user(kr->uid, kr->gid);
538 if (ret != EOK) {
539 DEBUG(1, ("become_user failed.\n"));
540 return ret;
541 }
542 } else {
543 DEBUG(9, ("TGT validation is disabled.\n"));
544 }
545
546 kerr = create_ccache_file(kr, kr->creds);
547 if (kerr != 0) {
548 KRB5_DEBUG(1, kerr);
549 goto done;
550 }
551
552 kerr = 0;
553
554 done:
555 krb5_free_cred_contents(kr->ctx, kr->creds);
556
557 return kerr;
558
559 }
560
561 static errno_t changepw_child(int fd, struct krb5_req *kr)
562 {
563 int ret;
564 krb5_error_code kerr = 0;
565 char *pass_str = NULL;
566 char *newpass_str = NULL;
567 int pam_status = PAM_SYSTEM_ERR;
568 int result_code = -1;
569 krb5_data result_code_string;
570 krb5_data result_string;
571 char *user_error_message = NULL;
572 size_t user_resp_len;
573 uint8_t *user_resp;
574 krb5_prompter_fct prompter = sss_krb5_prompter;
575
576 pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
577 kr->pd->authtok_size);
578 if (pass_str == NULL) {
579 DEBUG(1, ("talloc_strndup failed.\n"));
580 kerr = KRB5KRB_ERR_GENERIC;
581 goto sendresponse;
582 }
583
584 if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
585 /* We do not need a password expiration warning here. */
586 prompter = NULL;
587 }
588
589 kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
590 pass_str, prompter, kr, 0,
591 kr->krb5_ctx->changepw_principle,
592 kr->options);
593 if (kerr != 0) {
594 KRB5_DEBUG(1, kerr);
595 if (kerr == KRB5_KDC_UNREACH) {
596 pam_status = PAM_AUTHINFO_UNAVAIL;
597 }
598 goto sendresponse;
599 }
600
601 memset(pass_str, 0, kr->pd->authtok_size);
602 talloc_zfree(pass_str);
603 memset(kr->pd->authtok, 0, kr->pd->authtok_size);
604
605 if (kr->pd->cmd == SSS_PAM_CHAUTHTOK_PRELIM) {
606 DEBUG(9, ("Initial authentication for change password operation "
607 "successfull.\n"));
608 krb5_free_cred_contents(kr->ctx, kr->creds);
609 pam_status = PAM_SUCCESS;
610 goto sendresponse;
611 }
612
613 newpass_str = talloc_strndup(kr, (const char *) kr->pd->newauthtok,
614 kr->pd->newauthtok_size);
615 if (newpass_str == NULL) {
616 DEBUG(1, ("talloc_strndup failed.\n"));
617 kerr = KRB5KRB_ERR_GENERIC;
618 goto sendresponse;
619 }
620
621 memset(&result_code_string, 0, sizeof(krb5_data));
622 memset(&result_string, 0, sizeof(krb5_data));
623 kerr = krb5_change_password(kr->ctx, kr->creds, newpass_str, &result_code,
624 &result_code_string, &result_string);
625
626 if (kerr == KRB5_KDC_UNREACH) {
627 pam_status = PAM_AUTHTOK_LOCK_BUSY;
628 goto sendresponse;
629 }
630
631 if (kerr != 0 || result_code != 0) {
632 if (kerr != 0) {
633 KRB5_DEBUG(1, kerr);
634 } else {
635 kerr = KRB5KRB_ERR_GENERIC;
636 }
637
638 if (result_code_string.length > 0) {
639 DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
640 result_code_string.length, result_code_string.data));
641 user_error_message = talloc_strndup(kr->pd, result_code_string.data,
642 result_code_string.length);
643 if (user_error_message == NULL) {
644 DEBUG(1, ("talloc_strndup failed.\n"));
645 }
646 }
647
648 if (result_string.length > 0) {
649 DEBUG(1, ("krb5_change_password failed [%d][%.*s].\n", result_code,
650 result_string.length, result_string.data));
651 talloc_free(user_error_message);
652 user_error_message = talloc_strndup(kr->pd, result_string.data,
653 result_string.length);
654 if (user_error_message == NULL) {
655 DEBUG(1, ("talloc_strndup failed.\n"));
656 }
657 }
658
659 if (user_error_message != NULL) {
660 ret = pack_user_info_chpass_error(kr->pd, user_error_message,
661 &user_resp_len, &user_resp);
662 if (ret != EOK) {
663 DEBUG(1, ("pack_user_info_chpass_error failed.\n"));
664 } else {
665 ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, user_resp_len,
666 user_resp);
667 if (ret != EOK) {
668 DEBUG(1, ("pack_response_packet failed.\n"));
669 }
670 }
671 }
672
673 pam_status = PAM_AUTHTOK_ERR;
674 goto sendresponse;
675 }
676
677 krb5_free_cred_contents(kr->ctx, kr->creds);
678
679 kerr = get_and_save_tgt(kr, newpass_str);
680 memset(newpass_str, 0, kr->pd->newauthtok_size);
681 talloc_zfree(newpass_str);
682 memset(kr->pd->newauthtok, 0, kr->pd->newauthtok_size);
683
684 if (kerr != 0) {
685 KRB5_DEBUG(1, kerr);
686 if (kerr == KRB5_KDC_UNREACH) {
687 pam_status = PAM_AUTHINFO_UNAVAIL;
688 }
689 }
690
691 sendresponse:
692 ret = sendresponse(fd, kerr, pam_status, kr);
693 if (ret != EOK) {
694 DEBUG(1, ("sendresponse failed.\n"));
695 }
696
697 return ret;
698 }
699
700 static errno_t tgt_req_child(int fd, struct krb5_req *kr)
701 {
702 int ret;
703 krb5_error_code kerr = 0;
704 char *pass_str = NULL;
705 int pam_status = PAM_SYSTEM_ERR;
706
707 pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
708 kr->pd->authtok_size);
709 if (pass_str == NULL) {
710 DEBUG(1, ("talloc_strndup failed.\n"));
711 kerr = KRB5KRB_ERR_GENERIC;
712 goto sendresponse;
713 }
714
715 kerr = get_and_save_tgt(kr, pass_str);
716
717 /* If the password is expired the KDC will always return
718 KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
719 not. In general the password can still be used to get a changepw ticket.
720 So we validate the password by trying to get a changepw ticket. */
721 if (kerr == KRB5KDC_ERR_KEY_EXP) {
722 kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
723 pass_str, sss_krb5_prompter, kr, 0,
724 kr->krb5_ctx->changepw_principle,
725 kr->options);
726 krb5_free_cred_contents(kr->ctx, kr->creds);
727 if (kerr == 0) {
728 kerr = KRB5KDC_ERR_KEY_EXP;
729 }
730 }
731
732 memset(pass_str, 0, kr->pd->authtok_size);
733 talloc_zfree(pass_str);
734 memset(kr->pd->authtok, 0, kr->pd->authtok_size);
735
736 if (kerr != 0) {
737 KRB5_DEBUG(1, kerr);
738 switch (kerr) {
739 case KRB5_KDC_UNREACH:
740 pam_status = PAM_AUTHINFO_UNAVAIL;
741 break;
742 case KRB5KDC_ERR_KEY_EXP:
743 pam_status = PAM_NEW_AUTHTOK_REQD;
744 break;
745 case KRB5KDC_ERR_PREAUTH_FAILED:
746 pam_status = PAM_CRED_ERR;
747 break;
748 default:
749 pam_status = PAM_SYSTEM_ERR;
750 }
751 }
752
753 sendresponse:
754 ret = sendresponse(fd, kerr, pam_status, kr);
755 if (ret != EOK) {
756 DEBUG(1, ("sendresponse failed.\n"));
757 }
758
759 return ret;
760 }
761
762 static errno_t create_empty_ccache(int fd, struct krb5_req *kr)
763 {
764 int ret;
765 int pam_status = PAM_SUCCESS;
766
767 ret = create_ccache_file(kr, NULL);
768 if (ret != 0) {
769 KRB5_DEBUG(1, ret);
770 pam_status = PAM_SYSTEM_ERR;
771 }
772
773 ret = sendresponse(fd, ret, pam_status, kr);
774 if (ret != EOK) {
775 DEBUG(1, ("sendresponse failed.\n"));
776 }
777
778 return ret;
779 }
780
781 static errno_t unpack_buffer(uint8_t *buf, size_t size, struct pam_data *pd,
782 struct krb5_req *kr, uint32_t *offline)
783 {
784 size_t p = 0;
785 uint32_t len;
786 uint32_t validate;
787
788 SAFEALIGN_COPY_UINT32_CHECK(&pd->cmd, buf + p, size, &p);
789 SAFEALIGN_COPY_UINT32_CHECK(&kr->uid, buf + p, size, &p);
790 SAFEALIGN_COPY_UINT32_CHECK(&kr->gid, buf + p, size, &p);
791 SAFEALIGN_COPY_UINT32_CHECK(&validate, buf + p, size, &p);
792 kr->validate = (validate == 0) ? false : true;
793 SAFEALIGN_COPY_UINT32_CHECK(offline, buf + p, size, &p);
794
795 SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
796 if ((p + len ) > size) return EINVAL;
797 kr->upn = talloc_strndup(pd, (char *)(buf + p), len);
798 if (kr->upn == NULL) return ENOMEM;
799 p += len;
800
801 SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
802 if ((p + len ) > size) return EINVAL;
803 kr->ccname = talloc_strndup(pd, (char *)(buf + p), len);
804 if (kr->ccname == NULL) return ENOMEM;
805 p += len;
806
807 SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
808 if ((p + len ) > size) return EINVAL;
809 kr->keytab = talloc_strndup(pd, (char *)(buf + p), len);
810 if (kr->keytab == NULL) return ENOMEM;
811 p += len;
812
813 SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
814 if ((p + len) > size) return EINVAL;
815 pd->authtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
816 if (pd->authtok == NULL) return ENOMEM;
817 pd->authtok_size = len + 1;
818 p += len;
819
820 if (pd->cmd == SSS_PAM_CHAUTHTOK) {
821 SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
822
823 if ((p + len) > size) return EINVAL;
824 pd->newauthtok = (uint8_t *)talloc_strndup(pd, (char *)(buf + p), len);
825 if (pd->newauthtok == NULL) return ENOMEM;
826 pd->newauthtok_size = len + 1;
827 p += len;
828 } else {
829 pd->newauthtok = NULL;
830 pd->newauthtok_size = 0;
831 }
832
833 return EOK;
834 }
835
836 static int krb5_cleanup(void *ptr)
837 {
838 struct krb5_req *kr = talloc_get_type(ptr, struct krb5_req);
839 if (kr == NULL) return EOK;
840
841 if (kr->options != NULL) {
842 sss_krb5_get_init_creds_opt_free(kr->ctx, kr->options);
843 }
844
845 if (kr->creds != NULL) {
846 krb5_free_cred_contents(kr->ctx, kr->creds);
847 krb5_free_creds(kr->ctx, kr->creds);
848 }
849 if (kr->name != NULL)
850 sss_krb5_free_unparsed_name(kr->ctx, kr->name);
851 if (kr->princ != NULL)
852 krb5_free_principal(kr->ctx, kr->princ);
853 if (kr->ctx != NULL)
854 krb5_free_context(kr->ctx);
855
856 if (kr->krb5_ctx != NULL) {
857 memset(kr->krb5_ctx, 0, sizeof(struct krb5_child_ctx));
858 }
859 memset(kr, 0, sizeof(struct krb5_req));
860
861 return EOK;
862 }
863
864 static int krb5_setup(struct krb5_req *kr, uint32_t offline)
865 {
866 krb5_error_code kerr = 0;
867
868 kr->krb5_ctx = talloc_zero(kr, struct krb5_child_ctx);
869 if (kr->krb5_ctx == NULL) {
870 DEBUG(1, ("talloc failed.\n"));
871 kerr = ENOMEM;
872 goto failed;
873 }
874
875 kr->krb5_ctx->changepw_principle = getenv(SSSD_KRB5_CHANGEPW_PRINCIPLE);
876 if (kr->krb5_ctx->changepw_principle == NULL) {
877 DEBUG(1, ("Cannot read [%s] from environment.\n",
878 SSSD_KRB5_CHANGEPW_PRINCIPLE));
879 if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
880 goto failed;
881 }
882 }
883
884 kr->krb5_ctx->realm = getenv(SSSD_KRB5_REALM);
885 if (kr->krb5_ctx->realm == NULL) {
886 DEBUG(2, ("Cannot read [%s] from environment.\n", SSSD_KRB5_REALM));
887 }
888
889 switch(kr->pd->cmd) {
890 case SSS_PAM_AUTHENTICATE:
891 /* If we are offline, we need to create an empty ccache file */
892 if (offline) {
893 kr->child_req = create_empty_ccache;
894 } else {
895 kr->child_req = tgt_req_child;
896 }
897 break;
898 case SSS_PAM_CHAUTHTOK:
899 case SSS_PAM_CHAUTHTOK_PRELIM:
900 kr->child_req = changepw_child;
901 break;
902 default:
903 DEBUG(1, ("PAM command [%d] not supported.\n", kr->pd->cmd));
904 kerr = EINVAL;
905 goto failed;
906 }
907
908 kerr = krb5_init_context(&kr->ctx);
909 if (kerr != 0) {
910 KRB5_DEBUG(1, kerr);
911 goto failed;
912 }
913
914 kerr = krb5_parse_name(kr->ctx, kr->upn, &kr->princ);
915 if (kerr != 0) {
916 KRB5_DEBUG(1, kerr);
917 goto failed;
918 }
919
920 kerr = krb5_unparse_name(kr->ctx, kr->princ, &kr->name);
921 if (kerr != 0) {
922 KRB5_DEBUG(1, kerr);
923 goto failed;
924 }
925
926 kr->creds = calloc(1, sizeof(krb5_creds));
927 if (kr->creds == NULL) {
928 DEBUG(1, ("talloc_zero failed.\n"));
929 kerr = ENOMEM;
930 goto failed;
931 }
932
933 kerr = sss_krb5_get_init_creds_opt_alloc(kr->ctx, &kr->options);
934 if (kerr != 0) {
935 KRB5_DEBUG(1, kerr);
936 goto failed;
937 }
938
939 /* A prompter is used to catch messages about when a password will
940 * expired. The library shall not use the prompter to ask for a new password
941 * but shall return KRB5KDC_ERR_KEY_EXP. */
942 krb5_get_init_creds_opt_set_change_password_prompt(kr->options, 0);
943 if (kerr != 0) {
944 KRB5_DEBUG(1, kerr);
945 goto failed;
946 }
947
948 /* TODO: set options, e.g.
949 * krb5_get_init_creds_opt_set_tkt_life
950 * krb5_get_init_creds_opt_set_renew_life
951 * krb5_get_init_creds_opt_set_forwardable
952 * krb5_get_init_creds_opt_set_proxiable
953 * krb5_get_init_creds_opt_set_etype_list
954 * krb5_get_init_creds_opt_set_address_list
955 * krb5_get_init_creds_opt_set_preauth_list
956 * krb5_get_init_creds_opt_set_salt
957 * krb5_get_init_creds_opt_set_change_password_prompt
958 * krb5_get_init_creds_opt_set_pa
959 */
960
961 return EOK;
962
963 failed:
964
965 return kerr;
966 }
967
968 int main(int argc, const char *argv[])
969 {
970 uint8_t *buf = NULL;
971 int ret;
972 ssize_t len = 0;
973 struct pam_data *pd = NULL;
974 struct krb5_req *kr = NULL;
975 uint32_t offline;
976 int opt;
977 poptContext pc;
978 int debug_fd = -1;
979
980 struct poptOption long_options[] = {
981 POPT_AUTOHELP
982 {"debug-level", 'd', POPT_ARG_INT, &debug_level, 0,
983 _("Debug level"), NULL},
984 {"debug-timestamps", 0, POPT_ARG_INT, &debug_timestamps, 0,
985 _("Add debug timestamps"), NULL},
986 {"debug-fd", 0, POPT_ARG_INT, &debug_fd, 0,
987 _("An open file descriptor for the debug logs"), NULL},
988 POPT_TABLEEND
989 };
990
991
992 pc = poptGetContext(argv[0], argc, argv, long_options, 0);
993 while((opt = poptGetNextOpt(pc)) != -1) {
994 switch(opt) {
995 default:
996 fprintf(stderr, "\nInvalid option %s: %s\n\n",
997 poptBadOption(pc, 0), poptStrerror(opt));
998 poptPrintUsage(pc, stderr, 0);
999 _exit(-1);
1000 }
1001 }
1002
1003 poptFreeContext(pc);
1004
1005 DEBUG(7, ("krb5_child started.\n"));
1006
1007 pd = talloc(NULL, struct pam_data);
1008 if (pd == NULL) {
1009 DEBUG(1, ("malloc failed.\n"));
1010 _exit(-1);
1011 }
1012
1013 debug_prg_name = talloc_asprintf(pd, "[sssd[krb5_child[%d]]]", getpid());
1014
1015 if (debug_fd != -1) {
1016 ret = set_debug_file_from_fd(debug_fd);
1017 if (ret != EOK) {
1018 DEBUG(1, ("set_debug_file_from_fd failed.\n"));
1019 }
1020 }
1021
1022 buf = talloc_size(pd, sizeof(uint8_t)*IN_BUF_SIZE);
1023 if (buf == NULL) {
1024 DEBUG(1, ("malloc failed.\n"));
1025 _exit(-1);
1026 }
1027
1028 while ((ret = read(STDIN_FILENO, buf + len, IN_BUF_SIZE - len)) != 0) {
1029 if (ret == -1) {
1030 if (errno == EINTR || errno == EAGAIN) {
1031 continue;
1032 }
1033 DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
1034 goto fail;
1035 } else if (ret > 0) {
1036 len += ret;
1037 if (len > IN_BUF_SIZE) {
1038 DEBUG(1, ("read too much, this should never happen.\n"));
1039 goto fail;
1040 }
1041 continue;
1042 } else {
1043 DEBUG(1, ("unexpected return code of read [%d].\n", ret));
1044 goto fail;
1045 }
1046 }
1047 close(STDIN_FILENO);
1048
1049 kr = talloc_zero(pd, struct krb5_req);
1050 if (kr == NULL) {
1051 DEBUG(1, ("talloc failed.\n"));
1052 goto fail;
1053 }
1054 talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
1055 kr->pd = pd;
1056
1057 ret = unpack_buffer(buf, len, pd, kr, &offline);
1058 if (ret != EOK) {
1059 DEBUG(1, ("unpack_buffer failed.\n"));
1060 goto fail;
1061 }
1062
1063 ret = krb5_setup(kr, offline);
1064 if (ret != EOK) {
1065 DEBUG(1, ("krb5_setup failed.\n"));
1066 goto fail;
1067 }
1068
1069 ret = kr->child_req(STDOUT_FILENO, kr);
1070 if (ret != EOK) {
1071 DEBUG(1, ("Child request failed.\n"));
1072 goto fail;
1073 }
1074
1075 close(STDOUT_FILENO);
1076 talloc_free(pd);
1077
1078 return 0;
1079
1080 fail:
1081 close(STDOUT_FILENO);
1082 talloc_free(pd);
1083 exit(-1);
1084 }