blob: d524f20faa75608fa2c6522f34da9a3d96a13e46 [file] [log] [blame]
/*********************************************************************************************************
* Software License Agreement (BSD License) *
* Author: Sebastien Decugis <sdecugis@freediameter.net> *
* *
* Copyright (c) 2013, WIDE Project and NICT *
* All rights reserved. *
* *
* Redistribution and use of this software in source and binary forms, with or without modification, are *
* permitted provided that the following conditions are met: *
* *
* * Redistributions of source code must retain the above *
* copyright notice, this list of conditions and the *
* following disclaimer. *
* *
* * Redistributions in binary form must reproduce the above *
* copyright notice, this list of conditions and the *
* following disclaimer in the documentation and/or other *
* materials provided with the distribution. *
* *
* * Neither the name of the WIDE Project or NICT nor the *
* names of its contributors may be used to endorse or *
* promote products derived from this software without *
* specific prior written permission of WIDE Project and *
* NICT. *
* *
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A *
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR *
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT *
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS *
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR *
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
*********************************************************************************************************/
/* Manage the RADIUS server(s): opening sockets, receiving messages, ... */
#include "rgw.h"
#define RADIUS_MAX_MSG_LEN 3000
#define RADIUS_AUTH_PORT 1812
#define RADIUS_ACCT_PORT 1813
/* Declare the rgw_servers */
struct rgw_servs rgw_servers;
void rgw_servers_dump(void)
{
char ipstr[INET6_ADDRSTRLEN];
LOG_D(" auth server:");
LOG_D(" disabled..... : %s", rgw_servers.auth_serv.disabled ? "TRUE":"false");
LOG_D(" IP disabled.. : %s", rgw_servers.auth_serv.ip_disabled ? "TRUE":"false");
LOG_D(" IPv6 disabled : %s", rgw_servers.auth_serv.ip6_disabled ? "TRUE":"false");
LOG_D(" port......... : %hu", ntohs(rgw_servers.auth_serv.port));
inet_ntop(AF_INET, &rgw_servers.auth_serv.ip_endpoint,ipstr,sizeof(ipstr));
LOG_D(" IP bind...... : %s", ipstr);
inet_ntop(AF_INET6, &rgw_servers.auth_serv.ip6_endpoint,ipstr,sizeof(ipstr));
LOG_D(" IPv6 bind.... : %s", ipstr);
LOG_D(" acct server:");
LOG_D(" disabled..... : %s", rgw_servers.acct_serv.disabled ? "TRUE":"false");
LOG_D(" IP disabled.. : %s", rgw_servers.acct_serv.ip_disabled ? "TRUE":"false");
LOG_D(" IPv6 disabled : %s", rgw_servers.acct_serv.ip6_disabled ? "TRUE":"false");
LOG_D(" port......... : %hu", ntohs(rgw_servers.acct_serv.port));
inet_ntop(AF_INET, &rgw_servers.acct_serv.ip_endpoint,ipstr,sizeof(ipstr));
LOG_D(" IP bind...... : %s", ipstr);
inet_ntop(AF_INET6, &rgw_servers.acct_serv.ip6_endpoint,ipstr,sizeof(ipstr));
LOG_D(" IPv6 bind.... : %s", ipstr);
}
static struct servers_data {
int type; /* auth or acct */
int family; /* AF_INET or AF_INET6 */
int sock; /* the socket number */
pthread_t th; /* the running server thread, or NULL */
char name[10];
} SERVERS[4];
int rgw_servers_init(void)
{
memset(&rgw_servers, 0, sizeof(rgw_servers));
memset(&SERVERS[0], 0, sizeof(SERVERS));
rgw_servers.auth_serv.port = htons(RADIUS_AUTH_PORT);
rgw_servers.acct_serv.port = htons(RADIUS_ACCT_PORT);
return 0;
}
static void * server_thread(void * param)
{
struct servers_data * me = (struct servers_data *)param;
TRACE_ENTRY("%p", param);
CHECK_PARAMS_DO(param, return NULL);
/* Set the thread name */
{
char buf[48];
snprintf(buf, sizeof(buf), "radgw/%s serv", me->name);
fd_log_threadname ( buf );
}
/* Now loop on this socket, parse and queue each message received, until thread is cancelled. */
while (1) {
struct sockaddr_storage from;
char sa_buf[sSA_DUMP_STRLEN];
socklen_t fromlen = sizeof(from);
int len;
struct rgw_client * nas_info = NULL;
uint16_t port = 0;
unsigned char buf[RADIUS_MAX_MSG_LEN];
struct rgw_radius_msg_meta *msg = NULL;
pthread_testcancel();
/* receive the next message */
CHECK_SYS_DO( len = recvfrom( me->sock, &buf[0], sizeof(buf), 0, (struct sockaddr *) &from, &fromlen), break );
/* Get the port */
port = sSAport(&from);
if (!port) {
LOG_E("Invalid port (family: %d), discarding received %d bytes...", from.ss_family, len);
continue;
}
fd_sa_sdump_numeric(sa_buf, (sSA*)&from);
LOG_D("RADIUS: RCV %dB from %s", len, sa_buf);
/* Search the associated client definition, if any */
CHECK_FCT_DO( rgw_clients_search((struct sockaddr *) &from, &nas_info),
{
LOG_E("Discarding %d bytes received from unknown IP: %s", len, sa_buf);
continue;
} );
/* parse the message, loop if message is invalid */
CHECK_FCT_DO( rgw_msg_parse(&buf[0], len, &msg),
{
DiamId_t cliname = NULL;
size_t clisz;
CHECK_FCT_DO( rgw_clients_get_origin(nas_info, &cliname, &clisz, NULL, NULL), );
LOG_E( "Discarding invalid RADIUS message from '%s'", cliname);
rgw_clients_dispose(&nas_info);
continue;
} );
msg->serv_type = me->type;
msg->port = port;
rgw_msg_dump(msg, 1);
/* queue the message for a worker thread */
CHECK_FCT_DO( rgw_work_add(msg, nas_info), break );
/* Then wait for next incoming message */
}
TRACE_DEBUG(INFO, "Server thread terminated.");
return NULL;
}
/* Set the socket options for UDP sockets, before bind is called */
static int _udp_setsockopt(int family, int sk)
{
int ret = 0;
int opt;
/* In case of v6 address, force the v6only option, we use a different socket for v4 */
#ifdef IPV6_V6ONLY
if (family == AF_INET6) {
opt = 1;
ret = setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
if (ret != 0) {
ret = errno;
TRACE_DEBUG(INFO, "Unable to set the socket IPV6_V6ONLY option: %s", strerror(ret));
return ret;
}
}
#endif /* IPV6_V6ONLY */
return 0;
}
/* We reuse the same logic for all 4 possible servers (IP / IPv6, Auth / Acct ports) */
#define UDPSERV( _type_, _portval_, _family_ ) { \
/* Check that this type / family are not disabled by configuration */ \
if ( (! rgw_servers. _type_ ## _serv.disabled) \
&& ( ! rgw_servers. _type_ ## _serv.ip ## _family_ ## _disabled ) ) { \
struct sockaddr_in ## _family_ sin ## _family_ ; \
/* Create the socket */ \
CHECK_SYS( SERVERS[idx].sock = socket(AF_INET ## _family_, SOCK_DGRAM, 0) ); \
/* Set the parameters for bind into "sin" or "sin6" */ \
memset(& sin ## _family_, 0, sizeof(struct sockaddr_in ## _family_)); \
sin ## _family_ . sin ## _family_ ## _family = AF_INET ## _family_; \
sin ## _family_ . sin ## _family_ ## _port = rgw_servers. _type_ ## _serv . port; \
memcpy( &sin ## _family_ .sin ## _family_ ## _addr, \
&rgw_servers. _type_ ## _serv . ip ## _family_ ## _endpoint, \
sizeof(struct in ## _family_ ## _addr) ); \
/* This sockopt must be set before binding */ \
TRACE_DEBUG(ANNOYING, "Setting socket options..."); \
CHECK_FCT( _udp_setsockopt(AF_INET ## _family_, SERVERS[idx].sock) ); \
/* OK, now, bind */ \
TRACE_DEBUG(ANNOYING, "Binding " #_type_ " ip" #_family_ " server..."); \
CHECK_SYS( bind( SERVERS[idx].sock, \
(struct sockaddr *)&sin ## _family_, \
sizeof(struct sockaddr_in ## _family_) ) ); \
/* Save the server information in SERVERS structure */ \
SERVERS[idx].type = _portval_; \
SERVERS[idx].family = AF_INET ## _family_; \
snprintf(&SERVERS[idx].name[0], sizeof(SERVERS[idx].name), # _type_ "/ip" #_family_); \
/* Create the server thread */ \
CHECK_POSIX( pthread_create(&SERVERS[idx].th, NULL, server_thread, &SERVERS[idx]) ); \
idx++; \
} \
}
int rgw_servers_start(void)
{
int idx = 0;
TRACE_ENTRY();
UDPSERV( auth, RGW_PLG_TYPE_AUTH, );
UDPSERV( auth, RGW_PLG_TYPE_AUTH, 6 );
UDPSERV( acct, RGW_PLG_TYPE_ACCT, );
UDPSERV( acct, RGW_PLG_TYPE_ACCT, 6 );
TRACE_DEBUG(FULL, "%d UDP servers started succesfully.", idx);
return 0;
}
/* Send a RADIUS message */
int rgw_servers_send(int type, unsigned char *buf, size_t buflen, struct sockaddr *to, uint16_t to_port)
{
int idx = 0;
int ret = 0;
struct sockaddr_storage sto;
char sa_buf[sSA_DUMP_STRLEN];
/* Find the appropriate socket to use (not sure if it is important) */
for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
if ( SERVERS[idx].sock && (type == SERVERS[idx].type) && (to->sa_family == SERVERS[idx].family) ) {
ret = 1;
break;
}
}
if (!ret) {
LOG_E( "Trying to send a message from a disabled server: %s / %s",
(type == RGW_PLG_TYPE_AUTH) ? "Auth" : "Acct",
(to->sa_family == AF_INET) ? "IP (v4)" : "IPv6");
return EINVAL;
}
/* Prepare the destination info */
memset(&sto, 0, sizeof(sto));
if (to->sa_family == AF_INET) {
memcpy(&sto, to, sizeof(struct sockaddr_in));
((struct sockaddr_in *)&sto)->sin_port = to_port;
} else {
memcpy(&sto, to, sizeof(struct sockaddr_in6));
((struct sockaddr_in6 *)&sto)->sin6_port = to_port;
}
fd_sa_sdump_numeric(sa_buf, (sSA*)&sto);
LOG_D("RADIUS: SND %zdB to %s", buflen, sa_buf);
/* Send */
ret = sendto(SERVERS[idx].sock, buf, buflen, 0, (struct sockaddr *)&sto, sSAlen(&sto));
if (ret < 0) {
ret = errno;
TRACE_DEBUG(INFO, "An error prevented sending of a RADIUS message: %s", strerror(ret));
return ret;
}
if (ret != buflen) {
TRACE_DEBUG(INFO, "Incomplete send: %d bytes / %zd", ret, buflen);
return EAGAIN;
}
/* Done :) */
return 0;
}
void rgw_servers_fini(void)
{
int idx = 0;
for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) {
if (SERVERS[idx].sock == 0)
break;
CHECK_FCT_DO( fd_thr_term(&SERVERS[idx].th), /* continue */ );
close(SERVERS[idx].sock);
SERVERS[idx].sock = 0;
}
}