/* * Copyright 2000,2001 F.W. Dillema, University of Tromso, Norway * $Id: www6to4.c,v 1.38 2003/11/26 09:49:06 dillema Exp $ * * Distributed under the GNU General Public License; see the README file. * http://www.vermicelli.pasta.cs.uit.no/ipv6/software/ * * Based on code with the following license: * * Written and copyright 1997 Anonymous Coders and Junkbusters Corporation. * Distributed under the GNU General Public License; see the README file. * This code comes with NO WARRANTY. http://www.junkbusters.com/ht/en/gpl.html */ #define SYSLOG_NAMES #include "www6to4.h" #define VERSION "www6to4 v1.6" #define BANNER ""VERSION"" char CFAIL[] = "HTTP/1.0 503 Connect failed\r\n" "Content-Type: text/html\r\n\r\n" "\r\n" "\r\n" "WWW6to4 Proxy: Connect failed\r\n" "\r\n" "\r\n" "

" BANNER "

\r\n" "TCP connection to '%s' failed: %s.\r\n
" "\r\n" "\r\n"; char CNXDOM[] = "HTTP/1.0 404 Non-existent domain\r\n" "Content-Type: text/html\r\n\r\n" "\r\n" "\r\n" "WWW6to4 Proxy: Non-existent domain\r\n" "\r\n" "\r\n" "

" BANNER "

