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