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