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