Update CHANGES; fix a bogus entry, and mention Jake's work.
[polipo.git] / tunnel.c
blob494921ce9cd83af9b5094a2f146d22141e1e0168
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 = buf;
94 if(offset == len) {
95 tunnel->buf1.tail = 0;
96 tunnel->buf1.head = 0;
97 } else {
98 tunnel->buf1.tail = offset;
99 tunnel->buf1.head = len;
101 tunnel->buf2.buf = NULL;
102 tunnel->buf2.tail = 0;
103 tunnel->buf2.head = 0;
104 return tunnel;
107 static void
108 destroyTunnel(TunnelPtr tunnel)
110 assert(tunnel->fd1 < 0 && tunnel->fd2 < 0);
111 releaseAtom(tunnel->hostname);
112 if(tunnel->buf1.buf)
113 dispose_chunk(tunnel->buf1.buf);
114 if(tunnel->buf2.buf)
115 dispose_chunk(tunnel->buf2.buf);
116 free(tunnel);
119 void
120 do_tunnel(int fd, char *buf, int offset, int len, AtomPtr url)
122 TunnelPtr tunnel;
123 int port;
124 char *p, *q;
126 tunnel = makeTunnel(fd, buf, offset, len);
127 if(tunnel == NULL) {
128 do_log(L_ERROR, "Couldn't allocate tunnel.\n");
129 releaseAtom(url);
130 dispose_chunk(buf);
131 CLOSE(fd);
132 return;
135 p = memrchr(url->string, ':', url->length);
136 q = NULL;
137 if(p)
138 port = strtol(p + 1, &q, 10);
139 if(!p || q != url->string + url->length) {
140 do_log(L_ERROR, "Couldn't parse CONNECT.\n");
141 releaseAtom(url);
142 tunnelError(tunnel, 400, internAtom("Couldn't parse CONNECT"));
143 return;
145 tunnel->hostname = internAtomLowerN(url->string, p - url->string);
146 if(tunnel->hostname == NULL) {
147 releaseAtom(url);
148 tunnelError(tunnel, 501, internAtom("Couldn't allocate hostname"));
149 return;
152 if(!intListMember(port, tunnelAllowedPorts)) {
153 releaseAtom(url);
154 tunnelError(tunnel, 403, internAtom("Forbidden port"));
155 return;
157 tunnel->port = port;
159 releaseAtom(url);
161 if(socksParentProxy)
162 do_socks_connect(parentHost ?
163 parentHost->string : tunnel->hostname->string,
164 parentHost ? parentPort : tunnel->port,
165 tunnelSocksHandler, tunnel);
166 else
167 do_gethostbyname(parentHost ?
168 parentHost->string : tunnel->hostname->string, 0,
169 tunnelDnsHandler, tunnel);
172 static int
173 tunnelDnsHandler(int status, GethostbynameRequestPtr request)
175 TunnelPtr tunnel = request->data;
177 if(status <= 0) {
178 tunnelError(tunnel, 504,
179 internAtomError(-status,
180 "Host %s lookup failed",
181 atomString(tunnel->hostname)));
182 return 1;
185 if(request->addr->string[0] == DNS_CNAME) {
186 if(request->count > 10)
187 tunnelError(tunnel, 504, internAtom("CNAME loop"));
188 do_gethostbyname(request->addr->string + 1, request->count + 1,
189 tunnelDnsHandler, tunnel);
190 return 1;
193 do_connect(retainAtom(request->addr), 0,
194 parentHost ? parentPort : tunnel->port,
195 tunnelConnectionHandler, tunnel);
196 return 1;
199 static int
200 tunnelConnectionHandler(int status,
201 FdEventHandlerPtr event,
202 ConnectRequestPtr request)
204 TunnelPtr tunnel = request->data;
205 int rc;
207 if(status < 0) {
208 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
209 return 1;
212 rc = setNodelay(request->fd, 1);
213 if(rc < 0)
214 do_log_error(L_WARN, errno, "Couldn't disable Nagle's algorithm");
216 return tunnelHandlerCommon(request->fd, tunnel);
219 static int
220 tunnelSocksHandler(int status, SocksRequestPtr request)
222 TunnelPtr tunnel = request->data;
224 if(status < 0) {
225 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
226 return 1;
229 return tunnelHandlerCommon(request->fd, tunnel);
232 static int
233 tunnelHandlerParent(int fd, TunnelPtr tunnel)
235 char *message;
236 int n;
238 if(tunnel->buf1.buf == NULL)
239 tunnel->buf1.buf = get_chunk();
240 if(tunnel->buf1.buf == NULL) {
241 message = "Couldn't allocate buffer";
242 goto fail;
244 if(tunnel->buf1.tail != tunnel->buf1.head) {
245 message = "Pipelined connect to parent proxy not implemented";
246 goto fail;
249 n = snnprintf(tunnel->buf1.buf, tunnel->buf1.tail, CHUNK_SIZE,
250 "CONNECT %s:%d HTTP/1.1",
251 tunnel->hostname->string, tunnel->port);
252 if (parentAuthCredentials)
253 n = buildServerAuthHeaders(tunnel->buf1.buf, n, CHUNK_SIZE,
254 parentAuthCredentials);
255 n = snnprintf(tunnel->buf1.buf, n, CHUNK_SIZE, "\r\n\r\n");
257 if(n < 0) {
258 message = "Buffer overflow";
259 goto fail;
261 tunnel->buf1.head = n;
262 tunnelDispatch(tunnel);
263 return 1;
265 fail:
266 CLOSE(fd);
267 tunnel->fd2 = -1;
268 tunnelError(tunnel, 501, internAtom(message));
269 return 1;
272 static int
273 tunnelHandlerCommon(int fd, TunnelPtr tunnel)
275 const char *message = "HTTP/1.1 200 Tunnel established\r\n\r\n";
277 tunnel->fd2 = fd;
279 if(parentHost)
280 return tunnelHandlerParent(fd, tunnel);
282 if(tunnel->buf2.buf == NULL)
283 tunnel->buf2.buf = get_chunk();
284 if(tunnel->buf2.buf == NULL) {
285 CLOSE(fd);
286 tunnelError(tunnel, 501, internAtom("Couldn't allocate buffer"));
287 return 1;
290 memcpy(tunnel->buf2.buf, message, MIN(CHUNK_SIZE - 1, strlen(message)));
291 tunnel->buf2.head = MIN(CHUNK_SIZE - 1, strlen(message));
293 tunnelDispatch(tunnel);
294 return 1;
297 static void
298 bufRead(int fd, CircularBufferPtr buf,
299 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
300 void *data)
302 int tail;
304 if(buf->tail == 0)
305 tail = CHUNK_SIZE - 1;
306 else
307 tail = buf->tail - 1;
309 if(buf->head == 0)
310 do_stream_buf(IO_READ | IO_NOTNOW,
311 fd, 0,
312 &buf->buf, tail,
313 handler, data);
314 else if(buf->tail > buf->head)
315 do_stream(IO_READ | IO_NOTNOW,
316 fd, buf->head,
317 buf->buf, tail,
318 handler, data);
319 else
320 do_stream_2(IO_READ | IO_NOTNOW,
321 fd, buf->head,
322 buf->buf, CHUNK_SIZE,
323 buf->buf, tail,
324 handler, data);
327 static void
328 bufWrite(int fd, CircularBufferPtr buf,
329 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
330 void *data)
332 if(buf->head > buf->tail)
333 do_stream(IO_WRITE,
334 fd, buf->tail,
335 buf->buf, buf->head,
336 handler, data);
337 else
338 do_stream_2(IO_WRITE,
339 fd, buf->tail,
340 buf->buf, CHUNK_SIZE,
341 buf->buf, buf->head,
342 handler, data);
345 static void
346 tunnelDispatch(TunnelPtr tunnel)
348 if(circularBufferEmpty(&tunnel->buf1)) {
349 if(tunnel->buf1.buf &&
350 !(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER2))) {
351 dispose_chunk(tunnel->buf1.buf);
352 tunnel->buf1.buf = NULL;
353 tunnel->buf1.head = tunnel->buf1.tail = 0;
357 if(circularBufferEmpty(&tunnel->buf2)) {
358 if(tunnel->buf2.buf &&
359 !(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER1))) {
360 dispose_chunk(tunnel->buf2.buf);
361 tunnel->buf2.buf = NULL;
362 tunnel->buf2.head = tunnel->buf2.tail = 0;
366 if(tunnel->fd1 >= 0) {
367 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_EOF1)) &&
368 !circularBufferFull(&tunnel->buf1)) {
369 tunnel->flags |= TUNNEL_READER1;
370 bufRead(tunnel->fd1, &tunnel->buf1, tunnelRead1Handler, tunnel);
372 if(!(tunnel->flags & (TUNNEL_WRITER1 | TUNNEL_EPIPE1)) &&
373 !circularBufferEmpty(&tunnel->buf2)) {
374 tunnel->flags |= TUNNEL_WRITER1;
375 /* There's no IO_NOTNOW in bufWrite, so it might close the
376 file descriptor straight away. Wait until we're
377 rescheduled. */
378 bufWrite(tunnel->fd1, &tunnel->buf2, tunnelWrite1Handler, tunnel);
379 return;
381 if(tunnel->fd2 < 0 || (tunnel->flags & TUNNEL_EOF2)) {
382 if(!(tunnel->flags & TUNNEL_EPIPE1))
383 shutdown(tunnel->fd1, 1);
384 tunnel->flags |= TUNNEL_EPIPE1;
385 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE2)) {
386 if(!(tunnel->flags & TUNNEL_EOF1))
387 shutdown(tunnel->fd1, 0);
388 tunnel->flags |= TUNNEL_EOF1;
390 if((tunnel->flags & TUNNEL_EOF1) && (tunnel->flags & TUNNEL_EPIPE1)) {
391 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1))) {
392 CLOSE(tunnel->fd1);
393 tunnel->fd1 = -1;
398 if(tunnel->fd2 >= 0) {
399 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_EOF2)) &&
400 !circularBufferFull(&tunnel->buf2)) {
401 tunnel->flags |= TUNNEL_READER2;
402 bufRead(tunnel->fd2, &tunnel->buf2, tunnelRead2Handler, tunnel);
404 if(!(tunnel->flags & (TUNNEL_WRITER2 | TUNNEL_EPIPE2)) &&
405 !circularBufferEmpty(&tunnel->buf1)) {
406 tunnel->flags |= TUNNEL_WRITER2;
407 bufWrite(tunnel->fd2, &tunnel->buf1, tunnelWrite2Handler, tunnel);
408 return;
410 if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EOF1)) {
411 if(!(tunnel->flags & TUNNEL_EPIPE2))
412 shutdown(tunnel->fd2, 1);
413 tunnel->flags |= TUNNEL_EPIPE2;
414 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE1)) {
415 if(!(tunnel->flags & TUNNEL_EOF2))
416 shutdown(tunnel->fd2, 0);
417 tunnel->flags |= TUNNEL_EOF2;
419 if((tunnel->flags & TUNNEL_EOF2) && (tunnel->flags & TUNNEL_EPIPE2)) {
420 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER2))) {
421 CLOSE(tunnel->fd2);
422 tunnel->fd2 = -1;
427 if(tunnel->fd1 < 0 && tunnel->fd2 < 0)
428 destroyTunnel(tunnel);
429 else
430 assert(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1 |
431 TUNNEL_READER2 | TUNNEL_WRITER2));
434 static int
435 tunnelRead1Handler(int status,
436 FdEventHandlerPtr event, StreamRequestPtr request)
438 TunnelPtr tunnel = request->data;
439 if(status) {
440 if(status < 0)
441 do_log_error(L_ERROR, -status, "Couldn't read from client");
442 tunnel->flags |= TUNNEL_EOF1;
443 goto done;
445 tunnel->buf1.head = request->offset % CHUNK_SIZE;
446 done:
447 /* Keep buffer empty to avoid a deadlock */
448 if((tunnel->flags & TUNNEL_EPIPE2))
449 tunnel->buf1.tail = tunnel->buf1.head;
450 tunnel->flags &= ~TUNNEL_READER1;
451 tunnelDispatch(tunnel);
452 return 1;
455 static int
456 tunnelRead2Handler(int status,
457 FdEventHandlerPtr event, StreamRequestPtr request)
459 TunnelPtr tunnel = request->data;
460 if(status) {
461 if(status < 0)
462 do_log_error(L_ERROR, -status, "Couldn't read from server");
463 tunnel->flags |= TUNNEL_EOF2;
464 goto done;
466 tunnel->buf2.head = request->offset % CHUNK_SIZE;
467 done:
468 /* Keep buffer empty to avoid a deadlock */
469 if((tunnel->flags & TUNNEL_EPIPE1))
470 tunnel->buf2.tail = tunnel->buf2.head;
471 tunnel->flags &= ~TUNNEL_READER2;
472 tunnelDispatch(tunnel);
473 return 1;
476 static int
477 tunnelWrite1Handler(int status,
478 FdEventHandlerPtr event, StreamRequestPtr request)
480 TunnelPtr tunnel = request->data;
481 if(status || (tunnel->flags & TUNNEL_EPIPE1)) {
482 tunnel->flags |= TUNNEL_EPIPE1;
483 if(status < 0 && status != -EPIPE)
484 do_log_error(L_ERROR, -status, "Couldn't write to client");
485 /* Empty the buffer to avoid a deadlock */
486 tunnel->buf2.tail = tunnel->buf2.head;
487 goto done;
489 tunnel->buf2.tail = request->offset % CHUNK_SIZE;
490 done:
491 tunnel->flags &= ~TUNNEL_WRITER1;
492 tunnelDispatch(tunnel);
493 return 1;
496 static int
497 tunnelWrite2Handler(int status,
498 FdEventHandlerPtr event, StreamRequestPtr request)
500 TunnelPtr tunnel = request->data;
501 if(status || (tunnel->flags & TUNNEL_EPIPE2)) {
502 tunnel->flags |= TUNNEL_EPIPE2;
503 if(status < 0 && status != -EPIPE)
504 do_log_error(L_ERROR, -status, "Couldn't write to server");
505 /* Empty the buffer to avoid a deadlock */
506 tunnel->buf1.tail = tunnel->buf1.head;
507 goto done;
509 tunnel->buf1.tail = request->offset % CHUNK_SIZE;
510 done:
511 tunnel->flags &= ~TUNNEL_WRITER2;
512 tunnelDispatch(tunnel);
513 return 1;
516 static int
517 tunnelError(TunnelPtr tunnel, int code, AtomPtr message)
519 int n;
520 if(tunnel->fd2 > 0) {
521 CLOSE(tunnel->fd2);
522 tunnel->fd2 = -1;
525 if(tunnel->buf2.buf == NULL)
526 tunnel->buf2.buf = get_chunk();
527 if(tunnel->buf2.buf == NULL)
528 goto fail;
530 n = httpWriteErrorHeaders(tunnel->buf2.buf, CHUNK_SIZE - 1, 0,
531 1, code, message, 1, NULL,
532 NULL, 0, NULL);
534 if(n <= 0) goto fail;
536 tunnel->buf2.head = n;
538 tunnelDispatch(tunnel);
539 return 1;
541 fail:
542 CLOSE(tunnel->fd1);
543 tunnel->fd1 = -1;
544 tunnelDispatch(tunnel);
545 return 1;
547 #endif