1 /**************************************************************************************************
2 $Id: tcp.c,v 1.47 2005/04/20 17:30:38 bboy Exp $
3
4 Copyright (C) 2002-2005 Don Moore <bboy@bboy.net>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 **************************************************************************************************/
20
21 #include "named.h"
22
23 /* Make this nonzero to enable debugging for this source file */
24 #define DEBUG_TCP 1
25
26
27 /**************************************************************************************************
28 ACCEPT_TCP_QUERY
29 **************************************************************************************************/
30 int
accept_tcp_query(int fd,int family)31 accept_tcp_query(int fd, int family)
32 {
33 struct sockaddr_in addr4;
34 #if HAVE_IPV6
35 struct sockaddr_in6 addr6;
36 #endif
37 socklen_t addrlen;
38 int rmt_fd;
39 TASK *t;
40
41 #if HAVE_IPV6
42 if (family == AF_INET6)
43 {
44 addrlen = sizeof(struct sockaddr_in6);
45 if ((rmt_fd = accept(fd, (struct sockaddr *)&addr6, &addrlen)) < 0)
46 {
47 return Warn("%s", _("accept (TCPv6)"));
48 }
49 fcntl(rmt_fd, F_SETFL, fcntl(rmt_fd, F_GETFL, 0) | O_NONBLOCK);
50 if (!(t = task_init(NEED_READ, rmt_fd, SOCK_STREAM, AF_INET6, &addr6)))
51 return (-1);
52 }
53 else
54 #endif
55 {
56 addrlen = sizeof(struct sockaddr_in);
57 if ((rmt_fd = accept(fd, (struct sockaddr *)&addr4, &addrlen)) < 0)
58 {
59 return Warn("%s", _("accept (TCP)"));
60 }
61 fcntl(rmt_fd, F_SETFL, fcntl(rmt_fd, F_GETFL, 0) | O_NONBLOCK);
62 if (!(t = task_init(NEED_READ, rmt_fd, SOCK_STREAM, AF_INET, &addr4)))
63 return (-1);
64 }
65
66 #if DEBUG_ENABLED && DEBUG_TCP
67 Debug("%s: TCP connection accepted", clientaddr(t));
68 #endif
69
70 return 0;
71 }
72 /*--- accept_tcp_query() ------------------------------------------------------------------------*/
73
74
75 /**************************************************************************************************
76 READ_TCP_LENGTH
77 The first two octets of a TCP question are the length. Read them.
78 Returns 0 on success, -1 on failure.
79 **************************************************************************************************/
80 static int
read_tcp_length(TASK * t)81 read_tcp_length(TASK *t)
82 {
83 int rv;
84 char len[2];
85
86 if ((rv = recv(t->fd, len, 2, 0)) != 2)
87 {
88 if (rv < 0)
89 {
90 if (errno == EAGAIN)
91 return (0);
92 if (errno != ECONNRESET)
93 Warn("%s: %s", clientaddr(t), _("recv (length) (TCP)"));
94 return (-1);
95 }
96 if (rv == 0)
97 return (-1);
98 return Warnx("%s: %s", clientaddr(t), _("TCP message length invalid"));
99 }
100
101 if ((t->len = ((len[0] << 8) | (len[1]))) < DNS_HEADERSIZE)
102 return Warnx("%s: %s (%d octet%s)", clientaddr(t), _("TCP message too short"), t->len, S(t->len));
103 if (t->len > DNS_MAXPACKETLEN_TCP)
104 return Warnx("%s: %s (%d octet%s)", clientaddr(t), _("TCP message too long"), t->len, S(t->len));
105
106 if (!(t->query = calloc(1, t->len + 1)))
107 Err(_("out of memory"));
108 t->offset = 0;
109 return (0);
110 }
111 /*--- read_tcp_length() -------------------------------------------------------------------------*/
112
113
114 /**************************************************************************************************
115 READ_TCP_QUERY
116 Returns 0 on success, -1 on failure.
117 **************************************************************************************************/
118 int
read_tcp_query(TASK * t)119 read_tcp_query(TASK *t)
120 {
121 unsigned char *end;
122 int rv;
123
124 /* Read packet length if we haven't already */
125 if (!t->len)
126 return read_tcp_length(t);
127
128 end = t->query + t->len;
129
130 /* Read whatever data is ready */
131 if ((rv = recv(t->fd, t->query + t->offset, t->len - t->offset, 0)) < 0)
132 return Warn("%s: %s", clientaddr(t), _("recv (TCP)"));
133 if (!rv)
134 return (-1); /* Client closed connection */
135
136 #if DEBUG_ENABLED && DEBUG_TCP
137 Debug("%s: 2+%d TCP octets in", clientaddr(t), rv);
138 #endif
139
140 t->offset += rv;
141 if (t->offset > t->len)
142 return Warnx("%s: %s", clientaddr(t), _("TCP message data too long"));
143 if (t->offset < t->len)
144 return 0; /* Not finished reading */
145 t->offset = 0; /* Reset offset for writing reply */
146
147 return new_task(t, t->query, t->len);
148 }
149 /*--- read_tcp_query() --------------------------------------------------------------------------*/
150
151
152 /**************************************************************************************************
153 WRITE_TCP_LENGTH
154 Writes the length octets for TCP reply. Returns 0 on success, -1 on failure.
155 **************************************************************************************************/
156 static int
write_tcp_length(TASK * t)157 write_tcp_length(TASK *t)
158 {
159 char len[2], *l;
160 int rv;
161
162 l = len;
163 DNS_PUT16(l, t->replylen);
164
165 if ((rv = write(t->fd, len + t->offset, SIZE16 - t->offset)) < 0)
166 {
167 if (errno == EINTR)
168 return 0;
169 return Warn("%s: %s", clientaddr(t), _("write (length) (TCP)"));
170 }
171 if (!rv)
172 return (-1); /* Client closed connection */
173 t->offset += rv;
174 if (t->offset >= SIZE16)
175 {
176 t->len_written = 1;
177 t->offset = 0;
178 }
179 return 0;
180 }
181 /*--- write_tcp_length() ------------------------------------------------------------------------*/
182
183
184 /**************************************************************************************************
185 WRITE_TCP_REPLY
186 Returns 0 on success, -1 on error. If -1 is returned, the task is no longer valid.
187 **************************************************************************************************/
188 int
write_tcp_reply(TASK * t)189 write_tcp_reply(TASK *t)
190 {
191 int rv, rmt_fd;
192 struct sockaddr_in addr4;
193 #if HAVE_IPV6
194 struct sockaddr_in6 addr6;
195 #endif
196
197 /* Write TCP length if we haven't already */
198 if (!t->len_written)
199 {
200 if (write_tcp_length(t) < 0)
201 {
202 dequeue(Tasks, t);
203 return (-1);
204 }
205 return (0);
206 }
207
208 /* Write the reply */
209 if ((rv = write(t->fd, t->reply + t->offset, t->replylen - t->offset)) < 0)
210 {
211 if (errno == EINTR)
212 return (0);
213 dequeue(Tasks, t);
214 return Warn("%s: %s", clientaddr(t), _("write (TCP)"));
215 }
216 if (!rv)
217 {
218 dequeue(Tasks, t);
219 return (-1); /* Client closed connection */
220 }
221 t->offset += rv;
222 if (t->offset < t->replylen)
223 return (0); /* Not finished yet... */
224
225 /* Task complete; reset. The TCP client must be able to perform multiple queries on
226 the same connection (BIND8 AXFR does this for sure) */
227 #if HAVE_IPV6
228 if (t->family == AF_INET6)
229 {
230 memcpy(&addr6, &t->addr6, sizeof(struct sockaddr_in6));
231 rmt_fd = t->fd;
232 dequeue(Tasks, t);
233
234 /* Reinitialize to allow multiple queries on TCP */
235 if (!(t = task_init(NEED_READ, rmt_fd, SOCK_STREAM, AF_INET6, &addr6)))
236 return (-2);
237 }
238 else
239 #endif
240 {
241 memcpy(&addr4, &t->addr4, sizeof(struct sockaddr_in));
242 rmt_fd = t->fd;
243 dequeue(Tasks, t);
244
245 /* Reinitialize to allow multiple queries on TCP */
246 if (!(t = task_init(NEED_READ, rmt_fd, SOCK_STREAM, AF_INET, &addr4)))
247 return (-2);
248 }
249 return (0);
250 }
251 /*--- write_tcp_reply() -------------------------------------------------------------------------*/
252
253 /* vi:set ts=3: */
254 /* NEED_PO */
255