/* * cgi_sock.c -- Tiny CGI trampoline to persistent socket-based app servers. * * Copyright 2003-2007 Rob Warnock . All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * $Header: /u/rpw3/src/cmd/misc/RCS/cgi_sock.c,v 1.4 2007/11/08 09:39:15 rpw3 Exp rpw3 $ * * Description: * "cgi_sock" provides a facility similar in style to "mod_lisp" (q.v.), * which is in turn based on "mod_jserv", all somewhat like "FastCGI"). * While not quite as efficient as those others (specifically, it still * requires a fork/exec per HTTP request), it does not require modification * of the web server code or installation of any non-standard modules, * and thus is more acceptable to the server's systems administrator(s) * in some cases. [Plus, the source code is *MUCH* smaller than, say, * "mod_lisp", and thus concerned administrators can be more easily * convinced that it's safe.] It can also be used to debug application * servers designed to run with "mod_lisp", providing more visibility * into any suspected web-server/application-server communications problems. * * Compile with: cc -O cgi_sock.c -o cgi_sock * * Install as "cgi_sock" in your "/cgi-bin/" directory, or as "sock.cgi" * in some directory with "AddHandler cgi-script .cgi" enabled [Apache]. * Then use "Action" and "AddHandler" directives to enable your selected * file type(s) to be handled by the persistent server, e.g.: * Action lisp-handled-pages /sock.cgi * AddHandler lisp-handled-pages .lhp * * Operation: * When run as a CGI program by a web server [such as Apache], it * opens a socket connection to its associated application server * [see below for configuration] and sends its entire Unix environment * containing the usual CGI invocation data as text strings to the * server [using one of two styles, see SOCK_PROTOCOL definition below]. * Then, if the environment contained "REQUEST_METHOD=POST", it sends * its entire stdin through end-of-file. In either case, it then closes * the output side of the socket [with shutdown(SHUT_WR), which will * signal input EOF to the reading server process], and reads/copies the * applications server's output data from the socket to its own stdout * until EOF on the socket, then exits. * * Known bugs/errata/limitations: * - There is currently no authentication protocol between this program * and the application server. * - The connected-to server must read the *entire* input stream * through EOF before outputting anything, or risk deadlock. * - The configuration is currently completely fixed at compile-time, * e.g., with the distributed configuration the application * server must be listening on the Unix domain socket * "/usr/local/lisp/local/appsrv/run/cgi.sock" [was "/tmp/.cgi_sock"]. * - Current "mod_lisp" protocol explicitly downcases the names of all * environment variables before sending them; this program does not. * [At some point I may try to negotiate this with Marc Battyani, * along with some other simplifications to "mod_lisp".] * * Edits: * 2003-08-22/rpw3 Add [optional] HTML error page when the socket * connect fails. Usually means server is down, * but may mean "listen()" queue is too small. * 2003-05-16/rpw3 Apache sticks a newline onto the end of the * value of the $SERVER_SIGNATURE env var, which * royally confuses the protocol. Get rid of it. * 2003-05-10/rpw3 Created. */ #include #include #include #include #include #include #include #include /* Define these as macros so one can override them at compile time. * [Do we want command-line options for these? NO!! It's a security risk. * Consider "REQUEST_URI=/.../handled_file?-socket_path+/bad/place"!! * If you want that flexibility, then turn this program into a * "#!/interpreter" of a configuration file that's the real CGI script.] */ #ifndef SOCK_PATH /* Was: "/tmp/.cgi_sock" */ #define SOCK_PATH "/usr/local/lisp/local/appsrv/run/cgi.sock" #endif char opt_socket_path[] = SOCK_PATH; #ifndef SOCK_DEBUG #define SOCK_DEBUG 0 #endif int opt_debug = SOCK_DEBUG; #ifndef SOCK_ERROR_HTML #define SOCK_ERROR_HTML 1 #endif /* * A choice of socket header protocols is provided, depending on what * the server expects. PROTOCOL_GETENV passes the environment variables * as single lines, as "printenv" would output them, followed by a blank * line. PROTOCOL_MODLISP passes the environment as pairs of lines -- first * the variable name then the value (with no "=" in either) -- followed by * line containing only "end". */ #define PROTOCOL_GETENV 0 #define PROTOCOL_MODLISP 1 #ifndef SOCK_PROTOCOL #define SOCK_PROTOCOL PROTOCOL_MODLISP #endif void stderr_prefix() { #if SOCK_ERROR_HTML time_t t = time(0); char *ip_addr = getenv("REMOTE_ADDR"); fprintf(stderr, "[%.24s] [error] [client %s] ", ctime(&t), (ip_addr ? ip_addr : "(unknown)")); #endif fprintf(stderr, "cgi_sock: "); return; } int socket_connect(char *path) { struct sockaddr_un su; int sock; if (strlen(path) >= sizeof(su.sun_path)) { stderr_prefix(); fprintf(stderr, "AF_UNIX pathname too long: %s", path); return -1; } memset(&su, 0, sizeof su); su.sun_family = AF_UNIX; strncpy(su.sun_path, path, sizeof su.sun_path); if (opt_debug) { stderr_prefix(); fprintf(stderr, "Using AF_UNIX path '%s'...\n", &su.sun_path); } sock = socket(PF_UNIX, SOCK_STREAM, 0); if ( sock < 0) { stderr_prefix(); perror("socket"); return -1; } if (connect(sock, (struct sockaddr *)&su, sizeof su) == -1) { stderr_prefix(); perror(su.sun_path); (void) close(sock); return -1; } return sock; } #if SOCK_PROTOCOL == PROTOCOL_GETENV void forward_environment(int sock, char *envp[]) { char *ep, *p; while (ep = *envp++) { if (p = strchr(ep, '\n')) /* Truncate any value with newline in it. */ *p = 0; /* [Should only be $SERVER_SIGNATURE, but...]*/ write(sock, ep, strlen(ep)); /* XXX Should "cat -v" this? */ write(sock, "\n", 1); } write(sock, "\n", 1); /* Blank line after "headers" */ if (opt_debug) { stderr_prefix(); fprintf(stderr, "Environment sent.\n"); } } #elif SOCK_PROTOCOL == PROTOCOL_MODLISP void forward_environment(int sock, char *envp[]) { char *ep, *p; while (ep = *envp++) { if (p = strchr(ep, '\n')) /* Truncate any value with newline in it. */ *p = 0; /* [Should only be $SERVER_SIGNATURE, but...]*/ if (!(p = strchr(ep, '='))) { /* Find the VAR=val boundary. */ /* Can happen if perp tried to put a newline into var name. */ stderr_prefix(); fprintf(stderr, "Env var '%s' contains no '='!!\n", ep); exit(1); } if (p == ep) { /* Can happen if perp created a null var name. */ stderr_prefix(); fprintf(stderr, "Env var has NULL name!!\n"); exit(1); } /* Write name & value as two separate lines */ write(sock, ep, p - ep); write(sock, "\n", 1); if (p[1]) write(sock, p + 1, strlen(p + 1)); write(sock, "\n", 1); } write(sock,"end\n",4); /* flag end of environment section */ if (opt_debug) { stderr_prefix(); fprintf(stderr, "Environment sent.\n"); } } #else #error "Unknown SOCK_PROTOCOL type." #endif void post_stdin(int sock) { int nr, nw; char buf[BUFSIZ]; if (opt_debug) { stderr_prefix(); fprintf(stderr, "Using POST method: Need EOF to finish...\n"); } while (0 < (errno = 0, nr = read(0, buf, BUFSIZ))) { if (nr != (nw = write(sock, buf, nr))) { int save_errno = errno; stderr_prefix(); fprintf(stderr, "Error writing POST data: nr = %d, nw = %d\n", nr, nw); if (errno = save_errno) perror(NULL); exit(1); } if (opt_debug) { stderr_prefix(); fprintf(stderr, "POSTed %d bytes.\n", nr); } } if (nr < 0) { stderr_prefix(); perror("Error reading stdin"); exit(1); } } void copy_output(int sock) { int nr, nw; char buf[BUFSIZ]; while (0 < (nr = read(sock, buf, BUFSIZ))) if (nr != (nw = write(1, buf, nr))) { stderr_prefix(); fprintf(stderr, "Mismatch writing output: nr = %d, nw = %d\n", nr, nw); exit(1); } if (nr < 0) { stderr_prefix(); perror("Error reading socket"); exit(1); } } #if SOCK_ERROR_HTML /* * Here to send the browser user some HTML (and to keep Apache happy * with a proper CGI response header). The actual error has already * been printed to stderr (Apache err log), so no need to leak much * information. Just mimic the Apache "Internal Error" page (but just * different enough that maintainers can tell which is which). */ void socket_connect_error_page() { char *p; char *server = "
Apache/1.3.x Server at 127.0.0.1 Port 80
"; char *admin = "webmaster@localhost"; char *error_format = "\ Content-Type: text/html\n\ Status: 500 Internal Connect Error\n\ \n\ \n\ \n\ 500 Internal Server Error\n\ \n\

