Use shorts for chunk sizes when possible.
[polipo.git] / tunnel.c
blob80ab46d9d6d55a24b78dd6f0ade0e3bb3747be37
1 /*
2 Copyright (c) 2004-2006 by Juliusz Chroboczek
4 Permission is hereby granted, free of charge, to any person obtaining a copy
5 of this software and associated documentation files (the "Software"), to deal
6 in the Software without restriction, including without limitation the rights
7 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 copies of the Software, and to permit persons to whom the Software is
9 furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included in
12 all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 THE SOFTWARE.
23 #include "polipo.h"
25 #ifdef NO_TUNNEL
27 void
28 do_tunnel(int fd, char *buf, int offset, int len, AtomPtr url)
30 int n;
31 assert(buf);
33 n = httpWriteErrorHeaders(buf, CHUNK_SIZE, 0, 1,
34 501, internAtom("CONNECT not available "
35 "in this version."),
36 1, NULL, url->string, url->length, NULL);
37 releaseAtom(url);
38 if(n >= 0) {
39 /* This is completely wrong. The write is non-blocking, and we
40 don't reschedule it if it fails. But then, if the write
41 blocks, we'll simply drop the connection with no error message. */
42 write(fd, buf, n);
44 dispose_chunk(buf);
45 lingeringClose(fd);
46 return;
49 #else
51 static void tunnelDispatch(TunnelPtr);
52 static int tunnelRead1Handler(int, FdEventHandlerPtr, StreamRequestPtr);
53 static int tunnelRead2Handler(int, FdEventHandlerPtr, StreamRequestPtr);
54 static int tunnelWrite1Handler(int, FdEventHandlerPtr, StreamRequestPtr);
55 static int tunnelWrite2Handler(int, FdEventHandlerPtr, StreamRequestPtr);
56 static int tunnelDnsHandler(int, GethostbynameRequestPtr);
57 static int tunnelConnectionHandler(int, FdEventHandlerPtr, ConnectRequestPtr);
58 static int tunnelSocksHandler(int, SocksRequestPtr);
59 static int tunnelHandlerCommon(int, TunnelPtr);
60 static int tunnelError(TunnelPtr, int, AtomPtr);
62 static int
63 circularBufferFull(CircularBufferPtr buf)
65 if(buf->head == buf->tail - 1)
66 return 1;
67 if(buf->head == CHUNK_SIZE - 1 && buf->tail == 0)
68 return 1;
69 return 0;
72 static int
73 circularBufferEmpty(CircularBufferPtr buf)
75 return buf->head == buf->tail;
78 static TunnelPtr
79 makeTunnel(int fd, char *buf, int offset, int len)
81 TunnelPtr tunnel;
82 assert(offset < CHUNK_SIZE);
84 tunnel = malloc(sizeof(TunnelRec));
85 if(tunnel == NULL)
86 return NULL;
88 tunnel->hostname = NULL;
89 tunnel->port = -1;
90 tunnel->flags = 0;
91 tunnel->fd1 = fd;
92 tunnel->fd2 = -1;
93 tunnel->buf1.buf = NULL;
94 tunnel->buf1.tail = 0;
95 tunnel->buf1.head = 0;
96 tunnel->buf2.buf = buf;
97 tunnel->buf2.tail = 0;
98 tunnel->buf2.head = offset;
99 return tunnel;
102 static void
103 destroyTunnel(TunnelPtr tunnel)
105 assert(tunnel->fd1 < 0 && tunnel->fd2 < 0);
106 releaseAtom(tunnel->hostname);
107 if(tunnel->buf1.buf)
108 dispose_chunk(tunnel->buf1.buf);
109 if(tunnel->buf2.buf)
110 dispose_chunk(tunnel->buf2.buf);
111 free(tunnel);
114 void
115 do_tunnel(int fd, char *buf, int offset, int len, AtomPtr url)
117 TunnelPtr tunnel;
118 int port;
119 char *p, *q;
121 tunnel = makeTunnel(fd, buf, offset, len);
122 if(tunnel == NULL) {
123 do_log(L_ERROR, "Couldn't allocate tunnel.\n");
124 releaseAtom(url);
125 dispose_chunk(buf);
126 close(fd);
127 return;
130 p = memrchr(url->string, ':', url->length);
131 q = NULL;
132 if(p)
133 port = strtol(p + 1, &q, 10);
134 if(!p || q != url->string + url->length) {
135 do_log(L_ERROR, "Couldn't parse CONNECT.\n");
136 releaseAtom(url);
137 tunnelError(tunnel, 400, internAtom("Couldn't parse CONNECT"));
138 return;
140 tunnel->hostname = internAtomLowerN(url->string, p - url->string);
141 if(tunnel->hostname == NULL) {
142 releaseAtom(url);
143 tunnelError(tunnel, 501, internAtom("Couldn't allocate hostname"));
144 return;
147 if(!intListMember(port, tunnelAllowedPorts)) {
148 releaseAtom(url);
149 tunnelError(tunnel, 403, internAtom("Forbidden port"));
150 return;
152 tunnel->port = port;
154 releaseAtom(url);
156 if(socksParentProxy)
157 do_socks_connect(parentHost ?
158 parentHost->string : tunnel->hostname->string,
159 parentHost ? parentPort : tunnel->port,
160 tunnelSocksHandler, tunnel);
161 else
162 do_gethostbyname(parentHost ?
163 parentHost->string : tunnel->hostname->string, 0,
164 tunnelDnsHandler, tunnel);
167 static int
168 tunnelDnsHandler(int status, GethostbynameRequestPtr request)
170 TunnelPtr tunnel = request->data;
172 if(status <= 0) {
173 tunnelError(tunnel, 504,
174 internAtomError(-status,
175 "Host %s lookup failed",
176 atomString(tunnel->hostname)));
177 return 1;
180 if(request->addr->string[0] == DNS_CNAME) {
181 if(request->count > 10)
182 tunnelError(tunnel, 504, internAtom("CNAME loop"));
183 do_gethostbyname(request->addr->string + 1, request->count + 1,
184 tunnelDnsHandler, tunnel);
185 return 1;
188 do_connect(retainAtom(request->addr), 0,
189 parentHost ? parentPort : tunnel->port,
190 tunnelConnectionHandler, tunnel);
191 return 1;
194 static int
195 tunnelConnectionHandler(int status,
196 FdEventHandlerPtr event,
197 ConnectRequestPtr request)
199 TunnelPtr tunnel = request->data;
200 int rc;
202 if(status < 0) {
203 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
204 return 1;
207 rc = setNodelay(request->fd, 1);
208 if(rc < 0)
209 do_log_error(L_WARN, errno, "Couldn't disable Nagle's algorithm");
211 return tunnelHandlerCommon(request->fd, tunnel);
214 static int
215 tunnelSocksHandler(int status, SocksRequestPtr request)
217 TunnelPtr tunnel = request->data;
219 if(status < 0) {
220 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
221 return 1;
224 return tunnelHandlerCommon(request->fd, tunnel);
227 static int
228 tunnelHandlerParent(int fd, TunnelPtr tunnel)
230 char *message;
231 int n;
233 tunnel->fd2 = fd;
235 tunnel->buf2.buf = get_chunk();
236 if(tunnel->buf2.buf == NULL) {
237 message = "Couldn't allocate buffer";
238 goto fail;
241 n = snnprintf(tunnel->buf2.buf, 0, CHUNK_SIZE,
242 "CONNECT %s:%d HTTP/1.1"
243 "\r\n\r\n",
244 tunnel->hostname->string, tunnel->port);
245 if(n < 0) {
246 message = "Buffer overflow";
247 goto fail;
249 tunnel->buf2.head = n;
250 tunnelDispatch(tunnel);
251 return 1;
253 fail:
254 close(fd);
255 tunnel->fd2 = -1;
256 tunnelError(tunnel, 501, internAtom(message));
257 return 1;
260 static int
261 tunnelHandlerCommon(int fd, TunnelPtr tunnel)
263 const char *message = "HTTP/1.1 200 Tunnel established\r\n\r\n";
264 assert(tunnel->buf1.buf == NULL);
266 if(parentHost)
267 return tunnelHandlerParent(fd, tunnel);
269 tunnel->buf1.buf = get_chunk();
270 if(tunnel->buf1.buf == NULL) {
271 close(fd);
272 tunnelError(tunnel, 501, internAtom("Couldn't allocate buffer"));
273 return 1;
276 tunnel->fd2 = fd;
278 memcpy(tunnel->buf2.buf, message, MIN(CHUNK_SIZE - 1, strlen(message)));
279 tunnel->buf2.head = MIN(CHUNK_SIZE - 1, strlen(message));
281 tunnelDispatch(tunnel);
282 return 1;
285 static void
286 bufRead(int fd, CircularBufferPtr buf,
287 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
288 void *data)
290 int tail;
292 if(buf->tail == 0)
293 tail = CHUNK_SIZE - 1;
294 else
295 tail = buf->tail - 1;
297 if(buf->head == 0)
298 do_stream_buf(IO_READ | IO_NOTNOW,
299 fd, 0,
300 &buf->buf, tail,
301 handler, data);
302 else if(buf->tail > buf->head)
303 do_stream(IO_READ | IO_NOTNOW,
304 fd, buf->head,
305 buf->buf, tail,
306 handler, data);
307 else
308 do_stream_2(IO_READ | IO_NOTNOW,
309 fd, buf->head,
310 buf->buf, CHUNK_SIZE,
311 buf->buf, tail,
312 handler, data);
315 static void
316 bufWrite(int fd, CircularBufferPtr buf,
317 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
318 void *data)
320 if(buf->head > buf->tail)
321 do_stream(IO_WRITE,
322 fd, buf->tail,
323 buf->buf, buf->head,
324 handler, data);
325 else
326 do_stream_2(IO_WRITE,
327 fd, buf->tail,
328 buf->buf, CHUNK_SIZE,
329 buf->buf, buf->head,
330 handler, data);
333 static void
334 tunnelDispatch(TunnelPtr tunnel)
336 if(circularBufferEmpty(&tunnel->buf1)) {
337 if(tunnel->buf1.buf &&
338 !(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER2))) {
339 dispose_chunk(tunnel->buf1.buf);
340 tunnel->buf1.buf = NULL;
341 tunnel->buf1.head = tunnel->buf1.tail = 0;
345 if(circularBufferEmpty(&tunnel->buf2)) {
346 if(tunnel->buf2.buf &&
347 !(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER1))) {
348 dispose_chunk(tunnel->buf2.buf);
349 tunnel->buf2.buf = NULL;
350 tunnel->buf2.head = tunnel->buf2.tail = 0;
354 if(tunnel->fd1 >= 0) {
355 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_EOF1)) &&
356 !circularBufferFull(&tunnel->buf1)) {
357 tunnel->flags |= TUNNEL_READER1;
358 bufRead(tunnel->fd1, &tunnel->buf1, tunnelRead1Handler, tunnel);
360 if(!(tunnel->flags & (TUNNEL_WRITER1 | TUNNEL_EPIPE1)) &&
361 !circularBufferEmpty(&tunnel->buf2)) {
362 tunnel->flags |= TUNNEL_WRITER1;
363 /* There's no IO_NOTNOW in bufWrite, so it might close the
364 file descriptor straight away. Wait until we're
365 rescheduled. */
366 bufWrite(tunnel->fd1, &tunnel->buf2, tunnelWrite1Handler, tunnel);
367 return;
369 if(tunnel->fd2 < 0 || (tunnel->flags & TUNNEL_EOF2)) {
370 if(!(tunnel->flags & TUNNEL_EPIPE1))
371 shutdown(tunnel->fd1, 1);
372 tunnel->flags |= TUNNEL_EPIPE1;
373 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE2)) {
374 if(!(tunnel->flags & TUNNEL_EOF1))
375 shutdown(tunnel->fd1, 0);
376 tunnel->flags |= TUNNEL_EOF1;
378 if((tunnel->flags & TUNNEL_EOF1) && (tunnel->flags & TUNNEL_EPIPE1)) {
379 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1))) {
380 close(tunnel->fd1);
381 tunnel->fd1 = -1;
386 if(tunnel->fd2 >= 0) {
387 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_EOF2)) &&
388 !circularBufferFull(&tunnel->buf2)) {
389 tunnel->flags |= TUNNEL_READER2;
390 bufRead(tunnel->fd2, &tunnel->buf2, tunnelRead2Handler, tunnel);
392 if(!(tunnel->flags & (TUNNEL_WRITER2 | TUNNEL_EPIPE2)) &&
393 !circularBufferEmpty(&tunnel->buf1)) {
394 tunnel->flags |= TUNNEL_WRITER2;
395 bufWrite(tunnel->fd2, &tunnel->buf1, tunnelWrite2Handler, tunnel);
396 return;
398 if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EOF1)) {
399 if(!(tunnel->flags & TUNNEL_EPIPE2))
400 shutdown(tunnel->fd2, 1);
401 tunnel->flags |= TUNNEL_EPIPE2;
402 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE1)) {
403 if(!(tunnel->flags & TUNNEL_EOF2))
404 shutdown(tunnel->fd2, 0);
405 tunnel->flags |= TUNNEL_EOF2;
407 if((tunnel->flags & TUNNEL_EOF2) && (tunnel->flags & TUNNEL_EPIPE2)) {
408 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER2))) {
409 close(tunnel->fd2);
410 tunnel->fd2 = -1;
415 if(tunnel->fd1 < 0 && tunnel->fd2 < 0)
416 destroyTunnel(tunnel);
417 else
418 assert(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1 |
419 TUNNEL_READER2 | TUNNEL_WRITER2));
422 static int
423 tunnelRead1Handler(int status,
424 FdEventHandlerPtr event, StreamRequestPtr request)
426 TunnelPtr tunnel = request->data;
427 if(status) {
428 if(status < 0)
429 do_log_error(L_ERROR, -status, "Couldn't read from client");
430 tunnel->flags |= TUNNEL_EOF1;
431 goto done;
433 tunnel->buf1.head = request->offset % CHUNK_SIZE;
434 done:
435 /* Keep buffer empty to avoid a deadlock */
436 if((tunnel->flags & TUNNEL_EPIPE2))
437 tunnel->buf1.tail = tunnel->buf1.head;
438 tunnel->flags &= ~TUNNEL_READER1;
439 tunnelDispatch(tunnel);
440 return 1;
443 static int
444 tunnelRead2Handler(int status,
445 FdEventHandlerPtr event, StreamRequestPtr request)
447 TunnelPtr tunnel = request->data;
448 if(status) {
449 if(status < 0)
450 do_log_error(L_ERROR, -status, "Couldn't read from server");
451 tunnel->flags |= TUNNEL_EOF2;
452 goto done;
454 tunnel->buf2.head = request->offset % CHUNK_SIZE;
455 done:
456 /* Keep buffer empty to avoid a deadlock */
457 if((tunnel->flags & TUNNEL_EPIPE1))
458 tunnel->buf2.tail = tunnel->buf2.head;
459 tunnel->flags &= ~TUNNEL_READER2;
460 tunnelDispatch(tunnel);
461 return 1;
464 static int
465 tunnelWrite1Handler(int status,
466 FdEventHandlerPtr event, StreamRequestPtr request)
468 TunnelPtr tunnel = request->data;
469 if(status || (tunnel->flags & TUNNEL_EPIPE1)) {
470 tunnel->flags |= TUNNEL_EPIPE1;
471 if(status < 0 && status != -EPIPE)
472 do_log_error(L_ERROR, -status, "Couldn't write to client");
473 /* Empty the buffer to avoid a deadlock */
474 tunnel->buf2.tail = tunnel->buf2.head;
475 goto done;
477 tunnel->buf2.tail = request->offset % CHUNK_SIZE;
478 done:
479 tunnel->flags &= ~TUNNEL_WRITER1;
480 tunnelDispatch(tunnel);
481 return 1;
484 static int
485 tunnelWrite2Handler(int status,
486 FdEventHandlerPtr event, StreamRequestPtr request)
488 TunnelPtr tunnel = request->data;
489 if(status || (tunnel->flags & TUNNEL_EPIPE2)) {
490 tunnel->flags |= TUNNEL_EPIPE2;
491 if(status < 0 && status != -EPIPE)
492 do_log_error(L_ERROR, -status, "Couldn't write to server");
493 /* Empty the buffer to avoid a deadlock */
494 tunnel->buf1.tail = tunnel->buf1.head;
495 goto done;
497 tunnel->buf1.tail = request->offset % CHUNK_SIZE;
498 done:
499 tunnel->flags &= ~TUNNEL_WRITER2;
500 tunnelDispatch(tunnel);
501 return 1;
504 static int
505 tunnelError(TunnelPtr tunnel, int code, AtomPtr message)
507 int n;
508 if(tunnel->fd2 > 0) {
509 close(tunnel->fd2);
510 tunnel->fd2 = -1;
513 if(tunnel->buf2.buf == NULL)
514 tunnel->buf2.buf = get_chunk();
515 if(tunnel->buf2.buf == NULL)
516 goto fail;
518 n = httpWriteErrorHeaders(tunnel->buf2.buf, CHUNK_SIZE - 1, 0,
519 1, code, message, 1, NULL,
520 NULL, 0, NULL);
522 if(n <= 0) goto fail;
524 tunnel->buf2.head = n;
526 tunnelDispatch(tunnel);
527 return 1;
529 fail:
530 close(tunnel->fd1);
531 tunnel->fd1 = -1;
532 tunnelDispatch(tunnel);
533 return 1;
535 #endif