static int _map_proc_client_get_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { client_get_vp_ctx_t *client = uctx; VALUE_PAIR *head = NULL, *vp; fr_cursor_t cursor; fr_dict_attr_t const *da; CONF_PAIR const *cp; rad_assert(ctx != NULL); fr_cursor_init(&cursor, &head); /* * FIXME: allow multiple entries. */ if (map->lhs->type == TMPL_TYPE_ATTR) { da = map->lhs->tmpl_da; } else { char *attr; if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { RWDEBUG("Failed expanding string"); return -1; } da = fr_dict_attr_by_name(request->dict, attr); if (!da) { RWDEBUG("No such attribute '%s'", attr); return -1; } talloc_free(attr); } for (cp = client->cp; cp; cp = cf_pair_find_next(client->cs, cp, client->field)) { char const *value = cf_pair_value(cp); MEM(vp = fr_pair_afrom_da(ctx, da)); if (fr_pair_value_from_str(vp, value, talloc_array_length(value) - 1, '\0', false) < 0) { RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue(value), map->lhs->tmpl_da->name, fr_strerror()); fr_pair_list_free(&head); talloc_free(vp); return -1; } vp->op = map->op; fr_cursor_append(&cursor, vp); if (map->op != T_OP_ADD) break; /* Create multiple attribute for multiple CONF_PAIRs */ } *out = head; return 0; }
/* * Convert field X to a VP. */ static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { char const *str = uctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; DICT_ATTR const *da; rad_assert(ctx != NULL); fr_cursor_init(&cursor, &head); /* * FIXME: allow multiple entries. */ if (map->lhs->type == TMPL_TYPE_ATTR) { da = map->lhs->tmpl_da; } else { char *attr; if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { RWDEBUG("Failed expanding string"); return -1; } da = dict_attrbyname(attr); if (!da) { RWDEBUG("No such attribute '%s'", attr); return -1; } talloc_free(attr); } vp = pairalloc(ctx, da); rad_assert(vp); if (pairparsevalue(vp, str, talloc_array_length(str) - 1) < 0) { char *escaped; escaped = fr_aprints(vp, str, talloc_array_length(str) - 1, '\''); RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); /* also frees escaped */ return -1; } vp->op = map->op; fr_cursor_merge(&cursor, vp); *out = head; return 0; }
/** Perform a search and map the result of the search to server attributes * * @param[in] mod_inst #rlm_csv_t * @param[in] proc_inst mapping map entries to field numbers. * @param[in,out] request The current request. * @param[in] key key to look for * @param[in] maps Head of the map list. * @return * - #RLM_MODULE_NOOP no rows were returned. * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST. * - #RLM_MODULE_FAIL if an error occurred. */ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request, char const *key, vp_map_t const *maps) { rlm_csv_t *inst = mod_inst; rlm_csv_entry_t *e, my_entry; vp_map_t const *map; my_entry.key = key; e = rbtree_finddata(inst->tree, &my_entry); if (!e) return RLM_MODULE_NOOP; RINDENT(); for (map = maps; map != NULL; map = map->next) { int field; char *field_name; /* * Avoid memory allocations if possible. */ if (map->rhs->type != TMPL_TYPE_LITERAL) { if (tmpl_aexpand(request, &field_name, request, map->rhs, NULL, NULL) < 0) { RDEBUG("Failed expanding RHS at %s", map->lhs->name); return RLM_MODULE_FAIL; } } else { memcpy(&field_name, &map->rhs->name, sizeof(field_name)); /* const */ } field = fieldname2offset(inst, field_name); if (field_name != map->rhs->name) talloc_free(field_name); if (field < 0) { RDEBUG("No such field name %s", map->rhs->name); return RLM_MODULE_FAIL; } /* * Pass the raw data to the callback, which will * create the VP and add it to the map. */ if (map_to_request(request, map, csv_map_getvalue, e->data[field]) < 0) { return RLM_MODULE_FAIL; } } return RLM_MODULE_UPDATED; }
/* * Convert field X to a VP. */ static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { char const *str = uctx; VALUE_PAIR *head = NULL, *vp; fr_cursor_t cursor; fr_dict_attr_t const *da; rad_assert(ctx != NULL); fr_cursor_init(&cursor, &head); /* * FIXME: allow multiple entries. */ if (map->lhs->type == TMPL_TYPE_ATTR) { da = map->lhs->tmpl_da; } else { char *attr; if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { RWDEBUG("Failed expanding string"); return -1; } da = fr_dict_attr_by_name(request->dict, attr); if (!da) { RWDEBUG("No such attribute '%s'", attr); return -1; } talloc_free(attr); } vp = fr_pair_afrom_da(ctx, da); rad_assert(vp); if (fr_pair_value_from_str(vp, str, talloc_array_length(str) - 1, '\0', true) < 0) { RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue_buffer(str), map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); return -1; } vp->op = map->op; fr_cursor_append(&cursor, vp); *out = head; return 0; }
/** Evaluate a set of maps using the specified map processor * * Evaluate the map processor src template, then call a map processor function to do * something with the expanded src template and map the result to attributes in the request. * * @param request The current request. * @param inst of a map processor. */ rlm_rcode_t map_proc(REQUEST *request, map_proc_inst_t const *inst) { char *value; rlm_rcode_t rcode; if (tmpl_aexpand(request, &value, request, inst->src, inst->proc->escape, inst->proc->mod_inst) < 0) { return RLM_MODULE_FAIL; } rcode = inst->proc->evaluate(inst->proc->mod_inst, inst->data, request, value, inst->maps); talloc_free(value); return rcode; }
static rlm_rcode_t CC_HINT(nonnull) mod_delay(void *instance, UNUSED void *thread, REQUEST *request) { rlm_delay_t const *inst = instance; struct timeval delay, resume_at, *yielded_at; if (inst->delay) { if (tmpl_aexpand(request, &delay, request, inst->delay, NULL, NULL) < 0) return RLM_MODULE_FAIL; } else { memset(&delay, 0, sizeof(delay)); } /* * Record the time that we yielded the request */ MEM(yielded_at = talloc(request, struct timeval)); if (gettimeofday(yielded_at, NULL) < 0) { REDEBUG("Failed getting current time: %s", fr_syserror(errno)); return RLM_MODULE_FAIL; } /* * Setup the delay for this request */ if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->delay) != 0) { return RLM_MODULE_NOOP; } RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at)); if (unlang_event_module_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) { RPEDEBUG("Adding event failed"); return RLM_MODULE_FAIL; } return unlang_module_yield(request, mod_delay_return, mod_delay_cancel, yielded_at); }
/** Logging callback to write log messages to a destination * * This allows the logging destination to be customised on a per request basis. * * @note Function does not write log output immediately * * @param[in] type What type of message this is (error, warn, info, debug). * @param[in] lvl At what logging level this message should be output. * @param[in] request The current request. * @param[in] file src file the log message was generated in. * @param[in] line number the log message was generated on. * @param[in] fmt sprintf style fmt string. * @param[in] ap Arguments for the fmt string. * @param[in] uctx Context data for the log function. */ static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, UNUSED char const *file, UNUSED int line, char const *fmt, va_list ap, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; char *msg, *exp; fr_cursor_t cursor; VALUE_PAIR *vp; log_dst_t *dst; rad_assert(t->msg->vp_length == 0); /* Should have been cleared before returning */ /* * None of this should involve mallocs unless msg > 1k */ msg = talloc_typed_vasprintf(t->msg, fmt, ap); fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true); t->type->vp_uint32 = (uint32_t) type; t->lvl->vp_uint32 = (uint32_t) lvl; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_prepend(&cursor, t->msg); fr_cursor_prepend(&cursor, t->type); fr_cursor_prepend(&cursor, t->lvl); fr_cursor_head(&cursor); /* * Now expand our fmt string to encapsulate the * message and any metadata * * Fixme: Would be better to call tmpl_expand * into a variable length ring buffer. */ dst = request->log.dst; request->log.dst = NULL; if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish; request->log.dst = dst; fr_fring_overwrite(t->fring, exp); /* Insert it into the buffer */ if (!t->pending) { t->pending = true; logtee_fd_active(t); /* Listen for when the fd is writable */ } finish: /* * Don't free, we re-use the VALUE_PAIRs for the next message */ vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp); fr_value_box_clear(&t->msg->data); /* Clear message data */ }
/** Create a new server TLS session * * Configures a new server TLS session, configuring options, setting callbacks etc... * * @param ctx to alloc session data in. Should usually be NULL unless the lifetime of the * session is tied to another talloc'd object. * @param conf values for this TLS session. * @param request The current #REQUEST. * @param client_cert Whether to require a client_cert. * @return * - A new session on success. * - NULL on error. */ tls_session_t *tls_session_init_server(TALLOC_CTX *ctx, fr_tls_conf_t *conf, REQUEST *request, bool client_cert) { tls_session_t *session = NULL; SSL *new_tls = NULL; int verify_mode = 0; VALUE_PAIR *vp; SSL_CTX *ssl_ctx; rad_assert(request != NULL); rad_assert(conf->ctx_count > 0); RDEBUG2("Initiating new TLS session"); ssl_ctx = conf->ctx[(conf->ctx_count == 1) ? 0 : conf->ctx_next++ % conf->ctx_count]; /* mutex not needed */ rad_assert(ssl_ctx); new_tls = SSL_new(ssl_ctx); if (new_tls == NULL) { tls_log_error(request, "Error creating new TLS session"); return NULL; } session = talloc_zero(ctx, tls_session_t); if (session == NULL) { RERROR("Error allocating memory for TLS session"); SSL_free(new_tls); return NULL; } session_init(session); session->ctx = ssl_ctx; session->ssl = new_tls; talloc_set_destructor(session, _tls_session_free); /* * Initialize callbacks */ session->record_init = record_init; session->record_close = record_close; session->record_from_buff = record_from_buff; session->record_to_buff = record_to_buff; /* * Create & hook the BIOs to handle the dirty side of the * SSL. This is *very important* as we want to handle * the transmission part. Now the only IO interface * that SSL is aware of, is our defined BIO buffers. * * This means that all SSL IO is done to/from memory, * and we can update those BIOs from the packets we've * received. */ session->into_ssl = BIO_new(BIO_s_mem()); session->from_ssl = BIO_new(BIO_s_mem()); SSL_set_bio(session->ssl, session->into_ssl, session->from_ssl); /* * Add the message callback to identify what type of * message/handshake is passed */ SSL_set_msg_callback(new_tls, tls_session_msg_cb); SSL_set_msg_callback_arg(new_tls, session); SSL_set_info_callback(new_tls, tls_session_info_cb); /* * This sets the context sessions can be resumed in. * This is to prevent sessions being created by one application * and used by another. In our case it prevents sessions being * reused between modules, or TLS server components such as * RADSEC. * * A context must always be set when doing session resumption * otherwise session resumption will fail. * * As the context ID must be <= 32, we digest the context * data with sha256. */ rad_assert(conf->session_id_name); { char *context_id; EVP_MD_CTX *md_ctx; uint8_t digest[SHA256_DIGEST_LENGTH]; static_assert(sizeof(digest) <= SSL_MAX_SSL_SESSION_ID_LENGTH, "SSL_MAX_SSL_SESSION_ID_LENGTH must be >= SHA256_DIGEST_LENGTH"); if (tmpl_aexpand(session, &context_id, request, conf->session_id_name, NULL, NULL) < 0) { RPEDEBUG("Failed expanding session ID"); talloc_free(session); } MEM(md_ctx = EVP_MD_CTX_create()); EVP_DigestInit_ex(md_ctx, EVP_sha256(), NULL); EVP_DigestUpdate(md_ctx, context_id, talloc_array_length(context_id) - 1); EVP_DigestFinal_ex(md_ctx, digest, NULL); EVP_MD_CTX_destroy(md_ctx); talloc_free(context_id); if (!fr_cond_assert(SSL_set_session_id_context(session->ssl, digest, sizeof(digest)) == 1)) { talloc_free(session); return NULL; } } /* * Add the session certificate to the session. */ vp = fr_pair_find_by_da(request->control, attr_tls_session_cert_file, TAG_ANY); if (vp) { RDEBUG2("Loading TLS session certificate \"%s\"", vp->vp_strvalue); if (SSL_use_certificate_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) { tls_log_error(request, "Failed loading TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } if (SSL_use_PrivateKey_file(session->ssl, vp->vp_strvalue, SSL_FILETYPE_PEM) != 1) { tls_log_error(request, "Failed loading TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } if (SSL_check_private_key(session->ssl) != 1) { tls_log_error(request, "Failed validating TLS session certificate \"%s\"", vp->vp_strvalue); talloc_free(session); return NULL; } /* * Better to perform explicit checks, than rely * on OpenSSL's opaque error messages. */ } else { if (!conf->chains || !conf->chains[0]->private_key_file) { ERROR("TLS Server requires a private key file"); talloc_free(session); return NULL; } if (!conf->chains || !conf->chains[0]->certificate_file) { ERROR("TLS Server requires a certificate file"); talloc_free(session); return NULL; } } /* * In Server mode we only accept. * * This sets up the SSL session to work correctly with * tls_session_handhsake. */ SSL_set_accept_state(session->ssl); /* * Verify the peer certificate, if asked. */ if (client_cert) { RDEBUG2("Setting verify mode to require certificate from client"); verify_mode = SSL_VERIFY_PEER; verify_mode |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; verify_mode |= SSL_VERIFY_CLIENT_ONCE; } SSL_set_verify(session->ssl, verify_mode, tls_validate_cert_cb); SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_CONF, (void *)conf); SSL_set_ex_data(session->ssl, FR_TLS_EX_INDEX_TLS_SESSION, (void *)session); /* * We use default fragment size, unless the Framed-MTU * tells us it's too big. Note that we do NOT account * for the EAP-TLS headers if conf->fragment_size is * large, because that config item looks to be confusing. * * i.e. it should REALLY be called MTU, and the code here * should figure out what that means for TLS fragment size. * asking the administrator to know the internal details * of EAP-TLS in order to calculate fragment sizes is * just too much. */ session->mtu = conf->fragment_size; vp = fr_pair_find_by_da(request->packet->vps, attr_framed_mtu, TAG_ANY); if (vp && (vp->vp_uint32 > 100) && (vp->vp_uint32 < session->mtu)) { RDEBUG2("Setting fragment_len to %u from &Framed-MTU", vp->vp_uint32); session->mtu = vp->vp_uint32; } if (conf->session_cache_server) session->allow_session_resumption = true; /* otherwise it's false */ return session; }
/** * 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; }
/** Map multiple attributes from a client into the request * * @param[in] mod_inst NULL. * @param[in] proc_inst NULL. * @param[in] request The current request. * @param[in] client_override If NULL, use the current client, else use the client matching * the ip given. * @param[in] maps Head of the map list. * @return * - #RLM_MODULE_NOOP no rows were returned. * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST. * - #RLM_MODULE_FAIL if an error occurred. */ static rlm_rcode_t map_proc_client(UNUSED void *mod_inst, UNUSED void *proc_inst, REQUEST *request, fr_value_box_t **client_override, vp_map_t const *maps) { rlm_rcode_t rcode = RLM_MODULE_OK; vp_map_t const *map; RADCLIENT *client; client_get_vp_ctx_t uctx; if (*client_override) { fr_ipaddr_t ip; char const *client_str; /* * Concat don't asprint, as this becomes a noop * in the vast majority of cases. */ if (fr_value_box_list_concat(request, *client_override, client_override, FR_TYPE_STRING, true) < 0) { REDEBUG("Failed concatenating input data"); return RLM_MODULE_FAIL; } client_str = (*client_override)->vb_strvalue; if (fr_inet_pton(&ip, client_str, -1, AF_UNSPEC, false, true) < 0) { REDEBUG("\"%s\" is not a valid IPv4 or IPv6 address", client_str); rcode = RLM_MODULE_FAIL; goto finish; } client = client_find(NULL, &ip, IPPROTO_IP); if (!client) { RDEBUG("No client found with IP \"%s\"", client_str); rcode = RLM_MODULE_NOTFOUND; goto finish; } if (client->cs) { char const *filename; int line; filename = cf_filename(client->cs); line = cf_lineno(client->cs); if (filename) { RDEBUG2("Found client matching \"%s\". Defined in \"%s\" line %i", client_str, filename, line); } else { RDEBUG2("Found client matching \"%s\"", client_str); } } } else { client = request->client; } uctx.cs = client->cs; RINDENT(); for (map = maps; map != NULL; map = map->next) { char *field = NULL; if (tmpl_aexpand(request, &field, request, map->rhs, NULL, NULL) < 0) { REDEBUG("Failed expanding RHS at %s", map->lhs->name); rcode = RLM_MODULE_FAIL; talloc_free(field); break; } uctx.cp = cf_pair_find(client->cs, field); if (!uctx.cp) { RDEBUG3("No matching client property \"%s\", skipping...", field); goto next; /* No matching CONF_PAIR found */ } uctx.field = field; /* * Pass the raw data to the callback, which will * create the VP and add it to the map. */ if (map_to_request(request, map, _map_proc_client_get_vp, &uctx) < 0) { rcode = RLM_MODULE_FAIL; talloc_free(field); break; } rcode = RLM_MODULE_UPDATED; next: talloc_free(field); } REXDENT(); finish: return rcode; }