Require that the modules call talloc for their instance handle.
[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-2013  Arran Cudbard-Bell <arran.cudbardb@freeradius.org>
22  */
23 #include <freeradius-devel/ident.h>
24 RCSID("$Id$")
25
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <freeradius-devel/token.h>
29
30 #include "rest.h"
31
32 /*
33  *      TLS Configuration
34  */
35 static CONF_PARSER tls_config[] = {
36         { "cacertfile", PW_TYPE_FILENAME,
37           offsetof(rlm_rest_section_t,tls_cacertfile), NULL, NULL},
38         { "cacertdir", PW_TYPE_FILENAME,
39           offsetof(rlm_rest_section_t,tls_cacertdir), NULL, NULL},
40         { "certfile", PW_TYPE_FILENAME,
41           offsetof(rlm_rest_section_t,tls_certfile), NULL, NULL},
42         { "keyfile", PW_TYPE_FILENAME,
43           offsetof(rlm_rest_section_t,tls_keyfile), NULL, NULL },
44         { "keypassword", PW_TYPE_STRING_PTR,
45           offsetof(rlm_rest_section_t, tls_keypassword), NULL, NULL },
46         { "randfile", PW_TYPE_STRING_PTR, /* OK if it changes on HUP */
47           offsetof(rlm_rest_section_t,tls_randfile), NULL, NULL },
48         { "verify_cert", PW_TYPE_BOOLEAN,
49           offsetof(rlm_rest_section_t, tls_verify_cert), NULL, "yes" },
50         { "verify_cert_cn", PW_TYPE_BOOLEAN,
51           offsetof(rlm_rest_section_t, tls_verify_cert_cn), NULL, "yes" },
52         
53         { NULL, -1, 0, NULL, NULL }
54 };
55
56 /*
57  *      A mapping of configuration file names to internal variables.
58  *
59  *      Note that the string is dynamically allocated, so it MUST
60  *      be freed.  When the configuration file parse re-reads the string,
61  *      it free's the old one, and strdup's the new one, placing the pointer
62  *      to the strdup'd string into 'config.string'.  This gets around
63  *      buffer over-flows.
64  */
65 static const CONF_PARSER section_config[] = {
66         { "uri", PW_TYPE_STRING_PTR,
67          offsetof(rlm_rest_section_t, uri),        NULL, ""  },
68         { "method", PW_TYPE_STRING_PTR,
69          offsetof(rlm_rest_section_t, method_str), NULL, "GET"   },
70         { "body", PW_TYPE_STRING_PTR,
71          offsetof(rlm_rest_section_t, body_str),   NULL, "post"  },
72          
73         /* User authentication */
74         { "auth", PW_TYPE_STRING_PTR,
75          offsetof(rlm_rest_section_t, auth_str),   NULL, "none"  },
76         { "username", PW_TYPE_STRING_PTR,
77          offsetof(rlm_rest_section_t, username),   NULL, ""  },
78         { "password", PW_TYPE_STRING_PTR,
79          offsetof(rlm_rest_section_t, password),   NULL, ""  },
80         { "require_auth", PW_TYPE_BOOLEAN,
81          offsetof(rlm_rest_section_t, require_auth), NULL, "no"},
82
83         /* Transfer configuration */
84         { "timeout", PW_TYPE_INTEGER, 
85          offsetof(rlm_rest_section_t, timeout),    NULL, "0" },
86         { "chunk", PW_TYPE_INTEGER,
87          offsetof(rlm_rest_section_t, chunk),      NULL, "0" },
88
89         /* TLS Parameters */
90         { "tls", PW_TYPE_SUBSECTION, 0, NULL, (const void *) tls_config },
91
92         { NULL, -1, 0, NULL, NULL }
93 };
94  
95 static const CONF_PARSER module_config[] = {
96         { "connect_uri", PW_TYPE_STRING_PTR,
97          offsetof(rlm_rest_t, connect_uri), NULL, "http://localhost/" },
98
99         { NULL, -1, 0, NULL, NULL }
100 };
101
102 static int rlm_rest_perform (rlm_rest_t *instance, rlm_rest_section_t *section,
103                              void *handle, REQUEST *request)
104 {
105         size_t uri_len;
106         char uri[REST_URI_MAX_LEN];
107         
108         int ret;
109         
110         RDEBUG("Expanding URI components");
111         /*
112          *      Build xlat'd URI, this allows REST servers to be specified by
113          *      request attributes.
114          */
115         uri_len = rest_uri_build(instance, section, request, uri, sizeof(uri));
116         if (uri_len <= 0) return -1;
117
118         RDEBUG("Sending HTTP %s to \"%s\"",
119                 fr_int2str(http_method_table, section->method, NULL),
120                 uri);
121
122         /*
123          *      Configure various CURL options, and initialise the read/write
124          *      context data.
125          */
126         ret = rest_request_config(instance, section, request,
127                 handle,
128                 section->method,
129                 section->body,
130                 uri);
131         if (ret <= 0) return -1;
132
133         /*
134          *      Send the CURL request, pre-parse headers, aggregate incoming
135          *      HTTP body data into a single contiguous buffer.
136          */
137         ret = rest_request_perform(instance, section, handle);
138         if (ret <= 0) return -1;
139
140         return 1;
141 }
142
143 static void rlm_rest_cleanup (rlm_rest_t *instance, rlm_rest_section_t *section,
144                               void *handle)
145 {
146         rest_request_cleanup(instance, section, handle);
147 };
148
149 static int parse_sub_section(CONF_SECTION *parent, 
150                              rlm_rest_t *instance, rlm_rest_section_t *config,
151                              rlm_components_t comp)
152 {
153         CONF_SECTION *cs;
154
155         const char *name = section_type_value[comp].section;
156
157         cs = cf_section_sub_find(parent, name);
158         if (!cs) {
159                 /* TODO: Should really setup section with default values */
160                 return 0;
161         }
162         
163         if (cf_section_parse(cs, config, section_config) < 0) {
164                 radlog(L_ERR, "rlm_rest (%s): Parsing config section failed",
165                        instance->xlat_name);
166                 return -1;
167         }
168
169         /*
170          *      Add section name (Maybe add to headers later?).
171          */
172         config->name = name;
173
174         /*
175          *      Convert HTTP method auth and body type strings into their
176          *      integer equivalents.
177          */
178         config->auth = fr_str2int(http_auth_table, config->auth_str,
179                                   HTTP_AUTH_UNKNOWN);
180                                   
181         if (config->auth == HTTP_AUTH_UNKNOWN) {
182                 radlog(L_ERR, "rlm_rest (%s): Unknown HTTP auth type \"%s\"",
183                        instance->xlat_name, config->auth_str);
184                 return -1;      
185         }
186         
187         if (!http_curl_auth[config->auth]) {
188                 radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP auth type \"%s\""
189                        ", check libcurl version, OpenSSL build configuration," 
190                        " then recompile this module",
191                        instance->xlat_name, config->auth_str);
192                 return -1;
193         }
194                                     
195         config->method = fr_str2int(http_method_table, config->method_str,
196                                     HTTP_METHOD_CUSTOM);
197
198         config->body = fr_str2int(http_body_type_table, config->body_str,
199                                   HTTP_BODY_UNKNOWN);
200
201         if (config->body == HTTP_BODY_UNKNOWN) {
202                 radlog(L_ERR, "rlm_rest (%s): Unknown HTTP body type \"%s\"",
203                        instance->xlat_name, config->body_str);
204                 return -1;
205         }
206
207         if (http_body_type_supported[config->body] == HTTP_BODY_UNSUPPORTED) {
208                 radlog(L_ERR, "rlm_rest (%s): Unsupported HTTP body type \"%s\""
209                        ", please submit patches", instance->xlat_name,
210                        config->body_str);
211                 return -1;
212         }
213
214         return 1;
215 }
216
217 /*
218  *      Do any per-module initialization that is separate to each
219  *      configured instance of the module.  e.g. set up connections
220  *      to external databases, read configuration files, set up
221  *      dictionary entries, etc.
222  *
223  *      If configuration information is given in the config section
224  *      that must be referenced in later calls, store a handle to it
225  *      in *instance otherwise put a null pointer there.
226  */
227 static int rlm_rest_instantiate(CONF_SECTION *conf, void **instance)
228 {
229         rlm_rest_t *data;
230         const char *xlat_name;
231
232         /*
233          *      Allocate memory for instance data.
234          */
235         *instance = data = talloc_zero(conf, rlm_rest_t);
236         if (!data) return -1;
237
238         /*
239          *      If the configuration parameters can't be parsed, then
240          *      fail.
241          */
242         if (cf_section_parse(conf, data, module_config) < 0) {
243                 return -1;
244         }
245
246         xlat_name = cf_section_name2(conf);
247         if (xlat_name == NULL) {
248                 xlat_name = cf_section_name1(conf);
249         }
250
251         data->xlat_name = xlat_name;
252
253         /*
254          *      Parse sub-section configs.
255          */
256         if (
257                 (parse_sub_section(conf, data, &data->authorize,
258                                    RLM_COMPONENT_AUTZ) < 0) ||
259                 (parse_sub_section(conf, data, &data->authenticate,
260                                    RLM_COMPONENT_AUTH) < 0) ||
261                 (parse_sub_section(conf, data, &data->accounting,
262                                    RLM_COMPONENT_ACCT) < 0) ||
263                 (parse_sub_section(conf, data, &data->checksimul,
264                                    RLM_COMPONENT_SESS) < 0) ||
265                 (parse_sub_section(conf, data, &data->postauth,
266                                    RLM_COMPONENT_POST_AUTH) < 0))
267         {
268                 return -1;
269         }
270
271         /*
272          *      Initialise REST libraries.
273          */
274         if (!rest_init(data)) {
275                 return -1;
276         }
277
278         data->conn_pool = fr_connection_pool_init(conf, data,
279                                                   rest_socket_create,
280                                                   rest_socket_alive,
281                                                   rest_socket_delete);
282
283         if (!data->conn_pool) {
284                 return -1;
285         }
286
287         return 0;
288 }
289
290 /*
291  *      Find the named user in this modules database.  Create the set
292  *      of attribute-value pairs to check and reply with for this user
293  *      from the database. The authentication code only needs to check
294  *      the password, the rest is done here.
295  */
296 static rlm_rcode_t rlm_rest_authorize(void *instance, REQUEST *request)
297 {
298         rlm_rest_t *my_instance = instance;
299         rlm_rest_section_t *section = &my_instance->authorize;
300
301         void *handle;
302         int hcode;
303         int rcode = RLM_MODULE_OK;
304         int ret;
305
306         handle = fr_connection_get(my_instance->conn_pool);
307         if (!handle) return RLM_MODULE_FAIL;
308
309         ret = rlm_rest_perform(instance, section, handle, request);
310         if (ret < 0) { 
311                 rcode = RLM_MODULE_FAIL;
312                 goto end;
313         }
314
315         hcode = rest_get_handle_code(handle);
316
317         switch (hcode) {
318                 case 404:
319                 case 410:
320                         rcode = RLM_MODULE_NOTFOUND;
321                         break;
322                 case 403:
323                         rcode = RLM_MODULE_USERLOCK;
324                         break;
325                 case 401:
326                         /*
327                          *      Attempt to parse content if there was any.
328                          */
329                         ret = rest_request_decode(my_instance, section,
330                                                   request, handle);
331                         if (ret < 0) {
332                                 rcode = RLM_MODULE_FAIL;
333                                 break;
334                         }
335
336                         rcode = RLM_MODULE_REJECT;
337                         break;
338                 case 204:
339                         rcode = RLM_MODULE_OK;
340                         break;
341                 default:
342                         /*
343                          *      Attempt to parse content if there was any.
344                          */
345                         if ((hcode >= 200) && (hcode < 300)) {
346                                 ret = rest_request_decode(my_instance, section,
347                                                           request, handle);
348                                 if (ret < 0)       rcode = RLM_MODULE_FAIL;
349                                 else if (ret == 0) rcode = RLM_MODULE_OK;
350                                 else               rcode = RLM_MODULE_UPDATED;
351                                 break;
352                         } else if (hcode < 500) {
353                                 rcode = RLM_MODULE_INVALID;
354                         } else {
355                                 rcode = RLM_MODULE_FAIL;
356                         }
357         }
358
359         end:
360
361         rlm_rest_cleanup(instance, section, handle);
362
363         fr_connection_release(my_instance->conn_pool, handle);
364
365         return rcode;
366 }
367
368 /*
369  *      Authenticate the user with the given password.
370  */
371 static rlm_rcode_t rlm_rest_authenticate(void *instance, REQUEST *request)
372 {
373         /* quiet the compiler */
374         instance = instance;
375         request = request;
376
377         return RLM_MODULE_OK;
378 }
379
380 /*
381  *      Write accounting information to this modules database.
382  */
383 static rlm_rcode_t rlm_rest_accounting(UNUSED void *instance,
384                                        UNUSED REQUEST *request)
385 {
386         return RLM_MODULE_OK;
387 }
388
389 /*
390  *      See if a user is already logged in. Sets request->simul_count to the
391  *      current session count for this user and sets request->simul_mpp to 2
392  *      if it looks like a multilink attempt based on the requested IP
393  *      address, otherwise leaves request->simul_mpp alone.
394  *
395  *      Check twice. If on the first pass the user exceeds his
396  *      max. number of logins, do a second pass and validate all
397  *      logins by querying the terminal server (using eg. SNMP).
398  */
399 static rlm_rcode_t rlm_rest_checksimul(void *instance, REQUEST *request)
400 {
401         instance = instance;
402
403         request->simul_count=0;
404
405         return RLM_MODULE_OK;
406 }
407
408 /*
409  *      Only free memory we allocated.  The strings allocated via
410  *      cf_section_parse() do not need to be freed.
411  */
412 static int rlm_rest_detach(void *instance)
413 {
414         rlm_rest_t *my_instance = instance;
415
416         fr_connection_pool_delete(my_instance->conn_pool);
417
418         /* Free any memory used by libcurl */
419         rest_cleanup();
420
421         return 0;
422 }
423
424 /*
425  *      The module name should be the only globally exported symbol.
426  *      That is, everything else should be 'static'.
427  *
428  *      If the module needs to temporarily modify it's instantiation
429  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
430  *      The server will then take care of ensuring that the module
431  *      is single-threaded.
432  */
433 module_t rlm_rest = {
434         RLM_MODULE_INIT,
435         "rlm_rest",
436         RLM_TYPE_THREAD_SAFE,           /* type */
437         rlm_rest_instantiate,           /* instantiation */
438         rlm_rest_detach,                /* detach */
439         {
440                 rlm_rest_authenticate,  /* authentication */
441                 rlm_rest_authorize,     /* authorization */
442                 NULL,                   /* preaccounting */
443                 rlm_rest_accounting,    /* accounting */
444                 rlm_rest_checksimul,    /* checksimul */
445                 NULL,                   /* pre-proxy */
446                 NULL,                   /* post-proxy */
447                 NULL                    /* post-auth */
448         },
449 };