a5365ed24a3dbbf9a041d853c0432302623a01a7
[freeradius.git] / src / modules / rlm_couchbase / rlm_couchbase.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 Integrate FreeRADIUS with the Couchbase document database.
21  * @file rlm_couchbase.c
22  *
23  * @author Aaron Hurt <ahurt@anbcs.com>
24  * @copyright 2013-2014 The FreeRADIUS Server Project.
25  */
26
27 RCSID("$Id$")
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/libradius.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/rad_assert.h>
33
34 #include <libcouchbase/couchbase.h>
35
36 #include "mod.h"
37 #include "couchbase.h"
38 #include "jsonc_missing.h"
39
40 /**
41  * Client Configuration
42  */
43 static const CONF_PARSER client_config[] = {
44         { "view", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, client_view), "_design/client/_view/by_name" },
45         {NULL, -1, 0, NULL, NULL}     /* end the list */
46 };
47
48 /**
49  * Module Configuration
50  */
51 static const CONF_PARSER module_config[] = {
52         { "server", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, server_raw), NULL },
53         { "bucket", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, rlm_couchbase_t, bucket), NULL },
54         { "password", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, password), NULL },
55 #ifdef WITH_ACCOUNTING
56         { "acct_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, acct_key), "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}" },
57         { "doctype", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, doctype), "radacct" },
58         { "expire", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_couchbase_t, expire), 0 },
59 #endif
60         { "user_key", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, user_key), "raduser_%{md5:%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}}" },
61         { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, read_clients), NULL }, /* NULL defaults to "no" */
62         { "client", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) client_config },
63 #ifdef WITH_SESSION_MGMT
64         { "check_simul", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, check_simul), NULL }, /* NULL defaults to "no" */
65         { "simul_view", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_couchbase_t, simul_view), "_design/acct/_view/by_user" },
66         { "simul_vkey", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_couchbase_t, simul_vkey), "%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}" },
67         { "verify_simul", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_couchbase_t, verify_simul), NULL }, /* NULL defaults to "no" */
68 #endif
69         {NULL, -1, 0, NULL, NULL}     /* end the list */
70 };
71
72 /** Initialize the rlm_couchbase module
73  *
74  * Intialize the module and create the initial Couchbase connection pool.
75  *
76  * @param  conf     The module configuration.
77  * @param  instance The module instance.
78  * @return          Returns 0 on success, -1 on error.
79  */
80 static int mod_instantiate(CONF_SECTION *conf, void *instance)
81 {
82         static bool version_done;
83
84         rlm_couchbase_t *inst = instance;   /* our module instance */
85
86         if (!version_done) {
87                 version_done = true;
88                 INFO("rlm_couchbase: json-c version: %s", json_c_version());
89                 INFO("rlm_couchbase: libcouchbase version: %s", lcb_get_version(NULL));
90         }
91
92         {
93                 char *server, *p;
94                 size_t len, i;
95                 bool sep = false;
96
97                 len = talloc_array_length(inst->server_raw);
98                 server = p = talloc_array(inst, char, len);
99                 for (i = 0; i < len; i++) {
100                         switch (inst->server_raw[i]) {
101                         case '\t':
102                         case ' ':
103                         case ',':
104                                 /* Consume multiple separators occurring in sequence */
105                                 if (sep == true) continue;
106
107                                 sep = true;
108                                 *p++ = ';';
109                                 break;
110
111                         default:
112                                 sep = false;
113                                 *p++ = inst->server_raw[i];
114                                 break;
115                         }
116                 }
117
118                 *p = '\0';
119                 inst->server = server;
120         }
121
122         /* setup item map */
123         if (mod_build_attribute_element_map(conf, inst) != 0) {
124                 /* fail */
125                 return -1;
126         }
127
128         /* initiate connection pool */
129         inst->pool = fr_connection_pool_module_init(conf, inst, mod_conn_create, NULL, NULL);
130
131         /* check connection pool */
132         if (!inst->pool) {
133                 ERROR("rlm_couchbase: failed to initiate connection pool");
134                 /* fail */
135                 return -1;
136         }
137
138         /* load clients if requested */
139         if (inst->read_clients) {
140                 CONF_SECTION *cs, *map, *tmpl; /* conf section */
141
142                 /* attempt to find client section */
143                 cs = cf_section_sub_find(conf, "client");
144                 if (!cs) {
145                         ERROR("rlm_couchbase: failed to find client section while loading clients");
146                         /* fail */
147                         return -1;
148                 }
149
150                 /* attempt to find attribute subsection */
151                 map = cf_section_sub_find(cs, "attribute");
152                 if (!map) {
153                         ERROR("rlm_couchbase: failed to find attribute subsection while loading clients");
154                         /* fail */
155                         return -1;
156                 }
157
158                 tmpl = cf_section_sub_find(cs, "template");
159
160                 /* debugging */
161                 DEBUG("rlm_couchbase: preparing to load client documents");
162
163                 /* attempt to load clients */
164                 if (mod_load_client_documents(inst, tmpl, map) != 0) {
165                         /* fail */
166                         return -1;
167                 }
168         }
169
170         /* return okay */
171         return 0;
172 }
173
174 /** Handle authorization requests using Couchbase document data
175  *
176  * Attempt to fetch the document assocaited with the requested user by
177  * using the deterministic key defined in the configuration.  When a valid
178  * document is found it will be parsed and the containing value pairs will be
179  * injected into the request.
180  *
181  * @param  instance The module instance.
182  * @param  request  The authorization request.
183  * @return          Returns operation status (@p rlm_rcode_t).
184  */
185 static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
186 {
187         rlm_couchbase_t *inst = instance;       /* our module instance */
188         rlm_couchbase_handle_t *handle = NULL;  /* connection pool handle */
189         char dockey[MAX_KEY_SIZE];              /* our document key */
190         lcb_error_t cb_error = LCB_SUCCESS;     /* couchbase error holder */
191         rlm_rcode_t rcode = RLM_MODULE_OK;      /* return code */
192
193         /* assert packet as not null */
194         rad_assert(request->packet != NULL);
195
196         /* attempt to build document key */
197         if (radius_xlat(dockey, sizeof(dockey), request, inst->user_key, NULL, NULL) < 0) {
198                 /* log error */
199                 RERROR("could not find user key attribute (%s) in packet", inst->user_key);
200                 /* return */
201                 return RLM_MODULE_FAIL;
202         }
203
204         /* get handle */
205         handle = fr_connection_get(inst->pool);
206
207         /* check handle */
208         if (!handle) return RLM_MODULE_FAIL;
209
210         /* set couchbase instance */
211         lcb_t cb_inst = handle->handle;
212
213         /* set cookie */
214         cookie_t *cookie = handle->cookie;
215
216         /* fetch document */
217         cb_error = couchbase_get_key(cb_inst, cookie, dockey);
218
219         /* check error */
220         if (cb_error != LCB_SUCCESS || !cookie->jobj) {
221                 /* log error */
222                 RERROR("failed to fetch document or parse return");
223                 /* set return */
224                 rcode = RLM_MODULE_FAIL;
225                 /* return */
226                 goto finish;
227         }
228
229         /* debugging */
230         RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj));
231
232         /* inject config value pairs defined in this json oblect */
233         mod_json_object_to_value_pairs(cookie->jobj, "config", request);
234
235         /* inject reply value pairs defined in this json oblect */
236         mod_json_object_to_value_pairs(cookie->jobj, "reply", request);
237
238         finish:
239
240         /* free json object */
241         if (cookie->jobj) {
242                 json_object_put(cookie->jobj);
243                 cookie->jobj = NULL;
244         }
245
246         /* release handle */
247         if (handle) {
248                 fr_connection_release(inst->pool, handle);
249         }
250
251         /* return */
252         return rcode;
253 }
254
255 #ifdef WITH_ACCOUNTING
256 /** Write accounting data to Couchbase documents
257  *
258  * Handle accounting requests and store the associated data into JSON documents
259  * in couchbase mapping attribute names to JSON element names per the module configuration.
260  *
261  * When an existing document already exists for the same accounting section the new attributes
262  * will be merged with the currently existing data.  When conflicts arrise the new attribute
263  * value will replace or be added to the existing value.
264  *
265  * @param instance The module instance.
266  * @param request  The accounting request object.
267  * @return Returns operation status (@p rlm_rcode_t).
268  */
269 static rlm_rcode_t mod_accounting(void *instance, REQUEST *request)
270 {
271         rlm_couchbase_t *inst = instance;       /* our module instance */
272         rlm_couchbase_handle_t *handle = NULL;  /* connection pool handle */
273         rlm_rcode_t rcode = RLM_MODULE_OK;      /* return code */
274         VALUE_PAIR *vp;                         /* radius value pair linked list */
275         char dockey[MAX_KEY_SIZE];              /* our document key */
276         char document[MAX_VALUE_SIZE];          /* our document body */
277         char element[MAX_KEY_SIZE];             /* mapped radius attribute to element name */
278         int status = 0;                         /* account status type */
279         int docfound = 0;                       /* document found toggle */
280         lcb_error_t cb_error = LCB_SUCCESS;     /* couchbase error holder */
281
282         /* assert packet as not null */
283         rad_assert(request->packet != NULL);
284
285         /* sanity check */
286         if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) {
287                 /* log debug */
288                 RDEBUG("could not find status type in packet");
289                 /* return */
290                 return RLM_MODULE_NOOP;
291         }
292
293         /* set status */
294         status = vp->vp_integer;
295
296         /* acknowledge the request but take no action */
297         if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) {
298                 /* log debug */
299                 RDEBUG("handling accounting on/off request without action");
300                 /* return */
301                 return RLM_MODULE_OK;
302         }
303
304         /* get handle */
305         handle = fr_connection_get(inst->pool);
306
307         /* check handle */
308         if (!handle) return RLM_MODULE_FAIL;
309
310         /* set couchbase instance */
311         lcb_t cb_inst = handle->handle;
312
313         /* set cookie */
314         cookie_t *cookie = handle->cookie;
315
316         /* attempt to build document key */
317         if (radius_xlat(dockey, sizeof(dockey), request, inst->acct_key, NULL, NULL) < 0) {
318                 /* log error */
319                 RERROR("could not find accounting key attribute (%s) in packet", inst->acct_key);
320                 /* set return */
321                 rcode = RLM_MODULE_NOOP;
322                 /* return */
323                 goto finish;
324         }
325
326         /* attempt to fetch document */
327         cb_error = couchbase_get_key(cb_inst, cookie, dockey);
328
329         /* check error and object */
330         if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
331                 /* log error */
332                 RERROR("failed to execute get request or parse returned json object");
333                 /* free and reset json object */
334                 if (cookie->jobj) {
335                         json_object_put(cookie->jobj);
336                         cookie->jobj = NULL;
337                 }
338         /* check cookie json object */
339         } else if (cookie->jobj) {
340                 /* set doc found */
341                 docfound = 1;
342                 /* debugging */
343                 RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj));
344         }
345
346         /* start json document if needed */
347         if (docfound != 1) {
348                 /* debugging */
349                 RDEBUG("no existing document found - creating new json document");
350                 /* create new json object */
351                 cookie->jobj = json_object_new_object();
352                 /* set 'docType' element for new document */
353                 json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype));
354                 /* default startTimestamp and stopTimestamp to null values */
355                 json_object_object_add(cookie->jobj, "startTimestamp", NULL);
356                 json_object_object_add(cookie->jobj, "stopTimestamp", NULL);
357         }
358
359         /* status specific replacements for start/stop time */
360         switch (status) {
361         case PW_STATUS_START:
362                 /* add start time */
363                 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
364                         /* add to json object */
365                         json_object_object_add(cookie->jobj, "startTimestamp",
366                                                mod_value_pair_to_json_object(request, vp));
367                 }
368                 break;
369
370         case PW_STATUS_STOP:
371                 /* add stop time */
372                 if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) {
373                         /* add to json object */
374                         json_object_object_add(cookie->jobj, "stopTimestamp",
375                                                mod_value_pair_to_json_object(request, vp));
376                 }
377                 /* check start timestamp and adjust if needed */
378                 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
379                 break;
380
381         case PW_STATUS_ALIVE:
382                 /* check start timestamp and adjust if needed */
383                 mod_ensure_start_timestamp(cookie->jobj, request->packet->vps);
384                 break;
385
386         default:
387                 /* don't doing anything */
388                 rcode = RLM_MODULE_NOOP;
389                 /* return */
390                 goto finish;
391         }
392
393         /* loop through pairs and add to json document */
394         for (vp = request->packet->vps; vp; vp = vp->next) {
395                 /* map attribute to element */
396                 if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) {
397                         /* debug */
398                         RDEBUG3("mapped attribute %s => %s", vp->da->name, element);
399                         /* add to json object with mapped name */
400                         json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp));
401                 }
402         }
403
404         /* copy json string to document and check size */
405         if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) {
406                 /* this isn't good */
407                 RERROR("could not write json document - insufficient buffer space");
408                 /* set return */
409                 rcode = RLM_MODULE_FAIL;
410                 /* return */
411                 goto finish;
412         }
413
414         /* debugging */
415         RDEBUG3("setting '%s' => '%s'", dockey, document);
416
417         /* store document/key in couchbase */
418         cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire);
419
420         /* check return */
421         if (cb_error != LCB_SUCCESS) {
422                 RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error);
423         }
424
425 finish:
426         /* free and reset json object */
427         if (cookie->jobj) {
428                 json_object_put(cookie->jobj);
429                 cookie->jobj = NULL;
430         }
431
432         /* release our connection handle */
433         if (handle) {
434                 fr_connection_release(inst->pool, handle);
435         }
436
437         /* return */
438         return rcode;
439 }
440 #endif
441
442 #ifdef WITH_SESSION_MGMT
443 /** Check if a given user is already logged in.
444  *
445  * Process accounting data to determine if a user is already logged in. Sets request->simul_count
446  * to the current session count for this user.
447  *
448  * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second
449  * pass and validate all logins by querying the terminal server.
450  *
451  * @param instance The module instance.
452  * @param request  The checksimul request object.
453  * @return Returns operation status (@p rlm_rcode_t).
454  */
455 static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) {
456         rlm_couchbase_t *inst = instance;      /* our module instance */
457         rlm_rcode_t rcode = RLM_MODULE_OK;     /* return code */
458         rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */
459         char vpath[256], vkey[MAX_KEY_SIZE];   /* view path and query key */
460         char docid[MAX_KEY_SIZE];              /* document id returned from view */
461         char error[512];                       /* view error return */
462         int idx = 0;                           /* row array index counter */
463         char element[MAX_KEY_SIZE];            /* mapped radius attribute to element name */
464         lcb_error_t cb_error = LCB_SUCCESS;    /* couchbase error holder */
465         json_object *json, *jval;              /* json object holders */
466         json_object *jrows = NULL;             /* json object to hold view rows */
467         VALUE_PAIR *vp;                        /* value pair */
468         uint32_t client_ip_addr = 0;           /* current client ip address */
469         char const *client_cs_id = NULL;       /* current client calling station id */
470         char *user_name = NULL;                /* user name from accounting document */
471         char *session_id = NULL;               /* session id from accounting document */
472         char *cs_id = NULL;                    /* calling station id from accounting document */
473         uint32_t nas_addr = 0;                 /* nas address from accounting document */
474         uint32_t nas_port = 0;                 /* nas port from accounting document */
475         uint32_t framed_ip_addr = 0;           /* framed ip address from accounting document */
476         char framed_proto = 0;                 /* framed proto from accounting document */
477         int session_time = 0;                  /* session time from accounting document */
478
479         /* do nothing if this is not enabled */
480         if (inst->check_simul != true) {
481                 RDEBUG3("mod_checksimul returning noop - not enabled");
482                 return RLM_MODULE_NOOP;
483         }
484
485         /* ensure valid username in request */
486         if ((!request->username) || (request->username->vp_length == '\0')) {
487                 RDEBUG3("mod_checksimul - invalid username");
488                 return RLM_MODULE_INVALID;
489         }
490
491         /* attempt to build view key */
492         if (radius_xlat(vkey, sizeof(vkey), request, inst->simul_vkey, NULL, NULL) < 0) {
493                 /* log error */
494                 RERROR("could not find simultaneous use view key attribute (%s) in packet", inst->simul_vkey);
495                 /* return */
496                 return RLM_MODULE_FAIL;
497         }
498
499         /* get handle */
500         handle = fr_connection_get(inst->pool);
501
502         /* check handle */
503         if (!handle) return RLM_MODULE_FAIL;
504
505         /* set couchbase instance */
506         lcb_t cb_inst = handle->handle;
507
508         /* set cookie */
509         cookie_t *cookie = handle->cookie;
510
511         /* build view path */
512         snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after",
513                  inst->simul_view, vkey);
514
515         /* query view for document */
516         cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL);
517
518         /* check error and object */
519         if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
520                 /* log error */
521                 RERROR("failed to execute view request or parse return");
522                 /* set return */
523                 rcode = RLM_MODULE_FAIL;
524                 /* return */
525                 goto finish;
526         }
527
528         /* debugging */
529         RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
530
531         /* check for error in json object */
532         if (json_object_object_get_ex(cookie->jobj, "error", &json)) {
533                 /* build initial error buffer */
534                 strlcpy(error, json_object_get_string(json), sizeof(error));
535                 /* get error reason */
536                 if (json_object_object_get_ex(cookie->jobj, "reason", &json)) {
537                         /* append divider */
538                         strlcat(error, " - ", sizeof(error));
539                         /* append reason */
540                         strlcat(error, json_object_get_string(json), sizeof(error));
541                 }
542                 /* log error */
543                 RERROR("view request failed with error: %s", error);
544                 /* set return */
545                 rcode = RLM_MODULE_FAIL;
546                 /* return */
547                 goto finish;
548         }
549
550         /* check for document id in return */
551         if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) {
552                 /* log error */
553                 RERROR("failed to fetch rows from view payload");
554                 /* set return */
555                 rcode = RLM_MODULE_FAIL;
556                 /* return */
557                 goto finish;
558         }
559
560         /* get and hold rows */
561         jrows = json_object_get(json);
562
563         /* free cookie object */
564         if (cookie->jobj) {
565                 json_object_put(cookie->jobj);
566                 cookie->jobj = NULL;
567         }
568
569         /* check for valid row value */
570         if (!jrows || !json_object_is_type(jrows, json_type_array)) {
571                 /* log error */
572                 RERROR("no valid rows returned from view: %s", vpath);
573                 /* set return */
574                 rcode = RLM_MODULE_FAIL;
575                 /* return */
576                 goto finish;
577         }
578
579         /* debugging */
580         RDEBUG3("jrows == %s", json_object_to_json_string(jrows));
581
582         /* set the count */
583         request->simul_count = json_object_array_length(jrows);
584
585         /* debugging */
586         RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue);
587
588         /* check count */
589         if (request->simul_count < request->simul_max) {
590                 rcode = RLM_MODULE_OK;
591                 goto finish;
592         }
593
594         /*
595          * Current session count exceeds configured maximum.
596          * Continue on to verify the sessions if configured otherwise stop here.
597          */
598         if (inst->verify_simul != true) {
599                 rcode = RLM_MODULE_OK;
600                 goto finish;
601         }
602
603         /* debugging */
604         RDEBUG("verifying session count");
605
606         /* reset the count */
607         request->simul_count = 0;
608
609         /* get client ip address for MPP detection below */
610         if ((vp = fr_pair_find_by_num(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
611                 client_ip_addr = vp->vp_ipaddr;
612         }
613
614         /* get calling station id for MPP detection below */
615         if ((vp = fr_pair_find_by_num(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
616                 client_cs_id = vp->vp_strvalue;
617         }
618
619         /* loop across all row elements */
620         for (idx = 0; idx < json_object_array_length(jrows); idx++) {
621                 /* clear docid */
622                 memset(docid, 0, sizeof(docid));
623
624                 /* fetch current index */
625                 json = json_object_array_get_idx(jrows, idx);
626
627                 /* get document id */
628                 if (json_object_object_get_ex(json, "id", &jval)) {
629                         /* copy and check length */
630                         if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) {
631                                 RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE);
632                                 continue;
633                         }
634                 }
635
636                 /* check for valid doc id */
637                 if (docid[0] == 0) {
638                         RWARN("failed to fetch document id from row - skipping");
639                         continue;
640                 }
641
642                 /* fetch document */
643                 cb_error = couchbase_get_key(cb_inst, cookie, docid);
644
645                 /* check error and object */
646                 if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) {
647                         /* log error */
648                         RERROR("failed to execute get request or parse return");
649                         /* set return */
650                         rcode = RLM_MODULE_FAIL;
651                         /* return */
652                         goto finish;
653                 }
654
655                 /* debugging */
656                 RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj));
657
658                 /* get element name for User-Name attribute */
659                 if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) {
660                         /* get and check username element */
661                         if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
662                                 RDEBUG("cannot zap stale entry without username");
663                                 rcode = RLM_MODULE_FAIL;
664                                 goto finish;
665                         }
666                         /* copy json string value to user_name */
667                         user_name = talloc_typed_strdup(request, json_object_get_string(jval));
668                 } else {
669                         RDEBUG("failed to find map entry for User-Name attribute");
670                         rcode = RLM_MODULE_FAIL;
671                         goto finish;
672                 }
673
674                 /* get element name for Acct-Session-Id attribute */
675                 if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) {
676                         /* get and check session id element */
677                         if (!json_object_object_get_ex(cookie->jobj, element, &jval)){
678                                 RDEBUG("cannot zap stale entry without session id");
679                                 rcode = RLM_MODULE_FAIL;
680                                 goto finish;
681                         }
682                         /* copy json string value to session_id */
683                         session_id = talloc_typed_strdup(request, json_object_get_string(jval));
684                 } else {
685                         RDEBUG("failed to find map entry for Acct-Session-Id attribute");
686                         rcode = RLM_MODULE_FAIL;
687                         goto finish;
688                 }
689
690                 /* get element name for NAS-IP-Address attribute */
691                 if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) {
692                         /* attempt to get and nas address element */
693                         if (json_object_object_get_ex(cookie->jobj, element, &jval)){
694                                 nas_addr = inet_addr(json_object_get_string(jval));
695                         }
696                 }
697
698                 /* get element name for NAS-Port attribute */
699                 if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) {
700                         /* attempt to get nas port element */
701                         if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
702                                 nas_port = (uint32_t) json_object_get_int(jval);
703                         }
704                 }
705
706                 /* check terminal server */
707                 int check = rad_check_ts(nas_addr, nas_port, user_name, session_id);
708
709                 /* take action based on check return */
710                 if (check == 0) {
711                         /* stale record - zap it if enabled */
712                         if (inst->delete_stale_sessions) {
713                                 /* get element name for Framed-IP-Address attribute */
714                                 if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
715                                         /* attempt to get framed ip address element */
716                                         if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
717                                                 framed_ip_addr = inet_addr(json_object_get_string(jval));
718                                         }
719                                 }
720
721                                 /* get element name for Framed-Port attribute */
722                                 if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) {
723                                         /* attempt to get framed port element */
724                                         if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
725                                                 if (strcmp(json_object_get_string(jval), "PPP") == 0) {
726                                                         framed_proto = 'P';
727                                                 } else if (strcmp(json_object_get_string(jval), "SLIP") == 0) {
728                                                         framed_proto = 'S';
729                                                 }
730                                         }
731                                 }
732
733                                 /* get element name for Acct-Session-Time attribute */
734                                 if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) {
735                                         /* attempt to get session time element */
736                                         if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
737                                                 session_time = json_object_get_int(jval);
738                                         }
739                                 }
740
741                                 /* zap session */
742                                 session_zap(request, nas_addr, nas_port, user_name, session_id,
743                                             framed_ip_addr, framed_proto, session_time);
744                         }
745                 } else if (check == 1) {
746                         /* user is still logged in - increase count */
747                         ++request->simul_count;
748
749                         /* get element name for Framed-IP-Address attribute */
750                         if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) {
751                                 /* attempt to get framed ip address element */
752                                 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
753                                         framed_ip_addr = inet_addr(json_object_get_string(jval));
754                                 } else {
755                                         /* ensure 0 if not found */
756                                         framed_ip_addr = 0;
757                                 }
758                         }
759
760                         /* get element name for Calling-Station-Id attribute */
761                         if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) {
762                                 /* attempt to get framed ip address element */
763                                 if (json_object_object_get_ex(cookie->jobj, element, &jval)) {
764                                         /* copy json string value to cs_id */
765                                         cs_id = talloc_typed_strdup(request, json_object_get_string(jval));
766                                 } else {
767                                         /* ensure null if not found */
768                                         cs_id = NULL;
769                                 }
770                         }
771
772                         /* Does it look like a MPP attempt? */
773                         if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) {
774                                 request->simul_mpp = 2;
775                         } else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) {
776                                 request->simul_mpp = 2;
777                         }
778
779                 } else {
780                         /* check failed - return error */
781                         REDEBUG("failed to check the terminal server for user '%s'", user_name);
782                         rcode = RLM_MODULE_FAIL;
783                         goto finish;
784                 }
785
786                 /* free and reset document user name talloc */
787                 if (user_name) TALLOC_FREE(user_name);
788
789                 /* free and reset document calling station id talloc */
790                 if (cs_id) TALLOC_FREE(cs_id);
791
792                 /* free and reset document session id talloc */
793                 if (session_id) TALLOC_FREE(session_id);
794
795                 /* free and reset json object before fetching next row */
796                 if (cookie->jobj) {
797                         json_object_put(cookie->jobj);
798                         cookie->jobj = NULL;
799                 }
800         }
801
802         /* debugging */
803         RDEBUG("Retained %d open sessions for %s after verification",
804                request->simul_count, request->username->vp_strvalue);
805
806 finish:
807         if (user_name) talloc_free(user_name);
808         if (cs_id) talloc_free(cs_id);
809         if (session_id) talloc_free(session_id);
810
811         /* free rows */
812         if (jrows) json_object_put(jrows);
813
814         /* free and reset json object */
815         if (cookie->jobj) {
816                 json_object_put(cookie->jobj);
817                 cookie->jobj = NULL;
818         }
819
820         if (handle) fr_connection_release(inst->pool, handle);
821
822         /*
823          * The Auth module apparently looks at request->simul_count,
824          * not the return value of this module when deciding to deny
825          * a call for too many sessions.
826          */
827         return rcode;
828 }
829 #endif
830
831 /** Detach the module
832  *
833  * Detach the module instance and free any allocated resources.
834  *
835  * @param  instance The module instance.
836  * @return          Returns 0 (success) in all conditions.
837  */
838 static int mod_detach(void *instance)
839 {
840         rlm_couchbase_t *inst = instance;
841
842         if (inst->map) json_object_put(inst->map);
843         if (inst->pool) fr_connection_pool_free(inst->pool);
844
845         return 0;
846 }
847
848 /*
849  * Hook into the FreeRADIUS module system.
850  */
851 extern module_t rlm_couchbase;
852 module_t rlm_couchbase = {
853         .magic          = RLM_MODULE_INIT,
854         .name           = "couchbase",
855         .type           = RLM_TYPE_THREAD_SAFE,
856         .inst_size      = sizeof(rlm_couchbase_t),
857         .config         = module_config,
858         .instantiate    = mod_instantiate,
859         .detach         = mod_detach,
860         .methods = {
861                 [MOD_AUTHORIZE]         = mod_authorize,
862 #ifdef WITH_ACCOUNTING
863                 [MOD_ACCOUNTING]        = mod_accounting,
864 #endif
865 #ifdef WITH_SESSION_MGMT
866                 [MOD_SESSION]           = mod_checksimul
867 #endif
868         },
869 };