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#define CORE_PRIVATE 20 21#include "mod_proxy.h" 22#include "apr_poll.h" 23 24module 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 49static int allowed_port(proxy_server_conf *conf, int port) 50{ 51 int i; 52 int *list = (int *) conf->allowed_connect_ports->elts; 53 54 for(i = 0; i < conf->allowed_connect_ports->nelts; i++) { 55 if(port == list[i]) 56 return 1; 57 } 58 return 0; 59} 60 61/* canonicalise CONNECT URLs. */ 62static int proxy_connect_canon(request_rec *r, char *url) 63{ 64 65 if (r->method_number != M_CONNECT) { 66 return DECLINED; 67 } 68 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 69 "proxy: CONNECT: canonicalising URL %s", url); 70 71 return OK; 72} 73 74/* CONNECT handler */ 75static int proxy_connect_handler(request_rec *r, proxy_worker *worker, 76 proxy_server_conf *conf, 77 char *url, const char *proxyname, 78 apr_port_t proxyport) 79{ 80 apr_pool_t *p = r->pool; 81 apr_socket_t *sock; 82 apr_status_t err, rv; 83 apr_size_t i, o, nbytes; 84 char buffer[HUGE_STRING_LEN]; 85 apr_socket_t *client_socket = ap_get_module_config(r->connection->conn_config, &core_module); 86 int failed; 87 apr_pollset_t *pollset; 88 apr_pollfd_t pollfd; 89 const apr_pollfd_t *signalled; 90 apr_int32_t pollcnt, pi; 91 apr_int16_t pollevent; 92 apr_sockaddr_t *uri_addr, *connect_addr; 93 94 apr_uri_t uri; 95 const char *connectname; 96 int connectport = 0; 97 98 /* is this for us? */ 99 if (r->method_number != M_CONNECT) { 100 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 101 "proxy: CONNECT: declining URL %s", url); 102 return DECLINED; 103 } 104 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 105 "proxy: CONNECT: serving URL %s", url); 106 107 108 /* 109 * Step One: Determine Who To Connect To 110 * 111 * Break up the URL to determine the host to connect to 112 */ 113 114 /* we break the URL into host, port, uri */ 115 if (APR_SUCCESS != apr_uri_parse_hostinfo(p, url, &uri)) { 116 return ap_proxyerror(r, HTTP_BAD_REQUEST, 117 apr_pstrcat(p, "URI cannot be parsed: ", url, 118 NULL)); 119 } 120 121 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 122 "proxy: CONNECT: connecting %s to %s:%d", url, uri.hostname, uri.port); 123 124 /* do a DNS lookup for the destination host */ 125 err = apr_sockaddr_info_get(&uri_addr, uri.hostname, APR_UNSPEC, uri.port, 126 0, p); 127 if (APR_SUCCESS != err) { 128 return ap_proxyerror(r, HTTP_BAD_GATEWAY, 129 apr_pstrcat(p, "DNS lookup failure for: ", 130 uri.hostname, NULL)); 131 } 132 133 /* are we connecting directly, or via a proxy? */ 134 if (proxyname) { 135 connectname = proxyname; 136 connectport = proxyport; 137 err = apr_sockaddr_info_get(&connect_addr, proxyname, APR_UNSPEC, proxyport, 0, p); 138 } 139 else { 140 connectname = uri.hostname; 141 connectport = uri.port; 142 connect_addr = uri_addr; 143 } 144 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 145 "proxy: CONNECT: connecting to remote proxy %s on port %d", connectname, connectport); 146 147 /* check if ProxyBlock directive on this host */ 148 if (OK != ap_proxy_checkproxyblock(r, conf, uri_addr)) { 149 return ap_proxyerror(r, HTTP_FORBIDDEN, 150 "Connect to remote machine blocked"); 151 } 152 153 /* Check if it is an allowed port */ 154 if (conf->allowed_connect_ports->nelts == 0) { 155 /* Default setting if not overridden by AllowCONNECT */ 156 switch (uri.port) { 157 case APR_URI_HTTPS_DEFAULT_PORT: 158 case APR_URI_SNEWS_DEFAULT_PORT: 159 break; 160 default: 161 /* XXX can we call ap_proxyerror() here to get a nice log message? */ 162 return HTTP_FORBIDDEN; 163 } 164 } else if(!allowed_port(conf, uri.port)) { 165 /* XXX can we call ap_proxyerror() here to get a nice log message? */ 166 return HTTP_FORBIDDEN; 167 } 168 169 /* 170 * Step Two: Make the Connection 171 * 172 * We have determined who to connect to. Now make the connection. 173 */ 174 175 /* get all the possible IP addresses for the destname and loop through them 176 * until we get a successful connection 177 */ 178 if (APR_SUCCESS != err) { 179 return ap_proxyerror(r, HTTP_BAD_GATEWAY, 180 apr_pstrcat(p, "DNS lookup failure for: ", 181 connectname, NULL)); 182 } 183 184 /* 185 * At this point we have a list of one or more IP addresses of 186 * the machine to connect to. If configured, reorder this 187 * list so that the "best candidate" is first try. "best 188 * candidate" could mean the least loaded server, the fastest 189 * responding server, whatever. 190 * 191 * For now we do nothing, ie we get DNS round robin. 192 * XXX FIXME 193 */ 194 failed = ap_proxy_connect_to_backend(&sock, "CONNECT", connect_addr, 195 connectname, conf, r->server, 196 r->pool); 197 198 /* handle a permanent error from the above loop */ 199 if (failed) { 200 if (proxyname) { 201 return DECLINED; 202 } 203 else { 204 return HTTP_SERVICE_UNAVAILABLE; 205 } 206 } 207 208 /* 209 * Step Three: Send the Request 210 * 211 * Send the HTTP/1.1 CONNECT request to the remote server 212 */ 213 214 /* we are acting as a tunnel - the output filter stack should 215 * be completely empty, because when we are done here we are done completely. 216 * We add the NULL filter to the stack to do this... 217 */ 218 r->output_filters = NULL; 219 r->connection->output_filters = NULL; 220 221 222 /* If we are connecting through a remote proxy, we need to pass 223 * the CONNECT request on to it. 224 */ 225 if (proxyport) { 226 /* FIXME: Error checking ignored. 227 */ 228 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 229 "proxy: CONNECT: sending the CONNECT request to the remote proxy"); 230 nbytes = apr_snprintf(buffer, sizeof(buffer), 231 "CONNECT %s HTTP/1.0" CRLF, r->uri); 232 apr_socket_send(sock, buffer, &nbytes); 233 nbytes = apr_snprintf(buffer, sizeof(buffer), 234 "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner()); 235 apr_socket_send(sock, buffer, &nbytes); 236 } 237 else { 238 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 239 "proxy: CONNECT: Returning 200 OK Status"); 240 nbytes = apr_snprintf(buffer, sizeof(buffer), 241 "HTTP/1.0 200 Connection Established" CRLF); 242 ap_xlate_proto_to_ascii(buffer, nbytes); 243 apr_socket_send(client_socket, buffer, &nbytes); 244 nbytes = apr_snprintf(buffer, sizeof(buffer), 245 "Proxy-agent: %s" CRLF CRLF, ap_get_server_banner()); 246 ap_xlate_proto_to_ascii(buffer, nbytes); 247 apr_socket_send(client_socket, buffer, &nbytes); 248#if 0 249 /* This is safer code, but it doesn't work yet. I'm leaving it 250 * here so that I can fix it later. 251 */ 252 r->status = HTTP_OK; 253 r->header_only = 1; 254 apr_table_set(r->headers_out, "Proxy-agent: %s", ap_get_server_banner()); 255 ap_rflush(r); 256#endif 257 } 258 259 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 260 "proxy: CONNECT: setting up poll()"); 261 262 /* 263 * Step Four: Handle Data Transfer 264 * 265 * Handle two way transfer of data over the socket (this is a tunnel). 266 */ 267 268/* r->sent_bodyct = 1;*/ 269 270 if ((rv = apr_pollset_create(&pollset, 2, r->pool, 0)) != APR_SUCCESS) { 271 apr_socket_close(sock); 272 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 273 "proxy: CONNECT: error apr_pollset_create()"); 274 return HTTP_INTERNAL_SERVER_ERROR; 275 } 276 277 /* Add client side to the poll */ 278 pollfd.p = r->pool; 279 pollfd.desc_type = APR_POLL_SOCKET; 280 pollfd.reqevents = APR_POLLIN; 281 pollfd.desc.s = client_socket; 282 pollfd.client_data = NULL; 283 apr_pollset_add(pollset, &pollfd); 284 285 /* Add the server side to the poll */ 286 pollfd.desc.s = sock; 287 apr_pollset_add(pollset, &pollfd); 288 289 while (1) { /* Infinite loop until error (one side closes the connection) */ 290 if ((rv = apr_pollset_poll(pollset, -1, &pollcnt, &signalled)) != APR_SUCCESS) { 291 if (APR_STATUS_IS_EINTR(rv)) { 292 continue; 293 } 294 apr_socket_close(sock); 295 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, "proxy: CONNECT: error apr_poll()"); 296 return HTTP_INTERNAL_SERVER_ERROR; 297 } 298#ifdef DEBUGGING 299 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 300 "proxy: CONNECT: woke from select(), i=%d", pollcnt); 301#endif 302 303 for (pi = 0; pi < pollcnt; pi++) { 304 const apr_pollfd_t *cur = &signalled[pi]; 305 306 if (cur->desc.s == sock) { 307 pollevent = cur->rtnevents; 308 if (pollevent & APR_POLLIN) { 309#ifdef DEBUGGING 310 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 311 "proxy: CONNECT: sock was set"); 312#endif 313 nbytes = sizeof(buffer); 314 rv = apr_socket_recv(sock, buffer, &nbytes); 315 if (rv == APR_SUCCESS) { 316 o = 0; 317 i = nbytes; 318 while(i > 0) 319 { 320 nbytes = i; 321 /* This is just plain wrong. No module should ever write directly 322 * to the client. For now, this works, but this is high on my list of 323 * things to fix. The correct line is: 324 * if ((nbytes = ap_rwrite(buffer + o, nbytes, r)) < 0) 325 * rbb 326 */ 327 rv = apr_socket_send(client_socket, buffer + o, &nbytes); 328 if (rv != APR_SUCCESS) 329 break; 330 o += nbytes; 331 i -= nbytes; 332 } 333 } 334 else 335 break; 336 } 337 else if ((pollevent & APR_POLLERR) || (pollevent & APR_POLLHUP)) 338 break; 339 } 340 else if (cur->desc.s == client_socket) { 341 pollevent = cur->rtnevents; 342 if (pollevent & APR_POLLIN) { 343#ifdef DEBUGGING 344 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 345 "proxy: CONNECT: client was set"); 346#endif 347 nbytes = sizeof(buffer); 348 rv = apr_socket_recv(client_socket, buffer, &nbytes); 349 if (rv == APR_SUCCESS) { 350 o = 0; 351 i = nbytes; 352#ifdef DEBUGGING 353 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 354 "proxy: CONNECT: read %d from client", i); 355#endif 356 while(i > 0) 357 { 358 nbytes = i; 359 rv = apr_socket_send(sock, buffer + o, &nbytes); 360 if (rv != APR_SUCCESS) 361 break; 362 o += nbytes; 363 i -= nbytes; 364 } 365 } 366 else 367 break; 368 } 369 else if ((pollevent & APR_POLLERR) || (pollevent & APR_POLLHUP)) { 370 rv = APR_EOF; 371 break; 372 } 373 } 374 else 375 break; 376 } 377 if (rv != APR_SUCCESS) { 378 break; 379 } 380 } 381 382 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, 383 "proxy: CONNECT: finished with poll() - cleaning up"); 384 385 /* 386 * Step Five: Clean Up 387 * 388 * Close the socket and clean up 389 */ 390 391 apr_socket_close(sock); 392 393 return OK; 394} 395 396static void ap_proxy_connect_register_hook(apr_pool_t *p) 397{ 398 proxy_hook_scheme_handler(proxy_connect_handler, NULL, NULL, APR_HOOK_MIDDLE); 399 proxy_hook_canon_handler(proxy_connect_canon, NULL, NULL, APR_HOOK_MIDDLE); 400} 401 402module AP_MODULE_DECLARE_DATA proxy_connect_module = { 403 STANDARD20_MODULE_STUFF, 404 NULL, /* create per-directory config structure */ 405 NULL, /* merge per-directory config structures */ 406 NULL, /* create per-server config structure */ 407 NULL, /* merge per-server config structures */ 408 NULL, /* command apr_table_t */ 409 ap_proxy_connect_register_hook /* register hooks */ 410}; 411