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