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