Move vp_octets and vp_strvalue to pointer
[freeradius.git] / src / modules / rlm_krb5 / rlm_krb5.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_krb5.c
20  * @brief Authenticate users, retrieving their TGT from a Kerberos V5 TDC.
21  *
22  * @copyright 2000,2006,2012-2013  The FreeRADIUS server project
23  * @copyright 2013  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
24  * @copyright 2000  Nathan Neulinger <nneul@umr.edu>
25  * @copyright 2000  Alan DeKok <aland@ox.org>
26  */
27 RCSID("$Id$")
28
29 #include        <freeradius-devel/radiusd.h>
30 #include        <freeradius-devel/modules.h>
31 #include        <freeradius-devel/rad_assert.h>
32
33 /* krb5 includes */
34 #include <krb5.h>
35
36 #ifdef ET_COMM_ERR
37 #  include <et/com_err.h>
38 #else
39 #  include <com_err.h>
40 #endif
41
42 /** Instance configuration for rlm_krb5
43  *
44  * Holds the configuration and preparsed data for a instance of rlm_krb5.
45  */
46 typedef struct rlm_krb5_t {
47         char const      *xlat_name;     //!< This module's instance name.
48         char const      *keytabname;    //!< The keytab to resolve the service in.
49         char const      *service_princ; //!< The service name provided by the
50                                         //!< config parser.
51         
52         char            *hostname;      //!< The hostname component of
53                                         //!< service_princ, or NULL.
54         char            *service;       //!< The service component of service_princ, or NULL.
55         
56         krb5_context context;           //!< The kerberos context (cloned once per request).
57         
58 #ifndef HEIMDAL_KRB5
59         krb5_get_init_creds_opt         *gic_options;   //!< Options to pass to the get_initial_credentials
60                                                         //!< function.
61         krb5_verify_init_creds_opt      *vic_options;   //!< Options to pass to the validate_initial_creds
62                                                         //!< function.
63
64         krb5_principal server;          //!< A structure representing the parsed
65                                         //!< service_princ.
66 #endif
67         
68 } rlm_krb5_t;
69
70 static const CONF_PARSER module_config[] = {
71         { "keytab", PW_TYPE_STRING_PTR, offsetof(rlm_krb5_t, keytabname), NULL, NULL },
72         { "service_principal", PW_TYPE_STRING_PTR, offsetof(rlm_krb5_t,service_princ), NULL, NULL },
73         { NULL, -1, 0, NULL, NULL }
74 };
75
76 static int krb5_detach(void *instance)
77 {
78         rlm_krb5_t *inst = instance;
79                 
80 #ifndef HEIMDAL_KRB5
81         talloc_free(inst->vic_options);
82
83         if (inst->gic_options) {
84                 krb5_get_init_creds_opt_free(inst->context, inst->gic_options);
85         }
86 #endif
87
88         /* Don't free hostname, it's just a pointer into service_princ */
89         talloc_free(inst->service);
90                 
91         if (inst->context) {
92                 krb5_free_context(inst->context);
93         }
94
95         return 0;
96 }
97
98 static int krb5_instantiate(CONF_SECTION *conf, void *instance)
99 {
100         rlm_krb5_t *inst = instance;
101         krb5_error_code ret;
102         krb5_keytab keytab;
103         char keytab_name[200];
104         char *princ_name;
105
106 #ifdef HEIMDAL_KRB5
107         DEBUG("Using Heimdal Kerberos library");
108 #else
109         DEBUG("Using MIT Kerberos library");
110 #endif
111
112 #ifndef KRB5_IS_THREAD_SAFE
113         if (!krb5_is_thread_safe()) {
114                 DEBUGI("libkrb5 is not threadsafe, recompile it with thread support enabled");
115                 WDEBUG("rlm_krb5 will run in single threaded mode, performance may be degraded");
116         } else {
117                 WDEBUG("Build time libkrb5 was not threadsafe, but run time library claims to be");
118                 WDEBUG("Reconfigure and recompile rlm_krb5 to enable thread support");
119         }
120 #endif
121
122         inst->xlat_name = cf_section_name2(conf);
123         if (!inst->xlat_name) {
124                 inst->xlat_name = cf_section_name1(conf);
125         }
126         
127         ret = krb5_init_context(&inst->context);
128         if (ret) {
129                 EDEBUG("rlm_krb5 (%s): Context initialisation failed: %s", inst->xlat_name, error_message(ret));
130
131                 return -1;
132         }
133         
134         DEBUG("rlm_krb5 (%s): Context initialised successfully", inst->xlat_name);
135
136         /*
137          *      Split service principal into service and host components
138          *      they're needed to build the server principal in MIT,
139          *      and to set the validation service in Heimdal.
140          */
141         if (inst->service_princ) {
142                 size_t len;
143                 /* Service principal appears to contain a host component */
144                 inst->hostname = strchr(inst->service_princ, '/');
145                 if (inst->hostname) {   
146                         len = (inst->hostname - inst->service_princ);                   
147                         inst->hostname++;
148                 } else {
149                         len = strlen(inst->service_princ);
150                 }
151                 
152                 if (len) {
153                         inst->service = talloc_array(inst, char, (len + 1));
154                         strlcpy(inst->service, inst->service_princ, len + 1);
155                 }
156         }
157         
158 #ifdef HEIMDAL_KRB5
159         if (inst->hostname) {
160                 DEBUG("rlm_krb5 (%s): Ignoring hostname component of service principal \"%s\", not "
161                        "needed/supported by Heimdal", inst->xlat_name, inst->hostname);
162         }
163 #else
164
165         /*
166          *      Convert the service principal string to a krb5 principal.
167          */
168         ret = krb5_sname_to_principal(inst->context, inst->hostname, inst->service, KRB5_NT_SRV_HST, &(inst->server));
169         if (ret) {
170                 EDEBUG("rlm_krb5 (%s): Failed parsing service principal: %s", inst->xlat_name, error_message(ret));
171
172                 return -1;
173         }
174         
175         ret = krb5_unparse_name(inst->context, inst->server, &princ_name);
176         if (ret) {
177                 /* Uh? */
178                 EDEBUG("rlm_krb5 (%s): Failed constructing service principal string: %s", inst->xlat_name,
179                        error_message(ret));
180
181                 return -1;
182         }
183         
184         /*
185          *      Not necessarily the same as the config item
186          */
187         DEBUG("rlm_krb5 (%s): Using service principal \"%s\"", inst->xlat_name, princ_name);
188         
189         krb5_free_unparsed_name(inst->context, princ_name);
190         
191         /*
192          *      Setup options for getting credentials and verifying them
193          */
194         
195         /* For some reason the 'init' version of this function is deprecated */
196         ret = krb5_get_init_creds_opt_alloc(inst->context, &(inst->gic_options));
197         if (ret) {
198                 EDEBUG("rlm_krb5 (%s): Couldn't allocated inital credential options: %s", inst->xlat_name,
199                        error_message(ret));
200                 
201                 return -1;
202         }
203
204         /*
205          *      Perform basic checks on the keytab
206          */
207         ret = inst->keytabname ?
208                 krb5_kt_resolve(inst->context, inst->keytabname, &keytab) :
209                 krb5_kt_default(inst->context, &keytab);
210         if (ret) {
211                 EDEBUG("rlm_krb5 (%s): Resolving keytab failed: %s", inst->xlat_name, error_message(ret));
212                 
213                 return -1;
214         }
215
216         ret = krb5_kt_get_name(inst->context, keytab, keytab_name, sizeof(keytab_name));
217         krb5_kt_close(inst->context, keytab);
218         if (ret) {
219                 EDEBUG("rlm_krb5 (%s): Can't retrieve keytab name: %s", inst->xlat_name, error_message(ret)); 
220         
221                 return -1;
222         }
223         
224         DEBUG("rlm_krb5 (%s): Using keytab \"%s\"", inst->xlat_name, keytab_name);
225         
226         MEM(inst->vic_options = talloc_zero(inst, krb5_verify_init_creds_opt));
227         
228         krb5_verify_init_creds_opt_init(inst->vic_options);
229         krb5_verify_init_creds_opt_set_ap_req_nofail(inst->vic_options, true);
230         
231 #endif
232         
233         return 0;
234 }
235
236 static rlm_rcode_t krb5_parse_user(REQUEST *request, krb5_context context, krb5_principal *client)
237 {
238         krb5_error_code ret;
239         char *princ_name;
240         
241         /*
242          *      We can only authenticate user requests which HAVE
243          *      a User-Name attribute.
244          */
245         if (!request->username) {
246                 REDEBUG("Attribute \"User-Name\" is required for authentication");
247                 
248                 return RLM_MODULE_INVALID;
249         }
250
251         /*
252          *      We can only authenticate user requests which HAVE
253          *      a User-Password attribute.
254          */
255         if (!request->password) {
256                 REDEBUG("Attribute \"User-Password\" is required for authentication");
257                 
258                 return RLM_MODULE_INVALID;
259         }
260
261         /*
262          *      Ensure that we're being passed a plain-text password,
263          *      and not anything else.
264          */
265         if (request->password->da->attr != PW_USER_PASSWORD) {
266                 REDEBUG("Attribute \"User-Password\" is required for authentication.  Cannot use \"%s\".",
267                        request->password->da->name);
268                 
269                 return RLM_MODULE_INVALID;
270         }
271         
272         ret = krb5_parse_name(context, request->username->vp_strvalue, client);
273         if (ret) {
274                 REDEBUG("Failed parsing username as principal: %s", error_message(ret));
275                 
276                 return RLM_MODULE_FAIL;
277         }
278
279         krb5_unparse_name(context, *client, &princ_name);
280         RDEBUG("Using client principal \"%s\"", princ_name);
281         krb5_free_unparsed_name(context, princ_name);
282
283         return RLM_MODULE_OK;
284 }
285
286 #ifdef HEIMDAL_KRB5
287
288 /*
289  *      Validate user/pass (Heimdal)
290  */
291 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
292 {
293         rlm_krb5_t *inst = instance;
294         rlm_rcode_t rcode;
295         
296         krb5_error_code ret;
297         
298         krb5_principal client;
299         krb5_ccache ccache;
300         krb5_keytab keytab;
301         krb5_verify_opt options;
302         krb5_context context;
303         
304         rad_assert(inst->context);
305
306 #ifdef KRB5_IS_THREAD_SAFE
307         /*
308          *      See above in MIT krb5_auth
309          */
310         ret = krb5_copy_context(inst->context, &context);
311         if (ret) {
312                 REDEBUG("Error cloning krb5 context: %s", error_message(ret));
313                 
314                 return RLM_MODULE_FAIL;
315         }
316 #else
317         context = inst->context;
318 #endif
319
320         /*
321          *      Zero out local storage
322          */
323         memset(&keytab, 0, sizeof(keytab));
324         memset(&client, 0, sizeof(client));
325         memset(&init_creds, 0, sizeof(init_creds));
326         
327         /*
328          *      Setup krb5_verify_user options
329          *
330          *      Not entirely sure this is necessary, but as we use context
331          *      to get the cache handle, we probably do have to do this with
332          *      the cloned context.
333          */
334         krb5_cc_default(context, &ccache);
335         
336         krb5_verify_opt_init(&options);
337         krb5_verify_opt_set_ccache(&options, ccache);
338         ret = inst->keytabname ?
339                 krb5_kt_resolve(context, inst->keytabname, &keytab) :
340                 krb5_kt_default(context, &keytab);
341         if (ret) {
342                 REDEBUG("Resolving keytab failed: %s", error_message(ret));
343                 
344                 goto cleanup;
345         }
346         
347         krb5_verify_opt_set_keytab(&options, keytab);
348         krb5_verify_opt_set_secure(&options, true);
349
350         if (inst->service) {
351                 krb5_verify_opt_set_service(&options, inst->service);
352         }
353         
354         rcode = krb5_parse_user(request, context, &client);
355         if (rcode != RLM_MODULE_OK) goto cleanup;
356
357         /*
358          *      Verify the user, using the options we set in instantiate
359          */
360         ret = krb5_verify_user_opt(context, client, request->password->vp_strvalue, &options);
361         if (ret) {
362                 switch (ret) {
363                 case KRB5_LIBOS_BADPWDMATCH:
364                 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
365                         REDEBUG("Provided password was incorrect (%i): %s", ret, error_message(ret));
366                         rcode = RLM_MODULE_REJECT;
367                 
368                         break;
369                 case KRB5KDC_ERR_KEY_EXP:
370                 case KRB5KDC_ERR_CLIENT_REVOKED:
371                 case KRB5KDC_ERR_SERVICE_REVOKED:
372                         REDEBUG("Account has been locked out (%i): %s", ret, error_message(ret));
373                         rcode = RLM_MODULE_USERLOCK;
374                 
375                         break;
376                 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
377                         RDEBUG("User not found: %s (%i)", ret, error_message(ret));
378                         rcode = RLM_MODULE_NOTFOUND;
379                                         
380                 default:
381                         REDEBUG("Error verifying credentials (%i): %s", ret, error_message(ret));
382                         rcode = RLM_MODULE_FAIL;
383                 
384                         break;
385                 }
386
387                 goto cleanup;
388         }
389         
390         cleanup:
391         if (client) {
392                 krb5_free_principal(context, client);
393         }
394         if (keytab) {
395                 krb5_kt_close(context, keytab);
396         }
397 #ifdef KRB5_IS_THREAD_SAFE
398         krb5_free_context(context);
399 #endif
400         return rcode;
401 }
402
403 #else
404
405 /*
406  *  Validate userid/passwd (MIT)
407  */
408 static rlm_rcode_t krb5_auth(void *instance, REQUEST *request)
409 {
410         rlm_krb5_t *inst = instance;
411         rlm_rcode_t rcode;      
412         krb5_error_code ret;
413
414         krb5_principal client;
415         krb5_creds init_creds;
416         krb5_keytab keytab;     /* ktid */
417         krb5_context context;
418         char *password;         /* compiler warnings */
419         
420         rad_assert(inst->context);
421
422 #ifdef KRB5_IS_THREAD_SAFE      
423         /*
424          *      All the snippets on threadsafety say that individual threads
425          *      must each use their own copy of context.
426          *
427          *      As we don't have any per thread instantiation, we either have
428          *      to clone inst->context on every request, or use the connection
429          *      API.
430          *
431          *      @todo Use the connection API (3.0 only).
432          */
433         ret = krb5_copy_context(inst->context, &context);
434         if (ret) {
435                 REDEBUG("Error cloning krb5 context: %s", inst->xlat_name, error_message(ret));
436                 
437                 return RLM_MODULE_FAIL;
438         }
439         rad_assert(context != NULL); /* tell coverity copy context copies it */
440 #else
441         context = inst->context;
442 #endif
443
444         /*
445          *      Zero out local storage
446          */
447         memset(&keytab, 0, sizeof(keytab));
448         memset(&client, 0, sizeof(client));
449         memset(&init_creds, 0, sizeof(init_creds));
450
451         /*
452          *      Check we have all the required VPs, and convert the username
453          *      into a principal.
454          */
455         rcode = krb5_parse_user(request, context, &client);
456         if (rcode != RLM_MODULE_OK) goto cleanup;
457
458         /*
459          *      Setup the keytab
460          */
461         ret = inst->keytabname ?
462                 krb5_kt_resolve(context, inst->keytabname, &keytab) :
463                 krb5_kt_default(context, &keytab);
464         if (ret) {
465                 REDEBUG("Resolving keytab failed: %s", inst->xlat_name, error_message(ret));
466                 
467                 goto cleanup;
468         }
469         
470         /*
471          *      Retrieve the TGT from the TGS/KDC and check we can decrypt it.
472          */
473         memcpy(&password, request->password->vp_strvalue, sizeof(password));
474         ret = krb5_get_init_creds_password(context, &init_creds, client, password,
475                                            NULL, NULL, 0, NULL, inst->gic_options);
476         password = NULL;
477         if (ret) {
478                 error:
479                 switch (ret) {
480                 case KRB5_LIBOS_BADPWDMATCH:
481                 case KRB5KRB_AP_ERR_BAD_INTEGRITY:
482                         REDEBUG("Provided password was incorrect (%i): %s", ret, error_message(ret));
483                         rcode = RLM_MODULE_REJECT;
484                         break;
485                         
486                 case KRB5KDC_ERR_KEY_EXP:
487                 case KRB5KDC_ERR_CLIENT_REVOKED:
488                 case KRB5KDC_ERR_SERVICE_REVOKED:
489                         REDEBUG("Account has been locked out (%i): %s", ret, error_message(ret));
490                         rcode = RLM_MODULE_USERLOCK;
491                         break;
492                         
493                 case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
494                         RDEBUG("User not found (%i): %s", ret,  error_message(ret));
495                         rcode = RLM_MODULE_NOTFOUND;
496                         break;
497                         
498                 default:
499                         REDEBUG("Error retrieving or verifying credentials (%i): %s", ret, error_message(ret));
500                         rcode = RLM_MODULE_FAIL;
501                         break;
502                 }
503
504                 goto cleanup;
505         }
506         
507         RDEBUG("Successfully retrieved and decrypted TGT");
508
509         ret = krb5_verify_init_creds(context, &init_creds, inst->server, keytab, NULL, inst->vic_options);
510         if (ret) goto error;
511
512         cleanup:
513
514         if (context) {
515                 if (client) {
516                         krb5_free_principal(context, client);
517                 }
518                 if (keytab) {
519                         krb5_kt_close(context, keytab);
520                 }
521                 
522                 krb5_free_cred_contents(context, &init_creds);
523
524 #ifdef KRB5_IS_THREAD_SAFE
525                 krb5_free_context(context);
526 #endif
527         }
528         
529         return rcode;
530 }
531
532 #endif /* HEIMDAL_KRB5 */
533
534 module_t rlm_krb5 = {
535         RLM_MODULE_INIT,
536         "krb5",
537         RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE
538 #ifdef KRB5_IS_THREAD_SAFE
539         | RLM_TYPE_THREAD_SAFE
540 #endif
541         ,
542         sizeof(rlm_krb5_t),
543         module_config,
544         krb5_instantiate,               /* instantiation */
545         krb5_detach,                    /* detach */
546         {
547                 krb5_auth,              /* authenticate */
548                 NULL,                   /* authorize */
549                 NULL,                   /* pre-accounting */
550                 NULL,                   /* accounting */
551                 NULL,                   /* checksimul */
552                 NULL,                   /* pre-proxy */
553                 NULL,                   /* post-proxy */
554                 NULL                    /* post-auth */
555         },
556 };