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