Add compile time checking for config pointers
[freeradius.git] / src / lib / tcp.c
1 /*
2  * tcp.c        TCP-specific functions.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright (C) 2009 Dante http://dante.net
21  */
22
23 RCSID("$Id$")
24
25 #include        <freeradius-devel/libradius.h>
26
27 #ifdef WITH_TCP
28
29 /* FIXME: into common RADIUS header? */
30 #define MAX_PACKET_LEN 4096
31
32 /*
33  *      Open a socket TO the given IP and port.
34  */
35 int fr_tcp_client_socket(fr_ipaddr_t *src_ipaddr,
36                          fr_ipaddr_t *dst_ipaddr, uint16_t dst_port)
37 {
38         int sockfd;
39         struct sockaddr_storage salocal;
40         socklen_t       salen;
41
42         if (!dst_ipaddr) return -1;
43
44         sockfd = socket(dst_ipaddr->af, SOCK_STREAM, 0);
45         if (sockfd < 0) {
46                 return sockfd;
47         }
48
49         /*
50          *      Allow the caller to bind us to a specific source IP.
51          */
52         if (src_ipaddr && (src_ipaddr->af != AF_UNSPEC)) {
53                 if (!fr_ipaddr2sockaddr(src_ipaddr, 0, &salocal, &salen)) {
54                         close(sockfd);
55                         return -1;
56                 }
57
58                 if (bind(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
59                         fr_strerror_printf("Failure binding to IP: %s",
60                                            fr_syserror(errno));
61                         close(sockfd);
62                         return -1;
63                 }
64         }
65
66         if (!fr_ipaddr2sockaddr(dst_ipaddr, dst_port, &salocal, &salen)) {
67                 close(sockfd);
68                 return -1;
69         }
70
71         /*
72          *      FIXME: If EINPROGRESS, then tell the caller that
73          *      somehow.  The caller can then call connect() when the
74          *      socket is ready...
75          */
76         if (connect(sockfd, (struct sockaddr *) &salocal, salen) < 0) {
77                 fr_strerror_printf("Failed in connect(): %s", fr_syserror(errno));
78                 close(sockfd);
79                 return -1;
80         }
81
82         /*
83          *      Set non-block *AFTER* connecting to the remote server
84          *      so it doesn't return immediately.
85          */
86         if (fr_nonblock(sockfd) < 0) {
87                 close(sockfd);
88                 return -1;
89         }
90
91         return sockfd;
92 }
93
94
95 RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags)
96 {
97         RADIUS_PACKET *packet = rad_alloc(NULL, 0);
98
99         if (!packet) return NULL;
100
101         packet->sockfd = sockfd;
102
103         if (fr_tcp_read_packet(packet, flags) != 1) {
104                 rad_free(&packet);
105                 return NULL;
106         }
107
108         return packet;
109 }
110
111
112 /*
113  *      Receives a packet, assuming that the RADIUS_PACKET structure
114  *      has been filled out already.
115  *
116  *      This ASSUMES that the packet is allocated && fields
117  *      initialized.
118  *
119  *      This ASSUMES that the socket is marked as O_NONBLOCK, which
120  *      the function above does set, if your system supports it.
121  *
122  *      Calling this function MAY change sockfd,
123  *      if src_ipaddr.af == AF_UNSPEC.
124  */
125 int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags)
126 {
127         ssize_t len;
128
129         /*
130          *      No data allocated.  Read the 4-byte header into
131          *      a temporary buffer.
132          */
133         if (!packet->data) {
134                 int packet_len;
135
136                 len = recv(packet->sockfd, packet->vector + packet->data_len,
137                            4 - packet->data_len, 0);
138                 if (len == 0) return -2; /* clean close */
139
140 #ifdef ECONNRESET
141                 if ((len < 0) && (errno == ECONNRESET)) { /* forced */
142                         return -2;
143                 }
144 #endif
145
146                 if (len < 0) {
147                         fr_strerror_printf("Error receiving packet: %s",
148                                    fr_syserror(errno));
149                         return -1;
150                 }
151
152                 packet->data_len += len;
153                 if (packet->data_len < 4) { /* want more data */
154                         return 0;
155                 }
156
157                 packet_len = (packet->vector[2] << 8) | packet->vector[3];
158
159                 if (packet_len < AUTH_HDR_LEN) {
160                         fr_strerror_printf("Discarding packet: Smaller than RFC minimum of 20 bytes");
161                         return -1;
162                 }
163
164                 /*
165                  *      If the packet is too big, then the socket is bad.
166                  */
167                 if (packet_len > MAX_PACKET_LEN) {
168                         fr_strerror_printf("Discarding packet: Larger than RFC limitation of 4096 bytes");
169                         return -1;
170                 }
171
172                 packet->data = talloc_array(packet, uint8_t, packet_len);
173                 if (!packet->data) {
174                         fr_strerror_printf("Out of memory");
175                         return -1;
176                 }
177
178                 packet->data_len = packet_len;
179                 packet->partial = 4;
180                 memcpy(packet->data, packet->vector, 4);
181         }
182
183         /*
184          *      Try to read more data.
185          */
186         len = recv(packet->sockfd, packet->data + packet->partial,
187                    packet->data_len - packet->partial, 0);
188         if (len == 0) return -2; /* clean close */
189
190 #ifdef ECONNRESET
191         if ((len < 0) && (errno == ECONNRESET)) { /* forced */
192                 return -2;
193         }
194 #endif
195
196         if (len < 0) {
197                 fr_strerror_printf("Error receiving packet: %s", fr_syserror(errno));
198                 return -1;
199         }
200
201         packet->partial += len;
202
203         if (packet->partial < packet->data_len) {
204                 return 0;
205         }
206
207         /*
208          *      See if it's a well-formed RADIUS packet.
209          */
210         if (!rad_packet_ok(packet, flags, NULL)) {
211                 return -1;
212         }
213
214         /*
215          *      Explicitly set the VP list to empty.
216          */
217         packet->vps = NULL;
218
219         if (fr_debug_flag) {
220                 char ip_buf[128], buffer[256];
221
222                 if (packet->src_ipaddr.af != AF_UNSPEC) {
223                         inet_ntop(packet->src_ipaddr.af,
224                                   &packet->src_ipaddr.ipaddr,
225                                   ip_buf, sizeof(ip_buf));
226                         snprintf(buffer, sizeof(buffer), "host %s port %d",
227                                  ip_buf, packet->src_port);
228                 } else {
229                         snprintf(buffer, sizeof(buffer), "socket %d",
230                                  packet->sockfd);
231                 }
232
233
234                 if (is_radius_code(packet->code)) {
235                         DEBUG("Received %s packet from %s",
236                               fr_packet_codes[packet->code], buffer);
237                 } else {
238                         DEBUG("Received packet from %s code %d",
239                               buffer, packet->code);
240                 }
241                 DEBUG(", id=%d, length=%zu\n", packet->id, packet->data_len);
242         }
243
244         return 1;               /* done reading the packet */
245 }
246
247 #endif /* WITH_TCP */