All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
rlm_rest.c
Go to the documentation of this file.
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: 4ef697e1a0dd9c762cd91474e35ec8cf07888f09 $
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: 4ef697e1a0dd9c762cd91474e35ec8cf07888f09 $")
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  { FR_CONF_OFFSET("ca_file", PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_file) },
39  { FR_CONF_OFFSET("ca_path", PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_ca_path) },
40  { FR_CONF_OFFSET("certificate_file", PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_certificate_file) },
41  { FR_CONF_OFFSET("private_key_file", PW_TYPE_FILE_INPUT, rlm_rest_section_t, tls_private_key_file) },
42  { FR_CONF_OFFSET("private_key_password", PW_TYPE_STRING | PW_TYPE_SECRET, rlm_rest_section_t, tls_private_key_password) },
43  { FR_CONF_OFFSET("random_file", PW_TYPE_STRING, rlm_rest_section_t, tls_random_file) },
44  { FR_CONF_OFFSET("check_cert", PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert), .dflt = "yes" },
45  { FR_CONF_OFFSET("check_cert_cn", PW_TYPE_BOOLEAN, rlm_rest_section_t, tls_check_cert_cn), .dflt = "yes" },
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  { FR_CONF_OFFSET("uri", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, uri), .dflt = "" },
60  { FR_CONF_OFFSET("proxy", PW_TYPE_STRING, rlm_rest_section_t, proxy) },
61  { FR_CONF_OFFSET("method", PW_TYPE_STRING, rlm_rest_section_t, method_str), .dflt = "GET" },
62  { FR_CONF_OFFSET("body", PW_TYPE_STRING, rlm_rest_section_t, body_str), .dflt = "none" },
64  { FR_CONF_OFFSET("force_to", PW_TYPE_STRING, rlm_rest_section_t, force_to_str) },
65 
66  /* User authentication */
67  { FR_CONF_OFFSET("auth", PW_TYPE_STRING, rlm_rest_section_t, auth_str), .dflt = "none" },
68  { FR_CONF_OFFSET("username", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, username) },
69  { FR_CONF_OFFSET("password", PW_TYPE_STRING | PW_TYPE_XLAT, rlm_rest_section_t, password) },
70  { FR_CONF_OFFSET("require_auth", PW_TYPE_BOOLEAN, rlm_rest_section_t, require_auth), .dflt = "no" },
71 
72  /* Transfer configuration */
73  { FR_CONF_OFFSET("timeout", PW_TYPE_TIMEVAL, rlm_rest_section_t, timeout_tv), .dflt = "4.0" },
74  { FR_CONF_OFFSET("chunk", PW_TYPE_INTEGER, rlm_rest_section_t, chunk), .dflt = "0" },
75 
76  /* TLS Parameters */
77  { FR_CONF_POINTER("tls", PW_TYPE_SUBSECTION, NULL), .dflt = (void const *) tls_config },
79 };
80 
81 static const CONF_PARSER module_config[] = {
82  { FR_CONF_OFFSET("connect_uri", PW_TYPE_STRING, rlm_rest_t, connect_uri) },
83  { FR_CONF_DEPRECATED("connect_timeout", PW_TYPE_TIMEVAL, rlm_rest_t, connect_timeout) },
84  { FR_CONF_OFFSET("connect_proxy", PW_TYPE_STRING, rlm_rest_t, connect_proxy) },
86 };
87 
88 static int rlm_rest_perform(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle, REQUEST *request,
89  char const *username, char const *password)
90 {
91  ssize_t uri_len;
92  char *uri = NULL;
93 
94  int ret;
95 
96  RDEBUG("Expanding URI components");
97 
98  /*
99  * Build xlat'd URI, this allows REST servers to be specified by
100  * request attributes.
101  */
102  uri_len = rest_uri_build(&uri, instance, request, section->uri);
103  if (uri_len <= 0) return -1;
104 
105  RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section->method, NULL), uri);
106 
107  /*
108  * Configure various CURL options, and initialise the read/write
109  * context data.
110  */
111  ret = rest_request_config(instance, section, request, handle, section->method, section->body,
112  uri, username, password);
113  talloc_free(uri);
114  if (ret < 0) return -1;
115 
116  /*
117  * Send the CURL request, pre-parse headers, aggregate incoming
118  * HTTP body data into a single contiguous buffer.
119  */
120  ret = rest_request_perform(instance, section, request, handle);
121  if (ret < 0) return -1;
122 
123  {
124  TALLOC_CTX *ctx;
125  VALUE_PAIR **list;
126  int code;
127  value_data_t value;
128 
129  code = rest_get_handle_code(handle);
130 
131  RINDENT();
132  RDEBUG2("&REST-HTTP-Code := %i", code);
133  REXDENT();
134 
135  value.length = sizeof(value.integer);
136  value.integer = code;
137 
138  /*
139  * Find the reply list, and appropriate context in the
140  * current request.
141  */
143  if (!list || (fr_pair_update_by_num(ctx, list, 0, PW_REST_HTTP_STATUS_CODE, TAG_ANY, PW_TYPE_INTEGER,
144  &value) < 0)) {
145  REDEBUG("Failed updating &REST-HTTP-Code");
146  return -1;
147  }
148  }
149 
150  return 0;
151 }
152 
153 static void rlm_rest_cleanup(rlm_rest_t const *instance, rlm_rest_section_t *section, void *handle)
154 {
155  rest_request_cleanup(instance, section, handle);
156 }
157 
158 static ssize_t jsonquote_xlat(char **out, size_t outlen,
159  UNUSED void const *mod_inst, UNUSED void const *xlat_inst,
160  UNUSED REQUEST *request, char const *fmt)
161 {
162  char const *p;
163  char *out_p = *out;
164  size_t freespace = outlen;
165  size_t len;
166 
167  for (p = fmt; *p != '\0'; p++) {
168  /* Indicate truncation */
169  if (freespace < 3) {
170  *out_p = '\0';
171  return outlen + 1;
172  }
173 
174  if (*p == '"') {
175  *out_p++ = '\\';
176  *out_p++ = '"';
177  freespace -= 2;
178  } else if (*p == '\\') {
179  *out_p++ = '\\';
180  *out_p++ = '\\';
181  freespace -= 2;
182  } else if (*p == '/') {
183  *out_p++ = '\\';
184  *out_p++ = '/';
185  freespace -= 2;
186  } else if (*p >= ' ') {
187  *out_p++ = *p;
188  freespace--;
189  /*
190  * Unprintable chars
191  */
192  } else {
193  *out_p++ = '\\';
194  freespace--;
195 
196  switch (*p) {
197  case '\b':
198  *out_p++ = 'b';
199  freespace--;
200  break;
201 
202  case '\f':
203  *out_p++ = 'f';
204  freespace--;
205  break;
206 
207  case '\n':
208  *out_p++ = 'b';
209  freespace--;
210  break;
211 
212  case '\r':
213  *out_p++ = 'r';
214  freespace--;
215  break;
216 
217  case '\t':
218  *out_p++ = 't';
219  freespace--;
220  break;
221 
222  default:
223  len = snprintf(out_p, freespace, "u%04X", *p);
224  if (is_truncated(len, freespace)) return (outlen - freespace) + len;
225  out_p += len;
226  freespace -= len;
227  }
228  }
229  }
230 
231  *out_p = '\0';
232 
233  return outlen - freespace;
234 }
235 /*
236  * Simple xlat to read text data from a URL
237  */
238 static ssize_t rest_xlat(char **out, UNUSED size_t outlen,
239  void const *mod_inst, UNUSED void const *xlat_inst,
240  REQUEST *request, char const *fmt)
241 {
242  rlm_rest_t const *inst = mod_inst;
243  void *handle;
244  int hcode;
245  int ret;
246  ssize_t len, slen = 0;
247  char *uri = NULL;
248  char const *p = fmt, *q;
249  char const *body;
250  http_method_t method;
251 
252  rad_assert(*out == NULL);
253 
254  /* There are no configurable parameters other than the URI */
255  rlm_rest_section_t section = {
256  .name = "xlat",
257  .method = HTTP_METHOD_GET,
258  .body = HTTP_BODY_NONE,
259  .body_str = "application/x-www-form-urlencoded",
260  .require_auth = false,
261  .force_to = HTTP_BODY_PLAIN
262  };
263 
264  rad_assert(fmt);
265 
266  RDEBUG("Expanding URI components");
267 
268  handle = fr_connection_get(inst->pool);
269  if (!handle) return -1;
270 
271  /*
272  * Extract the method from the start of the format string (if there is one)
273  */
275  if (method != HTTP_METHOD_UNKNOWN) {
276  section.method = method;
277  p += strlen(http_method_table[method].name);
278  }
279 
280  /*
281  * Trim whitespace
282  */
283  while (isspace(*p) && p++);
284 
285  /*
286  * Unescape parts of xlat'd URI, this allows REST servers to be specified by
287  * request attributes.
288  */
289  len = rest_uri_host_unescape(&uri, mod_inst, request, handle, p);
290  if (len <= 0) {
291  slen = -1;
292  goto finish;
293  }
294 
295  /*
296  * Extract freeform body data (url can't contain spaces)
297  */
298  q = strchr(p, ' ');
299  if (q && (*++q != '\0')) {
300  section.body = HTTP_BODY_CUSTOM_LITERAL;
301  section.data = q;
302  }
303 
304  RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri);
305 
306  /*
307  * Configure various CURL options, and initialise the read/write
308  * context data.
309  *
310  * @todo We could extract the User-Name and password from the URL string.
311  */
312  ret = rest_request_config(mod_inst, &section, request, handle, section.method, section.body,
313  uri, NULL, NULL);
314  talloc_free(uri);
315  if (ret < 0) return -1;
316 
317  /*
318  * Send the CURL request, pre-parse headers, aggregate incoming
319  * HTTP body data into a single contiguous buffer.
320  */
321  ret = rest_request_perform(mod_inst, &section, request, handle);
322  if (ret < 0) return -1;
323 
324  hcode = rest_get_handle_code(handle);
325  switch (hcode) {
326  case 404:
327  case 410:
328  case 403:
329  case 401:
330  {
331  slen = -1;
332 error:
333  rest_response_error(request, handle);
334  goto finish;
335  }
336  case 204:
337  goto finish;
338 
339  default:
340  /*
341  * Attempt to parse content if there was any.
342  */
343  if ((hcode >= 200) && (hcode < 300)) {
344  break;
345  } else if (hcode < 500) {
346  slen = -2;
347  goto error;
348  } else {
349  slen = -1;
350  goto error;
351  }
352  }
353 
354  len = rest_get_handle_data(&body, handle);
355  if (len > 0) {
356  *out = talloc_bstrndup(request, body, len);
357  slen = len;
358  }
359 
360 finish:
361  rlm_rest_cleanup(mod_inst, &section, handle);
362 
363  fr_connection_release(inst->pool, handle);
364 
365  return slen;
366 }
367 
368 /*
369  * Find the named user in this modules database. Create the set
370  * of attribute-value pairs to check and reply with for this user
371  * from the database. The authentication code only needs to check
372  * the password, the rest is done here.
373  */
374 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
375 {
376  rlm_rest_t *inst = instance;
377  rlm_rest_section_t *section = &inst->authorize;
378 
379  void *handle;
380  int hcode;
381  int rcode = RLM_MODULE_OK;
382  int ret;
383 
384  if (!section->name) return RLM_MODULE_NOOP;
385 
386  handle = fr_connection_get(inst->pool);
387  if (!handle) return RLM_MODULE_FAIL;
388 
389  ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL);
390  if (ret < 0) {
391  rcode = RLM_MODULE_FAIL;
392  goto finish;
393  }
394 
395  hcode = rest_get_handle_code(handle);
396  switch (hcode) {
397  case 404:
398  case 410:
399  rcode = RLM_MODULE_NOTFOUND;
400  break;
401 
402  case 403:
403  rcode = RLM_MODULE_USERLOCK;
404  break;
405 
406  case 401:
407  /*
408  * Attempt to parse content if there was any.
409  */
410  ret = rest_response_decode(inst, section, request, handle);
411  if (ret < 0) {
412  rcode = RLM_MODULE_FAIL;
413  break;
414  }
415 
416  rcode = RLM_MODULE_REJECT;
417  break;
418 
419  case 204:
420  rcode = RLM_MODULE_OK;
421  break;
422 
423  default:
424  /*
425  * Attempt to parse content if there was any.
426  */
427  if ((hcode >= 200) && (hcode < 300)) {
428  ret = rest_response_decode(inst, section, request, handle);
429  if (ret < 0) rcode = RLM_MODULE_FAIL;
430  else if (ret == 0) rcode = RLM_MODULE_OK;
431  else rcode = RLM_MODULE_UPDATED;
432  break;
433  } else if (hcode < 500) {
434  rcode = RLM_MODULE_INVALID;
435  } else {
436  rcode = RLM_MODULE_FAIL;
437  }
438  }
439 
440 finish:
441  switch (rcode) {
442  case RLM_MODULE_INVALID:
443  case RLM_MODULE_FAIL:
444  case RLM_MODULE_USERLOCK:
445  rest_response_error(request, handle);
446  break;
447 
448  default:
449  break;
450  }
451 
452  rlm_rest_cleanup(instance, section, handle);
453 
454  fr_connection_release(inst->pool, handle);
455 
456  return rcode;
457 }
458 
459 /*
460  * Authenticate the user with the given password.
461  */
462 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
463 {
464  rlm_rest_t *inst = instance;
465  rlm_rest_section_t *section = &inst->authenticate;
466 
467  void *handle;
468  int hcode;
469  int rcode = RLM_MODULE_OK;
470  int ret;
471 
472  VALUE_PAIR const *username;
473  VALUE_PAIR const *password;
474 
475  if (!section->name) return RLM_MODULE_NOOP;
476 
477  username = request->username;
478  if (!request->username) {
479  REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request");
480 
481  return RLM_MODULE_INVALID;
482  }
483 
484  password = request->password;
485  if (!password ||
486  (password->da->attr != PW_USER_PASSWORD)) {
487  REDEBUG("You set 'Auth-Type = REST' for a request that does not contain a User-Password attribute!");
488  return RLM_MODULE_INVALID;
489  }
490 
491  handle = fr_connection_get(inst->pool);
492  if (!handle) return RLM_MODULE_FAIL;
493 
494  ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue);
495  if (ret < 0) {
496  rcode = RLM_MODULE_FAIL;
497  goto finish;
498  }
499 
500  hcode = rest_get_handle_code(handle);
501  switch (hcode) {
502  case 404:
503  case 410:
504  rcode = RLM_MODULE_NOTFOUND;
505  break;
506 
507  case 403:
508  rcode = RLM_MODULE_USERLOCK;
509  break;
510 
511  case 401:
512  /*
513  * Attempt to parse content if there was any.
514  */
515  ret = rest_response_decode(inst, section, request, handle);
516  if (ret < 0) {
517  rcode = RLM_MODULE_FAIL;
518  break;
519  }
520 
521  rcode = RLM_MODULE_REJECT;
522  break;
523 
524  case 204:
525  rcode = RLM_MODULE_OK;
526  break;
527 
528  default:
529  /*
530  * Attempt to parse content if there was any.
531  */
532  if ((hcode >= 200) && (hcode < 300)) {
533  ret = rest_response_decode(inst, section, request, handle);
534  if (ret < 0) rcode = RLM_MODULE_FAIL;
535  else if (ret == 0) rcode = RLM_MODULE_OK;
536  else rcode = RLM_MODULE_UPDATED;
537  break;
538  } else if (hcode < 500) {
539  rcode = RLM_MODULE_INVALID;
540  } else {
541  rcode = RLM_MODULE_FAIL;
542  }
543  }
544 
545 finish:
546  switch (rcode) {
547  case RLM_MODULE_INVALID:
548  case RLM_MODULE_FAIL:
549  case RLM_MODULE_USERLOCK:
550  rest_response_error(request, handle);
551  break;
552 
553  default:
554  break;
555  }
556 
557  rlm_rest_cleanup(instance, section, handle);
558 
559  fr_connection_release(inst->pool, handle);
560 
561  return rcode;
562 }
563 
564 /*
565  * Send accounting info to a REST API endpoint
566  */
567 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request)
568 {
569  rlm_rest_t *inst = instance;
570  rlm_rest_section_t *section = &inst->accounting;
571 
572  void *handle;
573  int hcode;
574  int rcode = RLM_MODULE_OK;
575  int ret;
576 
577  if (!section->name) return RLM_MODULE_NOOP;
578 
579  handle = fr_connection_get(inst->pool);
580  if (!handle) return RLM_MODULE_FAIL;
581 
582  ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
583  if (ret < 0) {
584  rcode = RLM_MODULE_FAIL;
585  goto finish;
586  }
587 
588  hcode = rest_get_handle_code(handle);
589  if (hcode >= 500) {
590  rcode = RLM_MODULE_FAIL;
591  } else if (hcode == 204) {
592  rcode = RLM_MODULE_OK;
593  } else if ((hcode >= 200) && (hcode < 300)) {
594  ret = rest_response_decode(inst, section, request, handle);
595  if (ret < 0) rcode = RLM_MODULE_FAIL;
596  else if (ret == 0) rcode = RLM_MODULE_OK;
597  else rcode = RLM_MODULE_UPDATED;
598  } else {
599  rcode = RLM_MODULE_INVALID;
600  }
601 
602 finish:
603  switch (rcode) {
604  case RLM_MODULE_INVALID:
605  case RLM_MODULE_FAIL:
606  rest_response_error(request, handle);
607  break;
608 
609  default:
610  break;
611  }
612 
613  rlm_rest_cleanup(inst, section, handle);
614 
615  fr_connection_release(inst->pool, handle);
616 
617  return rcode;
618 }
619 
620 /*
621  * Send post-auth info to a REST API endpoint
622  */
623 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request)
624 {
625  rlm_rest_t *inst = instance;
626  rlm_rest_section_t *section = &inst->post_auth;
627 
628  void *handle;
629  int hcode;
630  int rcode = RLM_MODULE_OK;
631  int ret;
632 
633  if (!section->name) return RLM_MODULE_NOOP;
634 
635  handle = fr_connection_get(inst->pool);
636  if (!handle) return RLM_MODULE_FAIL;
637 
638  ret = rlm_rest_perform(inst, section, handle, request, NULL, NULL);
639  if (ret < 0) {
640  rcode = RLM_MODULE_FAIL;
641  goto finish;
642  }
643 
644  hcode = rest_get_handle_code(handle);
645  if (hcode >= 500) {
646  rcode = RLM_MODULE_FAIL;
647  } else if (hcode == 204) {
648  rcode = RLM_MODULE_OK;
649  } else if ((hcode >= 200) && (hcode < 300)) {
650  ret = rest_response_decode(inst, section, request, handle);
651  if (ret < 0) rcode = RLM_MODULE_FAIL;
652  else if (ret == 0) rcode = RLM_MODULE_OK;
653  else rcode = RLM_MODULE_UPDATED;
654  } else {
655  rcode = RLM_MODULE_INVALID;
656  }
657 
658 finish:
659  switch (rcode) {
660  case RLM_MODULE_INVALID:
661  case RLM_MODULE_FAIL:
662  rest_response_error(request, handle);
663  break;
664 
665  default:
666  break;
667  }
668 
669  rlm_rest_cleanup(inst, section, handle);
670 
671  fr_connection_release(inst->pool, handle);
672 
673  return rcode;
674 }
675 
677 {
678  CONF_SECTION *cs;
679 
680  char const *name = section_type_value[comp].section;
681 
682  cs = cf_section_sub_find(parent, name);
683  if (!cs) {
684  config->name = NULL;
685  return 0;
686  }
687 
688  if (cf_section_parse(cs, config, section_config) < 0) {
689  config->name = NULL;
690  return -1;
691  }
692 
693  /*
694  * Add section name (Maybe add to headers later?).
695  */
696  config->name = name;
697 
698  /*
699  * Sanity check
700  */
701  if ((config->username && !config->password) || (!config->username && config->password)) {
702  cf_log_err_cs(cs, "'username' and 'password' must both be set or both be absent");
703 
704  return -1;
705  }
706 
707  /*
708  * Convert HTTP method auth and body type strings into their integer equivalents.
709  */
711  if (config->auth == HTTP_AUTH_UNKNOWN) {
712  cf_log_err_cs(cs, "Unknown HTTP auth type '%s'", config->auth_str);
713 
714  return -1;
715  } else if ((config->auth != HTTP_AUTH_NONE) && !http_curl_auth[config->auth]) {
716  cf_log_err_cs(cs, "Unsupported HTTP auth type \"%s\", check libcurl version, OpenSSL build "
717  "configuration, then recompile this module", config->auth_str);
718 
719  return -1;
720  }
721 
723  config->timeout = ((config->timeout_tv.tv_usec * 1000) + (config->timeout_tv.tv_sec / 1000));
724 
725  /*
726  * We don't have any custom user data, so we need to select the right encoder based
727  * on the body type.
728  *
729  * To make this slightly more/less confusing, we accept both canonical body_types,
730  * and content_types.
731  */
732  if (!config->data) {
734  if (config->body == HTTP_BODY_UNKNOWN) {
736  }
737 
738  if (config->body == HTTP_BODY_UNKNOWN) {
739  cf_log_err_cs(cs, "Unknown HTTP body type '%s'", config->body_str);
740  return -1;
741  }
742 
743  switch (http_body_type_supported[config->body]) {
745  cf_log_err_cs(cs, "Unsupported HTTP body type \"%s\", please submit patches",
746  config->body_str);
747  return -1;
748 
749  case HTTP_BODY_INVALID:
750  cf_log_err_cs(cs, "Invalid HTTP body type. \"%s\" is not a valid web API data "
751  "markup format", config->body_str);
752  return -1;
753 
755  cf_log_err_cs(cs, "Unavailable HTTP body type. \"%s\" is not available in this "
756  "build", config->body_str);
757  return -1;
758 
759  default:
760  break;
761  }
762  /*
763  * We have custom body data so we set HTTP_BODY_CUSTOM_XLAT, but also need to try and
764  * figure out what content-type to use. So if they've used the canonical form we
765  * need to convert it back into a proper HTTP content_type value.
766  */
767  } else {
768  http_body_type_t body;
769 
770  config->body = HTTP_BODY_CUSTOM_XLAT;
771 
773  if (body != HTTP_BODY_UNKNOWN) {
774  config->body_str = fr_int2str(http_content_type_table, body, config->body_str);
775  }
776  }
777 
778  if (config->force_to_str) {
780  if (config->force_to == HTTP_BODY_UNKNOWN) {
782  }
783 
784  if (config->force_to == HTTP_BODY_UNKNOWN) {
785  cf_log_err_cs(cs, "Unknown forced response body type '%s'", config->force_to_str);
786  return -1;
787  }
788 
789  switch (http_body_type_supported[config->force_to]) {
791  cf_log_err_cs(cs, "Unsupported forced response body type \"%s\", please submit patches",
792  config->force_to_str);
793  return -1;
794 
795  case HTTP_BODY_INVALID:
796  cf_log_err_cs(cs, "Invalid HTTP forced response body type. \"%s\" is not a valid web API data "
797  "markup format", config->force_to_str);
798  return -1;
799 
800  default:
801  break;
802  }
803  }
804 
805  return 0;
806 }
807 
808 
809 static int mod_bootstrap(CONF_SECTION *conf, void *instance)
810 {
811  rlm_rest_t *inst = instance;
812 
813  inst->xlat_name = cf_section_name2(conf);
814  if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
815 
816  /*
817  * Register the rest xlat function
818  */
819  xlat_register(inst, inst->xlat_name, rest_xlat, rest_uri_escape, NULL, 0, 0);
820  xlat_register(inst, "jsonquote", jsonquote_xlat, NULL, NULL, 0, XLAT_DEFAULT_BUF_LEN);
821 
822  return 0;
823 }
824 
825 
826 /*
827  * Do any per-module initialization that is separate to each
828  * configured instance of the module. e.g. set up connections
829  * to external databases, read configuration files, set up
830  * dictionary entries, etc.
831  *
832  * If configuration information is given in the config section
833  * that must be referenced in later calls, store a handle to it
834  * in *instance otherwise put a null pointer there.
835  */
836 static int mod_instantiate(CONF_SECTION *conf, void *instance)
837 {
838  rlm_rest_t *inst = instance;
839 
840  /*
841  * Parse sub-section configs.
842  */
843  if (
844  (parse_sub_section(conf, &inst->authorize, MOD_AUTHORIZE) < 0) ||
845  (parse_sub_section(conf, &inst->authenticate, MOD_AUTHENTICATE) < 0) ||
846  (parse_sub_section(conf, &inst->accounting, MOD_ACCOUNTING) < 0) ||
847 
848 /* @todo add behaviour for checksimul */
849 /* (parse_sub_section(conf, &inst->checksimul, MOD_SESSION) < 0) || */
850  (parse_sub_section(conf, &inst->post_auth, MOD_POST_AUTH) < 0))
851  {
852  return -1;
853  }
854 
855  /*
856  * Initialise REST libraries.
857  */
859  if (rest_init(inst) < 0) return -1;
861  if (!inst->pool) return -1;
862 
863  return 0;
864 }
865 
866 /*
867  * Only free memory we allocated. The strings allocated via
868  * cf_section_parse() do not need to be freed.
869  */
870 static int mod_detach(void *instance)
871 {
872  rlm_rest_t *inst = instance;
873 
875 
876  /* Free any memory used by libcurl */
877  rest_cleanup();
878 
879  return 0;
880 }
881 
882 /*
883  * The module name should be the only globally exported symbol.
884  * That is, everything else should be 'static'.
885  *
886  * If the module needs to temporarily modify it's instantiation
887  * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
888  * The server will then take care of ensuring that the module
889  * is single-threaded.
890  */
891 extern module_t rlm_rest;
892 module_t rlm_rest = {
894  .name = "rest",
895  .type = RLM_TYPE_THREAD_SAFE,
896  .inst_size = sizeof(rlm_rest_t),
897  .config = module_config,
898  .bootstrap = mod_bootstrap,
899  .instantiate = mod_instantiate,
900  .detach = mod_detach,
901  .methods = {
906  },
907 };
char const * name
Section name.
Definition: rest.h:106
#define PW_TYPE_FILE_INPUT
File matching value must exist, and must be readable.
Definition: conffile.h:204
http_body_type_t
Definition: rest.h:54
rlm_rest_section_t post_auth
Configuration specific to Post-auth.
Definition: rest.h:161
#define RINDENT()
Indent R* messages by one level.
Definition: log.h:265
static int rlm_rest_perform(rlm_rest_t *instance, rlm_rest_section_t *section, void *handle, REQUEST *request, char const *username, char const *password)
Definition: rlm_rest.c:88
int xlat_register(void *mod_inst, char const *name, xlat_func_t func, xlat_escape_t escape, xlat_instantiate_t instantiate, size_t inst_size, size_t buf_len)
Register an xlat function.
Definition: xlat.c:717
Time value (struct timeval), only for config items.
Definition: radius.h:55
#define rest_get_handle_code(_handle)
Definition: rest.h:282
static ssize_t jsonquote_xlat(char **out, size_t outlen, UNUSED void const *mod_inst, UNUSED void const *xlat_inst, UNUSED REQUEST *request, char const *fmt)
Definition: rlm_rest.c:158
static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
Write accounting data to Couchbase documents.
The module is OK, continue.
Definition: radiusd.h:91
static rlm_rcode_t mod_post_auth(void *instance, REQUEST *request) CC_HINT(nonnull)
Metadata exported by the module.
Definition: modules.h:134
const FR_NAME_NUMBER http_body_type_table[]
Conversion table for type config values.
Definition: rest.c:153
http_body_type_t force_to
Override the Content-Type header in the response to force decoding as a particular type...
Definition: rest.h:118
7 methods index for postauth section.
Definition: modules.h:48
fr_connection_pool_t * pool
Pointer to the connection pool.
Definition: rest.h:154
static char const * name
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
Handle authorization requests using Couchbase document data.
#define RLM_TYPE_THREAD_SAFE
Module is threadsafe.
Definition: modules.h:75
const FR_NAME_NUMBER http_content_type_table[]
Conversion table for "Content-Type" header values.
Definition: rest.c:198
#define UNUSED
Definition: libradius.h:134
#define RLM_MODULE_INIT
Definition: modules.h:86
char const * password
Password used for HTTP-Auth.
Definition: rest.h:127
int rest_init(rlm_rest_t *instance)
Initialises libcurl.
Definition: rest.c:255
#define CONF_PARSER_TERMINATOR
Definition: conffile.h:289
static void rlm_rest_cleanup(rlm_rest_t const *instance, rlm_rest_section_t *section, void *handle)
Definition: rlm_rest.c:153
PUBLIC int snprintf(char *string, size_t length, char *format, va_alist)
Definition: snprintf.c:686
static int parse_sub_section(CONF_SECTION *parent, rlm_rest_section_t *config, rlm_components_t comp)
Definition: rlm_rest.c:676
const http_body_type_t http_body_type_supported[HTTP_BODY_NUM_ENTRIES]
Table of encoder/decoder support.
Definition: rest.c:46
static rlm_rcode_t CC_HINT(nonnull)
Definition: rlm_rest.c:374
#define PW_TYPE_SECRET
Only print value if debug level >= 3.
Definition: conffile.h:202
#define inst
http_body_type_t body
What encoding type should be used.
Definition: rest.h:115
The module considers the request invalid.
Definition: radiusd.h:93
#define XLAT_DEFAULT_BUF_LEN
Definition: xlat.h:89
static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request) CC_HINT(nonnull)
#define PW_TYPE_SUBSECTION
Definition: conffile.h:188
Defines a CONF_PAIR to C data type mapping.
Definition: conffile.h:267
rlm_rest_section_t accounting
Configuration specific to accounting.
Definition: rest.h:158
int fr_pair_update_by_num(TALLOC_CTX *ctx, VALUE_PAIR **list, unsigned int vendor, unsigned int attr, int8_t tag, PW_TYPE type, value_data_t *value)
Create a new VALUE_PAIR or replace the value of the head pair in the specified list.
Definition: pair.c:761
size_t rest_uri_escape(UNUSED REQUEST *request, char *out, size_t outlen, char const *raw, UNUSED void *arg)
URL encodes a string.
Definition: rest.c:2469
int rest_request_config(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, void *handle, http_method_t method, http_body_type_t type, char const *uri, char const *username, char const *password)
Configures request curlopts.
Definition: rest.c:1976
#define is_truncated(_ret, _max)
Definition: libradius.h:204
int rest_response_decode(rlm_rest_t const *instance, rlm_rest_section_t *section, REQUEST *request, void *handle)
Sends the response to the correct decode function.
Definition: rest.c:2368
http_auth_type_t auth
HTTP auth type.
Definition: rest.h:124
#define rad_assert(expr)
Definition: rad_assert.h:38
Reject the request (user is locked out).
Definition: radiusd.h:94
int fr_str2int(FR_NAME_NUMBER const *table, char const *name, int def)
Definition: token.c:451
struct rlm_rest_t rlm_rest_t
rlm_rest_section_t authenticate
Configuration specific to authentication.
Definition: rest.h:157
const FR_NAME_NUMBER http_auth_table[]
Definition: rest.c:169
fr_connection_pool_t * module_connection_pool_init(CONF_SECTION *module, void *opaque, fr_connection_create_t c, fr_connection_alive_t a, char const *prefix)
Initialise a module specific connection pool.
Definition: modules.c:1759
char const * data
Custom body data (optional).
Definition: rest.h:121
const FR_NAME_NUMBER http_method_table[]
Conversion table for method config values.
Definition: rest.c:133
static int comp(void const *a, void const *b)
Definition: rbmonkey.c:44
void rest_response_error(REQUEST *request, rlm_rest_handle_t *handle)
Print out the response text as error lines.
Definition: rest.c:1823
static void * mod_conn_create(TALLOC_CTX *ctx, void *instance, struct timeval const *timeout)
Create a new memcached handle.
ssize_t rest_uri_host_unescape(char **out, UNUSED rlm_rest_t const *mod_inst, REQUEST *request, void *handle, char const *uri)
Unescapes the host portion of a URI string.
Definition: rest.c:2565
static int mod_bootstrap(CONF_SECTION *conf, void *instance)
Definition: rlm_rest.c:809
Must always come last, should not be in method table.
Definition: rest.h:51
#define PW_TYPE_XLAT
string will be dynamically expanded.
Definition: conffile.h:207
int cf_section_parse(CONF_SECTION *, void *base, CONF_PARSER const *variables)
Parse a configuration section into user-supplied variables.
Definition: conffile.c:2234
unsigned int attr
Attribute number.
Definition: dict.h:79
Immediately reject the request.
Definition: radiusd.h:89
Attributes in incoming or internally proxied request.
Definition: tmpl.h:82
3 methods index for accounting section.
Definition: modules.h:44
char const * uri
URI to send HTTP request to.
Definition: rest.h:107
const section_type_value_t section_type_value[]
Mappings between section names, typenames and control attributes.
Definition: modules.c:64
Stores an attribute, a value and various bits of other data.
Definition: pair.h:112
static int mod_instantiate(CONF_SECTION *conf, void *instance)
Definition: rlm_rest.c:836
void void cf_log_err_cs(CONF_SECTION const *cs, char const *fmt,...) CC_HINT(format(printf
0 methods index for authenticate section.
Definition: modules.h:41
#define REXDENT()
Exdent (unindent) R* messages by one level.
Definition: log.h:272
A truth value.
Definition: radius.h:56
The current request.
Definition: tmpl.h:113
char const * auth_str
The string version of the Auth-Type.
Definition: rest.h:123
#define FR_CONF_DEPRECATED(_n, _t, _p, _f)
Definition: conffile.h:179
int rest_request_perform(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, REQUEST *request, void *handle)
Sends a REST (HTTP) request.
Definition: rest.c:2337
32 Bit unsigned integer.
Definition: radius.h:34
enum rlm_rcodes rlm_rcode_t
Return codes indicating the result of the module call.
static rs_t * conf
Definition: radsniff.c:46
long timeout
Timeout in ms.
Definition: rest.h:139
CONF_SECTION * cf_section_sub_find(CONF_SECTION const *, char const *name)
Find a sub-section in a section.
Definition: conffile.c:3708
void fr_json_version_print(void)
Print JSON-C version.
Definition: json.c:265
char const * cf_section_name1(CONF_SECTION const *cs)
Definition: conffile.c:3592
const unsigned long http_curl_auth[HTTP_AUTH_NUM_ENTRIES]
Definition: rest.c:106
Module succeeded without doing anything.
Definition: radiusd.h:96
#define RDEBUG2(fmt,...)
Definition: log.h:244
static const CONF_PARSER section_config[]
Definition: rlm_rest.c:58
static const CONF_PARSER module_config[]
Definition: rlm_rest.c:81
Function prototypes and datatypes for the REST (HTTP) transport.
uint8_t data[]
Definition: eap_pwd.h:625
size_t length
Length of value data.
Definition: pair.h:87
char * talloc_bstrndup(void const *t, char const *in, size_t inlen)
Binary safe strndup function.
Definition: missing.c:632
uint64_t magic
Used to validate module struct.
Definition: modules.h:135
Module failed, don't reply.
Definition: radiusd.h:90
#define TAG_ANY
Definition: pair.h:191
#define FR_CONF_OFFSET(_n, _t, _s, _f)
Definition: conffile.h:168
void rest_cleanup(void)
Cleans up after libcurl.
Definition: rest.c:297
char const * method_str
The string version of the HTTP method.
Definition: rest.h:111
enum rlm_components rlm_components_t
The different section components of the server.
rlm_rest_section_t authorize
Configuration specific to authorisation.
Definition: rest.h:156
void * fr_connection_get(fr_connection_pool_t *pool)
Reserve a connection in the connection pool.
Definition: connection.c:1291
http_method_t
Definition: rest.h:44
static int mod_detach(void *instance)
Definition: rlm_rest.c:870
char const * username
Username used for HTTP-Auth.
Definition: rest.h:126
char const * body_str
The string version of the encoding/content type.
Definition: rest.h:114
#define REDEBUG(fmt,...)
Definition: log.h:254
size_t rest_get_handle_data(char const **out, rlm_rest_handle_t *handle)
Extracts pointer to buffer containing response data.
Definition: rest.c:1871
void rest_request_cleanup(UNUSED rlm_rest_t const *instance, UNUSED rlm_rest_section_t *section, void *handle)
Cleans up after a REST request.
Definition: rest.c:2422
int mod_conn_alive(UNUSED void *instance, void *handle)
Check the health of a connection handle.
Definition: mod.c:121
char const * xlat_name
Instance name.
Definition: rest.h:147
char const * fr_int2str(FR_NAME_NUMBER const *table, int number, char const *def)
Definition: token.c:506
fr_dict_attr_t const * da
Dictionary attribute defines the attribute.
Definition: pair.h:113
void fr_connection_release(fr_connection_pool_t *pool, void *conn)
Release a connection.
Definition: connection.c:1305
#define RADIUS_LIST_AND_CTX(_ctx, _head, _request, _ref, _list)
Determine the correct context and list head.
Definition: tmpl.h:333
String of printable characters.
Definition: radius.h:33
#define FR_CONF_POINTER(_n, _t, _p)
Definition: conffile.h:172
1 methods index for authorize section.
Definition: modules.h:42
User not found.
Definition: radiusd.h:95
module_t rlm_rest
Definition: rlm_rest.c:892
char const * section
Section name e.g. "Authorize".
Definition: modules.h:64
#define RCSID(id)
Definition: build.h:135
int fr_substr2int(FR_NAME_NUMBER const *table, char const *name, int def, int len)
Definition: token.c:471
static CONF_PARSER tls_config[]
Definition: rlm_rest.c:37
OK (pairs modified).
Definition: radiusd.h:97
char const * force_to_str
Force decoding with this decoder.
Definition: rest.h:117
http_method_t method
What HTTP method should be used, GET, POST etc...
Definition: rest.h:112
#define RDEBUG(fmt,...)
Definition: log.h:243
ssize_t rest_uri_build(char **out, UNUSED rlm_rest_t *instance, REQUEST *request, char const *uri)
Builds URI; performs XLAT expansions and encoding.
Definition: rest.c:2494
static ssize_t rest_xlat(char **out, UNUSED size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt)
Definition: rlm_rest.c:238
struct timeval timeout_tv
Timeout timeval.
Definition: rest.h:138
char const * cf_section_name2(CONF_SECTION const *cs)
Definition: conffile.c:3601
void fr_connection_pool_free(fr_connection_pool_t *pool)
Delete a connection pool.
Definition: connection.c:1226