\r\n" "No such domain: %s\r\n" "\r\n" "\r\n"; char CHEADER[] = "HTTP/1.0 400 Invalid header received from browser\r\n\r\n"; char SHEADER[] = "HTTP/1.0 502 Invalid header received from server\r\n\r\n"; char CSUCCEED[] = "HTTP/1.0 200 Connection established\r\n" "Proxy-Agent: WWW6to4/" "\r\n\r\n"; #define MAXHOST 255 #define MAXPORT 32 #define MAXPATTERNS 255 char hostname[MAXHOST] = "127.0.0.1,::1"; char hostport[MAXPORT] = "8000"; char *patterns[MAXPATTERNS]; int lastpattern = -1; char *default_configfile = "/etc/www6to4.conf"; char *configfile = NULL; char *forwardfile = NULL; int debug = 0; int forwardv4only = 0; int quiet = 1; int tfactor = 1; int potential_add; int logfacility = LOG_DAEMON; struct gateway gw_default = {NULL, NULL}; struct gateway gw_none = {NULL, NULL}; struct file_list *current_forwardfile; int (*loaders[NLOADERS]) (); void chat (struct client_state * csp) { char buf[BUFSIZ], *hdr, *p, *req; fd_set rfds; int i, n, maxfd, server_body; struct gateway *gw; struct http_request *http; struct timeval timeout; int bodylen, bodyrecv; int sent, length; char *bodybuf; http = csp->http; for (;;) { FD_ZERO (&rfds); FD_SET (csp->cfd, &rfds); timeout.tv_sec = 10; timeout.tv_usec = 0; n = select (csp->cfd + 1, &rfds, NULL, NULL, &timeout); if (!n) { syslog (LOG_NOTICE, "Timeout reading from client"); return; } if (n < 0) { syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } n = read (csp->cfd, buf, sizeof (buf)); if (n <= 0) break; add_to_iob (csp, buf, n); req = get_header (csp); if (!req) break; /* no HTTP request! */ if (*req == '\0') continue; /* more to come! */ parse_http_request (req, http, csp); freez (req); break; } if (!http->cmd) { strcpy (buf, CHEADER); write (csp->cfd, buf, strlen (buf)); return; } /* decide how to route the HTTP request */ syslog (LOG_INFO, "Requesting: %s", http->cmd); gw = forward_url (http, csp); potential_add = 0; for (i = lastpattern; i >= 0; i--) { if (strstr(http->path, patterns[i])) { if (debug) syslog (LOG_INFO, "Pattern: *%s*", patterns[i]); potential_add = 1; break; } } /* if forwarding, just pass the request as is */ if (gw->forward_host) enlist (csp->headers, http->cmd); else if (!http->ssl) { /* otherwise elide the host information from the url */ p = NULL; p = strsav (p, http->gpc); p = strsav (p, " "); p = strsav (p, http->path); p = strsav (p, " "); p = strsav (p, http->ver); enlist (csp->headers, p); freez (p); } /* grab the rest of the client's headers */ bodylen = 0; for (;;) { p = get_header (csp); if (p && (*p == '\0')) { n = read (csp->cfd, buf, sizeof (buf)); if (n < 0) { syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } add_to_iob (csp, buf, n); continue; } /* get_header returned NULL, means empty line */ if (!p) break; enlist (csp->headers, p); /* Check for Content-length. If a content-length header is present we have content data that needs to be read as well */ if (!strncasecmp ("Content-length", p, 14)) { /* found */ bodylen = parse_content_length(p); syslog (LOG_INFO, "Content-length: %d", bodylen); } /* Finally, free up p */ freez (p); } /* add the blank line at the end of the header */ enlist (csp->headers, ""); /* make hdr out of list */ hdr = list_to_text (csp->headers); destroy_list (csp->headers); length = strlen(hdr); /* Do we need to read the body as well? */ if (bodylen > 0) { /* Yepp! */ bodybuf = calloc(bodylen + length, 1); memcpy(bodybuf, hdr, length); /* Set hdr to be the new buffer */ freez (hdr); hdr = bodybuf; bodybuf = hdr+length; bodyrecv = csp->iob->eod - csp->iob->cur; memcpy(bodybuf, csp->iob->cur, bodyrecv); while (bodyrecv < bodylen) { n = read (csp->cfd, bodybuf+bodyrecv, bodylen-bodyrecv); if (n < 0) { syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } bodyrecv += n; } length += bodylen; } /* here we connect to the server, gateway, or the forwarder */ csp->sfd = direct_connect (gw, http, csp); if (csp->sfd < 0) { if (errno == EINVAL) syslog (LOG_NOTICE, "No Such Domain."); else syslog (LOG_ERR, "%s", safe_strerror (errno)); freez (hdr); return; } if (gw->forward_host || !http->ssl) { /* * write the client's (modified) header to the server (along * with anything else that may be in the buffer) */ sent = 0; while (sent < length) { n = write (csp->sfd, hdr+sent, length-sent); if (n < 0) { syslog (LOG_NOTICE, "Failed to send header data to server"); freez(hdr); return; } sent += n; } } else { /* * we're running an SSL tunnel and we're not forwarding, so * just send the "connect succeeded" message to the client, * flush the rest, and get out of the way. */ if (write (csp->cfd, CSUCCEED, sizeof (CSUCCEED) - 1) < 0) { freez (hdr); return; } } freez (csp->iob->buf); memset(csp->iob, '\0', sizeof(csp->iob)); /* we're finished with the client's header */ freez (hdr); maxfd = (csp->cfd > csp->sfd) ? csp->cfd : csp->sfd; /* * Pass data between the client and server until one or the other * shuts down the connection. * * FWD: This is quite a violation of the HTTP-1.1 * standard; it does not properly implement presistent connections. * This is no problem however when using netscape as client, but e.g. links * causes troubles (because it does the correct thing ;). Doing the right * thing is looking for multiple client requests on a single connection * that may be to multiple servers. The quick, dirty and easy but wrong way * implemented now is to shutdown the connection for reads after the first * request has been read. Brrr... */ server_body = 0; for (;;) { FD_ZERO (&rfds); FD_SET (csp->cfd, &rfds); FD_SET (csp->sfd, &rfds); timeout.tv_usec = 0; if (potential_add) timeout.tv_sec = 5*tfactor; else timeout.tv_sec = 60*tfactor; n = select (maxfd + 1, &rfds, NULL, NULL, &timeout); if (!n) { syslog (LOG_NOTICE, "Timed out after %d seconds", timeout.tv_sec); return; } if (n < 0) { syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } /* * this is the body of the browser's request just read it and * write it. */ if (FD_ISSET (csp->cfd, &rfds)) { n = read (csp->cfd, buf, sizeof (buf)); if (n <= 0) break; if (write (csp->sfd, buf, n) != n) { syslog (LOG_NOTICE, "Write to: %s failed", http->host); return; } /* Arf, depending on your browser you may need this */ if (0 && !http->ssl) shutdown (csp->cfd, SHUT_RD); continue; } /* * the server wants to talk. it could be the header or the * body. if `hdr' is null, then it's the header otherwise * it's the body */ if (FD_ISSET (csp->sfd, &rfds)) { n = read (csp->sfd, buf, sizeof (buf)); if (n < 0) { syslog (LOG_NOTICE, "Read from: %s failed: ", http->host); syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } if (!n) break; /* "game over, man" */ /* * if this is an SSL connection or we're in the body * of the server document, just write it to the * client. */ if (server_body || http->ssl) { /* just write */ if (write (csp->cfd, buf, n) != n) { syslog (LOG_NOTICE, "Write to client failed"); syslog (LOG_ERR, "%s", safe_strerror (errno)); return; } continue; } else { /* * we're still looking for the end of the * server's header ... */ add_to_iob (csp, buf, n); while ((p = get_header (csp))) { /* see following note */ if (*p == '\0') break; enlist (csp->headers, p); freez (p); } /* * NOTE: there are no "empty" headers so if * the pointer `p' is not NULL we must assume * that we reached the end of the buffer * before we hit the end of the header. * * Since we have to wait for more from the * server before we can parse the headers we * just continue here. */ if (p) continue; /* * add the blank line at the end of the * header */ enlist (csp->headers, ""); hdr = list_to_text (csp->headers); n = strlen (hdr); /* * write the server's (modified) header to * the client (along with anything else that * may be in the buffer) */ if ((n && write (csp->cfd, hdr, n) != n) || (flush_socket (csp->cfd, csp) < 0)) { /* * the write failed, so don't bother * mentioning it to the client... it * probably can't hear us anyway. */ freez (hdr); return; } /* we're finished with the server's header */ freez (hdr); server_body = 1; } continue; } return; /* huh? we should never get here */ } } void serve (struct client_state *csp) { chat (csp); shutdown (csp->cfd, SHUT_RDWR); close (csp->cfd); if (csp->sfd >= 0) close (csp->sfd); } int main (int argc, char *argv[]) { char buf[BUFSIZ]; int bfd[MAXADDRESSES + 1]; char *addr; int i; configfile = default_configfile; for (i = 1; i < argc; i++) { if (!strcmp (argv[i], "-d") ) debug = 1; else if (!strcmp (argv[i], "-forwardv4only")) forwardv4only = 1; else if (!strcmp (argv[i], "-c")) configfile = strdup(argv[++i]); else if (!strcmp (argv[i], "-l")) { int j; i++; j = 0; while (facilitynames[j].c_name) { if (!strcmp(argv[i], facilitynames[j].c_name)) logfacility = facilitynames[j].c_val; j++; } } else if (!strcmp (argv[i], "-q")) quiet = 1; else if (!strcmp (argv[i], "-v")) quiet = -1; else if (!strcmp (argv[i], "-h")) { fprintf (stdout, "www6to4 -q -v -h -d -c -l \n"); fprintf (stdout, "-forwardv4only: never forward requests for IPv6 hosts\n"); fprintf (stdout, "-q: quiet (less logging)\n"); fprintf (stdout, "-v: verbose (more logging)\n"); fprintf (stdout, "-d: debug mode (do not fork)\n"); } } if (debug) openlog("www6to4", LOG_PID | LOG_NDELAY | LOG_CONS | LOG_PERROR, logfacility); else openlog("www6to4", LOG_PID | LOG_NDELAY | LOG_CONS, logfacility); syslog (LOG_NOTICE, "%s", VERSION); if (quiet > 0) setlogmask (LOG_UPTO (LOG_ERR)); else if (!quiet) setlogmask (LOG_UPTO (LOG_NOTICE)); else if (quiet < 0) setlogmask (LOG_UPTO (LOG_INFO)); if (configfile) { FILE *configfp = fopen (configfile, "r"); if (!configfp) { syslog (LOG_ERR, "can't open configuration file '%s': ", configfile); syslog (LOG_ERR, "%s", safe_strerror (errno)); exit (1); } else { int line_num = 0; while (fgets (buf, sizeof (buf), configfp)) { char cmd[BUFSIZ], arg[BUFSIZ], tmp[BUFSIZ]; char *p, *q; line_num++; strcpy (tmp, buf); p = strpbrk (tmp, "#\r\n"); if (p) *p = '\0'; p = tmp; /* leading skip whitespace */ while (*p && ((*p == ' ') || (*p == '\t'))) p++; q = cmd; while (*p && (*p != ' ') && (*p != '\t')) *q++ = *p++; *q = '\0'; while (*p && ((*p == ' ') || (*p == '\t'))) p++; strcpy (arg, p); p = arg + strlen (arg) - 1; /* ignore trailing whitespace */ while (*p && ((*p == ' ') || (*p == '\t'))) *p-- = '\0'; if (*cmd == '\0') continue; /* insure the command field is lower case */ for (p = cmd; *p; p++) if (isupper (*p)) *p = tolower (*p); if (!strcmp (cmd, "listen-to")) { strncpy (hostname, arg, MAXHOST); continue; } if (!strcmp (cmd, "pattern")) { if (++lastpattern >= MAXPATTERNS) syslog (LOG_ERR, "Too many patterns, more than %d.", MAXPATTERNS); else patterns[lastpattern] = strdup(arg); continue; } if (!strcmp (cmd, "listen-port")) { strncpy (hostport, arg, MAXPORT); continue; } if (!strcmp (cmd, "forwardfile")) { forwardfile = strdup (arg); continue; } if (!strcmp (cmd, "timeout-factor")) { tfactor = atoi (arg); if (tfactor < 0 || tfactor > 10) { tfactor = 1; syslog (LOG_ERR, "Illegal Timeout factor."); } continue; } syslog (LOG_ERR, "Unrecognized directive in configuration file " "at line number %d", line_num); exit(1); } fclose (configfp); } } if (forwardfile) add_loader (load_forwardfile); addr = strtok (hostname, ","); if (!strcmp (addr, "*")) { bfd[0] = open_server_socket (NULL, hostport, AF_INET); bfd[1] = open_server_socket (NULL, hostport, AF_INET6); bfd[2] = 0; } else { for (i = 0; i < MAXADDRESSES; i++) { bfd[i] = open_server_socket (addr, hostport, AF_UNSPEC); addr = strtok (NULL, ","); if (!addr) break; } bfd[i + 1] = 0; } for (i = 0; (i < MAXADDRESSES) && bfd[i]; i++) { if (bfd[i] < 0) { syslog (LOG_ERR, "Can't bind %s:%s: ", hostname ? hostname : "INADDR_ANY", hostport); syslog (LOG_ERR, "%s", safe_strerror (errno)); syslog (LOG_ERR, "There may be another www6to4 or some other " "proxy running on port %s", hostport); exit (1); } } signal (SIGPIPE, SIG_IGN); signal (SIGCHLD, SIG_IGN); for (;;) { int cfd; struct client_state *csp; #ifdef DMALLOC dmalloc_log_unfreed(); #endif /* cleanup zombie children */ while (waitpid (-1, NULL, WNOHANG) > 0) {} cfd = accept_conn (bfd); if (cfd < 0) continue; csp = (struct client_state *) calloc (1, sizeof (*csp)); if (!csp) { syslog (LOG_ERR, "malloc(%d) for csp failed: ", sizeof (*csp)); syslog (LOG_ERR, "%s", safe_strerror (errno)); close (cfd); sleep (3); continue; } csp->cfd = cfd; csp->sfd = -1; if (run_loader (csp)) { syslog (LOG_ERR, "A loader failed - must exit"); exit (1); } if (!debug) { int child_id; child_id = fork (); if (child_id < 0) { /* failed */ syslog (LOG_ERR, "Can't fork"); syslog (LOG_ERR, "%s", safe_strerror (errno)); sprintf (buf, "%s: can't fork: errno = %d", VERSION, errno); write (csp->cfd, buf, strlen (buf)); close (csp->cfd); sleep (5); continue; } if (!child_id) { /* child */ if (setpgid (0, getpid ()) == -1) { syslog (LOG_ERR, "setpgid() failed with %d", errno); syslog (LOG_ERR, "%s", safe_strerror (errno)); exit(1); } serve (csp); freez (csp); exit (0); } else { /* parent */ close (csp->cfd); freez (csp); } } else { serve (csp); freez (csp); } } /* NOTREACHED */ } char *safe_strerror (int err) { char buf[BUFSIZ]; char *s = NULL; #ifndef NOSTRERROR s = strerror (err); #endif /* NOSTRERROR */ if (!s) { sprintf (buf, "(errno = %d)", err); s = buf; } return s; } int flush_socket (int fd, struct client_state * csp) { struct iob *iob = csp->iob; int n = iob->eod - iob->cur; if (n <= 0) return (0); n = write (fd, iob->cur, n); iob->eod = iob->cur = iob->buf; return (n); }