Don't disable tunnelling under mingw.
[polipo.git] / tunnel.c
blobd675712baa5c86ec203cd8d0c5cb39771ac0a139
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(tunnel->hostname->string, tunnel->port,
158 tunnelSocksHandler, tunnel);
159 else
160 do_gethostbyname(tunnel->hostname->string, 0,
161 tunnelDnsHandler, tunnel);
164 static int
165 tunnelDnsHandler(int status, GethostbynameRequestPtr request)
167 TunnelPtr tunnel = request->data;
169 if(status <= 0) {
170 tunnelError(tunnel, 504,
171 internAtomError(-status,
172 "Host %s lookup failed",
173 atomString(tunnel->hostname)));
174 return 1;
177 if(request->addr->string[0] == DNS_CNAME) {
178 if(request->count > 10)
179 tunnelError(tunnel, 504, internAtom("CNAME loop"));
180 do_gethostbyname(request->addr->string + 1, request->count + 1,
181 tunnelDnsHandler, tunnel);
182 return 1;
185 do_connect(retainAtom(request->addr), 0, tunnel->port,
186 tunnelConnectionHandler, tunnel);
187 return 1;
190 static int
191 tunnelConnectionHandler(int status,
192 FdEventHandlerPtr event,
193 ConnectRequestPtr request)
195 TunnelPtr tunnel = request->data;
196 int rc;
198 if(status < 0) {
199 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
200 return 1;
203 rc = setNodelay(request->fd, 1);
204 if(rc < 0)
205 do_log_error(L_WARN, errno, "Couldn't disable Nagle's algorithm");
207 return tunnelHandlerCommon(request->fd, tunnel);
210 static int
211 tunnelSocksHandler(int status, SocksRequestPtr request)
213 TunnelPtr tunnel = request->data;
215 if(status < 0) {
216 tunnelError(tunnel, 504, internAtomError(-status, "Couldn't connect"));
217 return 1;
220 return tunnelHandlerCommon(request->fd, tunnel);
223 static int
224 tunnelHandlerCommon(int fd, TunnelPtr tunnel)
226 const char *message = "HTTP/1.1 200 Tunnel established\r\n\r\n";
227 assert(tunnel->buf1.buf == NULL);
228 tunnel->buf1.buf = get_chunk();
229 if(tunnel->buf1.buf == NULL) {
230 close(fd);
231 tunnelError(tunnel, 501, internAtom("Couldn't allocate buffer"));
232 return 1;
235 tunnel->fd2 = fd;
237 memcpy(tunnel->buf2.buf, message, MIN(CHUNK_SIZE - 1, strlen(message)));
238 tunnel->buf2.head = MIN(CHUNK_SIZE - 1, strlen(message));
240 tunnelDispatch(tunnel);
241 return 1;
244 static void
245 bufRead(int fd, CircularBufferPtr buf,
246 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
247 void *data)
249 int tail;
251 if(buf->tail == 0)
252 tail = CHUNK_SIZE - 1;
253 else
254 tail = buf->tail - 1;
256 if(buf->head == 0)
257 do_stream_buf(IO_READ | IO_NOTNOW,
258 fd, 0,
259 &buf->buf, tail,
260 handler, data);
261 else if(buf->tail > buf->head)
262 do_stream(IO_READ | IO_NOTNOW,
263 fd, buf->head,
264 buf->buf, tail,
265 handler, data);
266 else
267 do_stream_2(IO_READ | IO_NOTNOW,
268 fd, buf->head,
269 buf->buf, CHUNK_SIZE,
270 buf->buf, tail,
271 handler, data);
274 static void
275 bufWrite(int fd, CircularBufferPtr buf,
276 int (*handler)(int, FdEventHandlerPtr, StreamRequestPtr),
277 void *data)
279 if(buf->head > buf->tail)
280 do_stream(IO_WRITE,
281 fd, buf->tail,
282 buf->buf, buf->head,
283 handler, data);
284 else
285 do_stream_2(IO_WRITE,
286 fd, buf->tail,
287 buf->buf, CHUNK_SIZE,
288 buf->buf, buf->head,
289 handler, data);
292 static void
293 tunnelDispatch(TunnelPtr tunnel)
295 if(circularBufferEmpty(&tunnel->buf1)) {
296 if(tunnel->buf1.buf &&
297 !(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER2))) {
298 dispose_chunk(tunnel->buf1.buf);
299 tunnel->buf1.buf = NULL;
300 tunnel->buf1.head = tunnel->buf1.tail = 0;
304 if(circularBufferEmpty(&tunnel->buf2)) {
305 if(tunnel->buf2.buf &&
306 !(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER1))) {
307 dispose_chunk(tunnel->buf2.buf);
308 tunnel->buf2.buf = NULL;
309 tunnel->buf2.head = tunnel->buf2.tail = 0;
313 if(tunnel->fd1 >= 0) {
314 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_EOF1)) &&
315 !circularBufferFull(&tunnel->buf1)) {
316 tunnel->flags |= TUNNEL_READER1;
317 bufRead(tunnel->fd1, &tunnel->buf1, tunnelRead1Handler, tunnel);
319 if(!(tunnel->flags & (TUNNEL_WRITER1 | TUNNEL_EPIPE1)) &&
320 !circularBufferEmpty(&tunnel->buf2)) {
321 tunnel->flags |= TUNNEL_WRITER1;
322 /* There's no IO_NOTNOW in bufWrite, so it might close the
323 file descriptor straight away. Wait until we're
324 rescheduled. */
325 bufWrite(tunnel->fd1, &tunnel->buf2, tunnelWrite1Handler, tunnel);
326 return;
328 if(tunnel->fd2 < 0 || (tunnel->flags & TUNNEL_EOF2)) {
329 if(!(tunnel->flags & TUNNEL_EPIPE1))
330 shutdown(tunnel->fd1, 1);
331 tunnel->flags |= TUNNEL_EPIPE1;
332 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE2)) {
333 if(!(tunnel->flags & TUNNEL_EOF1))
334 shutdown(tunnel->fd1, 0);
335 tunnel->flags |= TUNNEL_EOF1;
337 if((tunnel->flags & TUNNEL_EOF1) && (tunnel->flags & TUNNEL_EPIPE1)) {
338 if(!(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1))) {
339 close(tunnel->fd1);
340 tunnel->fd1 = -1;
345 if(tunnel->fd2 >= 0) {
346 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_EOF2)) &&
347 !circularBufferFull(&tunnel->buf2)) {
348 tunnel->flags |= TUNNEL_READER2;
349 bufRead(tunnel->fd2, &tunnel->buf2, tunnelRead2Handler, tunnel);
351 if(!(tunnel->flags & (TUNNEL_WRITER2 | TUNNEL_EPIPE2)) &&
352 !circularBufferEmpty(&tunnel->buf1)) {
353 tunnel->flags |= TUNNEL_WRITER2;
354 bufWrite(tunnel->fd2, &tunnel->buf1, tunnelWrite2Handler, tunnel);
355 return;
357 if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EOF1)) {
358 if(!(tunnel->flags & TUNNEL_EPIPE2))
359 shutdown(tunnel->fd2, 1);
360 tunnel->flags |= TUNNEL_EPIPE2;
361 } else if(tunnel->fd1 < 0 || (tunnel->flags & TUNNEL_EPIPE1)) {
362 if(!(tunnel->flags & TUNNEL_EOF2))
363 shutdown(tunnel->fd2, 0);
364 tunnel->flags |= TUNNEL_EOF2;
366 if((tunnel->flags & TUNNEL_EOF2) && (tunnel->flags & TUNNEL_EPIPE2)) {
367 if(!(tunnel->flags & (TUNNEL_READER2 | TUNNEL_WRITER2))) {
368 close(tunnel->fd2);
369 tunnel->fd2 = -1;
374 if(tunnel->fd1 < 0 && tunnel->fd2 < 0)
375 destroyTunnel(tunnel);
376 else
377 assert(tunnel->flags & (TUNNEL_READER1 | TUNNEL_WRITER1 |
378 TUNNEL_READER2 | TUNNEL_WRITER2));
381 static int
382 tunnelRead1Handler(int status,
383 FdEventHandlerPtr event, StreamRequestPtr request)
385 TunnelPtr tunnel = request->data;
386 if(status) {
387 if(status < 0)
388 do_log_error(L_ERROR, -status, "Couldn't read from client");
389 tunnel->flags |= TUNNEL_EOF1;
390 goto done;
392 tunnel->buf1.head = request->offset % CHUNK_SIZE;
393 done:
394 tunnel->flags &= ~TUNNEL_READER1;
395 tunnelDispatch(tunnel);
396 return 1;
399 static int
400 tunnelRead2Handler(int status,
401 FdEventHandlerPtr event, StreamRequestPtr request)
403 TunnelPtr tunnel = request->data;
404 if(status) {
405 if(status < 0)
406 do_log_error(L_ERROR, -status, "Couldn't read from server");
407 tunnel->flags |= TUNNEL_EOF2;
408 goto done;
410 tunnel->buf2.head = request->offset % CHUNK_SIZE;
411 done:
412 tunnel->flags &= ~TUNNEL_READER2;
413 tunnelDispatch(tunnel);
414 return 1;
417 static int
418 tunnelWrite1Handler(int status,
419 FdEventHandlerPtr event, StreamRequestPtr request)
421 TunnelPtr tunnel = request->data;
422 if(status || (tunnel->flags & TUNNEL_EPIPE1)) {
423 tunnel->flags |= TUNNEL_EPIPE1;
424 if(status < 0 && status != -EPIPE)
425 do_log_error(L_ERROR, -status, "Couldn't write to client");
426 goto done;
428 tunnel->buf2.tail = request->offset % CHUNK_SIZE;
429 done:
430 tunnel->flags &= ~TUNNEL_WRITER1;
431 tunnelDispatch(tunnel);
432 return 1;
435 static int
436 tunnelWrite2Handler(int status,
437 FdEventHandlerPtr event, StreamRequestPtr request)
439 TunnelPtr tunnel = request->data;
440 if(status || (tunnel->flags & TUNNEL_EPIPE2)) {
441 tunnel->flags |= TUNNEL_EPIPE2;
442 if(status < 0 && status != -EPIPE)
443 do_log_error(L_ERROR, -status, "Couldn't write to server");
444 goto done;
446 tunnel->buf1.tail = request->offset % CHUNK_SIZE;
447 done:
448 tunnel->flags &= ~TUNNEL_WRITER2;
449 tunnelDispatch(tunnel);
450 return 1;
453 static int
454 tunnelError(TunnelPtr tunnel, int code, AtomPtr message)
456 int n;
457 if(tunnel->fd2 > 0) {
458 close(tunnel->fd2);
459 tunnel->fd2 = -1;
462 if(tunnel->buf2.buf == NULL)
463 tunnel->buf2.buf = get_chunk();
464 if(tunnel->buf2.buf == NULL)
465 goto fail;
467 n = httpWriteErrorHeaders(tunnel->buf2.buf, CHUNK_SIZE - 1, 0,
468 1, code, message, 1, NULL,
469 NULL, 0, NULL);
471 if(n <= 0) goto fail;
473 tunnel->buf2.head = n;
475 tunnelDispatch(tunnel);
476 return 1;
478 fail:
479 close(tunnel->fd1);
480 tunnel->fd1 = -1;
481 tunnelDispatch(tunnel);
482 return 1;
484 #endif