| /* |
| * RADIUS client |
| * Copyright (c) 2002-2015, Jouni Malinen <j@w1.fi> |
| * |
| * This software may be distributed under the terms of the BSD license. |
| * See README for more details. |
| */ |
| |
| #include "includes.h" |
| #include <net/if.h> |
| |
| #include "common.h" |
| #include "radius.h" |
| #include "radius_client.h" |
| #include "eloop.h" |
| |
| /* Defaults for RADIUS retransmit values (exponential backoff) */ |
| |
| /** |
| * RADIUS_CLIENT_FIRST_WAIT - RADIUS client timeout for first retry in seconds |
| */ |
| #define RADIUS_CLIENT_FIRST_WAIT 3 |
| |
| /** |
| * RADIUS_CLIENT_MAX_WAIT - RADIUS client maximum retry timeout in seconds |
| */ |
| #define RADIUS_CLIENT_MAX_WAIT 120 |
| |
| /** |
| * RADIUS_CLIENT_MAX_FAILOVER - RADIUS client maximum retries |
| * |
| * Maximum number of server failovers before the entry is removed from |
| * retransmit list. |
| */ |
| #define RADIUS_CLIENT_MAX_FAILOVER 3 |
| |
| /** |
| * RADIUS_CLIENT_MAX_ENTRIES - RADIUS client maximum pending messages |
| * |
| * Maximum number of entries in retransmit list (oldest entries will be |
| * removed, if this limit is exceeded). |
| */ |
| #define RADIUS_CLIENT_MAX_ENTRIES 30 |
| |
| /** |
| * RADIUS_CLIENT_NUM_FAILOVER - RADIUS client failover point |
| * |
| * The number of failed retry attempts after which the RADIUS server will be |
| * changed (if one of more backup servers are configured). |
| */ |
| #define RADIUS_CLIENT_NUM_FAILOVER 4 |
| |
| |
| /** |
| * struct radius_rx_handler - RADIUS client RX handler |
| * |
| * This data structure is used internally inside the RADIUS client module to |
| * store registered RX handlers. These handlers are registered by calls to |
| * radius_client_register() and unregistered when the RADIUS client is |
| * deinitialized with a call to radius_client_deinit(). |
| */ |
| struct radius_rx_handler { |
| /** |
| * handler - Received RADIUS message handler |
| */ |
| RadiusRxResult (*handler)(struct radius_msg *msg, |
| struct radius_msg *req, |
| const u8 *shared_secret, |
| size_t shared_secret_len, |
| void *data); |
| |
| /** |
| * data - Context data for the handler |
| */ |
| void *data; |
| }; |
| |
| |
| /** |
| * struct radius_msg_list - RADIUS client message retransmit list |
| * |
| * This data structure is used internally inside the RADIUS client module to |
| * store pending RADIUS requests that may still need to be retransmitted. |
| */ |
| struct radius_msg_list { |
| /** |
| * addr - STA/client address |
| * |
| * This is used to find RADIUS messages for the same STA. |
| */ |
| u8 addr[ETH_ALEN]; |
| |
| /** |
| * msg - RADIUS message |
| */ |
| struct radius_msg *msg; |
| |
| /** |
| * msg_type - Message type |
| */ |
| RadiusType msg_type; |
| |
| /** |
| * first_try - Time of the first transmission attempt |
| */ |
| os_time_t first_try; |
| |
| /** |
| * next_try - Time for the next transmission attempt |
| */ |
| os_time_t next_try; |
| |
| /** |
| * attempts - Number of transmission attempts for one server |
| */ |
| int attempts; |
| |
| /** |
| * accu_attempts - Number of accumulated attempts |
| */ |
| int accu_attempts; |
| |
| /** |
| * next_wait - Next retransmission wait time in seconds |
| */ |
| int next_wait; |
| |
| /** |
| * last_attempt - Time of the last transmission attempt |
| */ |
| struct os_reltime last_attempt; |
| |
| /** |
| * shared_secret - Shared secret with the target RADIUS server |
| */ |
| const u8 *shared_secret; |
| |
| /** |
| * shared_secret_len - shared_secret length in octets |
| */ |
| size_t shared_secret_len; |
| |
| /* TODO: server config with failover to backup server(s) */ |
| |
| /** |
| * next - Next message in the list |
| */ |
| struct radius_msg_list *next; |
| }; |
| |
| |
| /** |
| * struct radius_client_data - Internal RADIUS client data |
| * |
| * This data structure is used internally inside the RADIUS client module. |
| * External users allocate this by calling radius_client_init() and free it by |
| * calling radius_client_deinit(). The pointer to this opaque data is used in |
| * calls to other functions as an identifier for the RADIUS client instance. |
| */ |
| struct radius_client_data { |
| /** |
| * ctx - Context pointer for hostapd_logger() callbacks |
| */ |
| void *ctx; |
| |
| /** |
| * conf - RADIUS client configuration (list of RADIUS servers to use) |
| */ |
| struct hostapd_radius_servers *conf; |
| |
| /** |
| * auth_serv_sock - IPv4 socket for RADIUS authentication messages |
| */ |
| int auth_serv_sock; |
| |
| /** |
| * acct_serv_sock - IPv4 socket for RADIUS accounting messages |
| */ |
| int acct_serv_sock; |
| |
| /** |
| * auth_serv_sock6 - IPv6 socket for RADIUS authentication messages |
| */ |
| int auth_serv_sock6; |
| |
| /** |
| * acct_serv_sock6 - IPv6 socket for RADIUS accounting messages |
| */ |
| int acct_serv_sock6; |
| |
| /** |
| * auth_sock - Currently used socket for RADIUS authentication server |
| */ |
| int auth_sock; |
| |
| /** |
| * acct_sock - Currently used socket for RADIUS accounting server |
| */ |
| int acct_sock; |
| |
| /** |
| * auth_handlers - Authentication message handlers |
| */ |
| struct radius_rx_handler *auth_handlers; |
| |
| /** |
| * num_auth_handlers - Number of handlers in auth_handlers |
| */ |
| size_t num_auth_handlers; |
| |
| /** |
| * acct_handlers - Accounting message handlers |
| */ |
| struct radius_rx_handler *acct_handlers; |
| |
| /** |
| * num_acct_handlers - Number of handlers in acct_handlers |
| */ |
| size_t num_acct_handlers; |
| |
| /** |
| * msgs - Pending outgoing RADIUS messages |
| */ |
| struct radius_msg_list *msgs; |
| |
| /** |
| * num_msgs - Number of pending messages in the msgs list |
| */ |
| size_t num_msgs; |
| |
| /** |
| * next_radius_identifier - Next RADIUS message identifier to use |
| */ |
| u8 next_radius_identifier; |
| |
| /** |
| * interim_error_cb - Interim accounting error callback |
| */ |
| void (*interim_error_cb)(const u8 *addr, void *ctx); |
| |
| /** |
| * interim_error_cb_ctx - interim_error_cb() context data |
| */ |
| void *interim_error_cb_ctx; |
| }; |
| |
| |
| static int |
| radius_change_server(struct radius_client_data *radius, |
| struct hostapd_radius_server *nserv, |
| struct hostapd_radius_server *oserv, |
| int sock, int sock6, int auth); |
| static int radius_client_init_acct(struct radius_client_data *radius); |
| static int radius_client_init_auth(struct radius_client_data *radius); |
| static void radius_client_auth_failover(struct radius_client_data *radius); |
| static void radius_client_acct_failover(struct radius_client_data *radius); |
| |
| |
| static void radius_client_msg_free(struct radius_msg_list *req) |
| { |
| radius_msg_free(req->msg); |
| os_free(req); |
| } |
| |
| |
| /** |
| * radius_client_register - Register a RADIUS client RX handler |
| * @radius: RADIUS client context from radius_client_init() |
| * @msg_type: RADIUS client type (RADIUS_AUTH or RADIUS_ACCT) |
| * @handler: Handler for received RADIUS messages |
| * @data: Context pointer for handler callbacks |
| * Returns: 0 on success, -1 on failure |
| * |
| * This function is used to register a handler for processing received RADIUS |
| * authentication and accounting messages. The handler() callback function will |
| * be called whenever a RADIUS message is received from the active server. |
| * |
| * There can be multiple registered RADIUS message handlers. The handlers will |
| * be called in order until one of them indicates that it has processed or |
| * queued the message. |
| */ |
| int radius_client_register(struct radius_client_data *radius, |
| RadiusType msg_type, |
| RadiusRxResult (*handler)(struct radius_msg *msg, |
| struct radius_msg *req, |
| const u8 *shared_secret, |
| size_t shared_secret_len, |
| void *data), |
| void *data) |
| { |
| struct radius_rx_handler **handlers, *newh; |
| size_t *num; |
| |
| if (msg_type == RADIUS_ACCT) { |
| handlers = &radius->acct_handlers; |
| num = &radius->num_acct_handlers; |
| } else { |
| handlers = &radius->auth_handlers; |
| num = &radius->num_auth_handlers; |
| } |
| |
| newh = os_realloc_array(*handlers, *num + 1, |
| sizeof(struct radius_rx_handler)); |
| if (newh == NULL) |
| return -1; |
| |
| newh[*num].handler = handler; |
| newh[*num].data = data; |
| (*num)++; |
| *handlers = newh; |
| |
| return 0; |
| } |
| |
| |
| /** |
| * radius_client_set_interim_erro_cb - Register an interim acct error callback |
| * @radius: RADIUS client context from radius_client_init() |
| * @addr: Station address from the failed message |
| * @cb: Handler for interim accounting errors |
| * @ctx: Context pointer for handler callbacks |
| * |
| * This function is used to register a handler for processing failed |
| * transmission attempts of interim accounting update messages. |
| */ |
| void radius_client_set_interim_error_cb(struct radius_client_data *radius, |
| void (*cb)(const u8 *addr, void *ctx), |
| void *ctx) |
| { |
| radius->interim_error_cb = cb; |
| radius->interim_error_cb_ctx = ctx; |
| } |
| |
| |
| /* |
| * Returns >0 if message queue was flushed (i.e., the message that triggered |
| * the error is not available anymore) |
| */ |
| static int radius_client_handle_send_error(struct radius_client_data *radius, |
| int s, RadiusType msg_type) |
| { |
| #ifndef CONFIG_NATIVE_WINDOWS |
| int _errno = errno; |
| wpa_printf(MSG_INFO, "send[RADIUS,s=%d]: %s", s, strerror(errno)); |
| if (_errno == ENOTCONN || _errno == EDESTADDRREQ || _errno == EINVAL || |
| _errno == EBADF || _errno == ENETUNREACH || _errno == EACCES) { |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_INFO, |
| "Send failed - maybe interface status changed -" |
| " try to connect again"); |
| if (msg_type == RADIUS_ACCT || |
| msg_type == RADIUS_ACCT_INTERIM) { |
| radius_client_init_acct(radius); |
| return 0; |
| } else { |
| radius_client_init_auth(radius); |
| return 1; |
| } |
| } |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| return 0; |
| } |
| |
| |
| static int radius_client_retransmit(struct radius_client_data *radius, |
| struct radius_msg_list *entry, |
| os_time_t now) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| int s; |
| struct wpabuf *buf; |
| size_t prev_num_msgs; |
| u8 *acct_delay_time; |
| size_t acct_delay_time_len; |
| int num_servers; |
| |
| if (entry->msg_type == RADIUS_ACCT || |
| entry->msg_type == RADIUS_ACCT_INTERIM) { |
| num_servers = conf->num_acct_servers; |
| if (radius->acct_sock < 0) |
| radius_client_init_acct(radius); |
| if (radius->acct_sock < 0 && conf->num_acct_servers > 1) { |
| prev_num_msgs = radius->num_msgs; |
| radius_client_acct_failover(radius); |
| if (prev_num_msgs != radius->num_msgs) |
| return 0; |
| } |
| s = radius->acct_sock; |
| if (entry->attempts == 0) |
| conf->acct_server->requests++; |
| else { |
| conf->acct_server->timeouts++; |
| conf->acct_server->retransmissions++; |
| } |
| } else { |
| num_servers = conf->num_auth_servers; |
| if (radius->auth_sock < 0) |
| radius_client_init_auth(radius); |
| if (radius->auth_sock < 0 && conf->num_auth_servers > 1) { |
| prev_num_msgs = radius->num_msgs; |
| radius_client_auth_failover(radius); |
| if (prev_num_msgs != radius->num_msgs) |
| return 0; |
| } |
| s = radius->auth_sock; |
| if (entry->attempts == 0) |
| conf->auth_server->requests++; |
| else { |
| conf->auth_server->timeouts++; |
| conf->auth_server->retransmissions++; |
| } |
| } |
| |
| if (entry->msg_type == RADIUS_ACCT_INTERIM) { |
| wpa_printf(MSG_DEBUG, |
| "RADIUS: Failed to transmit interim accounting update to " |
| MACSTR " - drop message and request a new update", |
| MAC2STR(entry->addr)); |
| if (radius->interim_error_cb) |
| radius->interim_error_cb(entry->addr, |
| radius->interim_error_cb_ctx); |
| return 1; |
| } |
| |
| if (s < 0) { |
| wpa_printf(MSG_INFO, |
| "RADIUS: No valid socket for retransmission"); |
| return 1; |
| } |
| |
| if (entry->msg_type == RADIUS_ACCT && |
| radius_msg_get_attr_ptr(entry->msg, RADIUS_ATTR_ACCT_DELAY_TIME, |
| &acct_delay_time, &acct_delay_time_len, |
| NULL) == 0 && |
| acct_delay_time_len == 4) { |
| struct radius_hdr *hdr; |
| u32 delay_time; |
| |
| /* |
| * Need to assign a new identifier since attribute contents |
| * changes. |
| */ |
| hdr = radius_msg_get_hdr(entry->msg); |
| hdr->identifier = radius_client_get_id(radius); |
| |
| /* Update Acct-Delay-Time to show wait time in queue */ |
| delay_time = now - entry->first_try; |
| WPA_PUT_BE32(acct_delay_time, delay_time); |
| |
| wpa_printf(MSG_DEBUG, |
| "RADIUS: Updated Acct-Delay-Time to %u for retransmission", |
| delay_time); |
| radius_msg_finish_acct(entry->msg, entry->shared_secret, |
| entry->shared_secret_len); |
| if (radius->conf->msg_dumps) |
| radius_msg_dump(entry->msg); |
| } |
| |
| /* retransmit; remove entry if too many attempts */ |
| if (entry->accu_attempts >= RADIUS_CLIENT_MAX_FAILOVER * |
| RADIUS_CLIENT_NUM_FAILOVER * num_servers) { |
| wpa_printf(MSG_INFO, |
| "RADIUS: Removing un-ACKed message due to too many failed retransmit attempts"); |
| return 1; |
| } |
| |
| entry->attempts++; |
| entry->accu_attempts++; |
| hostapd_logger(radius->ctx, entry->addr, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Resending RADIUS message (id=%d)", |
| radius_msg_get_hdr(entry->msg)->identifier); |
| |
| os_get_reltime(&entry->last_attempt); |
| buf = radius_msg_get_buf(entry->msg); |
| if (send(s, wpabuf_head(buf), wpabuf_len(buf), 0) < 0) { |
| if (radius_client_handle_send_error(radius, s, entry->msg_type) |
| > 0) |
| return 0; |
| } |
| |
| entry->next_try = now + entry->next_wait; |
| entry->next_wait *= 2; |
| if (entry->next_wait > RADIUS_CLIENT_MAX_WAIT) |
| entry->next_wait = RADIUS_CLIENT_MAX_WAIT; |
| |
| return 0; |
| } |
| |
| |
| static void radius_client_timer(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct radius_client_data *radius = eloop_ctx; |
| struct os_reltime now; |
| os_time_t first; |
| struct radius_msg_list *entry, *prev, *tmp; |
| int auth_failover = 0, acct_failover = 0; |
| size_t prev_num_msgs; |
| int s; |
| |
| entry = radius->msgs; |
| if (!entry) |
| return; |
| |
| os_get_reltime(&now); |
| |
| while (entry) { |
| if (now.sec >= entry->next_try) { |
| s = entry->msg_type == RADIUS_AUTH ? radius->auth_sock : |
| radius->acct_sock; |
| if (entry->attempts >= RADIUS_CLIENT_NUM_FAILOVER || |
| (s < 0 && entry->attempts > 0)) { |
| if (entry->msg_type == RADIUS_ACCT || |
| entry->msg_type == RADIUS_ACCT_INTERIM) |
| acct_failover++; |
| else |
| auth_failover++; |
| } |
| } |
| entry = entry->next; |
| } |
| |
| if (auth_failover) |
| radius_client_auth_failover(radius); |
| |
| if (acct_failover) |
| radius_client_acct_failover(radius); |
| |
| entry = radius->msgs; |
| first = 0; |
| |
| prev = NULL; |
| while (entry) { |
| prev_num_msgs = radius->num_msgs; |
| if (now.sec >= entry->next_try && |
| radius_client_retransmit(radius, entry, now.sec)) { |
| if (prev) |
| prev->next = entry->next; |
| else |
| radius->msgs = entry->next; |
| |
| tmp = entry; |
| entry = entry->next; |
| radius_client_msg_free(tmp); |
| radius->num_msgs--; |
| continue; |
| } |
| |
| if (prev_num_msgs != radius->num_msgs) { |
| wpa_printf(MSG_DEBUG, |
| "RADIUS: Message removed from queue - restart from beginning"); |
| entry = radius->msgs; |
| prev = NULL; |
| continue; |
| } |
| |
| if (first == 0 || entry->next_try < first) |
| first = entry->next_try; |
| |
| prev = entry; |
| entry = entry->next; |
| } |
| |
| if (radius->msgs) { |
| if (first < now.sec) |
| first = now.sec; |
| eloop_cancel_timeout(radius_client_timer, radius, NULL); |
| eloop_register_timeout(first - now.sec, 0, |
| radius_client_timer, radius, NULL); |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Next RADIUS client " |
| "retransmit in %ld seconds", |
| (long int) (first - now.sec)); |
| } |
| } |
| |
| |
| static void radius_client_auth_failover(struct radius_client_data *radius) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| struct hostapd_radius_server *next, *old; |
| struct radius_msg_list *entry; |
| char abuf[50]; |
| |
| old = conf->auth_server; |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_NOTICE, |
| "No response from Authentication server %s:%d - failover", |
| hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)), |
| old->port); |
| |
| for (entry = radius->msgs; entry; entry = entry->next) { |
| if (entry->msg_type == RADIUS_AUTH) |
| old->timeouts++; |
| } |
| |
| next = old + 1; |
| if (next > &(conf->auth_servers[conf->num_auth_servers - 1])) |
| next = conf->auth_servers; |
| conf->auth_server = next; |
| radius_change_server(radius, next, old, |
| radius->auth_serv_sock, |
| radius->auth_serv_sock6, 1); |
| } |
| |
| |
| static void radius_client_acct_failover(struct radius_client_data *radius) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| struct hostapd_radius_server *next, *old; |
| struct radius_msg_list *entry; |
| char abuf[50]; |
| |
| old = conf->acct_server; |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_NOTICE, |
| "No response from Accounting server %s:%d - failover", |
| hostapd_ip_txt(&old->addr, abuf, sizeof(abuf)), |
| old->port); |
| |
| for (entry = radius->msgs; entry; entry = entry->next) { |
| if (entry->msg_type == RADIUS_ACCT || |
| entry->msg_type == RADIUS_ACCT_INTERIM) |
| old->timeouts++; |
| } |
| |
| next = old + 1; |
| if (next > &conf->acct_servers[conf->num_acct_servers - 1]) |
| next = conf->acct_servers; |
| conf->acct_server = next; |
| radius_change_server(radius, next, old, |
| radius->acct_serv_sock, |
| radius->acct_serv_sock6, 0); |
| } |
| |
| |
| static void radius_client_update_timeout(struct radius_client_data *radius) |
| { |
| struct os_reltime now; |
| os_time_t first; |
| struct radius_msg_list *entry; |
| |
| eloop_cancel_timeout(radius_client_timer, radius, NULL); |
| |
| if (radius->msgs == NULL) { |
| return; |
| } |
| |
| first = 0; |
| for (entry = radius->msgs; entry; entry = entry->next) { |
| if (first == 0 || entry->next_try < first) |
| first = entry->next_try; |
| } |
| |
| os_get_reltime(&now); |
| if (first < now.sec) |
| first = now.sec; |
| eloop_register_timeout(first - now.sec, 0, radius_client_timer, radius, |
| NULL); |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Next RADIUS client retransmit in" |
| " %ld seconds", (long int) (first - now.sec)); |
| } |
| |
| |
| static void radius_client_list_add(struct radius_client_data *radius, |
| struct radius_msg *msg, |
| RadiusType msg_type, |
| const u8 *shared_secret, |
| size_t shared_secret_len, const u8 *addr) |
| { |
| struct radius_msg_list *entry, *prev; |
| |
| if (eloop_terminated()) { |
| /* No point in adding entries to retransmit queue since event |
| * loop has already been terminated. */ |
| radius_msg_free(msg); |
| return; |
| } |
| |
| entry = os_zalloc(sizeof(*entry)); |
| if (entry == NULL) { |
| wpa_printf(MSG_INFO, "RADIUS: Failed to add packet into retransmit list"); |
| radius_msg_free(msg); |
| return; |
| } |
| |
| if (addr) |
| os_memcpy(entry->addr, addr, ETH_ALEN); |
| entry->msg = msg; |
| entry->msg_type = msg_type; |
| entry->shared_secret = shared_secret; |
| entry->shared_secret_len = shared_secret_len; |
| os_get_reltime(&entry->last_attempt); |
| entry->first_try = entry->last_attempt.sec; |
| entry->next_try = entry->first_try + RADIUS_CLIENT_FIRST_WAIT; |
| entry->attempts = 1; |
| entry->accu_attempts = 1; |
| entry->next_wait = RADIUS_CLIENT_FIRST_WAIT * 2; |
| if (entry->next_wait > RADIUS_CLIENT_MAX_WAIT) |
| entry->next_wait = RADIUS_CLIENT_MAX_WAIT; |
| entry->next = radius->msgs; |
| radius->msgs = entry; |
| radius_client_update_timeout(radius); |
| |
| if (radius->num_msgs >= RADIUS_CLIENT_MAX_ENTRIES) { |
| wpa_printf(MSG_INFO, "RADIUS: Removing the oldest un-ACKed packet due to retransmit list limits"); |
| prev = NULL; |
| while (entry->next) { |
| prev = entry; |
| entry = entry->next; |
| } |
| if (prev) { |
| prev->next = NULL; |
| radius_client_msg_free(entry); |
| } |
| } else |
| radius->num_msgs++; |
| } |
| |
| |
| /** |
| * radius_client_send - Send a RADIUS request |
| * @radius: RADIUS client context from radius_client_init() |
| * @msg: RADIUS message to be sent |
| * @msg_type: Message type (RADIUS_AUTH, RADIUS_ACCT, RADIUS_ACCT_INTERIM) |
| * @addr: MAC address of the device related to this message or %NULL |
| * Returns: 0 on success, -1 on failure |
| * |
| * This function is used to transmit a RADIUS authentication (RADIUS_AUTH) or |
| * accounting request (RADIUS_ACCT or RADIUS_ACCT_INTERIM). The only difference |
| * between accounting and interim accounting messages is that the interim |
| * message will not be retransmitted. Instead, a callback is used to indicate |
| * that the transmission failed for the specific station @addr so that a new |
| * interim accounting update message can be generated with up-to-date session |
| * data instead of trying to resend old information. |
| * |
| * The message is added on the retransmission queue and will be retransmitted |
| * automatically until a response is received or maximum number of retries |
| * (RADIUS_CLIENT_MAX_FAILOVER * RADIUS_CLIENT_NUM_FAILOVER) is reached. No |
| * such retries are used with RADIUS_ACCT_INTERIM, i.e., such a pending message |
| * is removed from the queue automatically on transmission failure. |
| * |
| * The related device MAC address can be used to identify pending messages that |
| * can be removed with radius_client_flush_auth(). |
| */ |
| int radius_client_send(struct radius_client_data *radius, |
| struct radius_msg *msg, RadiusType msg_type, |
| const u8 *addr) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| const u8 *shared_secret; |
| size_t shared_secret_len; |
| char *name; |
| int s, res; |
| struct wpabuf *buf; |
| |
| if (msg_type == RADIUS_ACCT || msg_type == RADIUS_ACCT_INTERIM) { |
| if (conf->acct_server && radius->acct_sock < 0) |
| radius_client_init_acct(radius); |
| |
| if (conf->acct_server == NULL || radius->acct_sock < 0 || |
| conf->acct_server->shared_secret == NULL) { |
| hostapd_logger(radius->ctx, NULL, |
| HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_INFO, |
| "No accounting server configured"); |
| return -1; |
| } |
| shared_secret = conf->acct_server->shared_secret; |
| shared_secret_len = conf->acct_server->shared_secret_len; |
| radius_msg_finish_acct(msg, shared_secret, shared_secret_len); |
| name = "accounting"; |
| s = radius->acct_sock; |
| conf->acct_server->requests++; |
| } else { |
| if (conf->auth_server && radius->auth_sock < 0) |
| radius_client_init_auth(radius); |
| |
| if (conf->auth_server == NULL || radius->auth_sock < 0 || |
| conf->auth_server->shared_secret == NULL) { |
| hostapd_logger(radius->ctx, NULL, |
| HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_INFO, |
| "No authentication server configured"); |
| return -1; |
| } |
| shared_secret = conf->auth_server->shared_secret; |
| shared_secret_len = conf->auth_server->shared_secret_len; |
| radius_msg_finish(msg, shared_secret, shared_secret_len); |
| name = "authentication"; |
| s = radius->auth_sock; |
| conf->auth_server->requests++; |
| } |
| |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Sending RADIUS message to %s " |
| "server", name); |
| if (conf->msg_dumps) |
| radius_msg_dump(msg); |
| |
| buf = radius_msg_get_buf(msg); |
| res = send(s, wpabuf_head(buf), wpabuf_len(buf), 0); |
| if (res < 0) |
| radius_client_handle_send_error(radius, s, msg_type); |
| |
| radius_client_list_add(radius, msg, msg_type, shared_secret, |
| shared_secret_len, addr); |
| |
| return 0; |
| } |
| |
| |
| static void radius_client_receive(int sock, void *eloop_ctx, void *sock_ctx) |
| { |
| struct radius_client_data *radius = eloop_ctx; |
| struct hostapd_radius_servers *conf = radius->conf; |
| RadiusType msg_type = (uintptr_t) sock_ctx; |
| int len, roundtrip; |
| unsigned char buf[RADIUS_MAX_MSG_LEN]; |
| struct msghdr msghdr = {0}; |
| struct iovec iov; |
| struct radius_msg *msg; |
| struct radius_hdr *hdr; |
| struct radius_rx_handler *handlers; |
| size_t num_handlers, i; |
| struct radius_msg_list *req, *prev_req; |
| struct os_reltime now; |
| struct hostapd_radius_server *rconf; |
| int invalid_authenticator = 0; |
| |
| if (msg_type == RADIUS_ACCT) { |
| handlers = radius->acct_handlers; |
| num_handlers = radius->num_acct_handlers; |
| rconf = conf->acct_server; |
| } else { |
| handlers = radius->auth_handlers; |
| num_handlers = radius->num_auth_handlers; |
| rconf = conf->auth_server; |
| } |
| |
| iov.iov_base = buf; |
| iov.iov_len = RADIUS_MAX_MSG_LEN; |
| msghdr.msg_iov = &iov; |
| msghdr.msg_iovlen = 1; |
| msghdr.msg_flags = 0; |
| len = recvmsg(sock, &msghdr, MSG_DONTWAIT); |
| if (len < 0) { |
| wpa_printf(MSG_INFO, "recvmsg[RADIUS]: %s", strerror(errno)); |
| return; |
| } |
| |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Received %d bytes from RADIUS " |
| "server", len); |
| |
| if (msghdr.msg_flags & MSG_TRUNC) { |
| wpa_printf(MSG_INFO, "RADIUS: Possibly too long UDP frame for our buffer - dropping it"); |
| return; |
| } |
| |
| msg = radius_msg_parse(buf, len); |
| if (msg == NULL) { |
| wpa_printf(MSG_INFO, "RADIUS: Parsing incoming frame failed"); |
| rconf->malformed_responses++; |
| return; |
| } |
| hdr = radius_msg_get_hdr(msg); |
| |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "Received RADIUS message"); |
| if (conf->msg_dumps) |
| radius_msg_dump(msg); |
| |
| switch (hdr->code) { |
| case RADIUS_CODE_ACCESS_ACCEPT: |
| rconf->access_accepts++; |
| break; |
| case RADIUS_CODE_ACCESS_REJECT: |
| rconf->access_rejects++; |
| break; |
| case RADIUS_CODE_ACCESS_CHALLENGE: |
| rconf->access_challenges++; |
| break; |
| case RADIUS_CODE_ACCOUNTING_RESPONSE: |
| rconf->responses++; |
| break; |
| } |
| |
| prev_req = NULL; |
| req = radius->msgs; |
| while (req) { |
| /* TODO: also match by src addr:port of the packet when using |
| * alternative RADIUS servers (?) */ |
| if ((req->msg_type == msg_type || |
| (req->msg_type == RADIUS_ACCT_INTERIM && |
| msg_type == RADIUS_ACCT)) && |
| radius_msg_get_hdr(req->msg)->identifier == |
| hdr->identifier) |
| break; |
| |
| prev_req = req; |
| req = req->next; |
| } |
| |
| if (req == NULL) { |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, |
| "No matching RADIUS request found (type=%d " |
| "id=%d) - dropping packet", |
| msg_type, hdr->identifier); |
| goto fail; |
| } |
| |
| os_get_reltime(&now); |
| roundtrip = (now.sec - req->last_attempt.sec) * 100 + |
| (now.usec - req->last_attempt.usec) / 10000; |
| hostapd_logger(radius->ctx, req->addr, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, |
| "Received RADIUS packet matched with a pending " |
| "request, round trip time %d.%02d sec", |
| roundtrip / 100, roundtrip % 100); |
| rconf->round_trip_time = roundtrip; |
| |
| /* Remove ACKed RADIUS packet from retransmit list */ |
| if (prev_req) |
| prev_req->next = req->next; |
| else |
| radius->msgs = req->next; |
| radius->num_msgs--; |
| |
| for (i = 0; i < num_handlers; i++) { |
| RadiusRxResult res; |
| res = handlers[i].handler(msg, req->msg, req->shared_secret, |
| req->shared_secret_len, |
| handlers[i].data); |
| switch (res) { |
| case RADIUS_RX_PROCESSED: |
| radius_msg_free(msg); |
| /* fall through */ |
| case RADIUS_RX_QUEUED: |
| radius_client_msg_free(req); |
| return; |
| case RADIUS_RX_INVALID_AUTHENTICATOR: |
| invalid_authenticator++; |
| /* fall through */ |
| case RADIUS_RX_UNKNOWN: |
| /* continue with next handler */ |
| break; |
| } |
| } |
| |
| if (invalid_authenticator) |
| rconf->bad_authenticators++; |
| else |
| rconf->unknown_types++; |
| hostapd_logger(radius->ctx, req->addr, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, "No RADIUS RX handler found " |
| "(type=%d code=%d id=%d)%s - dropping packet", |
| msg_type, hdr->code, hdr->identifier, |
| invalid_authenticator ? " [INVALID AUTHENTICATOR]" : |
| ""); |
| radius_client_msg_free(req); |
| |
| fail: |
| radius_msg_free(msg); |
| } |
| |
| |
| /** |
| * radius_client_get_id - Get an identifier for a new RADIUS message |
| * @radius: RADIUS client context from radius_client_init() |
| * Returns: Allocated identifier |
| * |
| * This function is used to fetch a unique (among pending requests) identifier |
| * for a new RADIUS message. |
| */ |
| u8 radius_client_get_id(struct radius_client_data *radius) |
| { |
| struct radius_msg_list *entry, *prev, *_remove; |
| u8 id = radius->next_radius_identifier++; |
| |
| /* remove entries with matching id from retransmit list to avoid |
| * using new reply from the RADIUS server with an old request */ |
| entry = radius->msgs; |
| prev = NULL; |
| while (entry) { |
| if (radius_msg_get_hdr(entry->msg)->identifier == id) { |
| hostapd_logger(radius->ctx, entry->addr, |
| HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, |
| "Removing pending RADIUS message, " |
| "since its id (%d) is reused", id); |
| if (prev) |
| prev->next = entry->next; |
| else |
| radius->msgs = entry->next; |
| _remove = entry; |
| } else { |
| _remove = NULL; |
| prev = entry; |
| } |
| entry = entry->next; |
| |
| if (_remove) |
| radius_client_msg_free(_remove); |
| } |
| |
| return id; |
| } |
| |
| |
| /** |
| * radius_client_flush - Flush all pending RADIUS client messages |
| * @radius: RADIUS client context from radius_client_init() |
| * @only_auth: Whether only authentication messages are removed |
| */ |
| void radius_client_flush(struct radius_client_data *radius, int only_auth) |
| { |
| struct radius_msg_list *entry, *prev, *tmp; |
| |
| if (!radius) |
| return; |
| |
| prev = NULL; |
| entry = radius->msgs; |
| |
| while (entry) { |
| if (!only_auth || entry->msg_type == RADIUS_AUTH) { |
| if (prev) |
| prev->next = entry->next; |
| else |
| radius->msgs = entry->next; |
| |
| tmp = entry; |
| entry = entry->next; |
| radius_client_msg_free(tmp); |
| radius->num_msgs--; |
| } else { |
| prev = entry; |
| entry = entry->next; |
| } |
| } |
| |
| if (radius->msgs == NULL) |
| eloop_cancel_timeout(radius_client_timer, radius, NULL); |
| } |
| |
| |
| static void radius_client_update_acct_msgs(struct radius_client_data *radius, |
| const u8 *shared_secret, |
| size_t shared_secret_len) |
| { |
| struct radius_msg_list *entry; |
| |
| if (!radius) |
| return; |
| |
| for (entry = radius->msgs; entry; entry = entry->next) { |
| if (entry->msg_type == RADIUS_ACCT) { |
| entry->shared_secret = shared_secret; |
| entry->shared_secret_len = shared_secret_len; |
| radius_msg_finish_acct(entry->msg, shared_secret, |
| shared_secret_len); |
| } |
| } |
| } |
| |
| |
| static int |
| radius_change_server(struct radius_client_data *radius, |
| struct hostapd_radius_server *nserv, |
| struct hostapd_radius_server *oserv, |
| int sock, int sock6, int auth) |
| { |
| struct sockaddr_in serv, claddr; |
| #ifdef CONFIG_IPV6 |
| struct sockaddr_in6 serv6, claddr6; |
| #endif /* CONFIG_IPV6 */ |
| struct sockaddr *addr, *cl_addr; |
| socklen_t addrlen, claddrlen; |
| char abuf[50]; |
| int sel_sock; |
| struct radius_msg_list *entry; |
| struct hostapd_radius_servers *conf = radius->conf; |
| struct sockaddr_in disconnect_addr = { |
| .sin_family = AF_UNSPEC, |
| }; |
| |
| hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_INFO, |
| "%s server %s:%d", |
| auth ? "Authentication" : "Accounting", |
| hostapd_ip_txt(&nserv->addr, abuf, sizeof(abuf)), |
| nserv->port); |
| |
| if (oserv && oserv == nserv) { |
| /* Reconnect to same server, flush */ |
| if (auth) |
| radius_client_flush(radius, 1); |
| } |
| |
| if (oserv && oserv != nserv && |
| (nserv->shared_secret_len != oserv->shared_secret_len || |
| os_memcmp(nserv->shared_secret, oserv->shared_secret, |
| nserv->shared_secret_len) != 0)) { |
| /* Pending RADIUS packets used different shared secret, so |
| * they need to be modified. Update accounting message |
| * authenticators here. Authentication messages are removed |
| * since they would require more changes and the new RADIUS |
| * server may not be prepared to receive them anyway due to |
| * missing state information. Client will likely retry |
| * authentication, so this should not be an issue. */ |
| if (auth) |
| radius_client_flush(radius, 1); |
| else { |
| radius_client_update_acct_msgs( |
| radius, nserv->shared_secret, |
| nserv->shared_secret_len); |
| } |
| } |
| |
| /* Reset retry counters */ |
| for (entry = radius->msgs; oserv && entry; entry = entry->next) { |
| if ((auth && entry->msg_type != RADIUS_AUTH) || |
| (!auth && entry->msg_type != RADIUS_ACCT)) |
| continue; |
| entry->next_try = entry->first_try + RADIUS_CLIENT_FIRST_WAIT; |
| entry->attempts = 0; |
| entry->next_wait = RADIUS_CLIENT_FIRST_WAIT * 2; |
| } |
| |
| if (radius->msgs) { |
| eloop_cancel_timeout(radius_client_timer, radius, NULL); |
| eloop_register_timeout(RADIUS_CLIENT_FIRST_WAIT, 0, |
| radius_client_timer, radius, NULL); |
| } |
| |
| switch (nserv->addr.af) { |
| case AF_INET: |
| os_memset(&serv, 0, sizeof(serv)); |
| serv.sin_family = AF_INET; |
| serv.sin_addr.s_addr = nserv->addr.u.v4.s_addr; |
| serv.sin_port = htons(nserv->port); |
| addr = (struct sockaddr *) &serv; |
| addrlen = sizeof(serv); |
| sel_sock = sock; |
| break; |
| #ifdef CONFIG_IPV6 |
| case AF_INET6: |
| os_memset(&serv6, 0, sizeof(serv6)); |
| serv6.sin6_family = AF_INET6; |
| os_memcpy(&serv6.sin6_addr, &nserv->addr.u.v6, |
| sizeof(struct in6_addr)); |
| serv6.sin6_port = htons(nserv->port); |
| addr = (struct sockaddr *) &serv6; |
| addrlen = sizeof(serv6); |
| sel_sock = sock6; |
| break; |
| #endif /* CONFIG_IPV6 */ |
| default: |
| return -1; |
| } |
| |
| if (sel_sock < 0) { |
| wpa_printf(MSG_INFO, |
| "RADIUS: No server socket available (af=%d sock=%d sock6=%d auth=%d", |
| nserv->addr.af, sock, sock6, auth); |
| return -1; |
| } |
| |
| /* Force a reconnect by disconnecting the socket first */ |
| if (connect(sel_sock, (struct sockaddr *) &disconnect_addr, |
| sizeof(disconnect_addr)) < 0) |
| wpa_printf(MSG_INFO, "disconnect[radius]: %s", strerror(errno)); |
| |
| #ifdef __linux__ |
| if (conf->force_client_dev && conf->force_client_dev[0]) { |
| if (setsockopt(sel_sock, SOL_SOCKET, SO_BINDTODEVICE, |
| conf->force_client_dev, |
| os_strlen(conf->force_client_dev)) < 0) { |
| wpa_printf(MSG_ERROR, |
| "RADIUS: setsockopt[SO_BINDTODEVICE]: %s", |
| strerror(errno)); |
| /* Probably not a critical error; continue on and hope |
| * for the best. */ |
| } else { |
| wpa_printf(MSG_DEBUG, |
| "RADIUS: Bound client socket to device: %s", |
| conf->force_client_dev); |
| } |
| } |
| #endif /* __linux__ */ |
| |
| if (conf->force_client_addr) { |
| switch (conf->client_addr.af) { |
| case AF_INET: |
| os_memset(&claddr, 0, sizeof(claddr)); |
| claddr.sin_family = AF_INET; |
| claddr.sin_addr.s_addr = conf->client_addr.u.v4.s_addr; |
| claddr.sin_port = htons(0); |
| cl_addr = (struct sockaddr *) &claddr; |
| claddrlen = sizeof(claddr); |
| break; |
| #ifdef CONFIG_IPV6 |
| case AF_INET6: |
| os_memset(&claddr6, 0, sizeof(claddr6)); |
| claddr6.sin6_family = AF_INET6; |
| os_memcpy(&claddr6.sin6_addr, &conf->client_addr.u.v6, |
| sizeof(struct in6_addr)); |
| claddr6.sin6_port = htons(0); |
| cl_addr = (struct sockaddr *) &claddr6; |
| claddrlen = sizeof(claddr6); |
| break; |
| #endif /* CONFIG_IPV6 */ |
| default: |
| return -1; |
| } |
| |
| if (bind(sel_sock, cl_addr, claddrlen) < 0) { |
| wpa_printf(MSG_INFO, "bind[radius]: %s", |
| strerror(errno)); |
| return -1; |
| } |
| } |
| |
| if (connect(sel_sock, addr, addrlen) < 0) { |
| wpa_printf(MSG_INFO, "connect[radius]: %s", strerror(errno)); |
| return -1; |
| } |
| |
| #ifndef CONFIG_NATIVE_WINDOWS |
| switch (nserv->addr.af) { |
| case AF_INET: |
| claddrlen = sizeof(claddr); |
| if (getsockname(sel_sock, (struct sockaddr *) &claddr, |
| &claddrlen) == 0) { |
| wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u", |
| inet_ntoa(claddr.sin_addr), |
| ntohs(claddr.sin_port)); |
| } |
| break; |
| #ifdef CONFIG_IPV6 |
| case AF_INET6: { |
| claddrlen = sizeof(claddr6); |
| if (getsockname(sel_sock, (struct sockaddr *) &claddr6, |
| &claddrlen) == 0) { |
| wpa_printf(MSG_DEBUG, "RADIUS local address: %s:%u", |
| inet_ntop(AF_INET6, &claddr6.sin6_addr, |
| abuf, sizeof(abuf)), |
| ntohs(claddr6.sin6_port)); |
| } |
| break; |
| } |
| #endif /* CONFIG_IPV6 */ |
| } |
| #endif /* CONFIG_NATIVE_WINDOWS */ |
| |
| if (auth) |
| radius->auth_sock = sel_sock; |
| else |
| radius->acct_sock = sel_sock; |
| |
| return 0; |
| } |
| |
| |
| static void radius_retry_primary_timer(void *eloop_ctx, void *timeout_ctx) |
| { |
| struct radius_client_data *radius = eloop_ctx; |
| struct hostapd_radius_servers *conf = radius->conf; |
| struct hostapd_radius_server *oserv; |
| |
| if (radius->auth_sock >= 0 && conf->auth_servers && |
| conf->auth_server != conf->auth_servers) { |
| oserv = conf->auth_server; |
| conf->auth_server = conf->auth_servers; |
| if (radius_change_server(radius, conf->auth_server, oserv, |
| radius->auth_serv_sock, |
| radius->auth_serv_sock6, 1) < 0) { |
| conf->auth_server = oserv; |
| radius_change_server(radius, oserv, conf->auth_server, |
| radius->auth_serv_sock, |
| radius->auth_serv_sock6, 1); |
| } |
| } |
| |
| if (radius->acct_sock >= 0 && conf->acct_servers && |
| conf->acct_server != conf->acct_servers) { |
| oserv = conf->acct_server; |
| conf->acct_server = conf->acct_servers; |
| if (radius_change_server(radius, conf->acct_server, oserv, |
| radius->acct_serv_sock, |
| radius->acct_serv_sock6, 0) < 0) { |
| conf->acct_server = oserv; |
| radius_change_server(radius, oserv, conf->acct_server, |
| radius->acct_serv_sock, |
| radius->acct_serv_sock6, 0); |
| } |
| } |
| |
| if (conf->retry_primary_interval) |
| eloop_register_timeout(conf->retry_primary_interval, 0, |
| radius_retry_primary_timer, radius, |
| NULL); |
| } |
| |
| |
| static int radius_client_disable_pmtu_discovery(int s) |
| { |
| int r = -1; |
| #if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT) |
| /* Turn off Path MTU discovery on IPv4/UDP sockets. */ |
| int action = IP_PMTUDISC_DONT; |
| r = setsockopt(s, IPPROTO_IP, IP_MTU_DISCOVER, &action, |
| sizeof(action)); |
| if (r == -1) |
| wpa_printf(MSG_ERROR, "RADIUS: Failed to set IP_MTU_DISCOVER: %s", |
| strerror(errno)); |
| #endif |
| return r; |
| } |
| |
| |
| static void radius_close_auth_sockets(struct radius_client_data *radius) |
| { |
| radius->auth_sock = -1; |
| |
| if (radius->auth_serv_sock >= 0) { |
| eloop_unregister_read_sock(radius->auth_serv_sock); |
| close(radius->auth_serv_sock); |
| radius->auth_serv_sock = -1; |
| } |
| #ifdef CONFIG_IPV6 |
| if (radius->auth_serv_sock6 >= 0) { |
| eloop_unregister_read_sock(radius->auth_serv_sock6); |
| close(radius->auth_serv_sock6); |
| radius->auth_serv_sock6 = -1; |
| } |
| #endif /* CONFIG_IPV6 */ |
| } |
| |
| |
| static void radius_close_acct_sockets(struct radius_client_data *radius) |
| { |
| radius->acct_sock = -1; |
| |
| if (radius->acct_serv_sock >= 0) { |
| eloop_unregister_read_sock(radius->acct_serv_sock); |
| close(radius->acct_serv_sock); |
| radius->acct_serv_sock = -1; |
| } |
| #ifdef CONFIG_IPV6 |
| if (radius->acct_serv_sock6 >= 0) { |
| eloop_unregister_read_sock(radius->acct_serv_sock6); |
| close(radius->acct_serv_sock6); |
| radius->acct_serv_sock6 = -1; |
| } |
| #endif /* CONFIG_IPV6 */ |
| } |
| |
| |
| static int radius_client_init_auth(struct radius_client_data *radius) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| int ok = 0; |
| |
| radius_close_auth_sockets(radius); |
| |
| radius->auth_serv_sock = socket(PF_INET, SOCK_DGRAM, 0); |
| if (radius->auth_serv_sock < 0) |
| wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s", |
| strerror(errno)); |
| else { |
| radius_client_disable_pmtu_discovery(radius->auth_serv_sock); |
| ok++; |
| } |
| |
| #ifdef CONFIG_IPV6 |
| radius->auth_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0); |
| if (radius->auth_serv_sock6 < 0) |
| wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s", |
| strerror(errno)); |
| else |
| ok++; |
| #endif /* CONFIG_IPV6 */ |
| |
| if (ok == 0) |
| return -1; |
| |
| radius_change_server(radius, conf->auth_server, NULL, |
| radius->auth_serv_sock, radius->auth_serv_sock6, |
| 1); |
| |
| if (radius->auth_serv_sock >= 0 && |
| eloop_register_read_sock(radius->auth_serv_sock, |
| radius_client_receive, radius, |
| (void *) RADIUS_AUTH)) { |
| wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server"); |
| radius_close_auth_sockets(radius); |
| return -1; |
| } |
| |
| #ifdef CONFIG_IPV6 |
| if (radius->auth_serv_sock6 >= 0 && |
| eloop_register_read_sock(radius->auth_serv_sock6, |
| radius_client_receive, radius, |
| (void *) RADIUS_AUTH)) { |
| wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for authentication server"); |
| radius_close_auth_sockets(radius); |
| return -1; |
| } |
| #endif /* CONFIG_IPV6 */ |
| |
| return 0; |
| } |
| |
| |
| static int radius_client_init_acct(struct radius_client_data *radius) |
| { |
| struct hostapd_radius_servers *conf = radius->conf; |
| int ok = 0; |
| |
| radius_close_acct_sockets(radius); |
| |
| radius->acct_serv_sock = socket(PF_INET, SOCK_DGRAM, 0); |
| if (radius->acct_serv_sock < 0) |
| wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET,SOCK_DGRAM]: %s", |
| strerror(errno)); |
| else { |
| radius_client_disable_pmtu_discovery(radius->acct_serv_sock); |
| ok++; |
| } |
| |
| #ifdef CONFIG_IPV6 |
| radius->acct_serv_sock6 = socket(PF_INET6, SOCK_DGRAM, 0); |
| if (radius->acct_serv_sock6 < 0) |
| wpa_printf(MSG_INFO, "RADIUS: socket[PF_INET6,SOCK_DGRAM]: %s", |
| strerror(errno)); |
| else |
| ok++; |
| #endif /* CONFIG_IPV6 */ |
| |
| if (ok == 0) |
| return -1; |
| |
| radius_change_server(radius, conf->acct_server, NULL, |
| radius->acct_serv_sock, radius->acct_serv_sock6, |
| 0); |
| |
| if (radius->acct_serv_sock >= 0 && |
| eloop_register_read_sock(radius->acct_serv_sock, |
| radius_client_receive, radius, |
| (void *) RADIUS_ACCT)) { |
| wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server"); |
| radius_close_acct_sockets(radius); |
| return -1; |
| } |
| |
| #ifdef CONFIG_IPV6 |
| if (radius->acct_serv_sock6 >= 0 && |
| eloop_register_read_sock(radius->acct_serv_sock6, |
| radius_client_receive, radius, |
| (void *) RADIUS_ACCT)) { |
| wpa_printf(MSG_INFO, "RADIUS: Could not register read socket for accounting server"); |
| radius_close_acct_sockets(radius); |
| return -1; |
| } |
| #endif /* CONFIG_IPV6 */ |
| |
| return 0; |
| } |
| |
| |
| /** |
| * radius_client_init - Initialize RADIUS client |
| * @ctx: Callback context to be used in hostapd_logger() calls |
| * @conf: RADIUS client configuration (RADIUS servers) |
| * Returns: Pointer to private RADIUS client context or %NULL on failure |
| * |
| * The caller is responsible for keeping the configuration data available for |
| * the lifetime of the RADIUS client, i.e., until radius_client_deinit() is |
| * called for the returned context pointer. |
| */ |
| struct radius_client_data * |
| radius_client_init(void *ctx, struct hostapd_radius_servers *conf) |
| { |
| struct radius_client_data *radius; |
| |
| radius = os_zalloc(sizeof(struct radius_client_data)); |
| if (radius == NULL) |
| return NULL; |
| |
| radius->ctx = ctx; |
| radius->conf = conf; |
| radius->auth_serv_sock = radius->acct_serv_sock = |
| radius->auth_serv_sock6 = radius->acct_serv_sock6 = |
| radius->auth_sock = radius->acct_sock = -1; |
| |
| if (conf->auth_server && radius_client_init_auth(radius)) { |
| radius_client_deinit(radius); |
| return NULL; |
| } |
| |
| if (conf->acct_server && radius_client_init_acct(radius)) { |
| radius_client_deinit(radius); |
| return NULL; |
| } |
| |
| if (conf->retry_primary_interval) |
| eloop_register_timeout(conf->retry_primary_interval, 0, |
| radius_retry_primary_timer, radius, |
| NULL); |
| |
| return radius; |
| } |
| |
| |
| /** |
| * radius_client_deinit - Deinitialize RADIUS client |
| * @radius: RADIUS client context from radius_client_init() |
| */ |
| void radius_client_deinit(struct radius_client_data *radius) |
| { |
| if (!radius) |
| return; |
| |
| radius_close_auth_sockets(radius); |
| radius_close_acct_sockets(radius); |
| |
| eloop_cancel_timeout(radius_retry_primary_timer, radius, NULL); |
| |
| radius_client_flush(radius, 0); |
| os_free(radius->auth_handlers); |
| os_free(radius->acct_handlers); |
| os_free(radius); |
| } |
| |
| |
| /** |
| * radius_client_flush_auth - Flush pending RADIUS messages for an address |
| * @radius: RADIUS client context from radius_client_init() |
| * @addr: MAC address of the related device |
| * |
| * This function can be used to remove pending RADIUS authentication messages |
| * that are related to a specific device. The addr parameter is matched with |
| * the one used in radius_client_send() call that was used to transmit the |
| * authentication request. |
| */ |
| void radius_client_flush_auth(struct radius_client_data *radius, |
| const u8 *addr) |
| { |
| struct radius_msg_list *entry, *prev, *tmp; |
| |
| prev = NULL; |
| entry = radius->msgs; |
| while (entry) { |
| if (entry->msg_type == RADIUS_AUTH && |
| os_memcmp(entry->addr, addr, ETH_ALEN) == 0) { |
| hostapd_logger(radius->ctx, addr, |
| HOSTAPD_MODULE_RADIUS, |
| HOSTAPD_LEVEL_DEBUG, |
| "Removing pending RADIUS authentication" |
| " message for removed client"); |
| |
| if (prev) |
| prev->next = entry->next; |
| else |
| radius->msgs = entry->next; |
| |
| tmp = entry; |
| entry = entry->next; |
| radius_client_msg_free(tmp); |
| radius->num_msgs--; |
| continue; |
| } |
| |
| prev = entry; |
| entry = entry->next; |
| } |
| } |
| |
| |
| static int radius_client_dump_auth_server(char *buf, size_t buflen, |
| struct hostapd_radius_server *serv, |
| struct radius_client_data *cli) |
| { |
| int pending = 0; |
| struct radius_msg_list *msg; |
| char abuf[50]; |
| |
| if (cli) { |
| for (msg = cli->msgs; msg; msg = msg->next) { |
| if (msg->msg_type == RADIUS_AUTH) |
| pending++; |
| } |
| } |
| |
| return os_snprintf(buf, buflen, |
| "radiusAuthServerIndex=%d\n" |
| "radiusAuthServerAddress=%s\n" |
| "radiusAuthClientServerPortNumber=%d\n" |
| "radiusAuthClientRoundTripTime=%d\n" |
| "radiusAuthClientAccessRequests=%u\n" |
| "radiusAuthClientAccessRetransmissions=%u\n" |
| "radiusAuthClientAccessAccepts=%u\n" |
| "radiusAuthClientAccessRejects=%u\n" |
| "radiusAuthClientAccessChallenges=%u\n" |
| "radiusAuthClientMalformedAccessResponses=%u\n" |
| "radiusAuthClientBadAuthenticators=%u\n" |
| "radiusAuthClientPendingRequests=%u\n" |
| "radiusAuthClientTimeouts=%u\n" |
| "radiusAuthClientUnknownTypes=%u\n" |
| "radiusAuthClientPacketsDropped=%u\n", |
| serv->index, |
| hostapd_ip_txt(&serv->addr, abuf, sizeof(abuf)), |
| serv->port, |
| serv->round_trip_time, |
| serv->requests, |
| serv->retransmissions, |
| serv->access_accepts, |
| serv->access_rejects, |
| serv->access_challenges, |
| serv->malformed_responses, |
| serv->bad_authenticators, |
| pending, |
| serv->timeouts, |
| serv->unknown_types, |
| serv->packets_dropped); |
| } |
| |
| |
| static int radius_client_dump_acct_server(char *buf, size_t buflen, |
| struct hostapd_radius_server *serv, |
| struct radius_client_data *cli) |
| { |
| int pending = 0; |
| struct radius_msg_list *msg; |
| char abuf[50]; |
| |
| if (cli) { |
| for (msg = cli->msgs; msg; msg = msg->next) { |
| if (msg->msg_type == RADIUS_ACCT || |
| msg->msg_type == RADIUS_ACCT_INTERIM) |
| pending++; |
| } |
| } |
| |
| return os_snprintf(buf, buflen, |
| "radiusAccServerIndex=%d\n" |
| "radiusAccServerAddress=%s\n" |
| "radiusAccClientServerPortNumber=%d\n" |
| "radiusAccClientRoundTripTime=%d\n" |
| "radiusAccClientRequests=%u\n" |
| "radiusAccClientRetransmissions=%u\n" |
| "radiusAccClientResponses=%u\n" |
| "radiusAccClientMalformedResponses=%u\n" |
| "radiusAccClientBadAuthenticators=%u\n" |
| "radiusAccClientPendingRequests=%u\n" |
| "radiusAccClientTimeouts=%u\n" |
| "radiusAccClientUnknownTypes=%u\n" |
| "radiusAccClientPacketsDropped=%u\n", |
| serv->index, |
| hostapd_ip_txt(&serv->addr, abuf, sizeof(abuf)), |
| serv->port, |
| serv->round_trip_time, |
| serv->requests, |
| serv->retransmissions, |
| serv->responses, |
| serv->malformed_responses, |
| serv->bad_authenticators, |
| pending, |
| serv->timeouts, |
| serv->unknown_types, |
| serv->packets_dropped); |
| } |
| |
| |
| /** |
| * radius_client_get_mib - Get RADIUS client MIB information |
| * @radius: RADIUS client context from radius_client_init() |
| * @buf: Buffer for returning MIB data in text format |
| * @buflen: Maximum buf length in octets |
| * Returns: Number of octets written into the buffer |
| */ |
| int radius_client_get_mib(struct radius_client_data *radius, char *buf, |
| size_t buflen) |
| { |
| struct hostapd_radius_servers *conf; |
| int i; |
| struct hostapd_radius_server *serv; |
| int count = 0; |
| |
| if (!radius) |
| return 0; |
| |
| conf = radius->conf; |
| |
| if (conf->auth_servers) { |
| for (i = 0; i < conf->num_auth_servers; i++) { |
| serv = &conf->auth_servers[i]; |
| count += radius_client_dump_auth_server( |
| buf + count, buflen - count, serv, |
| serv == conf->auth_server ? |
| radius : NULL); |
| } |
| } |
| |
| if (conf->acct_servers) { |
| for (i = 0; i < conf->num_acct_servers; i++) { |
| serv = &conf->acct_servers[i]; |
| count += radius_client_dump_acct_server( |
| buf + count, buflen - count, serv, |
| serv == conf->acct_server ? |
| radius : NULL); |
| } |
| } |
| |
| return count; |
| } |
| |
| |
| void radius_client_reconfig(struct radius_client_data *radius, |
| struct hostapd_radius_servers *conf) |
| { |
| if (radius) |
| radius->conf = conf; |
| } |