Merge tag 'release_3_0_12' into branch moonshot-fr-3.0.12-upgrade.
[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 as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   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_rest.c
20  * @brief Integrate FreeRADIUS with RESTfull APIs
21  *
22  * @copyright 2012-2014  Arran Cudbard-Bell <arran.cudbardb@freeradius.org>
23  */
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/token.h>
29 #include <freeradius-devel/rad_assert.h>
30
31 #include <ctype.h>
32 #include "rest.h"
33
34 /*
35  *      TLS Configuration
36  */
37 static CONF_PARSER tls_config[] = {
38         { "ca_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_file), NULL },
39         { "ca_path", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_path), NULL },
40         { "certificate_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_certificate_file), NULL },
41         { "private_key_file", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_private_key_file), NULL },
42         { "private_key_password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_rest_section_t, tls_private_key_password), NULL },
43         { "random_file", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, tls_random_file), NULL },
44         { "check_cert", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert), "yes" },
45         { "check_cert_cn", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert_cn), "yes" },
46         CONF_PARSER_TERMINATOR
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 | PW_TYPE_XLAT, 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 | PW_TYPE_XLAT, rlm_rest_section_t, data), NULL },
63         { "force_to", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, force_to_str), NULL },
64
65         /* User authentication */
66         { "auth", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_rest_section_t, auth_str), "none" },
67         { "username", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, username), NULL },
68         { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, password), NULL },
69         { "require_auth", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_rest_section_t, require_auth), "no" },
70
71         /* Transfer configuration */
72         { "timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_section_t, timeout_tv), "4.0" },
73         { "chunk", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_rest_section_t, chunk), "0" },
74
75         /* TLS Parameters */
76         { "tls", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) tls_config },
77         CONF_PARSER_TERMINATOR
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         { "connect_timeout", FR_CONF_OFFSET(PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout_tv), "4.0" },
83         CONF_PARSER_TERMINATOR
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 static ssize_t jsonquote_xlat(UNUSED void *instance, UNUSED REQUEST *request,
130                               char const *fmt, char *out, size_t outlen)
131 {
132         char const *p;
133         size_t freespace = outlen;
134         size_t len;
135
136         for (p = fmt; *p != '\0'; p++) {
137                 /* Indicate truncation */
138                 if (freespace < 3) {
139                         *out = '\0';
140                         return outlen + 1;
141                 }
142
143                 if (*p == '"') {
144                         *out++ = '\\';
145                         *out++ = '"';
146                         freespace -= 2;
147                 } else if (*p == '\\') {
148                         *out++ = '\\';
149                         *out++ = '\\';
150                         freespace -= 2;
151                 } else if (*p == '/') {
152                         *out++ = '\\';
153                         *out++ = '/';
154                         freespace -= 2;
155                 } else if (*p >= ' ') {
156                         *out++ = *p;
157                         freespace--;
158                 /*
159                  *      Unprintable chars
160                  */
161                 } else {
162                         *out++ = '\\';
163                         freespace--;
164
165                         switch (*p) {
166                         case '\b':
167                                 *out++ = 'b';
168                                 freespace--;
169                                 break;
170
171                         case '\f':
172                                 *out++ = 'f';
173                                 freespace--;
174                                 break;
175
176                         case '\n':
177                                 *out++ = 'b';
178                                 freespace--;
179                                 break;
180
181                         case '\r':
182                                 *out++ = 'r';
183                                 freespace--;
184                                 break;
185
186                         case '\t':
187                                 *out++ = 't';
188                                 freespace--;
189                                 break;
190
191                         default:
192                                 len = snprintf(out, freespace, "u%04X", *p);
193                                 if (is_truncated(len, freespace)) return (outlen - freespace) + len;
194                                 out += len;
195                                 freespace -= len;
196                         }
197                 }
198         }
199
200         *out = '\0';
201
202         return outlen - freespace;
203 }
204 /*
205  *      Simple xlat to read text data from a URL
206  */
207 static ssize_t rest_xlat(void *instance, REQUEST *request,
208                          char const *fmt, char *out, size_t freespace)
209 {
210         rlm_rest_t      *inst = instance;
211         void            *handle;
212         int             hcode;
213         int             ret;
214         ssize_t         len, outlen = 0;
215         char            *uri = NULL;
216         char const      *p = fmt, *q;
217         char const      *body;
218         http_method_t   method;
219
220         /* There are no configurable parameters other than the URI */
221         rlm_rest_section_t section = {
222                 .name = "xlat",
223                 .method = HTTP_METHOD_GET,
224                 .body = HTTP_BODY_NONE,
225                 .body_str = "application/x-www-form-urlencoded",
226                 .require_auth = false,
227                 .force_to = HTTP_BODY_PLAIN
228         };
229         *out = '\0';
230
231         rad_assert(fmt);
232
233         RDEBUG("Expanding URI components");
234
235         handle = fr_connection_get(inst->pool);
236         if (!handle) return -1;
237
238         /*
239          *  Extract the method from the start of the format string (if there is one)
240          */
241         method = fr_substr2int(http_method_table, p, HTTP_METHOD_UNKNOWN, -1);
242         if (method != HTTP_METHOD_UNKNOWN) {
243                 section.method = method;
244                 p += strlen(http_method_table[method].name);
245         }
246
247         /*
248          *  Trim whitespace
249          */
250         while (isspace(*p) && p++);
251
252         /*
253          *  Unescape parts of xlat'd URI, this allows REST servers to be specified by
254          *  request attributes.
255          */
256         len = rest_uri_host_unescape(&uri, instance, request, handle, p);
257         if (len <= 0) {
258                 outlen = -1;
259                 goto finish;
260         }
261
262         /*
263          *  Extract freeform body data (url can't contain spaces)
264          */
265         q = strchr(p, ' ');
266         if (q && (*++q != '\0')) {
267                 section.body = HTTP_BODY_CUSTOM_LITERAL;
268                 section.data = q;
269         }
270
271         RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri);
272
273         /*
274          *  Configure various CURL options, and initialise the read/write
275          *  context data.
276          *
277          *  @todo We could extract the User-Name and password from the URL string.
278          */
279         ret = rest_request_config(instance, &section, request, handle, section.method, section.body,
280                                   uri, NULL, NULL);
281         talloc_free(uri);
282         if (ret < 0) {
283                 outlen = -1;
284                 goto finish;
285         }
286
287         /*
288          *  Send the CURL request, pre-parse headers, aggregate incoming
289          *  HTTP body data into a single contiguous buffer.
290          */
291         ret = rest_request_perform(instance, &section, request, handle);
292         if (ret < 0) {
293                 outlen = -1;
294                 goto finish;
295         }
296
297         hcode = rest_get_handle_code(handle);
298         switch (hcode) {
299         case 404:
300         case 410:
301         case 403:
302         case 401:
303         {
304                 outlen = -1;
305 error:
306                 rest_response_error(request, handle);
307                 goto finish;
308         }
309         case 204:
310                 goto finish;
311
312         default:
313                 /*
314                  *      Attempt to parse content if there was any.
315                  */
316                 if ((hcode >= 200) && (hcode < 300)) {
317                         break;
318                 } else if (hcode < 500) {
319                         outlen = -2;
320                         goto error;
321                 } else {
322                         outlen = -1;
323                         goto error;
324                 }
325         }
326
327         len = rest_get_handle_data(&body, handle);
328         if ((size_t) len >= freespace) {
329                 REDEBUG("Insufficient space to write HTTP response, needed %zu bytes, have %zu bytes", len + 1,
330                         freespace);
331                 outlen = -1;
332                 goto finish;
333         }
334         if (len > 0) {
335                 outlen = len;
336                 strlcpy(out, body, len + 1);    /* strlcpy takes the size of the buffer */
337         }
338
339 finish:
340         rlm_rest_cleanup(instance, &section, handle);
341
342         fr_connection_release(inst->pool, handle);
343
344         return outlen;
345 }
346
347 /*
348  *      Find the named user in this modules database.  Create the set
349  *      of attribute-value pairs to check and reply with for this user
350  *      from the database. The authentication code only needs to check
351  *      the password, the rest is done here.
352  */
353 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
354 {
355         rlm_rest_t *inst = instance;
356         rlm_rest_section_t *section = &inst->authorize;
357
358         void *handle;
359         int hcode;
360         int rcode = RLM_MODULE_OK;
361         int ret;
362
363         if (!section->name) return RLM_MODULE_NOOP;
364
365         handle = fr_connection_get(inst->pool);
366         if (!handle) return RLM_MODULE_FAIL;
367
368         ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL);
369         if (ret < 0) {
370                 rcode = RLM_MODULE_FAIL;
371                 goto finish;
372         }
373
374         hcode = rest_get_handle_code(handle);
375         switch (hcode) {
376         case 404:
377         case 410:
378                 rcode = RLM_MODULE_NOTFOUND;
379                 break;
380
381         case 403:
382                 rcode = RLM_MODULE_USERLOCK;
383                 break;
384
385         case 401:
386                 /*
387                  *      Attempt to parse content if there was any.
388                  */
389                 ret = rest_response_decode(inst, section, request, handle);
390                 if (ret < 0) {
391                         rcode = RLM_MODULE_FAIL;
392                         break;
393                 }
394
395                 rcode = RLM_MODULE_REJECT;
396                 break;
397
398         case 204:
399                 rcode = RLM_MODULE_OK;
400                 break;
401
402         default:
403                 /*
404                  *      Attempt to parse content if there was any.
405                  */
406                 if ((hcode >= 200) && (hcode < 300)) {
407                         ret = rest_response_decode(inst, section, request, handle);
408                         if (ret < 0)       rcode = RLM_MODULE_FAIL;
409                         else if (ret == 0) rcode = RLM_MODULE_OK;
410                         else               rcode = RLM_MODULE_UPDATED;
411                         break;
412                 } else if (hcode < 500) {
413                         rcode = RLM_MODULE_INVALID;
414                 } else {
415                         rcode = RLM_MODULE_FAIL;
416                 }
417         }
418
419 finish:
420         switch (rcode) {
421         case RLM_MODULE_INVALID:
422         case RLM_MODULE_FAIL:
423         case RLM_MODULE_USERLOCK:
424                 rest_response_error(request, handle);
425                 break;
426
427         default:
428                 break;
429         }
430
431         rlm_rest_cleanup(instance, section, handle);
432
433         fr_connection_release(inst->pool, handle);
434
435         return rcode;
436 }
437
438 /*
439  *      Authenticate the user with the given password.
440  */
441 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
442 {
443         rlm_rest_t *inst = instance;
444         rlm_rest_section_t *section = &inst->authenticate;
445
446         void *handle;
447         int hcode;
448         int rcode = RLM_MODULE_OK;
449         int ret;
450
451         VALUE_PAIR const *username;
452         VALUE_PAIR const *password;
453
454         if (!section->name) return RLM_MODULE_NOOP;
455
456         username = request->username;
457         if (!request->username) {
458                 REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request");
459
460                 return RLM_MODULE_INVALID;
461         }
462
463         password = request->password;
464         if (!password ||
465             (password->da->attr != PW_USER_PASSWORD)) {
466                 REDEBUG("You set 'Auth-Type = REST' for a request that does not contain a User-Password attribute!");
467                 return RLM_MODULE_INVALID;
468         }
469
470         handle = fr_connection_get(inst->pool);
471         if (!handle) return RLM_MODULE_FAIL;
472
473         ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue);
474         if (ret < 0) {
475                 rcode = RLM_MODULE_FAIL;
476                 goto finish;
477         }
478
479         hcode = rest_get_handle_code(handle);
480         switch (hcode) {
481         case 404:
482         case 410:
483                 rcode = RLM_MODULE_NOTFOUND;
484                 break;
485
486         case 403:
487                 rcode = RLM_MODULE_USERLOCK;
488                 break;
489
490         case 401:
491                 /*
492                  *      Attempt to parse content if there was any.
493                  */
494                 ret = rest_response_decode(inst, section, request, handle);
495                 if (ret < 0) {
496                         rcode = RLM_MODULE_FAIL;
497                         break;
498                 }
499
500                 rcode = RLM_MODULE_REJECT;
501                 break;
502
503         case 204:
504                 rcode = RLM_MODULE_OK;
505                 break;
506
507         default:
508                 /*
509                  *      Attempt to parse content if there was any.
510                  */
511                 if ((hcode >= 200) && (hcode < 300)) {
512                         ret = rest_response_decode(inst, section, request, handle);
513                         if (ret < 0)       rcode = RLM_MODULE_FAIL;
514                         else if (ret == 0) rcode = RLM_MODULE_OK;
515                         else               rcode = RLM_MODULE_UPDATED;
516                         break;
517                 } else if (hcode < 500) {
518                         rcode = RLM_MODULE_INVALID;
519                 } else {
520                         rcode = RLM_MODULE_FAIL;
521                 }
522         }
523
524 finish:
525         switch (rcode) {
526         case RLM_MODULE_INVALID:
527         case RLM_MODULE_FAIL:
528         case RLM_MODULE_USERLOCK:
529                 rest_response_error(request, handle);
530                 break;
531
532         default:
533                 break;
534         }
535
536         rlm_rest_cleanup(instance, section, handle);
537
538         fr_connection_release(inst->pool, handle);
539
540         return rcode;
541 }
542
543 /*
544  *      Send accounting info to a REST API endpoint
545  */
546 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
547 {
548         rlm_rest_t *inst = instance;
549         rlm_rest_section_t *section = &inst->accounting;
550
551         void *handle;
552         int hcode;
553         int rcode = RLM_MODULE_OK;
554         int ret;
555
556         if (!section->name) return RLM_MODULE_NOOP;
557
558         handle = fr_connection_get(inst->pool);
559         if (!handle) return RLM_MODULE_FAIL;
560
561         ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
562         if (ret < 0) {
563                 rcode = RLM_MODULE_FAIL;
564                 goto finish;
565         }
566
567         hcode = rest_get_handle_code(handle);
568         if (hcode >= 500) {
569                 rcode = RLM_MODULE_FAIL;
570         } else if (hcode == 204) {
571                 rcode = RLM_MODULE_OK;
572         } else if ((hcode >= 200) && (hcode < 300)) {
573                 ret = rest_response_decode(inst, section, request, handle);
574                 if (ret < 0)       rcode = RLM_MODULE_FAIL;
575                 else if (ret == 0) rcode = RLM_MODULE_OK;
576                 else               rcode = RLM_MODULE_UPDATED;
577         } else {
578                 rcode = RLM_MODULE_INVALID;
579         }
580
581 finish:
582         switch (rcode) {
583         case RLM_MODULE_INVALID:
584         case RLM_MODULE_FAIL:
585                 rest_response_error(request, handle);
586                 break;
587
588         default:
589                 break;
590         }
591
592         rlm_rest_cleanup(inst, section, handle);
593
594         fr_connection_release(inst->pool, handle);
595
596         return rcode;
597 }
598
599 /*
600  *      Send post-auth info to a REST API endpoint
601  */
602 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
603 {
604         rlm_rest_t *inst = instance;
605         rlm_rest_section_t *section = &inst->post_auth;
606
607         void *handle;
608         int hcode;
609         int rcode = RLM_MODULE_OK;
610         int ret;
611
612         if (!section->name) return RLM_MODULE_NOOP;
613
614         handle = fr_connection_get(inst->pool);
615         if (!handle) return RLM_MODULE_FAIL;
616
617         ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
618         if (ret < 0) {
619                 rcode = RLM_MODULE_FAIL;
620                 goto finish;
621         }
622
623         hcode = rest_get_handle_code(handle);
624         if (hcode >= 500) {
625                 rcode = RLM_MODULE_FAIL;
626         } else if (hcode == 204) {
627                 rcode = RLM_MODULE_OK;
628         } else if ((hcode >= 200) && (hcode < 300)) {
629                 ret = rest_response_decode(inst, section, request, handle);
630                 if (ret < 0)       rcode = RLM_MODULE_FAIL;
631                 else if (ret == 0) rcode = RLM_MODULE_OK;
632                 else               rcode = RLM_MODULE_UPDATED;
633         } else {
634                 rcode = RLM_MODULE_INVALID;
635         }
636
637 finish:
638         switch (rcode) {
639         case RLM_MODULE_INVALID:
640         case RLM_MODULE_FAIL:
641                 rest_response_error(request, handle);
642                 break;
643
644         default:
645                 break;
646         }
647
648         rlm_rest_cleanup(inst, section, handle);
649
650         fr_connection_release(inst->pool, handle);
651
652         return rcode;
653 }
654
655 static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, rlm_components_t comp)
656 {
657         CONF_SECTION *cs;
658
659         char const *name = section_type_value[comp].section;
660
661         cs = cf_section_sub_find(parent, name);
662         if (!cs) {
663                 config->name = NULL;
664                 return 0;
665         }
666
667         if (cf_section_parse(cs, config, section_config) < 0) {
668                 config->name = NULL;
669                 return -1;
670         }
671
672         /*
673          *  Add section name (Maybe add to headers later?).
674          */
675         config->name = name;
676
677         /*
678          *  Sanity check
679          */
680          if ((config->username && !config->password) || (!config->username && config->password)) {
681                 cf_log_err_cs(cs, "'username' and 'password' must both be set or both be absent");
682
683                 return -1;
684          }
685
686         /*
687          *  Convert HTTP method auth and body type strings into their integer equivalents.
688          */
689         config->auth = fr_str2int(http_auth_table, config->auth_str, HTTP_AUTH_UNKNOWN);
690         if (config->auth == HTTP_AUTH_UNKNOWN) {
691                 cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str);
692
693                 return -1;
694         } else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) {
695                 cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build "
696                               "configuration, then recompile this module", config->auth_str);
697
698                 return -1;
699         }
700
701         config->method = fr_str2int(http_method_table, config->method_str, HTTP_METHOD_CUSTOM);
702         config->timeout = ((config->timeout_tv.tv_usec * 1000) + (config->timeout_tv.tv_sec / 1000));
703
704         /*
705          *  We don't have any custom user data, so we need to select the right encoder based
706          *  on the body type.
707          *
708          *  To make this slightly more/less confusing, we accept both canonical body_types,
709          *  and content_types.
710          */
711         if (!config->data) {
712                 config->body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
713                 if (config->body == HTTP_BODY_UNKNOWN) {
714                         config->body = fr_str2int(http_content_type_table, config->body_str, HTTP_BODY_UNKNOWN);
715                 }
716
717                 if (config->body == HTTP_BODY_UNKNOWN) {
718                         cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str);
719                         return -1;
720                 }
721
722                 switch (http_body_type_supported[config->body]) {
723                 case HTTP_BODY_UNSUPPORTED:
724                         cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches",
725                                       config->body_str);
726                         return -1;
727
728                 case HTTP_BODY_INVALID:
729                         cf_log_err_cs(cs, "Invalid HTTP body type.  \"%s\" is not a valid web API data "
730                                       "markup format", config->body_str);
731                         return -1;
732
733                 case HTTP_BODY_UNAVAILABLE:
734                         cf_log_err_cs(cs, "Unavailable HTTP body type.  \"%s\" is not available in this "
735                                       "build", config->body_str);
736                         return -1;
737
738                 default:
739                         break;
740                 }
741         /*
742          *  We have custom body data so we set HTTP_BODY_CUSTOM_XLAT, but also need to try and
743          *  figure out what content-type to use. So if they've used the canonical form we
744          *  need to convert it back into a proper HTTP content_type value.
745          */
746         } else {
747                 http_body_type_t body;
748
749                 config->body = HTTP_BODY_CUSTOM_XLAT;
750
751                 body = fr_str2int(http_body_type_table, config->body_str, HTTP_BODY_UNKNOWN);
752                 if (body != HTTP_BODY_UNKNOWN) {
753                         config->body_str = fr_int2str(http_content_type_table, body, config->body_str);
754                 }
755         }
756
757         if (config->force_to_str) {
758                 config->force_to = fr_str2int(http_body_type_table, config->force_to_str, HTTP_BODY_UNKNOWN);
759                 if (config->force_to == HTTP_BODY_UNKNOWN) {
760                         config->force_to = fr_str2int(http_content_type_table, config->force_to_str, HTTP_BODY_UNKNOWN);
761                 }
762
763                 if (config->force_to == HTTP_BODY_UNKNOWN) {
764                         cf_log_err_cs(cs, "Unknown forced response body type '%s'", config->force_to_str);
765                         return -1;
766                 }
767
768                 switch (http_body_type_supported[config->force_to]) {
769                 case HTTP_BODY_UNSUPPORTED:
770                         cf_log_err_cs(cs, "Unsupported forced response body type \"%s\", please submit patches",
771                                       config->force_to_str);
772                         return -1;
773
774                 case HTTP_BODY_INVALID:
775                         cf_log_err_cs(cs, "Invalid HTTP forced response body type.  \"%s\" is not a valid web API data "
776                                       "markup format", config->force_to_str);
777                         return -1;
778
779                 default:
780                         break;
781                 }
782         }
783
784         return 0;
785 }
786
787
788 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
789 {
790         rlm_rest_t *inst = instance;
791
792         inst->xlat_name = cf_section_name2(conf);
793         if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
794
795         /*
796          *      Register the rest xlat function
797          */
798         xlat_register(inst->xlat_name, rest_xlat, rest_uri_escape, inst);
799         xlat_register("jsonquote", jsonquote_xlat, NULL, inst);
800
801         return 0;
802 }
803
804
805 /*
806  *      Do any per-module initialization that is separate to each
807  *      configured instance of the module.  e.g. set up connections
808  *      to external databases, read configuration files, set up
809  *      dictionary entries, etc.
810  *
811  *      If configuration information is given in the config section
812  *      that must be referenced in later calls, store a handle to it
813  *      in *instance otherwise put a null pointer there.
814  */
815 static int mod_instantiate(CONF_SECTION *conf, void *instance)
816 {
817         rlm_rest_t *inst = instance;
818
819         /*
820          *      Parse sub-section configs.
821          */
822         if (
823                 (parse_sub_section(conf, &inst->authorize, MOD_AUTHORIZE) < 0) ||
824                 (parse_sub_section(conf, &inst->authenticate, MOD_AUTHENTICATE) < 0) ||
825                 (parse_sub_section(conf, &inst->accounting, MOD_ACCOUNTING) < 0) ||
826
827 /* @todo add behaviour for checksimul */
828 /*              (parse_sub_section(conf, &inst->checksimul, MOD_SESSION) < 0) || */
829                 (parse_sub_section(conf, &inst->post_auth, MOD_POST_AUTH) < 0))
830         {
831                 return -1;
832         }
833
834         /*
835          *      Initialise REST libraries.
836          */
837         if (rest_init(inst) < 0) {
838                 return -1;
839         }
840
841         inst->connect_timeout = ((inst->connect_timeout_tv.tv_usec * 1000) +
842                                  (inst->connect_timeout_tv.tv_sec / 1000));
843         inst->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, mod_conn_alive, NULL);
844         if (!inst->pool) return -1;
845
846         return 0;
847 }
848
849 /*
850  *      Only free memory we allocated.  The strings allocated via
851  *      cf_section_parse() do not need to be freed.
852  */
853 static int mod_detach(void *instance)
854 {
855         rlm_rest_t *inst = instance;
856
857         fr_connection_pool_free(inst->pool);
858
859         /* Free any memory used by libcurl */
860         rest_cleanup();
861
862         return 0;
863 }
864
865 /*
866  *      The module name should be the only globally exported symbol.
867  *      That is, everything else should be 'static'.
868  *
869  *      If the module needs to temporarily modify it's instantiation
870  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
871  *      The server will then take care of ensuring that the module
872  *      is single-threaded.
873  */
874 extern module_t rlm_rest;
875 module_t rlm_rest = {
876         .magic          = RLM_MODULE_INIT,
877         .name           = "rest",
878         .type           = RLM_TYPE_THREAD_SAFE,
879         .inst_size      = sizeof(rlm_rest_t),
880         .config         = module_config,
881         .bootstrap      = mod_bootstrap,
882         .instantiate    = mod_instantiate,
883         .detach         = mod_detach,
884         .methods = {
885                 [MOD_AUTHENTICATE]      = mod_authenticate,
886                 [MOD_AUTHORIZE]         = mod_authorize,
887                 [MOD_ACCOUNTING]        = mod_accounting,
888                 [MOD_POST_AUTH]         = mod_post_auth
889         },
890 };