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
28 do_tunnel(int fd
, char *buf
, int offset
, int len
, AtomPtr url
)
33 n
= httpWriteErrorHeaders(buf
, CHUNK_SIZE
, 0, 1,
34 501, internAtom("CONNECT not available "
36 1, NULL
, url
->string
, url
->length
, NULL
);
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. */
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
);
63 circularBufferFull(CircularBufferPtr buf
)
65 if(buf
->head
== buf
->tail
- 1)
67 if(buf
->head
== CHUNK_SIZE
- 1 && buf
->tail
== 0)
73 circularBufferEmpty(CircularBufferPtr buf
)
75 return buf
->head
== buf
->tail
;
79 makeTunnel(int fd
, char *buf
, int offset
, int len
)
82 assert(offset
< CHUNK_SIZE
);
84 tunnel
= malloc(sizeof(TunnelRec
));
88 tunnel
->hostname
= NULL
;
93 tunnel
->buf1
.buf
= buf
;
95 tunnel
->buf1
.tail
= 0;
96 tunnel
->buf1
.head
= 0;
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;
108 destroyTunnel(TunnelPtr tunnel
)
110 assert(tunnel
->fd1
< 0 && tunnel
->fd2
< 0);
111 releaseAtom(tunnel
->hostname
);
113 dispose_chunk(tunnel
->buf1
.buf
);
115 dispose_chunk(tunnel
->buf2
.buf
);
120 do_tunnel(int fd
, char *buf
, int offset
, int len
, AtomPtr url
)
126 tunnel
= makeTunnel(fd
, buf
, offset
, len
);
128 do_log(L_ERROR
, "Couldn't allocate tunnel.\n");
135 p
= memrchr(url
->string
, ':', url
->length
);
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");
142 tunnelError(tunnel
, 400, internAtom("Couldn't parse CONNECT"));
145 tunnel
->hostname
= internAtomLowerN(url
->string
, p
- url
->string
);
146 if(tunnel
->hostname
== NULL
) {
148 tunnelError(tunnel
, 501, internAtom("Couldn't allocate hostname"));
152 if(!intListMember(port
, tunnelAllowedPorts
)) {
154 tunnelError(tunnel
, 403, internAtom("Forbidden port"));
162 do_socks_connect(parentHost
?
163 parentHost
->string
: tunnel
->hostname
->string
,
164 parentHost
? parentPort
: tunnel
->port
,
165 tunnelSocksHandler
, tunnel
);
167 do_gethostbyname(parentHost
?
168 parentHost
->string
: tunnel
->hostname
->string
, 0,
169 tunnelDnsHandler
, tunnel
);
173 tunnelDnsHandler(int status
, GethostbynameRequestPtr request
)
175 TunnelPtr tunnel
= request
->data
;
178 tunnelError(tunnel
, 504,
179 internAtomError(-status
,
180 "Host %s lookup failed",
181 atomString(tunnel
->hostname
)));
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
);
193 do_connect(retainAtom(request
->addr
), 0,
194 parentHost
? parentPort
: tunnel
->port
,
195 tunnelConnectionHandler
, tunnel
);
200 tunnelConnectionHandler(int status
,
201 FdEventHandlerPtr event
,
202 ConnectRequestPtr request
)
204 TunnelPtr tunnel
= request
->data
;
208 tunnelError(tunnel
, 504, internAtomError(-status
, "Couldn't connect"));
212 rc
= setNodelay(request
->fd
, 1);
214 do_log_error(L_WARN
, errno
, "Couldn't disable Nagle's algorithm");
216 return tunnelHandlerCommon(request
->fd
, tunnel
);
220 tunnelSocksHandler(int status
, SocksRequestPtr request
)
222 TunnelPtr tunnel
= request
->data
;
225 tunnelError(tunnel
, 504, internAtomError(-status
, "Couldn't connect"));
229 return tunnelHandlerCommon(request
->fd
, tunnel
);
233 tunnelHandlerParent(int fd
, TunnelPtr tunnel
)
238 if(tunnel
->buf1
.buf
== NULL
)
239 tunnel
->buf1
.buf
= get_chunk();
240 if(tunnel
->buf1
.buf
== NULL
) {
241 message
= "Couldn't allocate buffer";
244 if(tunnel
->buf1
.tail
!= tunnel
->buf1
.head
) {
245 message
= "Pipelined connect to parent proxy not implemented";
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");
258 message
= "Buffer overflow";
261 tunnel
->buf1
.head
= n
;
262 tunnelDispatch(tunnel
);
268 tunnelError(tunnel
, 501, internAtom(message
));
273 tunnelHandlerCommon(int fd
, TunnelPtr tunnel
)
275 const char *message
= "HTTP/1.1 200 Tunnel established\r\n\r\n";
280 return tunnelHandlerParent(fd
, tunnel
);
282 if(tunnel
->buf2
.buf
== NULL
)
283 tunnel
->buf2
.buf
= get_chunk();
284 if(tunnel
->buf2
.buf
== NULL
) {
286 tunnelError(tunnel
, 501, internAtom("Couldn't allocate buffer"));
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
);
298 bufRead(int fd
, CircularBufferPtr buf
,
299 int (*handler
)(int, FdEventHandlerPtr
, StreamRequestPtr
),
305 tail
= CHUNK_SIZE
- 1;
307 tail
= buf
->tail
- 1;
310 do_stream_buf(IO_READ
| IO_NOTNOW
,
314 else if(buf
->tail
> buf
->head
)
315 do_stream(IO_READ
| IO_NOTNOW
,
320 do_stream_2(IO_READ
| IO_NOTNOW
,
322 buf
->buf
, CHUNK_SIZE
,
328 bufWrite(int fd
, CircularBufferPtr buf
,
329 int (*handler
)(int, FdEventHandlerPtr
, StreamRequestPtr
),
332 if(buf
->head
> buf
->tail
)
338 do_stream_2(IO_WRITE
,
340 buf
->buf
, CHUNK_SIZE
,
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
378 bufWrite(tunnel
->fd1
, &tunnel
->buf2
, tunnelWrite1Handler
, tunnel
);
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
))) {
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
);
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
))) {
427 if(tunnel
->fd1
< 0 && tunnel
->fd2
< 0)
428 destroyTunnel(tunnel
);
430 assert(tunnel
->flags
& (TUNNEL_READER1
| TUNNEL_WRITER1
|
431 TUNNEL_READER2
| TUNNEL_WRITER2
));
435 tunnelRead1Handler(int status
,
436 FdEventHandlerPtr event
, StreamRequestPtr request
)
438 TunnelPtr tunnel
= request
->data
;
441 do_log_error(L_ERROR
, -status
, "Couldn't read from client");
442 tunnel
->flags
|= TUNNEL_EOF1
;
445 tunnel
->buf1
.head
= request
->offset
% CHUNK_SIZE
;
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
);
456 tunnelRead2Handler(int status
,
457 FdEventHandlerPtr event
, StreamRequestPtr request
)
459 TunnelPtr tunnel
= request
->data
;
462 do_log_error(L_ERROR
, -status
, "Couldn't read from server");
463 tunnel
->flags
|= TUNNEL_EOF2
;
466 tunnel
->buf2
.head
= request
->offset
% CHUNK_SIZE
;
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
);
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
;
489 tunnel
->buf2
.tail
= request
->offset
% CHUNK_SIZE
;
491 tunnel
->flags
&= ~TUNNEL_WRITER1
;
492 tunnelDispatch(tunnel
);
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
;
509 tunnel
->buf1
.tail
= request
->offset
% CHUNK_SIZE
;
511 tunnel
->flags
&= ~TUNNEL_WRITER2
;
512 tunnelDispatch(tunnel
);
517 tunnelError(TunnelPtr tunnel
, int code
, AtomPtr message
)
520 if(tunnel
->fd2
> 0) {
525 if(tunnel
->buf2
.buf
== NULL
)
526 tunnel
->buf2
.buf
= get_chunk();
527 if(tunnel
->buf2
.buf
== NULL
)
530 n
= httpWriteErrorHeaders(tunnel
->buf2
.buf
, CHUNK_SIZE
- 1, 0,
531 1, code
, message
, 1, NULL
,
534 if(n
<= 0) goto fail
;
536 tunnel
->buf2
.head
= n
;
538 tunnelDispatch(tunnel
);
544 tunnelDispatch(tunnel
);