1/* MODULE: auth_krb5 */
2
3/* COPYRIGHT
4 * Copyright (c) 1997 Messaging Direct Ltd.
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY MESSAGING DIRECT LTD. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL MESSAGING DIRECT LTD. OR
20 * ITS EMPLOYEES OR AGENTS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
22 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
23 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
25 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
26 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
27 * DAMAGE.
28 * END COPYRIGHT */
29
30#ifdef __GNUC__
31#ident "$Id: auth_krb5.c,v 1.18 2008/01/23 15:39:34 murch Exp $"
32#endif
33
34/* ok, this is  wrong but the most convenient way of doing
35 * it for now. We assume (possibly incorrectly) that if GSSAPI exists then
36 * the Kerberos 5 headers and libraries exist.
37 * What really should be done is a configure.in check for krb5.h and use
38 * that since none of this code is GSSAPI but rather raw Kerberos5.
39 */
40
41
42/* Also, at some point one would hope it would be possible to
43 * have less divergence between Heimdal and MIT Kerberos 5.
44 *
45 * As of the summer of 2003, the obvious issues are that
46 * MIT doesn't have krb5_verify_opt_*() and Heimdal doesn't
47 * have krb5_sname_to_principal().
48 */
49
50/* PUBLIC DEPENDENCIES */
51#include "mechanisms.h"
52#include "globals.h" /* mech_option */
53#include "cfile.h"
54#include "krbtf.h"
55
56#ifdef AUTH_KRB5
57# include <krb5.h>
58static cfile config = 0;
59static char *keytabname = NULL; /* "system default" */
60static char *verify_principal = "host"; /* a principal in the default keytab */
61#endif /* AUTH_KRB5 */
62
63#include <errno.h>
64#include <stdio.h>
65#include <stdlib.h>
66#include <string.h>
67#include <syslog.h>
68#include <unistd.h>
69#include <sys/stat.h>
70#include "auth_krb5.h"
71
72/* END PUBLIC DEPENDENCIES */
73
74int					/* R: -1 on failure, else 0 */
75auth_krb5_init (
76  /* PARAMETERS */
77  void					/* no parameters */
78  /* END PARAMETERS */
79  )
80{
81#ifdef AUTH_KRB5
82    int rc;
83    char *configname = 0;
84
85    if (krbtf_init() == -1) {
86	syslog(LOG_ERR, "auth_krb5_init krbtf_init failed");
87	return -1;
88    }
89
90    if (mech_option)
91	configname = mech_option;
92    else if (access(SASLAUTHD_CONF_FILE_DEFAULT, F_OK) == 0)
93	configname = SASLAUTHD_CONF_FILE_DEFAULT;
94
95    if (configname) {
96	char complaint[1024];
97
98	if (!(config = cfile_read(configname, complaint, sizeof (complaint)))) {
99	    syslog(LOG_ERR, "auth_krb5_init %s", complaint);
100	    return -1;
101	}
102    }
103
104    if (config) {
105	keytabname = (char *)cfile_getstring(config, "krb5_keytab", keytabname); /* APPLE: cast */
106	verify_principal = (char *)cfile_getstring(config, "krb5_verify_principal", verify_principal); /* APPLE: cast */
107    }
108
109    return 0;
110
111#else
112    return -1;
113#endif
114}
115
116#ifdef AUTH_KRB5
117
118static int
119form_principal_name (
120  const char *user,
121  const char *service,
122  const char *realm,
123  char *pname,
124  int pnamelen
125  )
126{
127    const char *forced_instance = 0;
128	int plen;
129
130    if (config) {
131	char keyname[1024];
132
133	snprintf(keyname, sizeof (keyname), "krb5_%s_instance", service);
134	forced_instance = cfile_getstring(config, keyname, 0);
135    }
136
137    if (forced_instance) {
138	char *user_specified;
139
140	if ((user_specified = strchr(user, '/'))) { /* APPLE: add parenthesizes */
141	    if (strcmp(user_specified + 1, forced_instance)) {
142		/* user not allowed to override sysadmin */
143		return -1;
144	    } else {
145		/* don't need to force--user already asked for it */
146		forced_instance = 0;
147	    }
148	}
149    }
150
151    /* form user[/instance][@realm] */
152    plen = snprintf(pname, pnamelen, "%s%s%s%s%s",
153	user,
154	(forced_instance ? "/" : ""),
155	(forced_instance ? forced_instance : ""),
156	((realm && realm[0]) ? "@" : ""),
157	((realm && realm[0]) ? realm : "")
158	);
159    if ((plen <= 0) || (plen >= pnamelen))
160	return -1;
161
162    /* Perhaps we should uppercase the realm? */
163
164    return 0;
165}
166
167#ifdef KRB5_HEIMDAL
168
169char *					/* R: allocated response string */
170auth_krb5 (
171  /* PARAMETERS */
172  const char *user,			/* I: plaintext authenticator */
173  const char *password,			/* I: plaintext password */
174  const char *service,                  /* I: service authenticating to */
175  const char *realm                     /* I: user's realm */
176  /* END PARAMETERS */
177  )
178{
179    /* VARIABLES */
180    krb5_context context;
181    krb5_ccache ccache = NULL;
182    krb5_keytab kt = NULL;
183    krb5_principal auth_user;
184    krb5_verify_opt opt;
185    char * result;
186    char tfname[2048];
187    char principalbuf[2048];
188    /* END VARIABLES */
189
190    if (!user || !password) {
191	syslog(LOG_ERR, "auth_krb5: NULL password or username?");
192	return strdup("NO saslauthd internal NULL password or username");
193    }
194
195    if (krb5_init_context(&context)) {
196	syslog(LOG_ERR, "auth_krb5: krb5_init_context");
197	return strdup("NO saslauthd internal krb5_init_context error");
198    }
199
200    if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) {
201	syslog(LOG_ERR, "auth_krb5: form_principal_name");
202	return strdup("NO saslauthd principal name error");
203    }
204
205    if (krb5_parse_name (context, principalbuf, &auth_user)) {
206	krb5_free_context(context);
207	syslog(LOG_ERR, "auth_krb5: krb5_parse_name");
208	return strdup("NO saslauthd internal krb5_parse_name error");
209    }
210
211    if (krbtf_name(tfname, sizeof (tfname)) != 0) {
212	syslog(LOG_ERR, "auth_krb5: could not generate ccache name");
213	return strdup("NO saslauthd internal error");
214    }
215
216    if (krb5_cc_resolve(context, tfname, &ccache)) {
217	krb5_free_principal(context, auth_user);
218	krb5_free_context(context);
219	syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve");
220	return strdup("NO saslauthd internal error");
221    }
222
223    if (keytabname) {
224	if (krb5_kt_resolve(context, keytabname, &kt)) {
225	    krb5_free_principal(context, auth_user);
226	    krb5_cc_destroy(context, ccache);
227	    krb5_free_context(context);
228	    syslog(LOG_ERR, "auth_krb5: krb5_kt_resolve");
229	    return strdup("NO saslauthd internal error");
230	}
231    }
232
233    krb5_verify_opt_init(&opt);
234    krb5_verify_opt_set_secure(&opt, 1);
235    krb5_verify_opt_set_ccache(&opt, ccache);
236    if (kt)
237	krb5_verify_opt_set_keytab(&opt,  kt);
238    krb5_verify_opt_set_service(&opt, verify_principal);
239
240    if (krb5_verify_user_opt(context, auth_user, password, &opt)) {
241	result = strdup("NO krb5_verify_user_opt failed");
242    } else {
243        result = strdup("OK");
244    }
245
246    krb5_free_principal(context, auth_user);
247    krb5_cc_destroy(context, ccache);
248    if (kt)
249	krb5_kt_close(context, kt);
250    krb5_free_context(context);
251
252    return result;
253}
254
255#else /* !KRB5_HEIMDAL */
256
257static void k5support_log_err(krb5_context context,
258			      krb5_error_code code,
259			      char const *msg)
260{
261    const char *k5_msg = krb5_get_error_message(context, code);
262
263    syslog(LOG_DEBUG, "auth_krb5: %s: %s (%d)\n", msg, k5_msg, code);
264    krb5_free_error_message(context, k5_msg);
265}
266
267/* returns 0 for failure, 1 for success */
268static int k5support_verify_tgt(krb5_context context,
269				krb5_ccache ccache)
270{
271    krb5_principal server;
272    krb5_data packet;
273    krb5_keyblock *keyblock = NULL;
274    krb5_auth_context auth_context = NULL;
275    krb5_error_code k5_retcode;
276    krb5_keytab kt = NULL;
277    char thishost[BUFSIZ];
278    int result = 0;
279
280    memset(&packet, 0, sizeof(packet));
281
282    if ((k5_retcode = krb5_sname_to_principal(context, NULL, verify_principal,
283					      KRB5_NT_SRV_HST, &server))) {
284	k5support_log_err(context, k5_retcode, "krb5_sname_to_principal()");
285	return 0;
286    }
287
288    if (keytabname) {
289	if ((k5_retcode = krb5_kt_resolve(context, keytabname, &kt))) {
290	    k5support_log_err(context, k5_retcode, "krb5_kt_resolve()");
291	    goto fini;
292	}
293    }
294
295    if ((k5_retcode = krb5_kt_read_service_key(context, kt, server, 0,
296					       0, &keyblock))) {
297	k5support_log_err(context, k5_retcode, "krb5_kt_read_service_key()");
298	goto fini;
299    }
300
301    if (keyblock) {
302	krb5_free_keyblock(context, keyblock);
303    }
304
305    /* this duplicates work done in krb5_sname_to_principal
306     * oh well.
307     */
308    if (gethostname(thishost, BUFSIZ) < 0) {
309	goto fini;
310    }
311    thishost[BUFSIZ-1] = '\0';
312
313    if ((k5_retcode = krb5_mk_req(context, &auth_context, 0, verify_principal,
314				  thishost, NULL, ccache, &packet))) {
315	k5support_log_err(context, k5_retcode, "krb5_mk_req()");
316    }
317
318    if (auth_context) {
319	krb5_auth_con_free(context, auth_context);
320	auth_context = NULL;
321    }
322
323    if (k5_retcode) {
324	goto fini;
325    }
326
327    if ((k5_retcode = krb5_rd_req(context, &auth_context, &packet,
328				  server, NULL, NULL, NULL))) {
329	k5support_log_err(context, k5_retcode, "krb5_rd_req()");
330	goto fini;
331    }
332
333    if (auth_context) {
334      krb5_auth_con_free(context, auth_context);
335      auth_context = NULL;
336    }
337
338    /* all is good now */
339    result = 1;
340 fini:
341    if (!k5_retcode) {
342        krb5_free_data_contents(context, &packet);
343    }
344    krb5_free_principal(context, server);
345
346    return result;
347}
348
349/* FUNCTION: auth_krb5 */
350
351/* SYNOPSIS
352 * Authenticate against Kerberos V.
353 * END SYNOPSIS */
354
355char *					/* R: allocated response string */
356auth_krb5 (
357  /* PARAMETERS */
358  const char *user,			/* I: plaintext authenticator */
359  const char *password,			/* I: plaintext password */
360  const char *service,			/* I: service authenticating to */
361  const char *realm			/* I: user's realm */
362  /* END PARAMETERS */
363  )
364{
365    /* VARIABLES */
366    krb5_context context;
367    krb5_ccache ccache = NULL;
368    krb5_principal auth_user;
369    krb5_creds creds;
370    krb5_get_init_creds_opt opts;
371    char * result;
372    char tfname[2048];
373    char principalbuf[2048];
374    krb5_error_code code;
375    /* END VARIABLES */
376
377    if (!user|| !password) {
378	syslog(LOG_ERR, "auth_krb5: NULL password or username?");
379	return strdup("NO saslauthd internal error");
380    }
381
382    if (krb5_init_context(&context)) {
383	syslog(LOG_ERR, "auth_krb5: krb5_init_context");
384	return strdup("NO saslauthd internal error");
385    }
386
387    if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) {
388	syslog(LOG_ERR, "auth_krb5: form_principal_name");
389	return strdup("NO saslauthd principal name error");
390    }
391
392    if (krb5_parse_name (context, principalbuf, &auth_user)) {
393	krb5_free_context(context);
394	syslog(LOG_ERR, "auth_krb5: krb5_parse_name");
395	return strdup("NO saslauthd internal error");
396    }
397
398    if (krbtf_name(tfname, sizeof (tfname)) != 0) {
399	syslog(LOG_ERR, "auth_krb5: could not generate ticket file name");
400	return strdup("NO saslauthd internal error");
401    }
402
403    if (krb5_cc_resolve(context, tfname, &ccache)) {
404	krb5_free_principal(context, auth_user);
405	krb5_free_context(context);
406	syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve");
407	return strdup("NO saslauthd internal error");
408    }
409
410    if (krb5_cc_initialize (context, ccache, auth_user)) {
411	krb5_free_principal(context, auth_user);
412	krb5_free_context(context);
413	syslog(LOG_ERR, "auth_krb5: krb5_cc_initialize");
414	return strdup("NO saslauthd internal error");
415    }
416
417    krb5_get_init_creds_opt_init(&opts);
418    /* 15 min should be more than enough */
419    krb5_get_init_creds_opt_set_tkt_life(&opts, 900);
420    if ((code = krb5_get_init_creds_password(context, &creds,
421				     auth_user, (char *)password, NULL, NULL,
422				     0, NULL, &opts))) { /* APPLE: add parenthesizes, cast */
423	krb5_cc_destroy(context, ccache);
424	krb5_free_principal(context, auth_user);
425	krb5_free_context(context);
426	syslog(LOG_ERR, "auth_krb5: krb5_get_init_creds_password: %d", code);
427	return strdup("NO saslauthd internal error");
428    }
429
430    /* at this point we should have a TGT. Let's make sure it is valid */
431    if (krb5_cc_store_cred(context, ccache, &creds)) {
432	krb5_free_principal(context, auth_user);
433	krb5_cc_destroy(context, ccache);
434	krb5_free_context(context);
435	syslog(LOG_ERR, "auth_krb5: krb5_cc_store_cred");
436	return strdup("NO saslauthd internal error");
437    }
438
439    if (!k5support_verify_tgt(context, ccache)) {
440	syslog(LOG_ERR, "auth_krb5: k5support_verify_tgt");
441	result = strdup("NO saslauthd internal error");
442	goto fini;
443    }
444
445    /*
446     * fall through -- user is valid beyond this point
447     */
448
449    result = strdup("OK");
450 fini:
451/* destroy any tickets we had */
452    krb5_free_cred_contents(context, &creds);
453    krb5_free_principal(context, auth_user);
454    krb5_cc_destroy(context, ccache);
455    krb5_free_context(context);
456
457    return result;
458}
459
460#endif /* KRB5_HEIMDAL */
461
462#else /* ! AUTH_KRB5 */
463
464char *
465auth_krb5 (
466  const char *login __attribute__((unused)),
467  const char *password __attribute__((unused)),
468  const char *service __attribute__((unused)),
469  const char *realm __attribute__((unused))
470  )
471{
472    return NULL;
473}
474
475#endif /* ! AUTH_KRB5 */
476
477/* END FUNCTION: auth_krb5 */
478
479/* END MODULE: auth_krb5 */
480