X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=webcit%2Fwebserver.c;h=07e10504fb792039a601e31c73d89e05d62df5ff;hb=84858f5f0b10e68c772122fbb07b61fc1f010f9c;hp=f3246ff5bd8860223c1d470afdba90ab649b3975;hpb=c70805eb903c215b967d1742c27eb16168422941;p=citadel.git diff --git a/webcit/webserver.c b/webcit/webserver.c index f3246ff5b..07e10504f 100644 --- a/webcit/webserver.c +++ b/webcit/webserver.c @@ -5,18 +5,28 @@ * waiting on the specified port for incoming HTTP connections. When a * connection is established, it calls context_loop() from context_loop.c. * + * $Id$ */ +#include #include +#ifdef HAVE_UNISTD_H #include +#endif #include +#ifdef HAVE_FCNTL_H #include +#endif #include #include #include #include +#ifdef HAVE_SYS_TIME_H #include +#endif +#ifdef HAVE_LIMITS_H #include +#endif #include #include #include @@ -24,10 +34,28 @@ #include #include #include +#include #include "webcit.h" +#include "webserver.h" + +#ifndef HAVE_SNPRINTF +int vsnprintf(char *buf, size_t max, const char *fmt, va_list argp); +#endif -int msock; /* master listening socket */ +int verbosity = 9; /* Logging level */ +int msock; /* master listening socket */ +int is_https = 0; /* Nonzero if I am an HTTPS service */ extern void *context_loop(int); +extern void *housekeeping_loop(void); +extern pthread_mutex_t SessionListMutex; +extern pthread_key_t MyConKey; + + +char *server_cookie = NULL; + + +char *ctdlhost = DEFAULT_HOST; +char *ctdlport = DEFAULT_PORT; /* * This is a generic function to set up a master socket for listening on @@ -37,119 +65,96 @@ int ig_tcp_server(int port_number, int queue_len) { struct sockaddr_in sin; int s, i; - struct linger WebLinger = { 1, 9000 }; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; if (port_number == 0) { - printf("webcit: Cannot start: no port number specified.\n"); + lprintf(1, "Cannot start: no port number specified.\n"); exit(1); - } - - sin.sin_port = htons((u_short)port_number); + } + sin.sin_port = htons((u_short) port_number); s = socket(PF_INET, SOCK_STREAM, (getprotobyname("tcp")->p_proto)); if (s < 0) { - printf("webcit: Can't create a socket: %s\n", - strerror(errno)); + lprintf(1, "Can't create a socket: %s\n", + strerror(errno)); exit(errno); - } - + } /* Set some socket options that make sense. */ i = 1; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); - setsockopt(s, SOL_SOCKET, SO_LINGER, &WebLinger, sizeof(WebLinger)); - if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { - printf("webcit: Can't bind: %s\n", strerror(errno)); + if (bind(s, (struct sockaddr *) &sin, sizeof(sin)) < 0) { + lprintf(1, "Can't bind: %s\n", strerror(errno)); exit(errno); - } - + } if (listen(s, queue_len) < 0) { - printf("webcit: Can't listen: %s\n", strerror(errno)); + lprintf(1, "Can't listen: %s\n", strerror(errno)); exit(errno); - } - - return(s); } - - -/* - * client_write() ... Send binary data to the client. - */ -void client_write(int sock, char *buf, int nbytes) -{ - int bytes_written = 0; - int retval; - while (bytes_written < nbytes) { - retval = write(sock, &buf[bytes_written], - nbytes - bytes_written); - if (retval < 1) { - printf("client_write() failed: %s\n", - strerror(errno)); - pthread_exit(NULL); - } - bytes_written = bytes_written + retval; - } - } - - -/* - * cprintf() ... Send formatted printable data to the client. It is - * implemented in terms of client_write() but remains in - * sysdep.c in case we port to somewhere without va_args... - */ -void cprintf(int sock, const char *format, ...) { - va_list arg_ptr; - char buf[256]; - - va_start(arg_ptr, format); - if (vsnprintf(buf, sizeof buf, format, arg_ptr) == -1) - buf[sizeof buf - 2] = '\n'; - client_write(sock, buf, strlen(buf)); - va_end(arg_ptr); - } + return (s); +} /* * Read data from the client socket. * Return values are: - * 1 Requested number of bytes has been read. - * 0 Request timed out. - * If the socket breaks, the session is immediately terminated. + * 1 Requested number of bytes has been read. + * 0 Request timed out. + * -1 Connection is broken, or other error. */ int client_read_to(int sock, char *buf, int bytes, int timeout) { - int len,rlen; + int len, rlen; fd_set rfds; struct timeval tv; int retval; + +#ifdef HAVE_OPENSSL + if (is_https) { + return(client_read_ssl(buf, bytes, timeout)); + } +#endif + len = 0; - while(lenhttp_sock, buf, count)); +} + /* * Read data from the client socket with default timeout. @@ -158,8 +163,8 @@ int client_read_to(int sock, char *buf, int bytes, int timeout) */ int client_read(int sock, char *buf, int bytes) { - return(client_read_to(sock, buf, bytes, SLEEPING)); - } + return (client_read_to(sock, buf, bytes, SLEEPING)); +} /* @@ -173,11 +178,11 @@ int client_gets(int sock, char *buf) /* Read one character at a time. */ - for (i = 0;;i++) { + for (i = 0;; i++) { retval = client_read(sock, &buf[i], 1); if (retval != 1 || buf[i] == '\n' || i == 255) break; - } + } /* If we got a long line, discard characters until the newline. */ @@ -186,99 +191,264 @@ int client_gets(int sock, char *buf) retval = client_read(sock, &buf[i], 1); /* - * Strip any trailing not-printable characters. + * Strip any trailing non-printable characters. */ buf[i] = 0; - while ((strlen(buf)>0)&&(!isprint(buf[strlen(buf)-1]))) { - buf[strlen(buf)-1] = 0; - } - return(retval); + while ((strlen(buf) > 0) && (!isprint(buf[strlen(buf) - 1]))) { + buf[strlen(buf) - 1] = 0; } + return (retval); +} /* * Start running as a daemon. Only close stdio if do_close_stdio is set. */ -void start_daemon(int do_close_stdio) { +void start_daemon(int do_close_stdio) +{ if (do_close_stdio) { /* close(0); */ close(1); close(2); - } - signal(SIGHUP,SIG_IGN); - signal(SIGINT,SIG_IGN); - signal(SIGQUIT,SIG_IGN); - if (fork()!=0) exit(0); } - -/* - * Issue an HTTP Redirect header - * url must be a complete url (with http://) - */ -void redirect(char *url) { - printf("Location: %s\n\n", url); + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + if (fork() != 0) + exit(0); } +void spawn_another_worker_thread() { + pthread_t SessThread; /* Thread descriptor */ + pthread_attr_t attr; /* Thread attributes */ + + lprintf(3, "Creating a new thread\n"); + + /* set attributes for the new thread */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + /* now create the thread */ + if (pthread_create(&SessThread, &attr, + (void *(*)(void *)) worker_entry, NULL) + != 0) { + lprintf(1, "Can't create thread: %s\n", + strerror(errno)); + } +} /* * Here's where it all begins. */ int main(int argc, char **argv) { - struct sockaddr_in fsin; /* Data for master socket */ - int alen; /* Data for master socket */ - int ssock; /* Descriptor for master socket */ - pthread_t SessThread; /* Thread descriptor */ - pthread_attr_t attr; /* Thread attributes */ - int a, i; /* General-purpose variables */ - char convbuf[128]; - + pthread_t SessThread; /* Thread descriptor */ + pthread_attr_t attr; /* Thread attributes */ + int a, i; /* General-purpose variables */ + int port = PORT_NUM; /* Port to listen on */ + char tracefile[PATH_MAX]; + + /* Parse command line */ +#ifdef HAVE_OPENSSL + while ((a = getopt(argc, argv, "hp:t:cs")) != EOF) +#else + while ((a = getopt(argc, argv, "hp:t:c")) != EOF) +#endif + switch (a) { + case 'p': + port = atoi(optarg); + break; + case 't': + strcpy(tracefile, optarg); + freopen(tracefile, "w", stdout); + freopen(tracefile, "w", stderr); + freopen(tracefile, "r", stdin); + break; + case 'x': + verbosity = atoi(optarg); + break; + case 'c': + server_cookie = malloc(SIZ); + if (server_cookie != NULL) { + strcpy(server_cookie, "Set-cookie: wcserver="); + if (gethostname( + &server_cookie[strlen(server_cookie)], + 200) != 0) { + lprintf(2, "gethostname: %s\n", + strerror(errno)); + free(server_cookie); + } + } + break; + case 's': + is_https = 1; + break; + default: + fprintf(stderr, "usage: webserver [-p localport] " + "[-t tracefile] [-c] " +#ifdef HAVE_OPENSSL + "[-s] " +#endif + "[remotehost [remoteport]]\n"); + return 1; + } + + if (optind < argc) { + ctdlhost = argv[optind]; + if (++optind < argc) + ctdlport = argv[optind]; + } /* Tell 'em who's in da house */ - printf("WebCit v2 experimental\n"); - printf("Copyright (C) 1996-1998 by Art Cancro. "); - printf("All rights reserved.\n\n"); + lprintf(1, SERVER "\n" +"Copyright (C) 1996-2004 by the Citadel/UX development team.\n" +"This software is distributed under the terms of the GNU General Public\n" +"License. If you paid for this software, someone is ripping you off.\n\n"); + + if (chdir(WEBCITDIR) != 0) + perror("chdir"); + + /* + * Set up a place to put thread-specific data. + * We only need a single pointer per thread - it points to the + * wcsession struct to which the thread is currently bound. + */ + if (pthread_key_create(&MyConKey, NULL) != 0) { + lprintf(1, "Can't create TSD key: %s\n", strerror(errno)); + } /* - * Bind the server to our favourite port. + * Set up a place to put thread-specific SSL data. + * We don't stick this in the wcsession struct because SSL starts + * up before the session is bound, and it gets torn down between + * transactions. + */ +#ifdef HAVE_OPENSSL + if (pthread_key_create(&ThreadSSL, NULL) != 0) { + lprintf(1, "Can't create TSD key: %s\n", strerror(errno)); + } +#endif + + /* + * Bind the server to our favorite port. * There is no need to check for errors, because ig_tcp_server() * exits if it doesn't succeed. */ - printf("Attempting to bind to port %d...\n", PORT_NUM); - msock = ig_tcp_server(PORT_NUM, 5); - printf("Listening on socket %d\n", msock); - - /* - * Endless loop. Listen on the master socket. When a connection - * comes in, create a socket, a context, and a thread. - */ - while (1) { - ssock = accept(msock, (struct sockaddr *)&fsin, &alen); - printf("New connection on socket %d\n", ssock); + lprintf(2, "Attempting to bind to port %d...\n", port); + msock = ig_tcp_server(port, LISTEN_QUEUE_LENGTH); + lprintf(2, "Listening on socket %d\n", msock); + signal(SIGPIPE, SIG_IGN); + + pthread_mutex_init(&SessionListMutex, NULL); + + /* + * Start up the housekeeping thread + */ + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + pthread_create(&SessThread, &attr, + (void *(*)(void *)) housekeeping_loop, NULL); + + + /* + * If this is an HTTPS server, fire up SSL + */ +#ifdef HAVE_OPENSSL + if (is_https) { + init_ssl(); + } +#endif + + /* Start a few initial worker threads */ + for (i=0; i<(MIN_WORKER_THREADS); ++i) { + spawn_another_worker_thread(); + } + + /* now the original thread becomes another worker */ + worker_entry(); + return 0; +} + + +/* + * Entry point for worker threads + */ +void worker_entry(void) { + int ssock; + int i = 0; + int time_to_die = 0; + int fail_this_transaction = 0; + + do { + /* Only one thread can accept at a time */ + fail_this_transaction = 0; + ssock = accept(msock, NULL, 0); if (ssock < 0) { - printf("webcit: accept() failed: %s\n", - strerror(errno)); - } - else { + lprintf(2, "accept() failed: %s\n", strerror(errno)); + } else { /* Set the SO_REUSEADDR socket option */ i = 1; setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, - &i, sizeof(i)); - - /* set attributes for the new thread */ - pthread_attr_init(&attr); - pthread_attr_setdetachstate(&attr, - PTHREAD_CREATE_DETACHED); - - /* now create the thread */ - if (pthread_create(&SessThread, &attr, - (void* (*)(void*)) context_loop, - ssock) - != 0) { - printf("webcit: can't create thread: %s\n", - strerror(errno)); + &i, sizeof(i)); + + /* If we are an HTTPS server, go crypto now. */ +#ifdef HAVE_OPENSSL + if (is_https) { + if (starttls(ssock) != 0) { + fail_this_transaction = 1; + close(ssock); } + } +#endif + if (fail_this_transaction == 0) { + /* Perform an HTTP transaction... */ + context_loop(ssock); + /* ...and close the socket. */ + lingering_close(ssock); } + } - } + } while (!time_to_die); + + pthread_exit(NULL); +} + + +int lprintf(int loglevel, const char *format, ...) +{ + va_list ap; + char buf[4096]; + + va_start(ap, format); + vsprintf(buf, format, ap); + va_end(ap); + + if (loglevel <= verbosity) { + struct timeval tv; + struct tm *tim; + + gettimeofday(&tv, NULL); + tim = localtime((time_t *)&(tv.tv_sec)); + + if (WC && WC->wc_session) { + fprintf(stderr, + "%04d/%02d/%02d %2d:%02d:%02d.%03ld [%ld:%d] %s", + tim->tm_year + 1900, tim->tm_mon + 1, + tim->tm_mday, tim->tm_hour, tim->tm_min, + tim->tm_sec, (long)tv.tv_usec / 1000, + (long)pthread_self(), + WC->wc_session, buf); + } else { + fprintf(stderr, + "%04d/%02d/%02d %2d:%02d:%02d.%03ld [%ld] %s", + tim->tm_year + 1900, tim->tm_mon + 1, + tim->tm_mday, tim->tm_hour, tim->tm_min, + tim->tm_sec, (long)tv.tv_usec / 1000, + (long)pthread_self(), + buf); + } + fflush(stderr); + } + return 1; +}