/* * $Id: proxy.c,v 1.1 2003/02/13 15:31:55 scott Exp $ * * proxy.c * this file contains source for proxying web requests to another * server in NSAPI 3.5 or higher * * Author(s) * Scott A. Leerssen, scott@leerssen.com * * Contributor(s) * Henry E. Thorpe in Jan 2003 * handle double-quotes in the value portion of the HTTP name: value * pairs. * * Protection Notice * Copyright (c) 1998, All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * How to Use * * * To use this, first build the NSAPI. I build on HPUX and use: * * -Ae +z -DHPUX -DMCC_HTTPD -DXP_UNIX -DNET_SSL * * In obj.conf, tell the server where your shared lib is: * * Init fn="load-modules" shlib="/path/to/libproxy.so" \ * funcs="send-proxy" * * then, in obj.conf, define which paths should be named to go * through the proxy: * * NameTrans fn="assign-name" from="/some/path" name="proxy" * * and, again in obj.conf, define the proxy object: * * * Service fn="send-proxy" host="127.0.0.1" port="81" \ * authorization="basic YWRtaW46YWRtaW4=" * * * * * * * Configuration is pretty simple. the send-proxy function expects * to find "host" in the parameters passed. The "port" parameter * is optional, and defaults to "80". An "authorization" string * can be sent by the proxy if your server on the backend needs an * HTTP Authorization header. The above example is username "admin" * and password "admin", munged and base64 encoded. It's the same * thing you'd expect to see if you watched the HTTP headers sent * between browser and server. The "authorization" parameter is * optional, and to get it, you'll have to do a little hacking of * your own :) * * At the receiving end of a proxied request, two HTTP headers * indicate what the original request and port were. These are * proxy-server-url and proxy-server-port. The values will show * up in a CGI environment as HTTP_PROXY_SERVER_URL and * HTTP_PROXY_SERVER_PORT respectively. * * * */ #include #include #include #include #include #include #include "nsapi.h" #define BUFSIZE 16384 #define NETREADTIMEOUT 120 /* seconds to wait before bailing on server */ #define NETREADSLEEP 250 /* msec to sleep on blocked thread */ #if 0 #define USE_SELECT /* use select for CGI reads/writes */ #define USE_NONBLOCK /* use non-blocking read/write over tga sd */ #endif #define USE_POLL /* use poll for CGI reads/write */ static int timeout_secs = NETREADTIMEOUT; static int sleep_msecs = NETREADSLEEP; #ifndef INADDR_NONE #define INADDR_NONE 0xffffffff /* should be in */ #endif #ifdef DEBUG static void dump_data (void * block_p, int nbytes) { int i,j,k; /* loop counters */ unsigned char * p; /* temp pointer */ char dots[] = "................"; /* a row of dots for display */ char show_chars[17]; /* displayed dump row */ char s[80]; /* string to display */ char t[80]; /* temp holding string */ p = block_p; i = nbytes; while (i>0) { strcpy(show_chars,dots); sprintf(s, "%.8x: ", p); for (j=0; j<4; j++) { for (k=0; k<4 ; k++) { i--; if (i >= 0) { if ((*p >= 0x20) && (*p < 0x7f)) show_chars[(j*4)+k] = *p; sprintf(t, "%2.2x", *p); } else { show_chars[(j*4)+k] = ' '; sprintf(t, " "); } p++; strcat(s, t); } strcat(s, " "); } strcat(s, show_chars); fprintf(stderr, "%s\n", s); } } #endif /* * set sd to non-blocking */ static int net_makenonblocking(int fd) { int flags; if ((flags = fcntl(fd, F_GETFL, 0)) == -1) { return(errno); } else { #ifdef HPUX flags |= O_NONBLOCK; #else flags |= O_NDELAY; #endif if (fcntl(fd, F_SETFL, flags) == -1) { return(errno); } } return(0); } /* * do_tcp_open * opens a tcp connection to specified host * source modified from Stevens TCP lib */ static int do_tcp_open(pblock *pb, Session *sn, Request *rq, char *host, int port) { int fd, resvport; unsigned long inaddr; struct servent *sp; struct hostent *hp; struct sockaddr_in tcp_srv_addr; /* server's Internet socket addr */ /* * Initialize the server's Internet address structure. * We'll store the actual 4-byte Internet address and the * 2-byte port# below. */ bzero((char *) &tcp_srv_addr, sizeof(tcp_srv_addr)); tcp_srv_addr.sin_family = AF_INET; if (port <= 0) { return(-1); } tcp_srv_addr.sin_port = htons(port); if ((inaddr = inet_addr(host)) == INADDR_NONE) { log_error(LOG_FAILURE, "send-proxy", sn, rq, "invalid IP address: %s", host); return(-1); } /* it's dotted-decimal */ bcopy((char *) &inaddr, (char *) &tcp_srv_addr.sin_addr, sizeof(inaddr)); fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { return(-1); } /* * Connect to the server. */ if (connect(fd, (struct sockaddr *) &tcp_srv_addr, sizeof(tcp_srv_addr)) < 0) { if (errno != EINPROGRESS) { close(fd); return(-1); } } #ifdef USE_NONBLOCK net_makenonblocking(fd); #endif return(fd); /* all OK */ } /* * fill_buffer * this function fills a passed, allocated buffer of passed size * with data from the passed socket (all passed, get the point?) * * returns * number of bytes read, possibly zero (means all done) * * arguments * pb, sn, rq * sd socket to read from * io_buf buffer to fill * bufsz size of said buffer */ static int fill_buffer(pblock *pb, Session *sn, Request *rq, int sd, char *io_buf, int bufsz) { char *myname = "fill_buffer()"; char *msg; int bytes; char *cur_pos; int cur_bytes; int start_time, cur_time; #ifdef USE_SELECT fd_set readfds, errorfds; struct timeval timeout; #endif #ifdef USE_POLL struct pollfd fds[1]; #endif start_time = time(NULL); #ifdef USE_SELECT timeout.tv_sec = timeout_secs; timeout.tv_usec = 0; FD_ZERO(&errorfds); FD_ZERO(&readfds); #endif errno = 0; for (cur_pos=io_buf, cur_bytes=0; cur_bytes < bufsz; cur_pos+=bytes, cur_bytes+=bytes) { #ifdef USE_POLL fds[0].fd = sd; fds[0].events = POLLNORM; if (poll(fds, 1, timeout_secs * 1000) <= 0) { log_error(LOG_INFORM, myname, sn, rq, "poll() timed out"); } #endif #ifdef USE_SELECT FD_SET(sd, &readfds); select(sd+1, &readfds, NULL, &errorfds, &timeout); #endif bytes = read(sd, cur_pos, bufsz-cur_bytes); if (bytes < 0) { if (timeout_secs) { /* * if we are checking timeouts (and why wouldn't we???!!!), * get the current time and see if we've waited too * long before getting any data from the server */ cur_time = time(NULL); if ((cur_time - start_time) > timeout_secs) { errno = ETIMEDOUT; } } if (errno == EAGAIN) { if (!cur_bytes) { systhread_sleep(sleep_msecs); } else { break; } } else if (errno == ETIMEDOUT) { msg = strerror(errno); protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); return(REQ_ABORTED); } /* * let's not go backward's through the buffet line and make * that big fat lady behind us really mad... */ bytes = 0; continue; } if (!bytes) { /* * connection gone (aka EOF), so we are done (burp) */ break; } /* * we know we got at least one byte of data. * just go ahead and return whatever we have * so we don't keep the client waiting needlessly */ cur_bytes += bytes; break; } return(cur_bytes); } /* * setup_connection * this function sets up the connection, argv and environment * needed by the TGA daemon. * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq */ static int setup_connection(pblock *pb, Session *sn, Request *rq, int *sd) { char *myname = "setup_connection()"; char *host = pblock_findval("host", pb); int port = atoi(pblock_findval("port", pb)); int status; if (!host) { log_error(LOG_FAILURE, "send-proxy", sn, rq, "%s: \"host\" must be specified in obj.conf", myname); return(REQ_ABORTED); } if (port <= 0) { log_error(LOG_WARN, "send-proxy", sn, rq, "%s: default port of 80 being used", myname); port = 80; } /* * open socket to backend web server */ *sd = do_tcp_open(pb, sn, rq, host, port); if (*sd < 0) { log_error(LOG_FAILURE, "send-proxy", sn, rq, "%s failed to connect to host:%s, port:%d.", myname, host, port); return(REQ_ABORTED); } return(REQ_PROCEED); } /* * post_write * this function writes data to a non-blocking socket * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq * sd socket descriptor * io_buf buffer to use for reading and writing * bufsz size of said buffer */ static int post_write(pblock *pb, Session *sn, Request *rq, int sd, char *io_buf, int bytes) { char *myname = "post_write()"; int bytes_out, written; #ifdef USE_POLL struct pollfd fds[1]; #endif #ifdef USE_SELECT fd_set writefds, errorfds; #endif #ifdef DEBUG fprintf(stderr, "writing %d bytes on sd %d\n", bytes, sd); #endif /* * write out the buffer to the server */ #ifdef USE_SELECT FD_ZERO(&writefds); FD_ZERO(&errorfds); #endif for(bytes_out=0; bytes_outheaders */ #define ADD_HEADER(name, value, rq) \ {\ param_free(pblock_remove((name), (rq)->headers));\ if (value) pblock_nvinsert((name), (value), (rq)->headers);\ } /* * add_client_headers * adds info about the client to the headers. this info is * typically lost when the server behind us thinks that we * originated the request. * * returns * void * * arguments * pb, sn and rq */ void add_client_headers(pblock *pb, Session *sn, Request *rq) { char buf[6]; char *value; char *cert_LF,*current_position,*next_position; value = http_uri2url_dynamic("", "", sn, rq); ADD_HEADER("proxy-server-url", value, rq); sprintf(buf, "%5d", server_portnum); value = buf; ADD_HEADER("proxy-server-port", value, rq); value = session_maxdns(sn); if (!value || !*value) { value = pblock_findval("ip", sn->client); ADD_HEADER("proxy-remote-host", value, rq); } value = server_hostname; ADD_HEADER("proxy-server-name", value, rq); value = pblock_findval("ip", sn->client); ADD_HEADER("proxy-remote-addr", value, rq); value = pblock_findval("auth-user", rq->vars); ADD_HEADER("proxy-remote-user", value, rq); value = pblock_findval("auth-type", rq->vars); ADD_HEADER("proxy-auth-type", value, rq); if (security_active) { value = "ON"; } else { value = "OFF"; } ADD_HEADER("proxy-https", value, rq); value = pblock_findval("keysize", sn->client); ADD_HEADER("proxy-keysize", value, rq); value = pblock_findval("secret-https-keysize", sn->client); ADD_HEADER("proxy-https-secretkeysize", value, rq); value = pblock_findval("ssl-id", sn->client); ADD_HEADER("proxy-https-sessionid", value, rq); value = pblock_findval("user_dn", sn->client); ADD_HEADER("proxy-https-userdn", value, rq); value = pblock_findval("issuer_dn", sn->client); ADD_HEADER("proxy-https-issuerdn", value, rq); /* we need to do some special processing around certificate header... * the certificate pointed to in the request has embedded linefeeds. * This causes some servers... like Apache 1.3.12.... to barf when * processing them. It should be legal... linefeeds are not specifically * prohibited and the terminator of a header filed is CRLF, but Apache * looks for LF ONLY so we are going to strip them from certificates. * The cert will appear as strings LFs with one at the end. */ value = pblock_findval("auth-cert", rq->vars); /* if value is NULL, skip the rest.... */ if ( value != NULL) { /* make a copy of this so that we can manipulate it... */ cert_LF=strdup(value); /* * lets go into a loop and looking for the last LF , and copy everything * at the next position after the LF to the LF position (overwrite LF) * Do that until the cert contains no more linefeeds.. */ while ( (current_position = strrchr(cert_LF, '\n')) != NULL) { next_position = ¤t_position[1] ; strcpy(current_position, next_position); } ADD_HEADER("proxy-client-cert", cert_LF , rq); free(cert_LF); } } /* * send_headers * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq * sd - socket to spew on */ static int send_headers(pblock *pb, Session *sn, Request *rq, int sd) { char *np, *vp, *name, *value; char *clientRequest = pblock_findval("clf-request", rq->reqpb); char *headers; static char sep[] = ": "; static char crlf[] = "\r\n"; char *authstr; char *autodgstr; char *host = pblock_findval("host", rq->headers); int status = REQ_PROCEED; /* * replace authorization string if necessary */ if (authstr = pblock_findval("authorization", pb)) { param_free(pblock_remove("authorization", rq->headers)); pblock_nvinsert("authorization", authstr, rq->headers); } /* * add info about client */ add_client_headers(pb, sn, rq); /* * keep-alive doesn't make any sense, now does it? */ param_free(pblock_remove("connection", rq->headers)); pblock_nvinsert("connection", "close", rq->headers); if (authstr = pblock_findval("autodowngrade", pb)) { char *t = strrstr(clientRequest, "HTTP/"); char *end = clientRequest + strlen(clientRequest); if (t && (t+8 <= end)) { sprintf(t+5, "1.0"); } } post_write(pb, sn, rq, sd, clientRequest, strlen(clientRequest)); post_write(pb, sn, rq, sd, crlf, strlen(crlf)); /* * send the client headers to the server Henry E. Thorpe Jan2003 * just iterate through the header parameters (ala answer * 5.4 of the NSAPI programming FAQ), and all is well. */ for (i=0; iheaders->hsize; i++) { for (p=rq->headers->ht[i]; p; p = p->next) { /* do what you want here p->param->name is the value name p->param->value is the value */ post_write(pb, sn, rq, sd, p->param->name, strlen(p->param->name)); post_write(pb, sn, rq, sd, sep, strlen(sep)); post_write(pb, sn, rq, sd, p->param->value, strlen(p->param->value)); post_write(pb, sn, rq, sd, crlf, strlen(crlf)); } } #ifdef DEBUG np = headers = pblock_pblock2str(rq->headers, NULL); fprintf(stderr, "===========pblock============\n"); fprintf(stderr, "%s\n", np); fprintf(stderr, "===========pblock============\n"); #endif /* * finish off the headers with one blank line */ post_write(pb, sn, rq, sd, crlf, strlen(crlf)); bail: FREE(headers); return(status); } /* * post_read * this function reads the POST data from the browser * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq * io_buf buffer to use for reading and writing * readsz amount to read * bytes bytes actually read */ static int post_read(pblock *pb, Session *sn, Request *rq, char *io_buf, int readsz, int *bytes) { char *myname = "post_read()"; int inbytes; /* bytes in NSAPI netbuf */ /* * suck characters from the netbuf that is associated with the browser's * connection to us. * * netbuf_getc() is kind of nice in that it will continue to advance * the netbuf, filling it as necessary. */ #ifdef DEBUG fprintf(stderr, "reading %d bytes from browser\n", readsz); #endif errno = 0; /* note post_data() makes certain readsz is not greater than BUFSIZE */ inbytes = netbuf_getbytes(sn->inbuf, io_buf, readsz); if (inbytes < 0) { /* catch all IO errors */ protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, NULL); log_error(LOG_FAILURE, myname, sn, rq, "io error errno %d reading POST data from browser, read returned %d",errno, inbytes); return(REQ_ABORTED); } #ifdef DEBUG fprintf(stderr, "got %d POST bytes from browser\n", inbytes); #endif *bytes = inbytes; return(REQ_PROCEED); } /* * post_data * this function POSTs data from the browser to the server * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq * sd server socket desscriptor to write on * io_buf buffer to use for reading and writing * bufsz size of said buffer * length content-length to post */ static int post_data(pblock *pb, Session *sn, Request *rq, int sd, char *io_buf, int bufsz, int length) { char *myname = "post_data()"; int bytes; int status = REQ_PROCEED; for(bytes=0; length > 0; length-=bytes) { if ((status = post_read(pb, sn, rq, io_buf, (length > bufsz) ? bufsz : length, &bytes)) != REQ_PROCEED) { break; } if ((status = post_write(pb, sn, rq, sd, io_buf, bytes)) != REQ_PROCEED) { /* * ugh, the server quit taking data, but he may have said * something before he whacked us, so allow pass_data() * to get called by returning REQ_PROCEED */ status = REQ_PROCEED; break; } } return(status); } /* * pass_data * this function does all the work of shuffling data from the * proxied server * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * our friends, pb, sn and rq * sd socket descriptor to proxied server * io_buf buffer to use for reading and writing * bufsz size of said buffer */ static int pass_data(pblock *pb, Session *sn, Request *rq, int sd, char *io_buf, int bufsz) { char *myname = "pass_data()"; int bytes; int total_bytes = 0; struct stat finfo; rq->senthdrs = 1; param_free(pblock_remove("content-type",rq->srvhdrs)); protocol_start_response(sn, rq); #ifdef DEBUG fprintf(stderr, "reading data from proxied server\n"); #endif for (bytes=fill_buffer(pb,sn,rq,sd,io_buf,bufsz); bytes > 0; bytes=fill_buffer(pb,sn,rq,sd,io_buf,bufsz)) { #ifdef DEBUG fprintf(stderr, "%d bytes read from proxied server\n", bytes); dump_data(io_buf, bytes); #endif if (net_write(sn->csd, io_buf, bytes) <= 0) { return(REQ_ABORTED); } #ifdef DEBUG fprintf(stderr, "%d bytes written to browser\n", bytes); dump_data(io_buf, bytes); #endif /* calculating the total number of bytes for writing into the access log */ total_bytes += bytes; } memset((char *)&finfo, '\0', sizeof(struct stat)); finfo.st_size = total_bytes; /* retrieves the content-length from stat struct finfo and adds into the response headers */ protocol_set_finfo(sn, rq, &finfo); protocol_start_response(sn, rq); return(REQ_PROCEED); } /* * check_post * this function checks to see if this request is a post, and if so, * returns the content-length * * returns * content-length if a post, zero if not a post * * arguments * good old sn and rq */ static int check_post(Session *sn, Request *rq) { char *content_len; if(strcasecmp(pblock_findval("method", rq->reqpb),"POST")) { return(0); } content_len = pblock_findval("content-length", rq->headers); return(atoi(content_len)); } /* * send_proxy * this function directs the connection setup and transfer of data * from the NSAPI to another web server somewhere else * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * good old pb, ns and rq */ NSAPI_PUBLIC int send_proxy(pblock *pb, Session *sn, Request *rq) { char *myname = "send_proxy()"; char *io_buf; int sd; int status; int post_length; struct stat finfo; #ifdef DEBUG fprintf(stderr, "\n\n\n\n===============\n= new request =\n===============\n"); #endif if ((io_buf = (char *) MALLOC(BUFSIZE)) == NULL) { protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, "proxy: malloc failed"); return(REQ_ABORTED); } status = setup_connection(pb, sn, rq, &sd); if (status != REQ_PROCEED) { protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, "proxy: setup_connection failed"); FREE(io_buf); return(status); } if ((status = send_headers(pb, sn, rq, sd)) != REQ_PROCEED) { protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, "proxy: send_headers failed"); close(sd); FREE(io_buf); return(status); } if (post_length = check_post(sn, rq)) { #ifdef DEBUG fprintf(stderr, "posting %d bytes to sd %d\n", post_length, sd); #endif if ((status = post_data(pb, sn, rq, sd, io_buf, BUFSIZE, post_length)) != REQ_PROCEED) { protocol_status(sn, rq, PROTOCOL_SERVER_ERROR, "proxy: post_data failed"); close(sd); FREE(io_buf); return(status); } } if ((status = pass_data(pb, sn, rq, sd, io_buf, BUFSIZE)) == REQ_PROCEED) /* sets the session's status and adds it into the response headers */ protocol_status(sn, rq, PROTOCOL_OK, "proxy: pass_data success"); else protocol_status(sn, rq, PROTOCOL_OK, "proxy: pass_data failed"); protocol_start_response(sn, rq); close(sd); shutdown(net_native_handle(sn->csd), SHUT_RDWR); FREE(io_buf); #ifdef DEBUG fprintf(stderr, "===============\n= end request =\n===============\n"); #endif return(status); } /* * init_proxy * this function sets up the proxy. * * returns * REQ_PROCEED on success, REQ_ABORTED on failure * * arguments * good old pb, ns and rq */ NSAPI_PUBLIC int init_proxy(pblock *pb, Session *sn, Request *rq) { char *myname = "init_proxy()"; char *timeout = pblock_findval("timeout", pb); char *sleeptime = pblock_findval("sleep", pb); if (timeout && (timeout_secs = atoi(timeout)) <= 0) { timeout_secs = (strcasecmp(timeout, "none")) ? NETREADTIMEOUT : 0; } fprintf(stderr, "%s: proxy read time-out set to %d seconds\n", myname, timeout_secs); if (sleeptime && (sleep_msecs = atoi(sleeptime)) <= 0) { sleep_msecs = NETREADSLEEP; } fprintf(stderr, "%s: thread sleep set to %d milliseconds\n", myname, sleep_msecs); }