/* * SQL xlat function * * For selects the first value of the first column will be returned, * for inserts, updates and deletes the number of rows affected will be * returned instead. */ static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace) { rlm_sql_handle_t *handle = NULL; rlm_sql_row_t row; rlm_sql_t *inst = instance; ssize_t ret = 0; size_t len = 0; /* * Add SQL-User-Name attribute just in case it is needed * We could search the string fmt for SQL-User-Name to see if this is * needed or not */ sql_set_user(inst, request, NULL); handle = fr_connection_get(inst->pool); if (!handle) { return 0; } rlm_sql_query_log(inst, request, NULL, query); /* * If the query starts with any of the following prefixes, * then return the number of rows affected */ if ((strncasecmp(query, "insert", 6) == 0) || (strncasecmp(query, "update", 6) == 0) || (strncasecmp(query, "delete", 6) == 0)) { int numaffected; char buffer[21]; /* 64bit max is 20 decimal chars + null byte */ if (rlm_sql_query(&handle, inst, query) != RLM_SQL_OK) { char const *error = (inst->module->sql_error)(handle, inst->config); REDEBUG("SQL query failed: %s", error); ret = -1; goto finish; } numaffected = (inst->module->sql_affected_rows)(handle, inst->config); if (numaffected < 1) { RDEBUG("SQL query affected no rows"); goto finish; } /* * Don't chop the returned number if freespace is * too small. This hack is necessary because * some implementations of snprintf return the * size of the written data, and others return * the size of the data they *would* have written * if the output buffer was large enough. */ snprintf(buffer, sizeof(buffer), "%d", numaffected); len = strlen(buffer); if (len >= freespace){ RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->config->xlat_name); (inst->module->sql_finish_query)(handle, inst->config); ret = -1; goto finish; } memcpy(out, buffer, len + 1); /* we did bounds checking above */ ret = len; (inst->module->sql_finish_query)(handle, inst->config); goto finish; } /* else it's a SELECT statement */ if (rlm_sql_select_query(&handle, inst, query) != RLM_SQL_OK) { ret = -1; /* error handled by rlm_sql_select_query */ goto finish; } ret = rlm_sql_fetch_row(&row, &handle, inst); if (ret) { REDEBUG("SQL query failed"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } if (!row) { RDEBUG("SQL query returned no results"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } if (!row[0]){ RDEBUG("NULL value in first column of result"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } len = strlen(row[0]); if (len >= freespace){ RDEBUG("Insufficient string space"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } strlcpy(out, row[0], freespace); ret = len; (inst->module->sql_finish_select_query)(handle, inst->config); finish: fr_connection_release(inst->pool, handle); return ret; }
/* * Simple xlat to read text data from a URL */ static ssize_t rest_xlat(void *instance, REQUEST *request, char const *fmt, char *out, size_t freespace) { rlm_rest_t *inst = instance; void *handle; int hcode; int ret; ssize_t len, outlen = 0; char *uri = NULL; char const *p = fmt, *q; char const *body; http_method_t method; /* There are no configurable parameters other than the URI */ rlm_rest_section_t section = { .name = "xlat", .method = HTTP_METHOD_GET, .body = HTTP_BODY_NONE, .body_str = "application/x-www-form-urlencoded", .require_auth = false, .timeout = 4, .force_to = HTTP_BODY_PLAIN }; *out = '\0'; rad_assert(fmt); RDEBUG("Expanding URI components"); handle = fr_connection_get(inst->conn_pool); if (!handle) return -1; /* * Extract the method from the start of the format string (if there is one) */ method = fr_substr2int(http_method_table, p, HTTP_METHOD_UNKNOWN, -1); if (method != HTTP_METHOD_UNKNOWN) { section.method = method; p += strlen(http_method_table[method].name); } /* * Trim whitespace */ while (isspace(*p) && p++); /* * Unescape parts of xlat'd URI, this allows REST servers to be specified by * request attributes. */ len = rest_uri_host_unescape(&uri, instance, request, handle, p); if (len <= 0) { outlen = -1; goto finish; } /* * Extract freeform body data (url can't contain spaces) */ q = strchr(p, ' '); if (q && (*++q != '\0')) { section.body = HTTP_BODY_CUSTOM_LITERAL; section.data = q; } RDEBUG("Sending HTTP %s to \"%s\"", fr_int2str(http_method_table, section.method, NULL), uri); /* * Configure various CURL options, and initialise the read/write * context data. * * @todo We could extract the User-Name and password from the URL string. */ ret = rest_request_config(instance, §ion, request, handle, section.method, section.body, uri, NULL, NULL); talloc_free(uri); if (ret < 0) return -1; /* * Send the CURL request, pre-parse headers, aggregate incoming * HTTP body data into a single contiguous buffer. */ ret = rest_request_perform(instance, §ion, request, handle); if (ret < 0) return -1; hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: case 403: case 401: { outlen = -1; error: rest_response_error(request, handle); goto finish; } case 204: goto finish; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { break; } else if (hcode < 500) { outlen = -2; goto error; } else { outlen = -1; goto error; } } len = rest_get_handle_data(&body, handle); if ((size_t) len >= freespace) { REDEBUG("Insufficient space to write HTTP response, needed %zu bytes, have %zu bytes", len + 1, freespace); outlen = -1; goto finish; } if (len > 0) { outlen = len; strlcpy(out, body, len + 1); /* strlcpy takes the size of the buffer */ } finish: rlm_rest_cleanup(instance, §ion, handle); fr_connection_release(inst->conn_pool, handle); return outlen; } /* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rest_t *inst = instance; rlm_rest_section_t *section = &inst->authorize; void *handle; int hcode; int rcode = RLM_MODULE_OK; int ret; if (!section->name) return RLM_MODULE_NOOP; handle = fr_connection_get(inst->conn_pool); if (!handle) return RLM_MODULE_FAIL; ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL); if (ret < 0) { rcode = RLM_MODULE_FAIL; goto finish; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: rcode = RLM_MODULE_NOTFOUND; break; case 403: rcode = RLM_MODULE_USERLOCK; break; case 401: /* * Attempt to parse content if there was any. */ ret = rest_response_decode(inst, section, request, handle); if (ret < 0) { rcode = RLM_MODULE_FAIL; break; } rcode = RLM_MODULE_REJECT; break; case 204: rcode = RLM_MODULE_OK; break; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { ret = rest_response_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; else rcode = RLM_MODULE_UPDATED; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; } else { rcode = RLM_MODULE_FAIL; } } finish: switch (rcode) { case RLM_MODULE_INVALID: case RLM_MODULE_FAIL: case RLM_MODULE_USERLOCK: rest_response_error(request, handle); break; default: break; } rlm_rest_cleanup(instance, section, handle); fr_connection_release(inst->conn_pool, handle); return rcode; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); 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; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); /* inject config value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "config", request); /* inject reply value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "reply", request); finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return rcode; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param request The authorization request. * @return Returns operation status (@p rlm_rcode_t). */ static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ void *handle = NULL; /* connection pool handle */ char dockey[MAX_KEY_SIZE]; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ if (radius_xlat(dockey, sizeof(dockey), request, inst->user_key, NULL, NULL) < 0) { /* log error */ RERROR("could not find user key attribute (%s) in packet", inst->user_key); /* return */ return RLM_MODULE_FAIL; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* 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 */ RERROR("cookie not usable - possibly not allocated"); /* free connection */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* reset cookie error status */ cookie->jerr = json_tokener_success; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || cookie->jobj == NULL) { /* log error */ RERROR("failed to fetch document or parse return"); /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* debugging */ RDEBUG("parsed user document == %s", json_object_to_json_string(cookie->jobj)); /* inject config value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "config", request); /* inject reply value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "reply", request); /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return okay */ return RLM_MODULE_OK; }
/* * Allocate an IP number from the pool. */ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) { rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance; char allocation[MAX_STRING_LEN]; int allocation_len; VALUE_PAIR *vp; rlm_sql_handle_t *handle; time_t now; /* * If there is a Framed-IP-Address attribute in the reply do nothing */ if (fr_pair_find_by_num(request->reply->vps, inst->framed_ip_address, 0, TAG_ANY) != NULL) { RDEBUG("Framed-IP-Address already exists"); return do_logging(request, inst->log_exists, RLM_MODULE_NOOP); } if (fr_pair_find_by_num(request->config, PW_POOL_NAME, 0, TAG_ANY) == NULL) { RDEBUG("No Pool-Name defined"); return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP); } handle = fr_connection_get(inst->sql_inst->pool); if (!handle) { REDEBUG("Failed reserving SQL connection"); return RLM_MODULE_FAIL; } if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Limit the number of clears we do. There are minor * race conditions for the check, but so what. The * actual work is protected by a transaction. The idea * here is that if we're allocating 100 IPs a second, * we're only do 1 CLEAR per second. */ now = time(NULL); if (inst->last_clear < now) { inst->last_clear = now; DO_PART(allocate_begin); DO_PART(allocate_clear); DO_PART(allocate_commit); } DO_PART(allocate_begin); allocation_len = sqlippool_query1(allocation, sizeof(allocation), inst->allocate_find, handle, inst, request, (char *) NULL, 0); /* * Nothing found... */ if (allocation_len == 0) { DO_PART(allocate_commit); /* *Should we perform pool-check ? */ if (inst->pool_check && *inst->pool_check) { /* *Ok, so the allocate-find query found nothing ... *Let's check if the pool exists at all */ allocation_len = sqlippool_query1(allocation, sizeof(allocation), inst->pool_check, handle, inst, request, (char *) NULL, 0); fr_connection_release(inst->sql_inst->pool, handle); if (allocation_len) { /* * Pool exists after all... So, * the failure to allocate the IP * address was most likely due to * the depletion of the pool. In * that case, we should return * NOTFOUND */ RDEBUG("pool appears to be full"); return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND); } /* * Pool doesn't exist in the table. It * may be handled by some other instance of * sqlippool, so we should just ignore this * allocation failure and return NOOP */ RDEBUG("IP address could not be allocated as no pool exists with that name"); return RLM_MODULE_NOOP; } fr_connection_release(inst->sql_inst->pool, handle); RDEBUG("IP address could not be allocated"); return do_logging(request, inst->log_failed, RLM_MODULE_NOOP); } /* * See if we can create the VP from the returned data. If not, * error out. If so, add it to the list. */ vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address, 0); if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) { DO_PART(allocate_commit); RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation); fr_connection_release(inst->sql_inst->pool, handle); return do_logging(request, inst->log_failed, RLM_MODULE_NOOP); } RDEBUG("Allocated IP %s", allocation); fr_pair_add(&request->reply->vps, vp); /* * UPDATE */ sqlippool_command(inst->allocate_update, &handle, inst, request, allocation, allocation_len); DO_PART(allocate_commit); fr_connection_release(inst->sql_inst->pool, handle); return do_logging(request, inst->log_success, RLM_MODULE_OK); }
/* * Authenticate the user with the given password. */ static rlm_rcode_t mod_authenticate(void *instance, UNUSED REQUEST *request) { rlm_rest_t *inst = instance; rlm_rest_section_t *section = &inst->authenticate; void *handle; int hcode; int rcode = RLM_MODULE_OK; int ret; VALUE_PAIR const *username; VALUE_PAIR const *password; username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY); if (!username) { REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request"); return RLM_MODULE_INVALID; } password = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); if (!password) { REDEBUG("Can't perform authentication, 'Cleartext-Password' attribute not found in the control list"); return RLM_MODULE_INVALID; } handle = fr_connection_get(inst->conn_pool); if (!handle) return RLM_MODULE_FAIL; ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue); if (ret < 0) { rcode = RLM_MODULE_FAIL; goto end; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: rcode = RLM_MODULE_NOTFOUND; break; case 403: rcode = RLM_MODULE_USERLOCK; break; case 401: /* * Attempt to parse content if there was any. */ ret = rest_request_decode(inst, section, request, handle); if (ret < 0) { rcode = RLM_MODULE_FAIL; break; } rcode = RLM_MODULE_REJECT; break; case 204: rcode = RLM_MODULE_OK; break; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { ret = rest_request_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; else rcode = RLM_MODULE_UPDATED; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; } else { rcode = RLM_MODULE_FAIL; } } end: rlm_rest_cleanup(instance, section, handle); fr_connection_release(inst->conn_pool, handle); return rcode; }
/** 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; }
static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp, VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) { rlm_sql_handle_t *handle; rlm_sql_t *inst = instance; rlm_sql_grouplist_t *head, *entry; /* * No group queries, don't do group comparisons. */ if (!inst->config->groupmemb_query) { RWARN("Cannot do group comparison when group_membership_query is not set"); return 1; } RDEBUG("sql_groupcmp"); if (check->vp_length == 0){ RDEBUG("sql_groupcmp: Illegal group name"); return 1; } /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) return 1; /* * Get a socket for this lookup */ handle = fr_connection_get(inst->pool); if (!handle) { return 1; } /* * Get the list of groups this user is a member of */ if (sql_get_grouplist(inst, &handle, request, &head) < 0) { REDEBUG("Error getting group membership"); fr_connection_release(inst->pool, handle); return 1; } for (entry = head; entry != NULL; entry = entry->next) { if (strcmp(entry->name, check->vp_strvalue) == 0){ RDEBUG("sql_groupcmp finished: User is a member of group %s", check->vp_strvalue); talloc_free(head); fr_connection_release(inst->pool, handle); return 0; } } /* Free the grouplist */ talloc_free(head); fr_connection_release(inst->pool, handle); RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue); return 1; }
/* * Check NTLM authentication direct to winbind via * Samba's libwbclient library * * Returns: * 0 success * -1 auth failure * -648 password expired */ int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request, uint8_t const *challenge, uint8_t const *response, uint8_t nthashhash[NT_DIGEST_LENGTH]) { int rcode = -1; struct wbcContext *wb_ctx; struct wbcAuthUserParams authparams; wbcErr err; int len; struct wbcAuthUserInfo *info = NULL; struct wbcAuthErrorInfo *error = NULL; char user_name_buf[500]; char domain_name_buf[500]; uint8_t resp[NT_LENGTH]; /* * Clear the auth parameters - this is important, as * there are options that will cause wbcAuthenticateUserEx * to bomb out if not zero. */ memset(&authparams, 0, sizeof(authparams)); /* * wb_username must be set for this function to be called */ rad_assert(inst->wb_username); /* * Get the username and domain from the configuration */ len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf), request, inst->wb_username, NULL, NULL); if (len < 0) { REDEBUG2("Unable to expand winbind_username"); goto done; } if (inst->wb_domain) { len = tmpl_expand(&authparams.domain_name, domain_name_buf, sizeof(domain_name_buf), request, inst->wb_domain, NULL, NULL); if (len < 0) { REDEBUG2("Unable to expand winbind_domain"); goto done; } } else { RWDEBUG2("No domain specified; authentication may fail because of this"); } /* * Build the wbcAuthUserParams structure with what we know */ authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE; authparams.password.response.nt_length = NT_LENGTH; memcpy(resp, response, NT_LENGTH); authparams.password.response.nt_data = resp; memcpy(authparams.password.response.challenge, challenge, sizeof(authparams.password.response.challenge)); /* * Send auth request across to winbind */ wb_ctx = fr_connection_get(inst->wb_pool); if (wb_ctx == NULL) { RERROR("Unable to get winbind connection from pool"); goto done; } RDEBUG2("sending authentication request user='******' domain='%s'", authparams.account_name, authparams.domain_name); err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error); fr_connection_release(inst->wb_pool, wb_ctx); /* * Try and give some useful feedback on what happened. There are only * a few errors that can actually be returned from wbcCtxAuthenticateUserEx. */ switch (err) { case WBC_ERR_SUCCESS: rcode = 0; RDEBUG2("Authenticated successfully"); /* Grab the nthashhash from the result */ memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH); break; case WBC_ERR_WINBIND_NOT_AVAILABLE: RERROR("Unable to contact winbind!"); RDEBUG2("Check that winbind is running and that FreeRADIUS has"); RDEBUG2("permission to connect to the winbind privileged socket."); break; case WBC_ERR_DOMAIN_NOT_FOUND: REDEBUG2("Domain not found"); break; case WBC_ERR_AUTH_ERROR: if (!error) { REDEBUG2("Authentication failed"); break; } /* * The password needs to be changed, so set rcode appropriately. */ if (error->nt_status & NT_STATUS_PASSWORD_EXPIRED || error->nt_status & NT_STATUS_PASSWORD_MUST_CHANGE) { rcode = -648; } /* * Return the NT_STATUS human readable error string, if there is one. */ if (error->display_string) { REDEBUG2("%s [0x%X]", error->display_string, error->nt_status); } else { REDEBUG2("Authentication failed [0x%X]", error->nt_status); } break; default: /* * Only errors left are * WBC_ERR_INVALID_PARAM * WBC_ERR_NO_MEMORY * neither of which are particularly likely. */ if (error && error->display_string) { REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string); } else { REDEBUG2("libwbclient error: wbcErr %d", err); } break; } done: if (info) wbcFreeMemory(info); if (error) wbcFreeMemory(error); return rcode; }
/* * Generic function for failing between a bunch of queries. * * Uses the same principle as rlm_linelog, expanding the 'reference' config * item using xlat to figure out what query it should execute. * * If the reference matches multiple config items, and a query fails or * doesn't update any rows, the next matching config item is used. * */ static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; int sql_ret; int numaffected = 0; CONF_ITEM *item; CONF_PAIR *pair; char const *attr = NULL; char const *value; char path[MAX_STRING_LEN]; char *p = path; char *expanded = NULL; rad_assert(section); if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } /* * If we can't find a matching config item we do * nothing so return RLM_MODULE_NOOP. */ item = cf_reference_item(NULL, section->cs, path); if (!item) { RWDEBUG("No such configuration item %s", path); rcode = RLM_MODULE_NOOP; goto finish; } if (cf_item_is_section(item)){ RWDEBUG("Sections are not supported as references"); rcode = RLM_MODULE_NOOP; goto finish; } pair = cf_item_to_pair(item); attr = cf_pair_attr(pair); RDEBUG2("Using query template '%s'", attr); handle = fr_connection_get(inst->pool); if (!handle) { rcode = RLM_MODULE_FAIL; goto finish; } sql_set_user(inst, request, NULL); while (true) { value = cf_pair_value(pair); if (!value) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; goto finish; } if (radius_axlat(&expanded, request, value, inst->sql_escape_func, handle) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!*expanded) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; talloc_free(expanded); goto finish; } rlm_sql_query_log(inst, request, section, expanded); sql_ret = rlm_sql_query(inst, request, &handle, expanded); TALLOC_FREE(expanded); RDEBUG("SQL query returned: %s", fr_int2str(sql_rcode_table, sql_ret, "<INVALID>")); switch (sql_ret) { /* * Query was a success! Now we just need to check if it did anything. */ case RLM_SQL_OK: break; /* * A general, unrecoverable server fault. */ case RLM_SQL_ERROR: /* * If we get RLM_SQL_RECONNECT it means all connections in the pool * were exhausted, and we couldn't create a new connection, * so we do not need to call fr_connection_release. */ case RLM_SQL_RECONNECT: rcode = RLM_MODULE_FAIL; goto finish; /* * Query was invalid, this is a terminal error, but we still need * to do cleanup, as the connection handle is still valid. */ case RLM_SQL_QUERY_INVALID: rcode = RLM_MODULE_INVALID; goto finish; /* * Driver found an error (like a unique key constraint violation) * that hinted it might be a good idea to try an alternative query. */ case RLM_SQL_ALT_QUERY: goto next; } rad_assert(handle); /* * We need to have updated something for the query to have been * counted as successful. */ numaffected = (inst->module->sql_affected_rows)(handle, inst->config); (inst->module->sql_finish_query)(handle, inst->config); RDEBUG("%i record(s) updated", numaffected); if (numaffected > 0) break; /* A query succeeded, were done! */ next: /* * We assume all entries with the same name form a redundant * set of queries. */ pair = cf_pair_find_next(section->cs, pair, attr); if (!pair) { RDEBUG("No additional queries configured"); rcode = RLM_MODULE_NOOP; goto finish; } RDEBUG("Trying next query..."); } finish: talloc_free(expanded); fr_connection_release(inst->pool, handle); sql_unset_user(inst, request); return rcode; }
/** Executes a SELECT query and maps the result to server attributes * * @param mod_inst #rlm_sql_t instance. * @param proc_inst Instance data for this specific mod_proc call (unused). * @param request The current request. * @param query string to execute. * @param maps Head of the map list. * @return * - #RLM_MODULE_NOOP no rows were returned or columns matched. * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST. * - #RLM_MODULE_FAIL if a fault occurred. */ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request, char const *query, vp_map_t const *maps) { rlm_sql_t *inst = talloc_get_type_abort(mod_inst, rlm_sql_t); rlm_sql_handle_t *handle = NULL; int i, j; rlm_rcode_t rcode = RLM_MODULE_UPDATED; sql_rcode_t ret; vp_map_t const *map; rlm_sql_row_t row; int rows; int field_cnt; char const **fields = NULL, *map_rhs; char map_rhs_buff[128]; #define MAX_SQL_FIELD_INDEX (64) int field_index[MAX_SQL_FIELD_INDEX]; bool found_field = false; /* Did we find any matching fields in the result set ? */ rad_assert(inst->module->sql_fields); /* Should have been caught during validation... */ for (i = 0; i < MAX_SQL_FIELD_INDEX; i++) field_index[i] = -1; /* * Add SQL-User-Name attribute just in case it is needed * We could search the string fmt for SQL-User-Name to see if this is * needed or not */ sql_set_user(inst, request, NULL); handle = fr_connection_get(inst->pool); /* connection pool should produce error */ if (!handle) return 0; rlm_sql_query_log(inst, request, NULL, query); ret = rlm_sql_select_query(inst, request, &handle, query); if (ret != RLM_SQL_OK) { RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, ret, "<INVALID>")); rcode = RLM_MODULE_FAIL; goto finish; } ret = (inst->module->sql_fields)(&fields, handle, inst->config); if (ret != RLM_SQL_OK) { RERROR("Failed retrieving field names: %s", fr_int2str(sql_rcode_table, ret, "<INVALID>")); error: rcode = RLM_MODULE_FAIL; (inst->module->sql_finish_select_query)(handle, inst->config); goto finish; } rad_assert(fields); field_cnt = talloc_array_length(fields); if (RDEBUG_ENABLED3) for (j = 0; j < field_cnt; j++) RDEBUG3("Got field: %s", fields[j]); /* * Iterate over the maps, it's O(N2)ish but probably * faster than building a radix tree each time the * map set is evaluated (map->rhs can be dynamic). */ for (map = maps, i = 0; map && (i < MAX_SQL_FIELD_INDEX); map = map->next, i++) { /* * Expand the RHS to get the name of the SQL field */ if (tmpl_expand(&map_rhs, map_rhs_buff, sizeof(map_rhs_buff), request, map->rhs, NULL, NULL) < 0) { RERROR("Failed getting field name: %s", fr_strerror()); goto error; } for (j = 0; j < field_cnt; j++) { if (strcmp(fields[j], map_rhs) != 0) continue; field_index[i] = j; found_field = true; } } /* * Couldn't resolve any map RHS values to fields * in the result set. */ if (!found_field) { RDEBUG("No fields matching map found in query result"); rcode = RLM_MODULE_NOOP; (inst->module->sql_finish_select_query)(handle, inst->config); goto finish; } /* * We've resolved all the maps to result indexes, now convert * the values at those indexes into VALUE_PAIRs. * * Note: Not all SQL client libraries provide a row count, * so we have to do the count here. */ for (ret = rlm_sql_fetch_row(&row, inst, request, &handle), rows = 0; (ret == RLM_SQL_OK) && row; ret = rlm_sql_fetch_row(&row, inst, request, &handle), rows++) { for (map = maps, j = 0; map && (j < MAX_SQL_FIELD_INDEX); map = map->next, j++) { if (field_index[j] < 0) continue; /* We didn't find the map RHS in the field set */ if (map_to_request(request, map, _sql_map_proc_get_value, row[field_index[j]]) < 0) goto error; } } if (ret == RLM_SQL_ERROR) goto error; if (!rows) { RDEBUG("SQL query returned no results"); rcode = RLM_MODULE_NOOP; } (inst->module->sql_finish_select_query)(handle, inst->config); finish: talloc_free(fields); fr_connection_release(inst->pool, handle); return rcode; }
/** Execute an arbitrary SQL query * * For selects the first value of the first column will be returned, * for inserts, updates and deletes the number of rows affected will be * returned instead. */ static ssize_t sql_xlat(char **out, UNUSED size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_sql_handle_t *handle = NULL; rlm_sql_row_t row; rlm_sql_t const *inst = mod_inst; sql_rcode_t rcode; ssize_t ret = 0; /* * Add SQL-User-Name attribute just in case it is needed * We could search the string fmt for SQL-User-Name to see if this is * needed or not */ sql_set_user(inst, request, NULL); handle = fr_connection_get(inst->pool); /* connection pool should produce error */ if (!handle) return 0; rlm_sql_query_log(inst, request, NULL, fmt); /* * If the query starts with any of the following prefixes, * then return the number of rows affected */ if ((strncasecmp(fmt, "insert", 6) == 0) || (strncasecmp(fmt, "update", 6) == 0) || (strncasecmp(fmt, "delete", 6) == 0)) { int numaffected; rcode = rlm_sql_query(inst, request, &handle, fmt); if (rcode != RLM_SQL_OK) { query_error: RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, rcode, "<INVALID>")); ret = -1; goto finish; } numaffected = (inst->module->sql_affected_rows)(handle, inst->config); if (numaffected < 1) { RDEBUG("SQL query affected no rows"); goto finish; } MEM(*out = talloc_asprintf(request, "%d", numaffected)); ret = talloc_array_length(*out) - 1; (inst->module->sql_finish_query)(handle, inst->config); goto finish; } /* else it's a SELECT statement */ rcode = rlm_sql_select_query(inst, request, &handle, fmt); if (rcode != RLM_SQL_OK) goto query_error; rcode = rlm_sql_fetch_row(&row, inst, request, &handle); if (rcode) { (inst->module->sql_finish_select_query)(handle, inst->config); goto query_error; } if (!row) { RDEBUG("SQL query returned no results"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } if (!row[0]){ RDEBUG("NULL value in first column of result"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } *out = talloc_bstrndup(request, row[0], strlen(row[0])); ret = talloc_array_length(*out) - 1; (inst->module->sql_finish_select_query)(handle, inst->config); finish: fr_connection_release(inst->pool, handle); return ret; }
static int redis_xlat(void *instance, REQUEST *request, char *fmt, char *out, size_t freespace, UNUSED RADIUS_ESCAPE_STRING func) { REDIS_INST *inst = instance; REDISSOCK *dissocket; size_t ret = 0; char *buffer_ptr; char buffer[21]; char querystr[MAX_QUERY_LEN]; if (!radius_xlat(querystr, sizeof(querystr), fmt, request, redis_escape_func)) { radlog(L_ERR, "rlm_redis (%s): xlat failed.", inst->xlat_name); return 0; } dissocket = fr_connection_get(inst->pool); if (!dissocket) { radlog(L_ERR, "rlm_redis (%s): redis_get_socket() failed", inst->xlat_name); return 0; } /* Query failed for some reason, release socket and return */ if (rlm_redis_query(&dissocket, inst, querystr) < 0) { goto release; } switch (dissocket->reply->type) { case REDIS_REPLY_INTEGER: buffer_ptr = buffer; snprintf(buffer_ptr, sizeof(buffer), "%lld", dissocket->reply->integer); ret = strlen(buffer_ptr); break; case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: buffer_ptr = dissocket->reply->str; ret = dissocket->reply->len; break; default: buffer_ptr = NULL; break; } if ((ret >= freespace) || (buffer_ptr == NULL)) { RDEBUG("rlm_redis (%s): Can't write result, insufficient space or unsupported result\n", inst->xlat_name); ret = 0; goto release; } strlcpy(out, buffer_ptr, freespace); release: rlm_redis_finish_query(dissocket); fr_connection_release(inst->pool, dissocket); return ret; }
/** Write accounting data to Couchbase documents * * Handle accounting requests and store the associated data into JSON documents * in couchbase mapping attribute names to JSON element names per the module configuration. * * When an existing document already exists for the same accounting section the new attributes * will be merged with the currently existing data. When conflicts arrise the new attribute * value will replace or be added to the existing value. * * @param instance The module instance. * @param request The accounting request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ VALUE_PAIR *vp; /* radius value pair linked list */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ char document[MAX_VALUE_SIZE]; /* our document body */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ int status = 0; /* account status type */ int docfound = 0; /* document found toggle */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { /* log debug */ RDEBUG("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_integer; /* acknowledge the request but take no action */ if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* 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; /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->acct_key, NULL, NULL); if (slen < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* 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 returned json object"); /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check cookie json object */ } else if (cookie->jobj) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG("no existing document found - creating new json document"); /* create new json object */ cookie->jobj = json_object_new_object(); /* set 'docType' element for new document */ json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype)); /* default startTimestamp and stopTimestamp to null values */ json_object_object_add(cookie->jobj, "startTimestamp", NULL); json_object_object_add(cookie->jobj, "stopTimestamp", NULL); } /* status specific replacements for start/stop time */ switch (status) { case PW_STATUS_START: /* add start time */ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case PW_STATUS_STOP: /* add stop time */ if ((vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp)); } /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; case PW_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* don't doing anything */ rcode = RLM_MODULE_NOOP; /* return */ goto finish; } /* loop through pairs and add to json document */ for (vp = request->packet->vps; vp; vp = vp->next) { /* map attribute to element */ if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) { /* debug */ RDEBUG3("mapped attribute %s => %s", vp->da->name, element); /* add to json object with mapped name */ json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp)); } } /* copy json string to document and check size */ if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) { /* this isn't good */ RERROR("could not write json document - insufficient buffer space"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("setting '%s' => '%s'", dockey, document); /* store document/key in couchbase */ cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire); /* check return */ if (cb_error != LCB_SUCCESS) { RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error); } finish: /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release our connection handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return rcode; }
static int generate_sql_clients(rlm_sql_t *inst) { rlm_sql_handle_t *handle; rlm_sql_row_t row; unsigned int i = 0; RADCLIENT *c; DEBUG("rlm_sql (%s): Processing generate_sql_clients", inst->config->xlat_name); DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s", inst->config->xlat_name, inst->config->client_query); handle = fr_connection_get(inst->pool); if (!handle) { return -1; } if (rlm_sql_select_query(&handle, inst, inst->config->client_query) != RLM_SQL_OK) return -1; while (rlm_sql_fetch_row(&row, &handle, inst) == 0) { char *server = NULL; i++; if (!row) break; /* * The return data for each row MUST be in the following order: * * 0. Row ID (currently unused) * 1. Name (or IP address) * 2. Shortname * 3. Type * 4. Secret * 5. Virtual Server (optional) */ if (!row[0]){ ERROR("rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i); continue; } if (!row[1]){ ERROR("rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]); continue; } if (!row[2]){ ERROR("rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]); continue; } if (!row[4]){ ERROR("rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]); continue; } if (((inst->module->sql_num_fields)(handle, inst->config) > 5) && (row[5] != NULL) && *row[5]) { server = row[5]; } DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list", inst->config->xlat_name, row[1], row[2], server ? server : "global"); /* FIXME: We should really pass a proper ctx */ c = client_afrom_query(NULL, row[1], /* identifier */ row[4], /* secret */ row[2], /* shortname */ row[3], /* type */ server, /* server */ false); /* require message authenticator */ if (!c) { continue; } if (!client_add(NULL, c)) { WARN("Failed to add client, possible duplicate?"); client_free(c); continue; } DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname, inst->config->xlat_name); } (inst->module->sql_finish_select_query)(handle, inst->config); fr_connection_release(inst->pool, handle); return 0; }
/************************************************************************* * * Function: sql_release_socket * * Purpose: Frees a SQL handle back to the connection pool * *************************************************************************/ int sql_release_socket(rlm_sql_t * inst, rlm_sql_handle_t * handle) { fr_connection_release(inst->pool, handle); return 0; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_rest_t *inst = instance; rlm_rest_section_t *section = &inst->authorize; void *handle; int hcode; int rcode = RLM_MODULE_OK; int ret; handle = fr_connection_get(inst->conn_pool); if (!handle) return RLM_MODULE_FAIL; ret = rlm_rest_perform(instance, section, handle, request, NULL, NULL); if (ret < 0) { rcode = RLM_MODULE_FAIL; goto end; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: rcode = RLM_MODULE_NOTFOUND; break; case 403: rcode = RLM_MODULE_USERLOCK; break; case 401: /* * Attempt to parse content if there was any. */ ret = rest_request_decode(inst, section, request, handle); if (ret < 0) { rcode = RLM_MODULE_FAIL; break; } rcode = RLM_MODULE_REJECT; break; case 204: rcode = RLM_MODULE_OK; break; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { ret = rest_request_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; else rcode = RLM_MODULE_UPDATED; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; } else { rcode = RLM_MODULE_FAIL; } } end: rlm_rest_cleanup(instance, section, handle); fr_connection_release(inst->conn_pool, handle); return rcode; }
/************************************************************************* * * Function: sql_release_socket * * Purpose: Frees a SQL sqlsocket back to the connection pool * *************************************************************************/ int sql_release_socket(SQL_INST * inst, SQLSOCK * sqlsocket) { fr_connection_release(inst->pool, sqlsocket); return 0; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param request The authorization request. * @return Returns operation status (@p rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char dockey[MAX_KEY_SIZE]; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ if (radius_xlat(dockey, sizeof(dockey), request, inst->user_key, NULL, NULL) < 0) { /* log error */ RERROR("could not find user key attribute (%s) in packet", inst->user_key); /* 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; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); /* inject config value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "config", request); /* inject reply value pairs defined in this json oblect */ mod_json_object_to_value_pairs(cookie->jobj, "reply", request); free_and_return: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return rcode; }
/* * Authenticate the user with the given password. */ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) { rlm_smsotp_t *inst = instance; VALUE_PAIR *state; int bufsize; int *fdp; rlm_rcode_t rcode = RLM_MODULE_FAIL; char buffer[1000]; char output[1000]; fdp = fr_connection_get(inst->pool); if (!fdp) return RLM_MODULE_FAIL; /* Get greeting */ bufsize = read_all(fdp, buffer, sizeof(buffer)); if (bufsize <= 0) { REDEBUG("Failed reading from socket"); goto done; } /* * Look for the 'state' attribute. */ #define WRITE_ALL(_a,_b,_c) if (write_all(_a,_b,_c) < 0) goto done; state = fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY); if (state) { RDEBUG("Found reply to access challenge"); /* send username */ snprintf(output, sizeof(output), "check otp for %s\n", request->username->vp_strvalue); WRITE_ALL(fdp, output, strlen(output)); (void) read_all(fdp, buffer, sizeof(buffer)); /* send password */ snprintf(output, sizeof(output), "user otp is %s\n", request->password->vp_strvalue); WRITE_ALL(fdp, output, strlen(output)); (void) read_all(fdp, buffer, sizeof(buffer)); /* set uuid */ snprintf(output, sizeof(output), "otp id is %s\n", state->vp_strvalue); WRITE_ALL(fdp, output, strlen(output)); (void) read_all(fdp, buffer, sizeof(buffer)); /* now check the otp */ WRITE_ALL(fdp, "get check result\n", 17); (void) read_all(fdp, buffer, sizeof(buffer)); /* end the sesssion */ WRITE_ALL(fdp, "quit\n", 5); RDEBUG("answer is %s", buffer); if (strcmp(buffer,"OK") == 0) { rcode = RLM_MODULE_OK; } goto done; } RDEBUG("Generating OTP"); /* set username */ snprintf(output, sizeof(output), "generate otp for %s\n", request->username->vp_strvalue); WRITE_ALL(fdp, output, strlen(output)); (void) read_all(fdp, buffer, sizeof(buffer)); /* end the sesssion */ WRITE_ALL(fdp, "quit\n", 5); RDEBUG("Unique ID is %s", buffer); /* check the return string */ if (strcmp(buffer,"FAILED") == 0) { /* smsotp script returns a error */ goto done; } /* * Create the challenge, and add it to the reply. */ pair_make_reply("Reply-Message", inst->challenge, T_OP_EQ); pair_make_reply("State", buffer, T_OP_EQ); /* * Mark the packet as an Access-Challenge packet. * * The server will take care of sending it to the user. */ request->reply->code = PW_CODE_ACCESS_CHALLENGE; DEBUG("rlm_smsotp: Sending Access-Challenge"); rcode = RLM_MODULE_HANDLED; done: fr_connection_release(inst->pool, fdp); return rcode; }
/** 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; }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_NOOP; rlm_sql_t *inst = instance; rlm_sql_handle_t *handle; VALUE_PAIR *check_tmp = NULL; VALUE_PAIR *reply_tmp = NULL; VALUE_PAIR *user_profile = NULL; bool user_found = false; sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT; int rows; char *expanded = NULL; rad_assert(request->packet != NULL); rad_assert(request->reply != NULL); if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query && !inst->config->read_groups && !inst->config->read_profiles) { RWDEBUG("No authorization checks configured, returning noop"); return RLM_MODULE_NOOP; } /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Reserve a socket * * After this point use goto error or goto release to cleanup socket temporary pairlists and * temporary attributes. */ handle = fr_connection_get(inst->pool); if (!handle) { rcode = RLM_MODULE_FAIL; goto error; } /* * Query the check table to find any conditions associated with this user/realm/whatever... */ if (inst->config->authorize_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; if (radius_axlat(&expanded, request, inst->config->authorize_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(request, inst, request, &handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error getting check attributes"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) goto skipreply; /* Don't need to free VPs we don't have */ /* * Only do this if *some* check pairs were returned */ RDEBUG2("User found in radcheck table"); user_found = true; if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) { pairfree(&check_tmp); check_tmp = NULL; goto skipreply; } RDEBUG2("Conditional check items matched, merging assignment check items"); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(2, request, vp, NULL); } REXDENT(); radius_pairmove(request, &request->config_items, check_tmp, true); rcode = RLM_MODULE_OK; check_tmp = NULL; } if (inst->config->authorize_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(request->reply, inst, request, &handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error getting reply attributes"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) goto skipreply; do_fall_through = fall_through(reply_tmp); RDEBUG2("User found in radreply table, merging reply items"); user_found = true; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL); radius_pairmove(request, &request->reply->vps, reply_tmp, true); rcode = RLM_MODULE_OK; reply_tmp = NULL; } /* * Neither group checks or profiles will work without * a group membership query. */ if (!inst->config->groupmemb_query) goto release; skipreply: if ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) { rlm_rcode_t ret; RDEBUG3("... falling-through to group processing"); ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * Repeat the above process with the default profile or User-Profile */ if ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) { rlm_rcode_t ret; /* * Check for a default_profile or for a User-Profile. */ RDEBUG3("... falling-through to profile processing"); user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY); char const *profile = user_profile ? user_profile->vp_strvalue : inst->config->default_profile; if (!profile || !*profile) { goto release; } RDEBUG2("Checking profile %s", profile); if (sql_set_user(inst, request, profile) < 0) { REDEBUG("Error setting profile"); rcode = RLM_MODULE_FAIL; goto error; } ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * At this point the key (user) hasn't be found in the check table, the reply table * or the group mapping table, and there was no matching profile. */ release: if (!user_found) { rcode = RLM_MODULE_NOTFOUND; } fr_connection_release(inst->pool, handle); sql_unset_user(inst, request); return rcode; error: pairfree(&check_tmp); pairfree(&reply_tmp); sql_unset_user(inst, request); fr_connection_release(inst->pool, handle); return rcode; }
/** Write accounting data to Couchbase documents * * Handle accounting requests and store the associated data into JSON documents * in couchbase mapping attribute names to JSON element names per the module configuration. * * When an existing document already exists for the same accounting section the new attributes * will be merged with the currently existing data. When conflicts arrise the new attribute * value will replace or be added to the existing value. * * @param instance The module instance. * @param request The accounting request object. * @return Returns operation status (@p rlm_rcode_t). */ CC_HINT(nonnull) static rlm_rcode_t mod_accounting(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ void *handle = NULL; /* connection pool handle */ VALUE_PAIR *vp; /* radius value pair linked list */ char dockey[MAX_KEY_SIZE]; /* our document key */ char document[MAX_VALUE_SIZE]; /* our document body */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ int status = 0; /* account status type */ int docfound = 0; /* document found toggle */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY)) == NULL) { /* log debug */ RDEBUG("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_integer; /* acknowledge the request but take no action */ if (status == PW_STATUS_ACCOUNTING_ON || status == PW_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* 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 */ RERROR("cookie not usable - possibly not allocated"); /* free connection */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* attempt to build document key */ if (radius_xlat(dockey, sizeof(dockey), request, inst->acct_key, NULL, NULL) < 0) { /* log error */ RERROR("could not find accounting key attribute (%s) in packet", inst->acct_key); /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_NOOP; } /* init cookie error status */ cookie->jerr = json_tokener_success; /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success) { /* log error */ RERROR("failed to execute get request or parse returned json object"); /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } } else { /* check cookie json object */ if (cookie->jobj != NULL) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG("document not found - creating new json document"); /* create new json object */ cookie->jobj = json_object_new_object(); /* set 'docType' element for new document */ json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype)); /* set start and stop times ... ensure we always have these elements */ json_object_object_add(cookie->jobj, "startTimestamp", json_object_new_string("null")); json_object_object_add(cookie->jobj, "stopTimestamp", json_object_new_string("null")); } /* status specific replacements for start/stop time */ switch (status) { case PW_STATUS_START: /* add start time */ if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case PW_STATUS_STOP: /* add stop time */ if ((vp = pairfind(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp)); } /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; case PW_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* we shouldn't get here - free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release our connection handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return without doing anything */ return RLM_MODULE_NOOP; } /* loop through pairs and add to json document */ for (vp = request->packet->vps; vp; vp = vp->next) { /* map attribute to element */ if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) { /* debug */ RDEBUG("mapped attribute %s => %s", vp->da->name, element); /* add to json object with mapped name */ json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp)); } } /* copy json string to document and check size */ if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) { /* this isn't good */ RERROR("could not write json document - insufficient buffer space"); /* free json output */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_FAIL; } /* free json output */ if (cookie->jobj) { json_object_put(cookie->jobj); } /* debugging */ RDEBUG("setting '%s' => '%s'", dockey, document); /* store document/key in couchbase */ cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire); /* check return */ if (cb_error != LCB_SUCCESS) { RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error); } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* return */ return RLM_MODULE_OK; }
/* * Generic function for failing between a bunch of queries. * * Uses the same principle as rlm_linelog, expanding the 'reference' config * item using xlat to figure out what query it should execute. * * If the reference matches multiple config items, and a query fails or * doesn't update any rows, the next matching config item is used. * */ static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; int sql_ret; int numaffected = 0; CONF_ITEM *item; CONF_PAIR *pair; char const *attr = NULL; char const *value; char path[MAX_STRING_LEN]; char *p = path; char *expanded = NULL; rad_assert(section); if (section->reference[0] != '.') { *p++ = '.'; } if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } item = cf_reference_item(NULL, section->cs, path); if (!item) { rcode = RLM_MODULE_FAIL; goto finish; } if (cf_item_is_section(item)){ REDEBUG("Sections are not supported as references"); rcode = RLM_MODULE_FAIL; goto finish; } pair = cf_itemtopair(item); attr = cf_pair_attr(pair); RDEBUG2("Using query template '%s'", attr); handle = fr_connection_get(inst->pool); if (!handle) { rcode = RLM_MODULE_FAIL; goto finish; } sql_set_user(inst, request, NULL); while (true) { value = cf_pair_value(pair); if (!value) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; goto finish; } if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!*expanded) { RDEBUG("Ignoring null query"); rcode = RLM_MODULE_NOOP; talloc_free(expanded); goto finish; } rlm_sql_query_log(inst, request, section, expanded); /* * If rlm_sql_query cannot use the socket it'll try and * reconnect. Reconnecting will automatically release * the current socket, and try to select a new one. * * If we get RLM_SQL_RECONNECT it means all connections in the pool * were exhausted, and we couldn't create a new connection, * so we do not need to call fr_connection_release. */ sql_ret = rlm_sql_query(&handle, inst, expanded); TALLOC_FREE(expanded); if (sql_ret == RLM_SQL_RECONNECT) { rcode = RLM_MODULE_FAIL; goto finish; } rad_assert(handle); /* * Assume all other errors are incidental, and just meant our * operation failed and its not a client or SQL syntax error. * * @fixme We should actually be able to distinguish between key * constraint violations (which we expect) and other errors. */ if (sql_ret == RLM_SQL_OK) { numaffected = (inst->module->sql_affected_rows)(handle, inst->config); if (numaffected > 0) { break; /* A query succeeded, were done! */ } RDEBUG("No records updated"); } (inst->module->sql_finish_query)(handle, inst->config); /* * We assume all entries with the same name form a redundant * set of queries. */ pair = cf_pair_find_next(section->cs, pair, attr); if (!pair) { RDEBUG("No additional queries configured"); rcode = RLM_MODULE_NOOP; goto finish; } RDEBUG("Trying next query..."); } (inst->module->sql_finish_query)(handle, inst->config); finish: talloc_free(expanded); fr_connection_release(inst->pool, handle); sql_unset_user(inst, request); return rcode; }
/* * Check for an Accounting-Stop * If we find one and we have allocated an IP to this nas/port * combination, then deallocate it. */ static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST *request) { int rcode = RLM_MODULE_NOOP; VALUE_PAIR *vp; int acct_status_type; rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance; rlm_sql_handle_t *handle; vp = fr_pair_find_by_num(request->packet->vps, PW_ACCT_STATUS_TYPE, 0, TAG_ANY); if (!vp) { RDEBUG("Could not find account status type in packet"); return RLM_MODULE_NOOP; } acct_status_type = vp->vp_integer; switch (acct_status_type) { case PW_STATUS_START: case PW_STATUS_ALIVE: case PW_STATUS_STOP: case PW_STATUS_ACCOUNTING_ON: case PW_STATUS_ACCOUNTING_OFF: break; /* continue through to the next section */ default: /* We don't care about any other accounting packet */ return RLM_MODULE_NOOP; } handle = fr_connection_get(inst->sql_inst->pool); if (!handle) { RDEBUG("Failed reserving SQL connection"); return RLM_MODULE_FAIL; } if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) return RLM_MODULE_FAIL; switch (acct_status_type) { case PW_STATUS_START: rcode = mod_accounting_start(&handle, inst, request); break; case PW_STATUS_ALIVE: rcode = mod_accounting_alive(&handle, inst, request); break; case PW_STATUS_STOP: rcode = mod_accounting_stop(&handle, inst, request); break; case PW_STATUS_ACCOUNTING_ON: rcode = mod_accounting_on(&handle, inst, request); break; case PW_STATUS_ACCOUNTING_OFF: rcode = mod_accounting_off(&handle, inst, request); break; } fr_connection_release(inst->sql_inst->pool, handle); return rcode; }
static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST * request) { rlm_rcode_t rcode = RLM_MODULE_OK; rlm_sql_handle_t *handle = NULL; rlm_sql_t *inst = instance; rlm_sql_row_t row; int check = 0; uint32_t ipno = 0; char const *call_num = NULL; VALUE_PAIR *vp; int ret; uint32_t nas_addr = 0; uint32_t nas_port = 0; char *expanded = NULL; /* If simul_count_query is not defined, we don't do any checking */ if (!inst->config->simul_count_query) return RLM_MODULE_NOOP; if ((!request->username) || (request->username->vp_length == '\0')) { REDEBUG("Zero Length username not permitted"); return RLM_MODULE_INVALID; } if(sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) { sql_unset_user(inst, request); return RLM_MODULE_FAIL; } /* initialize the sql socket */ handle = fr_connection_get(inst->pool); if (!handle) { talloc_free(expanded); sql_unset_user(inst, request); return RLM_MODULE_FAIL; } if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) { rcode = RLM_MODULE_FAIL; goto finish; } ret = rlm_sql_fetch_row(&row, &handle, inst); if (ret != 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (!row) { rcode = RLM_MODULE_FAIL; goto finish; } request->simul_count = atoi(row[0]); (inst->module->sql_finish_select_query)(handle, inst->config); TALLOC_FREE(expanded); if (request->simul_count < request->simul_max) { rcode = RLM_MODULE_OK; goto finish; } /* * Looks like too many sessions, so let's start verifying * them, unless told to rely on count query only. */ if (!inst->config->simul_verify_query) { rcode = RLM_MODULE_OK; goto finish; } if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) goto finish; /* * Setup some stuff, like for MPP detection. */ request->simul_count = 0; if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { ipno = vp->vp_ipaddr; } if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { call_num = vp->vp_strvalue; } while (rlm_sql_fetch_row(&row, &handle, inst) == 0) { if (!row) break; if (!row[2]){ RDEBUG("Cannot zap stale entry. No username present in entry"); rcode = RLM_MODULE_FAIL; goto finish; } if (!row[1]){ RDEBUG("Cannot zap stale entry. No session id in entry"); rcode = RLM_MODULE_FAIL; goto finish; } if (row[3]) { nas_addr = inet_addr(row[3]); } if (row[4]) { nas_port = atoi(row[4]); } check = rad_check_ts(nas_addr, nas_port, row[2], row[1]); if (check == 0) { /* * Stale record - zap it. */ if (inst->config->deletestalesessions == true) { uint32_t framed_addr = 0; char proto = 0; int sess_time = 0; if (row[5]) framed_addr = inet_addr(row[5]); if (row[7]){ if (strcmp(row[7], "PPP") == 0) proto = 'P'; else if (strcmp(row[7], "SLIP") == 0) proto = 'S'; } if (row[8]) sess_time = atoi(row[8]); session_zap(request, nas_addr, nas_port, row[2], row[1], framed_addr, proto, sess_time); } } else if (check == 1) { /* * User is still logged in. */ ++request->simul_count; /* * Does it look like a MPP attempt? */ if (row[5] && ipno && inet_addr(row[5]) == ipno) { request->simul_mpp = 2; } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) { request->simul_mpp = 2; } } else { /* * Failed to check the terminal server for * duplicate logins: return an error. */ REDEBUG("Failed to check the terminal server for user '%s'.", row[2]); rcode = RLM_MODULE_FAIL; goto finish; } } finish: (inst->module->sql_finish_select_query)(handle, inst->config); fr_connection_release(inst->pool, handle); talloc_free(expanded); sql_unset_user(inst, request); /* * 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; }
/* * Authenticate the user with the given password. */ static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, UNUSED REQUEST *request) { rlm_rest_t *inst = instance; rlm_rest_section_t *section = &inst->authenticate; void *handle; int hcode; int rcode = RLM_MODULE_OK; int ret; VALUE_PAIR const *username; VALUE_PAIR const *password; if (!section->name) return RLM_MODULE_NOOP; username = request->username; if (!request->username) { REDEBUG("Can't perform authentication, 'User-Name' attribute not found in the request"); return RLM_MODULE_INVALID; } password = request->password; if (!password || (password->da->attr != PW_USER_PASSWORD)) { REDEBUG("You set 'Auth-Type = REST' for a request that does not contain a User-Password attribute!"); return RLM_MODULE_INVALID; } handle = fr_connection_get(inst->conn_pool); if (!handle) return RLM_MODULE_FAIL; ret = rlm_rest_perform(instance, section, handle, request, username->vp_strvalue, password->vp_strvalue); if (ret < 0) { rcode = RLM_MODULE_FAIL; goto finish; } hcode = rest_get_handle_code(handle); switch (hcode) { case 404: case 410: rcode = RLM_MODULE_NOTFOUND; break; case 403: rcode = RLM_MODULE_USERLOCK; break; case 401: /* * Attempt to parse content if there was any. */ ret = rest_response_decode(inst, section, request, handle); if (ret < 0) { rcode = RLM_MODULE_FAIL; break; } rcode = RLM_MODULE_REJECT; break; case 204: rcode = RLM_MODULE_OK; break; default: /* * Attempt to parse content if there was any. */ if ((hcode >= 200) && (hcode < 300)) { ret = rest_response_decode(inst, section, request, handle); if (ret < 0) rcode = RLM_MODULE_FAIL; else if (ret == 0) rcode = RLM_MODULE_OK; else rcode = RLM_MODULE_UPDATED; break; } else if (hcode < 500) { rcode = RLM_MODULE_INVALID; } else { rcode = RLM_MODULE_FAIL; } } finish: switch (rcode) { case RLM_MODULE_INVALID: case RLM_MODULE_FAIL: case RLM_MODULE_USERLOCK: rest_response_error(request, handle); break; default: break; } rlm_rest_cleanup(instance, section, handle); fr_connection_release(inst->conn_pool, handle); return rcode; }
/** * Main module procedure. * @param[in] instance Module instance. * @param[in] request Radius request. * @return */ static rlm_rcode_t mod_proc(void *instance, REQUEST *request) { rlm_mongodb_t *inst = instance; rlm_mongodb_conn_t *conn = NULL; rlm_rcode_t code = RLM_MODULE_FAIL; bson_error_t error; conn = fr_connection_get(inst->pool); if (!conn) { goto end; } if (inst->action == RLM_MONGODB_GET) { // TODO: implement me! code = RLM_MODULE_FAIL; } else { mongoc_collection_t *mongo_collection = NULL; char *db = NULL, *collection = NULL, *query = NULL, *sort = NULL, *update = NULL; bson_t *bson_query = NULL, *bson_sort = NULL, *bson_update = NULL; if (tmpl_aexpand(request, &db, request, inst->cfg.db, NULL, NULL) < 0) { ERROR("failed to substitute attributes for db '%s'", inst->cfg.db->name); goto end_set; } if (tmpl_aexpand(request, &collection, request, inst->cfg.collection, NULL, NULL) < 0) { ERROR("failed to substitute attributes for collection '%s'", inst->cfg.collection->name); goto end_set; } ssize_t query_len = tmpl_aexpand(request, &query, request, inst->cfg.search_query, NULL, NULL); if (query_len < 0) { ERROR("failed to substitute attributes for search query '%s'", inst->cfg.search_query->name); goto end_set; } bson_query = bson_new_from_json((uint8_t *) query, query_len, &error); if (!bson_query) { RERROR("JSON->BSON conversion failed for search query '%s': %d.%d %s", query, error.domain, error.code, error.message); goto end_set; } ssize_t sort_len = tmpl_aexpand(request, &sort, request, inst->cfg.sort_query, NULL, NULL); if (query_len < 0) { ERROR("failed to substitute attributes for sort query '%s'", inst->cfg.sort_query->name); goto end_set; } if (sort_len) { bson_sort = bson_new_from_json((uint8_t *) sort, sort_len, &error); if (!bson_sort) { RERROR("JSON->BSON conversion failed for sort query '%s': %d.%d %s", sort, error.domain, error.code, error.message); goto end_set; } } ssize_t update_len = tmpl_aexpand(request, &update, request, inst->cfg.update_query, NULL, NULL); if (query_len < 0) { ERROR("failed to substitute attributes for update query '%s'", inst->cfg.update_query->name); goto end_set; } if (update_len) { bson_update = bson_new_from_json((uint8_t *) update, update_len, &error); if (!bson_update) { RERROR("JSON->BSON conversion failed for update query '%s': %d.%d %s", update, error.domain, error.code, error.message); goto end_set; } } mongo_collection = mongoc_client_get_collection(conn->client, db, collection); if (!mongo_collection) { RERROR("failed to get collection %s/%s", db, collection); goto end_set; } bool ok = mongoc_collection_find_and_modify(mongo_collection, bson_query, bson_sort, bson_update, NULL, inst->cfg.remove, inst->cfg.upsert, false, NULL, &error); code = ok ? RLM_MODULE_OK : RLM_MODULE_FAIL; end_set: if (mongo_collection) mongoc_collection_destroy(mongo_collection); if (bson_query) bson_destroy(bson_query); if (bson_sort) bson_destroy(bson_sort); if (bson_update) bson_destroy(bson_update); } end: if (conn) fr_connection_release(inst->pool, conn); return code; }