b23baae8ae0773a0e89566521d6c02850bcff1d6
[samba.git] / source3 / libads / kerberos_keytab.c
1 /*
2    Unix SMB/CIFS implementation.
3    kerberos keytab utility library
4    Copyright (C) Andrew Tridgell 2001
5    Copyright (C) Remus Koos 2001
6    Copyright (C) Luke Howard 2003
7    Copyright (C) Jim McDonough (jmcd@us.ibm.com) 2003
8    Copyright (C) Guenther Deschner 2003
9    Copyright (C) Rakesh Patel 2004
10    Copyright (C) Dan Perry 2004
11    Copyright (C) Jeremy Allison 2004
12    Copyright (C) Gerald Carter 2006
13
14    This program is free software; you can redistribute it and/or modify
15    it under the terms of the GNU General Public License as published by
16    the Free Software Foundation; either version 3 of the License, or
17    (at your option) any later version.
18
19    This program is distributed in the hope that it will be useful,
20    but WITHOUT ANY WARRANTY; without even the implied warranty of
21    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22    GNU General Public License for more details.
23
24    You should have received a copy of the GNU General Public License
25    along with this program.  If not, see <http://www.gnu.org/licenses/>.
26 */
27
28 #include "includes.h"
29 #include "smb_krb5.h"
30 #include "ads.h"
31 #include "secrets.h"
32
33 #ifdef HAVE_KRB5
34
35 #ifdef HAVE_ADS
36
37 /* This MAX_NAME_LEN is a constant defined in krb5.h */
38 #ifndef MAX_KEYTAB_NAME_LEN
39 #define MAX_KEYTAB_NAME_LEN 1100
40 #endif
41
42 static krb5_error_code ads_keytab_open(krb5_context context,
43                                        krb5_keytab *keytab)
44 {
45         char keytab_str[MAX_KEYTAB_NAME_LEN] = {0};
46         const char *keytab_name = NULL;
47         krb5_error_code ret = 0;
48
49         switch (lp_kerberos_method()) {
50         case KERBEROS_VERIFY_SYSTEM_KEYTAB:
51         case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
52                 ret = krb5_kt_default_name(context,
53                                            keytab_str,
54                                            sizeof(keytab_str) - 2);
55                 if (ret != 0) {
56                         DBG_WARNING("Failed to get default keytab name");
57                         goto out;
58                 }
59                 keytab_name = keytab_str;
60                 break;
61         case KERBEROS_VERIFY_DEDICATED_KEYTAB:
62                 keytab_name = lp_dedicated_keytab_file();
63                 break;
64         default:
65                 DBG_ERR("Invalid kerberos method set (%d)\n",
66                         lp_kerberos_method());
67                 ret = KRB5_KT_BADNAME;
68                 goto out;
69         }
70
71         if (keytab_name == NULL || keytab_name[0] == '\0') {
72                 DBG_ERR("Invalid keytab name\n");
73                 ret = KRB5_KT_BADNAME;
74                 goto out;
75         }
76
77         ret = smb_krb5_kt_open(context, keytab_name, true, keytab);
78         if (ret != 0) {
79                 DBG_WARNING("smb_krb5_kt_open failed (%s)\n",
80                             error_message(ret));
81                 goto out;
82         }
83
84 out:
85         return ret;
86 }
87
88 static bool fill_default_spns(TALLOC_CTX *ctx, const char *machine_name,
89                                           const char *my_fqdn, const char *spn,
90                                           const char ***spns)
91 {
92         char *psp1, *psp2;
93
94         if (*spns == NULL) {
95                 *spns = talloc_zero_array(ctx, const char*, 3);
96                 if (spns == NULL) {
97                         return false;
98                 }
99         }
100
101         psp1 = talloc_asprintf(ctx,
102                                "%s/%s",
103                                spn,
104                                machine_name);
105         if (psp1 == NULL) {
106                 return false;
107         }
108
109         if (!strlower_m(&psp1[strlen(spn) + 1])) {
110                 return false;
111         }
112         (*spns)[0] = psp1;
113
114         psp2 = talloc_asprintf(ctx,
115                                "%s/%s",
116                                spn,
117                                my_fqdn);
118         if (psp2 == NULL) {
119                 return false;
120         }
121
122         if (!strlower_m(&psp2[strlen(spn) + 1])) {
123                 return false;
124         }
125
126         (*spns)[1] = psp2;
127
128         return true;
129 }
130
131 static bool ads_set_machine_account_spns(TALLOC_CTX *ctx,
132                                          ADS_STRUCT *ads,
133                                          const char *service_or_spn,
134                                          const char *my_fqdn)
135 {
136         const char **spn_names = NULL;
137         ADS_STATUS aderr;
138         struct spn_struct* spn_struct = NULL;
139         char *tmp = NULL;
140
141         /* SPN should have '/' */
142         tmp = strchr_m(service_or_spn, '/');
143         if (tmp != NULL) {
144                 spn_struct = parse_spn(ctx, service_or_spn);
145                 if (spn_struct == NULL) {
146                         return false;
147                 }
148         }
149
150         DBG_INFO("Attempting to add/update '%s'\n", service_or_spn);
151
152         if (spn_struct != NULL) {
153                 spn_names = talloc_zero_array(ctx, const char*, 2);
154                 spn_names[0] = service_or_spn;
155         } else {
156                 bool ok;
157
158                 ok = fill_default_spns(ctx,
159                                        lp_netbios_name(),
160                                        my_fqdn,
161                                        service_or_spn,
162                                        &spn_names);
163                 if (!ok) {
164                         return false;
165                 }
166         }
167         aderr = ads_add_service_principal_names(ads,
168                                                 lp_netbios_name(),
169                                                 spn_names);
170         if (!ADS_ERR_OK(aderr)) {
171                 DBG_WARNING("Failed to add service principal name.\n");
172                 return false;
173         }
174
175         return true;
176 }
177
178 /*
179  * Create kerberos principal(s) from SPN or service name.
180  */
181 static bool service_or_spn_to_kerberos_princ(TALLOC_CTX *ctx,
182                                              const char *service_or_spn,
183                                              const char *my_fqdn,
184                                              char **p_princ_s,
185                                              char **p_short_princ_s)
186 {
187         char *princ_s = NULL;
188         char *short_princ_s = NULL;
189         const char *service = service_or_spn;
190         const char *host = my_fqdn;
191         struct spn_struct* spn_struct = NULL;
192         char *tmp = NULL;
193         bool ok = true;
194
195         /* SPN should have '/' */
196         tmp = strchr_m(service_or_spn, '/');
197         if (tmp != NULL) {
198                 spn_struct = parse_spn(ctx, service_or_spn);
199                 if (spn_struct == NULL) {
200                         ok = false;
201                         goto out;
202                 }
203         }
204         if (spn_struct != NULL) {
205                 service = spn_struct->serviceclass;
206                 host = spn_struct->host;
207         }
208         princ_s = talloc_asprintf(ctx, "%s/%s@%s",
209                                   service,
210                                   host, lp_realm());
211         if (princ_s == NULL) {
212                 ok = false;
213                 goto out;
214         }
215
216         if (spn_struct == NULL) {
217                 short_princ_s = talloc_asprintf(ctx, "%s/%s@%s",
218                                         service, lp_netbios_name(),
219                                         lp_realm());
220                 if (short_princ_s == NULL) {
221                         ok = false;
222                         goto out;
223                 }
224         }
225         *p_princ_s = princ_s;
226         *p_short_princ_s = short_princ_s;
227 out:
228         return ok;
229 }
230
231 /**********************************************************************
232  Adds a single service principal, i.e. 'host' to the system keytab
233 ***********************************************************************/
234
235 int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc)
236 {
237         krb5_error_code ret = 0;
238         krb5_context context = NULL;
239         krb5_keytab keytab = NULL;
240         krb5_data password;
241         krb5_kvno kvno;
242         krb5_enctype enctypes[6] = {
243                 ENCTYPE_DES_CBC_CRC,
244                 ENCTYPE_DES_CBC_MD5,
245 #ifdef HAVE_ENCTYPE_AES128_CTS_HMAC_SHA1_96
246                 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
247 #endif
248 #ifdef HAVE_ENCTYPE_AES256_CTS_HMAC_SHA1_96
249                 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
250 #endif
251                 ENCTYPE_ARCFOUR_HMAC,
252                 0
253         };
254         char *princ_s = NULL;
255         char *short_princ_s = NULL;
256         char *salt_princ_s = NULL;
257         char *password_s = NULL;
258         char *my_fqdn;
259         TALLOC_CTX *tmpctx = NULL;
260         int i;
261
262         initialize_krb5_error_table();
263         ret = krb5_init_context(&context);
264         if (ret) {
265                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
266                           error_message(ret)));
267                 return -1;
268         }
269
270         ret = ads_keytab_open(context, &keytab);
271         if (ret != 0) {
272                 goto out;
273         }
274
275         /* retrieve the password */
276         if (!secrets_init()) {
277                 DEBUG(1, (__location__ ": secrets_init failed\n"));
278                 ret = -1;
279                 goto out;
280         }
281         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
282         if (!password_s) {
283                 DEBUG(1, (__location__ ": failed to fetch machine password\n"));
284                 ret = -1;
285                 goto out;
286         }
287         ZERO_STRUCT(password);
288         password.data = password_s;
289         password.length = strlen(password_s);
290
291         /* we need the dNSHostName value here */
292         tmpctx = talloc_init(__location__);
293         if (!tmpctx) {
294                 DEBUG(0, (__location__ ": talloc_init() failed!\n"));
295                 ret = -1;
296                 goto out;
297         }
298
299         my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
300         if (!my_fqdn) {
301                 DEBUG(0, (__location__ ": unable to determine machine "
302                           "account's dns name in AD!\n"));
303                 ret = -1;
304                 goto out;
305         }
306
307         /* make sure we have a single instance of a the computer account */
308         if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
309                 DEBUG(0, (__location__ ": unable to determine machine "
310                           "account's short name in AD!\n"));
311                 ret = -1;
312                 goto out;
313         }
314
315         /* Construct our principal */
316         if (strchr_m(srvPrinc, '@')) {
317                 /* It's a fully-named principal. */
318                 princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
319                 if (!princ_s) {
320                         ret = -1;
321                         goto out;
322                 }
323         } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
324                 /* It's the machine account, as used by smbclient clients. */
325                 princ_s = talloc_asprintf(tmpctx, "%s@%s",
326                                           srvPrinc, lp_realm());
327                 if (!princ_s) {
328                         ret = -1;
329                         goto out;
330                 }
331         } else {
332                 /* It's a normal service principal.  Add the SPN now so that we
333                  * can obtain credentials for it and double-check the salt value
334                  * used to generate the service's keys. */
335
336                 if (!service_or_spn_to_kerberos_princ(tmpctx,
337                                                       srvPrinc,
338                                                       my_fqdn,
339                                                       &princ_s,
340                                                       &short_princ_s)) {
341                         ret = -1;
342                         goto out;
343                 }
344
345                 /* According to http://support.microsoft.com/kb/326985/en-us,
346                    certain principal names are automatically mapped to the
347                    host/... principal in the AD account.
348                    So only create these in the keytab, not in AD.  --jerry */
349
350                 if (!strequal(srvPrinc, "cifs") &&
351                     !strequal(srvPrinc, "host")) {
352                         if (!ads_set_machine_account_spns(tmpctx,
353                                                           ads,
354                                                           srvPrinc,
355                                                           my_fqdn)) {
356                                 ret = -1;
357                                 goto out;
358                         }
359                 }
360         }
361
362         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
363         if (kvno == -1) {
364                 /* -1 indicates failure, everything else is OK */
365                 DEBUG(1, (__location__ ": ads_get_machine_kvno failed to "
366                          "determine the system's kvno.\n"));
367                 ret = -1;
368                 goto out;
369         }
370
371         salt_princ_s = kerberos_secrets_fetch_salt_princ();
372         if (salt_princ_s == NULL) {
373                 DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
374                 ret = -1;
375                 goto out;
376         }
377
378         for (i = 0; enctypes[i]; i++) {
379
380                 /* add the fqdn principal to the keytab */
381                 ret = smb_krb5_kt_add_entry(context,
382                                             keytab,
383                                             kvno,
384                                             princ_s,
385                                             salt_princ_s,
386                                             enctypes[i],
387                                             &password,
388                                             false,
389                                             false);
390                 if (ret) {
391                         DEBUG(1, (__location__ ": Failed to add entry to keytab\n"));
392                         goto out;
393                 }
394
395                 /* add the short principal name if we have one */
396                 if (short_princ_s) {
397                         ret = smb_krb5_kt_add_entry(context,
398                                                     keytab,
399                                                     kvno,
400                                                     short_princ_s,
401                                                     salt_princ_s,
402                                                     enctypes[i],
403                                                     &password,
404                                                     false,
405                                                     false);
406                         if (ret) {
407                                 DEBUG(1, (__location__
408                                           ": Failed to add short entry to keytab\n"));
409                                 goto out;
410                         }
411                 }
412         }
413
414 out:
415         SAFE_FREE(salt_princ_s);
416         TALLOC_FREE(tmpctx);
417
418         if (keytab) {
419                 krb5_kt_close(context, keytab);
420         }
421         if (context) {
422                 krb5_free_context(context);
423         }
424         return (int)ret;
425 }
426
427 /**********************************************************************
428  Flushes all entries from the system keytab.
429 ***********************************************************************/
430
431 int ads_keytab_flush(ADS_STRUCT *ads)
432 {
433         krb5_error_code ret = 0;
434         krb5_context context = NULL;
435         krb5_keytab keytab = NULL;
436         krb5_kvno kvno;
437         ADS_STATUS aderr;
438
439         initialize_krb5_error_table();
440         ret = krb5_init_context(&context);
441         if (ret) {
442                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
443                           error_message(ret)));
444                 return ret;
445         }
446
447         ret = ads_keytab_open(context, &keytab);
448         if (ret != 0) {
449                 goto out;
450         }
451
452         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
453         if (kvno == -1) {
454                 /* -1 indicates a failure */
455                 DEBUG(1, (__location__ ": Error determining the kvno.\n"));
456                 goto out;
457         }
458
459         /* Seek and delete old keytab entries */
460         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
461                                                       keytab,
462                                                       kvno,
463                                                       ENCTYPE_NULL,
464                                                       NULL,
465                                                       NULL,
466                                                       true,
467                                                       false);
468         if (ret) {
469                 goto out;
470         }
471
472         aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
473         if (!ADS_ERR_OK(aderr)) {
474                 DEBUG(1, (__location__ ": Error while clearing service "
475                           "principal listings in LDAP.\n"));
476                 goto out;
477         }
478
479 out:
480         if (keytab) {
481                 krb5_kt_close(context, keytab);
482         }
483         if (context) {
484                 krb5_free_context(context);
485         }
486         return ret;
487 }
488
489 /**********************************************************************
490  Adds all the required service principals to the system keytab.
491 ***********************************************************************/
492
493 int ads_keytab_create_default(ADS_STRUCT *ads)
494 {
495         krb5_error_code ret = 0;
496         krb5_context context = NULL;
497         krb5_keytab keytab = NULL;
498         krb5_kt_cursor cursor = {0};
499         krb5_keytab_entry kt_entry = {0};
500         krb5_kvno kvno;
501         size_t found = 0;
502         char *sam_account_name, *upn;
503         char **oldEntries = NULL, *princ_s[26];
504         TALLOC_CTX *frame;
505         char *machine_name;
506         char **spn_array;
507         size_t num_spns;
508         size_t i;
509         bool ok = false;
510         ADS_STATUS status;
511
512         ZERO_STRUCT(kt_entry);
513         ZERO_STRUCT(cursor);
514
515         frame = talloc_stackframe();
516         if (frame == NULL) {
517                 ret = -1;
518                 goto done;
519         }
520
521         status = ads_get_service_principal_names(frame,
522                                                  ads,
523                                                  lp_netbios_name(),
524                                                  &spn_array,
525                                                  &num_spns);
526         if (!ADS_ERR_OK(status)) {
527                 ret = -1;
528                 goto done;
529         }
530
531         for (i = 0; i < num_spns; i++) {
532                 char *srv_princ;
533                 char *p;
534
535                 srv_princ = strlower_talloc(frame, spn_array[i]);
536                 if (srv_princ == NULL) {
537                         ret = -1;
538                         goto done;
539                 }
540
541                 p = strchr_m(srv_princ, '/');
542                 if (p == NULL) {
543                         continue;
544                 }
545                 p[0] = '\0';
546
547                 /* Add the SPNs found on the DC */
548                 ret = ads_keytab_add_entry(ads, srv_princ);
549                 if (ret != 0) {
550                         DEBUG(1, ("ads_keytab_add_entry failed while "
551                                   "adding '%s' principal.\n",
552                                   spn_array[i]));
553                         goto done;
554                 }
555         }
556
557 #if 0   /* don't create the CIFS/... keytab entries since no one except smbd
558            really needs them and we will fall back to verifying against
559            secrets.tdb */
560
561         ret = ads_keytab_add_entry(ads, "cifs"));
562         if (ret != 0 ) {
563                 DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
564                           "adding 'cifs'.\n"));
565                 return ret;
566         }
567 #endif
568
569         memset(princ_s, '\0', sizeof(princ_s));
570
571         initialize_krb5_error_table();
572         ret = krb5_init_context(&context);
573         if (ret) {
574                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
575                           error_message(ret)));
576                 goto done;
577         }
578
579         machine_name = talloc_strdup(frame, lp_netbios_name());
580         if (!machine_name) {
581                 ret = -1;
582                 goto done;
583         }
584
585         /* now add the userPrincipalName and sAMAccountName entries */
586         ok = ads_has_samaccountname(ads, frame, machine_name);
587         if (!ok) {
588                 DEBUG(0, (__location__ ": unable to determine machine "
589                           "account's name in AD!\n"));
590                 ret = -1;
591                 goto done;
592         }
593
594         /*
595          * append '$' to netbios name so 'ads_keytab_add_entry' recognises
596          * it as a machine account rather than a service or Windows SPN.
597          */
598         sam_account_name = talloc_asprintf(frame, "%s$",machine_name);
599         if (sam_account_name == NULL) {
600                 ret = -1;
601                 goto done;
602         }
603         /* upper case the sAMAccountName to make it easier for apps to
604            know what case to use in the keytab file */
605         if (!strupper_m(sam_account_name)) {
606                 ret = -1;
607                 goto done;
608         }
609
610         ret = ads_keytab_add_entry(ads, sam_account_name);
611         if (ret != 0) {
612                 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
613                           "while adding sAMAccountName (%s)\n",
614                           sam_account_name));
615                 goto done;
616         }
617
618         /* remember that not every machine account will have a upn */
619         upn = ads_get_upn(ads, frame, machine_name);
620         if (upn) {
621                 ret = ads_keytab_add_entry(ads, upn);
622                 if (ret != 0) {
623                         DEBUG(1, (__location__ ": ads_keytab_add_entry() "
624                                   "failed while adding UPN (%s)\n", upn));
625                         goto done;
626                 }
627         }
628
629         /* Now loop through the keytab and update any other existing entries */
630         kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
631         if (kvno == (krb5_kvno)-1) {
632                 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
633                           "determine the system's kvno.\n"));
634                 goto done;
635         }
636
637         DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
638                   "and update.\n"));
639
640         ret = ads_keytab_open(context, &keytab);
641         if (ret != 0) {
642                 goto done;
643         }
644
645         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
646         if (ret != KRB5_KT_END && ret != ENOENT ) {
647                 while ((ret = krb5_kt_next_entry(context, keytab,
648                                                  &kt_entry, &cursor)) == 0) {
649                         smb_krb5_kt_free_entry(context, &kt_entry);
650                         ZERO_STRUCT(kt_entry);
651                         found++;
652                 }
653         }
654         krb5_kt_end_seq_get(context, keytab, &cursor);
655         ZERO_STRUCT(cursor);
656
657         /*
658          * Hmmm. There is no "rewind" function for the keytab. This means we
659          * have a race condition where someone else could add entries after
660          * we've counted them. Re-open asap to minimise the race. JRA.
661          */
662         DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
663         if (!found) {
664                 goto done;
665         }
666
667         oldEntries = talloc_zero_array(frame, char *, found + 1);
668         if (!oldEntries) {
669                 DEBUG(1, (__location__ ": Failed to allocate space to store "
670                           "the old keytab entries (talloc failed?).\n"));
671                 ret = -1;
672                 goto done;
673         }
674
675         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
676         if (ret == KRB5_KT_END || ret == ENOENT) {
677                 krb5_kt_end_seq_get(context, keytab, &cursor);
678                 ZERO_STRUCT(cursor);
679                 goto done;
680         }
681
682         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
683                 if (kt_entry.vno != kvno) {
684                         char *ktprinc = NULL;
685                         char *p;
686
687                         /* This returns a malloc'ed string in ktprinc. */
688                         ret = smb_krb5_unparse_name(oldEntries, context,
689                                                     kt_entry.principal,
690                                                     &ktprinc);
691                         if (ret) {
692                                 DEBUG(1, (__location__
693                                          ": smb_krb5_unparse_name failed "
694                                          "(%s)\n", error_message(ret)));
695                                 goto done;
696                         }
697                         /*
698                          * From looking at the krb5 source they don't seem to
699                          * take locale or mb strings into account.
700                          * Maybe this is because they assume utf8 ?
701                          * In this case we may need to convert from utf8 to
702                          * mb charset here ? JRA.
703                          */
704                         p = strchr_m(ktprinc, '@');
705                         if (p) {
706                                 *p = '\0';
707                         }
708
709                         p = strchr_m(ktprinc, '/');
710                         if (p) {
711                                 *p = '\0';
712                         }
713                         for (i = 0; i < found; i++) {
714                                 if (!oldEntries[i]) {
715                                         oldEntries[i] = ktprinc;
716                                         break;
717                                 }
718                                 if (!strcmp(oldEntries[i], ktprinc)) {
719                                         TALLOC_FREE(ktprinc);
720                                         break;
721                                 }
722                         }
723                         if (i == found) {
724                                 TALLOC_FREE(ktprinc);
725                         }
726                 }
727                 smb_krb5_kt_free_entry(context, &kt_entry);
728                 ZERO_STRUCT(kt_entry);
729         }
730         krb5_kt_end_seq_get(context, keytab, &cursor);
731         ZERO_STRUCT(cursor);
732
733         ret = 0;
734         for (i = 0; oldEntries[i]; i++) {
735                 ret |= ads_keytab_add_entry(ads, oldEntries[i]);
736                 TALLOC_FREE(oldEntries[i]);
737         }
738
739 done:
740         TALLOC_FREE(oldEntries);
741         TALLOC_FREE(frame);
742
743         if (context) {
744                 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
745                         smb_krb5_kt_free_entry(context, &kt_entry);
746                 }
747                 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
748                         krb5_kt_end_seq_get(context, keytab, &cursor);
749                 }
750                 if (keytab) {
751                         krb5_kt_close(context, keytab);
752                 }
753                 krb5_free_context(context);
754         }
755         return ret;
756 }
757
758 #endif /* HAVE_ADS */
759
760 /**********************************************************************
761  List system keytab.
762 ***********************************************************************/
763
764 int ads_keytab_list(const char *keytab_name)
765 {
766         krb5_error_code ret = 0;
767         krb5_context context = NULL;
768         krb5_keytab keytab = NULL;
769         krb5_kt_cursor cursor;
770         krb5_keytab_entry kt_entry;
771
772         ZERO_STRUCT(kt_entry);
773         ZERO_STRUCT(cursor);
774
775         initialize_krb5_error_table();
776         ret = krb5_init_context(&context);
777         if (ret) {
778                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
779                           error_message(ret)));
780                 return ret;
781         }
782
783         if (keytab_name == NULL) {
784 #ifdef HAVE_ADS
785                 ret = ads_keytab_open(context, &keytab);
786 #else
787                 ret = ENOENT;
788 #endif
789         } else {
790                 ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
791         }
792         if (ret) {
793                 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
794                           error_message(ret)));
795                 goto out;
796         }
797
798         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
799         if (ret) {
800                 ZERO_STRUCT(cursor);
801                 goto out;
802         }
803
804         printf("Vno  Type                                        Principal\n");
805
806         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
807
808                 char *princ_s = NULL;
809                 char *etype_s = NULL;
810                 krb5_enctype enctype = 0;
811
812                 ret = smb_krb5_unparse_name(talloc_tos(), context,
813                                             kt_entry.principal, &princ_s);
814                 if (ret) {
815                         goto out;
816                 }
817
818                 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
819
820                 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
821                 if (ret &&
822                     (asprintf(&etype_s, "UNKNOWN: %d\n", enctype) == -1)) {
823                         TALLOC_FREE(princ_s);
824                         goto out;
825                 }
826
827                 printf("%3d  %-43s %s\n", kt_entry.vno, etype_s, princ_s);
828
829                 TALLOC_FREE(princ_s);
830                 SAFE_FREE(etype_s);
831
832                 ret = smb_krb5_kt_free_entry(context, &kt_entry);
833                 if (ret) {
834                         goto out;
835                 }
836         }
837
838         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
839         if (ret) {
840                 goto out;
841         }
842
843         /* Ensure we don't double free. */
844         ZERO_STRUCT(kt_entry);
845         ZERO_STRUCT(cursor);
846 out:
847
848         if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
849                 smb_krb5_kt_free_entry(context, &kt_entry);
850         }
851         if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
852                 krb5_kt_end_seq_get(context, keytab, &cursor);
853         }
854
855         if (keytab) {
856                 krb5_kt_close(context, keytab);
857         }
858         if (context) {
859                 krb5_free_context(context);
860         }
861         return ret;
862 }
863
864 #endif /* HAVE_KRB5 */