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