GSS_S_PROMPTING_NEEDED is a bit
[cyrus-sasl.git] / saslauthd / auth_krb5.c
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.17 2005/02/14 05:50:49 shadow 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>
58 static cfile config = 0;
59 static char *keytabname = NULL; /* "system default" */
60 static 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
74 int                                     /* R: -1 on failure, else 0 */
75 auth_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 = 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
118 static int
119 form_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
169 char *                                  /* R: allocated response string */
170 auth_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 */
258 static 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, verify_principal, 
301                              thishost, NULL, ccache, &packet);
302     
303     if (auth_context) {
304         krb5_auth_con_free(context, auth_context);
305         auth_context = NULL;
306     }
307     
308     if (k5_retcode) {
309         goto fini;
310     }
311     
312     if (krb5_rd_req(context, &auth_context, &packet, 
313                     server, NULL, NULL, NULL)) {
314         goto fini;
315     }
316
317     if (auth_context) {
318       krb5_auth_con_free(context, auth_context);
319       auth_context = NULL;
320     }
321     
322     /* all is good now */
323     result = 1;
324  fini:
325     krb5_free_data_contents(context, &packet);
326     krb5_free_principal(context, server);
327     
328     return result;
329 }
330
331 /* FUNCTION: auth_krb5 */
332
333 /* SYNOPSIS
334  * Authenticate against Kerberos V.
335  * END SYNOPSIS */
336
337 char *                                  /* R: allocated response string */
338 auth_krb5 (
339   /* PARAMETERS */
340   const char *user,                     /* I: plaintext authenticator */
341   const char *password,                 /* I: plaintext password */
342   const char *service,                  /* I: service authenticating to */
343   const char *realm                     /* I: user's realm */
344   /* END PARAMETERS */
345   )
346 {
347     /* VARIABLES */
348     krb5_context context;
349     krb5_ccache ccache = NULL;
350     krb5_principal auth_user;
351     krb5_creds creds;
352     krb5_get_init_creds_opt opts;
353     char * result;
354     char tfname[2048];
355     char principalbuf[2048];
356     krb5_error_code code;
357     /* END VARIABLES */
358
359     if (!user|| !password) {
360         syslog(LOG_ERR, "auth_krb5: NULL password or username?");
361         return strdup("NO saslauthd internal error");
362     }
363
364     if (krb5_init_context(&context)) {
365         syslog(LOG_ERR, "auth_krb5: krb5_init_context");
366         return strdup("NO saslauthd internal error");
367     }
368
369     if (form_principal_name(user, service, realm, principalbuf, sizeof (principalbuf))) {
370         syslog(LOG_ERR, "auth_krb5: form_principal_name");
371         return strdup("NO saslauthd principal name error");
372     }
373
374     if (krb5_parse_name (context, principalbuf, &auth_user)) {
375         krb5_free_context(context);
376         syslog(LOG_ERR, "auth_krb5: krb5_parse_name");
377         return strdup("NO saslauthd internal error");
378     }
379     
380     if (krbtf_name(tfname, sizeof (tfname)) != 0) {
381         syslog(LOG_ERR, "auth_krb5: could not generate ticket file name");
382         return strdup("NO saslauthd internal error");
383     }
384
385     if (krb5_cc_resolve(context, tfname, &ccache)) {
386         krb5_free_principal(context, auth_user);
387         krb5_free_context(context);
388         syslog(LOG_ERR, "auth_krb5: krb5_cc_resolve");
389         return strdup("NO saslauthd internal error");
390     }
391     
392     if (krb5_cc_initialize (context, ccache, auth_user)) {
393         krb5_free_principal(context, auth_user);
394         krb5_free_context(context);
395         syslog(LOG_ERR, "auth_krb5: krb5_cc_initialize");
396         return strdup("NO saslauthd internal error");
397     }
398     
399     krb5_get_init_creds_opt_init(&opts);
400     /* 15 min should be more than enough */
401     krb5_get_init_creds_opt_set_tkt_life(&opts, 900); 
402     if (code = krb5_get_init_creds_password(context, &creds, 
403                                      auth_user, password, NULL, NULL, 
404                                      0, NULL, &opts)) {
405         krb5_cc_destroy(context, ccache);
406         krb5_free_principal(context, auth_user);
407         krb5_free_context(context);
408         syslog(LOG_ERR, "auth_krb5: krb5_get_init_creds_password: %d", code);
409         return strdup("NO saslauthd internal error");
410     }
411     
412     /* at this point we should have a TGT. Let's make sure it is valid */
413     if (krb5_cc_store_cred(context, ccache, &creds)) {
414         krb5_free_principal(context, auth_user);
415         krb5_cc_destroy(context, ccache);
416         krb5_free_context(context);
417         syslog(LOG_ERR, "auth_krb5: krb5_cc_store_cred");
418         return strdup("NO saslauthd internal error");
419     }
420     
421     if (!k5support_verify_tgt(context, ccache)) {
422         syslog(LOG_ERR, "auth_krb5: k5support_verify_tgt");
423         result = strdup("NO saslauthd internal error");
424         goto fini;
425     }
426     
427     /* 
428      * fall through -- user is valid beyond this point  
429      */
430     
431     result = strdup("OK");
432  fini:
433 /* destroy any tickets we had */
434     krb5_free_cred_contents(context, &creds);
435     krb5_free_principal(context, auth_user);
436     krb5_cc_destroy(context, ccache);
437     krb5_free_context(context);
438
439     return result;
440 }
441
442 #endif /* KRB5_HEIMDAL */
443
444 #else /* ! AUTH_KRB5 */
445
446 char *
447 auth_krb5 (
448   const char *login __attribute__((unused)),
449   const char *password __attribute__((unused)),
450   const char *service __attribute__((unused)),
451   const char *realm __attribute__((unused))
452   )
453 {
454     return NULL;
455 }
456
457 #endif /* ! AUTH_KRB5 */
458
459 /* END FUNCTION: auth_krb5 */
460
461 /* END MODULE: auth_krb5 */