Pass a threadsafe ctx into fr_connection_pool create callback
[freeradius.git] / src / modules / rlm_couchbase / mod.c
1 /*
2  *   This program 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
5  *   (at 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  *
20  * @brief Utillity functions used in the module.
21  * @file mod.c
22  *
23  * @copyright 2013-2014 Aaron Hurt <ahurt@anbcs.com>
24  */
25
26 RCSID("$Id$");
27
28 #include <freeradius-devel/radiusd.h>
29
30 #include <libcouchbase/couchbase.h>
31 #include <json.h>
32
33 #include "mod.h"
34 #include "couchbase.h"
35 #include "jsonc_missing.h"
36
37 /* free couchbase instance handle and any additional context memory */
38 static int _mod_conn_free(rlm_couchbase_handle_t *chandle)
39 {
40         lcb_t cb_inst = chandle->handle;                /* couchbase instance */
41
42         /* destroy/free couchbase instance */
43         lcb_destroy(cb_inst);
44
45         /* return */
46         return 0;
47 }
48
49 /* create new connection pool handle */
50 void *mod_conn_create(TALLOC_CTX *ctx, void *instance)
51 {
52         rlm_couchbase_t *inst = instance;           /* module instance pointer */
53         rlm_couchbase_handle_t *chandle = NULL;     /* connection handle pointer */
54         cookie_t *cookie = NULL;                    /* couchbase cookie */
55         lcb_t cb_inst;                              /* couchbase connection instance */
56         lcb_error_t cb_error = LCB_SUCCESS;         /* couchbase error status */
57
58         /* create instance */
59         cb_inst = couchbase_init_connection(inst->server, inst->bucket, inst->password);
60
61         /* check couchbase instance status */
62         if ((cb_error = lcb_get_last_error(cb_inst)) != LCB_SUCCESS) {
63                 ERROR("rlm_couchbase: failed to initiate couchbase connection: %s (0x%x)", lcb_strerror(NULL, cb_error), cb_error);
64                 /* destroy/free couchbase instance */
65                 lcb_destroy(cb_inst);
66                 /* fail */
67                 return NULL;
68         }
69
70         /* allocate memory for couchbase connection instance abstraction */
71         chandle = talloc_zero(ctx, rlm_couchbase_handle_t);
72         talloc_set_destructor(chandle, _mod_conn_free);
73
74         cookie = talloc_zero(chandle, cookie_t);
75
76         /* initialize cookie error holder */
77         cookie->jerr = json_tokener_success;
78
79         /* populate handle with allocated structs */
80         chandle->cookie = cookie;
81         chandle->handle = cb_inst;
82
83         /* return handle struct */
84         return chandle;
85 }
86
87 /* verify valid couchbase connection handle */
88 int mod_conn_alive(UNUSED void *instance, void *handle)
89 {
90         rlm_couchbase_handle_t *chandle = handle;   /* connection handle pointer */
91         lcb_t cb_inst = chandle->handle;            /* couchbase instance */
92         lcb_error_t cb_error = LCB_SUCCESS;         /* couchbase error status */
93
94         /* attempt to get server list */
95         const char *const *servers = lcb_get_server_list(cb_inst);
96
97         /* check error state and server list return */
98         if (((cb_error = lcb_get_last_error(cb_inst)) != LCB_SUCCESS) || (servers == NULL)) {
99                 /* log error */
100                 ERROR("rlm_couchbase: failed to get couchbase server topology: %s (0x%x)", lcb_strerror(NULL, cb_error), cb_error);
101                 /* return false */
102                 return false;
103         }
104         return true;
105 }
106
107 /* build json object for mapping radius attributes to json elements */
108 int mod_build_attribute_element_map(CONF_SECTION *conf, void *instance)
109 {
110         rlm_couchbase_t *inst = instance;   /* our module instance */
111         CONF_SECTION *cs;                   /* module config section */
112         CONF_ITEM *ci;                      /* config item */
113         CONF_PAIR *cp;                      /* conig pair */
114         const char *attribute, *element;    /* attribute and element names */
115
116         /* find map section */
117         cs = cf_section_sub_find(conf, "map");
118
119         /* check section */
120         if (!cs) {
121                 ERROR("rlm_couchbase: failed to find 'map' section in config");
122                 /* fail */
123                 return -1;
124         }
125
126         /* create attribute map object */
127         inst->map = json_object_new_object();
128
129         /* parse update section */
130         for (ci = cf_item_find_next(cs, NULL); ci != NULL; ci = cf_item_find_next(cs, ci)) {
131                 /* validate item */
132                 if (!cf_item_is_pair(ci)) {
133                         ERROR("rlm_couchbase: failed to parse invalid item in 'map' section");
134                         /* free map */
135                         if (inst->map) {
136                                 json_object_put(inst->map);
137                         }
138                         /* fail */
139                         return -1;
140                 }
141
142                 /* get value pair from item */
143                 cp = cf_itemtopair(ci);
144
145                 /* get pair name (element name) */
146                 element = cf_pair_attr(cp);
147
148                 /* get pair value (attribute name) */
149                 attribute = cf_pair_value(cp);
150
151                 /* add pair name and value */
152                 json_object_object_add(inst->map, attribute, json_object_new_string(element));
153
154                 /* debugging */
155                 DEBUG("rlm_couchbase: added attribute '%s' to element '%s' map to object", attribute, element);
156         }
157
158         /* debugging */
159         DEBUG("rlm_couchbase: built attribute to element map %s", json_object_to_json_string(inst->map));
160
161         /* return */
162         return 0;
163 }
164
165 /* map free radius attribute to user defined json element name */
166 int mod_attribute_to_element(const char *name, json_object *map, void *buf)
167 {
168         json_object *jval;  /* json object values */
169
170         /* clear buffer */
171         memset((char *) buf, 0, MAX_KEY_SIZE);
172
173         /* attempt to map attribute */
174         if (json_object_object_get_ex(map, name, &jval)) {
175                 int length;     /* json value length */
176                 /* get value length */
177                 length = json_object_get_string_len(jval);
178                 /* check buffer size */
179                 if (length > MAX_KEY_SIZE -1) {
180                         /* oops ... this value is bigger than our buffer ... error out */
181                         ERROR("rlm_couchbase: json map value larger than MAX_KEY_SIZE - %d", MAX_KEY_SIZE);
182                         /* return fail */
183                         return -1;
184                 } else {
185                         /* copy string value to buffer */
186                         strncpy(buf, json_object_get_string(jval), length);
187                         /* return good */
188                         return 0;
189                 }
190         }
191
192         /* debugging */
193         DEBUG("rlm_couchbase: skipping attribute with no map entry - %s", name);
194
195         /* default return */
196         return -1;
197 }
198
199 /* inject value pairs into given request
200  * that are defined in the passed json object
201  */
202 void *mod_json_object_to_value_pairs(json_object *json, const char *section, REQUEST *request)
203 {
204         json_object *jobj, *jval, *jop;     /* json object pointers */
205         TALLOC_CTX *ctx;                    /* talloc context for pairmake */
206         VALUE_PAIR *vp, **ptr;              /* value pair and value pair pointer for pairmake */
207
208         /* assign ctx and vps for pairmake based on section */
209         if (strcmp(section, "config") == 0) {
210                 ctx = request;
211                 ptr = &(request->config_items);
212         } else if (strcmp(section, "reply") == 0) {
213                 ctx = request->reply;
214                 ptr = &(request->reply->vps);
215         } else {
216                 /* log error - this shouldn't happen */
217                 RERROR("invalid section passed for pairmake");
218                 /* return */
219                 return NULL;
220         }
221
222         /* get config payload */
223         if (json_object_object_get_ex(json, section, &jobj)) {
224                 /* make sure we have the correct type */
225                 if (!json_object_is_type(jobj, json_type_object)) {
226                         /* log error */
227                         RERROR("invalid json type for '%s' section - sections must be json objects", section);
228                         /* reuturn */
229                         return NULL;
230                 }
231                 /* loop through object */
232                 json_object_object_foreach(jobj, attribute, json_vp) {
233                         /* check for appropriate type in value and op */
234                         if (!json_object_is_type(json_vp, json_type_object)) {
235                                 /* log error */
236                                 RERROR("invalid json type for '%s' attribute - attributes must be json objects", attribute);
237                                 /* return */
238                                 return NULL;
239                         }
240                         /* debugging */
241                         RDEBUG("parsing '%s' attribute: %s => %s", section, attribute, json_object_to_json_string(json_vp));
242                         /* create pair from json object */
243                         if (json_object_object_get_ex(json_vp, "value", &jval) &&
244                                 json_object_object_get_ex(json_vp, "op", &jop)) {
245                                 /* make correct pairs based on json object type */
246                                 switch (json_object_get_type(jval)) {
247                                         case json_type_double:
248                                         case json_type_int:
249                                         case json_type_string:
250                                                 /* debugging */
251                                                 RDEBUG("adding '%s' attribute to '%s' section", attribute, section);
252                                                 /* add pair */
253                                                 vp = pairmake(ctx, ptr, attribute, json_object_get_string(jval),
254                                                         fr_str2int(fr_tokens, json_object_get_string(jop), 0));
255                                                 /* check pair */
256                                                 if (!vp) {
257                                                         RERROR("could not build value pair for '%s' attribute (%s)", attribute, fr_strerror());
258                                                         /* return */
259                                                         return NULL;
260                                                 }
261                                         break;
262                                         case json_type_object:
263                                         case json_type_array:
264                                                 /* log error - we want to handle these eventually */
265                                                 RERROR("skipping unhandled nested json object or array value pair object");
266                                         break;
267                                         default:
268                                                 /* log error - this shouldn't ever happen */
269                                                 RERROR("skipping unhandled json type in value pair object");
270                                         break;
271                                 }
272                         } else {
273                                 /* log error */
274                                 RERROR("failed to get 'value' or 'op' element for '%s' attribute", attribute);
275                         }
276                 }
277                 /* return NULL */
278                 return NULL;
279         }
280
281         /* debugging */
282         RDEBUG("couldn't find '%s' section in json object - not adding value pairs for this section", section);
283
284         /* return NULL */
285         return NULL;
286 }
287
288 /* convert freeradius value/pair to json object
289  * basic structure taken from freeradius function
290  * vp_prints_value_json in src/lib/print.c */
291 json_object *mod_value_pair_to_json_object(REQUEST *request, VALUE_PAIR *vp)
292 {
293         char value[255];    /* radius attribute value */
294
295         /* add this attribute/value pair to our json output */
296         if (!vp->da->flags.has_tag) {
297                 switch (vp->da->type) {
298                         case PW_TYPE_INTEGER:
299                         case PW_TYPE_BYTE:
300                         case PW_TYPE_SHORT:
301                                 /* skip if we have flags */
302                                 if (vp->da->flags.has_value) break;
303 #ifdef HAVE_JSON_OBJECT_NEW_INT64
304                                 /* debug */
305                                 RDEBUG3("creating new int64 for unsigned 32 bit int/byte/short '%s'", vp->da->name);
306                                 /* return as 64 bit int - JSON spec does not support unsigned ints */
307                                 return json_object_new_int64(vp->vp_integer);
308 #else
309                                 /* debug */
310                                 RDEBUG3("creating new int for unsigned 32 bit int/byte/short '%s'", vp->da->name);
311                                 /* return as 64 bit int - JSON spec does not support unsigned ints */
312                                 return json_object_new_int(vp->vp_integer);
313 #endif
314                         break;
315                         case PW_TYPE_SIGNED:
316 #ifdef HAVE_JSON_OBJECT_NEW_INT64
317                                 /* debug */
318                                 RDEBUG3("creating new int64 for signed 32 bit integer '%s'", vp->da->name);
319                                 /* return as 64 bit int - json-c represents all ints as 64 bits internally */
320                                 return json_object_new_int64(vp->vp_signed);
321 #else
322                                 RDEBUG3("creating new int for signed 32 bit integer '%s'", vp->da->name);
323                                 /* return as signed int */
324                                 return json_object_new_int(vp->vp_signed);
325 #endif
326                         break;
327                         case PW_TYPE_INTEGER64:
328 #ifdef HAVE_JSON_OBJECT_NEW_INT64
329                                 /* debug */
330                                 RDEBUG3("creating new int64 for 64 bit integer '%s'", vp->da->name);
331                                 /* return as 64 bit int - because it is a 64 bit int */
332                                 return json_object_new_int64(vp->vp_integer64);
333 #else
334                                 /* warning */
335                                 RWARN("skipping 64 bit integer attribute '%s' - please upgrade json-c to 0.10+", vp->da->name);
336 #endif
337                         break;
338                         default:
339                                 /* silence warnings - do nothing */
340                         break;
341                 }
342         }
343
344         /* keep going if not set above */
345         switch (vp->da->type) {
346                 case PW_TYPE_STRING:
347                         /* debug */
348                         RDEBUG3("assigning string '%s' as string", vp->da->name);
349                         /* return string value */
350                         return json_object_new_string(vp->vp_strvalue);
351                 default:
352                         /* debug */
353                         RDEBUG3("assigning unhandled '%s' as string", vp->da->name);
354                         /* get standard value */
355                         vp_prints_value(value, sizeof(value), vp, 0);
356                         /* return string value from above */
357                         return json_object_new_string(value);
358                 break;
359         }
360 }
361
362 /* check current value of start timestamp in json body and update if needed */
363 int mod_ensure_start_timestamp(json_object *json, VALUE_PAIR *vps)
364 {
365         json_object *jval;      /* json object value */
366         struct tm tm;           /* struct to hold event time */
367         time_t ts = 0;          /* values to hold time in seconds */
368         VALUE_PAIR *vp;         /* values to hold value pairs */
369         char value[255];        /* store radius attribute values and our timestamp */
370
371         /* get our current start timestamp from our json body */
372         if (json_object_object_get_ex(json, "startTimestamp", &jval) == 0) {
373                 /* debugging ... this shouldn't ever happen */
374                 DEBUG("rlm_couchbase: failed to find start timestamp in current json body");
375                 /* return */
376                 return -1;
377         }
378
379         /* check the value */
380         if (strcmp(json_object_get_string(jval), "null") != 0) {
381                 /* debugging */
382                 DEBUG("rlm_couchbase: start timestamp looks good - nothing to do");
383                 /* already set - nothing else to do */
384                 return 0;
385         }
386
387         /* get current event timestamp */
388         if ((vp = pairfind(vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
389                 /* get seconds value from attribute */
390                 ts = vp->vp_date;
391         } else {
392                 /* debugging */
393                 DEBUG("rlm_couchbase: failed to find event timestamp in current request");
394                 /* return */
395                 return -1;
396         }
397
398         /* clear value */
399         memset(value, 0, sizeof(value));
400
401         /* get elapsed session time */
402         if ((vp = pairfind(vps, PW_ACCT_SESSION_TIME, 0, TAG_ANY)) != NULL) {
403                 /* calculate diff */
404                 ts = (ts - vp->vp_integer);
405                 /* calculate start time */
406                 size_t length = strftime(value, sizeof(value), "%b %e %Y %H:%M:%S %Z", localtime_r(&ts, &tm));
407                 /* check length */
408                 if (length > 0) {
409                         /* debugging */
410                         DEBUG("rlm_couchbase: calculated start timestamp: %s", value);
411                         /* store new value in json body */
412                         json_object_object_add(json, "startTimestamp", json_object_new_string(value));
413                 } else {
414                         /* debugging */
415                         DEBUG("rlm_couchbase: failed to format calculated timestamp");
416                         /* return */
417                         return -1;
418                 }
419         }
420
421         /* default return */
422         return 0;
423 }