Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_rest / rlm_rest.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_rest.c
19  * @brief Integrate FreeRADIUS with RESTfull APIs
20  *
21  * @copyright 2012-2014  Arran Cudbard-Bell <arran.cudbardb@freeradius.org>
22  */
23 RCSID("$Id$")
24
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/modules.h>
27 #include <freeradius-devel/token.h>
28 #include <freeradius-devel/rad_assert.h>
29
30 #include <ctype.h>
31 #include "rest.h"
32
33 /*
34  *      TLS Configuration
35  */
36 static CONF_PARSER tls_config[] = {
37         { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_file), NULL },
38         { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_path), NULL },
39         { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_certificate_file), NULL },
40         { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_private_key_file), NULL },
41         { "private_key_password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_rest_section_t, tls_private_key_password), NULL },
42         { "random_file", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, tls_random_file), NULL },
43         { "check_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert), "yes" },
44         { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert_cn), "yes" },
45
46         { NULL, -1, 0, NULL, NULL }
47 };
48
49 /*
50  *      A mapping of configuration file names to internal variables.
51  *
52  *      Note that the string is dynamically allocated, so it MUST
53  *      be freed.  When the configuration file parse re-reads the string,
54  *      it free's the old one, and strdup's the new one, placing the pointer
55  *      to the strdup'd string into 'config.string'.  This gets around
56  *      buffer over-flows.
57  */
58 static const CONF_PARSER section_config[] = {
59         { "uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, uri), ""   },
60         { "method", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, method_str), "GET" },
61         { "body", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, body_str), "none" },
62         { "data", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, data), NULL },
63
64         /* User authentication */
65         { "auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, auth_str), "none" },
66         { "username", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, username), NULL },
67         { "password", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, password), NULL },
68         { "require_auth", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, require_auth), "no" },
69
70         /* Transfer configuration */
71         { "timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_rest_section_t, timeout), "4" },
72         { "chunk", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_rest_section_t, chunk), "0" },
73
74         /* TLS Parameters */
75         { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
76
77         { NULL, -1, 0, NULL, NULL }
78 };
79
80 static const CONF_PARSER module_config[] = {
81         { "connect_uri", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_t, connect_uri), NULL },
82
83         { NULL, -1, 0, NULL, NULL }
84 };
85
86 static int rlm_rest_perform(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle, REQUEST *request,
87                             char const *username, char const *password)
88 {
89         ssize_t uri_len;
90         char *uri = NULL;
91
92         int ret;
93
94         RDEBUG("Expanding URI components");
95
96         /*
97          *  Build xlat'd URI, this allows REST servers to be specified by
98          *  request attributes.
99          */
100         uri_len = rest_uri_build(&uri, instance, request, section->uri);
101         if (uri_len <= 0) return -1;
102
103         RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section->method, NULL), uri);
104
105         /*
106          *  Configure various CURL options, and initialise the read/write
107          *  context data.
108          */
109         ret = rest_request_config(instance, section, request, handle, section->method, section->body,
110                                   uri, username, password);
111         talloc_free(uri);
112         if (ret < 0) return -1;
113
114         /*
115          *  Send the CURL request, pre-parse headers, aggregate incoming
116          *  HTTP body data into a single contiguous buffer.
117          */
118         ret = rest_request_perform(instance, section, request, handle);
119         if (ret < 0) return -1;
120
121         return 0;
122 }
123
124 static void rlm_rest_cleanup(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle)
125 {
126         rest_request_cleanup(instance, section, handle);
127 };
128
129 /*
130  *      Simple xlat to read text data from a URL
131  */
132 static ssize_t rest_xlat(void *instance, REQUEST *request,
133                          char const *fmt, char *out, size_t freespace)
134 {
135         rlm_rest_t      *inst = instance;
136         void            *handle;
137         int             hcode;
138         int             ret;
139         ssize_t         len, outlen = 0;
140         char            *uri = NULL;
141         char const      *p = fmt, *q;
142         char const      *body;
143         http_method_t   method;
144
145         /* There are no configurable parameters other than the URI */
146         rlm_rest_section_t section = {
147                 .name = "xlat",
148                 .method = HTTP_METHOD_GET,
149                 .body = HTTP_BODY_NONE,
150                 .body_str = "application/x-www-form-urlencoded",
151                 .require_auth = false,
152                 .timeout = 4,
153                 .force_to = HTTP_BODY_PLAIN
154         };
155         *out = '\0';
156
157         rad_assert(fmt);
158
159         RDEBUG("Expanding URI components");
160
161         handle = fr_connection_get(inst->conn_pool);
162         if (!handle) return -1;
163
164         /*
165          *  Extract the method from the start of the format string (if there is one)
166          */
167         method = fr_substr2int(http_method_table, p, HTTP_METHOD_UNKNOWN, -1);
168         if (method != HTTP_METHOD_UNKNOWN) {
169                 section.method = method;
170                 p += strlen(http_method_table[method].name);
171         }
172
173         /*
174          *  Trim whitespace
175          */
176         while (isspace(*p) && p++);
177
178         /*
179          *  Unescape parts of xlat'd URI, this allows REST servers to be specified by
180          *  request attributes.
181          */
182         len = rest_uri_host_unescape(&uri, instance, request, handle, p);
183         if (len <= 0) {
184                 outlen = -1;
185                 goto finish;
186         }
187
188         /*
189          *  Extract freeform body data (url can't contain spaces)
190          */
191         q = strchr(p, ' ');
192         if (q && (*++q != '\0')) {
193                 section.body = HTTP_BODY_CUSTOM_LITERAL;
194                 section.data = q;
195         }
196
197         RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri);
198
199         /*
200          *  Configure various CURL options, and initialise the read/write
201          *  context data.
202          *
203          *  @todo We could extract the User-Name and password from the URL string.
204          */
205         ret = rest_request_config(instance, &section, request, handle, section.method, section.body,
206                                   uri, NULL, NULL);
207         talloc_free(uri);
208         if (ret < 0) return -1;
209
210         /*
211          *  Send the CURL request, pre-parse headers, aggregate incoming
212          *  HTTP body data into a single contiguous buffer.
213          */
214         ret = rest_request_perform(instance, &section, request, handle);
215         if (ret < 0) return -1;
216
217         hcode = rest_get_handle_code(handle);
218         switch (hcode) {
219         case 404:
220         case 410:
221         case 403:
222         case 401:
223         {
224 error:
225                 len = rest_get_handle_data(&body, handle);
226                 if (len > 0) REDEBUG("%s", body);
227                 outlen = -1;
228                 goto finish;
229         }
230         case 204:
231                 goto finish;
232
233         default:
234                 /*
235                  *      Attempt to parse content if there was any.
236                  */
237                 if ((hcode >= 200) && (hcode < 300)) {
238                         break;
239                 } else if (hcode < 500) {
240                         outlen = -2;
241                         goto error;
242                 } else {
243                         outlen = -1;
244                         goto error;
245                 }
246         }
247
248         len = rest_get_handle_data(&body, handle);
249         if ((size_t) len >= freespace) {
250                 REDEBUG("Insufficient space to write HTTP response, needed %zu bytes, have %zu bytes", len + 1,
251                         freespace);
252                 outlen = -1;
253                 goto finish;
254         }
255         if (len > 0) {
256                 outlen = len;
257                 strlcpy(out, body, len + 1);    /* strlcpy takes the size of the buffer */
258         }
259
260 finish:
261         rlm_rest_cleanup(instance, &section, handle);
262
263         fr_connection_release(inst->conn_pool, handle);
264
265         return outlen;
266 }
267
268 /*
269  *      Find the named user in this modules database.  Create the set
270  *      of attribute-value pairs to check and reply with for this user
271  *      from the database. The authentication code only needs to check
272  *      the password, the rest is done here.
273  */
274 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
275 {
276         rlm_rest_t *inst = instance;
277         rlm_rest_section_t *section = &inst->authorize;
278
279         void *handle;
280         int hcode;
281         int rcode = RLM_MODULE_OK;
282         int ret;
283
284         handle = fr_connection_get(inst->conn_pool);
285         if (!handle) return RLM_MODULE_FAIL;
286
287         ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL);
288         if (ret < 0) {
289                 rcode = RLM_MODULE_FAIL;
290                 goto finish;
291         }
292
293         hcode = rest_get_handle_code(handle);
294         switch (hcode) {
295         case 404:
296         case 410:
297                 rcode = RLM_MODULE_NOTFOUND;
298                 break;
299
300         case 403:
301                 rcode = RLM_MODULE_USERLOCK;
302                 break;
303
304         case 401:
305                 /*
306                  *      Attempt to parse content if there was any.
307                  */
308                 ret = rest_response_decode(inst, section, request, handle);
309                 if (ret < 0) {
310                         rcode = RLM_MODULE_FAIL;
311                         break;
312                 }
313
314                 rcode = RLM_MODULE_REJECT;
315                 break;
316
317         case 204:
318                 rcode = RLM_MODULE_OK;
319                 break;
320
321         default:
322                 /*
323                  *      Attempt to parse content if there was any.
324                  */
325                 if ((hcode >= 200) && (hcode < 300)) {
326                         ret = rest_response_decode(inst, section, request, handle);
327                         if (ret < 0)       rcode = RLM_MODULE_FAIL;
328                         else if (ret == 0) rcode = RLM_MODULE_OK;
329                         else               rcode = RLM_MODULE_UPDATED;
330                         break;
331                 } else if (hcode < 500) {
332                         rcode = RLM_MODULE_INVALID;
333                 } else {
334                         rcode = RLM_MODULE_FAIL;
335                 }
336         }
337
338 finish:
339         rlm_rest_cleanup(instance, section, handle);
340
341         fr_connection_release(inst->conn_pool, handle);
342
343         return rcode;
344 }
345
346 /*
347  *      Authenticate the user with the given password.
348  */
349 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED REQUEST *request)
350 {
351         rlm_rest_t *inst = instance;
352         rlm_rest_section_t *section = &inst->authenticate;
353
354         void *handle;
355         int hcode;
356         int rcode = RLM_MODULE_OK;
357         int ret;
358
359         VALUE_PAIR const *username;
360         VALUE_PAIR const *password;
361
362         username = request->username;
363         if (!request->username) {
364                 REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request");
365
366                 return RLM_MODULE_INVALID;
367         }
368
369         password = request->password;
370         if (!password ||
371             (password->da->attr != PW_USER_PASSWORD)) {
372                 REDEBUG("You set 'Auth-Type = REST' for a request that does not contain a User-Password attribute!");
373                 return RLM_MODULE_INVALID;
374         }
375
376         handle = fr_connection_get(inst->conn_pool);
377         if (!handle) return RLM_MODULE_FAIL;
378
379         ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue);
380         if (ret < 0) {
381                 rcode = RLM_MODULE_FAIL;
382                 goto finish;
383         }
384
385         hcode = rest_get_handle_code(handle);
386         switch (hcode) {
387         case 404:
388         case 410:
389                 rcode = RLM_MODULE_NOTFOUND;
390                 break;
391
392         case 403:
393                 rcode = RLM_MODULE_USERLOCK;
394                 break;
395
396         case 401:
397                 /*
398                  *      Attempt to parse content if there was any.
399                  */
400                 ret = rest_response_decode(inst, section, request, handle);
401                 if (ret < 0) {
402                         rcode = RLM_MODULE_FAIL;
403                         break;
404                 }
405
406                 rcode = RLM_MODULE_REJECT;
407                 break;
408
409         case 204:
410                 rcode = RLM_MODULE_OK;
411                 break;
412
413         default:
414                 /*
415                  *      Attempt to parse content if there was any.
416                  */
417                 if ((hcode >= 200) && (hcode < 300)) {
418                         ret = rest_response_decode(inst, section, request, handle);
419                         if (ret < 0)       rcode = RLM_MODULE_FAIL;
420                         else if (ret == 0) rcode = RLM_MODULE_OK;
421                         else               rcode = RLM_MODULE_UPDATED;
422                         break;
423                 } else if (hcode < 500) {
424                         rcode = RLM_MODULE_INVALID;
425                 } else {
426                         rcode = RLM_MODULE_FAIL;
427                 }
428         }
429
430 finish:
431         rlm_rest_cleanup(instance, section, handle);
432
433         fr_connection_release(inst->conn_pool, handle);
434
435         return rcode;
436 }
437
438 /*
439  *      Send accounting info to a REST API endpoint
440  */
441 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, UNUSED REQUEST *request)
442 {
443         rlm_rest_t *inst = instance;
444         rlm_rest_section_t *section = &inst->accounting;
445
446         void *handle;
447         int hcode;
448         int rcode = RLM_MODULE_OK;
449         int ret;
450
451         handle = fr_connection_get(inst->conn_pool);
452         if (!handle) return RLM_MODULE_FAIL;
453
454         ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
455         if (ret < 0) {
456                 rcode = RLM_MODULE_FAIL;
457                 goto finish;
458         }
459
460         hcode = rest_get_handle_code(handle);
461         if (hcode >= 500) {
462                 rcode = RLM_MODULE_FAIL;
463         } else if (hcode == 204) {
464                 rcode = RLM_MODULE_OK;
465         } else if ((hcode >= 200) && (hcode < 300)) {
466                 ret = rest_response_decode(inst, section, request, handle);
467                 if (ret < 0)       rcode = RLM_MODULE_FAIL;
468                 else if (ret == 0) rcode = RLM_MODULE_OK;
469                 else               rcode = RLM_MODULE_UPDATED;
470         } else {
471                 rcode = RLM_MODULE_INVALID;
472         }
473
474 finish:
475         rlm_rest_cleanup(inst, section, handle);
476
477         fr_connection_release(inst->conn_pool, handle);
478
479         return rcode;
480 }
481
482 /*
483  *      Send post-auth info to a REST API endpoint
484  */
485 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, UNUSED REQUEST *request)
486 {
487         rlm_rest_t *inst = instance;
488         rlm_rest_section_t *section = &inst->post_auth;
489
490         void *handle;
491         int hcode;
492         int rcode = RLM_MODULE_OK;
493         int ret;
494
495         handle = fr_connection_get(inst->conn_pool);
496         if (!handle) return RLM_MODULE_FAIL;
497
498         ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
499         if (ret < 0) {
500                 rcode = RLM_MODULE_FAIL;
501                 goto finish;
502         }
503
504         hcode = rest_get_handle_code(handle);
505         if (hcode >= 500) {
506                 rcode = RLM_MODULE_FAIL;
507         } else if (hcode == 204) {
508                 rcode = RLM_MODULE_OK;
509         } else if ((hcode >= 200) && (hcode < 300)) {
510                 ret = rest_response_decode(inst, section, request, handle);
511                 if (ret < 0)       rcode = RLM_MODULE_FAIL;
512                 else if (ret == 0) rcode = RLM_MODULE_OK;
513                 else               rcode = RLM_MODULE_UPDATED;
514         } else {
515                 rcode = RLM_MODULE_INVALID;
516         }
517
518 finish:
519         rlm_rest_cleanup(inst, section, handle);
520
521         fr_connection_release(inst->conn_pool, handle);
522
523         return rcode;
524 }
525
526 static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, rlm_components_t comp)
527 {
528         CONF_SECTION *cs;
529
530         char const *name = section_type_value[comp].section;
531
532         cs = cf_section_sub_find(parent, name);
533         if (!cs) {
534                 /* TODO: Should really setup section with default values */
535                 return 0;
536         }
537
538         if (cf_section_parse(cs, config, section_config) < 0) {
539                 return -1;
540         }
541
542         /*
543          *  Add section name (Maybe add to headers later?).
544          */
545         config->name = name;
546
547         /*
548          *  Sanity check
549          */
550          if ((config->username && !config->password) || (!config->username && config->password)) {
551                 cf_log_err_cs(cs, "'username' and 'password' must both be set or both be absent");
552
553                 return -1;
554          }
555
556         /*
557          *  Convert HTTP method auth and body type strings into their integer equivalents.
558          */
559         config->auth = fr_str2int(http_auth_table, config->auth_str, HTTP_AUTH_UNKNOWN);
560         if (config->auth == HTTP_AUTH_UNKNOWN) {
561                 cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str);
562
563                 return -1;
564         } else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) {
565                 cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build "
566                               "configuration, then recompile this module", config->auth_str);
567
568                 return -1;
569         }
570
571         config->method = fr_str2int(http_method_table, config->method_str, HTTP_METHOD_CUSTOM);
572
573         /*
574          *  We don't have any custom user data, so we need to select the right encoder based
575          *  on the body type.
576          *
577          *  To make this slightly more/less confusing, we accept both canonical body_types,
578          *  and content_types.
579          */
580         if (!config->data) {
581                 config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
582                 if (config->body == HTTP_BODY_UNKNOWN) {
583                         config->body = fr_str2int(http_content_type_table, config->body_str, HTTP_BODY_UNKNOWN);
584                 }
585
586                 if (config->body == HTTP_BODY_UNKNOWN) {
587                         cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str);
588                         return -1;
589                 }
590
591                 switch (http_body_type_supported[config->body]) {
592                 case HTTP_BODY_UNSUPPORTED:
593                         cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches",
594                                       config->body_str);
595                         return -1;
596
597                 case HTTP_BODY_INVALID:
598                         cf_log_err_cs(cs, "Invalid HTTP body type.  \"%s\" is not a valid web API data "
599                                       "markup format", config->body_str);
600                         return -1;
601
602                 default:
603                         break;
604                 }
605         /*
606          *  We have custom body data so we set HTTP_BODY_CUSTOM_XLAT, but also need to try and
607          *  figure out what content-type to use. So if they've used the canonical form we
608          *  need to convert it back into a proper HTTP content_type value.
609          */
610         } else {
611                 http_body_type_t body;
612
613                 config->body = HTTP_BODY_CUSTOM_XLAT;
614
615                 body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
616                 if (body != HTTP_BODY_UNKNOWN) {
617                         config->body_str = fr_int2str(http_content_type_table, body, config->body_str);
618                 }
619         }
620
621         return 0;
622 }
623
624 /*
625  *      Do any per-module initialization that is separate to each
626  *      configured instance of the module.  e.g. set up connections
627  *      to external databases, read configuration files, set up
628  *      dictionary entries, etc.
629  *
630  *      If configuration information is given in the config section
631  *      that must be referenced in later calls, store a handle to it
632  *      in *instance otherwise put a null pointer there.
633  */
634 static int mod_instantiate(CONF_SECTION *conf, void *instance)
635 {
636         rlm_rest_t *inst = instance;
637         char const *xlat_name;
638
639         xlat_name = cf_section_name2(conf);
640         if (!xlat_name) {
641                 xlat_name = cf_section_name1(conf);
642         }
643
644         inst->xlat_name = xlat_name;
645
646         /*
647          *      Register the rest xlat function
648          */
649         xlat_register(inst->xlat_name, rest_xlat, rest_uri_escape, inst);
650
651         /*
652          *      Parse sub-section configs.
653          */
654         if (
655                 (parse_sub_section(conf, &inst->authorize, RLM_COMPONENT_AUTZ) < 0) ||
656                 (parse_sub_section(conf, &inst->authenticate, RLM_COMPONENT_AUTH) < 0) ||
657                 (parse_sub_section(conf, &inst->accounting, RLM_COMPONENT_ACCT) < 0) ||
658
659 /* @todo add behaviour for checksimul */
660 /*              (parse_sub_section(conf, &inst->checksimul, RLM_COMPONENT_SESS) < 0) || */
661                 (parse_sub_section(conf, &inst->post_auth, RLM_COMPONENT_POST_AUTH) < 0))
662         {
663                 return -1;
664         }
665
666         /*
667          *      Initialise REST libraries.
668          */
669         if (rest_init(inst) < 0) {
670                 return -1;
671         }
672
673         inst->conn_pool = fr_connection_pool_init(conf, inst, mod_conn_create, mod_conn_alive, NULL, NULL);
674         if (!inst->conn_pool) {
675                 return -1;
676         }
677
678         return 0;
679 }
680
681 /*
682  *      Only free memory we allocated.  The strings allocated via
683  *      cf_section_parse() do not need to be freed.
684  */
685 static int mod_detach(void *instance)
686 {
687         rlm_rest_t *inst = instance;
688
689         fr_connection_pool_delete(inst->conn_pool);
690
691         xlat_unregister(inst->xlat_name, rest_xlat, instance);
692
693         /* Free any memory used by libcurl */
694         rest_cleanup();
695
696         return 0;
697 }
698
699 /*
700  *      The module name should be the only globally exported symbol.
701  *      That is, everything else should be 'static'.
702  *
703  *      If the module needs to temporarily modify it's instantiation
704  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
705  *      The server will then take care of ensuring that the module
706  *      is single-threaded.
707  */
708 module_t rlm_rest = {
709         RLM_MODULE_INIT,
710         "rlm_rest",
711         RLM_TYPE_THREAD_SAFE,           /* type */
712         sizeof(rlm_rest_t),
713         module_config,
714         mod_instantiate,                /* instantiation */
715         mod_detach,                     /* detach */
716         {
717                 mod_authenticate,       /* authentication */
718                 mod_authorize,          /* authorization */
719                 NULL,                   /* preaccounting */
720                 mod_accounting,         /* accounting */
721                 NULL,                   /* checksimul */
722                 NULL,                   /* pre-proxy */
723                 NULL,                   /* post-proxy */
724                 mod_post_auth           /* post-auth */
725         },
726 };