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