Fix build with heimdal-gssapi. Bug 2501
[exim.git] / src / src / auths / heimdal_gssapi.c
CommitLineData
dde3daac
PP
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
f9ba5e22 5/* Copyright (c) University of Cambridge 1995 - 2018 */
dde3daac
PP
6/* See the file NOTICE for conditions of use and distribution. */
7
8/* Copyright (c) Twitter Inc 2012
9 Author: Phil Pennock <pdp@exim.org> */
f35771fe 10/* Copyright (c) Phil Pennock 2012 */
dde3daac 11
b245b4a5 12/* Interface to Heimdal library for GSSAPI authentication. */
dde3daac
PP
13
14/* Naming and rationale
15
16Sensibly, this integration would be deferred to a SASL library, but none
17of them appear to offer keytab file selection interfaces in their APIs. It
18might be that this driver only requires minor modification to work with MIT
19Kerberos.
20
21Heimdal provides a number of interfaces for various forms of authentication.
22As GS2 does not appear to provide keytab control interfaces either, we may
23end up supporting that too. It's possible that we could trivially expand to
24support NTLM support via Heimdal, etc. Rather than try to be too generic
25immediately, this driver is directly only supporting GSSAPI.
26
27Without rename, we could add an option for GS2 support in the future.
28*/
29
30/* Sources
31
32* mailcheck-imap (Perl, client-side, written by me years ago)
33* gsasl driver (GPL, server-side)
34* heimdal sources and man-pages, plus http://www.h5l.org/manual/
35* FreeBSD man-pages (very informative!)
36* http://www.ggf.org/documents/GFD.24.pdf confirming GSS_KRB5_REGISTER_ACCEPTOR_IDENTITY_X
b245b4a5
PP
37 semantics, that found by browsing Heimdal source to find how to set the keytab; however,
38 after multiple attempts I failed to get that to work and instead switched to
39 gsskrb5_register_acceptor_identity().
dde3daac
PP
40*/
41
42#include "../exim.h"
43
44#ifndef AUTH_HEIMDAL_GSSAPI
45/* dummy function to satisfy compilers when we link in an "empty" file. */
e1d15f5e
JH
46static void dummy(int x);
47static void dummy2(int x) { dummy(x-1); }
48static void dummy(int x) { dummy2(x-1); }
dde3daac
PP
49#else
50
51#include <gssapi/gssapi.h>
52#include <gssapi/gssapi_krb5.h>
53
54/* for the _init debugging */
55#include <krb5.h>
56
dde3daac
PP
57#include "heimdal_gssapi.h"
58
59/* Authenticator-specific options. */
60optionlist auth_heimdal_gssapi_options[] = {
61 { "server_hostname", opt_stringptr,
62 (void *)(offsetof(auth_heimdal_gssapi_options_block, server_hostname)) },
63 { "server_keytab", opt_stringptr,
64 (void *)(offsetof(auth_heimdal_gssapi_options_block, server_keytab)) },
dde3daac
PP
65 { "server_service", opt_stringptr,
66 (void *)(offsetof(auth_heimdal_gssapi_options_block, server_service)) }
67};
68
69int auth_heimdal_gssapi_options_count =
70 sizeof(auth_heimdal_gssapi_options)/sizeof(optionlist);
71
72/* Defaults for the authenticator-specific options. */
73auth_heimdal_gssapi_options_block auth_heimdal_gssapi_option_defaults = {
74 US"$primary_hostname", /* server_hostname */
75 NULL, /* server_keytab */
dde3daac
PP
76 US"smtp", /* server_service */
77};
78
d185889f
JH
79
80#ifdef MACRO_PREDEF
81
82/* Dummy values */
aab9a843
PP
83void auth_heimdal_gssapi_init(auth_instance *ablock) {}
84int auth_heimdal_gssapi_server(auth_instance *ablock, uschar *data) {return 0;}
251b9eb4
JH
85int auth_heimdal_gssapi_client(auth_instance *ablock, void * sx,
86 int timeout, uschar *buffer, int buffsize) {return 0;}
f9df71c0 87void auth_heimdal_gssapi_version_report(FILE *f) {}
d185889f
JH
88
89#else /*!MACRO_PREDEF*/
90
91
92
dde3daac
PP
93/* "Globals" for managing the heimdal_gssapi interface. */
94
dde3daac
PP
95/* Utility functions */
96static void
97 exim_heimdal_error_debug(const char *, krb5_context, krb5_error_code);
98static int
f3ebb786 99 exim_gssapi_error_defer(rmark, OM_uint32, OM_uint32, const char *, ...)
dde3daac
PP
100 PRINTF_FUNCTION(4, 5);
101
102#define EmptyBuf(buf) do { buf.value = NULL; buf.length = 0; } while (0)
103
104
105/*************************************************
106* Initialization entry point *
107*************************************************/
108
109/* Called for each instance, after its options have been read, to
110enable consistency checks to be done, or anything else that needs
111to be set up. */
112
b245b4a5
PP
113/* Heimdal provides a GSSAPI extension method for setting the keytab;
114in the init, we mostly just use raw krb5 methods so that we can report
dde3daac
PP
115the keytab contents, for -D+auth debugging. */
116
117void
118auth_heimdal_gssapi_init(auth_instance *ablock)
119{
d7978c0f
JH
120krb5_context context;
121krb5_keytab keytab;
122krb5_kt_cursor cursor;
123krb5_keytab_entry entry;
124krb5_error_code krc;
125char *principal, *enctype_s;
126const char *k_keytab_typed_name = NULL;
127auth_heimdal_gssapi_options_block *ob =
128 (auth_heimdal_gssapi_options_block *)(ablock->options_block);
129
130ablock->server = FALSE;
131ablock->client = FALSE;
132
133if (!ob->server_service || !*ob->server_service)
134 {
135 HDEBUG(D_auth) debug_printf("heimdal: missing server_service\n");
136 return;
137}
dde3daac 138
d7978c0f
JH
139krc = krb5_init_context(&context);
140if (krc != 0)
141 {
142 int kerr = errno;
143 HDEBUG(D_auth) debug_printf("heimdal: failed to initialise krb5 context: %s\n",
144 strerror(kerr));
145 return;
dde3daac
PP
146 }
147
d7978c0f
JH
148if (ob->server_keytab)
149 {
150 k_keytab_typed_name = CCS string_sprintf("file:%s", expand_string(ob->server_keytab));
151 HDEBUG(D_auth) debug_printf("heimdal: using keytab %s\n", k_keytab_typed_name);
152 krc = krb5_kt_resolve(context, k_keytab_typed_name, &keytab);
153 if (krc)
154 {
155 HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_resolve", context, krc);
156 return;
dde3daac 157 }
d7978c0f
JH
158 }
159else
160 {
161 HDEBUG(D_auth) debug_printf("heimdal: using system default keytab\n");
162 krc = krb5_kt_default(context, &keytab);
163 if (krc)
164 {
165 HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_default", context, krc);
166 return;
dde3daac
PP
167 }
168 }
169
d7978c0f
JH
170HDEBUG(D_auth)
171 {
172 /* http://www.h5l.org/manual/HEAD/krb5/krb5_keytab_intro.html */
173 krc = krb5_kt_start_seq_get(context, keytab, &cursor);
174 if (krc)
175 exim_heimdal_error_debug("krb5_kt_start_seq_get", context, krc);
176 else
177 {
178 while ((krc = krb5_kt_next_entry(context, keytab, &entry, &cursor)) == 0)
179 {
180 principal = enctype_s = NULL;
181 krb5_unparse_name(context, entry.principal, &principal);
182 krb5_enctype_to_string(context, entry.keyblock.keytype, &enctype_s);
183 debug_printf("heimdal: keytab principal: %s vno=%d type=%s\n",
184 principal ? principal : "??",
185 entry.vno,
186 enctype_s ? enctype_s : "??");
187 free(principal);
188 free(enctype_s);
189 krb5_kt_free_entry(context, &entry);
dde3daac 190 }
d7978c0f
JH
191 krc = krb5_kt_end_seq_get(context, keytab, &cursor);
192 if (krc)
193 exim_heimdal_error_debug("krb5_kt_end_seq_get", context, krc);
dde3daac
PP
194 }
195 }
196
d7978c0f
JH
197krc = krb5_kt_close(context, keytab);
198if (krc)
199 HDEBUG(D_auth) exim_heimdal_error_debug("krb5_kt_close", context, krc);
dde3daac 200
d7978c0f 201krb5_free_context(context);
dde3daac 202
d7978c0f
JH
203/* RFC 4121 section 5.2, SHOULD support 64K input buffers */
204if (big_buffer_size < (64 * 1024))
205 {
206 uschar *newbuf;
207 big_buffer_size = 64 * 1024;
208 newbuf = store_malloc(big_buffer_size);
209 store_free(big_buffer);
210 big_buffer = newbuf;
dde3daac
PP
211 }
212
d7978c0f 213ablock->server = TRUE;
dde3daac
PP
214}
215
216
217static void
218exim_heimdal_error_debug(const char *label,
219 krb5_context context, krb5_error_code err)
220{
d7978c0f
JH
221const char *kerrsc;
222kerrsc = krb5_get_error_message(context, err);
223debug_printf("heimdal %s: %s\n", label, kerrsc ? kerrsc : "unknown error");
224krb5_free_error_message(context, kerrsc);
dde3daac
PP
225}
226
227/*************************************************
228* Server entry point *
229*************************************************/
230
231/* For interface, see auths/README */
232
233/* GSSAPI notes:
234OM_uint32: portable type for unsigned int32
235gss_buffer_desc / *gss_buffer_t: hold/point-to size_t .length & void *value
236 -- all strings/etc passed in should go through one of these
237 -- when allocated by gssapi, release with gss_release_buffer()
238*/
239
240int
241auth_heimdal_gssapi_server(auth_instance *ablock, uschar *initial_data)
242{
d7978c0f
JH
243gss_name_t gclient = GSS_C_NO_NAME;
244gss_name_t gserver = GSS_C_NO_NAME;
245gss_cred_id_t gcred = GSS_C_NO_CREDENTIAL;
246gss_ctx_id_t gcontext = GSS_C_NO_CONTEXT;
247uschar *ex_server_str;
248gss_buffer_desc gbufdesc = GSS_C_EMPTY_BUFFER;
249gss_buffer_desc gbufdesc_in = GSS_C_EMPTY_BUFFER;
250gss_buffer_desc gbufdesc_out = GSS_C_EMPTY_BUFFER;
251gss_OID mech_type;
252OM_uint32 maj_stat, min_stat;
253int step, error_out;
254uschar *tmp1, *tmp2, *from_client;
255auth_heimdal_gssapi_options_block *ob =
256 (auth_heimdal_gssapi_options_block *)(ablock->options_block);
257BOOL handled_empty_ir;
f3ebb786 258rmark store_reset_point;
d7978c0f
JH
259uschar *keytab;
260uschar sasl_config[4];
261uschar requested_qop;
262
f3ebb786 263store_reset_point = store_mark();
d7978c0f
JH
264
265HDEBUG(D_auth)
266 debug_printf("heimdal: initialising auth context for %s\n", ablock->name);
267
268/* Construct our gss_name_t gserver describing ourselves */
269tmp1 = expand_string(ob->server_service);
270tmp2 = expand_string(ob->server_hostname);
271ex_server_str = string_sprintf("%s@%s", tmp1, tmp2);
272gbufdesc.value = (void *) ex_server_str;
273gbufdesc.length = Ustrlen(ex_server_str);
274maj_stat = gss_import_name(&min_stat,
275 &gbufdesc, GSS_C_NT_HOSTBASED_SERVICE, &gserver);
276if (GSS_ERROR(maj_stat))
277 return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
278 "gss_import_name(%s)", CS gbufdesc.value);
279
280/* Use a specific keytab, if specified */
281if (ob->server_keytab)
282 {
283 keytab = expand_string(ob->server_keytab);
284 maj_stat = gsskrb5_register_acceptor_identity(CCS keytab);
dde3daac
PP
285 if (GSS_ERROR(maj_stat))
286 return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
d7978c0f
JH
287 "registering keytab \"%s\"", keytab);
288 HDEBUG(D_auth)
289 debug_printf("heimdal: using keytab \"%s\"\n", keytab);
dde3daac
PP
290 }
291
d7978c0f
JH
292/* Acquire our credentials */
293maj_stat = gss_acquire_cred(&min_stat,
294 gserver, /* desired name */
295 0, /* time */
296 GSS_C_NULL_OID_SET, /* desired mechs */
297 GSS_C_ACCEPT, /* cred usage */
298 &gcred, /* handle */
299 NULL /* actual mechs */,
300 NULL /* time rec */);
301if (GSS_ERROR(maj_stat))
302 return exim_gssapi_error_defer(store_reset_point, maj_stat, min_stat,
303 "gss_acquire_cred(%s)", ex_server_str);
304
305maj_stat = gss_release_name(&min_stat, &gserver);
306
307HDEBUG(D_auth) debug_printf("heimdal: have server credentials.\n");
308
309/* Loop talking to client */
310step = 0;
311from_client = initial_data;
312handled_empty_ir = FALSE;
313error_out = OK;
314
315/* buffer sizes: auth_get_data() uses big_buffer, which we grow per
316GSSAPI RFC in _init, if needed, to meet the SHOULD size of 64KB.
317(big_buffer starts life at the MUST size of 16KB). */
318
319/* step values
3200: getting initial data from client to feed into GSSAPI
3211: iterating for as long as GSS_S_CONTINUE_NEEDED
3222: GSS_S_COMPLETE, SASL wrapping for authz and qop to send to client
3233: unpick final auth message from client
3244: break/finish (non-step)
325*/
326while (step < 4)
327 switch (step)
328 {
329 case 0:
330 if (!from_client || *from_client == '\0')
331 {
332 if (handled_empty_ir)
333 {
334 HDEBUG(D_auth) debug_printf("gssapi: repeated empty input, grr.\n");
335 error_out = BAD64;
336 goto ERROR_OUT;
337 }
338 else
339 {
340 HDEBUG(D_auth) debug_printf("gssapi: missing initial response, nudging.\n");
341 error_out = auth_get_data(&from_client, US"", 0);
342 if (error_out != OK)
343 goto ERROR_OUT;
344 handled_empty_ir = TRUE;
345 continue;
346 }
347 }
348 /* We should now have the opening data from the client, base64-encoded. */
349 step += 1;
350 HDEBUG(D_auth) debug_printf("heimdal: have initial client data\n");
351 break;
352
353 case 1:
354 gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
355 if (gclient)
356 {
357 maj_stat = gss_release_name(&min_stat, &gclient);
358 gclient = GSS_C_NO_NAME;
359 }
360 maj_stat = gss_accept_sec_context(&min_stat,
361 &gcontext, /* context handle */
362 gcred, /* acceptor cred handle */
363 &gbufdesc_in, /* input from client */
364 GSS_C_NO_CHANNEL_BINDINGS, /* XXX fixme: use the channel bindings from GnuTLS */
365 &gclient, /* client identifier */
366 &mech_type, /* mechanism in use */
367 &gbufdesc_out, /* output to send to client */
368 NULL, /* return flags */
369 NULL, /* time rec */
370 NULL /* delegated cred_handle */
371 );
372 if (GSS_ERROR(maj_stat))
373 {
374 exim_gssapi_error_defer(NULL, maj_stat, min_stat,
375 "gss_accept_sec_context()");
376 error_out = FAIL;
377 goto ERROR_OUT;
378 }
379 if (gbufdesc_out.length != 0)
380 {
381 error_out = auth_get_data(&from_client,
382 gbufdesc_out.value, gbufdesc_out.length);
383 if (error_out != OK)
384 goto ERROR_OUT;
385
386 gss_release_buffer(&min_stat, &gbufdesc_out);
387 EmptyBuf(gbufdesc_out);
388 }
389 if (maj_stat == GSS_S_COMPLETE)
390 {
391 step += 1;
392 HDEBUG(D_auth) debug_printf("heimdal: GSS complete\n");
393 }
394 else
395 HDEBUG(D_auth) debug_printf("heimdal: need more data\n");
396 break;
397
398 case 2:
399 memset(sasl_config, 0xFF, 4);
400 /* draft-ietf-sasl-gssapi-06.txt defines bitmasks for first octet
401 0x01 No security layer
402 0x02 Integrity protection
403 0x04 Confidentiality protection
404
405 The remaining three octets are the maximum buffer size for wrapped
406 content. */
407 sasl_config[0] = 0x01; /* Exim does not wrap/unwrap SASL layers after auth */
408 gbufdesc.value = (void *) sasl_config;
409 gbufdesc.length = 4;
410 maj_stat = gss_wrap(&min_stat,
411 gcontext,
412 0, /* conf_req_flag: integrity only */
413 GSS_C_QOP_DEFAULT, /* qop requested */
414 &gbufdesc, /* message to protect */
415 NULL, /* conf_state: no confidentiality applied */
416 &gbufdesc_out /* output buffer */
417 );
5031095f 418 if (GSS_ERROR(maj_stat))
d7978c0f
JH
419 {
420 exim_gssapi_error_defer(NULL, maj_stat, min_stat,
421 "gss_wrap(SASL state after auth)");
422 error_out = FAIL;
423 goto ERROR_OUT;
424 }
425
426 HDEBUG(D_auth) debug_printf("heimdal SASL: requesting QOP with no security layers\n");
427
428 error_out = auth_get_data(&from_client,
429 gbufdesc_out.value, gbufdesc_out.length);
430 if (error_out != OK)
431 goto ERROR_OUT;
432
433 gss_release_buffer(&min_stat, &gbufdesc_out);
434 EmptyBuf(gbufdesc_out);
435 step += 1;
436 break;
437
438 case 3:
439 gbufdesc_in.length = b64decode(from_client, USS &gbufdesc_in.value);
440 maj_stat = gss_unwrap(&min_stat,
441 gcontext,
442 &gbufdesc_in, /* data from client */
443 &gbufdesc_out, /* results */
444 NULL, /* conf state */
445 NULL /* qop state */
446 );
447 if (GSS_ERROR(maj_stat))
448 {
449 exim_gssapi_error_defer(NULL, maj_stat, min_stat,
450 "gss_unwrap(final SASL message from client)");
451 error_out = FAIL;
452 goto ERROR_OUT;
453 }
454 if (gbufdesc_out.length < 4)
455 {
456 HDEBUG(D_auth)
457 debug_printf("gssapi: final message too short; "
458 "need flags, buf sizes and optional authzid\n");
459 error_out = FAIL;
460 goto ERROR_OUT;
461 }
462
463 requested_qop = (CS gbufdesc_out.value)[0];
464 if ((requested_qop & 0x01) == 0)
465 {
466 HDEBUG(D_auth)
467 debug_printf("gssapi: client requested security layers (%x)\n",
468 (unsigned int) requested_qop);
469 error_out = FAIL;
470 goto ERROR_OUT;
471 }
472
473 for (int i = 0; i < AUTH_VARS; i++) auth_vars[i] = NULL;
474 expand_nmax = 0;
475
476 /* Identifiers:
477 The SASL provided identifier is an unverified authzid.
478 GSSAPI provides us with a verified identifier, but it might be empty
479 for some clients.
480 */
481
482 /* $auth2 is authzid requested at SASL layer */
483 if (gbufdesc_out.length > 4)
484 {
485 expand_nlength[2] = gbufdesc_out.length - 4;
486 auth_vars[1] = expand_nstring[2] =
487 string_copyn((US gbufdesc_out.value) + 4, expand_nlength[2]);
488 expand_nmax = 2;
489 }
490
491 gss_release_buffer(&min_stat, &gbufdesc_out);
492 EmptyBuf(gbufdesc_out);
493
494 /* $auth1 is GSSAPI display name */
495 maj_stat = gss_display_name(&min_stat,
496 gclient,
497 &gbufdesc_out,
498 &mech_type);
499 if (GSS_ERROR(maj_stat))
500 {
501 auth_vars[1] = expand_nstring[2] = NULL;
502 expand_nmax = 0;
503 exim_gssapi_error_defer(NULL, maj_stat, min_stat,
504 "gss_display_name(client identifier)");
505 error_out = FAIL;
506 goto ERROR_OUT;
507 }
508
509 expand_nlength[1] = gbufdesc_out.length;
510 auth_vars[0] = expand_nstring[1] =
511 string_copyn(gbufdesc_out.value, gbufdesc_out.length);
512
513 if (expand_nmax == 0) /* should be: authzid was empty */
514 {
515 expand_nmax = 2;
516 expand_nlength[2] = expand_nlength[1];
517 auth_vars[1] = expand_nstring[2] = string_copyn(expand_nstring[1], expand_nlength[1]);
518 HDEBUG(D_auth)
519 debug_printf("heimdal SASL: empty authzid, set to dup of GSSAPI display name\n");
520 }
521
522 HDEBUG(D_auth)
523 debug_printf("heimdal SASL: happy with client request\n"
524 " auth1 (verified GSSAPI display-name): \"%s\"\n"
525 " auth2 (unverified SASL requested authzid): \"%s\"\n",
526 auth_vars[0], auth_vars[1]);
527
528 step += 1;
529 break;
dde3daac
PP
530
531 } /* switch */
d7978c0f 532 /* while step */
dde3daac
PP
533
534
535ERROR_OUT:
d7978c0f
JH
536maj_stat = gss_release_cred(&min_stat, &gcred);
537if (gclient)
538 {
539 gss_release_name(&min_stat, &gclient);
540 gclient = GSS_C_NO_NAME;
dde3daac 541 }
d7978c0f
JH
542if (gbufdesc_out.length)
543 {
544 gss_release_buffer(&min_stat, &gbufdesc_out);
545 EmptyBuf(gbufdesc_out);
dde3daac 546 }
d7978c0f
JH
547if (gcontext != GSS_C_NO_CONTEXT)
548 gss_delete_sec_context(&min_stat, &gcontext, GSS_C_NO_BUFFER);
dde3daac 549
d7978c0f 550store_reset(store_reset_point);
dde3daac 551
d7978c0f
JH
552if (error_out != OK)
553 return error_out;
dde3daac 554
d7978c0f
JH
555/* Auth succeeded, check server_condition */
556return auth_check_serv_cond(ablock);
dde3daac
PP
557}
558
559
560static int
f3ebb786 561exim_gssapi_error_defer(rmark store_reset_point,
dde3daac
PP
562 OM_uint32 major, OM_uint32 minor,
563 const char *format, ...)
564{
d7978c0f
JH
565va_list ap;
566OM_uint32 maj_stat, min_stat;
567OM_uint32 msgcontext = 0;
568gss_buffer_desc status_string;
569gstring * g;
570
571HDEBUG(D_auth)
572 {
573 va_start(ap, format);
f3ebb786 574 g = string_vformat(NULL, SVFMT_EXTEND|SVFMT_REBUFFER, format, ap);
d7978c0f
JH
575 va_end(ap);
576 }
dde3daac 577
d7978c0f 578auth_defer_msg = NULL;
dde3daac 579
d7978c0f
JH
580do {
581 maj_stat = gss_display_status(&min_stat,
582 major, GSS_C_GSS_CODE, GSS_C_NO_OID, &msgcontext, &status_string);
dde3daac 583
d7978c0f
JH
584 if (!auth_defer_msg)
585 auth_defer_msg = string_copy(US status_string.value);
dde3daac 586
d7978c0f
JH
587 HDEBUG(D_auth) debug_printf("heimdal %s: %.*s\n",
588 string_from_gstring(g), (int)status_string.length,
589 CS status_string.value);
590 gss_release_buffer(&min_stat, &status_string);
dde3daac
PP
591
592 } while (msgcontext != 0);
593
d7978c0f
JH
594if (store_reset_point)
595 store_reset(store_reset_point);
596return DEFER;
dde3daac
PP
597}
598
599
600/*************************************************
601* Client entry point *
602*************************************************/
603
604/* For interface, see auths/README */
605
606int
607auth_heimdal_gssapi_client(
608 auth_instance *ablock, /* authenticator block */
251b9eb4 609 void * sx, /* connection */
dde3daac
PP
610 int timeout, /* command timeout */
611 uschar *buffer, /* buffer for reading response */
612 int buffsize) /* size of buffer */
613{
d7978c0f
JH
614HDEBUG(D_auth)
615 debug_printf("Client side NOT IMPLEMENTED: you should not see this!\n");
616/* NOT IMPLEMENTED */
617return FAIL;
dde3daac
PP
618}
619
620/*************************************************
621* Diagnostic API *
622*************************************************/
623
624void
625auth_heimdal_gssapi_version_report(FILE *f)
626{
d7978c0f
JH
627/* No build-time constants available unless we link against libraries at
628build-time and export the result as a string into a header ourselves. */
629fprintf(f, "Library version: Heimdal: Runtime: %s\n"
630 " Build Info: %s\n",
631 heimdal_version, heimdal_long_version);
dde3daac
PP
632}
633
d185889f 634#endif /*!MACRO_PREDEF*/
dde3daac
PP
635#endif /* AUTH_HEIMDAL_GSSAPI */
636
637/* End of heimdal_gssapi.c */