Implement logFilePermissions.
[polipo.git] / tunnel.c
blob753d26bd4b2438d1a3136397bd3db1015f3e292d
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,
250 CHUNK_SIZE - tunnel->buf1.tail,
251 "CONNECT %s:%d HTTP/1.1"
252 "\r\n\r\n",
253 tunnel->hostname->string, tunnel->port);
254 if(n < 0) {
255 message = "Buffer overflow";
256 goto fail;
258 tunnel->buf1.head = n;
259 tunnelDispatch(tunnel);
260 return 1;
262 fail:
263 CLOSE(fd);
264 tunnel->fd2 = -1;
265 tunnelError(tunnel, 501, internAtom(message));
266 return 1;
269 static int
270 tunnelHandlerCommon(int fd, TunnelPtr tunnel)
272 const char *message = "HTTP/1.1 200 Tunnel established\r\n\r\n";
274 tunnel->fd2 = fd;
276 if(parentHost)
277 return tunnelHandlerParent(fd, tunnel);
279 if(tunnel->buf2.buf == NULL)
280 tunnel->buf2.buf = get_chunk();
281 if(tunnel->buf2.buf == NULL) {
282 CLOSE(fd);
283 tunnelError(tunnel, 501, internAtom("Couldn't allocate buffer"));
284 return 1;
287 memcpy(tunnel->buf2.buf, message, MIN(CHUNK_SIZE - 1, strlen(message)));
288 tunnel->buf2.head = MIN(CHUNK_SIZE - 1, strlen(message));
290 tunnelDispatch(tunnel);
291 return 1;
294 static void
295 bufRead(int fd, CircularBufferPtr buf,
296 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
297 void *data)
299 int tail;
301 if(buf->tail == 0)
302 tail = CHUNK_SIZE - 1;
303 else
304 tail = buf->tail - 1;
306 if(buf->head == 0)
307 do_stream_buf(IO_READ | IO_NOTNOW,
308 fd, 0,
309 &buf->buf, tail,
310 handler, data);
311 else if(buf->tail > buf->head)
312 do_stream(IO_READ | IO_NOTNOW,
313 fd, buf->head,
314 buf->buf, tail,
315 handler, data);
316 else
317 do_stream_2(IO_READ | IO_NOTNOW,
318 fd, buf->head,
319 buf->buf, CHUNK_SIZE,
320 buf->buf, tail,
321 handler, data);
324 static void
325 bufWrite(int fd, CircularBufferPtr buf,
326 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
327 void *data)
329 if(buf->head > buf->tail)
330 do_stream(IO_WRITE,
331 fd, buf->tail,
332 buf->buf, buf->head,
333 handler, data);
334 else
335 do_stream_2(IO_WRITE,
336 fd, buf->tail,
337 buf->buf, CHUNK_SIZE,
338 buf->buf, buf->head,
339 handler, data);
342 static void
343 tunnelDispatch(TunnelPtr tunnel)
345 if(circularBufferEmpty(&tunnel->buf1)) {
346 if(tunnel->buf1.buf &&
347 !(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER2))) {
348 dispose_chunk(tunnel->buf1.buf);
349 tunnel->buf1.buf = NULL;
350 tunnel->buf1.head = tunnel->buf1.tail = 0;
354 if(circularBufferEmpty(&tunnel->buf2)) {
355 if(tunnel->buf2.buf &&
356 !(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER1))) {
357 dispose_chunk(tunnel->buf2.buf);
358 tunnel->buf2.buf = NULL;
359 tunnel->buf2.head = tunnel->buf2.tail = 0;
363 if(tunnel->fd1 >= 0) {
364 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_EOF1)) &&
365 !circularBufferFull(&tunnel->buf1)) {
366 tunnel->flags |= TUNNEL_READER1;
367 bufRead(tunnel->fd1, &tunnel->buf1, tunnelRead1Handler, tunnel);
369 if(!(tunnel->flags & (TUNNEL_WRITER1 | TUNNEL_EPIPE1)) &&
370 !circularBufferEmpty(&tunnel->buf2)) {
371 tunnel->flags |= TUNNEL_WRITER1;
372 /* There's no IO_NOTNOW in bufWrite, so it might close the
373 file descriptor straight away. Wait until we're
374 rescheduled. */
375 bufWrite(tunnel->fd1, &tunnel->buf2, tunnelWrite1Handler, tunnel);
376 return;
378 if(tunnel->fd2 < 0 || (tunnel->flags & TUNNEL_EOF2)) {
379 if(!(tunnel->flags & TUNNEL_EPIPE1))
380 shutdown(tunnel->fd1, 1);
381 tunnel->flags |= TUNNEL_EPIPE1;
382 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE2)) {
383 if(!(tunnel->flags & TUNNEL_EOF1))
384 shutdown(tunnel->fd1, 0);
385 tunnel->flags |= TUNNEL_EOF1;
387 if((tunnel->flags & TUNNEL_EOF1) && (tunnel->flags & TUNNEL_EPIPE1)) {
388 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1))) {
389 CLOSE(tunnel->fd1);
390 tunnel->fd1 = -1;
395 if(tunnel->fd2 >= 0) {
396 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_EOF2)) &&
397 !circularBufferFull(&tunnel->buf2)) {
398 tunnel->flags |= TUNNEL_READER2;
399 bufRead(tunnel->fd2, &tunnel->buf2, tunnelRead2Handler, tunnel);
401 if(!(tunnel->flags & (TUNNEL_WRITER2 | TUNNEL_EPIPE2)) &&
402 !circularBufferEmpty(&tunnel->buf1)) {
403 tunnel->flags |= TUNNEL_WRITER2;
404 bufWrite(tunnel->fd2, &tunnel->buf1, tunnelWrite2Handler, tunnel);
405 return;
407 if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EOF1)) {
408 if(!(tunnel->flags & TUNNEL_EPIPE2))
409 shutdown(tunnel->fd2, 1);
410 tunnel->flags |= TUNNEL_EPIPE2;
411 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE1)) {
412 if(!(tunnel->flags & TUNNEL_EOF2))
413 shutdown(tunnel->fd2, 0);
414 tunnel->flags |= TUNNEL_EOF2;
416 if((tunnel->flags & TUNNEL_EOF2) && (tunnel->flags & TUNNEL_EPIPE2)) {
417 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER2))) {
418 CLOSE(tunnel->fd2);
419 tunnel->fd2 = -1;
424 if(tunnel->fd1 < 0 && tunnel->fd2 < 0)
425 destroyTunnel(tunnel);
426 else
427 assert(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1 |
428 TUNNEL_READER2 | TUNNEL_WRITER2));
431 static int
432 tunnelRead1Handler(int status,
433 FdEventHandlerPtr event, StreamRequestPtr request)
435 TunnelPtr tunnel = request->data;
436 if(status) {
437 if(status < 0)
438 do_log_error(L_ERROR, -status, "Couldn't read from client");
439 tunnel->flags |= TUNNEL_EOF1;
440 goto done;
442 tunnel->buf1.head = request->offset % CHUNK_SIZE;
443 done:
444 /* Keep buffer empty to avoid a deadlock */
445 if((tunnel->flags & TUNNEL_EPIPE2))
446 tunnel->buf1.tail = tunnel->buf1.head;
447 tunnel->flags &= ~TUNNEL_READER1;
448 tunnelDispatch(tunnel);
449 return 1;
452 static int
453 tunnelRead2Handler(int status,
454 FdEventHandlerPtr event, StreamRequestPtr request)
456 TunnelPtr tunnel = request->data;
457 if(status) {
458 if(status < 0)
459 do_log_error(L_ERROR, -status, "Couldn't read from server");
460 tunnel->flags |= TUNNEL_EOF2;
461 goto done;
463 tunnel->buf2.head = request->offset % CHUNK_SIZE;
464 done:
465 /* Keep buffer empty to avoid a deadlock */
466 if((tunnel->flags & TUNNEL_EPIPE1))
467 tunnel->buf2.tail = tunnel->buf2.head;
468 tunnel->flags &= ~TUNNEL_READER2;
469 tunnelDispatch(tunnel);
470 return 1;
473 static int
474 tunnelWrite1Handler(int status,
475 FdEventHandlerPtr event, StreamRequestPtr request)
477 TunnelPtr tunnel = request->data;
478 if(status || (tunnel->flags & TUNNEL_EPIPE1)) {
479 tunnel->flags |= TUNNEL_EPIPE1;
480 if(status < 0 && status != -EPIPE)
481 do_log_error(L_ERROR, -status, "Couldn't write to client");
482 /* Empty the buffer to avoid a deadlock */
483 tunnel->buf2.tail = tunnel->buf2.head;
484 goto done;
486 tunnel->buf2.tail = request->offset % CHUNK_SIZE;
487 done:
488 tunnel->flags &= ~TUNNEL_WRITER1;
489 tunnelDispatch(tunnel);
490 return 1;
493 static int
494 tunnelWrite2Handler(int status,
495 FdEventHandlerPtr event, StreamRequestPtr request)
497 TunnelPtr tunnel = request->data;
498 if(status || (tunnel->flags & TUNNEL_EPIPE2)) {
499 tunnel->flags |= TUNNEL_EPIPE2;
500 if(status < 0 && status != -EPIPE)
501 do_log_error(L_ERROR, -status, "Couldn't write to server");
502 /* Empty the buffer to avoid a deadlock */
503 tunnel->buf1.tail = tunnel->buf1.head;
504 goto done;
506 tunnel->buf1.tail = request->offset % CHUNK_SIZE;
507 done:
508 tunnel->flags &= ~TUNNEL_WRITER2;
509 tunnelDispatch(tunnel);
510 return 1;
513 static int
514 tunnelError(TunnelPtr tunnel, int code, AtomPtr message)
516 int n;
517 if(tunnel->fd2 > 0) {
518 CLOSE(tunnel->fd2);
519 tunnel->fd2 = -1;
522 if(tunnel->buf2.buf == NULL)
523 tunnel->buf2.buf = get_chunk();
524 if(tunnel->buf2.buf == NULL)
525 goto fail;
527 n = httpWriteErrorHeaders(tunnel->buf2.buf, CHUNK_SIZE - 1, 0,
528 1, code, message, 1, NULL,
529 NULL, 0, NULL);
531 if(n <= 0) goto fail;
533 tunnel->buf2.head = n;
535 tunnelDispatch(tunnel);
536 return 1;
538 fail:
539 CLOSE(tunnel->fd1);
540 tunnel->fd1 = -1;
541 tunnelDispatch(tunnel);
542 return 1;
544 #endif