d1813abdbb782581d4a17debcc10f9095f268b4d
[freeradius.git] / src / modules / rlm_eap / types / rlm_eap_pwd / rlm_eap_pwd.c
1 /*
2  * Copyright (c) Dan Harkins, 2012
3  *
4  *  Copyright holder grants permission for redistribution and use in source
5  *  and binary forms, with or without modification, provided that the
6  *  following conditions are met:
7  *     1. Redistribution of source code must retain the above copyright
8  *      notice, this list of conditions, and the following disclaimer
9  *      in all source files.
10  *     2. Redistribution in binary form must retain the above copyright
11  *      notice, this list of conditions, and the following disclaimer
12  *      in the documentation and/or other materials provided with the
13  *      distribution.
14  *
15  *  "DISCLAIMER OF LIABILITY
16  *
17  *  THIS SOFTWARE IS PROVIDED BY DAN HARKINS ``AS IS'' AND
18  *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
19  *  THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20  *  PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE INDUSTRIAL LOUNGE BE LIABLE
21  *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23  *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  *  SUCH DAMAGE."
28  *
29  * This license and distribution terms cannot be changed. In other words,
30  * this code cannot simply be copied and put under a different distribution
31  * license (including the GNU public license).
32  */
33
34 RCSID("$Id$")
35 USES_APPLE_DEPRECATED_API       /* OpenSSL API has been deprecated by Apple */
36
37 #include "rlm_eap_pwd.h"
38
39 #include "eap_pwd.h"
40
41 #define MPPE_KEY_LEN    32
42 #define MSK_EMSK_LEN    (2*MPPE_KEY_LEN)
43
44 static CONF_PARSER pwd_module_config[] = {
45         { "group", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, group), "19" },
46         { "fragment_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, eap_pwd_t, fragment_size), "1020" },
47         { "server_id", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, server_id), NULL },
48         { "virtual_server", FR_CONF_OFFSET(PW_TYPE_STRING, eap_pwd_t, virtual_server), NULL },
49         CONF_PARSER_TERMINATOR
50 };
51
52 static int mod_detach (void *arg)
53 {
54         eap_pwd_t *inst;
55
56         inst = (eap_pwd_t *) arg;
57
58         if (inst->bnctx) BN_CTX_free(inst->bnctx);
59
60         return 0;
61 }
62
63 static int mod_instantiate (CONF_SECTION *cs, void **instance)
64 {
65         eap_pwd_t *inst;
66
67         *instance = inst = talloc_zero(cs, eap_pwd_t);
68         if (!inst) return -1;
69
70         if (cf_section_parse(cs, inst, pwd_module_config) < 0) {
71                 return -1;
72         }
73
74         if (inst->fragment_size < 100) {
75                 cf_log_err_cs(cs, "Fragment size is too small");
76                 return -1;
77         }
78
79         if ((inst->bnctx = BN_CTX_new()) == NULL) {
80                 cf_log_err_cs(cs, "Failed to get BN context");
81                 return -1;
82         }
83
84         return 0;
85 }
86
87 static int _free_pwd_session (pwd_session_t *session)
88 {
89         BN_clear_free(session->private_value);
90         BN_clear_free(session->peer_scalar);
91         BN_clear_free(session->my_scalar);
92         BN_clear_free(session->k);
93         EC_POINT_clear_free(session->my_element);
94         EC_POINT_clear_free(session->peer_element);
95         EC_GROUP_free(session->group);
96         EC_POINT_clear_free(session->pwe);
97         BN_clear_free(session->order);
98         BN_clear_free(session->prime);
99
100         return 0;
101 }
102
103 static int send_pwd_request (pwd_session_t *session, EAP_DS *eap_ds)
104 {
105         size_t len;
106         uint16_t totlen;
107         pwd_hdr *hdr;
108
109         len = (session->out_len - session->out_pos) + sizeof(pwd_hdr);
110         rad_assert(len > 0);
111         eap_ds->request->code = PW_EAP_REQUEST;
112         eap_ds->request->type.num = PW_EAP_PWD;
113         eap_ds->request->type.length = (len > session->mtu) ? session->mtu : len;
114         eap_ds->request->type.data = talloc_zero_array(eap_ds->request, uint8_t, eap_ds->request->type.length);
115         hdr = (pwd_hdr *)eap_ds->request->type.data;
116
117         switch (session->state) {
118         case PWD_STATE_ID_REQ:
119                 EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_ID);
120                 break;
121
122         case PWD_STATE_COMMIT:
123                 EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_COMMIT);
124                 break;
125
126         case PWD_STATE_CONFIRM:
127                 EAP_PWD_SET_EXCHANGE(hdr, EAP_PWD_EXCH_CONFIRM);
128                 break;
129
130         default:
131                 ERROR("rlm_eap_pwd: PWD state is invalid.  Can't send request");
132                 return 0;
133         }
134         /*
135          * are we fragmenting?
136          */
137         if (((session->out_len - session->out_pos) + sizeof(pwd_hdr)) > session->mtu) {
138                 EAP_PWD_SET_MORE_BIT(hdr);
139                 if (session->out_pos == 0) {
140                         /*
141                          * the first fragment, add the total length
142                          */
143                         EAP_PWD_SET_LENGTH_BIT(hdr);
144                         totlen = ntohs(session->out_len);
145                         memcpy(hdr->data, (char *)&totlen, sizeof(totlen));
146                         memcpy(hdr->data + sizeof(uint16_t),
147                                session->out,
148                                session->mtu - sizeof(pwd_hdr) - sizeof(uint16_t));
149                         session->out_pos += (session->mtu - sizeof(pwd_hdr) - sizeof(uint16_t));
150                 } else {
151                         /*
152                          * an intermediate fragment
153                          */
154                         memcpy(hdr->data, session->out + session->out_pos, (session->mtu - sizeof(pwd_hdr)));
155                         session->out_pos += (session->mtu - sizeof(pwd_hdr));
156                 }
157         } else {
158                 /*
159                  * either it's not a fragment or it's the last fragment.
160                  * The out buffer isn't needed anymore though so get rid of it.
161                  */
162                 memcpy(hdr->data, session->out + session->out_pos,
163                 (session->out_len - session->out_pos));
164                 talloc_free(session->out);
165                 session->out = NULL;
166                 session->out_pos = session->out_len = 0;
167         }
168         return 1;
169 }
170
171 static int mod_session_init (void *instance, eap_handler_t *handler)
172 {
173         pwd_session_t *session;
174         eap_pwd_t *inst = (eap_pwd_t *)instance;
175         VALUE_PAIR *vp;
176         pwd_id_packet_t *packet;
177
178         if (!inst || !handler) {
179                 ERROR("rlm_eap_pwd: Initiate, NULL data provided");
180                 return 0;
181         }
182
183         /*
184         * make sure the server's been configured properly
185         */
186         if (!inst->server_id) {
187                 ERROR("rlm_eap_pwd: Server ID is not configured");
188                 return 0;
189         }
190         switch (inst->group) {
191         case 19:
192         case 20:
193         case 21:
194         case 25:
195         case 26:
196                 break;
197
198         default:
199                 ERROR("rlm_eap_pwd: Group is not supported");
200                 return 0;
201         }
202
203         if ((session = talloc_zero(handler, pwd_session_t)) == NULL) return 0;
204         talloc_set_destructor(session, _free_pwd_session);
205         /*
206          * set things up so they can be free'd reliably
207          */
208         session->group_num = inst->group;
209         session->private_value = NULL;
210         session->peer_scalar = NULL;
211         session->my_scalar = NULL;
212         session->k = NULL;
213         session->my_element = NULL;
214         session->peer_element = NULL;
215         session->group = NULL;
216         session->pwe = NULL;
217         session->order = NULL;
218         session->prime = NULL;
219
220         /*
221          *      The admin can dynamically change the MTU.
222          */
223         session->mtu = inst->fragment_size;
224         vp = fr_pair_find_by_num(handler->request->packet->vps, PW_FRAMED_MTU, 0, TAG_ANY);
225
226         /*
227          *      session->mtu is *our* MTU.  We need to subtract off the EAP
228          *      overhead.
229          *
230          *      9 = 4 (EAPOL header) + 4 (EAP header) + 1 (EAP type)
231          *
232          *      The fragmentation code deals with the included length
233          *      so we don't need to subtract that here.
234          */
235         if (vp && (vp->vp_integer > 100) && (vp->vp_integer < session->mtu)) {
236                 session->mtu = vp->vp_integer - 9;
237         }
238
239         session->state = PWD_STATE_ID_REQ;
240         session->in = NULL;
241         session->out_pos = 0;
242         handler->opaque = session;
243
244         /*
245          * construct an EAP-pwd-ID/Request
246          */
247         session->out_len = sizeof(pwd_id_packet_t) + strlen(inst->server_id);
248         if ((session->out = talloc_zero_array(session, uint8_t, session->out_len)) == NULL) {
249                 return 0;
250         }
251
252         packet = (pwd_id_packet_t *)session->out;
253         packet->group_num = htons(session->group_num);
254         packet->random_function = EAP_PWD_DEF_RAND_FUN;
255         packet->prf = EAP_PWD_DEF_PRF;
256         session->token = fr_rand();
257         memcpy(packet->token, (char *)&session->token, 4);
258         packet->prep = EAP_PWD_PREP_NONE;
259         memcpy(packet->identity, inst->server_id, session->out_len - sizeof(pwd_id_packet_t) );
260
261         handler->stage = PROCESS;
262
263         return send_pwd_request(session, handler->eap_ds);
264 }
265
266 static int mod_process(void *arg, eap_handler_t *handler)
267 {
268         pwd_session_t *session;
269         pwd_hdr *hdr;
270         pwd_id_packet_t *packet;
271         eap_packet_t *response;
272         REQUEST *request, *fake;
273         VALUE_PAIR *pw, *vp;
274         EAP_DS *eap_ds;
275         size_t in_len;
276         int ret = 0;
277         eap_pwd_t *inst = (eap_pwd_t *)arg;
278         uint16_t offset;
279         uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN];
280         uint8_t peer_confirm[SHA256_DIGEST_LENGTH];
281         BIGNUM *x = NULL, *y = NULL;
282         char *p;
283
284         if (!handler || ((eap_ds = handler->eap_ds) == NULL) || !inst) return 0;
285
286         session = (pwd_session_t *)handler->opaque;
287         request = handler->request;
288         response = handler->eap_ds->response;
289         hdr = (pwd_hdr *)response->type.data;
290
291         /*
292          *      The header must be at least one byte.
293          */
294         if (!hdr || (response->type.length < sizeof(pwd_hdr))) {
295                 RDEBUG("Packet with insufficient data");
296                 return 0;
297         }
298
299         in = hdr->data;
300         in_len = response->type.length - sizeof(pwd_hdr);
301
302         /*
303         * see if we're fragmenting, if so continue until we're done
304         */
305         if (session->out_pos) {
306                 if (in_len) RDEBUG2("pwd got something more than an ACK for a fragment");
307
308                 return send_pwd_request(session, eap_ds);
309         }
310
311         /*
312         * the first fragment will have a total length, make a
313         * buffer to hold all the fragments
314         */
315         if (EAP_PWD_GET_LENGTH_BIT(hdr)) {
316                 if (session->in) {
317                         RDEBUG2("pwd already alloced buffer for fragments");
318                         return 0;
319                 }
320
321                 if (in_len < 2) {
322                         RDEBUG("Invalid packet: length bit set, but no length field");
323                         return 0;
324                 }
325
326                 session->in_len = ntohs(in[0] * 256 | in[1]);
327                 if ((session->in = talloc_zero_array(session, uint8_t, session->in_len)) == NULL) {
328                         RDEBUG2("pwd cannot allocate %zd buffer to hold fragments",
329                                 session->in_len);
330                         return 0;
331                 }
332                 memset(session->in, 0, session->in_len);
333                 session->in_pos = 0;
334                 in += sizeof(uint16_t);
335                 in_len -= sizeof(uint16_t);
336         }
337
338         /*
339          * all fragments, including the 1st will have the M(ore) bit set,
340          * buffer those fragments!
341          */
342         if (EAP_PWD_GET_MORE_BIT(hdr)) {
343                 rad_assert(session->in != NULL);
344
345                 if ((session->in_pos + in_len) > session->in_len) {
346                         RDEBUG2("Fragment overflows packet.");
347                         return 0;
348                 }
349
350                 memcpy(session->in + session->in_pos, in, in_len);
351                 session->in_pos += in_len;
352
353                 /*
354                  * send back an ACK for this fragment
355                  */
356                 exch = EAP_PWD_GET_EXCHANGE(hdr);
357                 eap_ds->request->code = PW_EAP_REQUEST;
358                 eap_ds->request->type.num = PW_EAP_PWD;
359                 eap_ds->request->type.length = sizeof(pwd_hdr);
360                 if ((eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, sizeof(pwd_hdr))) == NULL) {
361                         return 0;
362                 }
363                 hdr = (pwd_hdr *)eap_ds->request->type.data;
364                 EAP_PWD_SET_EXCHANGE(hdr, exch);
365                 return 1;
366         }
367
368
369         if (session->in) {
370                 /*
371                  * the last fragment...
372                  */
373                 if ((session->in_pos + in_len) > session->in_len) {
374                         RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent");
375                         return 0;
376                 }
377                 memcpy(session->in + session->in_pos, in, in_len);
378                 in = session->in;
379                 in_len = session->in_len;
380         }
381
382         switch (session->state) {
383         case PWD_STATE_ID_REQ:
384                 if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_ID) {
385                         RDEBUG2("pwd exchange is incorrect: not ID");
386                         return 0;
387                 }
388
389                 packet = (pwd_id_packet_t *) in;
390                 if (in_len < sizeof(packet)) {
391                         RDEBUG("Packet is too small (%zd < %zd).", in_len, sizeof(packet));
392                         return 0;
393                 }
394
395                 if ((packet->prf != EAP_PWD_DEF_PRF) ||
396                     (packet->random_function != EAP_PWD_DEF_RAND_FUN) ||
397                     (packet->prep != EAP_PWD_PREP_NONE) ||
398                     (CRYPTO_memcmp(packet->token, &session->token, 4)) ||
399                     (packet->group_num != ntohs(session->group_num))) {
400                         RDEBUG2("pwd id response is invalid");
401                         return 0;
402                 }
403                 /*
404                  * we've agreed on the ciphersuite, record it...
405                  */
406                 ptr = (uint8_t *)&session->ciphersuite;
407                 memcpy(ptr, (char *)&packet->group_num, sizeof(uint16_t));
408                 ptr += sizeof(uint16_t);
409                 *ptr = EAP_PWD_DEF_RAND_FUN;
410                 ptr += sizeof(uint8_t);
411                 *ptr = EAP_PWD_DEF_PRF;
412
413                 session->peer_id_len = in_len - sizeof(pwd_id_packet_t);
414                 if (session->peer_id_len >= sizeof(session->peer_id)) {
415                         RDEBUG2("pwd id response is malformed");
416                         return 0;
417                 }
418
419                 memcpy(session->peer_id, packet->identity, session->peer_id_len);
420                 session->peer_id[session->peer_id_len] = '\0';
421
422                 /*
423                  * make fake request to get the password for the usable ID
424                  */
425                 if ((fake = request_alloc_fake(handler->request)) == NULL) {
426                         RDEBUG("pwd unable to create fake request!");
427                         return 0;
428                 }
429                 fake->username = pair_make_request("User-Name", NULL, T_OP_EQ);
430                 if (!fake->username) {
431                         RDEBUG("pwd unanable to create value pair for username!");
432                         talloc_free(fake);
433                         return 0;
434                 }
435                 fake->username->vp_length = session->peer_id_len;
436                 fake->username->vp_strvalue = p = talloc_array(fake->username, char, fake->username->vp_length + 1);
437                 memcpy(p, session->peer_id, session->peer_id_len);
438                 p[fake->username->vp_length] = '\0';
439
440                 fr_pair_add(&fake->packet->vps, fake->username);
441
442                 if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
443                         fake->server = vp->vp_strvalue;
444                 } else if (inst->virtual_server) {
445                         fake->server = inst->virtual_server;
446                 } /* else fake->server == request->server */
447
448                 RDEBUG("Sending tunneled request");
449                 rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL);
450
451                 if (fake->server) {
452                         RDEBUG("server %s {", fake->server);
453                 } else {
454                         RDEBUG("server {");
455                 }
456
457                 /*
458                  *      Call authorization recursively, which will
459                  *      get the password.
460                  */
461                 RINDENT();
462                 process_authorize(0, fake);
463                 REXDENT();
464
465                 /*
466                  *      Note that we don't do *anything* with the reply
467                  *      attributes.
468                  */
469                 if (fake->server) {
470                         RDEBUG("} # server %s", fake->server);
471                 } else {
472                         RDEBUG("}");
473                 }
474
475                 RDEBUG("Got tunneled reply code %d", fake->reply->code);
476                 rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL);
477
478                 if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) {
479                         DEBUG2("failed to find password for %s to do pwd authentication",
480                         session->peer_id);
481                         talloc_free(fake);
482                         return 0;
483                 }
484
485                 if (compute_password_element(session, session->group_num,
486                                              pw->data.strvalue, strlen(pw->data.strvalue),
487                                              inst->server_id, strlen(inst->server_id),
488                                              session->peer_id, strlen(session->peer_id),
489                                              &session->token)) {
490                         DEBUG2("failed to obtain password element");
491                         talloc_free(fake);
492                         return 0;
493                 }
494                 TALLOC_FREE(fake);
495
496                 /*
497                  * compute our scalar and element
498                  */
499                 if (compute_scalar_element(session, inst->bnctx)) {
500                         DEBUG2("failed to compute server's scalar and element");
501                         return 0;
502                 }
503
504                 if (((x = BN_new()) == NULL) || ((y = BN_new()) == NULL)) {
505                         DEBUG2("server point allocation failed");
506                         return 0;
507                 }
508
509                 /*
510                  * element is a point, get both coordinates: x and y
511                  */
512                 if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y,
513                                                          inst->bnctx)) {
514                         DEBUG2("server point assignment failed");
515                         BN_clear_free(x);
516                         BN_clear_free(y);
517                         return 0;
518                 }
519
520                 /*
521                  * construct request
522                  */
523                 session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime));
524                 if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) {
525                         return 0;
526                 }
527                 memset(session->out, 0, session->out_len);
528
529                 ptr = session->out;
530                 offset = BN_num_bytes(session->prime) - BN_num_bytes(x);
531                 BN_bn2bin(x, ptr + offset);
532
533                 ptr += BN_num_bytes(session->prime);
534                 offset = BN_num_bytes(session->prime) - BN_num_bytes(y);
535                 BN_bn2bin(y, ptr + offset);
536
537                 ptr += BN_num_bytes(session->prime);
538                 offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar);
539                 BN_bn2bin(session->my_scalar, ptr + offset);
540
541                 session->state = PWD_STATE_COMMIT;
542                 ret = send_pwd_request(session, eap_ds);
543                 break;
544
545                 case PWD_STATE_COMMIT:
546                 if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) {
547                         RDEBUG2("pwd exchange is incorrect: not commit!");
548                         return 0;
549                 }
550
551                 /*
552                  * process the peer's commit and generate the shared key, k
553                  */
554                 if (process_peer_commit(session, in, in_len, inst->bnctx)) {
555                         RDEBUG2("failed to process peer's commit");
556                         return 0;
557                 }
558
559                 /*
560                  * compute our confirm blob
561                  */
562                 if (compute_server_confirm(session, session->my_confirm, inst->bnctx)) {
563                         ERROR("rlm_eap_pwd: failed to compute confirm!");
564                         return 0;
565                 }
566
567                 /*
568                  * construct a response...which is just our confirm blob
569                  */
570                 session->out_len = SHA256_DIGEST_LENGTH;
571                 if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) {
572                         return 0;
573                 }
574
575                 memset(session->out, 0, session->out_len);
576                 memcpy(session->out, session->my_confirm, SHA256_DIGEST_LENGTH);
577
578                 session->state = PWD_STATE_CONFIRM;
579                 ret = send_pwd_request(session, eap_ds);
580                 break;
581
582         case PWD_STATE_CONFIRM:
583                 if (in_len < SHA256_DIGEST_LENGTH) {
584                         RDEBUG("Peer confirm is too short (%zd < %d)",
585                                in_len, SHA256_DIGEST_LENGTH);
586                         return 0;
587                 }
588
589                 if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_CONFIRM) {
590                         RDEBUG2("pwd exchange is incorrect: not commit!");
591                         return 0;
592                 }
593                 if (compute_peer_confirm(session, peer_confirm, inst->bnctx)) {
594                         RDEBUG2("pwd exchange cannot compute peer's confirm");
595                         return 0;
596                 }
597                 if (CRYPTO_memcmp(peer_confirm, in, SHA256_DIGEST_LENGTH)) {
598                         RDEBUG2("pwd exchange fails: peer confirm is incorrect!");
599                         return 0;
600                 }
601                 if (compute_keys(session, peer_confirm, msk, emsk)) {
602                         RDEBUG2("pwd exchange cannot generate (E)MSK!");
603                         return 0;
604                 }
605                 eap_ds->request->code = PW_EAP_SUCCESS;
606                 /*
607                  * return the MSK (in halves)
608                  */
609                 eap_add_reply(handler->request, "MS-MPPE-Recv-Key", msk, MPPE_KEY_LEN);
610                 eap_add_reply(handler->request, "MS-MPPE-Send-Key", msk + MPPE_KEY_LEN, MPPE_KEY_LEN);
611
612                 ret = 1;
613                 break;
614
615         default:
616                 RDEBUG2("unknown PWD state");
617                 return 0;
618         }
619
620         /*
621          * we processed the buffered fragments, get rid of them
622          */
623         if (session->in) {
624                 talloc_free(session->in);
625                 session->in = NULL;
626         }
627
628         return ret;
629 }
630
631 extern rlm_eap_module_t rlm_eap_pwd;
632 rlm_eap_module_t rlm_eap_pwd = {
633         .name           = "eap_pwd",
634         .instantiate    = mod_instantiate,      /* Create new submodule instance */
635         .session_init   = mod_session_init,             /* Create the initial request */
636         .process        = mod_process,          /* Process next round of EAP method */
637         .detach         = mod_detach            /* Destroy the submodule instance */
638 };
639