smbd: Remove unused [push_pull]_file_id_24
[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\n");
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 static int add_kt_entry_etypes(krb5_context context, TALLOC_CTX *tmpctx,
232                                ADS_STRUCT *ads, const char *salt_princ_s,
233                                krb5_keytab keytab, krb5_kvno kvno,
234                                const char *srvPrinc, const char *my_fqdn,
235                                krb5_data *password, bool update_ads)
236 {
237         krb5_error_code ret = 0;
238         char *princ_s = NULL;
239         char *short_princ_s = NULL;
240         krb5_enctype enctypes[4] = {
241                 ENCTYPE_AES256_CTS_HMAC_SHA1_96,
242                 ENCTYPE_AES128_CTS_HMAC_SHA1_96,
243                 ENCTYPE_ARCFOUR_HMAC,
244                 0
245         };
246         size_t i;
247
248         /* Construct our principal */
249         if (strchr_m(srvPrinc, '@')) {
250                 /* It's a fully-named principal. */
251                 princ_s = talloc_asprintf(tmpctx, "%s", srvPrinc);
252                 if (!princ_s) {
253                         ret = -1;
254                         goto out;
255                 }
256         } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
257                 /* It's the machine account, as used by smbclient clients. */
258                 princ_s = talloc_asprintf(tmpctx, "%s@%s",
259                                           srvPrinc, lp_realm());
260                 if (!princ_s) {
261                         ret = -1;
262                         goto out;
263                 }
264         } else {
265                 /* It's a normal service principal.  Add the SPN now so that we
266                  * can obtain credentials for it and double-check the salt value
267                  * used to generate the service's keys. */
268
269                 if (!service_or_spn_to_kerberos_princ(tmpctx,
270                                                       srvPrinc,
271                                                       my_fqdn,
272                                                       &princ_s,
273                                                       &short_princ_s)) {
274                         ret = -1;
275                         goto out;
276                 }
277
278                 /* According to http://support.microsoft.com/kb/326985/en-us,
279                    certain principal names are automatically mapped to the
280                    host/... principal in the AD account.
281                    So only create these in the keytab, not in AD.  --jerry */
282
283                 if (update_ads && !strequal(srvPrinc, "cifs") &&
284                     !strequal(srvPrinc, "host")) {
285                         if (!ads_set_machine_account_spns(tmpctx,
286                                                           ads,
287                                                           srvPrinc,
288                                                           my_fqdn)) {
289                                 ret = -1;
290                                 goto out;
291                         }
292                 }
293         }
294
295         for (i = 0; enctypes[i]; i++) {
296
297                 /* add the fqdn principal to the keytab */
298                 ret = smb_krb5_kt_add_password(context,
299                                                keytab,
300                                                kvno,
301                                                princ_s,
302                                                salt_princ_s,
303                                                enctypes[i],
304                                                password);
305                 if (ret) {
306                         DBG_WARNING("Failed to add entry to keytab\n");
307                         goto out;
308                 }
309
310                 /* add the short principal name if we have one */
311                 if (short_princ_s) {
312                         ret = smb_krb5_kt_add_password(context,
313                                                        keytab,
314                                                        kvno,
315                                                        short_princ_s,
316                                                        salt_princ_s,
317                                                        enctypes[i],
318                                                        password);
319                         if (ret) {
320                                 DBG_WARNING("Failed to add short entry to keytab\n");
321                                 goto out;
322                         }
323                 }
324         }
325 out:
326         return ret;
327 }
328
329 /**********************************************************************
330  Adds a single service principal, i.e. 'host' to the system keytab
331 ***********************************************************************/
332
333 int ads_keytab_add_entry(ADS_STRUCT *ads, const char *srvPrinc, bool update_ads)
334 {
335         krb5_error_code ret = 0;
336         krb5_context context = NULL;
337         krb5_keytab keytab = NULL;
338         krb5_data password;
339         krb5_kvno kvno;
340         char *salt_princ_s = NULL;
341         char *password_s = NULL;
342         char *my_fqdn;
343         TALLOC_CTX *tmpctx = NULL;
344         char **hostnames_array = NULL;
345         size_t num_hostnames = 0;
346
347         ret = smb_krb5_init_context_common(&context);
348         if (ret) {
349                 DBG_ERR("kerberos init context failed (%s)\n",
350                         error_message(ret));
351                 return -1;
352         }
353
354         ret = ads_keytab_open(context, &keytab);
355         if (ret != 0) {
356                 goto out;
357         }
358
359         /* retrieve the password */
360         if (!secrets_init()) {
361                 DBG_WARNING("secrets_init failed\n");
362                 ret = -1;
363                 goto out;
364         }
365         password_s = secrets_fetch_machine_password(lp_workgroup(), NULL, NULL);
366         if (!password_s) {
367                 DBG_WARNING("failed to fetch machine password\n");
368                 ret = -1;
369                 goto out;
370         }
371         ZERO_STRUCT(password);
372         password.data = password_s;
373         password.length = strlen(password_s);
374
375         /* we need the dNSHostName value here */
376         tmpctx = talloc_init(__location__);
377         if (!tmpctx) {
378                 DBG_ERR("talloc_init() failed!\n");
379                 ret = -1;
380                 goto out;
381         }
382
383         my_fqdn = ads_get_dnshostname(ads, tmpctx, lp_netbios_name());
384         if (!my_fqdn) {
385                 DBG_ERR("unable to determine machine account's dns name in "
386                         "AD!\n");
387                 ret = -1;
388                 goto out;
389         }
390
391         /* make sure we have a single instance of the computer account */
392         if (!ads_has_samaccountname(ads, tmpctx, lp_netbios_name())) {
393                 DBG_ERR("unable to determine machine account's short name in "
394                         "AD!\n");
395                 ret = -1;
396                 goto out;
397         }
398
399         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
400         if (kvno == -1) {
401                 /* -1 indicates failure, everything else is OK */
402                 DBG_WARNING("ads_get_machine_kvno failed to determine the "
403                             "system's kvno.\n");
404                 ret = -1;
405                 goto out;
406         }
407
408         salt_princ_s = kerberos_secrets_fetch_salt_princ();
409         if (salt_princ_s == NULL) {
410                 DBG_WARNING("kerberos_secrets_fetch_salt_princ() failed\n");
411                 ret = -1;
412                 goto out;
413         }
414
415         ret = add_kt_entry_etypes(context, tmpctx, ads, salt_princ_s, keytab,
416                                   kvno, srvPrinc, my_fqdn, &password,
417                                   update_ads);
418         if (ret != 0) {
419                 goto out;
420         }
421
422         if (ADS_ERR_OK(ads_get_additional_dns_hostnames(tmpctx, ads,
423                                                         lp_netbios_name(),
424                                                         &hostnames_array,
425                                                         &num_hostnames))) {
426                 size_t i;
427
428                 for (i = 0; i < num_hostnames; i++) {
429
430                         ret = add_kt_entry_etypes(context, tmpctx, ads,
431                                                   salt_princ_s, keytab,
432                                                   kvno, srvPrinc,
433                                                   hostnames_array[i],
434                                                   &password, update_ads);
435                         if (ret != 0) {
436                                 goto out;
437                         }
438                 }
439         }
440
441 out:
442         SAFE_FREE(salt_princ_s);
443         TALLOC_FREE(tmpctx);
444
445         if (keytab) {
446                 krb5_kt_close(context, keytab);
447         }
448         if (context) {
449                 krb5_free_context(context);
450         }
451         return (int)ret;
452 }
453
454 /**********************************************************************
455  Delete a single service principal, i.e. 'host' from the system keytab
456 ***********************************************************************/
457
458 int ads_keytab_delete_entry(ADS_STRUCT *ads, const char *srvPrinc)
459 {
460         TALLOC_CTX *frame = talloc_stackframe();
461         krb5_error_code ret = 0;
462         krb5_context context = NULL;
463         krb5_keytab keytab = NULL;
464         char *princ_s = NULL;
465         krb5_principal princ = NULL;
466         char *short_princ_s = NULL;
467         krb5_principal short_princ = NULL;
468         bool ok;
469
470         ret = smb_krb5_init_context_common(&context);
471         if (ret) {
472                 DBG_ERR("kerberos init context failed (%s)\n",
473                         error_message(ret));
474                 goto out;
475         }
476
477         ret = ads_keytab_open(context, &keytab);
478         if (ret != 0) {
479                 goto out;
480         }
481
482         /* Construct our principal */
483         if (strchr_m(srvPrinc, '@')) {
484                 /* It's a fully-named principal. */
485                 princ_s = talloc_asprintf(frame, "%s", srvPrinc);
486                 if (!princ_s) {
487                         ret = -1;
488                         goto out;
489                 }
490         } else if (srvPrinc[strlen(srvPrinc)-1] == '$') {
491                 /* It's the machine account, as used by smbclient clients. */
492                 princ_s = talloc_asprintf(frame, "%s@%s",
493                                           srvPrinc, lp_realm());
494                 if (!princ_s) {
495                         ret = -1;
496                         goto out;
497                 }
498         } else {
499                 /*
500                  * It's a normal service principal.
501                  */
502                 char *my_fqdn = NULL;
503                 char *tmp = NULL;
504
505                 /*
506                  * SPN should have '/' otherwise we
507                  * need to fallback and find our dnshostname
508                  */
509                 tmp = strchr_m(srvPrinc, '/');
510                 if (tmp == NULL) {
511                         my_fqdn = ads_get_dnshostname(ads, frame, lp_netbios_name());
512                         if (!my_fqdn) {
513                                 DBG_ERR("unable to determine machine account's dns name in "
514                                         "AD!\n");
515                                 ret = -1;
516                                 goto out;
517                         }
518                 }
519
520                 ok = service_or_spn_to_kerberos_princ(frame,
521                                                       srvPrinc,
522                                                       my_fqdn,
523                                                       &princ_s,
524                                                       &short_princ_s);
525                 if (!ok) {
526                         ret = -1;
527                         goto out;
528                 }
529         }
530
531         ret = smb_krb5_parse_name(context, princ_s, &princ);
532         if (ret) {
533                 DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
534                           "failed (%s)\n", princ_s, error_message(ret)));
535                 goto out;
536         }
537
538         if (short_princ_s != NULL) {
539                 ret = smb_krb5_parse_name(context, short_princ_s, &short_princ);
540                 if (ret) {
541                         DEBUG(1, (__location__ ": smb_krb5_parse_name(%s) "
542                                   "failed (%s)\n", short_princ_s, error_message(ret)));
543                         goto out;
544                 }
545         }
546
547         /* Seek and delete old keytab entries */
548         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
549                                                       keytab,
550                                                       false, /* keep_old_kvno */
551                                                       -1,
552                                                       false, /* enctype_only */
553                                                       ENCTYPE_NULL,
554                                                       princ_s,
555                                                       princ,
556                                                       false); /* flush */
557         if (ret) {
558                 goto out;
559         }
560
561         if (short_princ_s == NULL) {
562                 goto out;
563         }
564
565         /* Seek and delete old keytab entries */
566         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
567                                                       keytab,
568                                                       false, /* keep_old_kvno */
569                                                       -1,
570                                                       false, /* enctype_only */
571                                                       ENCTYPE_NULL,
572                                                       short_princ_s,
573                                                       short_princ,
574                                                       false); /* flush */
575         if (ret) {
576                 goto out;
577         }
578
579 out:
580         if (princ) {
581                 krb5_free_principal(context, princ);
582         }
583         if (short_princ) {
584                 krb5_free_principal(context, short_princ);
585         }
586         if (keytab) {
587                 krb5_kt_close(context, keytab);
588         }
589         if (context) {
590                 krb5_free_context(context);
591         }
592         TALLOC_FREE(frame);
593         return ret;
594 }
595
596 /**********************************************************************
597  Flushes all entries from the system keytab.
598 ***********************************************************************/
599
600 int ads_keytab_flush(ADS_STRUCT *ads)
601 {
602         krb5_error_code ret = 0;
603         krb5_context context = NULL;
604         krb5_keytab keytab = NULL;
605         ADS_STATUS aderr;
606
607         ret = smb_krb5_init_context_common(&context);
608         if (ret) {
609                 DBG_ERR("kerberos init context failed (%s)\n",
610                         error_message(ret));
611                 return ret;
612         }
613
614         ret = ads_keytab_open(context, &keytab);
615         if (ret != 0) {
616                 goto out;
617         }
618
619         /* Seek and delete all old keytab entries */
620         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
621                                                       keytab,
622                                                       false, /* keep_old_kvno */
623                                                       -1,
624                                                       false, /* enctype_only */
625                                                       ENCTYPE_NULL,
626                                                       NULL,
627                                                       NULL,
628                                                       true); /* flush */
629         if (ret) {
630                 goto out;
631         }
632
633         aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
634         if (!ADS_ERR_OK(aderr)) {
635                 DEBUG(1, (__location__ ": Error while clearing service "
636                           "principal listings in LDAP.\n"));
637                 ret = -1;
638                 goto out;
639         }
640
641 out:
642         if (keytab) {
643                 krb5_kt_close(context, keytab);
644         }
645         if (context) {
646                 krb5_free_context(context);
647         }
648         return ret;
649 }
650
651 /**********************************************************************
652  Adds all the required service principals to the system keytab.
653 ***********************************************************************/
654
655 int ads_keytab_create_default(ADS_STRUCT *ads)
656 {
657         krb5_error_code ret = 0;
658         krb5_context context = NULL;
659         krb5_keytab keytab = NULL;
660         krb5_kt_cursor cursor = {0};
661         krb5_keytab_entry kt_entry = {0};
662         krb5_kvno kvno;
663         size_t found = 0;
664         char *sam_account_name, *upn;
665         char **oldEntries = NULL, *princ_s[26];
666         TALLOC_CTX *frame;
667         char *machine_name;
668         char **spn_array;
669         size_t num_spns;
670         size_t i;
671         bool ok = false;
672         ADS_STATUS status;
673
674         ZERO_STRUCT(kt_entry);
675         ZERO_STRUCT(cursor);
676
677         frame = talloc_stackframe();
678         if (frame == NULL) {
679                 ret = -1;
680                 goto done;
681         }
682
683         status = ads_get_service_principal_names(frame,
684                                                  ads,
685                                                  lp_netbios_name(),
686                                                  &spn_array,
687                                                  &num_spns);
688         if (!ADS_ERR_OK(status)) {
689                 ret = -1;
690                 goto done;
691         }
692
693         for (i = 0; i < num_spns; i++) {
694                 char *srv_princ;
695                 char *p;
696
697                 srv_princ = strlower_talloc(frame, spn_array[i]);
698                 if (srv_princ == NULL) {
699                         ret = -1;
700                         goto done;
701                 }
702
703                 p = strchr_m(srv_princ, '/');
704                 if (p == NULL) {
705                         continue;
706                 }
707                 p[0] = '\0';
708
709                 /* Add the SPNs found on the DC */
710                 ret = ads_keytab_add_entry(ads, srv_princ, false);
711                 if (ret != 0) {
712                         DEBUG(1, ("ads_keytab_add_entry failed while "
713                                   "adding '%s' principal.\n",
714                                   spn_array[i]));
715                         goto done;
716                 }
717         }
718
719 #if 0   /* don't create the CIFS/... keytab entries since no one except smbd
720            really needs them and we will fall back to verifying against
721            secrets.tdb */
722
723         ret = ads_keytab_add_entry(ads, "cifs", false));
724         if (ret != 0 ) {
725                 DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
726                           "adding 'cifs'.\n"));
727                 return ret;
728         }
729 #endif
730
731         memset(princ_s, '\0', sizeof(princ_s));
732
733         ret = smb_krb5_init_context_common(&context);
734         if (ret) {
735                 DBG_ERR("kerberos init context failed (%s)\n",
736                         error_message(ret));
737                 goto done;
738         }
739
740         machine_name = talloc_strdup(frame, lp_netbios_name());
741         if (!machine_name) {
742                 ret = -1;
743                 goto done;
744         }
745
746         /* now add the userPrincipalName and sAMAccountName entries */
747         ok = ads_has_samaccountname(ads, frame, machine_name);
748         if (!ok) {
749                 DEBUG(0, (__location__ ": unable to determine machine "
750                           "account's name in AD!\n"));
751                 ret = -1;
752                 goto done;
753         }
754
755         /*
756          * append '$' to netbios name so 'ads_keytab_add_entry' recognises
757          * it as a machine account rather than a service or Windows SPN.
758          */
759         sam_account_name = talloc_asprintf(frame, "%s$",machine_name);
760         if (sam_account_name == NULL) {
761                 ret = -1;
762                 goto done;
763         }
764         /* upper case the sAMAccountName to make it easier for apps to
765            know what case to use in the keytab file */
766         if (!strupper_m(sam_account_name)) {
767                 ret = -1;
768                 goto done;
769         }
770
771         ret = ads_keytab_add_entry(ads, sam_account_name, false);
772         if (ret != 0) {
773                 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
774                           "while adding sAMAccountName (%s)\n",
775                           sam_account_name));
776                 goto done;
777         }
778
779         /* remember that not every machine account will have a upn */
780         upn = ads_get_upn(ads, frame, machine_name);
781         if (upn) {
782                 ret = ads_keytab_add_entry(ads, upn, false);
783                 if (ret != 0) {
784                         DEBUG(1, (__location__ ": ads_keytab_add_entry() "
785                                   "failed while adding UPN (%s)\n", upn));
786                         goto done;
787                 }
788         }
789
790         /* Now loop through the keytab and update any other existing entries */
791         kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
792         if (kvno == (krb5_kvno)-1) {
793                 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
794                           "determine the system's kvno.\n"));
795                 goto done;
796         }
797
798         DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
799                   "and update.\n"));
800
801         ret = ads_keytab_open(context, &keytab);
802         if (ret != 0) {
803                 goto done;
804         }
805
806         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
807         if (ret != KRB5_KT_END && ret != ENOENT ) {
808                 while ((ret = krb5_kt_next_entry(context, keytab,
809                                                  &kt_entry, &cursor)) == 0) {
810                         smb_krb5_kt_free_entry(context, &kt_entry);
811                         ZERO_STRUCT(kt_entry);
812                         found++;
813                 }
814         }
815         krb5_kt_end_seq_get(context, keytab, &cursor);
816         ZERO_STRUCT(cursor);
817
818         /*
819          * Hmmm. There is no "rewind" function for the keytab. This means we
820          * have a race condition where someone else could add entries after
821          * we've counted them. Re-open asap to minimise the race. JRA.
822          */
823         DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
824         if (!found) {
825                 goto done;
826         }
827
828         oldEntries = talloc_zero_array(frame, char *, found + 1);
829         if (!oldEntries) {
830                 DEBUG(1, (__location__ ": Failed to allocate space to store "
831                           "the old keytab entries (talloc failed?).\n"));
832                 ret = -1;
833                 goto done;
834         }
835
836         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
837         if (ret == KRB5_KT_END || ret == ENOENT) {
838                 krb5_kt_end_seq_get(context, keytab, &cursor);
839                 ZERO_STRUCT(cursor);
840                 goto done;
841         }
842
843         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
844                 if (kt_entry.vno != kvno) {
845                         char *ktprinc = NULL;
846                         char *p;
847
848                         /* This returns a malloc'ed string in ktprinc. */
849                         ret = smb_krb5_unparse_name(oldEntries, context,
850                                                     kt_entry.principal,
851                                                     &ktprinc);
852                         if (ret) {
853                                 DEBUG(1, (__location__
854                                          ": smb_krb5_unparse_name failed "
855                                          "(%s)\n", error_message(ret)));
856                                 goto done;
857                         }
858                         /*
859                          * From looking at the krb5 source they don't seem to
860                          * take locale or mb strings into account.
861                          * Maybe this is because they assume utf8 ?
862                          * In this case we may need to convert from utf8 to
863                          * mb charset here ? JRA.
864                          */
865                         p = strchr_m(ktprinc, '@');
866                         if (p) {
867                                 *p = '\0';
868                         }
869
870                         p = strchr_m(ktprinc, '/');
871                         if (p) {
872                                 *p = '\0';
873                         }
874                         for (i = 0; i < found; i++) {
875                                 if (!oldEntries[i]) {
876                                         oldEntries[i] = ktprinc;
877                                         break;
878                                 }
879                                 if (!strcmp(oldEntries[i], ktprinc)) {
880                                         TALLOC_FREE(ktprinc);
881                                         break;
882                                 }
883                         }
884                         if (i == found) {
885                                 TALLOC_FREE(ktprinc);
886                         }
887                 }
888                 smb_krb5_kt_free_entry(context, &kt_entry);
889                 ZERO_STRUCT(kt_entry);
890         }
891         krb5_kt_end_seq_get(context, keytab, &cursor);
892         ZERO_STRUCT(cursor);
893
894         ret = 0;
895         for (i = 0; oldEntries[i]; i++) {
896                 ret |= ads_keytab_add_entry(ads, oldEntries[i], false);
897                 TALLOC_FREE(oldEntries[i]);
898         }
899
900 done:
901         TALLOC_FREE(oldEntries);
902         TALLOC_FREE(frame);
903
904         if (context) {
905                 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
906                         smb_krb5_kt_free_entry(context, &kt_entry);
907                 }
908                 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
909                         krb5_kt_end_seq_get(context, keytab, &cursor);
910                 }
911                 if (keytab) {
912                         krb5_kt_close(context, keytab);
913                 }
914                 krb5_free_context(context);
915         }
916         return ret;
917 }
918
919 #endif /* HAVE_ADS */
920
921 /**********************************************************************
922  List system keytab.
923 ***********************************************************************/
924
925 int ads_keytab_list(const char *keytab_name)
926 {
927         krb5_error_code ret = 0;
928         krb5_context context = NULL;
929         krb5_keytab keytab = NULL;
930         krb5_kt_cursor cursor;
931         krb5_keytab_entry kt_entry;
932
933         ZERO_STRUCT(kt_entry);
934         ZERO_STRUCT(cursor);
935
936         ret = smb_krb5_init_context_common(&context);
937         if (ret) {
938                 DBG_ERR("kerberos init context failed (%s)\n",
939                         error_message(ret));
940                 return ret;
941         }
942
943         if (keytab_name == NULL) {
944 #ifdef HAVE_ADS
945                 ret = ads_keytab_open(context, &keytab);
946 #else
947                 ret = ENOENT;
948 #endif
949         } else {
950                 ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
951         }
952         if (ret) {
953                 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
954                           error_message(ret)));
955                 goto out;
956         }
957
958         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
959         if (ret) {
960                 ZERO_STRUCT(cursor);
961                 goto out;
962         }
963
964         printf("Vno  Type                                        Principal\n");
965
966         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
967
968                 char *princ_s = NULL;
969                 char *etype_s = NULL;
970                 krb5_enctype enctype = 0;
971
972                 ret = smb_krb5_unparse_name(talloc_tos(), context,
973                                             kt_entry.principal, &princ_s);
974                 if (ret) {
975                         goto out;
976                 }
977
978                 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
979
980                 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
981                 if (ret &&
982                     (asprintf(&etype_s, "UNKNOWN: %d", enctype) == -1)) {
983                         TALLOC_FREE(princ_s);
984                         goto out;
985                 }
986
987                 printf("%3d  %-43s %s\n", kt_entry.vno, etype_s, princ_s);
988
989                 TALLOC_FREE(princ_s);
990                 SAFE_FREE(etype_s);
991
992                 ret = smb_krb5_kt_free_entry(context, &kt_entry);
993                 if (ret) {
994                         goto out;
995                 }
996         }
997
998         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
999         if (ret) {
1000                 goto out;
1001         }
1002
1003         /* Ensure we don't double free. */
1004         ZERO_STRUCT(kt_entry);
1005         ZERO_STRUCT(cursor);
1006 out:
1007
1008         if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
1009                 smb_krb5_kt_free_entry(context, &kt_entry);
1010         }
1011         if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
1012                 krb5_kt_end_seq_get(context, keytab, &cursor);
1013         }
1014
1015         if (keytab) {
1016                 krb5_kt_close(context, keytab);
1017         }
1018         if (context) {
1019                 krb5_free_context(context);
1020         }
1021         return ret;
1022 }
1023
1024 #endif /* HAVE_KRB5 */