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