/** Load client entries from Couchbase client documents on startup * * This function executes the view defined in the module configuration and loops * through all returned rows. The view is called with "stale=false" to ensure the * most accurate data available when the view is called. This will force an index * rebuild on this design document in Couchbase. However, since this function is only * run once at sever startup this should not be a concern. * * @param inst The module instance. * @param cs The client attribute configuration section. * @return Returns 0 on success, -1 on error. */ int mod_load_client_documents(rlm_couchbase_t *inst, CONF_SECTION *cs) { void *handle = NULL; /* connection pool handle */ char vpath[256], docid[MAX_KEY_SIZE]; /* view path and document id */ char error[512]; /* view error return */ int idx = 0; /* row array index counter */ int retval = 0; /* return value */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ json_object *json, *jval; /* json object holders */ json_object *jrows = NULL; /* json object to hold view rows */ CONF_SECTION *client; /* freeradius config section */ RADCLIENT *c; /* freeradius client */ /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return -1; /* set handle pointer */ rlm_couchbase_handle_t *handle_t = handle; /* set couchbase instance */ lcb_t cb_inst = handle_t->handle; /* set cookie */ cookie_t *cookie = handle_t->cookie; /* check cookie */ if (cookie) { /* clear cookie */ memset(cookie, 0, sizeof(cookie_t)); } else { /* log error */ ERROR("rlm_couchbase: cookie not usable - possibly not allocated"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* build view path */ snprintf(vpath, sizeof(vpath), "%s?stale=false", inst->client_view); /* init cookie error status */ cookie->jerr = json_tokener_success; /* setup cookie tokener */ cookie->jtok = json_tokener_new(); /* query view for document */ cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL); /* free json token */ json_tokener_free(cookie->jtok); /* check error */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success) { /* log error */ ERROR("rlm_couchbase: failed to execute view request or parse return"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* check cookie */ if (!cookie->jobj) { /* log error */ ERROR("rlm_couchbase: failed to fetch view"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* check for error in json object */ if (json_object_object_get_ex(cookie->jobj, "error", &json)) { /* build initial error buffer */ strlcpy(error, json_object_get_string(json), sizeof(error)); /* get error reason */ if (json_object_object_get_ex(cookie->jobj, "reason", &json)) { /* append divider */ strlcat(error, " - ", sizeof(error)); /* append reason */ strlcat(error, json_object_get_string(json), sizeof(error)); } /* log error */ ERROR("rlm_couchbase: view request failed with error: %s", error); /* set return */ retval = -1; /* return */ goto free_and_return; } /* check for document id in return */ if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) { /* log error */ ERROR("rlm_couchbase: failed to fetch rows from view payload"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* get and hold rows */ jrows = json_object_get(json); /* free cookie object */ json_object_put(cookie->jobj); /* debugging */ DEBUG("rlm_couchbase: jrows == %s", json_object_to_json_string(jrows)); /* check for valid row value */ if (!json_object_is_type(jrows, json_type_array) && json_object_array_length(jrows) < 1) { /* log error */ ERROR("rlm_couchbase: couldn't find valid rows in view return"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* loop across all row elements */ for (idx = 0; idx < json_object_array_length(jrows); idx++) { /* fetch current index */ json = json_object_array_get_idx(jrows, idx); /* get document id */ if (json_object_object_get_ex(json, "id", &jval)) { /* clear docid */ memset(docid, 0, sizeof(docid)); /* copy and check length */ if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) { ERROR("rlm_couchbase: document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } /* check for valid doc id */ if (docid[0] == 0) { WARN("rlm_couchbase: failed to fetch document id from row - skipping"); continue; } /* debugging */ DEBUG("rlm_couchbase: preparing to fetch docid '%s'", docid); /* reset cookie error status */ cookie->jerr = json_tokener_success; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, docid); /* check error */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success) { /* log error */ ERROR("rlm_couchbase: failed to execute get request or parse return"); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG("rlm_couchbase: cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* allocate conf section */ client = cf_section_alloc(NULL, "client", docid); if (_mod_client_map_section(client, cs, cookie->jobj, docid) != 0) { /* free config setion */ talloc_free(client); /* set return */ retval = -1; /* return */ goto free_and_return; } /* * @todo These should be parented from something. */ c = client_afrom_cs(NULL, client, false); if (!c) { ERROR("rlm_couchbase: failed to allocate client"); /* free config setion */ talloc_free(client); /* set return */ retval = -1; /* return */ goto free_and_return; } /* * Client parents the CONF_SECTION which defined it. */ talloc_steal(c, client); /* attempt to add client */ if (!client_add(NULL, c)) { ERROR("rlm_couchbase: failed to add client from %s, possible duplicate?", docid); /* free client */ client_free(c); /* set return */ retval = -1; /* return */ goto free_and_return; } /* debugging */ DEBUG("rlm_couchbase: client '%s' added", c->longname); /* free json object */ json_object_put(cookie->jobj); } free_and_return: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* free rows */ if (jrows) { json_object_put(jrows); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return retval; }
/** Check if a given user is already logged in. * * Process accounting data to determine if a user is already logged in. Sets request->simul_count * to the current session count for this user. * * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second * pass and validate all logins by querying the terminal server. * * @param instance The module instance. * @param request The checksimul request object. * @return Returns operation status (@p rlm_rcode_t). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char vpath[256], vkey[MAX_KEY_SIZE]; /* view path and query key */ char docid[MAX_KEY_SIZE]; /* document id returned from view */ char error[512]; /* view error return */ int idx = 0; /* row array index counter */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ json_object *json, *jval; /* json object holders */ json_object *jrows = NULL; /* json object to hold view rows */ VALUE_PAIR *vp; /* value pair */ uint32_t client_ip_addr = 0; /* current client ip address */ char const *client_cs_id = NULL; /* current client calling station id */ char *user_name = NULL; /* user name from accounting document */ char *session_id = NULL; /* session id from accounting document */ char *cs_id = NULL; /* calling station id from accounting document */ uint32_t nas_addr = 0; /* nas address from accounting document */ uint32_t nas_port = 0; /* nas port from accounting document */ uint32_t framed_ip_addr = 0; /* framed ip address from accounting document */ char framed_proto = 0; /* framed proto from accounting document */ int session_time = 0; /* session time from accounting document */ /* do nothing if this is not enabled */ if (inst->check_simul != true) { RDEBUG3("mod_checksimul returning noop - not enabled"); return RLM_MODULE_NOOP; } /* ensure valid username in request */ if ((!request->username) || (request->username->vp_length == '\0')) { RDEBUG3("mod_checksimul - invalid username"); return RLM_MODULE_INVALID; } /* attempt to build view key */ if (radius_xlat(vkey, sizeof(vkey), request, inst->simul_vkey, NULL, NULL) < 0) { /* log error */ RERROR("could not find simultaneous use view key attribute (%s) in packet", inst->simul_vkey); /* return */ return RLM_MODULE_FAIL; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* build view path */ snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after", inst->simul_view, vkey); /* query view for document */ cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute view request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* check for error in json object */ if (json_object_object_get_ex(cookie->jobj, "error", &json)) { /* build initial error buffer */ strlcpy(error, json_object_get_string(json), sizeof(error)); /* get error reason */ if (json_object_object_get_ex(cookie->jobj, "reason", &json)) { /* append divider */ strlcat(error, " - ", sizeof(error)); /* append reason */ strlcat(error, json_object_get_string(json), sizeof(error)); } /* log error */ RERROR("view request failed with error: %s", error); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* check for document id in return */ if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) { /* log error */ RERROR("failed to fetch rows from view payload"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* get and hold rows */ jrows = json_object_get(json); /* free cookie object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check for valid row value */ if (!jrows || !json_object_is_type(jrows, json_type_array)) { /* log error */ RERROR("no valid rows returned from view: %s", vpath); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("jrows == %s", json_object_to_json_string(jrows)); /* set the count */ request->simul_count = json_object_array_length(jrows); /* debugging */ RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue); /* check count */ if (request->simul_count < request->simul_max) { rcode = RLM_MODULE_OK; goto free_and_return; } /* * Current session count exceeds configured maximum. * Continue on to verify the sessions if configured otherwise stop here. */ if (inst->verify_simul != true) { rcode = RLM_MODULE_OK; goto free_and_return; } /* debugging */ RDEBUG("verifying session count"); /* reset the count */ request->simul_count = 0; /* get client ip address for MPP detection below */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { client_ip_addr = vp->vp_ipaddr; } /* get calling station id for MPP detection below */ if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { client_cs_id = vp->vp_strvalue; } /* loop across all row elements */ for (idx = 0; idx < json_object_array_length(jrows); idx++) { /* clear docid */ memset(docid, 0, sizeof(docid)); /* fetch current index */ json = json_object_array_get_idx(jrows, idx); /* get document id */ if (json_object_object_get_ex(json, "id", &jval)) { /* copy and check length */ if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) { RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } /* check for valid doc id */ if (docid[0] == 0) { RWARN("failed to fetch document id from row - skipping"); continue; } /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, docid); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* get element name for User-Name attribute */ if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) { /* get and check username element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without username"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* copy json string value to user_name */ user_name = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for User-Name attribute"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* get element name for Acct-Session-Id attribute */ if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) { /* get and check session id element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without session id"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* copy json string value to session_id */ session_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for Acct-Session-Id attribute"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* get element name for NAS-IP-Address attribute */ if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) { /* attempt to get and nas address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)){ nas_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for NAS-Port attribute */ if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) { /* attempt to get nas port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { nas_port = (uint32_t) json_object_get_int(jval); } } /* check terminal server */ int check = rad_check_ts(nas_addr, nas_port, user_name, session_id); /* take action based on check return */ if (check == 0) { /* stale record - zap it if enabled */ if (inst->delete_stale_sessions) { /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for Framed-Port attribute */ if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) { /* attempt to get framed port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { if (strcmp(json_object_get_string(jval), "PPP") == 0) { framed_proto = 'P'; } else if (strcmp(json_object_get_string(jval), "SLIP") == 0) { framed_proto = 'S'; } } } /* get element name for Acct-Session-Time attribute */ if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) { /* attempt to get session time element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { session_time = json_object_get_int(jval); } } /* zap session */ session_zap(request, nas_addr, nas_port, user_name, session_id, framed_ip_addr, framed_proto, session_time); } } else if (check == 1) { /* user is still logged in - increase count */ ++request->simul_count; /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } else { /* ensure 0 if not found */ framed_ip_addr = 0; } } /* get element name for Calling-Station-Id attribute */ if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { /* copy json string value to cs_id */ cs_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { /* ensure null if not found */ cs_id = NULL; } } /* Does it look like a MPP attempt? */ if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) { request->simul_mpp = 2; } else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) { request->simul_mpp = 2; } } else { /* check failed - return error */ REDEBUG("failed to check the terminal server for user '%s'", user_name); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* free and reset document user name talloc */ if (user_name) { talloc_free(user_name); user_name = NULL; } /* free and reset document calling station id talloc */ if (cs_id) { talloc_free(cs_id); cs_id = NULL; } /* free and reset document session id talloc */ if (session_id) { talloc_free(session_id); session_id = NULL; } /* free and reset json object before fetching next row */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } } /* debugging */ RDEBUG("retained %d open sessions for %s after verification", request->simul_count, request->username->vp_strvalue); free_and_return: /* free document user name talloc */ if (user_name) { talloc_free(user_name); } /* free document calling station id talloc */ if (cs_id) { talloc_free(cs_id); } /* free document session id talloc */ if (session_id) { talloc_free(session_id); } /* free rows */ if (jrows) { json_object_put(jrows); } /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* * The Auth module apparently looks at request->simul_count, * not the return value of this module when deciding to deny * a call for too many sessions. */ return rcode; }