Line data Source code
1 : /*
2 : * GSSAPI Security Extensions
3 : * Krb5 helpers
4 : * Copyright (C) Simo Sorce 2010.
5 : *
6 : * This program is free software; you can redistribute it and/or modify
7 : * it under the terms of the GNU General Public License as published by
8 : * the Free Software Foundation; either version 3 of the License, or
9 : * (at your option) any later version.
10 : *
11 : * This program is distributed in the hope that it will be useful,
12 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : * GNU General Public License for more details.
15 : *
16 : * You should have received a copy of the GNU General Public License
17 : * along with this program; if not, see <http://www.gnu.org/licenses/>.
18 : */
19 :
20 : #include "includes.h"
21 : #include "smb_krb5.h"
22 : #include "secrets.h"
23 : #include "librpc/gen_ndr/secrets.h"
24 : #include "gse_krb5.h"
25 : #include "lib/param/loadparm.h"
26 : #include "libads/kerberos_proto.h"
27 : #include "lib/util/string_wrappers.h"
28 :
29 : #ifdef HAVE_KRB5
30 :
31 0 : static krb5_error_code flush_keytab(krb5_context krbctx, krb5_keytab keytab)
32 : {
33 0 : krb5_error_code ret;
34 0 : krb5_kt_cursor kt_cursor;
35 0 : krb5_keytab_entry kt_entry;
36 :
37 0 : ZERO_STRUCT(kt_entry);
38 :
39 0 : ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
40 0 : if (ret != 0) {
41 0 : return ret;
42 : }
43 :
44 0 : ret = krb5_kt_next_entry(krbctx, keytab, &kt_entry, &kt_cursor);
45 0 : while (ret == 0) {
46 :
47 : /* we need to close and reopen enumeration because we modify
48 : * the keytab */
49 0 : ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
50 0 : if (ret != 0) {
51 0 : DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
52 : "failed (%s)\n", error_message(ret)));
53 0 : goto out;
54 : }
55 :
56 : /* remove the entry */
57 0 : ret = krb5_kt_remove_entry(krbctx, keytab, &kt_entry);
58 0 : if (ret != 0) {
59 0 : DEBUG(1, (__location__ ": krb5_kt_remove_entry() "
60 : "failed (%s)\n", error_message(ret)));
61 0 : goto out;
62 : }
63 0 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
64 0 : ZERO_STRUCT(kt_entry);
65 :
66 : /* now reopen */
67 0 : ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
68 0 : if (ret != 0) {
69 0 : DEBUG(1, (__location__ ": krb5_kt_start_seq() failed "
70 : "(%s)\n", error_message(ret)));
71 0 : goto out;
72 : }
73 :
74 0 : ret = krb5_kt_next_entry(krbctx, keytab,
75 : &kt_entry, &kt_cursor);
76 : }
77 :
78 0 : if (ret != KRB5_KT_END && ret != ENOENT) {
79 0 : DEBUG(1, (__location__ ": flushing keytab we got [%s]!\n",
80 : error_message(ret)));
81 : }
82 :
83 0 : ret = krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
84 0 : if (ret != 0) {
85 0 : DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
86 : "failed (%s)\n", error_message(ret)));
87 0 : goto out;
88 : }
89 0 : ret = 0;
90 :
91 0 : out:
92 0 : return ret;
93 : }
94 :
95 9274 : static krb5_error_code fill_keytab_from_password(krb5_context krbctx,
96 : krb5_keytab keytab,
97 : krb5_principal princ,
98 : krb5_kvno vno,
99 : struct secrets_domain_info1_password *pw)
100 : {
101 0 : krb5_error_code ret;
102 0 : krb5_enctype *enctypes;
103 0 : uint16_t i;
104 :
105 9274 : ret = smb_krb5_get_allowed_etypes(krbctx, &enctypes);
106 9274 : if (ret) {
107 0 : DEBUG(1, (__location__
108 : ": Can't determine permitted enctypes!\n"));
109 0 : return ret;
110 : }
111 :
112 37096 : for (i = 0; i < pw->num_keys; i++) {
113 : krb5_keytab_entry kt_entry;
114 27822 : krb5_keyblock *key = NULL;
115 : unsigned int ei;
116 27822 : bool found_etype = false;
117 :
118 78944 : for (ei=0; enctypes[ei] != 0; ei++) {
119 78944 : if ((uint32_t)enctypes[ei] != pw->keys[i].keytype) {
120 51122 : continue;
121 : }
122 :
123 27822 : found_etype = true;
124 27822 : break;
125 : }
126 :
127 27822 : if (!found_etype) {
128 0 : continue;
129 : }
130 :
131 27822 : ZERO_STRUCT(kt_entry);
132 27822 : kt_entry.principal = princ;
133 27822 : kt_entry.vno = vno;
134 :
135 27822 : key = KRB5_KT_KEY(&kt_entry);
136 27822 : KRB5_KEY_TYPE(key) = pw->keys[i].keytype;
137 27822 : KRB5_KEY_DATA(key) = pw->keys[i].value.data;
138 27822 : KRB5_KEY_LENGTH(key) = pw->keys[i].value.length;
139 :
140 27822 : ret = krb5_kt_add_entry(krbctx, keytab, &kt_entry);
141 27822 : if (ret) {
142 0 : DEBUG(1, (__location__ ": Failed to add entry to "
143 : "keytab for enctype %d (error: %s)\n",
144 : (unsigned)pw->keys[i].keytype,
145 : error_message(ret)));
146 0 : goto out;
147 : }
148 : }
149 :
150 9274 : ret = 0;
151 :
152 9274 : out:
153 9274 : krb5_free_enctypes(krbctx, enctypes);
154 9274 : return ret;
155 : }
156 :
157 : #define SRV_MEM_KEYTAB_NAME "MEMORY:cifs_srv_keytab"
158 : #define CLEARTEXT_PRIV_ENCTYPE -99
159 :
160 4393 : static krb5_error_code fill_mem_keytab_from_secrets(krb5_context krbctx,
161 : krb5_keytab *keytab)
162 : {
163 4393 : TALLOC_CTX *frame = talloc_stackframe();
164 0 : krb5_error_code ret, ret2;
165 4393 : const char *domain = lp_workgroup();
166 4393 : struct secrets_domain_info1 *info = NULL;
167 4393 : const char *realm = NULL;
168 4393 : const DATA_BLOB *ct = NULL;
169 0 : krb5_kt_cursor kt_cursor;
170 0 : krb5_keytab_entry kt_entry;
171 4393 : krb5_principal princ = NULL;
172 4393 : krb5_kvno kvno = 0; /* FIXME: fetch current vno from KDC ? */
173 0 : NTSTATUS status;
174 :
175 4393 : if (!secrets_init()) {
176 0 : DEBUG(1, (__location__ ": secrets_init failed\n"));
177 0 : TALLOC_FREE(frame);
178 0 : return KRB5_CONFIG_CANTOPEN;
179 : }
180 :
181 4393 : status = secrets_fetch_or_upgrade_domain_info(domain,
182 : frame,
183 : &info);
184 4393 : if (!NT_STATUS_IS_OK(status)) {
185 4 : DBG_WARNING("secrets_fetch_or_upgrade_domain_info(%s) - %s\n",
186 : domain, nt_errstr(status));
187 4 : TALLOC_FREE(frame);
188 4 : return KRB5_LIBOS_CANTREADPWD;
189 : }
190 4389 : ct = &info->password->cleartext_blob;
191 :
192 4389 : if (info->domain_info.dns_domain.string != NULL) {
193 4389 : realm = strupper_talloc(frame,
194 4389 : info->domain_info.dns_domain.string);
195 4389 : if (realm == NULL) {
196 0 : TALLOC_FREE(frame);
197 0 : return ENOMEM;
198 : }
199 : }
200 :
201 4389 : ZERO_STRUCT(kt_entry);
202 4389 : ZERO_STRUCT(kt_cursor);
203 :
204 : /* check if the keytab already has any entry */
205 4389 : ret = krb5_kt_start_seq_get(krbctx, *keytab, &kt_cursor);
206 4389 : if (ret != 0) {
207 0 : goto out;
208 : }
209 :
210 : /* check if we have our special enctype used to hold
211 : * the clear text password. If so, check it out so that
212 : * we can verify if the keytab needs to be upgraded */
213 4485 : while ((ret = krb5_kt_next_entry(krbctx, *keytab,
214 4485 : &kt_entry, &kt_cursor)) == 0) {
215 118 : if (smb_krb5_kt_get_enctype_from_entry(&kt_entry) ==
216 : CLEARTEXT_PRIV_ENCTYPE) {
217 22 : break;
218 : }
219 96 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
220 96 : ZERO_STRUCT(kt_entry);
221 : }
222 :
223 4389 : ret2 = krb5_kt_end_seq_get(krbctx, *keytab, &kt_cursor);
224 4389 : if (ret2 != 0) {
225 0 : ret = ret2;
226 0 : DEBUG(1, (__location__ ": krb5_kt_end_seq_get() "
227 : "failed (%s)\n", error_message(ret)));
228 0 : goto out;
229 : }
230 :
231 4389 : if (ret != 0 && ret != KRB5_KT_END && ret != ENOENT ) {
232 : /* Error parsing keytab */
233 0 : DEBUG(1, (__location__ ": Failed to parse memory "
234 : "keytab!\n"));
235 0 : goto out;
236 : }
237 :
238 4389 : if (ret == 0) {
239 : /* found private entry,
240 : * check if keytab is up to date */
241 :
242 44 : if ((ct->length == KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry))) &&
243 22 : (mem_equal_const_time(KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)),
244 22 : ct->data, ct->length))) {
245 : /* keytab is already up to date, return */
246 22 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
247 22 : goto out;
248 : }
249 :
250 0 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
251 0 : ZERO_STRUCT(kt_entry);
252 :
253 :
254 : /* flush keytab, we need to regen it */
255 0 : ret = flush_keytab(krbctx, *keytab);
256 0 : if (ret) {
257 0 : DEBUG(1, (__location__ ": Failed to flush "
258 : "memory keytab!\n"));
259 0 : goto out;
260 : }
261 : }
262 :
263 : /* keytab is not up to date, fill it up */
264 :
265 4367 : ret = smb_krb5_make_principal(krbctx, &princ, realm,
266 4367 : info->account_name, NULL);
267 4367 : if (ret) {
268 0 : DEBUG(1, (__location__ ": Failed to get host principal!\n"));
269 0 : goto out;
270 : }
271 :
272 4367 : ret = fill_keytab_from_password(krbctx, *keytab,
273 : princ, kvno,
274 4367 : info->password);
275 4367 : if (ret) {
276 0 : DBG_WARNING("fill_keytab_from_password() failed for "
277 : "info->password.\n.");
278 0 : goto out;
279 : }
280 :
281 4367 : if (info->old_password != NULL) {
282 2627 : ret = fill_keytab_from_password(krbctx, *keytab,
283 : princ, kvno - 1,
284 2627 : info->old_password);
285 2627 : if (ret) {
286 0 : DBG_WARNING("fill_keytab_from_password() failed for "
287 : "info->old_password.\n.");
288 0 : goto out;
289 : }
290 : }
291 :
292 4367 : if (info->older_password != NULL) {
293 2280 : ret = fill_keytab_from_password(krbctx, *keytab,
294 : princ, kvno - 2,
295 2280 : info->older_password);
296 2280 : if (ret) {
297 0 : DBG_WARNING("fill_keytab_from_password() failed for "
298 : "info->older_password.\n.");
299 0 : goto out;
300 : }
301 : }
302 :
303 4367 : if (info->next_change != NULL) {
304 0 : ret = fill_keytab_from_password(krbctx, *keytab,
305 : princ, kvno - 3,
306 0 : info->next_change->password);
307 0 : if (ret) {
308 0 : DBG_WARNING("fill_keytab_from_password() failed for "
309 : "info->next_change->password.\n.");
310 0 : goto out;
311 : }
312 : }
313 :
314 : /* add our private enctype + cleartext password so that we can
315 : * update the keytab if secrets change later on */
316 4367 : ZERO_STRUCT(kt_entry);
317 4367 : kt_entry.principal = princ;
318 4367 : kt_entry.vno = 0;
319 :
320 4367 : KRB5_KEY_TYPE(KRB5_KT_KEY(&kt_entry)) = CLEARTEXT_PRIV_ENCTYPE;
321 4367 : KRB5_KEY_LENGTH(KRB5_KT_KEY(&kt_entry)) = ct->length;
322 4367 : KRB5_KEY_DATA(KRB5_KT_KEY(&kt_entry)) = ct->data;
323 :
324 4367 : ret = krb5_kt_add_entry(krbctx, *keytab, &kt_entry);
325 4367 : if (ret) {
326 0 : DEBUG(1, (__location__ ": Failed to add entry to "
327 : "keytab for private enctype (%d) (error: %s)\n",
328 : CLEARTEXT_PRIV_ENCTYPE, error_message(ret)));
329 0 : goto out;
330 : }
331 :
332 4367 : ret = 0;
333 :
334 4389 : out:
335 :
336 4389 : if (princ) {
337 4367 : krb5_free_principal(krbctx, princ);
338 : }
339 :
340 4389 : TALLOC_FREE(frame);
341 4389 : return ret;
342 : }
343 :
344 0 : static krb5_error_code fill_mem_keytab_from_system_keytab(krb5_context krbctx,
345 : krb5_keytab *mkeytab)
346 : {
347 0 : krb5_error_code ret = 0;
348 0 : krb5_keytab keytab = NULL;
349 0 : krb5_kt_cursor kt_cursor = { 0, };
350 0 : krb5_keytab_entry kt_entry = { 0, };
351 0 : char *valid_princ_formats[7] = { NULL, NULL, NULL,
352 : NULL, NULL, NULL, NULL };
353 0 : char *entry_princ_s = NULL;
354 0 : fstring my_name, my_fqdn;
355 0 : unsigned i;
356 0 : int err;
357 :
358 : /* Generate the list of principal names which we expect
359 : * clients might want to use for authenticating to the file
360 : * service. We allow name$,{host,cifs}/{name,fqdn,name.REALM}. */
361 :
362 0 : fstrcpy(my_name, lp_netbios_name());
363 :
364 0 : my_fqdn[0] = '\0';
365 0 : name_to_fqdn(my_fqdn, lp_netbios_name());
366 :
367 0 : err = asprintf(&valid_princ_formats[0],
368 : "%s$@%s", my_name, lp_realm());
369 0 : if (err == -1) {
370 0 : ret = ENOMEM;
371 0 : goto out;
372 : }
373 0 : err = asprintf(&valid_princ_formats[1],
374 : "host/%s@%s", my_name, lp_realm());
375 0 : if (err == -1) {
376 0 : ret = ENOMEM;
377 0 : goto out;
378 : }
379 0 : err = asprintf(&valid_princ_formats[2],
380 : "host/%s@%s", my_fqdn, lp_realm());
381 0 : if (err == -1) {
382 0 : ret = ENOMEM;
383 0 : goto out;
384 : }
385 0 : err = asprintf(&valid_princ_formats[3],
386 : "host/%s.%s@%s", my_name, lp_realm(), lp_realm());
387 0 : if (err == -1) {
388 0 : ret = ENOMEM;
389 0 : goto out;
390 : }
391 0 : err = asprintf(&valid_princ_formats[4],
392 : "cifs/%s@%s", my_name, lp_realm());
393 0 : if (err == -1) {
394 0 : ret = ENOMEM;
395 0 : goto out;
396 : }
397 0 : err = asprintf(&valid_princ_formats[5],
398 : "cifs/%s@%s", my_fqdn, lp_realm());
399 0 : if (err == -1) {
400 0 : ret = ENOMEM;
401 0 : goto out;
402 : }
403 0 : err = asprintf(&valid_princ_formats[6],
404 : "cifs/%s.%s@%s", my_name, lp_realm(), lp_realm());
405 0 : if (err == -1) {
406 0 : ret = ENOMEM;
407 0 : goto out;
408 : }
409 :
410 0 : ret = smb_krb5_kt_open_relative(krbctx, NULL, false, &keytab);
411 0 : if (ret) {
412 0 : DEBUG(1, ("smb_krb5_kt_open failed (%s)\n",
413 : error_message(ret)));
414 0 : goto out;
415 : }
416 :
417 : /*
418 : * Iterate through the keytab. For each key, if the principal
419 : * name case-insensitively matches one of the allowed formats,
420 : * copy it to the memory keytab.
421 : */
422 :
423 0 : ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
424 0 : if (ret) {
425 0 : DEBUG(1, (__location__ ": krb5_kt_start_seq_get failed (%s)\n",
426 : error_message(ret)));
427 : /*
428 : * krb5_kt_start_seq_get() may leaves bogus data
429 : * in kt_cursor. And we want to use the all_zero()
430 : * logic below.
431 : *
432 : * See bug #10490
433 : */
434 0 : ZERO_STRUCT(kt_cursor);
435 0 : goto out;
436 : }
437 :
438 0 : while ((krb5_kt_next_entry(krbctx, keytab,
439 0 : &kt_entry, &kt_cursor) == 0)) {
440 0 : ret = smb_krb5_unparse_name(talloc_tos(), krbctx,
441 0 : kt_entry.principal,
442 : &entry_princ_s);
443 0 : if (ret) {
444 0 : DEBUG(1, (__location__ ": smb_krb5_unparse_name "
445 : "failed (%s)\n", error_message(ret)));
446 0 : goto out;
447 : }
448 :
449 0 : for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
450 :
451 0 : if (!strequal(entry_princ_s, valid_princ_formats[i])) {
452 0 : continue;
453 : }
454 :
455 0 : ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry);
456 0 : if (ret) {
457 0 : DEBUG(1, (__location__ ": smb_krb5_unparse_name "
458 : "failed (%s)\n", error_message(ret)));
459 0 : goto out;
460 : }
461 : }
462 :
463 : /* Free the name we parsed. */
464 0 : TALLOC_FREE(entry_princ_s);
465 :
466 : /* Free the entry we just read. */
467 0 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
468 0 : ZERO_STRUCT(kt_entry);
469 : }
470 0 : krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
471 :
472 0 : ZERO_STRUCT(kt_cursor);
473 :
474 0 : out:
475 :
476 0 : for (i = 0; i < ARRAY_SIZE(valid_princ_formats); i++) {
477 0 : SAFE_FREE(valid_princ_formats[i]);
478 : }
479 :
480 0 : TALLOC_FREE(entry_princ_s);
481 :
482 0 : if (!all_zero((uint8_t *)&kt_entry, sizeof(kt_entry))) {
483 0 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
484 : }
485 :
486 0 : if (!all_zero((uint8_t *)&kt_cursor, sizeof(kt_cursor)) && keytab) {
487 0 : krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
488 : }
489 :
490 0 : if (keytab) {
491 0 : krb5_kt_close(krbctx, keytab);
492 : }
493 :
494 0 : return ret;
495 : }
496 :
497 6 : static krb5_error_code fill_mem_keytab_from_dedicated_keytab(krb5_context krbctx,
498 : krb5_keytab *mkeytab)
499 : {
500 6 : krb5_error_code ret = 0;
501 6 : krb5_keytab keytab = NULL;
502 0 : krb5_kt_cursor kt_cursor;
503 0 : krb5_keytab_entry kt_entry;
504 :
505 6 : ret = smb_krb5_kt_open(krbctx, lp_dedicated_keytab_file(),
506 : false, &keytab);
507 6 : if (ret) {
508 0 : DEBUG(1, ("smb_krb5_kt_open of %s failed (%s)\n",
509 : lp_dedicated_keytab_file(),
510 : error_message(ret)));
511 0 : return ret;
512 : }
513 :
514 : /*
515 : * Copy the dedicated keyab to our in-memory keytab.
516 : */
517 :
518 6 : ret = krb5_kt_start_seq_get(krbctx, keytab, &kt_cursor);
519 6 : if (ret) {
520 6 : DEBUG(1, (__location__ ": krb5_kt_start_seq_get on %s "
521 : "failed (%s)\n",
522 : lp_dedicated_keytab_file(),
523 : error_message(ret)));
524 6 : goto out;
525 : }
526 :
527 0 : while ((krb5_kt_next_entry(krbctx, keytab,
528 0 : &kt_entry, &kt_cursor) == 0)) {
529 :
530 0 : ret = krb5_kt_add_entry(krbctx, *mkeytab, &kt_entry);
531 :
532 : /* Free the entry we just read. */
533 0 : smb_krb5_kt_free_entry(krbctx, &kt_entry);
534 :
535 0 : if (ret) {
536 0 : DEBUG(1, (__location__ ": smb_krb5_unparse_name "
537 : "failed (%s)\n", error_message(ret)));
538 0 : break;
539 : }
540 : }
541 0 : krb5_kt_end_seq_get(krbctx, keytab, &kt_cursor);
542 :
543 6 : out:
544 :
545 6 : krb5_kt_close(krbctx, keytab);
546 :
547 6 : return ret;
548 : }
549 :
550 4399 : krb5_error_code gse_krb5_get_server_keytab(krb5_context krbctx,
551 : krb5_keytab *keytab)
552 : {
553 4399 : krb5_error_code ret = 0;
554 4399 : krb5_error_code ret1 = 0;
555 4399 : krb5_error_code ret2 = 0;
556 :
557 4399 : *keytab = NULL;
558 :
559 : /* create memory keytab */
560 4399 : ret = krb5_kt_resolve(krbctx, SRV_MEM_KEYTAB_NAME, keytab);
561 4399 : if (ret) {
562 0 : DEBUG(1, (__location__ ": Failed to get memory "
563 : "keytab!\n"));
564 0 : return ret;
565 : }
566 :
567 4399 : switch (lp_kerberos_method()) {
568 4393 : default:
569 : case KERBEROS_VERIFY_SECRETS:
570 4393 : ret = fill_mem_keytab_from_secrets(krbctx, keytab);
571 4393 : break;
572 0 : case KERBEROS_VERIFY_SYSTEM_KEYTAB:
573 0 : ret = fill_mem_keytab_from_system_keytab(krbctx, keytab);
574 0 : break;
575 6 : case KERBEROS_VERIFY_DEDICATED_KEYTAB:
576 : /* just use whatever keytab is configured */
577 6 : ret = fill_mem_keytab_from_dedicated_keytab(krbctx, keytab);
578 6 : break;
579 0 : case KERBEROS_VERIFY_SECRETS_AND_KEYTAB:
580 0 : ret1 = fill_mem_keytab_from_secrets(krbctx, keytab);
581 0 : if (ret1) {
582 0 : DEBUG(3, (__location__ ": Warning! Unable to set mem "
583 : "keytab from secrets!\n"));
584 : }
585 : /* Now append system keytab keys too */
586 0 : ret2 = fill_mem_keytab_from_system_keytab(krbctx, keytab);
587 0 : if (ret2) {
588 0 : DEBUG(3, (__location__ ": Warning! Unable to set mem "
589 : "keytab from system keytab!\n"));
590 : }
591 0 : if (ret1 == 0 || ret2 == 0) {
592 0 : ret = 0;
593 : } else {
594 0 : ret = ret1;
595 : }
596 0 : break;
597 : }
598 :
599 4399 : if (ret) {
600 10 : krb5_kt_close(krbctx, *keytab);
601 10 : *keytab = NULL;
602 10 : DEBUG(1,("%s: Error! Unable to set mem keytab - %d\n",
603 : __location__, ret));
604 : }
605 :
606 4399 : return ret;
607 : }
608 :
609 : #endif /* HAVE_KRB5 */
|