7424a5254b76d90b8f45d16393ab1fd93357ab5f
[freeradius.git] / src / modules / rlm_eap / rlm_eap.c
1 /*
2  * rlm_eap.c  contains handles that are called from modules.
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-2003,2006  The FreeRADIUS server project
21  * Copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
22  * Copyright 2003  Alan DeKok <aland@freeradius.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 #include "rlm_eap.h"
32
33 static const CONF_PARSER module_config[] = {
34         { "default_eap_type", PW_TYPE_STRING_PTR,
35           offsetof(rlm_eap_t, default_eap_type_name), NULL, "md5" },
36         { "timer_expire", PW_TYPE_INTEGER,
37           offsetof(rlm_eap_t, timer_limit), NULL, "60"},
38         { "ignore_unknown_eap_types", PW_TYPE_BOOLEAN,
39           offsetof(rlm_eap_t, ignore_unknown_eap_types), NULL, "no" },
40         { "cisco_accounting_username_bug", PW_TYPE_BOOLEAN,
41           offsetof(rlm_eap_t, cisco_accounting_username_bug), NULL, "no" },
42         { "max_sessions", PW_TYPE_INTEGER,
43           offsetof(rlm_eap_t, max_sessions), NULL, "2048"},
44
45         { NULL, -1, 0, NULL, NULL }           /* end the list */
46 };
47
48 /*
49  * delete all the allocated space by eap module
50  */
51 static int eap_detach(void *instance)
52 {
53         rlm_eap_t *inst;
54         int i;
55
56         inst = (rlm_eap_t *)instance;
57
58         rbtree_free(inst->session_tree);
59         if (inst->handler_tree) rbtree_free(inst->handler_tree);
60         inst->session_tree = NULL;
61         eaplist_free(inst);
62
63         for (i = 0; i < PW_EAP_MAX_TYPES; i++) {
64                 if (inst->types[i]) eaptype_free(inst->types[i]);
65                 inst->types[i] = NULL;
66         }
67
68         pthread_mutex_destroy(&(inst->session_mutex));
69
70         free(inst);
71
72         return 0;
73 }
74
75
76 /*
77  *      Compare two handlers.
78  */
79 static int eap_handler_cmp(const void *a, const void *b)
80 {
81         int rcode;
82         const EAP_HANDLER *one = a;
83         const EAP_HANDLER *two = b;
84
85         if (one->eap_id < two->eap_id) return -1;
86         if (one->eap_id > two->eap_id) return +1;
87
88         rcode = memcmp(one->state, two->state, sizeof(one->state));
89         if (rcode != 0) return rcode;
90
91         /*
92          *      As of 2.1.8, we don't key off of source IP.  This
93          *      a NAS to send packets load-balanced (or fail-over)
94          *      across multiple intermediate proxies, and still have
95          *      EAP work.
96          */
97         if (fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr) != 0) {
98                 DEBUG("WARNING: EAP packets are arriving from two different upstream servers.  Has there been a proxy fail-over?");
99         }
100
101         return 0;
102 }
103
104
105 /*
106  *      Compare two handler pointers
107  */
108 static int eap_handler_ptr_cmp(const void *a, const void *b)
109 {
110   return (a - b);
111 }
112
113
114 /*
115  * read the config section and load all the eap authentication types present.
116  */
117 static int eap_instantiate(CONF_SECTION *cs, void **instance)
118 {
119         int             i, eap_type;
120         int             num_types;
121         CONF_SECTION    *scs;
122         rlm_eap_t       *inst;
123
124         inst = (rlm_eap_t *) malloc(sizeof(*inst));
125         if (!inst) {
126                 return -1;
127         }
128         memset(inst, 0, sizeof(*inst));
129         if (cf_section_parse(cs, inst, module_config) < 0) {
130                 eap_detach(inst);
131                 return -1;
132         }
133
134         /*
135          *      Create our own random pool.
136          */
137         for (i = 0; i < 256; i++) {
138                 inst->rand_pool.randrsl[i] = fr_rand();
139         }
140         fr_randinit(&inst->rand_pool, 1);
141         inst->rand_pool.randcnt = 0;
142
143         inst->xlat_name = cf_section_name2(cs);
144         if (!inst->xlat_name) inst->xlat_name = "EAP";
145
146         /* Load all the configured EAP-Types */
147         num_types = 0;
148         for(scs=cf_subsection_find_next(cs, NULL, NULL);
149                 scs != NULL;
150                 scs=cf_subsection_find_next(cs, scs, NULL)) {
151
152                 const char      *auth_type;
153
154                 auth_type = cf_section_name1(scs);
155
156                 if (!auth_type)  continue;
157
158                 eap_type = eaptype_name2type(auth_type);
159                 if (eap_type < 0) {
160                         radlog(L_ERR, "rlm_eap: Unknown EAP type %s",
161                                auth_type);
162                         eap_detach(inst);
163                         return -1;
164                 }
165
166 #ifndef HAVE_OPENSSL_SSL_H
167                 /*
168                  *      This allows the default configuration to be
169                  *      shipped with EAP-TLS, etc. enabled.  If the
170                  *      system doesn't have OpenSSL, they will be
171                  *      ignored.
172                  *
173                  *      If the system does have OpenSSL, then this
174                  *      code will not be used.  The administrator will
175                  *      then have to delete the tls,
176                  *      etc. configurations from eap.conf in order to
177                  *      have EAP without the TLS types.
178                  */
179                 if ((eap_type == PW_EAP_TLS) ||
180                     (eap_type == PW_EAP_TTLS) ||
181                     (eap_type == PW_EAP_PEAP)) {
182                         DEBUG2("Ignoring EAP-Type/%s because we do not have OpenSSL support.", auth_type);
183                         continue;
184                 }
185 #endif
186
187                 /*
188                  *      If we're asked to load TTLS or PEAP, ensure
189                  *      that we've first loaded TLS.
190                  */
191                 if (((eap_type == PW_EAP_TTLS) ||
192                      (eap_type == PW_EAP_PEAP)) &&
193                     (inst->types[PW_EAP_TLS] == NULL)) {
194                         radlog(L_ERR, "rlm_eap: Unable to load EAP-Type/%s, as EAP-Type/TLS is required first.",
195                                auth_type);
196                         return -1;
197                 }
198
199                 /*
200                  *      Load the type.
201                  */
202                 if (eaptype_load(&inst->types[eap_type], eap_type, scs) < 0) {
203                         eap_detach(inst);
204                         return -1;
205                 }
206
207                 num_types++;    /* successfully loaded one more types */
208         }
209
210         if (num_types == 0) {
211                 radlog(L_ERR|L_CONS, "rlm_eap: No EAP type configured, module cannot do anything.");
212                 eap_detach(inst);
213                 return -1;
214         }
215
216         /*
217          *      Ensure that the default EAP type is loaded.
218          */
219         eap_type = eaptype_name2type(inst->default_eap_type_name);
220         if (eap_type < 0) {
221                 radlog(L_ERR|L_CONS, "rlm_eap: Unknown default EAP type %s",
222                        inst->default_eap_type_name);
223                 eap_detach(inst);
224                 return -1;
225         }
226
227         if (inst->types[eap_type] == NULL) {
228                 radlog(L_ERR|L_CONS, "rlm_eap: No such sub-type for default EAP type %s",
229                        inst->default_eap_type_name);
230                 eap_detach(inst);
231                 return -1;
232         }
233         inst->default_eap_type = eap_type; /* save the numerical type */
234
235         /*
236          *      List of sessions are set to NULL by the memset
237          *      of 'inst', above.
238          */
239
240         /*
241          *      Lookup sessions in the tree.  We don't free them in
242          *      the tree, as that's taken care of elsewhere...
243          */
244         inst->session_tree = rbtree_create(eap_handler_cmp, NULL, 0);
245         if (!inst->session_tree) {
246                 radlog(L_ERR|L_CONS, "rlm_eap: Cannot initialize tree");
247                 eap_detach(inst);
248                 return -1;
249         }
250
251         if (fr_debug_flag) {
252                 inst->handler_tree = rbtree_create(eap_handler_ptr_cmp, NULL, 0);
253                 if (!inst->handler_tree) {
254                         radlog(L_ERR|L_CONS, "rlm_eap: Cannot initialize tree");
255                         eap_detach(inst);
256                         return -1;
257                 }
258         }
259
260         pthread_mutex_init(&(inst->session_mutex), NULL);
261
262         *instance = inst;
263         return 0;
264 }
265
266
267 /*
268  *      For backwards compatibility.
269  */
270 static int eap_authenticate(void *instance, REQUEST *request)
271 {
272         rlm_eap_t       *inst;
273         EAP_HANDLER     *handler;
274         eap_packet_t    *eap_packet;
275         int             rcode;
276
277         inst = (rlm_eap_t *) instance;
278
279         if (!pairfind(request->packet->vps, PW_EAP_MESSAGE, 0)) {
280                 RDEBUG("ERROR: You set 'Auth-Type = EAP' for a request that does not contain an EAP-Message attribute!");
281                 return RLM_MODULE_INVALID;
282         }
283
284         /*
285          *      Get the eap packet  to start with
286          */
287         eap_packet = eap_vp2packet(request->packet->vps);
288         if (eap_packet == NULL) {
289                 radlog_request(L_ERR, 0, request, "Malformed EAP Message");
290                 return RLM_MODULE_FAIL;
291         }
292
293         /*
294          *      Create the eap handler.  The eap_packet will end up being
295          *      "swallowed" into the handler, so we can't access it after
296          *      this call.
297          */
298         handler = eap_handler(inst, &eap_packet, request);
299         if (handler == NULL) {
300                 RDEBUG2("Failed in handler");
301                 return RLM_MODULE_INVALID;
302         }
303
304         /*
305          *      Select the appropriate eap_type or default to the
306          *      configured one
307          */
308         rcode = eaptype_select(inst, handler);
309
310         /*
311          *      If it failed, die.
312          */
313         if (rcode == EAP_INVALID) {
314                 eap_fail(handler);
315                 eap_handler_free(inst, handler);
316                 RDEBUG2("Failed in EAP select");
317                 return RLM_MODULE_INVALID;
318         }
319
320 #ifdef WITH_PROXY
321         /*
322          *      If we're doing horrible tunneling work, remember it.
323          */
324         if ((request->options & RAD_REQUEST_OPTION_PROXY_EAP) != 0) {
325                 RDEBUG2("  Not-EAP proxy set.  Not composing EAP");
326                 /*
327                  *      Add the handle to the proxied list, so that we
328                  *      can retrieve it in the post-proxy stage, and
329                  *      send a response.
330                  */
331                 rcode = request_data_add(request,
332                                          inst, REQUEST_DATA_EAP_HANDLER,
333                                          handler,
334                                          (void *) eap_handler_free);
335                 rad_assert(rcode == 0);
336
337                 return RLM_MODULE_HANDLED;
338         }
339 #endif
340
341 #ifdef WITH_PROXY
342         /*
343          *      Maybe the request was marked to be proxied.  If so,
344          *      proxy it.
345          */
346         if (request->proxy != NULL) {
347                 VALUE_PAIR *vp = NULL;
348
349                 rad_assert(request->proxy_reply == NULL);
350
351                 /*
352                  *      Add the handle to the proxied list, so that we
353                  *      can retrieve it in the post-proxy stage, and
354                  *      send a response.
355                  */
356                 rcode = request_data_add(request,
357                                          inst, REQUEST_DATA_EAP_HANDLER,
358                                          handler,
359                                          (void *) eap_handler_free);
360                 rad_assert(rcode == 0);
361
362                 /*
363                  *      Some simple sanity checks.  These should really
364                  *      be handled by the radius library...
365                  */
366                 vp = pairfind(request->proxy->vps, PW_EAP_MESSAGE, 0);
367                 if (vp) {
368                         vp = pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR, 0);
369                         if (!vp) {
370                                 vp = pairmake("Message-Authenticator",
371                                               "0x00", T_OP_EQ);
372                                 rad_assert(vp != NULL);
373                                 pairadd(&(request->proxy->vps), vp);
374                         }
375                 }
376
377                 /*
378                  *      Delete the "proxied to" attribute, as it's
379                  *      set to 127.0.0.1 for tunneled requests, and
380                  *      we don't want to tell the world that...
381                  */
382                 pairdelete(&request->proxy->vps, PW_FREERADIUS_PROXIED_TO, VENDORPEC_FREERADIUS);
383
384                 RDEBUG2("  Tunneled session will be proxied.  Not doing EAP.");
385                 return RLM_MODULE_HANDLED;
386         }
387 #endif
388
389         /*
390          *      We are done, wrap the EAP-request in RADIUS to send
391          *      with all other required radius attributes
392          */
393         rcode = eap_compose(handler);
394
395         /*
396          *      Add to the list only if it is EAP-Request, OR if
397          *      it's LEAP, and a response.
398          */
399         if (((handler->eap_ds->request->code == PW_EAP_REQUEST) &&
400             (handler->eap_ds->request->type.type >= PW_EAP_MD5)) ||
401
402                 /*
403                  *      LEAP is a little different.  At Stage 4,
404                  *      it sends an EAP-Success message, but we still
405                  *      need to keep the State attribute & session
406                  *      data structure around for the AP Challenge.
407                  *
408                  *      At stage 6, LEAP sends an EAP-Response, which
409                  *      isn't put into the list.
410                  */
411             ((handler->eap_ds->response->code == PW_EAP_RESPONSE) &&
412              (handler->eap_ds->response->type.type == PW_EAP_LEAP) &&
413              (handler->eap_ds->request->code == PW_EAP_SUCCESS) &&
414              (handler->eap_ds->request->type.type == 0))) {
415
416                 /*
417                  *      Return FAIL if we can't remember the handler.
418                  *      This is actually disallowed by the
419                  *      specification, as unexpected FAILs could have
420                  *      been forged.  However, we want to signal to
421                  *      everyone else involved that we are
422                  *      intentionally failing the session, as opposed
423                  *      to accidentally failing it.
424                  */
425                 if (!eaplist_add(inst, handler)) {
426                         eap_fail(handler);
427                         eap_handler_free(inst, handler);
428                         return RLM_MODULE_FAIL;
429                 }
430
431         } else {
432                 RDEBUG2("Freeing handler");
433                 /* handler is not required any more, free it now */
434                 eap_handler_free(inst, handler);
435         }
436
437         /*
438          *      If it's an Access-Accept, RFC 2869, Section 2.3.1
439          *      says that we MUST include a User-Name attribute in the
440          *      Access-Accept.
441          */
442         if ((request->reply->code == PW_AUTHENTICATION_ACK) &&
443             request->username) {
444                 VALUE_PAIR *vp;
445
446                 /*
447                  *      Doesn't exist, add it in.
448                  */
449                 vp = pairfind(request->reply->vps, PW_USER_NAME, 0);
450                 if (!vp) {
451                         vp = pairmake("User-Name", "",
452                                       T_OP_EQ);
453                         strlcpy(vp->vp_strvalue, request->username->vp_strvalue,
454                                 sizeof(vp->vp_strvalue));
455                         vp->length = request->username->length;
456                         rad_assert(vp != NULL);
457                         pairadd(&(request->reply->vps), vp);
458                 }
459
460                 /*
461                  *      Cisco AP1230 has a bug and needs a zero
462                  *      terminated string in Access-Accept.
463                  */
464                 if ((inst->cisco_accounting_username_bug) &&
465                     (vp->length < (int) sizeof(vp->vp_strvalue))) {
466                         vp->vp_strvalue[vp->length] = '\0';
467                         vp->length++;
468                 }
469         }
470
471         return rcode;
472 }
473
474 /*
475  * EAP authorization DEPENDS on other rlm authorizations,
476  * to check for user existance & get their configured values.
477  * It Handles EAP-START Messages, User-Name initilization.
478  */
479 static int eap_authorize(void *instance, REQUEST *request)
480 {
481         rlm_eap_t       *inst;
482         int             status;
483         VALUE_PAIR      *vp;
484
485         inst = (rlm_eap_t *)instance;
486
487 #ifdef WITH_PROXY
488         /*
489          *      We don't do authorization again, once we've seen the
490          *      proxy reply (or the proxied packet)
491          */
492         if (request->proxy != NULL)
493                 return RLM_MODULE_NOOP;
494 #endif
495
496         /*
497          *      For EAP_START, send Access-Challenge with EAP Identity
498          *      request.  even when we have to proxy this request
499          *
500          *      RFC 2869, Section 2.3.1 notes that the "domain" of the
501          *      user, (i.e. where to proxy him) comes from the EAP-Identity,
502          *      so we CANNOT proxy the user, until we know his identity.
503          *
504          *      We therefore send an EAP Identity request.
505          */
506         status = eap_start(inst, request);
507         switch(status) {
508         case EAP_NOOP:
509                 return RLM_MODULE_NOOP;
510         case EAP_FAIL:
511                 return RLM_MODULE_FAIL;
512         case EAP_FOUND:
513                 return RLM_MODULE_HANDLED;
514         case EAP_OK:
515         case EAP_NOTFOUND:
516         default:
517                 break;
518         }
519
520         /*
521          *      RFC 2869, Section 2.3.1.  If a NAS sends an EAP-Identity,
522          *      it MUST copy the identity into the User-Name attribute.
523          *
524          *      But we don't worry about that too much.  We depend on
525          *      each EAP sub-module to look for handler->request->username,
526          *      and to get excited if it doesn't appear.
527          */
528
529         vp = pairfind(request->config_items, PW_AUTH_TYPE, 0);
530         if ((!vp) ||
531             (vp->vp_integer != PW_AUTHTYPE_REJECT)) {
532                 vp = pairmake("Auth-Type", inst->xlat_name, T_OP_EQ);
533                 if (!vp) {
534                         RDEBUG2("Failed to create Auth-Type %s: %s\n",
535                                 inst->xlat_name, fr_strerror());
536                         return RLM_MODULE_FAIL;
537                 }
538                 pairadd(&request->config_items, vp);
539         } else {
540                 RDEBUG2("WARNING: Auth-Type already set.  Not setting to EAP");
541         }
542
543         if (status == EAP_OK) return RLM_MODULE_OK;
544
545         return RLM_MODULE_UPDATED;
546 }
547
548
549 #ifdef WITH_PROXY
550 /*
551  *      If we're proxying EAP, then there may be magic we need
552  *      to do.
553  */
554 static int eap_post_proxy(void *inst, REQUEST *request)
555 {
556         size_t          i;
557         size_t          len;
558         VALUE_PAIR      *vp;
559         EAP_HANDLER     *handler;
560
561         /*
562          *      Just in case the admin lists EAP in post-proxy-type Fail.
563          */
564         if (!request->proxy_reply) return RLM_MODULE_NOOP;
565
566         /*
567          *      If there was a handler associated with this request,
568          *      then it's a tunneled request which was proxied...
569          */
570         handler = request_data_get(request, inst, REQUEST_DATA_EAP_HANDLER);
571         if (handler != NULL) {
572                 int             rcode;
573                 eap_tunnel_data_t *data;
574
575                 /*
576                  *      Grab the tunnel callbacks from the request.
577                  */
578                 data = (eap_tunnel_data_t *) request_data_get(request,
579                                                               request->proxy,
580                                                               REQUEST_DATA_EAP_TUNNEL_CALLBACK);
581                 if (!data) {
582                         radlog_request(L_ERR, 0, request, "Failed to retrieve callback for tunneled session!");
583                         eap_handler_free(inst, handler);
584                         return RLM_MODULE_FAIL;
585                 }
586
587                 /*
588                  *      Do the callback...
589                  */
590                 RDEBUG2("Doing post-proxy callback");
591                 rcode = data->callback(handler, data->tls_session);
592                 free(data);
593                 if (rcode == 0) {
594                         RDEBUG2("Failed in post-proxy callback");
595                         eap_fail(handler);
596                         eap_handler_free(inst, handler);
597                         return RLM_MODULE_REJECT;
598                 }
599
600                 /*
601                  *      We are done, wrap the EAP-request in RADIUS to send
602                  *      with all other required radius attributes
603                  */
604                 eap_compose(handler);
605
606                 /*
607                  *      Add to the list only if it is EAP-Request, OR if
608                  *      it's LEAP, and a response.
609                  */
610                 if ((handler->eap_ds->request->code == PW_EAP_REQUEST) &&
611                     (handler->eap_ds->request->type.type >= PW_EAP_MD5)) {
612                         if (!eaplist_add(inst, handler)) {
613                                 eap_fail(handler);
614                                 eap_handler_free(inst, handler);
615                                 return RLM_MODULE_FAIL;
616                         }
617                         
618                 } else {        /* couldn't have been LEAP, there's no tunnel */
619                         RDEBUG2("Freeing handler");
620                         /* handler is not required any more, free it now */
621                         eap_handler_free(inst, handler);
622                 }
623
624                 /*
625                  *      If it's an Access-Accept, RFC 2869, Section 2.3.1
626                  *      says that we MUST include a User-Name attribute in the
627                  *      Access-Accept.
628                  */
629                 if ((request->reply->code == PW_AUTHENTICATION_ACK) &&
630                     request->username) {
631                         /*
632                          *      Doesn't exist, add it in.
633                          */
634                         vp = pairfind(request->reply->vps, PW_USER_NAME, 0);
635                         if (!vp) {
636                                 vp = pairmake("User-Name", request->username->vp_strvalue,
637                                               T_OP_EQ);
638                                 rad_assert(vp != NULL);
639                                 pairadd(&(request->reply->vps), vp);
640                         }
641                 }
642
643                 return RLM_MODULE_OK;
644         } else {
645                 RDEBUG2("No pre-existing handler found");
646         }
647
648         /*
649          *      There may be more than one Cisco-AVPair.
650          *      Ensure we find the one with the LEAP attribute.
651          */
652         vp = request->proxy_reply->vps;
653         for (;;) {
654                 /*
655                  *      Hmm... there's got to be a better way to
656                  *      discover codes for vendor attributes.
657                  *
658                  *      This is vendor Cisco (9), Cisco-AVPair
659                  *      attribute (1)
660                  */
661                 vp = pairfind(vp, 1, 9);
662                 if (!vp) {
663                         return RLM_MODULE_NOOP;
664                 }
665
666                 /*
667                  *      If it's "leap:session-key", then stop.
668                  *
669                  *      The format is VERY specific!
670                  */
671                 if (strncasecmp(vp->vp_strvalue, "leap:session-key=", 17) == 0) {
672                         break;
673                 }
674
675                 /*
676                  *      Not this AV-pair.  Go to the next one.
677                  */
678                 vp = vp->next;
679         }
680
681         /*
682          *      The format is very specific.
683          */
684         if (vp->length != 17 + 34) {
685                 RDEBUG2("Cisco-AVPair with leap:session-key has incorrect length %d: Expected %d",
686                        vp->length, 17 + 34);
687                 return RLM_MODULE_NOOP;
688         }
689
690         /*
691          *      Decrypt the session key, using the proxy data.
692          */
693         i = 34;                 /* starts off with 34 octets */
694         len = rad_tunnel_pwdecode(vp->vp_octets + 17, &i,
695                                   request->home_server->secret,
696                                   request->proxy->vector);
697
698         /*
699          *      FIXME: Assert that i == 16.
700          */
701
702         /*
703          *      Encrypt the session key again, using the request data.
704          */
705         rad_tunnel_pwencode(vp->vp_strvalue + 17, &len,
706                             request->client->secret,
707                             request->packet->vector);
708
709         return RLM_MODULE_UPDATED;
710 }
711 #endif
712
713 /*
714  *      The module name should be the only globally exported symbol.
715  *      That is, everything else should be 'static'.
716  */
717 module_t rlm_eap = {
718         RLM_MODULE_INIT,
719         "eap",
720         RLM_TYPE_CHECK_CONFIG_SAFE,     /* type */
721         eap_instantiate,                /* instantiation */
722         eap_detach,                     /* detach */
723         {
724                 eap_authenticate,       /* authentication */
725                 eap_authorize,          /* authorization */
726                 NULL,                   /* preaccounting */
727                 NULL,                   /* accounting */
728                 NULL,                   /* checksimul */
729                 NULL,                   /* pre-proxy */
730 #ifdef WITH_PROXY
731                 eap_post_proxy,         /* post-proxy */
732 #else
733                 NULL,
734 #endif
735                 NULL                    /* post-auth */
736         },
737 };