static ssize_t cache_xlat(TALLOC_CTX *ctx, char **out, UNUSED size_t freespace, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_cache_entry_t *c = NULL; rlm_cache_t const *inst = mod_inst; rlm_cache_handle_t *handle = NULL; ssize_t slen; ssize_t ret = 0; uint8_t buffer[1024]; uint8_t const *key; ssize_t key_len; vp_tmpl_t *target = NULL; vp_map_t *map = NULL; key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), request, inst->config.key, NULL, NULL); if (key_len < 0) return -1; slen = tmpl_afrom_attr_substr(ctx, NULL, &target, fmt, &(vp_tmpl_rules_t){ .dict_def = request->dict, .prefix = VP_ATTR_REF_PREFIX_AUTO });
/** Expand values in an attribute map where needed * * @param[out] expanded array of attributes. Need not be initialised (we'll initialise). * @param[in] request The current request. * @param[in] maps to expand. * @return * - 0 on success. * - -1 on failure. */ int rlm_ldap_map_expand(rlm_ldap_map_exp_t *expanded, REQUEST *request, vp_map_t const *maps) { vp_map_t const *map; unsigned int total = 0; TALLOC_CTX *ctx = NULL; char const *attr; char attr_buff[1024 + 1]; /* X.501 says we need to support at least 1024 chars for attr names */ for (map = maps; map != NULL; map = map->next) { if (tmpl_expand(&attr, attr_buff, sizeof(attr_buff), request, map->rhs, NULL, NULL) < 0) { RDEBUG("Expansion of LDAP attribute \"%s\" failed", map->rhs->name); TALLOC_FREE(ctx); return -1; } /* * Dynamic value */ if (attr == attr_buff) { if (!ctx) ctx = talloc_new(NULL); expanded->attrs[total++] = talloc_strdup(ctx, attr_buff); continue; } expanded->attrs[total++] = attr; } expanded->attrs[total] = NULL; expanded->ctx = ctx; /* Freeing this frees any dynamic values */ expanded->count = total; expanded->maps = maps; return 0; }
/** 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 thread specific data. * @param request The accounting request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *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_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) == NULL) { /* log debug */ RDEBUG2("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_uint32; /* acknowledge the request but take no action */ if (status == FR_STATUS_ACCOUNTING_ON || status == FR_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG2("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* 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 */ RDEBUG2("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 FR_STATUS_START: /* add start time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case FR_STATUS_STOP: /* add stop time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_event_timestamp, 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 FR_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_pool_connection_release(inst->pool, request, 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 thread specific data. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *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_pool_connection_get(inst->pool, request); /* 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)); { TALLOC_CTX *pool = talloc_pool(request, 1024); /* We need to do lots of allocs */ fr_cursor_t maps, vlms; vp_map_t *map_head = NULL, *map; vp_list_mod_t *vlm_head = NULL, *vlm; fr_cursor_init(&maps, &map_head); /* * Convert JSON data into maps */ if ((mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_CONTROL) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REPLY) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REQUEST) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_STATE) < 0)) { invalid: talloc_free(pool); rcode = RLM_MODULE_INVALID; goto finish; } fr_cursor_init(&vlms, &vlm_head); /* * Convert all the maps into list modifications, * which are guaranteed to succeed. */ for (map = fr_cursor_head(&maps); map; map = fr_cursor_next(&maps)) { if (map_to_list_mod(pool, &vlm, request, map, NULL, NULL) < 0) goto invalid; fr_cursor_insert(&vlms, vlm); } if (!vlm_head) { RDEBUG2("Nothing to update"); talloc_free(pool); rcode = RLM_MODULE_NOOP; goto finish; } /* * Apply the list of modifications */ for (vlm = fr_cursor_head(&vlms); vlm; vlm = fr_cursor_next(&vlms)) { int ret; ret = map_list_mod_apply(request, vlm); /* SHOULD NOT FAIL */ if (!fr_cond_assert(ret == 0)) { talloc_free(pool); rcode = RLM_MODULE_FAIL; goto finish; } } talloc_free(pool); } finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) fr_pool_connection_release(inst->pool, request, handle); /* return */ return rcode; }
static rlm_rcode_t mod_cache_it(void *instance, UNUSED void *thread, REQUEST *request) { rlm_cache_entry_t *c = NULL; rlm_cache_t const *inst = instance; rlm_cache_handle_t *handle; fr_cursor_t cursor; VALUE_PAIR *vp; bool merge = true, insert = true, expire = false, set_ttl = false; int exists = -1; uint8_t buffer[1024]; uint8_t const *key; ssize_t key_len; rlm_rcode_t rcode = RLM_MODULE_NOOP; int ttl = inst->config.ttl; key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), request, inst->config.key, NULL, NULL); if (key_len < 0) return RLM_MODULE_FAIL; if (key_len == 0) { REDEBUG("Zero length key string is invalid"); return RLM_MODULE_INVALID; } /* * If Cache-Status-Only == yes, only return whether we found a * valid cache entry */ vp = fr_pair_find_by_da(request->control, attr_cache_status_only, TAG_ANY); if (vp && vp->vp_bool) { RINDENT(); RDEBUG3("status-only: yes"); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; rcode = cache_find(&c, inst, request, &handle, key, key_len); if (rcode == RLM_MODULE_FAIL) goto finish; rad_assert(!inst->driver->acquire || handle); rcode = c ? RLM_MODULE_OK: RLM_MODULE_NOTFOUND; goto finish; } /* * Figure out what operation we're doing */ vp = fr_pair_find_by_da(request->control, attr_cache_allow_merge, TAG_ANY); if (vp) merge = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_allow_insert, TAG_ANY); if (vp) insert = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_ttl, TAG_ANY); if (vp) { if (vp->vp_int32 == 0) { expire = true; } else if (vp->vp_int32 < 0) { expire = true; ttl = -(vp->vp_int32); /* Updating the TTL */ } else { set_ttl = true; ttl = vp->vp_int32; } } RINDENT(); RDEBUG3("merge : %s", merge ? "yes" : "no"); RDEBUG3("insert : %s", insert ? "yes" : "no"); RDEBUG3("expire : %s", expire ? "yes" : "no"); RDEBUG3("ttl : %i", ttl); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; /* * Retrieve the cache entry and merge it with the current request * recording whether the entry existed. */ if (merge) { rcode = cache_find(&c, inst, request, &handle, key, key_len); switch (rcode) { case RLM_MODULE_FAIL: goto finish; case RLM_MODULE_OK: rcode = cache_merge(inst, request, c); exists = 1; break; case RLM_MODULE_NOTFOUND: rcode = RLM_MODULE_NOTFOUND; exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * Expire the entry if told to, and we either don't know whether * it exists, or we know it does. * * We only expire if we're not inserting, as driver insert methods * should perform upserts. */ if (expire && ((exists == -1) || (exists == 1))) { if (!insert) { rad_assert(!set_ttl); switch (cache_expire(inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_NOTFOUND; break; default: rad_assert(0); break; } /* If it previously existed, it doesn't now */ } /* Otherwise use insert to overwrite */ exists = 0; } /* * If we still don't know whether it exists or not * and we need to do an insert or set_ttl operation * determine that now. */ if ((exists < 0) && (insert || set_ttl)) { switch (cache_find(&c, inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: exists = 1; if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * We can only alter the TTL on an entry if it exists. */ if (set_ttl && (exists == 1)) { rad_assert(c); c->expires = request->packet->timestamp.tv_sec + ttl; switch (cache_set_ttl(inst, request, &handle, c)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; goto finish; default: rad_assert(0); } } /* * Inserts are upserts, so we don't care about the * entry state, just that we're not meant to be * setting the TTL, which precludes performing an * insert. */ if (insert && (exists == 0)) { switch (cache_insert(inst, request, &handle, key, key_len, ttl)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); goto finish; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); /* * Clear control attributes */ for (vp = fr_cursor_init(&cursor, &request->control); vp; vp = fr_cursor_next(&cursor)) { again: if (!fr_dict_attr_is_top_level(vp->da)) continue; switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_ALLOW_MERGE: case FR_CACHE_ALLOW_INSERT: case FR_CACHE_MERGE_NEW: RDEBUG2("Removing &control:%s", vp->da->name); vp = fr_cursor_remove(&cursor); talloc_free(vp); vp = fr_cursor_current(&cursor); if (!vp) break; goto again; } } return rcode; }
static rlm_rcode_t mod_do_linelog(void *instance, REQUEST *request) { int fd = -1; linelog_conn_t *conn; struct timeval *timeout = NULL; char buff[4096]; char *p = buff; linelog_instance_t *inst = instance; char const *value; vp_tmpl_t empty, *vpt = NULL, *vpt_p = NULL; rlm_rcode_t rcode = RLM_MODULE_OK; ssize_t slen; struct iovec vector_s[2]; struct iovec *vector = NULL, *vector_p; size_t vector_len; bool with_delim; buff[0] = '.'; /* force to be in current section */ buff[1] = '\0'; buff[2] = '\0'; /* * Expand log_ref to a config path, using the module * configuration section as the root. */ if (inst->log_ref) { CONF_ITEM *ci; CONF_PAIR *cp; char const *tmpl_str; if (tmpl_expand(NULL, buff + 1, sizeof(buff) - 1, request, inst->log_ref, linelog_escape_func, NULL) < 0) { return RLM_MODULE_FAIL; } if (buff[1] == '.') p++; /* * Don't go back up. */ if (buff[2] == '.') { REDEBUG("Invalid path \"%s\"", p); return RLM_MODULE_FAIL; } ci = cf_reference_item(NULL, inst->cs, p); if (!ci) { RDEBUG2("Path \"%s\" doesn't exist", p); goto default_msg; } if (!cf_item_is_pair(ci)) { REDEBUG("Path \"%s\" resolves to a section (should be a pair)", p); return RLM_MODULE_FAIL; } cp = cf_item_to_pair(ci); tmpl_str = cf_pair_value(cp); if (!tmpl_str || (tmpl_str[0] == '\0')) { RDEBUG2("Path \"%s\" resolves to an empty config pair", p); vpt_p = tmpl_init(&empty, TMPL_TYPE_LITERAL, "", 0); goto build_vector; } /* * Alloc a template from the value of the CONF_PAIR * using request as the context (which will hopefully avoid a malloc). */ slen = tmpl_afrom_str(request, &vpt, tmpl_str, talloc_array_length(tmpl_str) - 1, cf_pair_value_type(cp), REQUEST_CURRENT, PAIR_LIST_REQUEST, true); if (slen <= 0) { REMARKER(tmpl_str, -slen, fr_strerror()); return RLM_MODULE_FAIL; } vpt_p = vpt; } else { default_msg: /* * Use the default format string */ if (!inst->log_src) { RDEBUG2("No default message configured"); return RLM_MODULE_NOOP; } /* * Use the pre-parsed format template */ RDEBUG2("Using default message"); vpt_p = inst->log_src; } build_vector: with_delim = (inst->log_dst != LINELOG_DST_SYSLOG) && (inst->delimiter_len > 0); /* * Log all the things! */ switch (vpt_p->type) { case TMPL_TYPE_ATTR: case TMPL_TYPE_LIST: { #define VECTOR_INCREMENT 20 vp_cursor_t cursor; VALUE_PAIR *vp; int alloced = VECTOR_INCREMENT, i; MEM(vector = talloc_array(request, struct iovec, alloced)); for (vp = tmpl_cursor_init(NULL, &cursor, request, vpt_p), i = 0; vp; vp = tmpl_cursor_next(&cursor, vpt_p), i++) { /* need extra for line terminator */ if ((with_delim && ((i + 1) >= alloced)) || (i >= alloced)) { alloced += VECTOR_INCREMENT; MEM(vector = talloc_realloc(request, vector, struct iovec, alloced)); } switch (vp->da->type) { case PW_TYPE_OCTETS: case PW_TYPE_STRING: vector[i].iov_base = vp->data.ptr; vector[i].iov_len = vp->vp_length; break; default: p = vp_aprints_value(vector, vp, '\0'); vector[i].iov_base = p; vector[i].iov_len = talloc_array_length(p) - 1; break; } /* * Add the line delimiter string */ if (with_delim) { i++; memcpy(&vector[i].iov_base, &(inst->delimiter), sizeof(vector[i].iov_base)); vector[i].iov_len = inst->delimiter_len; } } vector_p = vector; vector_len = i; } break; /* * Log a single thing. */ default: slen = tmpl_expand(&value, buff, sizeof(buff), request, vpt_p, linelog_escape_func, NULL); if (slen < 0) { rcode = RLM_MODULE_FAIL; goto finish; } /* iov_base is not declared as const *sigh* */ memcpy(&vector_s[0].iov_base, &value, sizeof(vector_s[0].iov_base)); vector_s[0].iov_len = slen; if (!with_delim) { vector_len = 1; } else { memcpy(&vector_s[1].iov_base, &(inst->delimiter), sizeof(vector_s[1].iov_base)); vector_s[1].iov_len = inst->delimiter_len; vector_len = 2; } vector_p = &vector_s[0]; }
/** Allocate a new IP address from a pool * */ static ippool_rcode_t redis_ippool_allocate(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; rad_assert(key_prefix); rad_assert(device_id); gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!gateway_id) gateway_id = (uint8_t const *)""; status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_alloc_digest, lua_alloc_cmd, "EVALSHA %s 1 %b %u %u %b %b", lua_alloc_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, device_id, device_id_len, gateway_id, gateway_id_len); if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } rad_assert(reply); if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process IP address */ if (reply->elements > 1) { vp_tmpl_t ip_rhs = { .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; switch (reply->element[1]->type) { /* * Destination attribute may not be IPv4, in which case * we want to pre-convert the integer value to an IPv4 * address before casting it once more to the type of * the destination attribute. */ case REDIS_REPLY_INTEGER: { if (ip_map.lhs->tmpl_da->type != FR_TYPE_IPV4_ADDR) { fr_value_box_t tmp; memset(&tmp, 0, sizeof(tmp)); tmp.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); tmp.type = FR_TYPE_UINT32; if (fr_value_box_cast(NULL, &ip_map.rhs->tmpl_value, FR_TYPE_IPV4_ADDR, NULL, &tmp)) { RPEDEBUG("Failed converting integer to IPv4 address"); ret = IPPOOL_RCODE_FAIL; goto finish; } } else { ip_map.rhs->tmpl_value.vb_uint32 = ntohl((uint32_t)reply->element[1]->integer); ip_map.rhs->tmpl_value_type = FR_TYPE_UINT32; } } goto do_ip_map; case REDIS_REPLY_STRING: ip_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; ip_map.rhs->tmpl_value_length = reply->element[1]->len; ip_map.rhs->tmpl_value_type = FR_TYPE_STRING; do_ip_map: if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; default: REDEBUG("Server returned unexpected type \"%s\" for IP element (result[1])", fr_int2str(redis_reply_types, reply->element[1]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Range identifier */ if (reply->elements > 2) { switch (reply->element[2]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: { vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; range_map.rhs->tmpl_value.vb_strvalue = reply->element[2]->str; range_map.rhs->tmpl_value_length = reply->element[2]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[2])", fr_int2str(redis_reply_types, reply->element[2]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Process Expiry time */ if (inst->expiry_attr && (reply->elements > 3)) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; if (reply->element[3]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for expiry element (result[3])", fr_int2str(redis_reply_types, reply->element[3]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } expiry_map.rhs->tmpl_value.vb_uint32 = reply->element[3]->integer; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Update an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_update(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; vp_tmpl_t range_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t range_map = { .lhs = inst->range_attr, .op = T_OP_SET, .rhs = &range_rhs }; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if (!gateway_id) gateway_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %u %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, htonl(ip->addr.v4.s_addr), device_id, device_id_len, gateway_id, gateway_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_update_digest, lua_update_cmd, "EVALSHA %s 1 %b %u %u %s %b %b", lua_update_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, expires, ip_buff, device_id, device_id_len, gateway_id, gateway_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; /* * Process Range identifier */ if (reply->elements > 1) { switch (reply->element[1]->type) { /* * Add range ID to request */ case REDIS_REPLY_STRING: range_map.rhs->tmpl_value.vb_strvalue = reply->element[1]->str; range_map.rhs->tmpl_value_length = reply->element[1]->len; range_map.rhs->tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &range_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } break; case REDIS_REPLY_NIL: break; default: REDEBUG("Server returned unexpected type \"%s\" for range element (result[1])", fr_int2str(redis_reply_types, reply->element[0]->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } } /* * Copy expiry time to expires attribute (if set) */ if (inst->expiry_attr) { vp_tmpl_t expiry_rhs = { .name = "", .type = TMPL_TYPE_DATA, .tmpl_value_type = FR_TYPE_STRING, .quote = T_DOUBLE_QUOTED_STRING }; vp_map_t expiry_map = { .lhs = inst->expiry_attr, .op = T_OP_SET, .rhs = &expiry_rhs }; expiry_map.rhs->tmpl_value.vb_uint32 = expires; expiry_map.rhs->tmpl_value_type = FR_TYPE_UINT32; if (map_to_request(request, &expiry_map, map_to_vp, NULL) < 0) { ret = IPPOOL_RCODE_FAIL; goto finish; } } finish: fr_redis_reply_free(&reply); return ret; } /** Release an existing IP address in a pool * */ static ippool_rcode_t redis_ippool_release(rlm_redis_ippool_t const *inst, REQUEST *request, uint8_t const *key_prefix, size_t key_prefix_len, fr_ipaddr_t *ip, uint8_t const *device_id, size_t device_id_len) { struct timeval now; redisReply *reply = NULL; fr_redis_rcode_t status; ippool_rcode_t ret = IPPOOL_RCODE_SUCCESS; gettimeofday(&now, NULL); /* * hiredis doesn't deal well with NULL string pointers */ if (!device_id) device_id = (uint8_t const *)""; if ((ip->af == AF_INET) && inst->ipv4_integer) { status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %u %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, htonl(ip->addr.v4.s_addr), device_id, device_id_len); } else { char ip_buff[FR_IPADDR_PREFIX_STRLEN]; IPPOOL_SPRINT_IP(ip_buff, ip, ip->prefix); status = ippool_script(&reply, request, inst->cluster, key_prefix, key_prefix_len, inst->wait_num, FR_TIMEVAL_TO_MS(&inst->wait_timeout), lua_release_digest, lua_release_cmd, "EVALSHA %s 1 %b %u %s %b", lua_release_digest, key_prefix, key_prefix_len, (unsigned int)now.tv_sec, ip_buff, device_id, device_id_len); } if (status != REDIS_RCODE_SUCCESS) { ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Expected result to be array got \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } if (reply->elements == 0) { REDEBUG("Got empty result array"); ret = IPPOOL_RCODE_FAIL; goto finish; } /* * Process return code */ if (reply->element[0]->type != REDIS_REPLY_INTEGER) { REDEBUG("Server returned unexpected type \"%s\" for rcode element (result[0])", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = IPPOOL_RCODE_FAIL; goto finish; } ret = reply->element[0]->integer; if (ret < 0) goto finish; finish: fr_redis_reply_free(&reply); return ret; } /** Find the pool name we'll be allocating from * * @param[out] out Where to write the pool name. * @param[out] buff Where to write the pool name (in the case of an expansion). * @param[in] bufflen Size of the output buffer. * @param[in] inst This instance of the rlm_redis_ippool module. * @param[in] request The current request. * @return * - < 0 on error. * - 0 if no pool attribute exists, or the pool name is a zero length string. * - > 0 on success (length of data written to out). */ static inline ssize_t ippool_pool_name(uint8_t const **out, uint8_t buff[], size_t bufflen, rlm_redis_ippool_t const *inst, REQUEST *request) { ssize_t slen; slen = tmpl_expand(out, (char *)buff, bufflen, request, inst->pool_name, NULL, NULL); if (slen < 0) { if (inst->pool_name->type == TMPL_TYPE_ATTR) { RDEBUG2("Pool attribute not present in request. Doing nothing"); return 0; } REDEBUG("Failed expanding pool name"); return -1; } if (slen == 0) { RDEBUG2("Empty pool name. Doing nothing"); return 0; } if ((*out == buff) && is_truncated((size_t)slen, bufflen)) { REDEBUG("Pool name too long. Expected %zu bytes, got %zu bytes", bufflen, (size_t)slen); return -1; } return slen; } static rlm_rcode_t mod_action(rlm_redis_ippool_t const *inst, REQUEST *request, ippool_action_t action) { uint8_t key_prefix_buff[IPPOOL_MAX_KEY_PREFIX_SIZE], device_id_buff[256], gateway_id_buff[256]; uint8_t const *key_prefix, *device_id = NULL, *gateway_id = NULL; size_t key_prefix_len, device_id_len = 0, gateway_id_len = 0; ssize_t slen; fr_ipaddr_t ip; char expires_buff[20]; char const *expires_str; unsigned long expires = 0; char *q; slen = ippool_pool_name(&key_prefix, (uint8_t *)&key_prefix_buff, sizeof(key_prefix_len), inst, request); if (slen < 0) return RLM_MODULE_FAIL; if (slen == 0) return RLM_MODULE_NOOP; key_prefix_len = (size_t)slen; if (inst->device_id) { slen = tmpl_expand((char const **)&device_id, (char *)&device_id_buff, sizeof(device_id_buff), request, inst->device_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding device (%s)", inst->device_id->name); return RLM_MODULE_FAIL; } device_id_len = (size_t)slen; } if (inst->gateway_id) { slen = tmpl_expand((char const **)&gateway_id, (char *)&gateway_id_buff, sizeof(gateway_id_buff), request, inst->gateway_id, NULL, NULL); if (slen < 0) { REDEBUG("Failed expanding gateway (%s)", inst->gateway_id->name); return RLM_MODULE_FAIL; } gateway_id_len = (size_t)slen; } switch (action) { case POOL_ACTION_ALLOCATE: if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->offer_time, NULL, NULL) < 0) { REDEBUG("Failed expanding offer_time (%s)", inst->offer_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid offer_time. Must be an integer value"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, NULL, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_allocate(inst, request, key_prefix, key_prefix_len, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address lease allocated"); return RLM_MODULE_UPDATED; case IPPOOL_RCODE_POOL_EMPTY: RWDEBUG("Pool contains no free addresses"); return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } case POOL_ACTION_UPDATE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&expires_str, expires_buff, sizeof(expires_buff), request, inst->lease_time, NULL, NULL) < 0) { REDEBUG("Failed expanding lease_time (%s)", inst->lease_time->name); return RLM_MODULE_FAIL; } expires = strtoul(expires_str, &q, 10); if (q != (expires_str + strlen(expires_str))) { REDEBUG("Invalid expires. Must be an integer value"); return RLM_MODULE_FAIL; } if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, expires); switch (redis_ippool_update(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len, gateway_id, gateway_id_len, (uint32_t)expires)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("Requested IP address' \"%s\" lease updated", ip_str); /* * Copy over the input IP address to the reply attribute */ if (inst->copy_on_update) { vp_tmpl_t ip_rhs = { .name = "", .type = TMPL_TYPE_DATA, .quote = T_BARE_WORD, }; vp_map_t ip_map = { .lhs = inst->allocated_address_attr, .op = T_OP_SET, .rhs = &ip_rhs }; ip_rhs.tmpl_value_length = strlen(ip_str); ip_rhs.tmpl_value.vb_strvalue = ip_str; ip_rhs.tmpl_value_type = FR_TYPE_STRING; if (map_to_request(request, &ip_map, map_to_vp, NULL) < 0) return RLM_MODULE_FAIL; } return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_EXPIRED: REDEBUG("Requested IP address' \"%s\" lease already expired at time of renewal", ip_str); return RLM_MODULE_INVALID; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_RELEASE: { char ip_buff[INET6_ADDRSTRLEN + 4]; char const *ip_str; if (tmpl_expand(&ip_str, ip_buff, sizeof(ip_buff), request, inst->requested_address, NULL, NULL) < 0) { REDEBUG("Failed expanding requested_address (%s)", inst->requested_address->name); return RLM_MODULE_FAIL; } if (fr_inet_pton(&ip, ip_str, -1, AF_UNSPEC, false, true) < 0) { RPEDEBUG("Failed parsing address"); return RLM_MODULE_FAIL; } ippool_action_print(request, action, L_DBG_LVL_2, key_prefix, key_prefix_len, ip_str, device_id, device_id_len, gateway_id, gateway_id_len, 0); switch (redis_ippool_release(inst, request, key_prefix, key_prefix_len, &ip, device_id, device_id_len)) { case IPPOOL_RCODE_SUCCESS: RDEBUG2("IP address \"%s\" released", ip_str); return RLM_MODULE_UPDATED; /* * It's useful to be able to identify the 'not found' case * as we can relay to a server where the IP address might * be found. This extremely useful for migrations. */ case IPPOOL_RCODE_NOT_FOUND: REDEBUG("Requested IP address \"%s\" is not a member of the specified pool", ip_str); return RLM_MODULE_NOTFOUND; case IPPOOL_RCODE_DEVICE_MISMATCH: REDEBUG("Requested IP address' \"%s\" lease allocated to another device", ip_str); return RLM_MODULE_INVALID; default: return RLM_MODULE_FAIL; } } case POOL_ACTION_BULK_RELEASE: RDEBUG2("Bulk release not yet implemented"); return RLM_MODULE_NOOP; default: rad_assert(0); return RLM_MODULE_FAIL; } } static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Pool-Action override */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) return mod_action(inst, request, vp->vp_uint32); /* * Otherwise, guess the action by Acct-Status-Type */ vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY); if (!vp) { RDEBUG2("Couldn't find &request:Acct-Status-Type or &control:Pool-Action, doing nothing..."); return RLM_MODULE_NOOP; } switch (vp->vp_uint32) { case FR_STATUS_START: case FR_STATUS_ALIVE: return mod_action(inst, request, POOL_ACTION_UPDATE); case FR_STATUS_STOP: return mod_action(inst, request, POOL_ACTION_RELEASE); case FR_STATUS_ACCOUNTING_OFF: case FR_STATUS_ACCOUNTING_ON: return mod_action(inst, request, POOL_ACTION_BULK_RELEASE); default: return RLM_MODULE_NOOP; } } static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); return mod_action(inst, request, vp ? vp->vp_uint32 : POOL_ACTION_ALLOCATE); } static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) CC_HINT(nonnull); static rlm_rcode_t mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) { rlm_redis_ippool_t const *inst = instance; VALUE_PAIR *vp; ippool_action_t action = POOL_ACTION_ALLOCATE; /* * Unless it's overridden the default action is to allocate * when called in Post-Auth. */ vp = fr_pair_find_by_da(request->control, attr_pool_action, TAG_ANY); if (vp) { if ((vp->vp_uint32 > 0) && (vp->vp_uint32 <= POOL_ACTION_BULK_RELEASE)) { action = vp->vp_uint32; } else { RWDEBUG("Ignoring invalid action %d", vp->vp_uint32); return RLM_MODULE_NOOP; } #ifdef WITH_DHCP } else if (request->dict == dict_dhcpv4) { vp = fr_pair_find_by_da(request->control, attr_message_type, TAG_ANY); if (!vp) goto run; if (vp->vp_uint8 == FR_DHCP_REQUEST) action = POOL_ACTION_UPDATE; #endif } run: return mod_action(inst, request, action); } static int mod_instantiate(void *instance, CONF_SECTION *conf) { static bool done_hash = false; CONF_SECTION *subcs = cf_section_find(conf, "redis", NULL); rlm_redis_ippool_t *inst = instance; rad_assert(inst->allocated_address_attr->type == TMPL_TYPE_ATTR); rad_assert(subcs); inst->cluster = fr_redis_cluster_alloc(inst, subcs, &inst->conf, true, NULL, NULL, NULL); if (!inst->cluster) return -1; if (!fr_redis_cluster_min_version(inst->cluster, "3.0.2")) { PERROR("Cluster error"); return -1; } /* * Pre-Compute the SHA1 hashes of the Lua scripts */ if (!done_hash) { fr_sha1_ctx sha1_ctx; uint8_t digest[SHA1_DIGEST_LENGTH]; fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_alloc_cmd, sizeof(lua_alloc_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_alloc_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_update_cmd, sizeof(lua_update_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_update_digest, digest, sizeof(digest)); fr_sha1_init(&sha1_ctx); fr_sha1_update(&sha1_ctx, (uint8_t const *)lua_release_cmd, sizeof(lua_release_cmd) - 1); fr_sha1_final(digest, &sha1_ctx); fr_bin2hex(lua_release_digest, digest, sizeof(digest)); } /* * If we don't have a separate time specifically for offers * just use the lease time. */ if (!inst->offer_time) inst->offer_time = inst->lease_time; return 0; } static int mod_load(void) { fr_redis_version_print(); return 0; } extern module_t rlm_redis_ippool; module_t rlm_redis_ippool = { .magic = RLM_MODULE_INIT, .name = "redis", .type = RLM_TYPE_THREAD_SAFE, .inst_size = sizeof(rlm_redis_ippool_t), .config = module_config, .onload = mod_load, .instantiate = mod_instantiate, .methods = { [MOD_ACCOUNTING] = mod_accounting, [MOD_AUTHORIZE] = mod_authorize, [MOD_POST_AUTH] = mod_post_auth, }, };
/** Query the LDAP directory to check if a group object includes a user object as a member * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] check vp containing the group value (name or dn). * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_check_groupobj_dynamic(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, VALUE_PAIR *check) { ldap_rcode_t status; char const *base_dn; char base_dn_buff[LDAP_MAX_DN_STR_LEN + 1]; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; int ret; rad_assert(inst->groupobj_base_dn); switch (check->op) { case T_OP_CMP_EQ: case T_OP_CMP_FALSE: case T_OP_CMP_TRUE: case T_OP_REG_EQ: case T_OP_REG_NE: break; default: REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons", fr_int2str(fr_tokens, check->op, "<INVALID>")); return 1; } RDEBUG2("Checking for user in group objects"); if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) { char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; base_dn = check->vp_strvalue; } else { char name_filter[LDAP_MAX_FILTER_STR_LEN]; char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter }; if (!inst->groupobj_name_attr) { REDEBUG("Told to search for group by name, but missing 'group.name_attribute' " "directive"); return RLM_MODULE_INVALID; } snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue); RINDENT(); ret = rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)); REXDENT(); if (ret < 0) return RLM_MODULE_INVALID; /* * rlm_ldap_find_user does this, too. Oh well. */ RINDENT(); ret = tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL); REXDENT(); if (ret < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } } RINDENT(); status = rlm_ldap_search(NULL, inst, request, pconn, base_dn, inst->groupobj_scope, filter, NULL, NULL, NULL); REXDENT(); switch (status) { case LDAP_PROC_SUCCESS: RDEBUG("User found in group object \"%s\"", base_dn); break; case LDAP_PROC_NO_RESULT: return RLM_MODULE_NOTFOUND; default: return RLM_MODULE_FAIL; } return RLM_MODULE_OK; }
/* * 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; }
/** Convert multiple group names into a DNs * * Given an array of group names, builds a filter matching all names, then retrieves all group objects * and stores the DN associated with each group object. * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] names to covert to DNs (NULL terminated). * @param[out] out Where to write the DNs. DNs must be freed with ldap_memfree(). Will be NULL terminated. * @param[in] outlen Size of out. * @return One of the RLM_MODULE_* values. */ static rlm_rcode_t rlm_ldap_group_name2dn(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, char **names, char **out, size_t outlen) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; unsigned int name_cnt = 0; unsigned int entry_cnt; char const *attrs[] = { NULL }; LDAPMessage *result = NULL, *entry; char **name = names; char **dn = out; char const *base_dn = NULL; char base_dn_buff[LDAP_MAX_DN_STR_LEN]; char buffer[LDAP_MAX_GROUP_NAME_LEN + 1]; char *filter; *dn = NULL; if (!*names) { return RLM_MODULE_OK; } if (!inst->groupobj_name_attr) { REDEBUG("Told to convert group names to DNs but missing 'group.name_attribute' directive"); return RLM_MODULE_INVALID; } RDEBUG("Converting group name(s) to group DN(s)"); /* * It'll probably only save a few ms in network latency, but it means we can send a query * for the entire group list at once. */ filter = talloc_typed_asprintf(request, "%s%s%s", inst->groupobj_filter ? "(&" : "", inst->groupobj_filter ? inst->groupobj_filter : "", names[0] && names[1] ? "(|" : ""); while (*name) { rlm_ldap_escape_func(request, buffer, sizeof(buffer), *name++, NULL); filter = talloc_asprintf_append_buffer(filter, "(%s=%s)", inst->groupobj_name_attr, buffer); name_cnt++; } filter = talloc_asprintf_append_buffer(filter, "%s%s", inst->groupobj_filter ? ")" : "", names[0] && names[1] ? ")" : ""); if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, NULL, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG("Tried to resolve group name(s) to DNs but got no results"); goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; } entry_cnt = ldap_count_entries((*pconn)->handle, result); if (entry_cnt > name_cnt) { REDEBUG("Number of DNs exceeds number of names, group and/or dn should be more restrictive"); rcode = RLM_MODULE_INVALID; goto finish; } if (entry_cnt > (outlen - 1)) { REDEBUG("Number of DNs exceeds limit (%zu)", outlen - 1); rcode = RLM_MODULE_INVALID; goto finish; } if (entry_cnt < name_cnt) { RWDEBUG("Got partial mapping of group names (%i) to DNs (%i), membership information may be incomplete", name_cnt, entry_cnt); } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); rcode = RLM_MODULE_FAIL; goto finish; } do { *dn = ldap_get_dn((*pconn)->handle, entry); if (!*dn) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno)); rcode = RLM_MODULE_FAIL; goto finish; } rlm_ldap_normalise_dn(*dn, *dn); RDEBUG("Got group DN \"%s\"", *dn); dn++; } while((entry = ldap_next_entry((*pconn)->handle, entry))); *dn = NULL; finish: talloc_free(filter); if (result) { ldap_msgfree(result); } /* * Be nice and cleanup the output array if we error out. */ if (rcode != RLM_MODULE_OK) { dn = out; while(*dn) ldap_memfree(*dn++); *dn = NULL; } return rcode; }
/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_groupobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; LDAPMessage *result = NULL; LDAPMessage *entry; char const *base_dn; char base_dn_buff[LDAP_MAX_DN_STR_LEN]; char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter }; char filter[LDAP_MAX_FILTER_STR_LEN + 1]; char const *attrs[] = { inst->groupobj_name_attr, NULL }; VALUE_PAIR *vp; char *dn; rad_assert(inst->groupobj_base_dn); if (!inst->groupobj_membership_filter) { RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set"); return RLM_MODULE_OK; } if (rlm_ldap_xlat_filter(request, filters, sizeof(filters) / sizeof(*filters), filter, sizeof(filter)) < 0) { return RLM_MODULE_INVALID; } if (tmpl_expand(&base_dn, base_dn_buff, sizeof(base_dn_buff), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) { REDEBUG("Failed creating base_dn"); return RLM_MODULE_INVALID; } status = rlm_ldap_search(&result, inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, NULL, NULL); switch (status) { case LDAP_PROC_SUCCESS: break; case LDAP_PROC_NO_RESULT: RDEBUG2("No cacheable group memberships found in group objects"); goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; } entry = ldap_first_entry((*pconn)->handle, result); if (!entry) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } RDEBUG("Adding cacheable group object memberships"); do { if (inst->cacheable_group_dn) { dn = ldap_get_dn((*pconn)->handle, entry); if (!dn) { ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Retrieving object DN from entry failed: %s", ldap_err2string(ldap_errno)); goto finish; } rlm_ldap_normalise_dn(dn, dn); MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_strcpy(vp, dn); RINDENT(); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, dn); REXDENT(); ldap_memfree(dn); } if (inst->cacheable_group_name) { struct berval **values; values = ldap_get_values_len((*pconn)->handle, entry, inst->groupobj_name_attr); if (!values) continue; MEM(vp = pair_make_config(inst->cache_da->name, NULL, T_OP_ADD)); fr_pair_value_bstrncpy(vp, values[0]->bv_val, values[0]->bv_len); RINDENT(); RDEBUG("&control:%s += \"%.*s\"", inst->cache_da->name, (int)values[0]->bv_len, values[0]->bv_val); REXDENT(); ldap_value_free_len(values); } } while ((entry = ldap_next_entry((*pconn)->handle, entry))); finish: if (result) ldap_msgfree(result); 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; }
/** 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 Operation status (#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]; char buffer[MAX_KEY_SIZE]; char const *vkey; /* 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 */ ssize_t slen; /* 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; } slen = tmpl_expand(&vkey, buffer, sizeof(buffer), request, inst->simul_vkey, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((vkey == 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; /* 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 finish; } /* 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 finish; } /* 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 finish; } /* 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 || !fr_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 finish; } /* 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 finish; } /* * 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 finish; } /* debugging */ RDEBUG("verifying session count"); /* reset the count */ request->simul_count = 0; /* get client ip address for MPP detection below */ if ((vp = fr_pair_find_by_num(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 = fr_pair_find_by_num(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 finish; } /* 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 finish; } /* 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 finish; } /* 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 finish; } /* 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 finish; } /* 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 finish; } /* free and reset document user name talloc */ if (user_name) TALLOC_FREE(user_name); /* free and reset document calling station id talloc */ if (cs_id) TALLOC_FREE(cs_id); /* free and reset document session id talloc */ if (session_id) TALLOC_FREE(session_id); /* 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); finish: if (user_name) talloc_free(user_name); if (cs_id) talloc_free(cs_id); 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; } 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; }
/** 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; }