/** Do any RADIUS-layer fixups for proxying. * */ static void radius_fixups(rlm_radius_t *inst, REQUEST *request) { VALUE_PAIR *vp; /* * Check for proxy loops. */ if (RDEBUG_ENABLED) { fr_cursor_t cursor; for (vp = fr_cursor_iter_by_da_init(&cursor, &request->packet->vps, attr_proxy_state); vp; vp = fr_cursor_next(&cursor)) { if (vp->vp_length != 4) continue; if (memcmp(&inst->proxy_state, vp->vp_octets, 4) == 0) { RWARN("Possible proxy loop - please check server configuration."); break; } } } if (request->packet->code != FR_CODE_ACCESS_REQUEST) return; if (fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY) && !fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY)) { MEM(pair_add_request(&vp, attr_chap_challenge) >= 0); fr_pair_value_memcpy(vp, request->packet->vector, sizeof(request->packet->vector)); } }
/** Convert value pairs to json objects * * Take the passed value pair and convert it to a json-c JSON object. * This code is heavily based on the vp_prints_value_json() function * from src/lib/print.c. * * @param request The request object. * @param vp The value pair to convert. * @return Returns a JSON object. */ json_object *mod_value_pair_to_json_object(REQUEST *request, VALUE_PAIR *vp) { char value[255]; /* radius attribute value */ /* add this attribute/value pair to our json output */ if (!vp->da->flags.has_tag) { unsigned int i; switch (vp->da->type) { case PW_TYPE_INTEGER: i = vp->vp_integer; goto print_int; case PW_TYPE_SHORT: i = vp->vp_short; goto print_int; case PW_TYPE_BYTE: i = vp->vp_byte; print_int: /* skip if we have flags */ if (vp->da->flags.has_value) break; #ifdef HAVE_JSON_OBJECT_NEW_INT64 /* debug */ RDEBUG3("creating new int64 for unsigned 32 bit int/byte/short '%s'", vp->da->name); /* return as 64 bit int - JSON spec does not support unsigned ints */ return json_object_new_int64(i); #else /* debug */ RDEBUG3("creating new int for unsigned 32 bit int/byte/short '%s'", vp->da->name); /* return as 64 bit int - JSON spec does not support unsigned ints */ return json_object_new_int(i); #endif break; case PW_TYPE_SIGNED: #ifdef HAVE_JSON_OBJECT_NEW_INT64 /* debug */ RDEBUG3("creating new int64 for signed 32 bit integer '%s'", vp->da->name); /* return as 64 bit int - json-c represents all ints as 64 bits internally */ return json_object_new_int64(vp->vp_signed); #else RDEBUG3("creating new int for signed 32 bit integer '%s'", vp->da->name); /* return as signed int */ return json_object_new_int(vp->vp_signed); #endif break; case PW_TYPE_INTEGER64: #ifdef HAVE_JSON_OBJECT_NEW_INT64 /* debug */ RDEBUG3("creating new int64 for 64 bit integer '%s'", vp->da->name); /* return as 64 bit int - because it is a 64 bit int */ return json_object_new_int64(vp->vp_integer64); #else /* warning */ RWARN("skipping 64 bit integer attribute '%s' - please upgrade json-c to 0.10+", vp->da->name); #endif break; default: /* silence warnings - do nothing */ break; } } /* keep going if not set above */ switch (vp->da->type) { case PW_TYPE_STRING: /* debug */ RDEBUG3("assigning string '%s' as string", vp->da->name); /* return string value */ return json_object_new_string(vp->vp_strvalue); default: /* debug */ RDEBUG3("assigning unhandled '%s' as string", vp->da->name); /* get standard value */ vp_prints_value(value, sizeof(value), vp, 0); /* return string value from above */ return json_object_new_string(value); } }
/* * Common code called by everything below. */ static rlm_rcode_t file_common(rlm_files_t *inst, REQUEST *request, char const *filename, fr_hash_table_t *ht, VALUE_PAIR *request_pairs, VALUE_PAIR **reply_pairs) { char const *name, *match; VALUE_PAIR *check_tmp; VALUE_PAIR *reply_tmp; PAIR_LIST const *user_pl, *default_pl; int found = 0; PAIR_LIST my_pl; char buffer[256]; if (!inst->key) { VALUE_PAIR *namepair; namepair = request->username; name = namepair ? namepair->vp_strvalue : "NONE"; } else { int len; len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } name = len ? buffer : "NONE"; } if (!ht) return RLM_MODULE_NOOP; my_pl.name = name; user_pl = fr_hash_table_finddata(ht, &my_pl); my_pl.name = "DEFAULT"; default_pl = fr_hash_table_finddata(ht, &my_pl); /* * Find the entry for the user. */ while (user_pl || default_pl) { vp_cursor_t cursor; VALUE_PAIR *vp; PAIR_LIST const *pl; if (!default_pl && user_pl) { pl = user_pl; match = name; user_pl = user_pl->next; } else if (!user_pl && default_pl) { pl = default_pl; match = "DEFAULT"; default_pl = default_pl->next; } else if (user_pl->order < default_pl->order) { pl = user_pl; match = name; user_pl = user_pl->next; } else { pl = default_pl; match = "DEFAULT"; default_pl = default_pl->next; } check_tmp = paircopy(request, pl->check); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (radius_xlat_do(request, vp) < 0) { RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror()); pairfree(&check_tmp); continue; } } if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) { RDEBUG2("%s: Matched entry %s at line %d", filename, match, pl->lineno); found = 1; /* ctx may be reply or proxy */ reply_tmp = paircopy(request, pl->reply); radius_xlat_move(request, reply_pairs, &reply_tmp); pairmove(request, &request->config_items, &check_tmp); /* Cleanup any unmoved valuepairs */ pairfree(&reply_tmp); pairfree(&check_tmp); /* * Fallthrough? */ if (!fallthrough(pl->reply)) break; } } /* * Remove server internal parameters. */ pairdelete(reply_pairs, PW_FALL_THROUGH, 0, TAG_ANY); /* * See if we succeeded. */ if (!found) return RLM_MODULE_NOOP; /* on to the next module */ return RLM_MODULE_OK; }
/** Check if a given user is already logged in. * * Process accounting data to determine if a user is already logged in. Sets request->simul_count * to the current session count for this user. * * Check twice. If on the first pass the user exceeds his maximum number of logins, do a second * pass and validate all logins by querying the terminal server. * * @param instance The module instance. * @param request The checksimul request object. * @return Returns operation status (@p rlm_rcode_t). */ static rlm_rcode_t mod_checksimul(void *instance, REQUEST *request) { rlm_couchbase_t *inst = instance; /* our module instance */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char vpath[256], vkey[MAX_KEY_SIZE]; /* view path and query key */ char docid[MAX_KEY_SIZE]; /* document id returned from view */ char error[512]; /* view error return */ int idx = 0; /* row array index counter */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ json_object *json, *jval; /* json object holders */ json_object *jrows = NULL; /* json object to hold view rows */ VALUE_PAIR *vp; /* value pair */ uint32_t client_ip_addr = 0; /* current client ip address */ char const *client_cs_id = NULL; /* current client calling station id */ char *user_name = NULL; /* user name from accounting document */ char *session_id = NULL; /* session id from accounting document */ char *cs_id = NULL; /* calling station id from accounting document */ uint32_t nas_addr = 0; /* nas address from accounting document */ uint32_t nas_port = 0; /* nas port from accounting document */ uint32_t framed_ip_addr = 0; /* framed ip address from accounting document */ char framed_proto = 0; /* framed proto from accounting document */ int session_time = 0; /* session time from accounting document */ /* do nothing if this is not enabled */ if (inst->check_simul != true) { RDEBUG3("mod_checksimul returning noop - not enabled"); return RLM_MODULE_NOOP; } /* ensure valid username in request */ if ((!request->username) || (request->username->vp_length == '\0')) { RDEBUG3("mod_checksimul - invalid username"); return RLM_MODULE_INVALID; } /* attempt to build view key */ if (radius_xlat(vkey, sizeof(vkey), request, inst->simul_vkey, NULL, NULL) < 0) { /* log error */ RERROR("could not find simultaneous use view key attribute (%s) in packet", inst->simul_vkey); /* return */ return RLM_MODULE_FAIL; } /* get handle */ handle = fr_connection_get(inst->pool); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* build view path */ snprintf(vpath, sizeof(vpath), "%s?key=\"%s\"&stale=update_after", inst->simul_view, vkey); /* query view for document */ cb_error = couchbase_query_view(cb_inst, cookie, vpath, NULL); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute view request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* check for error in json object */ if (json_object_object_get_ex(cookie->jobj, "error", &json)) { /* build initial error buffer */ strlcpy(error, json_object_get_string(json), sizeof(error)); /* get error reason */ if (json_object_object_get_ex(cookie->jobj, "reason", &json)) { /* append divider */ strlcat(error, " - ", sizeof(error)); /* append reason */ strlcat(error, json_object_get_string(json), sizeof(error)); } /* log error */ RERROR("view request failed with error: %s", error); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* check for document id in return */ if (!json_object_object_get_ex(cookie->jobj, "rows", &json)) { /* log error */ RERROR("failed to fetch rows from view payload"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* get and hold rows */ jrows = json_object_get(json); /* free cookie object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check for valid row value */ if (!jrows || !json_object_is_type(jrows, json_type_array)) { /* log error */ RERROR("no valid rows returned from view: %s", vpath); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("jrows == %s", json_object_to_json_string(jrows)); /* set the count */ request->simul_count = json_object_array_length(jrows); /* debugging */ RDEBUG("found %d open sessions for %s", request->simul_count, request->username->vp_strvalue); /* check count */ if (request->simul_count < request->simul_max) { rcode = RLM_MODULE_OK; goto free_and_return; } /* * Current session count exceeds configured maximum. * Continue on to verify the sessions if configured otherwise stop here. */ if (inst->verify_simul != true) { rcode = RLM_MODULE_OK; goto free_and_return; } /* debugging */ RDEBUG("verifying session count"); /* reset the count */ request->simul_count = 0; /* get client ip address for MPP detection below */ if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) { client_ip_addr = vp->vp_ipaddr; } /* get calling station id for MPP detection below */ if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) { client_cs_id = vp->vp_strvalue; } /* loop across all row elements */ for (idx = 0; idx < json_object_array_length(jrows); idx++) { /* clear docid */ memset(docid, 0, sizeof(docid)); /* fetch current index */ json = json_object_array_get_idx(jrows, idx); /* get document id */ if (json_object_object_get_ex(json, "id", &jval)) { /* copy and check length */ if (strlcpy(docid, json_object_get_string(jval), sizeof(docid)) >= sizeof(docid)) { RERROR("document id from row longer than MAX_KEY_SIZE (%d)", MAX_KEY_SIZE); continue; } } /* check for valid doc id */ if (docid[0] == 0) { RWARN("failed to fetch document id from row - skipping"); continue; } /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, docid); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto free_and_return; } /* debugging */ RDEBUG3("cookie->jobj == %s", json_object_to_json_string(cookie->jobj)); /* get element name for User-Name attribute */ if (mod_attribute_to_element("User-Name", inst->map, &element) == 0) { /* get and check username element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without username"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* copy json string value to user_name */ user_name = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for User-Name attribute"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* get element name for Acct-Session-Id attribute */ if (mod_attribute_to_element("Acct-Session-Id", inst->map, &element) == 0) { /* get and check session id element */ if (!json_object_object_get_ex(cookie->jobj, element, &jval)){ RDEBUG("cannot zap stale entry without session id"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* copy json string value to session_id */ session_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { RDEBUG("failed to find map entry for Acct-Session-Id attribute"); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* get element name for NAS-IP-Address attribute */ if (mod_attribute_to_element("NAS-IP-Address", inst->map, &element) == 0) { /* attempt to get and nas address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)){ nas_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for NAS-Port attribute */ if (mod_attribute_to_element("NAS-Port", inst->map, &element) == 0) { /* attempt to get nas port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { nas_port = (uint32_t) json_object_get_int(jval); } } /* check terminal server */ int check = rad_check_ts(nas_addr, nas_port, user_name, session_id); /* take action based on check return */ if (check == 0) { /* stale record - zap it if enabled */ if (inst->delete_stale_sessions) { /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } } /* get element name for Framed-Port attribute */ if (mod_attribute_to_element("Framed-Port", inst->map, &element) == 0) { /* attempt to get framed port element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { if (strcmp(json_object_get_string(jval), "PPP") == 0) { framed_proto = 'P'; } else if (strcmp(json_object_get_string(jval), "SLIP") == 0) { framed_proto = 'S'; } } } /* get element name for Acct-Session-Time attribute */ if (mod_attribute_to_element("Acct-Session-Time", inst->map, &element) == 0) { /* attempt to get session time element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { session_time = json_object_get_int(jval); } } /* zap session */ session_zap(request, nas_addr, nas_port, user_name, session_id, framed_ip_addr, framed_proto, session_time); } } else if (check == 1) { /* user is still logged in - increase count */ ++request->simul_count; /* get element name for Framed-IP-Address attribute */ if (mod_attribute_to_element("Framed-IP-Address", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { framed_ip_addr = inet_addr(json_object_get_string(jval)); } else { /* ensure 0 if not found */ framed_ip_addr = 0; } } /* get element name for Calling-Station-Id attribute */ if (mod_attribute_to_element("Calling-Station-Id", inst->map, &element) == 0) { /* attempt to get framed ip address element */ if (json_object_object_get_ex(cookie->jobj, element, &jval)) { /* copy json string value to cs_id */ cs_id = talloc_typed_strdup(request, json_object_get_string(jval)); } else { /* ensure null if not found */ cs_id = NULL; } } /* Does it look like a MPP attempt? */ if (client_ip_addr && framed_ip_addr && framed_ip_addr == client_ip_addr) { request->simul_mpp = 2; } else if (client_cs_id && cs_id && !strncmp(cs_id, client_cs_id, 16)) { request->simul_mpp = 2; } } else { /* check failed - return error */ REDEBUG("failed to check the terminal server for user '%s'", user_name); rcode = RLM_MODULE_FAIL; goto free_and_return; } /* free and reset document user name talloc */ if (user_name) { talloc_free(user_name); user_name = NULL; } /* free and reset document calling station id talloc */ if (cs_id) { talloc_free(cs_id); cs_id = NULL; } /* free and reset document session id talloc */ if (session_id) { talloc_free(session_id); session_id = NULL; } /* free and reset json object before fetching next row */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } } /* debugging */ RDEBUG("retained %d open sessions for %s after verification", request->simul_count, request->username->vp_strvalue); free_and_return: /* free document user name talloc */ if (user_name) { talloc_free(user_name); } /* free document calling station id talloc */ if (cs_id) { talloc_free(cs_id); } /* free document session id talloc */ if (session_id) { talloc_free(session_id); } /* free rows */ if (jrows) { json_object_put(jrows); } /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) { fr_connection_release(inst->pool, handle); } /* * The Auth module apparently looks at request->simul_count, * not the return value of this module when deciding to deny * a call for too many sessions. */ return rcode; }
/* * Preprocess a request before accounting */ static rlm_rcode_t CC_HINT(nonnull) mod_preaccounting(void *instance, REQUEST *request) { int r; VALUE_PAIR *vp; rlm_preprocess_t *inst = instance; /* * Ensure that we have the SAME user name for both * authentication && accounting. */ rad_mangle(inst, request); if (inst->with_cisco_vsa_hack) { /* * We need to run this hack because the h323-conf-id * attribute should be used. */ cisco_vsa_hack(request); } if (inst->with_alvarion_vsa_hack) { /* * We need to run this hack because the Alvarion * people are crazy. */ alvarion_vsa_hack(request->packet->vps); } if (inst->with_cablelabs_vsa_hack) { /* * We need to run this hack because the Cablelabs * people are crazy. */ cablelabs_vsa_hack(&request->packet->vps); } /* * Ensure that we log the NAS IP Address in the packet. */ if (add_nas_attr(request) < 0) { return RLM_MODULE_FAIL; } hints_setup(inst->hints, request); /* * Add an event timestamp. This means that the rest of * the server can use it, rather than various error-prone * manual calculations. */ vp = fr_pair_find_by_num(request->packet->vps, PW_EVENT_TIMESTAMP, 0, TAG_ANY); if (!vp) { VALUE_PAIR *delay; vp = radius_pair_create(request->packet, &request->packet->vps, PW_EVENT_TIMESTAMP, 0); vp->vp_date = request->packet->timestamp.tv_sec; delay = fr_pair_find_by_num(request->packet->vps, PW_ACCT_DELAY_TIME, 0, TAG_ANY); if (delay) { if ((delay->vp_integer >= vp->vp_date) || (delay->vp_integer == UINT32_MAX)) { RWARN("Ignoring invalid Acct-Delay-time of %u seconds", delay->vp_integer); } else { vp->vp_date -= delay->vp_integer; } } } if ((r = huntgroup_access(request, inst->huntgroups)) != RLM_MODULE_OK) { char buf[1024]; RIDEBUG("No huntgroup access: [%s] (%s)", request->username ? request->username->vp_strvalue : "<NO User-Name>", auth_name(buf, sizeof(buf), request, 1)); return r; } return r; }
/* * Common code called by everything below. */ static rlm_rcode_t file_common(rlm_files_t const *inst, REQUEST *request, char const *filename, rbtree_t *tree, RADIUS_PACKET *request_packet, RADIUS_PACKET *reply_packet) { char const *name; VALUE_PAIR *check_tmp = NULL; VALUE_PAIR *reply_tmp = NULL; PAIR_LIST const *user_pl, *default_pl; bool found = false; PAIR_LIST my_pl; char buffer[256]; if (!inst->key) { VALUE_PAIR *namepair; namepair = request->username; name = namepair ? namepair->vp_strvalue : "NONE"; } else { int len; len = xlat_eval(buffer, sizeof(buffer), request, inst->key, NULL, NULL); if (len < 0) { return RLM_MODULE_FAIL; } name = len ? buffer : "NONE"; } if (!tree) return RLM_MODULE_NOOP; my_pl.name = name; user_pl = rbtree_finddata(tree, &my_pl); my_pl.name = "DEFAULT"; default_pl = rbtree_finddata(tree, &my_pl); /* * Find the entry for the user. */ while (user_pl || default_pl) { fr_cursor_t cursor; VALUE_PAIR *vp; PAIR_LIST const *pl; /* * Figure out which entry to match on. */ if (!default_pl && user_pl) { pl = user_pl; user_pl = user_pl->next; } else if (!user_pl && default_pl) { pl = default_pl; default_pl = default_pl->next; } else if (user_pl->order < default_pl->order) { pl = user_pl; user_pl = user_pl->next; } else { pl = default_pl; default_pl = default_pl->next; } MEM(fr_pair_list_copy(request, &check_tmp, pl->check) >= 0); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (xlat_eval_pair(request, vp) < 0) { RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror()); fr_pair_list_free(&check_tmp); continue; } } if (paircmp(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) { RDEBUG2("Found match \"%s\" one line %d of %s", pl->name, pl->lineno, filename); found = true; /* ctx may be reply or proxy */ MEM(fr_pair_list_copy(reply_packet, &reply_tmp, pl->reply) >= 0); radius_pairmove(request, &reply_packet->vps, reply_tmp, true); fr_pair_list_move(&request->control, &check_tmp); reply_tmp = NULL; /* radius_pairmove() frees input attributes */ fr_pair_list_free(&check_tmp); /* * Fallthrough? */ if (!fall_through(pl->reply)) break; } } /* * Remove server internal parameters. */ fr_pair_delete_by_da(&reply_packet->vps, attr_fall_through); /* * See if we succeeded. */ if (!found) return RLM_MODULE_NOOP; /* on to the next module */ return RLM_MODULE_OK; }
/* * Send a response packet */ int dual_tls_send(rad_listen_t *listener, REQUEST *request) { listen_socket_t *sock = listener->data; VERIFY_REQUEST(request); rad_assert(request->listener == listener); rad_assert(listener->send == dual_tls_send); if (listener->status != RAD_LISTEN_STATUS_KNOWN) return 0; /* * Accounting reject's are silently dropped. * * We do it here to avoid polluting the rest of the * code with this knowledge */ if (request->reply->code == 0) return 0; /* * Pack the VPs */ if (rad_encode(request->reply, request->packet, request->client->secret) < 0) { RERROR("Failed encoding packet: %s", fr_strerror()); return 0; } if (request->reply->data_len > (MAX_PACKET_LEN - 100)) { RWARN("Packet is large, and possibly truncated - %zd vs max %d", request->reply->data_len, MAX_PACKET_LEN); } /* * Sign the packet. */ if (rad_sign(request->reply, request->packet, request->client->secret) < 0) { RERROR("Failed signing packet: %s", fr_strerror()); return 0; } PTHREAD_MUTEX_LOCK(&sock->mutex); /* * Write the packet to the SSL buffers. */ sock->ssn->record_plus(&sock->ssn->clean_in, request->reply->data, request->reply->data_len); dump_hex("TUNNELED DATA < ", sock->ssn->clean_in.data, sock->ssn->clean_in.used); /* * Do SSL magic to get encrypted data. */ tls_handshake_send(request, sock->ssn); /* * And finally write the data to the socket. */ if (sock->ssn->dirty_out.used > 0) { dump_hex("WRITE TO SSL", sock->ssn->dirty_out.data, sock->ssn->dirty_out.used); tls_socket_write(listener, request); } PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, sql_fall_through_t *do_fall_through) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request->packet != NULL); if (!inst->config->groupmemb_query) { RWARN("Cannot do check groups when group_membership_query is not set"); do_nothing: *do_fall_through = FALL_THROUGH_DEFAULT; /* * Didn't add group attributes or allocate * memory, so don't do anything else. */ return RLM_MODULE_NOTFOUND; } /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); goto do_nothing; } rad_assert(head); RDEBUG2("User found in the group table"); /* * Add the Sql-Group attribute to the request list so we know * which group we're retrieving attributes for */ sql_group = pair_make_request(inst->group_da->name, NULL, T_OP_EQ); if (!sql_group) { REDEBUG("Error creating %s attribute", inst->group_da->name); rcode = RLM_MODULE_FAIL; goto finish; } entry = head; do { next: rad_assert(entry != NULL); fr_pair_value_strcpy(sql_group, entry->name); if (inst->config->authorize_group_check_query) { vp_cursor_t cursor; VALUE_PAIR *vp; /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, inst->sql_escape_func, *handle) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to * process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { fr_pair_list_free(&check_tmp); entry = entry->next; if (!entry) break; goto next; /* != continue */ } RDEBUG2("Group \"%s\": Conditional check items matched", entry->name); rcode = RLM_MODULE_OK; RDEBUG2("Group \"%s\": Merging assignment check items", entry->name); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(L_DBG_LVL_2, request, vp, NULL); } REXDENT(); radius_pairmove(request, &request->config, check_tmp, true); check_tmp = NULL; } if (inst->config->authorize_group_reply_query) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query, inst->sql_escape_func, *handle) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *do_fall_through = fall_through(reply_tmp); RDEBUG2("Group \"%s\": Merging reply items", entry->name); rcode = RLM_MODULE_OK; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL); radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; /* * If there's no reply query configured, then we assume * FALL_THROUGH_NO, which is the same as the users file if you * had no reply attributes. */ } else { *do_fall_through = FALL_THROUGH_DEFAULT; } entry = entry->next; } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES)); finish: talloc_free(head); fr_pair_delete_by_num(&request->packet->vps, 0, inst->group_da->attr, TAG_ANY); return rcode; }
static int sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp, VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) { rlm_sql_handle_t *handle; rlm_sql_t *inst = instance; rlm_sql_grouplist_t *head, *entry; /* * No group queries, don't do group comparisons. */ if (!inst->config->groupmemb_query) { RWARN("Cannot do group comparison when group_membership_query is not set"); return 1; } RDEBUG("sql_groupcmp"); if (check->vp_length == 0){ RDEBUG("sql_groupcmp: Illegal group name"); return 1; } /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) return 1; /* * Get a socket for this lookup */ handle = fr_connection_get(inst->pool); if (!handle) { return 1; } /* * Get the list of groups this user is a member of */ if (sql_get_grouplist(inst, &handle, request, &head) < 0) { REDEBUG("Error getting group membership"); fr_connection_release(inst->pool, handle); return 1; } for (entry = head; entry != NULL; entry = entry->next) { if (strcmp(entry->name, check->vp_strvalue) == 0){ RDEBUG("sql_groupcmp finished: User is a member of group %s", check->vp_strvalue); talloc_free(head); fr_connection_release(inst->pool, handle); return 0; } } /* Free the grouplist */ talloc_free(head); fr_connection_release(inst->pool, handle); RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue); return 1; }
/** Extract attributes from an X509 certificate * * @param cursor to copy attributes to. * @param ctx to allocate attributes in. * @param session current TLS session. * @param cert to validate. * @param depth the certificate is in the certificate chain (0 == leaf). * @return * - 0 on success. * - < 0 on failure. */ int tls_session_pairs_from_x509_cert(fr_cursor_t *cursor, TALLOC_CTX *ctx, tls_session_t *session, X509 *cert, int depth) { char buffer[1024]; char attribute[256]; char **identity; int attr_index, loc; #if OPENSSL_VERSION_NUMBER >= 0x10100000L STACK_OF(X509_EXTENSION) const *ext_list = NULL; #else STACK_OF(X509_EXTENSION) *ext_list = NULL; #endif ASN1_INTEGER *sn = NULL; ASN1_TIME *asn_time = NULL; VALUE_PAIR *vp = NULL; REQUEST *request; #define CERT_ATTR_ADD(_attr, _attr_index, _value) tls_session_cert_attr_add(ctx, request, cursor, _attr, _attr_index, _value) attr_index = depth; if (attr_index > 1) attr_index = 1; request = (REQUEST *)SSL_get_ex_data(session->ssl, FR_TLS_EX_INDEX_REQUEST); rad_assert(request != NULL); identity = (char **)SSL_get_ex_data(session->ssl, FR_TLS_EX_INDEX_IDENTITY); if (RDEBUG_ENABLED3) { buffer[0] = '\0'; X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; RDEBUG3("Creating attributes for \"%s\":", buffer[0] ? buffer : "Cert missing subject OID"); } /* * Get the Serial Number */ sn = X509_get_serialNumber(cert); if (sn && ((size_t) sn->length < (sizeof(buffer) / 2))) { char *p = buffer; int i; for (i = 0; i < sn->length; i++) { sprintf(p, "%02x", (unsigned int)sn->data[i]); p += 2; } CERT_ATTR_ADD(IDX_SERIAL, attr_index, buffer); } /* * Get the Expiration Date */ buffer[0] = '\0'; asn_time = X509_get_notAfter(cert); if (identity && asn_time && (asn_time->length < (int)sizeof(buffer))) { time_t expires; /* * Add expiration as a time since the epoch */ if (tls_utils_asn1time_to_epoch(&expires, asn_time) < 0) { RPWDEBUG("Failed parsing certificate expiry time"); } else { vp = CERT_ATTR_ADD(IDX_EXPIRATION, attr_index, NULL); vp->vp_date = expires; } } /* * Get the Subject & Issuer */ buffer[0] = '\0'; X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (identity && buffer[0]) { CERT_ATTR_ADD(IDX_SUBJECT, attr_index, buffer); /* * Get the Common Name, if there is a subject. */ X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (buffer[0]) { CERT_ATTR_ADD(IDX_COMMON_NAME, attr_index, buffer); } } X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (identity && buffer[0]) { CERT_ATTR_ADD(IDX_ISSUER, attr_index, buffer); } /* * Get the RFC822 Subject Alternative Name */ loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0); if (loc >= 0) { X509_EXTENSION *ext = NULL; GENERAL_NAMES *names = NULL; int i; ext = X509_get_ext(cert, loc); if (ext && (names = X509V3_EXT_d2i(ext))) { for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); switch (name->type) { #ifdef GEN_EMAIL case GEN_EMAIL: { #if OPENSSL_VERSION_NUMBER >= 0x10100000L char const *rfc822Name = (char const *)ASN1_STRING_get0_data(name->d.rfc822Name); #else char *rfc822Name = (char *)ASN1_STRING_data(name->d.rfc822Name); #endif CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_EMAIL, attr_index, rfc822Name); break; } #endif /* GEN_EMAIL */ #ifdef GEN_DNS case GEN_DNS: { #if OPENSSL_VERSION_NUMBER >= 0x10100000L char const *dNSName = (char const *)ASN1_STRING_get0_data(name->d.dNSName); #else char *dNSName = (char *)ASN1_STRING_data(name->d.dNSName); #endif CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_DNS, attr_index, dNSName); break; } #endif /* GEN_DNS */ #ifdef GEN_OTHERNAME case GEN_OTHERNAME: /* look for a MS UPN */ if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break; /* we've got a UPN - Must be ASN1-encoded UTF8 string */ if (name->d.otherName->value->type == V_ASN1_UTF8STRING) { CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_UPN, attr_index, (char *)name->d.otherName->value->value.utf8string); break; } RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)"); break; #endif /* GEN_OTHERNAME */ default: /* XXX TODO handle other SAN types */ break; } } } if (names != NULL) GENERAL_NAMES_free(names); } /* * Only add extensions for the actual client certificate */ if (attr_index == 0) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ext_list = X509_get0_extensions(cert); #else ext_list = cert->cert_info->extensions; #endif /* * Grab the X509 extensions, and create attributes out of them. * For laziness, we re-use the OpenSSL names */ if (sk_X509_EXTENSION_num(ext_list) > 0) { int i, len; char *p; BIO *out; out = BIO_new(BIO_s_mem()); strlcpy(attribute, "TLS-Client-Cert-", sizeof(attribute)); for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) { char value[1024]; ASN1_OBJECT *obj; X509_EXTENSION *ext; fr_dict_attr_t const *da; ext = sk_X509_EXTENSION_value(ext_list, i); obj = X509_EXTENSION_get_object(ext); i2a_ASN1_OBJECT(out, obj); len = BIO_read(out, attribute + 16 , sizeof(attribute) - 16 - 1); if (len <= 0) continue; attribute[16 + len] = '\0'; for (p = attribute + 16; *p != '\0'; p++) if (*p == ' ') *p = '-'; X509V3_EXT_print(out, ext, 0, 0); len = BIO_read(out, value , sizeof(value) - 1); if (len <= 0) continue; value[len] = '\0'; da = fr_dict_attr_by_name(dict_freeradius, attribute); if (!da) { RWDEBUG3("Skipping attribute %s: " "Add dictionary definition if you want to access it", attribute); continue; } MEM(vp = fr_pair_afrom_da(request, da)); if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) { RPWDEBUG3("Skipping: %s += '%s'", attribute, value); talloc_free(vp); continue; } fr_cursor_append(cursor, vp); } BIO_free_all(out); } } return 0; }
static fr_io_final_t mod_process(UNUSED void const *instance, REQUEST *request, fr_io_action_t action) { VALUE_PAIR *vp; rlm_rcode_t rcode; CONF_SECTION *unlang; REQUEST_VERIFY(request); /* * Pass this through asynchronously to the module which * is waiting for something to happen. */ if (action != FR_IO_ACTION_RUN) { unlang_signal(request, (fr_state_signal_t) action); return FR_IO_DONE; } switch (request->request_state) { case REQUEST_INIT: RDEBUG("Received %s ID %i", fr_dict_enum_alias_by_value(attr_packet_type, fr_box_uint32(request->reply->code)), request->packet->id); log_request_pair_list(L_DBG_LVL_1, request, request->packet->vps, ""); request->component = "radius"; unlang = cf_section_find(request->server_cs, "recv", NULL); if (!unlang) { REDEBUG("Failed to find 'recv' section"); return FR_IO_FAIL; } RDEBUG("Running 'recv' from file %s", cf_filename(unlang)); unlang_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_RECV; /* FALL-THROUGH */ case REQUEST_RECV: rcode = unlang_interpret_continue(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { /* * The module has a number of OK return codes. */ case RLM_MODULE_OK: case RLM_MODULE_UPDATED: switch (request->packet->code) { case FR_CODE_ACCOUNTING_REQUEST: request->reply->code = FR_CODE_ACCOUNTING_RESPONSE; break; case FR_CODE_COA_REQUEST: request->reply->code = FR_CODE_COA_ACK; break; case FR_CODE_DISCONNECT_REQUEST: request->reply->code = FR_CODE_DISCONNECT_ACK; break; default: request->reply->code = 0; break; } break; case RLM_MODULE_HANDLED: break; /* * The module failed, or said the request is * invalid, therefore we stop here. */ case RLM_MODULE_NOOP: case RLM_MODULE_FAIL: case RLM_MODULE_INVALID: case RLM_MODULE_NOTFOUND: case RLM_MODULE_REJECT: case RLM_MODULE_USERLOCK: default: request->reply->code = 0; break; } /* * Allow for over-ride of reply code. */ vp = fr_pair_find_by_da(request->reply->vps, attr_packet_type, TAG_ANY); if (vp) request->reply->code = vp->vp_uint32; if (request->reply->code == FR_CODE_DO_NOT_RESPOND) { RWARN("Ignoring 'do_not_respond' as it does not apply to detail files"); } unlang = cf_section_find(request->server_cs, "send", "ok"); if (!unlang) goto send_reply; RDEBUG("Running 'send %s { ... }' from file %s", cf_section_name2(unlang), cf_filename(unlang)); unlang_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME); request->request_state = REQUEST_SEND; /* FALL-THROUGH */ case REQUEST_SEND: rcode = unlang_interpret_continue(request); if (request->master_state == REQUEST_STOP_PROCESSING) return FR_IO_DONE; if (rcode == RLM_MODULE_YIELD) return FR_IO_YIELD; rad_assert(request->log.unlang_indent == 0); switch (rcode) { case RLM_MODULE_NOOP: case RLM_MODULE_OK: case RLM_MODULE_UPDATED: case RLM_MODULE_HANDLED: /* reply is already set */ break; default: request->reply->code = 0; break; } send_reply: /* * Failed, but we still reply with a magic code, * so that the reader can retransmit. */ if (!request->reply->code) { REDEBUG("Failed ID %i", request->reply->id); } else { RDEBUG("Sent %s ID %i", fr_dict_enum_alias_by_value(attr_packet_type, fr_box_uint32(request->reply->code)), request->reply->id); } log_request_proto_pair_list(L_DBG_LVL_1, request, request->reply->vps, ""); break; default: return FR_IO_FAIL; } return FR_IO_REPLY; }
static unlang_action_t unlang_module(REQUEST *request, rlm_rcode_t *presult, int *priority) { unlang_module_t *sp; unlang_stack_t *stack = request->stack; unlang_stack_frame_t *frame = &stack->frame[stack->depth]; unlang_t *instruction = frame->instruction; unlang_frame_state_module_t *ms; int stack_depth = stack->depth; char const *caller; #ifndef NDEBUG int unlang_indent = request->log.unlang_indent; #endif /* * Process a stand-alone child, and fall through * to dealing with it's parent. */ sp = unlang_generic_to_module(instruction); rad_assert(sp); RDEBUG4("[%i] %s - %s (%s)", stack->depth, __FUNCTION__, sp->module_instance->name, sp->module_instance->module->name); /* * Return administratively configured return code */ if (sp->module_instance->force) { *presult = request->rcode = sp->module_instance->code; goto done; } frame->state = ms = talloc_zero(stack, unlang_frame_state_module_t); /* * Grab the thread/module specific data if any exists. */ ms->thread = module_thread_instance_find(sp->module_instance); rad_assert(ms->thread != NULL); /* * For logging unresponsive children. */ ms->thread->total_calls++; caller = request->module; request->module = sp->module_instance->name; safe_lock(sp->module_instance); /* Noop unless instance->mutex set */ *presult = sp->method(sp->module_instance->dl_inst->data, ms->thread->data, request); safe_unlock(sp->module_instance); request->module = caller; /* * Is now marked as "stop" when it wasn't before, we must have been blocked. */ if (request->master_state == REQUEST_STOP_PROCESSING) { RWARN("Module %s became unblocked", sp->module_instance->module->name); return UNLANG_ACTION_STOP_PROCESSING; } if (*presult == RLM_MODULE_YIELD) { ms->thread->active_callers++; goto done; } /* * Module execution finished, ident should be the same. */ rad_assert(unlang_indent == request->log.unlang_indent); rad_assert(*presult >= RLM_MODULE_REJECT); rad_assert(*presult < RLM_MODULE_NUMCODES); *priority = instruction->actions[*presult]; request->rcode = *presult; done: /* * Must be left at RDEBUG() level otherwise RDEBUG becomes pointless */ RDEBUG("%s (%s)", instruction->name ? instruction->name : "", fr_int2str(mod_rcode_table, *presult, "<invalid>")); switch (*presult) { case RLM_MODULE_YIELD: if (stack_depth < stack->depth) return UNLANG_ACTION_PUSHED_CHILD; rad_assert(stack_depth == stack->depth); return UNLANG_ACTION_YIELD; default: return UNLANG_ACTION_CALCULATE_RESULT; } }