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