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.10 2006/02/03 22:33:14 snsimon 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 const char *keytabname = NULL; /* "system default" */
60static const 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, (int)sizeof (complaint)))) {
99	    syslog(LOG_ERR, "auth_krb5_init %s", complaint);
100	    return -1;
101	}
102    }
103
104    if (config) {
105	keytabname = cfile_getstring(config, "krb5_keytab", keytabname);
106	verify_principal = cfile_getstring(config, "krb5_verify_principal", verify_principal);
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, '/')) {
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
257/* returns 0 for failure, 1 for success */
258static int k5support_verify_tgt(krb5_context context,
259				krb5_ccache ccache)
260{
261    krb5_principal server;
262    krb5_data packet;
263    krb5_keyblock *keyblock = NULL;
264    krb5_auth_context auth_context = NULL;
265    krb5_error_code k5_retcode;
266    krb5_keytab kt = NULL;
267    char thishost[BUFSIZ];
268    int result = 0;
269
270    memset(&packet, 0, sizeof(packet));
271
272    if (krb5_sname_to_principal(context, NULL, verify_principal,
273				KRB5_NT_SRV_HST, &server)) {
274	return 0;
275    }
276
277    if (keytabname) {
278	if (krb5_kt_resolve(context, keytabname, &kt)) {
279	    goto fini;
280	}
281    }
282
283    if (krb5_kt_read_service_key(context, kt, server, 0,
284				 0, &keyblock)) {
285	goto fini;
286    }
287
288    if (keyblock) {
289	krb5_free_keyblock(context, keyblock);
290    }
291
292    /* this duplicates work done in krb5_sname_to_principal
293     * oh well.
294     */
295    if (gethostname(thishost, BUFSIZ) < 0) {
296	goto fini;
297    }
298    thishost[BUFSIZ-1] = '\0';
299
300    k5_retcode = krb5_mk_req(context, &auth_context, 0, (char*)verify_principal, thishost, NULL, ccache, &packet);
301    if (auth_context) {
302		krb5_auth_con_free(context, auth_context);
303		auth_context = NULL;
304    }
305
306    if (k5_retcode) {
307	goto fini;
308    }
309
310    if (krb5_rd_req(context, &auth_context, &packet,
311		    server, NULL, NULL, NULL)) {
312	goto fini;
313    }
314
315    if (auth_context) {
316      krb5_auth_con_free(context, auth_context);
317      auth_context = NULL;
318    }
319
320    /* all is good now */
321    result = 1;
322 fini:
323    krb5_free_data_contents(context, &packet);
324    krb5_free_principal(context, server);
325
326    return result;
327}
328
329/* FUNCTION: auth_krb5 */
330
331/* SYNOPSIS
332 * Authenticate against Kerberos V.
333 * END SYNOPSIS */
334
335char *					/* R: allocated response string */
336auth_krb5 (
337  /* PARAMETERS */
338  const char *user,			/* I: plaintext authenticator */
339  const char *password,			/* I: plaintext password */
340  const char *service,			/* I: service authenticating to */
341  const char *realm			/* I: user's realm */
342  /* END PARAMETERS */
343  )
344{
345    /* VARIABLES */
346    krb5_context context;
347    krb5_ccache ccache = NULL;
348    krb5_principal auth_user;
349    krb5_creds creds;
350    krb5_get_init_creds_opt opts;
351    char * result;
352    char tfname[2048];
353    char principalbuf[2048];
354    krb5_error_code code;
355    /* END VARIABLES */
356
357    if (!user|| !password) {
358	syslog(LOG_ERR, "auth_krb5: NULL password or username?");
359	return strdup("NO saslauthd internal error");
360    }
361
362    if (krb5_init_context(&context)) {
363	syslog(LOG_ERR, "auth_krb5: krb5_init_context");
364	return strdup("NO saslauthd internal error");
365    }
366
367    if (form_principal_name(user, service, realm, principalbuf, (int)sizeof (principalbuf))) {
368	syslog(LOG_ERR, "auth_krb5: form_principal_name");
369	return strdup("NO saslauthd principal name error");
370    }
371
372    if (krb5_parse_name (context, principalbuf, &auth_user)) {
373	krb5_free_context(context);
374	syslog(LOG_ERR, "auth_krb5: krb5_parse_name");
375	return strdup("NO saslauthd internal error");
376    }
377
378    if (krbtf_name(tfname, (int)sizeof (tfname)) != 0) {
379	syslog(LOG_ERR, "auth_krb5: could not generate ticket file name");
380	return strdup("NO saslauthd internal error");
381    }
382
383    if (krb5_cc_resolve(context, tfname, &ccache)) {
384	krb5_free_principal(context, auth_user);
385	krb5_free_context(context);
386	syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve");
387	return strdup("NO saslauthd internal error");
388    }
389
390    if (krb5_cc_initialize (context, ccache, auth_user)) {
391	krb5_free_principal(context, auth_user);
392	krb5_free_context(context);
393	syslog(LOG_ERR, "auth_krb5: krb5_cc_initialize");
394	return strdup("NO saslauthd internal error");
395    }
396
397    krb5_get_init_creds_opt_init(&opts);
398    /* 15 min should be more than enough */
399    krb5_get_init_creds_opt_set_tkt_life(&opts, 900);
400    if (code = krb5_get_init_creds_password(context, &creds,
401				     auth_user, (char*)password, NULL, NULL,
402				     0, NULL, &opts)) {
403	krb5_cc_destroy(context, ccache);
404	krb5_free_principal(context, auth_user);
405	krb5_free_context(context);
406	syslog(LOG_ERR, "auth_krb5: krb5_get_init_creds_password: %d", code);
407	return strdup("NO saslauthd internal error");
408    }
409
410    /* at this point we should have a TGT. Let's make sure it is valid */
411    if (krb5_cc_store_cred(context, ccache, &creds)) {
412	krb5_free_principal(context, auth_user);
413	krb5_cc_destroy(context, ccache);
414	krb5_free_context(context);
415	syslog(LOG_ERR, "auth_krb5: krb5_cc_store_cred");
416	return strdup("NO saslauthd internal error");
417    }
418
419    if (!k5support_verify_tgt(context, ccache)) {
420	syslog(LOG_ERR, "auth_krb5: k5support_verify_tgt");
421	result = strdup("NO saslauthd internal error");
422	goto fini;
423    }
424
425    /*
426     * fall through -- user is valid beyond this point
427     */
428
429    result = strdup("OK");
430 fini:
431/* destroy any tickets we had */
432    krb5_free_cred_contents(context, &creds);
433    krb5_free_principal(context, auth_user);
434    krb5_cc_destroy(context, ccache);
435    krb5_free_context(context);
436
437    return result;
438}
439
440#endif /* KRB5_HEIMDAL */
441
442#else /* ! AUTH_KRB5 */
443
444char *
445auth_krb5 (
446  const char *login __attribute__((unused)),
447  const char *password __attribute__((unused)),
448  const char *service __attribute__((unused)),
449  const char *realm __attribute__((unused))
450  )
451{
452    return NULL;
453}
454
455#endif /* ! AUTH_KRB5 */
456
457/* END FUNCTION: auth_krb5 */
458
459/* END MODULE: auth_krb5 */
460