]> granicus.if.org Git - apache/blob - modules/proxy/mod_proxy_connect.c
95513f1f5174ba74a6af6ae8b62a87c5de57dd7e
[apache] / modules / proxy / mod_proxy_connect.c
1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 /* CONNECT method for Apache proxy */
18
19 #include "mod_proxy.h"
20 #include "apr_poll.h"
21
22 #define CONN_BLKSZ AP_IOBUFSIZE
23
24 module AP_MODULE_DECLARE_DATA proxy_connect_module;
25
26 /*
27  * This handles Netscape CONNECT method secure proxy requests.
28  * A connection is opened to the specified host and data is
29  * passed through between the WWW site and the browser.
30  *
31  * This code is based on the INTERNET-DRAFT document
32  * "Tunneling SSL Through a WWW Proxy" currently at
33  * http://www.mcom.com/newsref/std/tunneling_ssl.html.
34  *
35  * If proxyhost and proxyport are set, we send a CONNECT to
36  * the specified proxy..
37  *
38  * FIXME: this doesn't log the number of bytes sent, but
39  *        that may be okay, since the data is supposed to
40  *        be transparent. In fact, this doesn't log at all
41  *        yet. 8^)
42  * FIXME: doesn't check any headers initally sent from the
43  *        client.
44  * FIXME: should allow authentication, but hopefully the
45  *        generic proxy authentication is good enough.
46  * FIXME: no check for r->assbackwards, whatever that is.
47  */
48
49 typedef struct {
50     apr_array_header_t *allowed_connect_ports;
51 } connect_conf;
52
53 typedef struct {
54     int first;
55     int last;
56 } port_range;
57
58 static void *create_config(apr_pool_t *p, server_rec *s)
59 {
60     connect_conf *c = apr_pcalloc(p, sizeof(connect_conf));
61     c->allowed_connect_ports = apr_array_make(p, 10, sizeof(port_range));
62     return c;
63 }
64
65 static void *merge_config(apr_pool_t *p, void *basev, void *overridesv)
66 {
67     connect_conf *c = apr_pcalloc(p, sizeof(connect_conf));
68     connect_conf *base = (connect_conf *) basev;
69     connect_conf *overrides = (connect_conf *) overridesv;
70
71     c->allowed_connect_ports = apr_array_append(p,
72                                                 base->allowed_connect_ports,
73                                                 overrides->allowed_connect_ports);
74     
75     return c;
76 }
77
78
79 /*
80  * Set the ports CONNECT can use
81  */
82 static const char *
83     set_allowed_ports(cmd_parms *parms, void *dummy, const char *arg)
84 {
85     server_rec *s = parms->server;
86     int first, last;
87     connect_conf *conf =
88         ap_get_module_config(s->module_config, &proxy_connect_module);
89     port_range *New;
90     char *endptr;
91     const char *p = arg;
92
93     if (!apr_isdigit(arg[0]))
94         return "AllowCONNECT: port numbers must be numeric";
95
96     first = strtol(p, &endptr, 10);
97     if (*endptr == '-') {
98         p = endptr + 1;
99         last = strtol(p, &endptr, 10);
100     }
101     else {
102         last = first;
103     }
104
105     if (endptr == p || *endptr != '\0')  {
106         return apr_psprintf(parms->temp_pool,
107                             "Cannot parse '%s' as port number", p);
108     }
109
110     New = apr_array_push(conf->allowed_connect_ports);
111     New->first = first;
112     New->last  = last;
113     return NULL;
114 }
115
116
117 static int allowed_port(connect_conf *conf, int port)
118 {
119     int i;
120     port_range *list = (port_range *) conf->allowed_connect_ports->elts;
121     
122     if (apr_is_empty_array(conf->allowed_connect_ports)){
123         return port == APR_URI_HTTPS_DEFAULT_PORT
124                || port == APR_URI_SNEWS_DEFAULT_PORT;
125     }
126
127     for (i = 0; i < conf->allowed_connect_ports->nelts; i++) {
128         if (port >= list[i].first && port <= list[i].last)
129             return 1;
130     }
131     return 0;
132 }
133
134 /* canonicalise CONNECT URLs. */
135 static int proxy_connect_canon(request_rec *r, char *url)
136 {
137
138     if (r->method_number != M_CONNECT) {
139     return DECLINED;
140     }
141     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
142          "proxy: CONNECT: canonicalising URL %s", url);
143
144     return OK;
145 }
146
147 /* read available data (in blocks of CONN_BLKSZ) from c_i and copy to c_o */
148 static int proxy_connect_transfer(request_rec *r, conn_rec *c_i, conn_rec *c_o,
149                                   apr_bucket_brigade *bb, char *name)
150 {
151     int rv;
152 #ifdef DEBUGGING
153     apr_off_t len;
154 #endif
155
156     do {
157         apr_brigade_cleanup(bb);
158         rv = ap_get_brigade(c_i->input_filters, bb, AP_MODE_READBYTES,
159                             APR_NONBLOCK_READ, CONN_BLKSZ);
160         if (rv == APR_SUCCESS) {
161             if (c_o->aborted)
162                 return APR_EPIPE;
163             if (APR_BRIGADE_EMPTY(bb))
164                 break;
165 #ifdef DEBUGGING
166             len = -1;
167             apr_brigade_length(bb, 0, &len);
168             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
169                           "proxy: CONNECT: read %" APR_OFF_T_FMT
170                           " bytes from %s", len, name);
171 #endif
172             rv = ap_pass_brigade(c_o->output_filters, bb);
173             if (rv == APR_SUCCESS) {
174                 ap_fflush(c_o->output_filters, bb);
175             }
176             else {
177                 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
178                               "proxy: CONNECT: error on %s - ap_pass_brigade",
179                               name);
180             }
181         } else if (!APR_STATUS_IS_EAGAIN(rv)) {
182             ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
183                           "proxy: CONNECT: error on %s - ap_get_brigade",
184                           name);
185         }
186     } while (rv == APR_SUCCESS);
187
188     if (APR_STATUS_IS_EAGAIN(rv)) {
189         rv = APR_SUCCESS;
190     }
191     return rv;
192 }
193
194 /* CONNECT handler */
195 static int proxy_connect_handler(request_rec *r, proxy_worker *worker,
196                                  proxy_server_conf *conf,
197                                  char *url, const char *proxyname,
198                                  apr_port_t proxyport)
199 {
200     connect_conf *c_conf =
201         ap_get_module_config(r->server->module_config, &proxy_connect_module);
202
203     apr_pool_t *p = r->pool;
204     apr_socket_t *sock;
205     conn_rec *c = r->connection;
206     conn_rec *backconn;
207
208     apr_bucket_brigade *bb = apr_brigade_create(p, c->bucket_alloc);
209     apr_status_t err, rv;
210     apr_size_t nbytes;
211     char buffer[HUGE_STRING_LEN];
212     apr_socket_t *client_socket = ap_get_module_config(c->conn_config, &core_module);
213     int failed, rc;
214     int client_error = 0;
215     apr_pollset_t *pollset;
216     apr_pollfd_t pollfd;
217     const apr_pollfd_t *signalled;
218     apr_int32_t pollcnt, pi;
219     apr_int16_t pollevent;
220     apr_sockaddr_t *uri_addr, *connect_addr;
221
222     apr_uri_t uri;
223     const char *connectname;
224     int connectport = 0;
225
226     /* is this for us? */
227     if (r->method_number != M_CONNECT) {
228         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
229                      "proxy: CONNECT: declining URL %s", url);
230         return DECLINED;
231     }
232     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
233                  "proxy: CONNECT: serving URL %s", url);
234
235
236     /*
237      * Step One: Determine Who To Connect To
238      *
239      * Break up the URL to determine the host to connect to
240      */
241
242     /* we break the URL into host, port, uri */
243     if (APR_SUCCESS != apr_uri_parse_hostinfo(p, url, &uri)) {
244         return ap_proxyerror(r, HTTP_BAD_REQUEST,
245                              apr_pstrcat(p, "URI cannot be parsed: ", url,
246                                          NULL));
247     }
248
249     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
250                  "proxy: CONNECT: connecting %s to %s:%d", url, uri.hostname,
251                  uri.port);
252
253     /* do a DNS lookup for the destination host */
254     err = apr_sockaddr_info_get(&uri_addr, uri.hostname, APR_UNSPEC, uri.port,
255                                 0, p);
256     if (APR_SUCCESS != err) {
257         return ap_proxyerror(r, HTTP_BAD_GATEWAY,
258                              apr_pstrcat(p, "DNS lookup failure for: ",
259                                          uri.hostname, NULL));
260     }
261
262     /* are we connecting directly, or via a proxy? */
263     if (proxyname) {
264         connectname = proxyname;
265         connectport = proxyport;
266         err = apr_sockaddr_info_get(&connect_addr, proxyname, APR_UNSPEC,
267                                     proxyport, 0, p);
268     }
269     else {
270         connectname = uri.hostname;
271         connectport = uri.port;
272         connect_addr = uri_addr;
273     }
274     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
275                  "proxy: CONNECT: connecting to remote proxy %s on port %d",
276                  connectname, connectport);
277
278     /* check if ProxyBlock directive on this host */
279     if (OK != ap_proxy_checkproxyblock(r, conf, uri_addr)) {
280         return ap_proxyerror(r, HTTP_FORBIDDEN,
281                              "Connect to remote machine blocked");
282     }
283
284     /* Check if it is an allowed port */
285     if(!allowed_port(c_conf, uri.port)) {
286               return ap_proxyerror(r, HTTP_FORBIDDEN,
287                                    "Connect to remote machine blocked");
288     }
289
290     /*
291      * Step Two: Make the Connection
292      *
293      * We have determined who to connect to. Now make the connection.
294      */
295
296     /* get all the possible IP addresses for the destname and loop through them
297      * until we get a successful connection
298      */
299     if (APR_SUCCESS != err) {
300         return ap_proxyerror(r, HTTP_BAD_GATEWAY,
301                              apr_pstrcat(p, "DNS lookup failure for: ",
302                                          connectname, NULL));
303     }
304
305     /*
306      * At this point we have a list of one or more IP addresses of
307      * the machine to connect to. If configured, reorder this
308      * list so that the "best candidate" is first try. "best
309      * candidate" could mean the least loaded server, the fastest
310      * responding server, whatever.
311      *
312      * For now we do nothing, ie we get DNS round robin.
313      * XXX FIXME
314      */
315     failed = ap_proxy_connect_to_backend(&sock, "CONNECT", connect_addr,
316                                          connectname, conf, r->server,
317                                          r->pool);
318
319     /* handle a permanent error from the above loop */
320     if (failed) {
321         if (proxyname) {
322             return DECLINED;
323         }
324         else {
325             return HTTP_SERVICE_UNAVAILABLE;
326         }
327     }
328
329     /* setup polling for connection */
330     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
331                   "proxy: CONNECT: setting up poll()");
332
333     if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) {
334         apr_socket_close(sock);
335         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
336                       "proxy: CONNECT: error apr_pollset_create()");
337         return HTTP_INTERNAL_SERVER_ERROR;
338     }
339
340     /* Add client side to the poll */
341     pollfd.p = r->pool;
342     pollfd.desc_type = APR_POLL_SOCKET;
343     pollfd.reqevents = APR_POLLIN;
344     pollfd.desc.s = client_socket;
345     pollfd.client_data = NULL;
346     apr_pollset_add(pollset, &pollfd);
347
348     /* Add the server side to the poll */
349     pollfd.desc.s = sock;
350     apr_pollset_add(pollset, &pollfd);
351
352     /*
353      * Step Three: Send the Request
354      *
355      * Send the HTTP/1.1 CONNECT request to the remote server
356      */
357
358     backconn = ap_run_create_connection(c->pool, r->server, sock,
359                                         c->id, c->sbh, c->bucket_alloc);
360     if (!backconn) {
361         /* peer reset */
362         ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
363                       "proxy: an error occurred creating a new connection "
364                       "to %pI (%s)", connect_addr, connectname);
365         apr_socket_close(sock);
366         return HTTP_INTERNAL_SERVER_ERROR;
367     }
368     ap_proxy_ssl_disable(backconn);
369     rc = ap_run_pre_connection(backconn, sock);
370     if (rc != OK && rc != DONE) {
371         backconn->aborted = 1;
372         ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
373                       "proxy: CONNECT: pre_connection setup failed (%d)", rc);
374         return HTTP_INTERNAL_SERVER_ERROR;
375     }
376
377     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
378                   "proxy: CONNECT: connection complete to %pI (%s)",
379                   connect_addr, connectname);
380
381
382     /* If we are connecting through a remote proxy, we need to pass
383      * the CONNECT request on to it.
384      */
385     if (proxyport) {
386     /* FIXME: Error checking ignored.
387      */
388         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
389                      "proxy: CONNECT: sending the CONNECT request"
390                      " to the remote proxy");
391         ap_fprintf(backconn->output_filters, bb,
392                    "CONNECT %s HTTP/1.0" CRLF, r->uri);
393         ap_fprintf(backconn->output_filters, bb,
394                    "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner());
395         ap_fflush(backconn->output_filters, bb);
396     }
397     else {
398         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
399                      "proxy: CONNECT: Returning 200 OK Status");
400         nbytes = apr_snprintf(buffer, sizeof(buffer),
401                               "HTTP/1.0 200 Connection Established" CRLF);
402         ap_xlate_proto_to_ascii(buffer, nbytes);
403         ap_fwrite(c->output_filters, bb, buffer, nbytes); 
404         nbytes = apr_snprintf(buffer, sizeof(buffer),
405                               "Proxy-agent: %s" CRLF CRLF,
406                               ap_get_server_banner());
407         ap_xlate_proto_to_ascii(buffer, nbytes);
408         ap_fwrite(c->output_filters, bb, buffer, nbytes);
409         ap_fflush(c->output_filters, bb);
410 #if 0
411         /* This is safer code, but it doesn't work yet.  I'm leaving it
412          * here so that I can fix it later.
413          */
414         r->status = HTTP_OK;
415         r->header_only = 1;
416         apr_table_set(r->headers_out, "Proxy-agent: %s", ap_get_server_banner());
417         ap_rflush(r);
418 #endif
419     }
420
421     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
422                  "proxy: CONNECT: setting up poll()");
423
424     /*
425      * Step Four: Handle Data Transfer
426      *
427      * Handle two way transfer of data over the socket (this is a tunnel).
428      */
429
430     /* we are now acting as a tunnel - the input/output filter stacks should
431      * not contain any non-connection filters.
432      */
433     r->output_filters = c->output_filters;
434     r->proto_output_filters = c->output_filters;
435     r->input_filters = c->input_filters;
436     r->proto_input_filters = c->input_filters;
437 /*    r->sent_bodyct = 1;*/
438
439     while (1) { /* Infinite loop until error (one side closes the connection) */
440         if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled))
441             != APR_SUCCESS) {
442             apr_socket_close(sock);
443             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "proxy: CONNECT: error apr_poll()");
444             return HTTP_INTERNAL_SERVER_ERROR;
445         }
446 #ifdef DEBUGGING
447         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
448                      "proxy: CONNECT: woke from poll(), i=%d", pollcnt);
449 #endif
450
451         for (pi = 0; pi < pollcnt; pi++) {
452             const apr_pollfd_t *cur = &signalled[pi];
453
454             if (cur->desc.s == sock) {
455                 pollevent = cur->rtnevents;
456                 if (pollevent & APR_POLLIN) {
457 #ifdef DEBUGGING
458                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
459                                   "proxy: CONNECT: sock was readable");
460 #endif
461                     rv = proxy_connect_transfer(r, backconn, c, bb, "sock");
462                     }
463                 else if ((pollevent & APR_POLLERR)
464                          || (pollevent & APR_POLLHUP)) {
465                          rv = APR_EPIPE;
466                          ap_log_rerror(APLOG_MARK, APLOG_NOTICE, 0, r,
467                                        "proxy: CONNECT: err/hup on backconn");
468                 }
469                 if (rv != APR_SUCCESS)
470                     client_error = 1;
471             }
472             else if (cur->desc.s == client_socket) {
473                 pollevent = cur->rtnevents;
474                 if (pollevent & APR_POLLIN) {
475 #ifdef DEBUGGING
476                     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
477                                   "proxy: CONNECT: client was readable");
478 #endif
479                     rv = proxy_connect_transfer(r, c, backconn, bb, "client");
480                 }
481             }
482             else {
483                 rv = APR_EBADF;
484                 ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
485                               "proxy: CONNECT: unknown socket in pollset");
486             }
487
488         }
489         if (rv != APR_SUCCESS) {
490             break;
491         }
492     }
493
494     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
495                  "proxy: CONNECT: finished with poll() - cleaning up");
496
497     /*
498      * Step Five: Clean Up
499      *
500      * Close the socket and clean up
501      */
502
503     if (client_error)
504         apr_socket_close(sock);
505     else
506         ap_lingering_close(backconn);
507
508     c->aborted = 1;
509
510     return OK;
511 }
512
513 static void ap_proxy_connect_register_hook(apr_pool_t *p)
514 {
515     proxy_hook_scheme_handler(proxy_connect_handler, NULL, NULL, APR_HOOK_MIDDLE);
516     proxy_hook_canon_handler(proxy_connect_canon, NULL, NULL, APR_HOOK_MIDDLE);
517 }
518
519 static const command_rec cmds[] =
520 {
521     AP_INIT_ITERATE("AllowCONNECT", set_allowed_ports, NULL, RSRC_CONF,
522      "A list of ports or port ranges which CONNECT may connect to"),
523     {NULL}
524 };
525
526 module AP_MODULE_DECLARE_DATA proxy_connect_module = {
527     STANDARD20_MODULE_STUFF,
528     NULL,       /* create per-directory config structure */
529     NULL,       /* merge per-directory config structures */
530     create_config,       /* create per-server config structure */
531     merge_config,       /* merge per-server config structures */
532     cmds,       /* command apr_table_t */
533     ap_proxy_connect_register_hook  /* register hooks */
534 };