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
,
250 CHUNK_SIZE
- tunnel
->buf1
.tail
,
251 "CONNECT %s:%d HTTP/1.1"
253 tunnel
->hostname
->string
, tunnel
->port
);
255 message
= "Buffer overflow";
258 tunnel
->buf1
.head
= n
;
259 tunnelDispatch(tunnel
);
265 tunnelError(tunnel
, 501, internAtom(message
));
270 tunnelHandlerCommon(int fd
, TunnelPtr tunnel
)
272 const char *message
= "HTTP/1.1 200 Tunnel established\r\n\r\n";
277 return tunnelHandlerParent(fd
, tunnel
);
279 if(tunnel
->buf2
.buf
== NULL
)
280 tunnel
->buf2
.buf
= get_chunk();
281 if(tunnel
->buf2
.buf
== NULL
) {
283 tunnelError(tunnel
, 501, internAtom("Couldn't allocate buffer"));
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
);
295 bufRead(int fd
, CircularBufferPtr buf
,
296 int (*handler
)(int, FdEventHandlerPtr
, StreamRequestPtr
),
302 tail
= CHUNK_SIZE
- 1;
304 tail
= buf
->tail
- 1;
307 do_stream_buf(IO_READ
| IO_NOTNOW
,
311 else if(buf
->tail
> buf
->head
)
312 do_stream(IO_READ
| IO_NOTNOW
,
317 do_stream_2(IO_READ
| IO_NOTNOW
,
319 buf
->buf
, CHUNK_SIZE
,
325 bufWrite(int fd
, CircularBufferPtr buf
,
326 int (*handler
)(int, FdEventHandlerPtr
, StreamRequestPtr
),
329 if(buf
->head
> buf
->tail
)
335 do_stream_2(IO_WRITE
,
337 buf
->buf
, CHUNK_SIZE
,
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
375 bufWrite(tunnel
->fd1
, &tunnel
->buf2
, tunnelWrite1Handler
, tunnel
);
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
))) {
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
);
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
))) {
424 if(tunnel
->fd1
< 0 && tunnel
->fd2
< 0)
425 destroyTunnel(tunnel
);
427 assert(tunnel
->flags
& (TUNNEL_READER1
| TUNNEL_WRITER1
|
428 TUNNEL_READER2
| TUNNEL_WRITER2
));
432 tunnelRead1Handler(int status
,
433 FdEventHandlerPtr event
, StreamRequestPtr request
)
435 TunnelPtr tunnel
= request
->data
;
438 do_log_error(L_ERROR
, -status
, "Couldn't read from client");
439 tunnel
->flags
|= TUNNEL_EOF1
;
442 tunnel
->buf1
.head
= request
->offset
% CHUNK_SIZE
;
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
);
453 tunnelRead2Handler(int status
,
454 FdEventHandlerPtr event
, StreamRequestPtr request
)
456 TunnelPtr tunnel
= request
->data
;
459 do_log_error(L_ERROR
, -status
, "Couldn't read from server");
460 tunnel
->flags
|= TUNNEL_EOF2
;
463 tunnel
->buf2
.head
= request
->offset
% CHUNK_SIZE
;
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
);
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
;
486 tunnel
->buf2
.tail
= request
->offset
% CHUNK_SIZE
;
488 tunnel
->flags
&= ~TUNNEL_WRITER1
;
489 tunnelDispatch(tunnel
);
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
;
506 tunnel
->buf1
.tail
= request
->offset
% CHUNK_SIZE
;
508 tunnel
->flags
&= ~TUNNEL_WRITER2
;
509 tunnelDispatch(tunnel
);
514 tunnelError(TunnelPtr tunnel
, int code
, AtomPtr message
)
517 if(tunnel
->fd2
> 0) {
522 if(tunnel
->buf2
.buf
== NULL
)
523 tunnel
->buf2
.buf
= get_chunk();
524 if(tunnel
->buf2
.buf
== NULL
)
527 n
= httpWriteErrorHeaders(tunnel
->buf2
.buf
, CHUNK_SIZE
- 1, 0,
528 1, code
, message
, 1, NULL
,
531 if(n
<= 0) goto fail
;
533 tunnel
->buf2
.head
= n
;
535 tunnelDispatch(tunnel
);
541 tunnelDispatch(tunnel
);