libads: Add kerberos tracing
[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, bool update_ads)
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         ret = smb_krb5_init_context_common(&context);
263         if (ret) {
264                 DBG_ERR("kerberos init context failed (%s)\n",
265                         error_message(ret));
266                 return -1;
267         }
268
269         ret = ads_keytab_open(context, &keytab);
270         if (ret != 0) {
271                 goto out;
272         }
273
274         /* retrieve the password */
275         if (!secrets_init()) {
276                 DEBUG(1, (__location__ ": secrets_init failed\n"));
277                 ret = -1;
278                 goto out;
279         }
280         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
281         if (!password_s) {
282                 DEBUG(1, (__location__ ": failed to fetch machine password\n"));
283                 ret = -1;
284                 goto out;
285         }
286         ZERO_STRUCT(password);
287         password.data = password_s;
288         password.length = strlen(password_s);
289
290         /* we need the dNSHostName value here */
291         tmpctx = talloc_init(__location__);
292         if (!tmpctx) {
293                 DEBUG(0, (__location__ ": talloc_init() failed!\n"));
294                 ret = -1;
295                 goto out;
296         }
297
298         my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
299         if (!my_fqdn) {
300                 DEBUG(0, (__location__ ": unable to determine machine "
301                           "account's dns name in AD!\n"));
302                 ret = -1;
303                 goto out;
304         }
305
306         /* make sure we have a single instance of a the computer account */
307         if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
308                 DEBUG(0, (__location__ ": unable to determine machine "
309                           "account's short name in AD!\n"));
310                 ret = -1;
311                 goto out;
312         }
313
314         /* Construct our principal */
315         if (strchr_m(srvPrinc, '@')) {
316                 /* It's a fully-named principal. */
317                 princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
318                 if (!princ_s) {
319                         ret = -1;
320                         goto out;
321                 }
322         } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
323                 /* It's the machine account, as used by smbclient clients. */
324                 princ_s = talloc_asprintf(tmpctx, "%s@%s",
325                                           srvPrinc, lp_realm());
326                 if (!princ_s) {
327                         ret = -1;
328                         goto out;
329                 }
330         } else {
331                 /* It's a normal service principal.  Add the SPN now so that we
332                  * can obtain credentials for it and double-check the salt value
333                  * used to generate the service's keys. */
334
335                 if (!service_or_spn_to_kerberos_princ(tmpctx,
336                                                       srvPrinc,
337                                                       my_fqdn,
338                                                       &princ_s,
339                                                       &short_princ_s)) {
340                         ret = -1;
341                         goto out;
342                 }
343
344                 /* According to http://support.microsoft.com/kb/326985/en-us,
345                    certain principal names are automatically mapped to the
346                    host/... principal in the AD account.
347                    So only create these in the keytab, not in AD.  --jerry */
348
349                 if (update_ads && !strequal(srvPrinc, "cifs") &&
350                     !strequal(srvPrinc, "host")) {
351                         if (!ads_set_machine_account_spns(tmpctx,
352                                                           ads,
353                                                           srvPrinc,
354                                                           my_fqdn)) {
355                                 ret = -1;
356                                 goto out;
357                         }
358                 }
359         }
360
361         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
362         if (kvno == -1) {
363                 /* -1 indicates failure, everything else is OK */
364                 DEBUG(1, (__location__ ": ads_get_machine_kvno failed to "
365                          "determine the system's kvno.\n"));
366                 ret = -1;
367                 goto out;
368         }
369
370         salt_princ_s = kerberos_secrets_fetch_salt_princ();
371         if (salt_princ_s == NULL) {
372                 DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
373                 ret = -1;
374                 goto out;
375         }
376
377         for (i = 0; enctypes[i]; i++) {
378
379                 /* add the fqdn principal to the keytab */
380                 ret = smb_krb5_kt_add_entry(context,
381                                             keytab,
382                                             kvno,
383                                             princ_s,
384                                             salt_princ_s,
385                                             enctypes[i],
386                                             &password,
387                                             false,
388                                             false);
389                 if (ret) {
390                         DEBUG(1, (__location__ ": Failed to add entry to keytab\n"));
391                         goto out;
392                 }
393
394                 /* add the short principal name if we have one */
395                 if (short_princ_s) {
396                         ret = smb_krb5_kt_add_entry(context,
397                                                     keytab,
398                                                     kvno,
399                                                     short_princ_s,
400                                                     salt_princ_s,
401                                                     enctypes[i],
402                                                     &password,
403                                                     false,
404                                                     false);
405                         if (ret) {
406                                 DEBUG(1, (__location__
407                                           ": Failed to add short entry to keytab\n"));
408                                 goto out;
409                         }
410                 }
411         }
412
413 out:
414         SAFE_FREE(salt_princ_s);
415         TALLOC_FREE(tmpctx);
416
417         if (keytab) {
418                 krb5_kt_close(context, keytab);
419         }
420         if (context) {
421                 krb5_free_context(context);
422         }
423         return (int)ret;
424 }
425
426 /**********************************************************************
427  Flushes all entries from the system keytab.
428 ***********************************************************************/
429
430 int ads_keytab_flush(ADS_STRUCT *ads)
431 {
432         krb5_error_code ret = 0;
433         krb5_context context = NULL;
434         krb5_keytab keytab = NULL;
435         krb5_kvno kvno;
436         ADS_STATUS aderr;
437
438         ret = smb_krb5_init_context_common(&context);
439         if (ret) {
440                 DBG_ERR("kerberos init context failed (%s)\n",
441                         error_message(ret));
442                 return ret;
443         }
444
445         ret = ads_keytab_open(context, &keytab);
446         if (ret != 0) {
447                 goto out;
448         }
449
450         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
451         if (kvno == -1) {
452                 /* -1 indicates a failure */
453                 DEBUG(1, (__location__ ": Error determining the kvno.\n"));
454                 ret = -1;
455                 goto out;
456         }
457
458         /* Seek and delete old keytab entries */
459         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
460                                                       keytab,
461                                                       kvno,
462                                                       ENCTYPE_NULL,
463                                                       NULL,
464                                                       NULL,
465                                                       true,
466                                                       false);
467         if (ret) {
468                 goto out;
469         }
470
471         aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
472         if (!ADS_ERR_OK(aderr)) {
473                 DEBUG(1, (__location__ ": Error while clearing service "
474                           "principal listings in LDAP.\n"));
475                 ret = -1;
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, false);
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", false));
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         ret = smb_krb5_init_context_common(&context);
572         if (ret) {
573                 DBG_ERR("kerberos init context failed (%s)\n",
574                         error_message(ret));
575                 goto done;
576         }
577
578         machine_name = talloc_strdup(frame, lp_netbios_name());
579         if (!machine_name) {
580                 ret = -1;
581                 goto done;
582         }
583
584         /* now add the userPrincipalName and sAMAccountName entries */
585         ok = ads_has_samaccountname(ads, frame, machine_name);
586         if (!ok) {
587                 DEBUG(0, (__location__ ": unable to determine machine "
588                           "account's name in AD!\n"));
589                 ret = -1;
590                 goto done;
591         }
592
593         /*
594          * append '$' to netbios name so 'ads_keytab_add_entry' recognises
595          * it as a machine account rather than a service or Windows SPN.
596          */
597         sam_account_name = talloc_asprintf(frame, "%s$",machine_name);
598         if (sam_account_name == NULL) {
599                 ret = -1;
600                 goto done;
601         }
602         /* upper case the sAMAccountName to make it easier for apps to
603            know what case to use in the keytab file */
604         if (!strupper_m(sam_account_name)) {
605                 ret = -1;
606                 goto done;
607         }
608
609         ret = ads_keytab_add_entry(ads, sam_account_name, false);
610         if (ret != 0) {
611                 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
612                           "while adding sAMAccountName (%s)\n",
613                           sam_account_name));
614                 goto done;
615         }
616
617         /* remember that not every machine account will have a upn */
618         upn = ads_get_upn(ads, frame, machine_name);
619         if (upn) {
620                 ret = ads_keytab_add_entry(ads, upn, false);
621                 if (ret != 0) {
622                         DEBUG(1, (__location__ ": ads_keytab_add_entry() "
623                                   "failed while adding UPN (%s)\n", upn));
624                         goto done;
625                 }
626         }
627
628         /* Now loop through the keytab and update any other existing entries */
629         kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
630         if (kvno == (krb5_kvno)-1) {
631                 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
632                           "determine the system's kvno.\n"));
633                 goto done;
634         }
635
636         DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
637                   "and update.\n"));
638
639         ret = ads_keytab_open(context, &keytab);
640         if (ret != 0) {
641                 goto done;
642         }
643
644         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
645         if (ret != KRB5_KT_END && ret != ENOENT ) {
646                 while ((ret = krb5_kt_next_entry(context, keytab,
647                                                  &kt_entry, &cursor)) == 0) {
648                         smb_krb5_kt_free_entry(context, &kt_entry);
649                         ZERO_STRUCT(kt_entry);
650                         found++;
651                 }
652         }
653         krb5_kt_end_seq_get(context, keytab, &cursor);
654         ZERO_STRUCT(cursor);
655
656         /*
657          * Hmmm. There is no "rewind" function for the keytab. This means we
658          * have a race condition where someone else could add entries after
659          * we've counted them. Re-open asap to minimise the race. JRA.
660          */
661         DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
662         if (!found) {
663                 goto done;
664         }
665
666         oldEntries = talloc_zero_array(frame, char *, found + 1);
667         if (!oldEntries) {
668                 DEBUG(1, (__location__ ": Failed to allocate space to store "
669                           "the old keytab entries (talloc failed?).\n"));
670                 ret = -1;
671                 goto done;
672         }
673
674         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
675         if (ret == KRB5_KT_END || ret == ENOENT) {
676                 krb5_kt_end_seq_get(context, keytab, &cursor);
677                 ZERO_STRUCT(cursor);
678                 goto done;
679         }
680
681         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
682                 if (kt_entry.vno != kvno) {
683                         char *ktprinc = NULL;
684                         char *p;
685
686                         /* This returns a malloc'ed string in ktprinc. */
687                         ret = smb_krb5_unparse_name(oldEntries, context,
688                                                     kt_entry.principal,
689                                                     &ktprinc);
690                         if (ret) {
691                                 DEBUG(1, (__location__
692                                          ": smb_krb5_unparse_name failed "
693                                          "(%s)\n", error_message(ret)));
694                                 goto done;
695                         }
696                         /*
697                          * From looking at the krb5 source they don't seem to
698                          * take locale or mb strings into account.
699                          * Maybe this is because they assume utf8 ?
700                          * In this case we may need to convert from utf8 to
701                          * mb charset here ? JRA.
702                          */
703                         p = strchr_m(ktprinc, '@');
704                         if (p) {
705                                 *p = '\0';
706                         }
707
708                         p = strchr_m(ktprinc, '/');
709                         if (p) {
710                                 *p = '\0';
711                         }
712                         for (i = 0; i < found; i++) {
713                                 if (!oldEntries[i]) {
714                                         oldEntries[i] = ktprinc;
715                                         break;
716                                 }
717                                 if (!strcmp(oldEntries[i], ktprinc)) {
718                                         TALLOC_FREE(ktprinc);
719                                         break;
720                                 }
721                         }
722                         if (i == found) {
723                                 TALLOC_FREE(ktprinc);
724                         }
725                 }
726                 smb_krb5_kt_free_entry(context, &kt_entry);
727                 ZERO_STRUCT(kt_entry);
728         }
729         krb5_kt_end_seq_get(context, keytab, &cursor);
730         ZERO_STRUCT(cursor);
731
732         ret = 0;
733         for (i = 0; oldEntries[i]; i++) {
734                 ret |= ads_keytab_add_entry(ads, oldEntries[i], false);
735                 TALLOC_FREE(oldEntries[i]);
736         }
737
738 done:
739         TALLOC_FREE(oldEntries);
740         TALLOC_FREE(frame);
741
742         if (context) {
743                 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
744                         smb_krb5_kt_free_entry(context, &kt_entry);
745                 }
746                 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
747                         krb5_kt_end_seq_get(context, keytab, &cursor);
748                 }
749                 if (keytab) {
750                         krb5_kt_close(context, keytab);
751                 }
752                 krb5_free_context(context);
753         }
754         return ret;
755 }
756
757 #endif /* HAVE_ADS */
758
759 /**********************************************************************
760  List system keytab.
761 ***********************************************************************/
762
763 int ads_keytab_list(const char *keytab_name)
764 {
765         krb5_error_code ret = 0;
766         krb5_context context = NULL;
767         krb5_keytab keytab = NULL;
768         krb5_kt_cursor cursor;
769         krb5_keytab_entry kt_entry;
770
771         ZERO_STRUCT(kt_entry);
772         ZERO_STRUCT(cursor);
773
774         ret = smb_krb5_init_context_common(&context);
775         if (ret) {
776                 DBG_ERR("kerberos init context failed (%s)\n",
777                         error_message(ret));
778                 return ret;
779         }
780
781         if (keytab_name == NULL) {
782 #ifdef HAVE_ADS
783                 ret = ads_keytab_open(context, &keytab);
784 #else
785                 ret = ENOENT;
786 #endif
787         } else {
788                 ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
789         }
790         if (ret) {
791                 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
792                           error_message(ret)));
793                 goto out;
794         }
795
796         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
797         if (ret) {
798                 ZERO_STRUCT(cursor);
799                 goto out;
800         }
801
802         printf("Vno  Type                                        Principal\n");
803
804         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
805
806                 char *princ_s = NULL;
807                 char *etype_s = NULL;
808                 krb5_enctype enctype = 0;
809
810                 ret = smb_krb5_unparse_name(talloc_tos(), context,
811                                             kt_entry.principal, &princ_s);
812                 if (ret) {
813                         goto out;
814                 }
815
816                 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
817
818                 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
819                 if (ret &&
820                     (asprintf(&etype_s, "UNKNOWN: %d\n", enctype) == -1)) {
821                         TALLOC_FREE(princ_s);
822                         goto out;
823                 }
824
825                 printf("%3d  %-43s %s\n", kt_entry.vno, etype_s, princ_s);
826
827                 TALLOC_FREE(princ_s);
828                 SAFE_FREE(etype_s);
829
830                 ret = smb_krb5_kt_free_entry(context, &kt_entry);
831                 if (ret) {
832                         goto out;
833                 }
834         }
835
836         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
837         if (ret) {
838                 goto out;
839         }
840
841         /* Ensure we don't double free. */
842         ZERO_STRUCT(kt_entry);
843         ZERO_STRUCT(cursor);
844 out:
845
846         if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
847                 smb_krb5_kt_free_entry(context, &kt_entry);
848         }
849         if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
850                 krb5_kt_end_seq_get(context, keytab, &cursor);
851         }
852
853         if (keytab) {
854                 krb5_kt_close(context, keytab);
855         }
856         if (context) {
857                 krb5_free_context(context);
858         }
859         return ret;
860 }
861
862 #endif /* HAVE_KRB5 */