import from HEAD:
[freeradius.git] / src / modules / rlm_krb5 / rlm_krb5.c
1 /*
2  * rlm_krb5.c   module to authenticate against krb5
3  *
4  * Version:     $Id$
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 2 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, write to the Free Software
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  * Copyright 2000  Nathan Neulinger <nneul@umr.edu>
22  * Copyright 2000  Alan DeKok <aland@ox.org>
23  */
24
25
26 static const char rcsid[] = "$Id$";
27
28 #include        "autoconf.h"
29 #include        "libradius.h"
30
31 #include        <stdio.h>
32 #include        <stdlib.h>
33 #include        <string.h>
34
35 #include        "radiusd.h"
36 #include        "modules.h"
37
38 /* krb5 includes */
39 #include <krb5.h>
40 #include <com_err.h>
41
42 typedef struct rlm_krb5_t {
43         const char *keytab;
44         const char *service_princ;
45         krb5_context *context;
46 } rlm_krb5_t;
47
48 static CONF_PARSER module_config[] = {
49         { "keytab", PW_TYPE_STRING_PTR,
50           offsetof(rlm_krb5_t,keytab), NULL, NULL },
51         { "service_principal", PW_TYPE_STRING_PTR,
52           offsetof(rlm_krb5_t,service_princ), NULL, NULL },
53         { NULL, -1, 0, NULL, NULL }
54 };
55
56 #ifndef HEIMDAL_KRB5
57 static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *instance,
58                            const char *user, krb5_ccache ccache)
59 {
60         int r;
61         char phost[BUFSIZ];
62         krb5_principal princ;
63         krb5_keyblock *keyblock = 0;
64         krb5_data packet;
65         krb5_auth_context auth_context = NULL;
66         krb5_keytab keytab;
67         /* arbitrary 64-byte limit on service names; I've never seen a
68            service name this long, and hope never to. -srl */
69         char service[64] = "host";
70         char *servername = NULL;
71
72         if (instance->service_princ != NULL) {
73                 servername = strchr(instance->service_princ, '/');
74                 if (servername != NULL) {
75                         *servername = '\0';
76                 }
77
78                 strncpy(service,instance->service_princ,sizeof(service));
79                 service[sizeof(service)-1] = '\0';
80
81                 if (servername != NULL) {
82                         *servername = '/';
83                         servername++;
84                 }
85         }
86
87         memset(&packet, 0, sizeof packet);
88         if ((r = krb5_sname_to_principal(context, servername, service,
89                                             KRB5_NT_SRV_HST, &princ)))
90         {
91                 radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
92                         user, error_message(r));
93                 return RLM_MODULE_REJECT;
94         }
95
96         strncpy(phost, krb5_princ_component(c, princ, 1)->data, BUFSIZ);
97         phost[BUFSIZ - 1] = '\0';
98
99         /*
100          * Do we have host/<host> keys?
101          * (use default/configured keytab, kvno IGNORE_VNO to get the
102          * first match, and enctype is currently ignored anyhow.)
103          */
104         if ((r = krb5_kt_read_service_key(context, instance->keytab, princ, 0,
105                                           ENCTYPE_DES_CBC_MD5, &keyblock)))
106         {
107                 /* Keytab or service key does not exist */
108                 radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
109                        error_message(r));
110                 return RLM_MODULE_OK;
111         }
112         if (keyblock)
113                 krb5_free_keyblock(context, keyblock);
114
115         /* Talk to the kdc and construct the ticket. */
116         r = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
117                         ccache, &packet);
118         if (auth_context) {
119                 krb5_auth_con_free(context, auth_context);
120                 auth_context = NULL; /* setup for rd_req */
121         }
122
123         if (r) {
124                 radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
125                        user, error_message(r));
126                 r = RLM_MODULE_REJECT;
127                 goto cleanup;
128         }
129
130         if (instance->keytab != NULL) {
131                 r = krb5_kt_resolve(context, instance->keytab, &keytab);
132         }
133
134         if (instance->keytab == NULL || r) {
135                 r = krb5_kt_default(context, &keytab);
136         }
137
138         /* Hmm?  The keytab was just fine a second ago! */
139         if (r) {
140                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
141                         user, error_message(r));
142                 r = RLM_MODULE_REJECT;
143                 goto cleanup;
144         }
145
146         /* Try to use the ticket. */
147         r = krb5_rd_req(context, &auth_context, &packet, princ,
148                         keytab, NULL, NULL);
149         if (auth_context)
150                 krb5_auth_con_free(context, auth_context);
151
152         krb5_kt_close(context, keytab);
153
154         if (r) {
155                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
156                        user, error_message(r));
157                 r = RLM_MODULE_REJECT;
158         } else {
159                 r = RLM_MODULE_OK;
160         }
161
162 cleanup:
163         if (packet.data)
164                 krb5_free_data_contents(context, &packet);
165         return r;
166 }
167 #endif
168
169 /* instantiate */
170 static int krb5_instantiate(CONF_SECTION *conf, void **instance)
171 {
172         int r;
173         rlm_krb5_t *data;
174         krb5_context *context;
175
176         data = rad_malloc(sizeof(*data));
177
178         memset(data, 0, sizeof(*data));
179
180         if (cf_section_parse(conf, data, module_config) < 0) {
181                 free(data);
182                 return -1;
183         }
184
185         context = data->context = rad_malloc(sizeof(*context));
186
187         if ((r = krb5_init_context(context)) ) {
188                 radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
189                        error_message(r));
190                 free(data);
191                 return -1;
192         } else {
193                 radlog(L_AUTH, "rlm_krb5: krb5_init ok");
194         }
195
196         *instance = data;
197         return 0;
198 }
199
200 /* detach */
201 static int krb5_detach(void *instance)
202 {
203         free(((rlm_krb5_t *)instance)->context);
204         free(instance);
205         return 0;
206 }
207
208 /* validate userid/passwd */
209 /* MIT case */
210 #ifndef HEIMDAL_KRB5
211 static int krb5_auth(void *instance, REQUEST *request)
212 {
213         int r;
214
215         krb5_data tgtname = {
216                 0,
217                 KRB5_TGS_NAME_SIZE,
218                 KRB5_TGS_NAME
219         };
220         krb5_creds kcreds;
221         krb5_ccache ccache;
222         char cache_name[L_tmpnam + 8];
223
224         krb5_context context = *((rlm_krb5_t *)instance)->context; /* copy data */
225         const char *user, *pass;
226
227         /*
228          *      We can only authenticate user requests which HAVE
229          *      a User-Name attribute.
230          */
231         if (!request->username) {
232                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
233                 return RLM_MODULE_INVALID;
234         }
235
236         /*
237          *      We can only authenticate user requests which HAVE
238          *      a User-Password attribute.
239          */
240         if (!request->password) {
241                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
242                 return RLM_MODULE_INVALID;
243         }
244
245         /*
246          *  Ensure that we're being passed a plain-text password,
247          *  and not anything else.
248          */
249         if (request->password->attribute != PW_PASSWORD) {
250                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
251                 return RLM_MODULE_INVALID;
252         }
253
254         /*
255          *      shortcuts
256          */
257         user = request->username->strvalue;
258         pass = request->password->strvalue;
259
260         /* Generate a unique cache_name */
261         memset(cache_name, 0, sizeof(cache_name));
262         strcpy(cache_name, "MEMORY:");
263         (void) tmpnam(&cache_name[7]);
264
265         if ((r = krb5_cc_resolve(context, cache_name, &ccache))) {
266                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
267                        user, error_message(r));
268                 return RLM_MODULE_REJECT;
269         }
270
271         /*
272          *      Actually perform the authentication
273          */
274         memset((char *)&kcreds, 0, sizeof(kcreds));
275
276         if ( (r = krb5_parse_name(context, user, &kcreds.client)) ) {
277                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
278                        user, error_message(r));
279                 return RLM_MODULE_REJECT;
280         }
281
282         if ((r = krb5_cc_initialize(context, ccache, kcreds.client))) {
283                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
284                        user, error_message(r));
285                 return RLM_MODULE_REJECT;
286         }
287
288         /*
289          * MIT krb5 verification
290          */
291         if ( (r = krb5_build_principal_ext(context, &kcreds.server,
292                 krb5_princ_realm(context, kcreds.client)->length,
293                 krb5_princ_realm(context, kcreds.client)->data,
294                 tgtname.length,
295                 tgtname.data,
296                 krb5_princ_realm(context, kcreds.client)->length,
297                 krb5_princ_realm(context, kcreds.client)->data,
298                 0)) ) {
299                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
300                         user, error_message(r));
301                 krb5_cc_destroy(context, ccache);
302                 return RLM_MODULE_REJECT;
303         }
304
305         if ( (r = krb5_get_in_tkt_with_password(context,
306                 0, NULL, NULL, NULL, pass, ccache, &kcreds, 0)) ) {
307                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
308                         user, error_message(r));
309                 krb5_free_cred_contents(context, &kcreds);
310                 krb5_cc_destroy(context, ccache);
311                 return RLM_MODULE_REJECT;
312         } else {
313                 /* Now verify the KDC's identity. */
314                 r = verify_krb5_tgt(context, (rlm_krb5_t *)instance, user, ccache);
315                 krb5_free_cred_contents(context, &kcreds);
316                 krb5_cc_destroy(context, ccache);
317                 return r;
318         }
319
320         return RLM_MODULE_REJECT;
321 }
322
323 #else /* HEIMDAL_KRB5 */
324
325 /* validate user/pass, heimdal krb5 way */
326 static int krb5_auth(void *instance, REQUEST *request)
327 {
328         int r;
329         krb5_error_code ret;
330         krb5_ccache id;
331         krb5_principal userP;
332
333         krb5_context context = *((rlm_krb5_t *)instance)->context; /* copy data */
334         const char *user, *pass;
335
336         /*
337          *      We can only authenticate user requests which HAVE
338          *      a User-Name attribute.
339          */
340         if (!request->username) {
341                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
342                 return RLM_MODULE_INVALID;
343         }
344
345         /*
346          *      We can only authenticate user requests which HAVE
347          *      a User-Password attribute.
348          */
349         if (!request->password) {
350                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
351                 return RLM_MODULE_INVALID;
352         }
353
354         /*
355          *  Ensure that we're being passed a plain-text password,
356          *  and not anything else.
357          */
358         if (request->password->attribute != PW_PASSWORD) {
359                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
360                 return RLM_MODULE_INVALID;
361         }
362
363         /*
364          *      shortcuts
365          */
366         user = request->username->strvalue;
367         pass = request->password->strvalue;
368
369         if ( (r = krb5_parse_name(context, user, &userP)) ) {
370                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
371                        user, error_message(r));
372                 return RLM_MODULE_REJECT;
373         }
374
375         /*
376          * Heimdal krb5 verification
377          */
378         radlog(L_AUTH, "rlm_krb5: Parsed name is: %s@%s\n",
379                *userP->name.name_string.val,
380                userP->realm);
381
382         krb5_cc_default(context, &id);
383
384         ret = krb5_verify_user(context,
385                                userP,
386                                id,
387                                pass, 1, "radius");
388
389        if (ret == 0)
390          return RLM_MODULE_OK;
391
392        radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s )",
393               error_message(ret),
394               *userP->name.name_string.val,
395               userP->realm);
396
397        return RLM_MODULE_REJECT;
398 }
399
400 #endif /* HEIMDAL_KRB5 */
401
402 module_t rlm_krb5 = {
403   "Kerberos",
404   RLM_TYPE_THREAD_UNSAFE,       /* type: not thread safe */
405   NULL,                         /* initialize */
406   krb5_instantiate,             /* instantiation */
407   {
408           krb5_auth,            /* authenticate */
409           NULL,                 /* authorize */
410           NULL,                 /* pre-accounting */
411           NULL,                 /* accounting */
412           NULL,                 /* checksimul */
413           NULL,                 /* pre-proxy */
414           NULL,                 /* post-proxy */
415           NULL                  /* post-auth */
416   },
417   krb5_detach,                  /* detach */
418   NULL,                         /* destroy */
419 };