Internal Application Server Error

\n\ The application server encountered an internal error or\n\ misconfiguration and was unable to complete your request.

\n\ Please contact the server administrator,\n\ %s,\n\ and inform them of the time the error occurred\n\ and what page you were attempting to access or\n\ the action you were attempting to perform when the error occurred.

\n\ More information about this error may be available\n\ in the server error log.

\n\


\n\ %s\n\ \n"; if (p = getenv("SERVER_SIGNATURE")) server = p; if (p = getenv("SERVER_ADMIN")) admin = p; printf(error_format, admin, server); /* stdout == web user */ } #endif /* SOCK_ERROR_HTML */ main(int argc, char *argv[], char *envp[]) { int sock; char *request_method = getenv("REQUEST_METHOD"); char **ap = argv; if (0 > (sock = socket_connect(opt_socket_path))) { #if SOCK_ERROR_HTML socket_connect_error_page(); #endif exit (1); } forward_environment(sock, envp); /* If POST'ing, copy stdin to socket */ if (request_method && 0 == strcmp(request_method, "POST")) post_stdin(sock); shutdown(sock, SHUT_WR); /* Send input EOF to other end. */ /* Now copy results back from socket to out stdout */ if (opt_debug) { stderr_prefix(); fprintf(stderr, "Waiting for output from socket...\n"); } copy_output(sock); if (opt_debug) { stderr_prefix(); fprintf(stderr, "All done.\n"); } exit(0); }