static void ippool_action_print(REQUEST *request, ippool_action_t action, fr_log_lvl_t lvl, uint8_t const *key_prefix, size_t key_prefix_len, char const *ip_str, uint8_t const *device_id, size_t device_id_len, uint8_t const *gateway_id, size_t gateway_id_len, uint32_t expires) { char *key_prefix_str, *device_str = NULL, *gateway_str = NULL; key_prefix_str = fr_asprint(request, (char const *)key_prefix, key_prefix_len, '"'); if (gateway_id) gateway_str = fr_asprint(request, (char const *)gateway_id, gateway_id_len, '"'); if (device_id) device_str = fr_asprint(request, (char const *)device_id, device_id_len, '"'); switch (action) { case POOL_ACTION_ALLOCATE: RDEBUGX(lvl, "Allocating lease from pool \"%s\"%s%s%s%s%s%s, expires in %us", key_prefix_str, device_str ? ", to \"" : "", device_str ? device_str : "", device_str ? "\"" : "", gateway_str ? ", on \"" : "", gateway_str ? gateway_str : "", gateway_str ? "\"" : "", expires); break; case POOL_ACTION_UPDATE: RDEBUGX(lvl, "Updating %s in pool \"%s\"%s%s%s%s%s%s, expires in %us", ip_str, key_prefix_str, device_str ? ", device \"" : "", device_str ? device_str : "", device_str ? "\"" : "", gateway_str ? ", gateway \"" : "", gateway_str ? gateway_str : "", gateway_str ? "\"" : "", expires); break; case POOL_ACTION_RELEASE: RDEBUGX(lvl, "Releasing %s%s%s%s to pool \"%s\"", ip_str, device_str ? " leased by \"" : "", device_str ? device_str : "", device_str ? "\"" : "", key_prefix_str); break; default: break; } /* * Ordering is important, needs to be LIFO * for proper talloc pool re-use. */ talloc_free(key_prefix_str); talloc_free(device_str); talloc_free(gateway_str); }
/** Converts a string value into a #VALUE_PAIR * * @param[in,out] ctx to allocate #VALUE_PAIR (s). * @param[out] out where to write the resulting #VALUE_PAIR. * @param[in] request The current request. * @param[in] map to process. * @param[in] uctx The value to parse. * @return * - 0 on success. * - -1 on failure. */ static int _sql_map_proc_get_value(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { VALUE_PAIR *vp; char const *value = uctx; vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da); /* * Buffer not always talloced, sometimes it's * just a pointer to a field in a result struct. */ if (fr_pair_value_from_str(vp, value, strlen(value)) < 0) { char *escaped; escaped = fr_asprint(vp, value, talloc_array_length(value), '"'); REDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); return -1; } vp->op = map->op; *out = vp; return 0; }
/** Locate a cache entry in redis * * @copydetails cache_entry_find_t */ static cache_status_t cache_entry_find(rlm_cache_entry_t **out, UNUSED rlm_cache_config_t const *config, void *driver_inst, REQUEST *request, UNUSED void *handle, uint8_t const *key, size_t key_len) { rlm_cache_redis_t *driver = driver_inst; size_t i; fr_redis_cluster_state_t state; fr_redis_conn_t *conn; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; vp_map_t *head = NULL, **last = &head; #ifdef HAVE_TALLOC_POOLED_OBJECT size_t pool_size = 0; #endif rlm_cache_entry_t *c; for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, key, key_len, false); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) { /* * Grab all the data for this hash, should return an array * of alternating keys/values which we then convert into maps. */ if (RDEBUG_ENABLED3) { char *p; p = fr_asprint(NULL, (char const *)key, key_len, '"'); RDEBUG3("LRANGE %s 0 -1", key); talloc_free(p); } reply = redisCommand(conn->handle, "LRANGE %b 0 -1", key, key_len); status = fr_redis_command_status(conn, reply); } if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed retrieving entry"); fr_redis_reply_free(reply); return CACHE_ERROR; } rad_assert(reply); /* clang scan */ if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Bad result type, expected array, got %s", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); fr_redis_reply_free(reply); return CACHE_ERROR; } RDEBUG3("Entry contains %zu elements", reply->elements); if (reply->elements == 0) { fr_redis_reply_free(reply); return CACHE_MISS; } if (reply->elements % 3) { REDEBUG("Invalid number of reply elements (%zu). " "Reply must contain triplets of keys operators and values", reply->elements); fr_redis_reply_free(reply); return CACHE_ERROR; } #ifdef HAVE_TALLOC_POOLED_OBJECT /* * We can get a pretty good idea of the required size of the pool */ for (i = 0; i < reply->elements; i += 3) { pool_size += sizeof(vp_map_t) + (sizeof(vp_tmpl_t) * 2); if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1; } /* * reply->elements gives us the number of chunks, as the maps are triplets, and there * are three chunks per map */ c = talloc_pooled_object(NULL, rlm_cache_entry_t, reply->elements, pool_size); memset(&pool, 0, sizeof(rlm_cache_entry_t)); #else c = talloc_zero(NULL, rlm_cache_entry_t); #endif /* * Convert the key/value pairs back into maps */ for (i = 0; i < reply->elements; i += 3) { if (fr_redis_reply_to_map(c, last, request, reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) { talloc_free(c); fr_redis_reply_free(reply); return CACHE_ERROR; } last = &(*last)->next; } fr_redis_reply_free(reply); /* * Pull out the cache created date */ if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_CREATED)) { vp_map_t *map; c->created = head->rhs->tmpl_data_value.date; map = head; head = head->next; talloc_free(map); } /* * Pull out the cache expires date */ if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_EXPIRES)) { vp_map_t *map; c->expires = head->rhs->tmpl_data_value.date; map = head; head = head->next; talloc_free(map); } c->key = talloc_memdup(c, key, key_len); c->key_len = key_len; c->maps = head; *out = c; return CACHE_OK; }
/** Insert a new entry into the data store * * @copydetails cache_entry_insert_t */ static cache_status_t cache_entry_insert(UNUSED rlm_cache_config_t const *config, void *driver_inst, REQUEST *request, UNUSED void *handle, const rlm_cache_entry_t *c) { rlm_cache_redis_t *driver = driver_inst; TALLOC_CTX *pool; vp_map_t *map; fr_redis_conn_t *conn; fr_redis_cluster_state_t state; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; static char const command[] = "RPUSH"; char const **argv; size_t *argv_len; char const **argv_p; size_t *argv_len_p; int pipelined = 0; /* How many commands pending in the pipeline */ redisReply *replies[5]; /* Should have the same number of elements as pipelined commands */ size_t reply_num = 0, i; char *p; int cnt; vp_tmpl_t expires_value; vp_map_t expires = { .op = T_OP_SET, .lhs = &driver->expires_attr, .rhs = &expires_value, }; vp_tmpl_t created_value; vp_map_t created = { .op = T_OP_SET, .lhs = &driver->created_attr, .rhs = &created_value, .next = &expires }; /* * Encode the entry created date */ tmpl_init(&created_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD); created_value.tmpl_data_type = PW_TYPE_DATE; created_value.tmpl_data_length = sizeof(created_value.tmpl_data_value.date); created_value.tmpl_data_value.date = c->created; /* * Encode the entry expiry time * * Although Redis objects expire on their own, we still need this * to ignore entries that were created before the last epoch. */ tmpl_init(&expires_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD); expires_value.tmpl_data_type = PW_TYPE_DATE; expires_value.tmpl_data_length = sizeof(expires_value.tmpl_data_value.date); expires_value.tmpl_data_value.date = c->expires; expires.next = c->maps; /* Head of the list */ for (cnt = 0, map = &created; map; cnt++, map = map->next); /* * The majority of serialized entries should be under 1k. * * @todo We should really calculate this using some sort of moving average. */ pool = talloc_pool(request, 1024); if (!pool) return CACHE_ERROR; argv_p = argv = talloc_array(pool, char const *, (cnt * 3) + 2); /* pair = 3 + cmd + key */ argv_len_p = argv_len = talloc_array(pool, size_t, (cnt * 3) + 2); /* pair = 3 + cmd + key */ *argv_p++ = command; *argv_len_p++ = sizeof(command) - 1; *argv_p++ = (char const *)c->key; *argv_len_p++ = c->key_len; /* * Add the maps to the command string in reverse order */ for (map = &created; map; map = map->next) { if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, map) < 0) { REDEBUG("Failed encoding map as Redis K/V pair"); talloc_free(pool); return CACHE_ERROR; } argv_p += 3; argv_len_p += 3; } RDEBUG3("Pipelining commands"); RINDENT(); for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, c->key, c->key_len, false); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) { /* * Start the transaction, as we need to set an expiry time too. */ if (c->expires > 0) { RDEBUG3("MULTI"); if (redisAppendCommand(conn->handle, "MULTI") != REDIS_OK) { append_error: REXDENT(); RERROR("Failed appending Redis command to output buffer: %s", conn->handle->errstr); talloc_free(pool); return CACHE_ERROR; } pipelined++; } if (RDEBUG_ENABLED3) { p = fr_asprint(request, (char const *)c->key, c->key_len, '\0'); RDEBUG3("DEL \"%s\"", p); talloc_free(p); } if (redisAppendCommand(conn->handle, "DEL %b", c->key, c->key_len) != REDIS_OK) goto append_error; pipelined++; if (RDEBUG_ENABLED3) { RDEBUG3("argv command"); RINDENT(); for (i = 0; i < talloc_array_length(argv); i++) { p = fr_asprint(request, argv[i], argv_len[i], '\0'); RDEBUG3("%s", p); talloc_free(p); } REXDENT(); } redisAppendCommandArgv(conn->handle, talloc_array_length(argv), argv, argv_len); pipelined++; /* * Set the expiry time and close out the transaction. */ if (c->expires > 0) { if (RDEBUG_ENABLED3) { p = fr_asprint(request, (char const *)c->key, c->key_len, '\"'); RDEBUG3("EXPIREAT \"%s\" %li", p, (long)c->expires); talloc_free(p); } if (redisAppendCommand(conn->handle, "EXPIREAT %b %i", c->key, c->key_len, c->expires) != REDIS_OK) goto append_error; pipelined++; RDEBUG3("EXEC"); if (redisAppendCommand(conn->handle, "EXEC") != REDIS_OK) goto append_error; pipelined++; } REXDENT(); reply_num = fr_redis_pipeline_result(&status, replies, sizeof(replies) / sizeof(*replies), conn, pipelined); reply = replies[0]; } talloc_free(pool); if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed inserting entry"); return CACHE_ERROR; } RDEBUG3("Command results"); RINDENT(); for (i = 0; i < reply_num; i++) { fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i); fr_redis_reply_free(replies[i]); } REXDENT(); return CACHE_OK; } /** Call delete the cache entry from redis * * @copydetails cache_entry_expire_t */ static cache_status_t cache_entry_expire(UNUSED rlm_cache_config_t const *config, void *driver_inst, REQUEST *request, UNUSED void *handle, uint8_t const *key, size_t key_len) { rlm_cache_redis_t *driver = driver_inst; fr_redis_cluster_state_t state; fr_redis_conn_t *conn; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, key, key_len, false); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) { reply = redisCommand(conn->handle, "DEL %b", key, key_len); status = fr_redis_command_status(conn, reply); } if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed expiring entry"); fr_redis_reply_free(reply); return CACHE_ERROR; } rad_assert(reply); /* clang scan */ if (reply->type == REDIS_REPLY_INTEGER) { fr_redis_reply_free(reply); if (reply->integer) return CACHE_OK; /* Affected */ return CACHE_MISS; } REDEBUG("Bad result type, expected integer, got %s", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); fr_redis_reply_free(reply); return CACHE_ERROR; } extern cache_driver_t rlm_cache_redis; cache_driver_t rlm_cache_redis = { .name = "rlm_cache_redis", .instantiate = mod_instantiate, .inst_size = sizeof(rlm_cache_redis_t), .free = cache_entry_free, .find = cache_entry_find, .insert = cache_entry_insert, .expire = cache_entry_expire, };
/** Initiate an LDAP interactive bind * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request, this may be NULL, in which case all debug logging is done with radlog. * @param[in] conn to use. May change as this function calls functions which auto re-connect. * @param[in] identity of the user. * @param[in] password of the user. * @param[in] sasl mechanism to use for bind, and additional parameters. * @param[in] serverctrls Search controls to pass to the server. May be NULL. * @param[in] clientctrls Search controls for ldap_sasl_interactive. May be NULL. * @param[out] error message resulting from bind. * @param[out] extra information about the error. * @return One of the LDAP_PROC_* (#ldap_rcode_t) values. */ ldap_rcode_t rlm_ldap_sasl_interactive(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t *conn, char const *identity, char const *password, ldap_sasl *sasl, LDAPControl **serverctrls, LDAPControl **clientctrls, char const **error, char **extra) { ldap_rcode_t status; int ret = 0; int msgid; char const *mech; LDAPMessage *result = NULL; rlm_ldap_sasl_ctx_t sasl_ctx; /* SASL defaults */ LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; rlm_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), conn, serverctrls, clientctrls); /* rlm_ldap_result may not be called */ if (error) *error = NULL; if (extra) *extra = NULL; sasl_ctx.inst = inst; sasl_ctx.request = request; sasl_ctx.identity = identity; sasl_ctx.password = password; sasl_ctx.extra = sasl; MOD_ROPTIONAL(RDEBUG2, DEBUG2, "Starting SASL mech(s): %s", sasl->mech); for (;;) { ret = ldap_sasl_interactive_bind(conn->handle, NULL, sasl->mech, our_serverctrls, our_clientctrls, LDAP_SASL_AUTOMATIC, _sasl_interact, &sasl_ctx, result, &mech, &msgid); /* * If ldap_sasl_interactive_bind indicates it didn't want * to continue, then we're done. * * Calling ldap_result here, results in a timeout in some * cases, so we need to figure out whether the bind was * successful without the help of ldap_result. */ if (ret != LDAP_SASL_BIND_IN_PROGRESS) { status = rlm_ldap_result(inst, conn, -1, identity, NULL, error, extra); break; /* Old result gets freed on after exit */ } ldap_msgfree(result); /* We always need to free the old message */ /* * If LDAP parse result indicates there was an error * then we're done. */ status = rlm_ldap_result(inst, conn, msgid, identity, &result, error, extra); switch (status) { case LDAP_PROC_SUCCESS: /* ldap_sasl_interactive_bind should have indicated success */ case LDAP_PROC_CONTINUE: break; default: goto done; } /* * ...otherwise, the bind is still in progress. */ MOD_ROPTIONAL(RDEBUG3, DEBUG3, "Continuing SASL mech %s...", mech); /* * Write the servers response to the debug log */ if (((request && RDEBUG_ENABLED3) || DEBUG_ENABLED3) && result) { struct berval *srv_cred; if (ldap_parse_sasl_bind_result(conn->handle, result, &srv_cred, 0) == 0) { char *escaped; escaped = fr_asprint(request, srv_cred->bv_val, srv_cred->bv_len, '\0'); MOD_ROPTIONAL(RDEBUG3, DEBUG3, "SASL response : %s", escaped); talloc_free(escaped); ldap_memfree(srv_cred); } } } done: ldap_msgfree(result); return status; }
/** Callback for map_to_request * * Performs exactly the same job as map_to_vp, but pulls attribute values from LDAP entries * * @see map_to_vp */ int rlm_ldap_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { rlm_ldap_result_t *self = uctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; int i; fr_cursor_init(&cursor, &head); switch (map->lhs->type) { /* * This is a mapping in the form of: * <list>: += <ldap attr> * * Where <ldap attr> is: * <list>:<attr> <op> <value> * * It is to allow for legacy installations which stored * RADIUS control and reply attributes in separate LDAP * attributes. */ case TMPL_TYPE_LIST: for (i = 0; i < self->count; i++) { vp_map_t *attr = NULL; RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]->bv_val); if (map_afrom_attr_str(ctx, &attr, self->values[i]->bv_val, map->lhs->tmpl_request, map->lhs->tmpl_list, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing \"%s\" as valuepair (%s), skipping...", fr_strerror(), self->values[i]->bv_val); continue; } if (attr->lhs->tmpl_request != map->lhs->tmpl_request) { RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(request_refs, attr->lhs->tmpl_request, "<INVALID>"), fr_int2str(request_refs, map->lhs->tmpl_request, "<INVALID>")); next_pair: talloc_free(attr); continue; } if ((attr->lhs->tmpl_list != map->lhs->tmpl_list)) { RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(pair_lists, attr->lhs->tmpl_list, "<INVALID>"), fr_int2str(pair_lists, map->lhs->tmpl_list, "<INVALID>")); goto next_pair; } if (map_to_vp(request, &vp, request, attr, NULL) < 0) { RWDEBUG("Failed creating attribute for valuepair \"%s\", skipping...", self->values[i]->bv_val); goto next_pair; } fr_cursor_merge(&cursor, vp); talloc_free(attr); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } break; /* * Iterate over all the retrieved values, * don't try and be clever about changing operators * just use whatever was set in the attribute map. */ case TMPL_TYPE_ATTR: for (i = 0; i < self->count; i++) { if (!self->values[i]->bv_len) continue; vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da); rad_assert(vp); if (fr_pair_value_from_str(vp, self->values[i]->bv_val, self->values[i]->bv_len) < 0) { char *escaped; escaped = fr_asprint(vp, self->values[i]->bv_val, self->values[i]->bv_len, '"'); RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); /* also frees escaped */ continue; } vp->op = map->op; fr_cursor_insert(&cursor, vp); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } break; default: rad_assert(0); } *out = head; return 0; }