newvector should be a bool
[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         return sockfd;
83 }
84
85
86 RADIUS_PACKET *fr_tcp_recv(int sockfd, int flags)
87 {
88         RADIUS_PACKET *packet = rad_alloc(NULL, false);
89
90         if (!packet) return NULL;
91
92         packet->sockfd = sockfd;
93
94         if (fr_tcp_read_packet(packet, flags) != 1) {
95                 rad_free(&packet);
96                 return NULL;
97         }
98
99         return packet;
100 }
101
102
103 /*
104  *      Receives a packet, assuming that the RADIUS_PACKET structure
105  *      has been filled out already.
106  *
107  *      This ASSUMES that the packet is allocated && fields
108  *      initialized.
109  *
110  *      This ASSUMES that the socket is marked as O_NONBLOCK, which
111  *      the function above does set, if your system supports it.
112  *
113  *      Calling this function MAY change sockfd,
114  *      if src_ipaddr.af == AF_UNSPEC.
115  */
116 int fr_tcp_read_packet(RADIUS_PACKET *packet, int flags)
117 {
118         ssize_t len;
119
120         /*
121          *      No data allocated.  Read the 4-byte header into
122          *      a temporary buffer.
123          */
124         if (!packet->data) {
125                 int packet_len;
126
127                 len = recv(packet->sockfd, packet->vector + packet->data_len,
128                            4 - packet->data_len, 0);
129                 if (len == 0) return -2; /* clean close */
130
131 #ifdef ECONNRESET
132                 if ((len < 0) && (errno == ECONNRESET)) { /* forced */
133                         return -2;
134                 }
135 #endif
136
137                 if (len < 0) {
138                         fr_strerror_printf("Error receiving packet: %s",
139                                    fr_syserror(errno));
140                         return -1;
141                 }
142
143                 packet->data_len += len;
144                 if (packet->data_len < 4) { /* want more data */
145                         return 0;
146                 }
147
148                 packet_len = (packet->vector[2] << 8) | packet->vector[3];
149
150                 if (packet_len < AUTH_HDR_LEN) {
151                         fr_strerror_printf("Discarding packet: Smaller than RFC minimum of 20 bytes");
152                         return -1;
153                 }
154
155                 /*
156                  *      If the packet is too big, then the socket is bad.
157                  */
158                 if (packet_len > MAX_PACKET_LEN) {
159                         fr_strerror_printf("Discarding packet: Larger than RFC limitation of 4096 bytes");
160                         return -1;
161                 }
162
163                 packet->data = talloc_array(packet, uint8_t, packet_len);
164                 if (!packet->data) {
165                         fr_strerror_printf("Out of memory");
166                         return -1;
167                 }
168
169                 packet->data_len = packet_len;
170                 packet->partial = 4;
171                 memcpy(packet->data, packet->vector, 4);
172         }
173
174         /*
175          *      Try to read more data.
176          */
177         len = recv(packet->sockfd, packet->data + packet->partial,
178                    packet->data_len - packet->partial, 0);
179         if (len == 0) return -2; /* clean close */
180
181 #ifdef ECONNRESET
182         if ((len < 0) && (errno == ECONNRESET)) { /* forced */
183                 return -2;
184         }
185 #endif
186
187         if (len < 0) {
188                 fr_strerror_printf("Error receiving packet: %s", fr_syserror(errno));
189                 return -1;
190         }
191
192         packet->partial += len;
193
194         if (packet->partial < packet->data_len) {
195                 return 0;
196         }
197
198         /*
199          *      See if it's a well-formed RADIUS packet.
200          */
201         if (!rad_packet_ok(packet, flags, NULL)) {
202                 return -1;
203         }
204
205         /*
206          *      Explicitly set the VP list to empty.
207          */
208         packet->vps = NULL;
209
210         if (fr_debug_flag) {
211                 char ip_buf[128], buffer[256];
212
213                 if (packet->src_ipaddr.af != AF_UNSPEC) {
214                         inet_ntop(packet->src_ipaddr.af,
215                                   &packet->src_ipaddr.ipaddr,
216                                   ip_buf, sizeof(ip_buf));
217                         snprintf(buffer, sizeof(buffer), "host %s port %d",
218                                  ip_buf, packet->src_port);
219                 } else {
220                         snprintf(buffer, sizeof(buffer), "socket %d",
221                                  packet->sockfd);
222                 }
223
224
225                 if (is_radius_code(packet->code)) {
226                         DEBUG("Received %s packet from %s",
227                               fr_packet_codes[packet->code], buffer);
228                 } else {
229                         DEBUG("Received packet from %s code %d",
230                               buffer, packet->code);
231                 }
232                 DEBUG(", id=%d, length=%zu\n", packet->id, packet->data_len);
233         }
234
235         return 1;               /* done reading the packet */
236 }
237
238 #endif /* WITH_TCP */