Pass correct arguments to memcpy
[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,2012  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  /* Arbitrary 64char limit on service names */
36 #define SERVICE_NAME_LEN 64
37
38 typedef struct rlm_krb5_t {
39         const char *keytab;
40         const char *service_princ;
41         const char *cache;
42         krb5_context *context;
43 } rlm_krb5_t;
44
45 static const CONF_PARSER module_config[] = {
46         { "keytab", PW_TYPE_STRING_PTR,
47           offsetof(rlm_krb5_t,keytab), NULL, NULL },
48         { "service_principal", PW_TYPE_STRING_PTR,
49           offsetof(rlm_krb5_t,service_princ), NULL, NULL },
50         { "cache", PW_TYPE_BOOLEAN,
51           offsetof(rlm_krb5_t,cache), NULL, "yes" },
52         { NULL, -1, 0, NULL, NULL }
53 };
54
55 #ifndef HEIMDAL_KRB5
56
57 static int krb5_build_auth_context(rlm_krb5_t *inst,
58                                    krb5_context context,
59                                    krb5_auth_context *auth_context)
60 {
61         int ret;
62         krb5_int32 flags;
63         
64         ret = krb5_auth_con_init(context, auth_context);
65         if (ret)
66                 return ret;
67         
68         ret = krb5_auth_con_getflags(context, *auth_context, &flags);
69         if (ret)
70                 return ret;
71                 
72         if (!inst->cache && (flags & KRB5_AUTH_CONTEXT_DO_TIME)) {
73                 ret = krb5_auth_con_setflags(context, *auth_context, flags & ~KRB5_AUTH_CONTEXT_DO_TIME);
74
75                 if (ret)
76                         return ret;
77         }
78         
79         return 0;
80 }
81
82 static int verify_krb5_tgt(krb5_context context, rlm_krb5_t *inst,
83                            const char *user, krb5_ccache ccache)
84 {
85         int rcode;
86         int ret;
87         char phost[BUFSIZ];
88         krb5_principal princ;
89         krb5_keyblock *keyblock = 0;
90         krb5_data packet, *server;
91         krb5_auth_context auth_context = NULL;
92         krb5_keytab keytab;
93
94         char service[SERVICE_NAME_LEN] = "host";
95         char *server_name = NULL;
96         char *keytab_name;
97         
98         /* krb5_kt_read_service_key lacks const qualifier */
99         memcpy(&keytab_name, &inst->keytab, sizeof(keytab_name));
100
101         if (inst->service_princ != NULL) {
102                 server_name = strchr(inst->service_princ, '/');
103                 if (server_name != NULL) {
104                         *server_name = '\0';
105                 }
106
107                 strlcpy(service, inst->service_princ, sizeof(service));
108
109                 if (server_name != NULL) {
110                         *server_name = '/';
111                         server_name++;
112                 }
113         }
114
115         memset(&packet, 0, sizeof packet);
116         ret = krb5_sname_to_principal(context, server_name, service,
117                                       KRB5_NT_SRV_HST, &princ);
118         if (ret) {
119                 radlog(L_DBG, "rlm_krb5: [%s] krb5_sname_to_principal failed: %s",
120                         user, error_message(ret));
121
122                 return RLM_MODULE_REJECT;
123         }
124
125         server = krb5_princ_component(c, princ, 1);
126         if (!server) {
127                 radlog(L_DBG, "rlm_krb5: [%s] krb5_princ_component failed.",
128                        user);
129
130                 return RLM_MODULE_REJECT;
131         }
132         
133         strlcpy(phost, server->data, sizeof(phost));
134
135         /*
136          *  Do we have host/<host> keys?
137          *  (use default/configured keytab, kvno IGNORE_VNO to get the
138          *  first match, and enctype is currently ignored anyhow.)
139          */
140         ret = krb5_kt_read_service_key(context, keytab_name, princ, 0,
141                                        ENCTYPE_DES_CBC_MD5, &keyblock);
142         if (ret) {
143                 /* Keytab or service key does not exist */
144                 radlog(L_DBG, "rlm_krb5: verify_krb_v5_tgt: host key not found : %s",
145                        error_message(ret));
146                        
147                 return RLM_MODULE_OK;
148         }
149         
150         if (keyblock)
151                 krb5_free_keyblock(context, keyblock);
152
153         /*
154          *  Talk to the kdc and construct the ticket.
155          */
156         ret = krb5_build_auth_context(inst, context, &auth_context);
157         if (ret) {
158                 radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
159                        user, error_message(ret));
160                        
161                 rcode = RLM_MODULE_REJECT;
162                 goto cleanup;
163         }
164         
165         ret = krb5_mk_req(context, &auth_context, 0, service, phost, NULL,
166                           ccache, &packet);
167         if (auth_context) {
168                 krb5_auth_con_free(context, auth_context);
169                 auth_context = NULL; /* setup for rd_req */
170         }
171
172         if (ret) {
173                 radlog(L_DBG, "rlm_krb5: [%s] krb5_mk_req() failed: %s",
174                        user, error_message(ret));
175
176                 rcode = RLM_MODULE_REJECT;
177                 goto cleanup;
178         }
179
180         if (keytab_name != NULL) {
181                 ret = krb5_kt_resolve(context, keytab_name, &keytab);
182         }
183
184         if (keytab_name == NULL || ret) {
185                 ret = krb5_kt_default(context, &keytab);
186         }
187
188         /* Hmm?  The keytab was just fine a second ago! */
189         if (ret) {
190                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_kt_resolve failed: %s",
191                         user, error_message(ret));
192                         
193                 rcode = RLM_MODULE_REJECT;
194                 goto cleanup;
195         }
196
197         /* Try to use the ticket. */
198         ret = krb5_build_auth_context(inst, context, &auth_context);
199         if (ret) {
200                 radlog(L_DBG, "rlm_krb5: [%s] krb5_build_auth_context() failed: %s",
201                        user, error_message(ret));
202
203                 rcode = RLM_MODULE_REJECT;
204                 goto cleanup;
205         }
206         
207         ret = krb5_rd_req(context, &auth_context, &packet, princ,
208                           keytab, NULL, NULL);
209         if (auth_context)
210                 krb5_auth_con_free(context, auth_context);
211
212         krb5_kt_close(context, keytab);
213
214         if (ret) {
215                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_rd_req() failed: %s",
216                        user, error_message(ret));
217
218                 rcode = RLM_MODULE_REJECT;
219         } else {
220                 rcode = RLM_MODULE_OK;
221         }
222         
223 cleanup:
224         if (packet.data) {
225                 krb5_free_data_contents(context, &packet);
226         }
227         
228         return rcode;
229 }
230 #endif
231
232 static int krb5_instantiate(CONF_SECTION *conf, void **instance)
233 {
234         int ret;
235         rlm_krb5_t *data;
236         krb5_context *context;
237
238         data = rad_malloc(sizeof(*data));
239
240         memset(data, 0, sizeof(*data));
241
242         if (cf_section_parse(conf, data, module_config) < 0) {
243                 free(data);
244                 return -1;
245         }
246         
247         context = data->context = rad_malloc(sizeof(*context));
248
249         ret = krb5_init_context(context);
250         if (ret) {
251                 radlog(L_AUTH, "rlm_krb5: krb5_init failed: %s",
252                        error_message(ret));
253   
254                 free(data);
255                 return -1;
256         } else {
257                 radlog(L_AUTH, "rlm_krb5: krb5_init ok");
258         }
259         
260         *instance = data;
261         
262         return 0;
263 }
264
265 static int krb5_detach(void *instance)
266 {
267         free(((rlm_krb5_t *)instance)->context);
268         free(instance);
269         
270         return 0;
271 }
272
273 /* 
274  *  Validate userid/passwd (MIT)
275  */
276 #ifndef HEIMDAL_KRB5
277 static int krb5_auth(void *instance, REQUEST *request)
278 {
279         rlm_krb5_t *inst = instance;
280         int ret;
281         
282         static char tgs_name[] = KRB5_TGS_NAME;
283         krb5_data tgtname = {
284                 0,
285                 KRB5_TGS_NAME_SIZE,
286                 tgs_name
287         };
288         
289         krb5_creds kcreds;
290         krb5_ccache ccache;
291         
292         /* MEMORY: + unsigned int (20) + NULL */
293         char cache_name[28];
294
295         krb5_context context = *(inst->context); /* copy data */
296         const char *user, *pass;
297
298         /*
299          *  We can only authenticate user requests which HAVE
300          *  a User-Name attribute.
301          */
302         if (!request->username) {
303                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
304                 
305                 return RLM_MODULE_INVALID;
306         }
307
308         /*
309          *  We can only authenticate user requests which HAVE
310          *  a User-Password attribute.
311          */
312         if (!request->password) {
313                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
314                 
315                 return RLM_MODULE_INVALID;
316         }
317
318         /*
319          *  Ensure that we're being passed a plain-text password,
320          *  and not anything else.
321          */
322         if (request->password->attribute != PW_USER_PASSWORD) {
323                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
324                 
325                 return RLM_MODULE_INVALID;
326         }
327
328         user = request->username->vp_strvalue;
329         pass = request->password->vp_strvalue;
330
331         /*
332          *  Generate a unique cache_name.
333          */
334         snprintf(cache_name, sizeof(cache_name), "MEMORY:%u", request->number);
335
336         ret = krb5_cc_resolve(context, cache_name, &ccache);
337         if (ret) {
338                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_resolve(): %s",
339                        user, error_message(ret));
340                        
341                 return RLM_MODULE_REJECT;
342         }
343
344         /*
345          *  Actually perform the authentication.
346          */
347         memset((char *)&kcreds, 0, sizeof(kcreds));
348
349         ret = krb5_parse_name(context, user, &kcreds.client);
350         if (ret) {
351                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
352                        user, error_message(ret));
353                        
354                 return RLM_MODULE_REJECT;
355         }
356
357         ret = krb5_cc_initialize(context, ccache, kcreds.client);
358         if (ret) {
359                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_cc_initialize(): %s",
360                        user, error_message(ret));
361                        
362                 return RLM_MODULE_REJECT;
363         }
364
365         /*
366          *  MIT krb5 verification.
367          */
368         ret = krb5_build_principal_ext(context, &kcreds.server,
369                         krb5_princ_realm(context, kcreds.client)->length,
370                         krb5_princ_realm(context, kcreds.client)->data,
371                         tgtname.length,
372                         tgtname.data,
373                         krb5_princ_realm(context, kcreds.client)->length,
374                         krb5_princ_realm(context, kcreds.client)->data,
375                         0);
376         if (ret) {
377                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_build_principal_ext failed: %s",
378                         user, error_message(ret));
379                 krb5_cc_destroy(context, ccache);
380                 
381                 return RLM_MODULE_REJECT;
382         }
383
384         ret = krb5_get_in_tkt_with_password(context, 0, NULL, NULL, NULL, pass,
385                                             ccache, &kcreds, 0);
386         if (ret) {
387                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_g_i_t_w_p failed: %s",
388                        user, error_message(ret));
389                 krb5_free_cred_contents(context, &kcreds);
390                 krb5_cc_destroy(context, ccache);
391                 
392                 return RLM_MODULE_REJECT;
393         }
394
395         /*
396          *  Now verify the KDC's identity.
397          */
398         ret = verify_krb5_tgt(context, inst, user, ccache);
399         krb5_free_cred_contents(context, &kcreds);
400         krb5_cc_destroy(context, ccache);
401         
402         return ret;
403 }
404
405 #else /* HEIMDAL_KRB5 */
406
407 /*
408  *  validate user/pass (Heimdal)
409  */
410 static int krb5_auth(void *instance, REQUEST *request)
411 {
412         rlm_krb5_t *inst = instance;
413
414         krb5_error_code ret;
415         krb5_ccache id;
416         krb5_principal userP;
417
418         krb5_context context = *(inst->context); /* copy data */
419         const char *user, *pass;
420
421         char service[SERVICE_NAME_LEN] = "host";
422         char *server_name = NULL;
423         char *princ_name;
424
425         krb5_verify_opt krb_verify_options;
426         krb5_keytab keytab;
427
428         if (inst->service_princ != NULL) {
429                 server_name = strchr(inst->service_princ, '/');
430                 if (server_name != NULL) {
431                         *server_name = '\0';
432                 }
433
434                 strlcpy(service, inst->service_princ, sizeof(service));
435                 if (server_name != NULL) {
436                         *server_name = '/';
437                         server_name++;
438                 }
439         }
440
441         /*
442          *  We can only authenticate user requests which HAVE
443          *  a User-Name attribute.
444          */
445         if (!request->username) {
446                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Name\" is required for authentication.");
447                 
448                 return RLM_MODULE_INVALID;
449         }
450
451         /*
452          *  We can only authenticate user requests which HAVE
453          *  a User-Password attribute.
454          */
455         if (!request->password) {
456                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.");
457                 
458                 return RLM_MODULE_INVALID;
459         }
460
461         /*
462          *  Ensure that we're being passed a plain-text password,
463          *  and not anything else.
464          */
465         if (request->password->attribute != PW_USER_PASSWORD) {
466                 radlog(L_AUTH, "rlm_krb5: Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
467                 
468                 return RLM_MODULE_INVALID;
469         }
470
471         user = request->username->vp_strvalue;
472         pass = request->password->vp_strvalue;
473
474         ret = krb5_parse_name(context, user, &userP);
475         if (ret) {
476                 radlog(L_AUTH, "rlm_krb5: [%s] krb5_parse_name failed: %s",
477                        user, error_message(ret));
478                        
479                 return RLM_MODULE_REJECT;
480         }
481
482         /*
483          *  Heimdal krb5 verification.
484          */
485          
486         /*
487          *  The following bit allows us to also log user/instance@REALM if someone
488          *  logs in using an instance.
489          */
490         ret = krb5_unparse_name(context, userP, &princ_name);
491         if (ret != 0) {
492                 radlog(L_AUTH, "rlm_krb5: Unparsable name");
493         } else {
494                 radlog(L_AUTH, "rlm_krb5: Parsed name is: %s", princ_name);
495                 free(princ_name);
496         }
497
498         krb5_cc_default(context, &id);
499
500         /*
501          *  Set up krb5_verify_user options.
502          */
503         krb5_verify_opt_init(&krb_verify_options);
504         krb5_verify_opt_set_ccache(&krb_verify_options, id);
505
506         /*
507          *  Resolve keytab name. This allows us to use something other than
508          *  the default system keytab
509          */
510         if (inst->keytab != NULL) {
511                 ret = krb5_kt_resolve(context, inst->keytab, &keytab);
512                 if (ret) {
513                         radlog(L_AUTH, "rlm_krb5: unable to resolve keytab %s: %s",
514                                inst->keytab, error_message(ret));
515                         krb5_kt_close(context, keytab);
516                         
517                         return RLM_MODULE_REJECT;
518                 }
519                 
520                 krb5_verify_opt_set_keytab(&krb_verify_options, keytab);
521         }
522
523         /*
524          *  Verify aquired credentials against the keytab.
525          */
526         krb5_verify_opt_set_secure(&krb_verify_options, 1);
527
528         /*
529          *  Allow us to use an arbitrary service name.
530          */
531         krb5_verify_opt_set_service(&krb_verify_options, service);
532
533         /* 
534          *  Verify the user, using the above set options.
535          */
536         ret = krb5_verify_user_opt(context, userP, pass, &krb_verify_options);
537
538         /*
539          *  We are done with the keytab, close it.
540          */
541         krb5_kt_close(context, keytab);
542
543         if (ret == 0)
544                 return RLM_MODULE_OK;
545
546         radlog(L_AUTH, "rlm_krb5: failed verify_user: %s (%s@%s)",
547                error_message(ret),
548                *userP->name.name_string.val,
549                userP->realm);
550
551         return RLM_MODULE_REJECT;
552 }
553
554 #endif /* HEIMDAL_KRB5 */
555
556 module_t rlm_krb5 = {
557         RLM_MODULE_INIT,
558         "Kerberos",
559         RLM_TYPE_THREAD_UNSAFE, /* type: not thread safe */
560         krb5_instantiate,               /* instantiation */
561         krb5_detach,                    /* detach */
562         {
563                 krb5_auth,              /* authenticate */
564                 NULL,                   /* authorize */
565                 NULL,                   /* pre-accounting */
566                 NULL,                   /* accounting */
567                 NULL,                   /* checksimul */
568                 NULL,                   /* pre-proxy */
569                 NULL,                   /* post-proxy */
570                 NULL                    /* post-auth */
571         },
572 };