s3:libads: Correctly handle the keytab kerberos methods
[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         for (i = 0; enctypes[i]; i++) {
241                 salt_princ_s = kerberos_fetch_salt_princ_for_host_princ(context,
242                                                                         princ_s,
243                                                                         enctypes[i]);
244
245                 /* add the fqdn principal to the keytab */
246                 ret = smb_krb5_kt_add_entry(context,
247                                             keytab,
248                                             kvno,
249                                             princ_s,
250                                             salt_princ_s,
251                                             enctypes[i],
252                                             &password,
253                                             false,
254                                             false);
255                 if (ret) {
256                         DEBUG(1, (__location__ ": Failed to add entry to keytab\n"));
257                         SAFE_FREE(salt_princ_s);
258                         goto out;
259                 }
260
261                 /* add the short principal name if we have one */
262                 if (short_princ_s) {
263                         ret = smb_krb5_kt_add_entry(context,
264                                                     keytab,
265                                                     kvno,
266                                                     short_princ_s,
267                                                     salt_princ_s,
268                                                     enctypes[i],
269                                                     &password,
270                                                     false,
271                                                     false);
272                         if (ret) {
273                                 DEBUG(1, (__location__
274                                           ": Failed to add short entry to keytab\n"));
275                                 SAFE_FREE(salt_princ_s);
276                                 goto out;
277                         }
278                 }
279                 SAFE_FREE(salt_princ_s);
280         }
281
282 out:
283         TALLOC_FREE(tmpctx);
284
285         if (keytab) {
286                 krb5_kt_close(context, keytab);
287         }
288         if (context) {
289                 krb5_free_context(context);
290         }
291         return (int)ret;
292 }
293
294 /**********************************************************************
295  Flushes all entries from the system keytab.
296 ***********************************************************************/
297
298 int ads_keytab_flush(ADS_STRUCT *ads)
299 {
300         krb5_error_code ret = 0;
301         krb5_context context = NULL;
302         krb5_keytab keytab = NULL;
303         krb5_kvno kvno;
304         ADS_STATUS aderr;
305
306         initialize_krb5_error_table();
307         ret = krb5_init_context(&context);
308         if (ret) {
309                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
310                           error_message(ret)));
311                 return ret;
312         }
313
314         ret = ads_keytab_open(context, &keytab);
315         if (ret != 0) {
316                 goto out;
317         }
318
319         kvno = (krb5_kvno)ads_get_machine_kvno(ads, lp_netbios_name());
320         if (kvno == -1) {
321                 /* -1 indicates a failure */
322                 DEBUG(1, (__location__ ": Error determining the kvno.\n"));
323                 goto out;
324         }
325
326         /* Seek and delete old keytab entries */
327         ret = smb_krb5_kt_seek_and_delete_old_entries(context,
328                                                       keytab,
329                                                       kvno,
330                                                       ENCTYPE_NULL,
331                                                       NULL,
332                                                       NULL,
333                                                       true,
334                                                       false);
335         if (ret) {
336                 goto out;
337         }
338
339         aderr = ads_clear_service_principal_names(ads, lp_netbios_name());
340         if (!ADS_ERR_OK(aderr)) {
341                 DEBUG(1, (__location__ ": Error while clearing service "
342                           "principal listings in LDAP.\n"));
343                 goto out;
344         }
345
346 out:
347         if (keytab) {
348                 krb5_kt_close(context, keytab);
349         }
350         if (context) {
351                 krb5_free_context(context);
352         }
353         return ret;
354 }
355
356 /**********************************************************************
357  Adds all the required service principals to the system keytab.
358 ***********************************************************************/
359
360 int ads_keytab_create_default(ADS_STRUCT *ads)
361 {
362         krb5_error_code ret = 0;
363         krb5_context context = NULL;
364         krb5_keytab keytab = NULL;
365         krb5_kt_cursor cursor = {0};
366         krb5_keytab_entry kt_entry = {0};
367         krb5_kvno kvno;
368         size_t found = 0;
369         char *sam_account_name, *upn;
370         char **oldEntries = NULL, *princ_s[26];
371         TALLOC_CTX *frame;
372         char *machine_name;
373         char **spn_array;
374         size_t num_spns;
375         size_t i;
376         ADS_STATUS status;
377
378         ZERO_STRUCT(kt_entry);
379         ZERO_STRUCT(cursor);
380
381         frame = talloc_stackframe();
382         if (frame == NULL) {
383                 ret = -1;
384                 goto done;
385         }
386
387         status = ads_get_service_principal_names(frame,
388                                                  ads,
389                                                  lp_netbios_name(),
390                                                  &spn_array,
391                                                  &num_spns);
392         if (!ADS_ERR_OK(status)) {
393                 ret = -1;
394                 goto done;
395         }
396
397         for (i = 0; i < num_spns; i++) {
398                 char *srv_princ;
399                 char *p;
400
401                 srv_princ = strlower_talloc(frame, spn_array[i]);
402                 if (srv_princ == NULL) {
403                         ret = -1;
404                         goto done;
405                 }
406
407                 p = strchr_m(srv_princ, '/');
408                 if (p == NULL) {
409                         continue;
410                 }
411                 p[0] = '\0';
412
413                 /* Add the SPNs found on the DC */
414                 ret = ads_keytab_add_entry(ads, srv_princ);
415                 if (ret != 0) {
416                         DEBUG(1, ("ads_keytab_add_entry failed while "
417                                   "adding '%s' principal.\n",
418                                   spn_array[i]));
419                         goto done;
420                 }
421         }
422
423 #if 0   /* don't create the CIFS/... keytab entries since no one except smbd
424            really needs them and we will fall back to verifying against
425            secrets.tdb */
426
427         ret = ads_keytab_add_entry(ads, "cifs"));
428         if (ret != 0 ) {
429                 DEBUG(1, (__location__ ": ads_keytab_add_entry failed while "
430                           "adding 'cifs'.\n"));
431                 return ret;
432         }
433 #endif
434
435         memset(princ_s, '\0', sizeof(princ_s));
436
437         initialize_krb5_error_table();
438         ret = krb5_init_context(&context);
439         if (ret) {
440                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
441                           error_message(ret)));
442                 goto done;
443         }
444
445         machine_name = talloc_strdup(frame, lp_netbios_name());
446         if (!machine_name) {
447                 ret = -1;
448                 goto done;
449         }
450
451         /* now add the userPrincipalName and sAMAccountName entries */
452         sam_account_name = ads_get_samaccountname(ads, frame, machine_name);
453         if (!sam_account_name) {
454                 DEBUG(0, (__location__ ": unable to determine machine "
455                           "account's name in AD!\n"));
456                 ret = -1;
457                 goto done;
458         }
459
460         /* upper case the sAMAccountName to make it easier for apps to
461            know what case to use in the keytab file */
462         if (!strupper_m(sam_account_name)) {
463                 ret = -1;
464                 goto done;
465         }
466
467         ret = ads_keytab_add_entry(ads, sam_account_name);
468         if (ret != 0) {
469                 DEBUG(1, (__location__ ": ads_keytab_add_entry() failed "
470                           "while adding sAMAccountName (%s)\n",
471                           sam_account_name));
472                 goto done;
473         }
474
475         /* remember that not every machine account will have a upn */
476         upn = ads_get_upn(ads, frame, machine_name);
477         if (upn) {
478                 ret = ads_keytab_add_entry(ads, upn);
479                 if (ret != 0) {
480                         DEBUG(1, (__location__ ": ads_keytab_add_entry() "
481                                   "failed while adding UPN (%s)\n", upn));
482                         goto done;
483                 }
484         }
485
486         /* Now loop through the keytab and update any other existing entries */
487         kvno = (krb5_kvno)ads_get_machine_kvno(ads, machine_name);
488         if (kvno == (krb5_kvno)-1) {
489                 DEBUG(1, (__location__ ": ads_get_machine_kvno() failed to "
490                           "determine the system's kvno.\n"));
491                 goto done;
492         }
493
494         DEBUG(3, (__location__ ": Searching for keytab entries to preserve "
495                   "and update.\n"));
496
497         ret = ads_keytab_open(context, &keytab);
498         if (ret != 0) {
499                 goto done;
500         }
501
502         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
503         if (ret != KRB5_KT_END && ret != ENOENT ) {
504                 while ((ret = krb5_kt_next_entry(context, keytab,
505                                                  &kt_entry, &cursor)) == 0) {
506                         smb_krb5_kt_free_entry(context, &kt_entry);
507                         ZERO_STRUCT(kt_entry);
508                         found++;
509                 }
510         }
511         krb5_kt_end_seq_get(context, keytab, &cursor);
512         ZERO_STRUCT(cursor);
513
514         /*
515          * Hmmm. There is no "rewind" function for the keytab. This means we
516          * have a race condition where someone else could add entries after
517          * we've counted them. Re-open asap to minimise the race. JRA.
518          */
519         DEBUG(3, (__location__ ": Found %zd entries in the keytab.\n", found));
520         if (!found) {
521                 goto done;
522         }
523
524         oldEntries = talloc_zero_array(frame, char *, found + 1);
525         if (!oldEntries) {
526                 DEBUG(1, (__location__ ": Failed to allocate space to store "
527                           "the old keytab entries (talloc failed?).\n"));
528                 ret = -1;
529                 goto done;
530         }
531
532         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
533         if (ret == KRB5_KT_END || ret == ENOENT) {
534                 krb5_kt_end_seq_get(context, keytab, &cursor);
535                 ZERO_STRUCT(cursor);
536                 goto done;
537         }
538
539         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
540                 if (kt_entry.vno != kvno) {
541                         char *ktprinc = NULL;
542                         char *p;
543
544                         /* This returns a malloc'ed string in ktprinc. */
545                         ret = smb_krb5_unparse_name(oldEntries, context,
546                                                     kt_entry.principal,
547                                                     &ktprinc);
548                         if (ret) {
549                                 DEBUG(1, (__location__
550                                          ": smb_krb5_unparse_name failed "
551                                          "(%s)\n", error_message(ret)));
552                                 goto done;
553                         }
554                         /*
555                          * From looking at the krb5 source they don't seem to
556                          * take locale or mb strings into account.
557                          * Maybe this is because they assume utf8 ?
558                          * In this case we may need to convert from utf8 to
559                          * mb charset here ? JRA.
560                          */
561                         p = strchr_m(ktprinc, '@');
562                         if (p) {
563                                 *p = '\0';
564                         }
565
566                         p = strchr_m(ktprinc, '/');
567                         if (p) {
568                                 *p = '\0';
569                         }
570                         for (i = 0; i < found; i++) {
571                                 if (!oldEntries[i]) {
572                                         oldEntries[i] = ktprinc;
573                                         break;
574                                 }
575                                 if (!strcmp(oldEntries[i], ktprinc)) {
576                                         TALLOC_FREE(ktprinc);
577                                         break;
578                                 }
579                         }
580                         if (i == found) {
581                                 TALLOC_FREE(ktprinc);
582                         }
583                 }
584                 smb_krb5_kt_free_entry(context, &kt_entry);
585                 ZERO_STRUCT(kt_entry);
586         }
587         krb5_kt_end_seq_get(context, keytab, &cursor);
588         ZERO_STRUCT(cursor);
589
590         ret = 0;
591         for (i = 0; oldEntries[i]; i++) {
592                 ret |= ads_keytab_add_entry(ads, oldEntries[i]);
593                 TALLOC_FREE(oldEntries[i]);
594         }
595
596 done:
597         TALLOC_FREE(oldEntries);
598         TALLOC_FREE(frame);
599
600         if (context) {
601                 if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
602                         smb_krb5_kt_free_entry(context, &kt_entry);
603                 }
604                 if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
605                         krb5_kt_end_seq_get(context, keytab, &cursor);
606                 }
607                 if (keytab) {
608                         krb5_kt_close(context, keytab);
609                 }
610                 krb5_free_context(context);
611         }
612         return ret;
613 }
614
615 #endif /* HAVE_ADS */
616
617 /**********************************************************************
618  List system keytab.
619 ***********************************************************************/
620
621 int ads_keytab_list(const char *keytab_name)
622 {
623         krb5_error_code ret = 0;
624         krb5_context context = NULL;
625         krb5_keytab keytab = NULL;
626         krb5_kt_cursor cursor;
627         krb5_keytab_entry kt_entry;
628
629         ZERO_STRUCT(kt_entry);
630         ZERO_STRUCT(cursor);
631
632         initialize_krb5_error_table();
633         ret = krb5_init_context(&context);
634         if (ret) {
635                 DEBUG(1, (__location__ ": could not krb5_init_context: %s\n",
636                           error_message(ret)));
637                 return ret;
638         }
639
640         ret = smb_krb5_kt_open(context, keytab_name, False, &keytab);
641         if (ret) {
642                 DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
643                           error_message(ret)));
644                 goto out;
645         }
646
647         ret = krb5_kt_start_seq_get(context, keytab, &cursor);
648         if (ret) {
649                 ZERO_STRUCT(cursor);
650                 goto out;
651         }
652
653         printf("Vno  Type                                        Principal\n");
654
655         while (krb5_kt_next_entry(context, keytab, &kt_entry, &cursor) == 0) {
656
657                 char *princ_s = NULL;
658                 char *etype_s = NULL;
659                 krb5_enctype enctype = 0;
660
661                 ret = smb_krb5_unparse_name(talloc_tos(), context,
662                                             kt_entry.principal, &princ_s);
663                 if (ret) {
664                         goto out;
665                 }
666
667                 enctype = smb_krb5_kt_get_enctype_from_entry(&kt_entry);
668
669                 ret = smb_krb5_enctype_to_string(context, enctype, &etype_s);
670                 if (ret &&
671                     (asprintf(&etype_s, "UNKNOWN: %d\n", enctype) == -1)) {
672                         TALLOC_FREE(princ_s);
673                         goto out;
674                 }
675
676                 printf("%3d  %-43s %s\n", kt_entry.vno, etype_s, princ_s);
677
678                 TALLOC_FREE(princ_s);
679                 SAFE_FREE(etype_s);
680
681                 ret = smb_krb5_kt_free_entry(context, &kt_entry);
682                 if (ret) {
683                         goto out;
684                 }
685         }
686
687         ret = krb5_kt_end_seq_get(context, keytab, &cursor);
688         if (ret) {
689                 goto out;
690         }
691
692         /* Ensure we don't double free. */
693         ZERO_STRUCT(kt_entry);
694         ZERO_STRUCT(cursor);
695 out:
696
697         if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
698                 smb_krb5_kt_free_entry(context, &kt_entry);
699         }
700         if (!all_zero((uint8_t *)&cursor, sizeof(cursor)) && keytab) {
701                 krb5_kt_end_seq_get(context, keytab, &cursor);
702         }
703
704         if (keytab) {
705                 krb5_kt_close(context, keytab);
706         }
707         if (context) {
708                 krb5_free_context(context);
709         }
710         return ret;
711 }
712
713 #endif /* HAVE_KRB5 */