]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Header file and protocol handler for RADIUS over TCP
authorDante <dante@dante.org.uk>
Tue, 15 Sep 2009 15:17:40 +0000 (17:17 +0200)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 15 Sep 2009 15:17:40 +0000 (17:17 +0200)
This is the first step to getting full support for RADIUS
over TCP into the server.

Signed-off-by: Alan T. DeKok <aland@freeradius.org>
src/include/tcp.h [new file with mode: 0644]
src/lib/tcp.c [new file with mode: 0644]

diff --git a/src/include/tcp.h b/src/include/tcp.h
new file mode 100644 (file)
index 0000000..5f9bea4
--- /dev/null
@@ -0,0 +1,84 @@
+#ifndef FR_TCP_H
+#define FR_TCP_H
+
+/*
+ * tcp.h       RADIUS over TCP
+ *
+ * Version:    $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright (C) 2009 Dante http://dante.net
+ */
+
+#include <freeradius-devel/ident.h>
+RCSIDH(tcp_h, "$Id$")
+
+/*
+ *     Application-layer watchdog from RFC 3539, Appendix A.
+ */
+typedef enum fr_watchdog_t {
+       ALW_INITIAL = 0,
+       ALW_OK,
+       ALW_SUSPECT,
+       ALW_DOWN,
+       ALW_REOPEN
+} fr_watchdog_t;
+
+#define ALW_TWINIT (6)
+
+typedef struct fr_tcp_radius_t {
+       int             fd;
+       fr_ipaddr_t     src_ipaddr;
+       fr_ipaddr_t     dst_ipaddr;
+       int             src_port;
+       int             dst_port;
+
+       int             num_packets;
+       int             lifetime;
+
+       time_t          opened;
+       time_t          last_packet;
+#ifdef WITH_TCP_PING
+       struct timeval  when;
+       void            *ev;
+#endif
+
+       int             state;
+       int             ping_interval;
+       int             num_pings_to_alive;
+       int             num_received_pings;
+       int             num_pings_sent;
+       int             ping_timeout;
+
+       int             used;
+       void            *ev;
+       RADIUS_PACKET   **ids[256];
+} fr_tcp_radius_t;
+
+int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port);
+int fr_tcp_client_socket(fr_ipaddr_t *ipaddr, int port);
+int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags);
+RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags);
+RADIUS_PACKET *fr_tcp_accept(int sockfd);
+ssize_t fr_tcp_write_packet(RADIUS_PACKET *packet);
+
+int fr_tcp_list_init(fr_tcp_radius_t *list);
+int fr_tcp_list_insert(RADIUS_PACKET **packet, int num,
+                      fr_tcp_radius_t *array[]);
+
+int fr_tcp_id_free(int ids[256], int id);
+
+#endif /* FR_TCP_H */
diff --git a/src/lib/tcp.c b/src/lib/tcp.c
new file mode 100644 (file)
index 0000000..6500a95
--- /dev/null
@@ -0,0 +1,409 @@
+/*
+ * tcp.c       TCP-specific functions.
+ *
+ * Version:    $Id$
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright (C) 2009 Dante http://dante.net
+ */
+
+#include       <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include       <freeradius-devel/libradius.h>
+#include       <freeradius-devel/tcp.h>
+
+#ifdef WITH_TCP
+
+/* FIXME: into common RADIUS header? */
+#define MAX_PACKET_LEN 4096
+
+/*
+ *     Open a socket on the given IP and port.
+ */
+int fr_tcp_socket(fr_ipaddr_t *ipaddr, int port)
+{
+       int sockfd;
+       int on = 1;
+       struct sockaddr_storage salocal;
+       socklen_t       salen;
+
+       if ((port < 0) || (port > 65535)) {
+               fr_strerror_printf("Port %d is out of allowed bounds", port);
+               return -1;
+       }
+
+       sockfd = socket(ipaddr->af, SOCK_STREAM, 0);
+       if (sockfd < 0) {
+               return sockfd;
+       }
+
+       if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) {
+               return sockfd;  /* don't bind it */
+       }
+
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       if (ipaddr->af == AF_INET6) {
+               /*
+                *      Listening on '::' does NOT get you IPv4 to
+                *      IPv6 mapping.  You've got to listen on an IPv4
+                *      address, too.  This makes the rest of the server
+                *      design a little simpler.
+                */
+#ifdef IPV6_V6ONLY
+
+               if (IN6_IS_ADDR_UNSPECIFIED(&ipaddr->ipaddr.ip6addr)) {
+                       setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY,
+                                  (char *)&on, sizeof(on));
+               }
+#endif /* IPV6_V6ONLY */
+       }
+#endif /* HAVE_STRUCT_SOCKADDR_IN6 */
+
+       if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
+               fr_strerror_printf("Failed in setsockopt(): %s", strerror(errno));
+               close(sockfd);
+               return -1;
+       }
+       if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+               fr_strerror_printf("Failed in bind(): %s", strerror(errno));
+               close(sockfd);
+               return -1;
+       }
+
+       if (listen(sockfd, 8) < 0) {
+               fr_strerror_printf("Failed in listen(): %s", strerror(errno));
+               close(sockfd);
+               return -1;
+       }
+
+#if 0
+#ifdef O_NONBLOCK
+       {
+               int flags;
+               
+               if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0)  {
+                       fr_strerror_printf("Failure getting socket flags: %s",
+                                  strerror(errno));
+                       close(sockfd);
+                       return -1;
+               }
+               
+               flags |= O_NONBLOCK;
+               if( fcntl(sockfd, F_SETFL, flags) < 0) {
+                       fr_strerror_printf("Failure setting socket flags: %s",
+                                  strerror(errno));
+                       close(sockfd);
+                       return -1;
+               }
+       }
+#endif
+#endif
+       return sockfd;
+}
+
+
+/*
+ *     Open a socket TO the given IP and port.
+ */
+int fr_tcp_client_socket(fr_ipaddr_t *ipaddr, int port)
+{
+       int sockfd;
+       struct sockaddr_storage salocal;
+       socklen_t       salen;
+
+       if ((port < 0) || (port > 65535)) {
+               fr_strerror_printf("Port %d is out of allowed bounds", port);
+               return -1;
+       }
+
+       sockfd = socket(ipaddr->af, SOCK_STREAM, 0);
+       if (sockfd < 0) {
+               return sockfd;
+       }
+
+#if 0
+#ifdef O_NONBLOCK
+       {
+               int flags;
+               
+               if ((flags = fcntl(sockfd, F_GETFL, NULL)) < 0)  {
+                       fr_strerror_printf("Failure getting socket flags: %s",
+                                  strerror(errno));
+                       close(sockfd);
+                       return -1;
+               }
+               
+               flags |= O_NONBLOCK;
+               if( fcntl(sockfd, F_SETFL, flags) < 0) {
+                       fr_strerror_printf("Failure setting socket flags: %s",
+                                  strerror(errno));
+                       close(sockfd);
+                       return -1;
+               }
+       }
+#endif
+#endif
+
+       if (!fr_ipaddr2sockaddr(ipaddr, port, &salocal, &salen)) {
+               return -1;
+       }
+
+       /*
+        *      FIXME: If EINPROGRESS, then tell the caller that
+        *      somehow.  The caller can then call connect() when the
+        *      socket is ready...
+        */
+       if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
+               fr_strerror_printf("Failed in connect(): %s", strerror(errno));
+               close(sockfd);
+               return -1;
+       }
+
+       return sockfd;
+}
+
+
+RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags)
+{
+       RADIUS_PACKET *packet = rad_alloc(0);
+
+       if (!packet) return NULL;
+
+       packet->sockfd = sockfd;
+
+       if (fr_tcp_read_packet(packet, flags) != 1) {
+               rad_free(&packet);
+               return NULL;
+       }
+
+       return packet;
+}
+
+
+/*
+ *     Receives a packet, assuming that the RADIUS_PACKET structure
+ *     has been filled out already.
+ *
+ *     This ASSUMES that the packet is allocated && fields
+ *     initialized.
+ *
+ *     This ASSUMES that the socket is marked as O_NONBLOCK, which
+ *     the function above does set, if your system supports it.
+ *
+ *     Calling this function MAY change sockfd,
+ *     if src_ipaddr.af == AF_UNSPEC.
+ */
+int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags)
+{
+       ssize_t len;
+
+       /*
+        *      No data allocated.  Read the 4-byte header into
+        *      a temporary buffer.
+        */
+       if (!packet->data) {
+               int packet_len;
+
+               len = recv(packet->sockfd, packet->vector + packet->data_len,
+                          4 - packet->data_len, 0);
+               if ((len == 0) || /* clean close */
+                   ((len < 0) && (errno == ECONNRESET))) { /* forced */
+                       return -2;
+               }
+
+               if (len < 0) {
+                       fr_strerror_printf("Error receiving packet: %s",
+                                  strerror(errno));
+                       return -1;
+               }
+
+               packet->data_len += len;
+               if (packet->data_len < 4) { /* want more data */
+                       return 0;
+               }
+
+               packet_len = (packet->vector[2] << 8) | packet->vector[3];
+
+               if (packet_len < AUTH_HDR_LEN) {
+                       fr_strerror_printf("Discarding packet: Smaller than RFC minimum of 20 bytes.");
+                       return -1;
+               }
+
+               /*
+                *      If the packet is too big, then the socket is bad.
+                */
+               if (packet_len > MAX_PACKET_LEN) {
+                       fr_strerror_printf("Discarding packet: Larger than RFC limitation of 4096 bytes.");
+                       return -1;
+               }
+               
+               packet->data = malloc(packet_len);
+               if (!packet->data) {
+                       fr_strerror_printf("Out of memory");
+                       return -1;
+               }
+
+               packet->data_len = packet_len;
+               packet->partial = 4;
+               memcpy(packet->data, packet->vector, 4);
+       }
+
+       /*
+        *      Try to read more data.
+        */
+       len = recv(packet->sockfd, packet->data + packet->partial,
+                  packet->data_len - packet->partial, 0);
+       if ((len == 0) || /* clean close */
+           ((len < 0) && (errno == ECONNRESET))) { /* forced */
+         return -2;
+       }
+
+       if (len < 0) {
+               fr_strerror_printf("Error receiving packet: %s", strerror(errno));
+               return -1;
+       }
+
+       packet->partial += len;
+
+       if (packet->partial < packet->data_len) {
+               return 0;
+       }
+
+       /*
+        *      See if it's a well-formed RADIUS packet.
+        */
+       if (!rad_packet_ok(packet, flags)) {
+               return -1;
+       }
+
+       /*
+        *      Explicitly set the VP list to empty.
+        */
+       packet->vps = NULL;
+
+       if (fr_debug_flag) {
+               char ip_buf[128], buffer[256];
+
+               if (packet->src_ipaddr.af != AF_UNSPEC) {
+                       inet_ntop(packet->src_ipaddr.af,
+                                 &packet->src_ipaddr.ipaddr,
+                                 ip_buf, sizeof(ip_buf));
+                       snprintf(buffer, sizeof(buffer), "host %s port %d",
+                                ip_buf, packet->src_port);
+               } else {
+                       snprintf(buffer, sizeof(buffer), "socket %d",
+                                packet->sockfd);
+               }
+
+
+               if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) {
+                       DEBUG("rad_recv: %s packet from %s",
+                             fr_packet_codes[packet->code], buffer);
+               } else {
+                       DEBUG("rad_recv: Packet from %s code=%d",
+                             buffer, packet->code);
+               }
+               DEBUG(", id=%d, length=%ld\n", packet->id, packet->data_len);
+       }
+
+       return 1;               /* done reading the packet */
+}
+
+RADIUS_PACKET *fr_tcp_accept(int sockfd)
+{
+       int newfd;
+       socklen_t salen;
+       RADIUS_PACKET *packet;
+       struct sockaddr_storage src;
+       
+       salen = sizeof(src);
+
+       newfd = accept(sockfd, (struct sockaddr *) &src, &salen);
+       if (newfd < 0) {
+               /*
+                *      Non-blocking sockets must handle this.
+                */
+               if (errno == EWOULDBLOCK) {
+                       packet = rad_alloc(0);
+                       if (!packet) return NULL;
+
+                       packet->sockfd = sockfd;
+                       packet->src_ipaddr.af = AF_UNSPEC;
+                       return packet;
+               }
+       }
+               
+       packet = rad_alloc(0);
+       if (!packet) return NULL;
+
+       if (src.ss_family == AF_INET) {
+               struct sockaddr_in      s4;
+
+               memcpy(&s4, &src, sizeof(s4));
+               packet->src_ipaddr.af = AF_INET;
+               packet->src_ipaddr.ipaddr.ip4addr = s4.sin_addr;
+               packet->src_port = ntohs(s4.sin_port);
+
+#ifdef HAVE_STRUCT_SOCKADDR_IN6
+       } else if (src.ss_family == AF_INET6) {
+               struct sockaddr_in6     s6;
+
+               memcpy(&s6, &src, sizeof(s6));
+               packet->src_ipaddr.af = AF_INET6;
+               packet->src_ipaddr.ipaddr.ip6addr = s6.sin6_addr;
+               packet->src_port = ntohs(s6.sin6_port);
+
+#endif
+       } else {
+               rad_free(&packet);
+               return NULL;
+       }
+
+       packet->sockfd = newfd;
+
+       /*
+        *      Note: Caller has to set dst_ipaddr && dst_port.
+        */
+       return packet;
+}
+
+
+/*
+ *     Writes a packet, assuming it's already been encoded.
+ *
+ *     It returns the number of bytes written, which MAY be less than
+ *     the packet size (data_len).  It is the caller's responsibility
+ *     to check the return code, and to schedule writes again.
+ */
+ssize_t fr_tcp_write_packet(RADIUS_PACKET *packet)
+{
+       ssize_t rcode;
+
+       if (!packet || !packet->data) return 0;
+
+       if (packet->partial >= packet->data_len) return packet->data_len;
+
+       rcode = write(packet->sockfd, packet->data + packet->partial,
+                     packet->data_len - packet->partial);
+       if (rcode < 0) return packet->partial; /* ignore most errors */
+
+       packet->partial += rcode;
+
+       return packet->partial;
+}
+#endif /* WITH_TCP */