source: trunk/samba-3.0.25pre1/source/libads/kerberos.c@ 5

Last change on this file since 5 was 1, checked in by Paul Smedley, 18 years ago

Initial code import

File size: 19.0 KB
Line 
1/*
2 Unix SMB/CIFS implementation.
3 kerberos utility library
4 Copyright (C) Andrew Tridgell 2001
5 Copyright (C) Remus Koos 2001
6 Copyright (C) Nalin Dahyabhai <nalin@redhat.com> 2004.
7 Copyright (C) Jeremy Allison 2004.
8 Copyright (C) Gerald Carter 2006.
9
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License, or
13 (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; if not, write to the Free Software
22 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23*/
24
25#include "includes.h"
26
27#ifdef HAVE_KRB5
28
29#define LIBADS_CCACHE_NAME "MEMORY:libads"
30
31/*
32 we use a prompter to avoid a crash bug in the kerberos libs when
33 dealing with empty passwords
34 this prompter is just a string copy ...
35*/
36static krb5_error_code
37kerb_prompter(krb5_context ctx, void *data,
38 const char *name,
39 const char *banner,
40 int num_prompts,
41 krb5_prompt prompts[])
42{
43 if (num_prompts == 0) return 0;
44
45 memset(prompts[0].reply->data, '\0', prompts[0].reply->length);
46 if (prompts[0].reply->length > 0) {
47 if (data) {
48 strncpy(prompts[0].reply->data, (const char *)data,
49 prompts[0].reply->length-1);
50 prompts[0].reply->length = strlen(prompts[0].reply->data);
51 } else {
52 prompts[0].reply->length = 0;
53 }
54 }
55 return 0;
56}
57
58/*
59 simulate a kinit, putting the tgt in the given cache location. If cache_name == NULL
60 place in default cache location.
61 remus@snapserver.com
62*/
63int kerberos_kinit_password_ext(const char *principal,
64 const char *password,
65 int time_offset,
66 time_t *expire_time,
67 time_t *renew_till_time,
68 const char *cache_name,
69 BOOL request_pac,
70 BOOL add_netbios_addr,
71 time_t renewable_time)
72{
73 krb5_context ctx = NULL;
74 krb5_error_code code = 0;
75 krb5_ccache cc = NULL;
76 krb5_principal me;
77 krb5_creds my_creds;
78 krb5_get_init_creds_opt *opt = NULL;
79 smb_krb5_addresses *addr = NULL;
80
81 initialize_krb5_error_table();
82 if ((code = krb5_init_context(&ctx)))
83 return code;
84
85 if (time_offset != 0) {
86 krb5_set_real_time(ctx, time(NULL) + time_offset, 0);
87 }
88
89 DEBUG(10,("kerberos_kinit_password: using [%s] as ccache and config [%s]\n",
90 cache_name ? cache_name: krb5_cc_default_name(ctx),
91 getenv("KRB5_CONFIG")));
92
93 if ((code = krb5_cc_resolve(ctx, cache_name ? cache_name : krb5_cc_default_name(ctx), &cc))) {
94 krb5_free_context(ctx);
95 return code;
96 }
97
98 if ((code = smb_krb5_parse_name(ctx, principal, &me))) {
99 krb5_cc_close(ctx, cc);
100 krb5_free_context(ctx);
101 return code;
102 }
103
104 code = krb5_get_init_creds_opt_alloc(ctx, &opt);
105 if (code) {
106 krb5_cc_close(ctx, cc);
107 krb5_free_context(ctx);
108 return code;
109 }
110
111 krb5_get_init_creds_opt_set_renew_life(opt, renewable_time);
112 krb5_get_init_creds_opt_set_forwardable(opt, True);
113#if 0
114 /* insane testing */
115 krb5_get_init_creds_opt_set_tkt_life(opt, 60);
116#endif
117
118#ifdef HAVE_KRB5_GET_INIT_CREDS_OPT_SET_PAC_REQUEST
119 if (request_pac) {
120 code = krb5_get_init_creds_opt_set_pac_request(ctx, opt, (krb5_boolean)request_pac);
121 if (code) {
122 krb5_cc_close(ctx, cc);
123 krb5_free_principal(ctx, me);
124 krb5_free_context(ctx);
125 return code;
126 }
127 }
128#endif
129 if (add_netbios_addr) {
130 code = smb_krb5_gen_netbios_krb5_address(&addr);
131 if (code) {
132 krb5_cc_close(ctx, cc);
133 krb5_free_principal(ctx, me);
134 krb5_free_context(ctx);
135 return code;
136 }
137 krb5_get_init_creds_opt_set_address_list(opt, addr->addrs);
138 }
139
140 if ((code = krb5_get_init_creds_password(ctx, &my_creds, me, CONST_DISCARD(char *,password),
141 kerb_prompter, NULL, 0, NULL, opt)))
142 {
143 krb5_get_init_creds_opt_free(opt);
144 smb_krb5_free_addresses(ctx, addr);
145 krb5_cc_close(ctx, cc);
146 krb5_free_principal(ctx, me);
147 krb5_free_context(ctx);
148 return code;
149 }
150
151 krb5_get_init_creds_opt_free(opt);
152
153 if ((code = krb5_cc_initialize(ctx, cc, me))) {
154 smb_krb5_free_addresses(ctx, addr);
155 krb5_free_cred_contents(ctx, &my_creds);
156 krb5_cc_close(ctx, cc);
157 krb5_free_principal(ctx, me);
158 krb5_free_context(ctx);
159 return code;
160 }
161
162 if ((code = krb5_cc_store_cred(ctx, cc, &my_creds))) {
163 krb5_cc_close(ctx, cc);
164 smb_krb5_free_addresses(ctx, addr);
165 krb5_free_cred_contents(ctx, &my_creds);
166 krb5_free_principal(ctx, me);
167 krb5_free_context(ctx);
168 return code;
169 }
170
171 if (expire_time) {
172 *expire_time = (time_t) my_creds.times.endtime;
173 }
174
175 if (renew_till_time) {
176 *renew_till_time = (time_t) my_creds.times.renew_till;
177 }
178
179 krb5_cc_close(ctx, cc);
180 smb_krb5_free_addresses(ctx, addr);
181 krb5_free_cred_contents(ctx, &my_creds);
182 krb5_free_principal(ctx, me);
183 krb5_free_context(ctx);
184
185 return 0;
186}
187
188
189
190/* run kinit to setup our ccache */
191int ads_kinit_password(ADS_STRUCT *ads)
192{
193 char *s;
194 int ret;
195 const char *account_name;
196 fstring acct_name;
197
198 if ( IS_DC ) {
199 /* this will end up getting a ticket for DOMAIN@RUSTED.REA.LM */
200 account_name = lp_workgroup();
201 } else {
202 /* always use the sAMAccountName for security = domain */
203 /* global_myname()$@REA.LM */
204 if ( lp_security() == SEC_DOMAIN ) {
205 fstr_sprintf( acct_name, "%s$", global_myname() );
206 account_name = acct_name;
207 }
208 else
209 /* This looks like host/global_myname()@REA.LM */
210 account_name = ads->auth.user_name;
211 }
212
213 if (asprintf(&s, "%s@%s", account_name, ads->auth.realm) == -1) {
214 return KRB5_CC_NOMEM;
215 }
216
217 if (!ads->auth.password) {
218 SAFE_FREE(s);
219 return KRB5_LIBOS_CANTREADPWD;
220 }
221
222 ret = kerberos_kinit_password_ext(s, ads->auth.password, ads->auth.time_offset,
223 &ads->auth.tgt_expire, NULL, NULL, False, False, ads->auth.renewable);
224
225 if (ret) {
226 DEBUG(0,("kerberos_kinit_password %s failed: %s\n",
227 s, error_message(ret)));
228 }
229 SAFE_FREE(s);
230 return ret;
231}
232
233int ads_kdestroy(const char *cc_name)
234{
235 krb5_error_code code;
236 krb5_context ctx = NULL;
237 krb5_ccache cc = NULL;
238
239 initialize_krb5_error_table();
240 if ((code = krb5_init_context (&ctx))) {
241 DEBUG(3, ("ads_kdestroy: kdb5_init_context failed: %s\n",
242 error_message(code)));
243 return code;
244 }
245
246 if (!cc_name) {
247 if ((code = krb5_cc_default(ctx, &cc))) {
248 krb5_free_context(ctx);
249 return code;
250 }
251 } else {
252 if ((code = krb5_cc_resolve(ctx, cc_name, &cc))) {
253 DEBUG(3, ("ads_kdestroy: krb5_cc_resolve failed: %s\n",
254 error_message(code)));
255 krb5_free_context(ctx);
256 return code;
257 }
258 }
259
260 if ((code = krb5_cc_destroy (ctx, cc))) {
261 DEBUG(3, ("ads_kdestroy: krb5_cc_destroy failed: %s\n",
262 error_message(code)));
263 }
264
265 krb5_free_context (ctx);
266 return code;
267}
268
269/************************************************************************
270 Routine to fetch the salting principal for a service. Active
271 Directory may use a non-obvious principal name to generate the salt
272 when it determines the key to use for encrypting tickets for a service,
273 and hopefully we detected that when we joined the domain.
274 ************************************************************************/
275
276static char *kerberos_secrets_fetch_salting_principal(const char *service, int enctype)
277{
278 char *key = NULL;
279 char *ret = NULL;
280
281 asprintf(&key, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL, service, enctype);
282 if (!key) {
283 return NULL;
284 }
285 ret = (char *)secrets_fetch(key, NULL);
286 SAFE_FREE(key);
287 return ret;
288}
289
290/************************************************************************
291 Return the standard DES salt key
292************************************************************************/
293
294char* kerberos_standard_des_salt( void )
295{
296 fstring salt;
297
298 fstr_sprintf( salt, "host/%s.%s@", global_myname(), lp_realm() );
299 strlower_m( salt );
300 fstrcat( salt, lp_realm() );
301
302 return SMB_STRDUP( salt );
303}
304
305/************************************************************************
306************************************************************************/
307
308static char* des_salt_key( void )
309{
310 char *key;
311
312 asprintf(&key, "%s/DES/%s", SECRETS_SALTING_PRINCIPAL, lp_realm());
313
314 return key;
315}
316
317/************************************************************************
318************************************************************************/
319
320BOOL kerberos_secrets_store_des_salt( const char* salt )
321{
322 char* key;
323 BOOL ret;
324
325 if ( (key = des_salt_key()) == NULL ) {
326 DEBUG(0,("kerberos_secrets_store_des_salt: failed to generate key!\n"));
327 return False;
328 }
329
330 if ( !salt ) {
331 DEBUG(8,("kerberos_secrets_store_des_salt: deleting salt\n"));
332 secrets_delete( key );
333 return True;
334 }
335
336 DEBUG(3,("kerberos_secrets_store_des_salt: Storing salt \"%s\"\n", salt));
337
338 ret = secrets_store( key, salt, strlen(salt)+1 );
339
340 SAFE_FREE( key );
341
342 return ret;
343}
344
345/************************************************************************
346************************************************************************/
347
348char* kerberos_secrets_fetch_des_salt( void )
349{
350 char *salt, *key;
351
352 if ( (key = des_salt_key()) == NULL ) {
353 DEBUG(0,("kerberos_secrets_fetch_des_salt: failed to generate key!\n"));
354 return False;
355 }
356
357 salt = (char*)secrets_fetch( key, NULL );
358
359 SAFE_FREE( key );
360
361 return salt;
362}
363
364
365/************************************************************************
366 Routine to get the salting principal for this service. This is
367 maintained for backwards compatibilty with releases prior to 3.0.24.
368 Since we store the salting principal string only at join, we may have
369 to look for the older tdb keys. Caller must free if return is not null.
370 ************************************************************************/
371
372krb5_principal kerberos_fetch_salt_princ_for_host_princ(krb5_context context,
373 krb5_principal host_princ,
374 int enctype)
375{
376 char *unparsed_name = NULL, *salt_princ_s = NULL;
377 krb5_principal ret_princ = NULL;
378
379 /* lookup new key first */
380
381 if ( (salt_princ_s = kerberos_secrets_fetch_des_salt()) == NULL ) {
382
383 /* look under the old key. If this fails, just use the standard key */
384
385 if (smb_krb5_unparse_name(context, host_princ, &unparsed_name) != 0) {
386 return (krb5_principal)NULL;
387 }
388 if ((salt_princ_s = kerberos_secrets_fetch_salting_principal(unparsed_name, enctype)) == NULL) {
389 /* fall back to host/machine.realm@REALM */
390 salt_princ_s = kerberos_standard_des_salt();
391 }
392 }
393
394 if (smb_krb5_parse_name(context, salt_princ_s, &ret_princ) != 0) {
395 ret_princ = NULL;
396 }
397
398 SAFE_FREE(unparsed_name);
399 SAFE_FREE(salt_princ_s);
400
401 return ret_princ;
402}
403
404/************************************************************************
405 Routine to set the salting principal for this service. Active
406 Directory may use a non-obvious principal name to generate the salt
407 when it determines the key to use for encrypting tickets for a service,
408 and hopefully we detected that when we joined the domain.
409 Setting principal to NULL deletes this entry.
410 ************************************************************************/
411
412BOOL kerberos_secrets_store_salting_principal(const char *service,
413 int enctype,
414 const char *principal)
415{
416 char *key = NULL;
417 BOOL ret = False;
418 krb5_context context = NULL;
419 krb5_principal princ = NULL;
420 char *princ_s = NULL;
421 char *unparsed_name = NULL;
422
423 krb5_init_context(&context);
424 if (!context) {
425 return False;
426 }
427 if (strchr_m(service, '@')) {
428 asprintf(&princ_s, "%s", service);
429 } else {
430 asprintf(&princ_s, "%s@%s", service, lp_realm());
431 }
432
433 if (smb_krb5_parse_name(context, princ_s, &princ) != 0) {
434 goto out;
435
436 }
437 if (smb_krb5_unparse_name(context, princ, &unparsed_name) != 0) {
438 goto out;
439 }
440
441 asprintf(&key, "%s/%s/enctype=%d", SECRETS_SALTING_PRINCIPAL, unparsed_name, enctype);
442 if (!key) {
443 goto out;
444 }
445
446 if ((principal != NULL) && (strlen(principal) > 0)) {
447 ret = secrets_store(key, principal, strlen(principal) + 1);
448 } else {
449 ret = secrets_delete(key);
450 }
451
452 out:
453
454 SAFE_FREE(key);
455 SAFE_FREE(princ_s);
456 SAFE_FREE(unparsed_name);
457
458 if (context) {
459 krb5_free_context(context);
460 }
461
462 return ret;
463}
464
465
466/************************************************************************
467************************************************************************/
468
469int kerberos_kinit_password(const char *principal,
470 const char *password,
471 int time_offset,
472 const char *cache_name)
473{
474 return kerberos_kinit_password_ext(principal,
475 password,
476 time_offset,
477 0,
478 0,
479 cache_name,
480 False,
481 False,
482 0);
483}
484
485/************************************************************************
486 Create a string list of available kdc's, possibly searching by sitename.
487 Does DNS queries.
488************************************************************************/
489
490static char *get_kdc_ip_string(char *mem_ctx, const char *realm, const char *sitename, struct in_addr primary_ip)
491{
492 struct ip_service *ip_srv_site;
493 struct ip_service *ip_srv_nonsite;
494 int count_site, count_nonsite, i;
495 char *kdc_str = talloc_asprintf(mem_ctx, "\tkdc = %s\n",
496 inet_ntoa(primary_ip));
497
498 if (kdc_str == NULL) {
499 return NULL;
500 }
501
502 /* Get the KDC's only in this site. */
503
504 if (sitename) {
505
506 get_kdc_list(realm, sitename, &ip_srv_site, &count_site);
507
508 for (i = 0; i < count_site; i++) {
509 if (ip_equal(ip_srv_site[i].ip, primary_ip)) {
510 continue;
511 }
512 /* Append to the string - inefficient but not done often. */
513 kdc_str = talloc_asprintf(mem_ctx, "%s\tkdc = %s\n",
514 kdc_str, inet_ntoa(ip_srv_site[i].ip));
515 if (!kdc_str) {
516 SAFE_FREE(ip_srv_site);
517 return NULL;
518 }
519 }
520 }
521
522 /* Get all KDC's. */
523
524 get_kdc_list(realm, NULL, &ip_srv_nonsite, &count_nonsite);
525
526 for (i = 0; i < count_nonsite; i++) {
527 int j;
528
529 if (ip_equal(ip_srv_nonsite[i].ip, primary_ip)) {
530 continue;
531 }
532
533 /* Ensure this isn't an IP already seen (YUK! this is n*n....) */
534 for (j = 0; j < count_site; j++) {
535 if (ip_equal(ip_srv_nonsite[i].ip, ip_srv_site[j].ip)) {
536 break;
537 }
538 /* As the lists are sorted we can break early if nonsite > site. */
539 if (ip_service_compare(&ip_srv_nonsite[i], &ip_srv_site[j]) > 0) {
540 break;
541 }
542 }
543 if (j != i) {
544 continue;
545 }
546
547 /* Append to the string - inefficient but not done often. */
548 kdc_str = talloc_asprintf(mem_ctx, "%s\tkdc = %s\n",
549 kdc_str, inet_ntoa(ip_srv_nonsite[i].ip));
550 if (!kdc_str) {
551 SAFE_FREE(ip_srv_site);
552 SAFE_FREE(ip_srv_nonsite);
553 return NULL;
554 }
555 }
556
557
558 SAFE_FREE(ip_srv_site);
559 SAFE_FREE(ip_srv_nonsite);
560
561 DEBUG(10,("get_kdc_ip_string: Returning %s\n",
562 kdc_str ));
563
564 return kdc_str;
565}
566
567/************************************************************************
568 Create a specific krb5.conf file in the private directory pointing
569 at a specific kdc for a realm. Keyed off domain name. Sets
570 KRB5_CONFIG environment variable to point to this file. Must be
571 run as root or will fail (which is a good thing :-).
572************************************************************************/
573
574BOOL create_local_private_krb5_conf_for_domain(const char *realm, const char *domain,
575 const char *sitename, struct in_addr ip)
576{
577 char *dname = talloc_asprintf(NULL, "%s/smb_krb5", lp_lockdir());
578 char *tmpname = NULL;
579 char *fname = NULL;
580 char *file_contents = NULL;
581 char *kdc_ip_string = NULL;
582 size_t flen = 0;
583 ssize_t ret;
584 int fd;
585 char *realm_upper = NULL;
586
587 if (!dname) {
588 return False;
589 }
590 if ((mkdir(dname, 0755)==-1) && (errno != EEXIST)) {
591 DEBUG(0,("create_local_private_krb5_conf_for_domain: "
592 "failed to create directory %s. Error was %s\n",
593 dname, strerror(errno) ));
594 TALLOC_FREE(dname);
595 return False;
596 }
597
598 tmpname = talloc_asprintf(dname, "%s/smb_tmp_krb5.XXXXXX", lp_lockdir());
599 if (!tmpname) {
600 TALLOC_FREE(dname);
601 return False;
602 }
603
604 fname = talloc_asprintf(dname, "%s/krb5.conf.%s", dname, domain);
605 if (!fname) {
606 TALLOC_FREE(dname);
607 return False;
608 }
609
610 DEBUG(10,("create_local_private_krb5_conf_for_domain: fname = %s, realm = %s, domain = %s\n",
611 fname, realm, domain ));
612
613 realm_upper = talloc_strdup(fname, realm);
614 strupper_m(realm_upper);
615
616 kdc_ip_string = get_kdc_ip_string(dname, realm, sitename, ip);
617 if (!kdc_ip_string) {
618 TALLOC_FREE(dname);
619 return False;
620 }
621
622 file_contents = talloc_asprintf(fname, "[libdefaults]\n\tdefault_realm = %s\n\n"
623 "[realms]\n\t%s = {\n"
624 "\t%s\t}\n",
625 realm_upper, realm_upper, kdc_ip_string);
626
627 if (!file_contents) {
628 TALLOC_FREE(dname);
629 return False;
630 }
631
632 flen = strlen(file_contents);
633
634 fd = smb_mkstemp(tmpname);
635 if (fd == -1) {
636 DEBUG(0,("create_local_private_krb5_conf_for_domain: smb_mkstemp failed,"
637 " for file %s. Errno %s\n",
638 tmpname, strerror(errno) ));
639 }
640
641 if (fchmod(fd, 0644)==-1) {
642 DEBUG(0,("create_local_private_krb5_conf_for_domain: fchmod failed for %s."
643 " Errno %s\n",
644 tmpname, strerror(errno) ));
645 unlink(tmpname);
646 close(fd);
647 TALLOC_FREE(dname);
648 return False;
649 }
650
651 ret = write(fd, file_contents, flen);
652 if (flen != ret) {
653 DEBUG(0,("create_local_private_krb5_conf_for_domain: write failed,"
654 " returned %d (should be %u). Errno %s\n",
655 (int)ret, (unsigned int)flen, strerror(errno) ));
656 unlink(tmpname);
657 close(fd);
658 TALLOC_FREE(dname);
659 return False;
660 }
661 if (close(fd)==-1) {
662 DEBUG(0,("create_local_private_krb5_conf_for_domain: close failed."
663 " Errno %s\n", strerror(errno) ));
664 unlink(tmpname);
665 TALLOC_FREE(dname);
666 return False;
667 }
668
669 if (rename(tmpname, fname) == -1) {
670 DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
671 "of %s to %s failed. Errno %s\n",
672 tmpname, fname, strerror(errno) ));
673 unlink(tmpname);
674 TALLOC_FREE(dname);
675 return False;
676 }
677
678 DEBUG(5,("create_local_private_krb5_conf_for_domain: wrote "
679 "file %s with realm %s KDC = %s\n",
680 fname, realm_upper, inet_ntoa(ip) ));
681
682 /* Set the environment variable to this file. */
683 setenv("KRB5_CONFIG", fname, 1);
684
685#if defined(OVERWRITE_SYSTEM_KRB5_CONF)
686
687#define SYSTEM_KRB5_CONF_PATH "/etc/krb5.conf"
688 /* Insanity, sheer insanity..... */
689
690 if (strequal(realm, lp_realm())) {
691 pstring linkpath;
692 int lret;
693
694 lret = readlink(SYSTEM_KRB5_CONF_PATH, linkpath, sizeof(linkpath)-1);
695 linkpath[sizeof(pstring)-1] = '\0';
696
697 if (lret == 0 || strcmp(linkpath, fname) == 0) {
698 /* Symlink already exists. */
699 TALLOC_FREE(dname);
700 return True;
701 }
702
703 /* Try and replace with a symlink. */
704 if (symlink(fname, SYSTEM_KRB5_CONF_PATH) == -1) {
705 if (errno != EEXIST) {
706 DEBUG(0,("create_local_private_krb5_conf_for_domain: symlink "
707 "of %s to %s failed. Errno %s\n",
708 fname, SYSTEM_KRB5_CONF_PATH, strerror(errno) ));
709 TALLOC_FREE(dname);
710 return True; /* Not a fatal error. */
711 }
712
713 pstrcpy(linkpath, SYSTEM_KRB5_CONF_PATH);
714 pstrcat(linkpath, ".saved");
715
716 /* Yes, this is a race conditon... too bad. */
717 if (rename(SYSTEM_KRB5_CONF_PATH, linkpath) == -1) {
718 DEBUG(0,("create_local_private_krb5_conf_for_domain: rename "
719 "of %s to %s failed. Errno %s\n",
720 SYSTEM_KRB5_CONF_PATH, linkpath,
721 strerror(errno) ));
722 TALLOC_FREE(dname);
723 return True; /* Not a fatal error. */
724 }
725
726 if (symlink(fname, "/etc/krb5.conf") == -1) {
727 DEBUG(0,("create_local_private_krb5_conf_for_domain: "
728 "forced symlink of %s to /etc/krb5.conf failed. Errno %s\n",
729 fname, strerror(errno) ));
730 TALLOC_FREE(dname);
731 return True; /* Not a fatal error. */
732 }
733 }
734 }
735#endif
736
737 TALLOC_FREE(dname);
738
739 return True;
740}
741#endif
Note: See TracBrowser for help on using the repository browser.