/*********************************************************************************************************
* Software License Agreement (BSD License)                                                               *
* Author: Sebastien Decugis <sdecugis@freediameter.net>							 *
*													 *
* Copyright (c) 2023, 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 successfully.", 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;
	}
	
}


