Brian Waters | 13d9601 | 2017-12-08 16:53:31 -0600 | [diff] [blame] | 1 | /********************************************************************************************************* |
| 2 | * Software License Agreement (BSD License) * |
| 3 | * Author: Sebastien Decugis <sdecugis@freediameter.net> * |
| 4 | * * |
| 5 | * Copyright (c) 2013, WIDE Project and NICT * |
| 6 | * All rights reserved. * |
| 7 | * * |
| 8 | * Redistribution and use of this software in source and binary forms, with or without modification, are * |
| 9 | * permitted provided that the following conditions are met: * |
| 10 | * * |
| 11 | * * Redistributions of source code must retain the above * |
| 12 | * copyright notice, this list of conditions and the * |
| 13 | * following disclaimer. * |
| 14 | * * |
| 15 | * * Redistributions in binary form must reproduce the above * |
| 16 | * copyright notice, this list of conditions and the * |
| 17 | * following disclaimer in the documentation and/or other * |
| 18 | * materials provided with the distribution. * |
| 19 | * * |
| 20 | * * Neither the name of the WIDE Project or NICT nor the * |
| 21 | * names of its contributors may be used to endorse or * |
| 22 | * promote products derived from this software without * |
| 23 | * specific prior written permission of WIDE Project and * |
| 24 | * NICT. * |
| 25 | * * |
| 26 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * |
| 27 | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * |
| 28 | * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * |
| 29 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * |
| 30 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * |
| 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * |
| 32 | * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * |
| 33 | * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * |
| 34 | *********************************************************************************************************/ |
| 35 | |
| 36 | /* Manage the RADIUS server(s): opening sockets, receiving messages, ... */ |
| 37 | |
| 38 | #include "rgw.h" |
| 39 | |
| 40 | #define RADIUS_MAX_MSG_LEN 3000 |
| 41 | #define RADIUS_AUTH_PORT 1812 |
| 42 | #define RADIUS_ACCT_PORT 1813 |
| 43 | |
| 44 | /* Declare the rgw_servers */ |
| 45 | struct rgw_servs rgw_servers; |
| 46 | |
| 47 | void rgw_servers_dump(void) |
| 48 | { |
| 49 | char ipstr[INET6_ADDRSTRLEN]; |
| 50 | |
| 51 | LOG_D(" auth server:"); |
| 52 | LOG_D(" disabled..... : %s", rgw_servers.auth_serv.disabled ? "TRUE":"false"); |
| 53 | LOG_D(" IP disabled.. : %s", rgw_servers.auth_serv.ip_disabled ? "TRUE":"false"); |
| 54 | LOG_D(" IPv6 disabled : %s", rgw_servers.auth_serv.ip6_disabled ? "TRUE":"false"); |
| 55 | LOG_D(" port......... : %hu", ntohs(rgw_servers.auth_serv.port)); |
| 56 | inet_ntop(AF_INET, &rgw_servers.auth_serv.ip_endpoint,ipstr,sizeof(ipstr)); |
| 57 | LOG_D(" IP bind...... : %s", ipstr); |
| 58 | inet_ntop(AF_INET6, &rgw_servers.auth_serv.ip6_endpoint,ipstr,sizeof(ipstr)); |
| 59 | LOG_D(" IPv6 bind.... : %s", ipstr); |
| 60 | |
| 61 | LOG_D(" acct server:"); |
| 62 | LOG_D(" disabled..... : %s", rgw_servers.acct_serv.disabled ? "TRUE":"false"); |
| 63 | LOG_D(" IP disabled.. : %s", rgw_servers.acct_serv.ip_disabled ? "TRUE":"false"); |
| 64 | LOG_D(" IPv6 disabled : %s", rgw_servers.acct_serv.ip6_disabled ? "TRUE":"false"); |
| 65 | LOG_D(" port......... : %hu", ntohs(rgw_servers.acct_serv.port)); |
| 66 | inet_ntop(AF_INET, &rgw_servers.acct_serv.ip_endpoint,ipstr,sizeof(ipstr)); |
| 67 | LOG_D(" IP bind...... : %s", ipstr); |
| 68 | inet_ntop(AF_INET6, &rgw_servers.acct_serv.ip6_endpoint,ipstr,sizeof(ipstr)); |
| 69 | LOG_D(" IPv6 bind.... : %s", ipstr); |
| 70 | |
| 71 | } |
| 72 | |
| 73 | static struct servers_data { |
| 74 | int type; /* auth or acct */ |
| 75 | int family; /* AF_INET or AF_INET6 */ |
| 76 | int sock; /* the socket number */ |
| 77 | pthread_t th; /* the running server thread, or NULL */ |
| 78 | char name[10]; |
| 79 | } SERVERS[4]; |
| 80 | |
| 81 | int rgw_servers_init(void) |
| 82 | { |
| 83 | memset(&rgw_servers, 0, sizeof(rgw_servers)); |
| 84 | memset(&SERVERS[0], 0, sizeof(SERVERS)); |
| 85 | |
| 86 | rgw_servers.auth_serv.port = htons(RADIUS_AUTH_PORT); |
| 87 | rgw_servers.acct_serv.port = htons(RADIUS_ACCT_PORT); |
| 88 | |
| 89 | return 0; |
| 90 | } |
| 91 | |
| 92 | static void * server_thread(void * param) |
| 93 | { |
| 94 | struct servers_data * me = (struct servers_data *)param; |
| 95 | |
| 96 | TRACE_ENTRY("%p", param); |
| 97 | |
| 98 | CHECK_PARAMS_DO(param, return NULL); |
| 99 | |
| 100 | /* Set the thread name */ |
| 101 | { |
| 102 | char buf[48]; |
| 103 | snprintf(buf, sizeof(buf), "radgw/%s serv", me->name); |
| 104 | fd_log_threadname ( buf ); |
| 105 | } |
| 106 | |
| 107 | /* Now loop on this socket, parse and queue each message received, until thread is cancelled. */ |
| 108 | while (1) { |
| 109 | struct sockaddr_storage from; |
| 110 | char sa_buf[sSA_DUMP_STRLEN]; |
| 111 | socklen_t fromlen = sizeof(from); |
| 112 | int len; |
| 113 | struct rgw_client * nas_info = NULL; |
| 114 | uint16_t port = 0; |
| 115 | unsigned char buf[RADIUS_MAX_MSG_LEN]; |
| 116 | struct rgw_radius_msg_meta *msg = NULL; |
| 117 | |
| 118 | pthread_testcancel(); |
| 119 | |
| 120 | /* receive the next message */ |
| 121 | CHECK_SYS_DO( len = recvfrom( me->sock, &buf[0], sizeof(buf), 0, (struct sockaddr *) &from, &fromlen), break ); |
| 122 | |
| 123 | /* Get the port */ |
| 124 | port = sSAport(&from); |
| 125 | if (!port) { |
| 126 | LOG_E("Invalid port (family: %d), discarding received %d bytes...", from.ss_family, len); |
| 127 | continue; |
| 128 | } |
| 129 | |
| 130 | fd_sa_sdump_numeric(sa_buf, (sSA*)&from); |
| 131 | LOG_D("RADIUS: RCV %dB from %s", len, sa_buf); |
| 132 | |
| 133 | /* Search the associated client definition, if any */ |
| 134 | CHECK_FCT_DO( rgw_clients_search((struct sockaddr *) &from, &nas_info), |
| 135 | { |
| 136 | LOG_E("Discarding %d bytes received from unknown IP: %s", len, sa_buf); |
| 137 | continue; |
| 138 | } ); |
| 139 | |
| 140 | |
| 141 | /* parse the message, loop if message is invalid */ |
| 142 | CHECK_FCT_DO( rgw_msg_parse(&buf[0], len, &msg), |
| 143 | { |
| 144 | DiamId_t cliname = NULL; |
| 145 | size_t clisz; |
| 146 | CHECK_FCT_DO( rgw_clients_get_origin(nas_info, &cliname, &clisz, NULL, NULL), ); |
| 147 | LOG_E( "Discarding invalid RADIUS message from '%s'", cliname); |
| 148 | rgw_clients_dispose(&nas_info); |
| 149 | continue; |
| 150 | } ); |
| 151 | |
| 152 | msg->serv_type = me->type; |
| 153 | msg->port = port; |
| 154 | |
| 155 | rgw_msg_dump(msg, 1); |
| 156 | |
| 157 | /* queue the message for a worker thread */ |
| 158 | CHECK_FCT_DO( rgw_work_add(msg, nas_info), break ); |
| 159 | |
| 160 | /* Then wait for next incoming message */ |
| 161 | } |
| 162 | |
| 163 | TRACE_DEBUG(INFO, "Server thread terminated."); |
| 164 | return NULL; |
| 165 | } |
| 166 | |
| 167 | /* Set the socket options for UDP sockets, before bind is called */ |
| 168 | static int _udp_setsockopt(int family, int sk) |
| 169 | { |
| 170 | int ret = 0; |
| 171 | int opt; |
| 172 | |
| 173 | /* In case of v6 address, force the v6only option, we use a different socket for v4 */ |
| 174 | #ifdef IPV6_V6ONLY |
| 175 | if (family == AF_INET6) { |
| 176 | opt = 1; |
| 177 | ret = setsockopt(sk, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)); |
| 178 | if (ret != 0) { |
| 179 | ret = errno; |
| 180 | TRACE_DEBUG(INFO, "Unable to set the socket IPV6_V6ONLY option: %s", strerror(ret)); |
| 181 | return ret; |
| 182 | } |
| 183 | } |
| 184 | #endif /* IPV6_V6ONLY */ |
| 185 | |
| 186 | return 0; |
| 187 | } |
| 188 | |
| 189 | /* We reuse the same logic for all 4 possible servers (IP / IPv6, Auth / Acct ports) */ |
| 190 | #define UDPSERV( _type_, _portval_, _family_ ) { \ |
| 191 | /* Check that this type / family are not disabled by configuration */ \ |
| 192 | if ( (! rgw_servers. _type_ ## _serv.disabled) \ |
| 193 | && ( ! rgw_servers. _type_ ## _serv.ip ## _family_ ## _disabled ) ) { \ |
| 194 | struct sockaddr_in ## _family_ sin ## _family_ ; \ |
| 195 | /* Create the socket */ \ |
| 196 | CHECK_SYS( SERVERS[idx].sock = socket(AF_INET ## _family_, SOCK_DGRAM, 0) ); \ |
| 197 | /* Set the parameters for bind into "sin" or "sin6" */ \ |
| 198 | memset(& sin ## _family_, 0, sizeof(struct sockaddr_in ## _family_)); \ |
| 199 | sin ## _family_ . sin ## _family_ ## _family = AF_INET ## _family_; \ |
| 200 | sin ## _family_ . sin ## _family_ ## _port = rgw_servers. _type_ ## _serv . port; \ |
| 201 | memcpy( &sin ## _family_ .sin ## _family_ ## _addr, \ |
| 202 | &rgw_servers. _type_ ## _serv . ip ## _family_ ## _endpoint, \ |
| 203 | sizeof(struct in ## _family_ ## _addr) ); \ |
| 204 | /* This sockopt must be set before binding */ \ |
| 205 | TRACE_DEBUG(ANNOYING, "Setting socket options..."); \ |
| 206 | CHECK_FCT( _udp_setsockopt(AF_INET ## _family_, SERVERS[idx].sock) ); \ |
| 207 | /* OK, now, bind */ \ |
| 208 | TRACE_DEBUG(ANNOYING, "Binding " #_type_ " ip" #_family_ " server..."); \ |
| 209 | CHECK_SYS( bind( SERVERS[idx].sock, \ |
| 210 | (struct sockaddr *)&sin ## _family_, \ |
| 211 | sizeof(struct sockaddr_in ## _family_) ) ); \ |
| 212 | /* Save the server information in SERVERS structure */ \ |
| 213 | SERVERS[idx].type = _portval_; \ |
| 214 | SERVERS[idx].family = AF_INET ## _family_; \ |
| 215 | snprintf(&SERVERS[idx].name[0], sizeof(SERVERS[idx].name), # _type_ "/ip" #_family_); \ |
| 216 | /* Create the server thread */ \ |
| 217 | CHECK_POSIX( pthread_create(&SERVERS[idx].th, NULL, server_thread, &SERVERS[idx]) ); \ |
| 218 | idx++; \ |
| 219 | } \ |
| 220 | } |
| 221 | |
| 222 | int rgw_servers_start(void) |
| 223 | { |
| 224 | int idx = 0; |
| 225 | |
| 226 | TRACE_ENTRY(); |
| 227 | |
| 228 | UDPSERV( auth, RGW_PLG_TYPE_AUTH, ); |
| 229 | UDPSERV( auth, RGW_PLG_TYPE_AUTH, 6 ); |
| 230 | UDPSERV( acct, RGW_PLG_TYPE_ACCT, ); |
| 231 | UDPSERV( acct, RGW_PLG_TYPE_ACCT, 6 ); |
| 232 | |
| 233 | TRACE_DEBUG(FULL, "%d UDP servers started succesfully.", idx); |
| 234 | return 0; |
| 235 | } |
| 236 | |
| 237 | /* Send a RADIUS message */ |
| 238 | int rgw_servers_send(int type, unsigned char *buf, size_t buflen, struct sockaddr *to, uint16_t to_port) |
| 239 | { |
| 240 | int idx = 0; |
| 241 | int ret = 0; |
| 242 | struct sockaddr_storage sto; |
| 243 | char sa_buf[sSA_DUMP_STRLEN]; |
| 244 | |
| 245 | /* Find the appropriate socket to use (not sure if it is important) */ |
| 246 | for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) { |
| 247 | if ( SERVERS[idx].sock && (type == SERVERS[idx].type) && (to->sa_family == SERVERS[idx].family) ) { |
| 248 | ret = 1; |
| 249 | break; |
| 250 | } |
| 251 | } |
| 252 | |
| 253 | if (!ret) { |
| 254 | LOG_E( "Trying to send a message from a disabled server: %s / %s", |
| 255 | (type == RGW_PLG_TYPE_AUTH) ? "Auth" : "Acct", |
| 256 | (to->sa_family == AF_INET) ? "IP (v4)" : "IPv6"); |
| 257 | return EINVAL; |
| 258 | } |
| 259 | |
| 260 | /* Prepare the destination info */ |
| 261 | memset(&sto, 0, sizeof(sto)); |
| 262 | if (to->sa_family == AF_INET) { |
| 263 | memcpy(&sto, to, sizeof(struct sockaddr_in)); |
| 264 | ((struct sockaddr_in *)&sto)->sin_port = to_port; |
| 265 | } else { |
| 266 | memcpy(&sto, to, sizeof(struct sockaddr_in6)); |
| 267 | ((struct sockaddr_in6 *)&sto)->sin6_port = to_port; |
| 268 | } |
| 269 | |
| 270 | fd_sa_sdump_numeric(sa_buf, (sSA*)&sto); |
| 271 | LOG_D("RADIUS: SND %zdB to %s", buflen, sa_buf); |
| 272 | |
| 273 | /* Send */ |
| 274 | ret = sendto(SERVERS[idx].sock, buf, buflen, 0, (struct sockaddr *)&sto, sSAlen(&sto)); |
| 275 | if (ret < 0) { |
| 276 | ret = errno; |
| 277 | TRACE_DEBUG(INFO, "An error prevented sending of a RADIUS message: %s", strerror(ret)); |
| 278 | return ret; |
| 279 | } |
| 280 | if (ret != buflen) { |
| 281 | TRACE_DEBUG(INFO, "Incomplete send: %d bytes / %zd", ret, buflen); |
| 282 | return EAGAIN; |
| 283 | } |
| 284 | |
| 285 | /* Done :) */ |
| 286 | return 0; |
| 287 | } |
| 288 | |
| 289 | void rgw_servers_fini(void) |
| 290 | { |
| 291 | int idx = 0; |
| 292 | |
| 293 | for (idx = 0; idx < sizeof(SERVERS) / sizeof(SERVERS[0]); idx++) { |
| 294 | if (SERVERS[idx].sock == 0) |
| 295 | break; |
| 296 | |
| 297 | CHECK_FCT_DO( fd_thr_term(&SERVERS[idx].th), /* continue */ ); |
| 298 | close(SERVERS[idx].sock); |
| 299 | SERVERS[idx].sock = 0; |
| 300 | } |
| 301 | |
| 302 | } |
| 303 | |
| 304 | |