
/* ====================================================================
 * Copyright (c) 1995 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * IT'S CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */


/*
 * httpd.c: simple http daemon for answering WWW file requests
 *
 * 
 * 03-21-93  Rob McCool wrote original code (up to NCSA HTTPd 1.3)
 * 
 * 03-06-95  blong
 *  changed server number for child-alone processes to 0 and changed name
 *   of processes
 *
 * 03-10-95  blong
 * 	Added numerous speed hacks proposed by Robert S. Thau (rst@ai.mit.edu) 
 *	including set group before fork, and call gettime before to fork
 * 	to set up libraries.
 *
 * 04-14-95  rst / rh
 *      Brandon's code snarfed from NCSA 1.4, but tinkered to work with the
 *      Apache server, and also to have child processes do accept() directly.
 *
 * April-July '95 rst
 *      Extensive rework for Shambhala.
 */


#define CORE_PRIVATE

#include "httpd.h"
#include "http_main.h"
#include "http_log.h"
#include "http_config.h"	/* for read_config */
#include "http_protocol.h"	/* for read_request */
#include "http_request.h"	/* for process_request */
#include "http_conf_globals.h"
#include "scoreboard.h"
#include <setjmp.h>

/*
 * Actual definitions of config globals... here because this is
 * for the most part the only code that acts on 'em.  (Hmmm... mod_main.c?)
 */

int standalone;
uid_t user_id;
char *user_name;
gid_t group_id;
int max_requests_per_child;
char *pid_fname;
char *server_argv0;
struct in_addr bind_address;
int daemons_to_start;
int daemons_min_free;
int daemons_max_free;
int daemons_limit;

char server_root[MAX_STRING_LEN];
char server_confname[MAX_STRING_LEN];

/* *Non*-shared http_main globals... */

server_rec *server_conf;
JMP_BUF jmpbuffer;
JMP_BUF restart_buffer;
int sd;
pid_t pgrp;

/* one_process --- debugging mode variable; can be set from the command line
 * with the -X flag.  If set, this gets you the child_main loop running
 * in the process which originally started up (no detach, no make_child),
 * which is a pretty nice debugging environment.  (You'll get a SIGHUP
 * early in standalone_main; just continue through.  This is the server
 * trying to kill off any child processes which it might have lying
 * around --- Shambhala doesn't keep track of their pids, it just sends
 * SIGHUP to the process group, ignoring it in the root process.
 * Continue through and you'll be fine.).
 */

int one_process = 0;

#ifdef FCNTL_SERIALIZED_ACCEPT
static struct flock lock_it = { F_WRLCK, 0, 0, 0 };
static struct flock unlock_it = { F_UNLCK, 0, 0, 0 };

static int lock_fd=-1;

/*
 * Initialise mutex lock.
 * Must be safe to call this on a restart.
 */
void
accept_mutex_init(pool *p)
{
    char lock_fname[30];

    strcpy(lock_fname, "/usr/tmp/htlock.XXXXXX");
    
    if (mktemp(lock_fname) == NULL || lock_fname[0] == '\0')
    {
	fprintf (stderr, "Cannot assign name to lock file!\n");
	exit (1);
    }

    lock_fd = popenf(p, lock_fname, O_CREAT | O_WRONLY, 0644);
    if (lock_fd == -1)
    {
	perror ("open");
	fprintf (stderr, "Cannot open lcok file\n");
	exit (1);
    }
    unlink(lock_fname);
}

void accept_mutex_on()
{
    int ret;
    
    while ((ret = fcntl(lock_fd, F_SETLKW, &lock_it)) < 0 && errno == EINTR)
	continue;

    if (ret < 0) {
	log_error ("Unknown failure grabbing accept lock.  Exiting!",
		   server_conf);
	exit(1);
    }
}

void accept_mutex_off()
{
    if (fcntl (lock_fd, F_SETLKW, &unlock_it) < 0)
    {
	log_error("Error freeing accept lock.  Exiting!", server_conf);
	exit(1);
    }
}
#else
/* Default --- no serialization.  Other methods *could* go here,
 * as #elifs...
 */
#define accept_mutex_init(x)
#define accept_mutex_on()
#define accept_mutex_off()
#endif

void usage(char *bin)
{
    fprintf(stderr,"Usage: %s [-d directory] [-f file] [-v]\n",bin);
    fprintf(stderr,"-d directory : specify an alternate initial ServerRoot\n");
    fprintf(stderr,"-f file : specify an alternate ServerConfigFile\n");
    exit(1);
}

/*****************************************************************
 *
 * Timeout handling.  DISTINCTLY not thread-safe, but all this stuff
 * has to change for threads anyway.  Note that this code allows only
 * one timeout in progress at a time...
 */

static conn_rec *current_conn;
static request_rec *timeout_req;
static char *timeout_name;
static int alarms_blocked = 0;
static int alarm_pending = 0;

void abort_connection (conn_rec *);

void timeout(sig)			/* Also called on SIGPIPE */
int sig;
{
    char errstr[MAX_STRING_LEN];

    if (alarms_blocked) {
	alarm_pending = 1;
	return;
    }
    
    if (!current_conn) {
#ifdef NEXT
	longjmp(jmpbuffer,1);
#else
	siglongjmp(jmpbuffer,1);
#endif
    }
    
    sprintf(errstr,"%s timed out for %s",
	    timeout_name ? timeout_name : "request",
	    current_conn->remote_name);
    
    log_error(errstr, current_conn->server);
      
    if (timeout_req) {
	/* Someone has asked for this transaction to just be aborted
	 * if it times out...
	 */
	
	request_rec *log_req = timeout_req;
	
	while (log_req->main || log_req->prev) {
	    /* Get back to original request... */
	    if (log_req->main) log_req = log_req->main;
	    else log_req = log_req->prev;
	}
	
	log_transaction(log_req);

	os_conn_close (timeout_req->connection);

	if (!standalone) exit(0);
#ifdef NEXT
	longjmp(jmpbuffer,1);
#else
	siglongjmp(jmpbuffer,1);
#endif
    }
    else {
	abort_connection (current_conn);
    }
}

/*
 * These two called from alloc.c to protect its critical sections...
 * Note that they can nest (as when destroying the sub_pools of a pool
 * which is itself being cleared); we have to support that here.
 */

void block_alarms() {
    ++alarms_blocked;
}

void unblock_alarms() {
    --alarms_blocked;
    if (alarms_blocked == 0 && alarm_pending) {
	alarm_pending = 0;
	timeout(0);
    }
}

void hard_timeout (char *name, request_rec *r)
{
    timeout_req = r;
    timeout_name = name;
    
    signal(SIGALRM,(void (*)())timeout);
    alarm (r->server->timeout);
}

void soft_timeout (char *name, request_rec *r)
{
    timeout_name = name;
    
    signal(SIGALRM,(void (*)())timeout);
    alarm (r->server->timeout);
}

void kill_timeout (request_rec *dummy) {
    alarm (0);
    timeout_req = NULL;
    timeout_name = NULL;
}
 
/*****************************************************************
 *
 * Dealing with the scoreboard... a lot of these variables are global
 * only to avoid getting clobbered by the longjmp() that happens when
 * a hard timeout expires...
 *
 * We begin with routines which deal with the file itself... 
 */

#ifdef HAVE_MMAP
static short_score *scoreboard_image=NULL;
#else
static short_score scoreboard_image[HARD_SERVER_MAX];
static char scoreboard_fname[] = "/tmp/htstatus.XXXXXX";
static int have_scoreboard_fname = 0;
static int scoreboard_fd;

static int force_write (int fd, char *buffer, int bufsz)
{
    int rv, orig_sz = bufsz;
    
    do {
	rv = write (fd, buffer, bufsz);
	if (rv > 0) {
	    buffer += rv;
	    bufsz -= rv;
	}
    } while (rv > 0 && bufsz > 0);

    return rv < 0? rv : orig_sz - bufsz;
}

static int force_read (int fd, char *buffer, int bufsz)
{
    int rv, orig_sz = bufsz;
    
    do {
	rv = read (fd, buffer, bufsz);
	if (rv > 0) {
	    buffer += rv;
	    bufsz -= rv;
	}
    } while (rv > 0 && bufsz > 0);
    
    return rv < 0? rv : orig_sz - bufsz;
}
#endif

/* Called by parent process */
void reinit_scoreboard (pool *p)
{
#ifdef HAVE_MMAP
    if (scoreboard_image == NULL)
    {
	caddr_t m;
#if defined(MAP_ANON) || defined(MAP_FILE)
/* BSD style */
	m = mmap((caddr_t)0, HARD_SERVER_MAX*sizeof(short_score),
		 PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0);
	if (m == (caddr_t)-1)
	{
	    perror("mmap");
	    fprintf(stderr, "httpd: Could not mmap memory\n");
	    exit(1);
	}
#else
/* Sun style */
	int fd;

	fd = open("/dev/zero", O_RDWR);
	if (fd == -1)
	{
	    perror("open");
	    fprintf(stderr, "httpd: Could not open /dev/zero\n");
	    exit(1);
	}
	m = mmap((caddr_t)0, HARD_SERVER_MAX*sizeof(short_score),
		 PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (m == (caddr_t)-1)
	{
	    perror("mmap");
	    fprintf(stderr, "httpd: Could not mmap /dev/zero\n");
	    exit(1);
	}
	close(fd);
#endif
	scoreboard_image = (short_score *)m;
    }
    memset(scoreboard_image, 0, HARD_SERVER_MAX*sizeof(short_score));
#else
    if (!have_scoreboard_fname && (mktemp(scoreboard_fname) == NULL ||
				   scoreboard_fname[0] == '\0')) {
	fprintf (stderr, "Cannot assign name to scoreboard file!\n");
	exit (1);
    }
    
    have_scoreboard_fname = 1;
    
    scoreboard_fd = popenf(p, scoreboard_fname, O_CREAT|O_RDWR, 0644);
    if (scoreboard_fd == -1)
    {
	fprintf (stderr, "Cannot open scoreboard file:\n");
	perror (scoreboard_fname);
	exit (1);
    }

    memset ((char*)scoreboard_image, 0, sizeof(scoreboard_image));
    force_write (scoreboard_fd, (char*)scoreboard_image,
		 sizeof(scoreboard_image));
#endif
}

/* called by child */
void reopen_scoreboard (pool *p)
{
#ifndef HAVE_MMAP
    if (scoreboard_fd != -1) pclosef (p, scoreboard_fd);
    
    scoreboard_fd = popenf(p, scoreboard_fname, O_CREAT|O_RDWR, 0666);
    if (scoreboard_fd == -1)
    {
	fprintf (stderr, "Cannot open scoreboard file:\n");
	perror (scoreboard_fname);
	exit (1);
    }
#endif
}

void cleanup_scoreboard ()
{
#ifndef HAVE_MMAP
    unlink (scoreboard_fname);
#endif
}

/* Routines called to deal with the scoreboard image
 * --- note that we do *not* need write locks, since update_child_status
 * only updates a *single* record in place, and only one process writes to
 * a given scoreboard slot at a time (either the child process owning that
 * slot, or the parent, noting that the child has died).
 *
 * As a final note --- setting the score entry to getpid() is always safe,
 * since when the parent is writing an entry, it's only noting SERVER_DEAD
 * anyway.
 */

void sync_scoreboard_image ()
{
#ifndef HAVE_MMAP
    lseek (scoreboard_fd, 0L, 0);
    force_read (scoreboard_fd, (char*)scoreboard_image,
		sizeof(scoreboard_image));
#endif
}

void update_child_status (int child_num, int status)
{
    short_score new_score_rec;
    new_score_rec.pid = getpid();
    new_score_rec.status = status;

#ifdef HAVE_MMAP
    memcpy(&scoreboard_image[child_num], &new_score_rec, sizeof(short_score));
#else
    lseek (scoreboard_fd, (long)child_num * sizeof(short_score), 0);
    force_write (scoreboard_fd, (char*)&new_score_rec, sizeof(short_score));
#endif
}

int count_idle_servers ()
{
    int i;
    int res = 0;

    for (i = 0; i < HARD_SERVER_MAX; ++i)
	if (scoreboard_image[i].status == SERVER_READY
	  || scoreboard_image[i].status == SERVER_STARTING)
	    ++res;

    return res;
}

int find_free_child_num ()
{
    int i;

    for (i = 0; i < HARD_SERVER_MAX; ++i)
	if (scoreboard_image[i].status == SERVER_DEAD)
	    return i;

    return -1;
}

int find_child_by_pid (int pid)
{
    int i;

    for (i = 0; i < HARD_SERVER_MAX; ++i)
	if (scoreboard_image[i].pid == pid)
	    return i;

    return -1;
}

void reclaim_child_processes ()
{
    int i, status;
    int my_pid = getpid();

    sync_scoreboard_image();
    for (i = 0; i < HARD_SERVER_MAX; ++i) {
	int pid = scoreboard_image[i].pid;

	if (pid != my_pid && pid != 0)
	    waitpid (scoreboard_image[i].pid, &status, 0);
    }
}

/* Finally, this routine is used by the caretaker process to wait for
 * a while...
 */

static jmp_buf wait_timeout_buf;
static int wait_or_timeout_retval = -1;

static void longjmp_out_of_alarm (int sig) {
    longjmp (wait_timeout_buf, 1);
}

int wait_or_timeout (int *status)
{
    wait_or_timeout_retval = -1;
    
    if (setjmp(wait_timeout_buf) != 0) {
	errno = ETIMEDOUT;
	alarm(0);
	return wait_or_timeout_retval;
    }
    
    signal (SIGALRM, longjmp_out_of_alarm);
    alarm(1);
#if defined(NEXT)
    wait_or_timeout_retval = wait((union wait *)status);
#else
    wait_or_timeout_retval = wait(status);
#endif
    alarm(0);
    return wait_or_timeout_retval;
}


/*****************************************************************
 * Here follows a long bunch of generic server bookkeeping stuff...
 */

void detach()
{
    int x;

    chdir("/");
    if((x = fork()) > 0)
        exit(0);
    else if(x == -1) {
        fprintf(stderr,"httpd: unable to fork new process\n");
        perror("fork");
        exit(1);
    }
#ifndef NO_SETSID
    if((pgrp=setsid()) == -1) {
        fprintf(stderr,"httpd: setsid failed\n");
        perror("setsid");
        exit(1);
    }
#else
#if defined(NEXT)
    if(setpgrp(0,getpid()) == -1 || (pgrp = getpgrp(0)) == -1) {
        fprintf(stderr,"httpd: setpgrp or getpgrp failed\n");
        perror("setpgrp");
        exit(1);
    }
#else
    if((pgrp=setpgrp(getpid(),0)) == -1) {
        fprintf(stderr,"httpd: setpgrp failed\n");
        perror("setpgrp");
        exit(1);
    }
#endif    
#endif
}

void sig_term() {
    log_error("httpd: caught SIGTERM, shutting down", server_conf);
    cleanup_scoreboard();
#ifndef NO_KILLPG
    killpg(pgrp,SIGKILL);
#else
    kill(-pgrp,SIGKILL);
#endif
    shutdown(sd,2);
    close(sd);
    exit(1);
}

void bus_error() {
    log_error("httpd: caught SIGBUS, dumping core", server_conf);
    chdir(server_root);
    abort();         
    exit(1);
}

void seg_fault() {
    log_error("httpd: caught SIGSEGV, dumping core", server_conf);
    chdir(server_root);
    abort();
    exit(1);
}

void just_die()			/* SIGHUP to child process??? */
{
    exit (0);
}

/* Reset group privileges, after rereading the config files
 * (our uid may have changed, and if so, we want the new perms).
 *
 * Don't reset the uid yet --- we do that only in the child process,
 * so as not to lose any root privs.  But we can set the group stuff
 * now, once, as opposed to once per each new child.
 *
 * Note that we use the username as set in the config files, rather than
 * the lookup of to uid --- the same uid may have multiple passwd entries,
 * with different sets of groups for each.
 */
  
static void set_group_privs()
{
  if(!geteuid()) {
    char *name;
  
    /* Get username if passed as a uid */
    
    if (user_name[0] == '#') {
      struct passwd* ent;
      uid_t uid=atoi(&user_name[1]);

      if ((ent = getpwuid(uid)) == NULL) {
	 log_error("couldn't determine user name from uid", server_conf);
	 exit(1);
      }
      
      name = ent->pw_name;
    } else name = user_name;

    /* Reset `groups' attributes. */
    
    if (initgroups(name, group_id) == -1) {
        log_error ("unable to set groups", server_conf);
	exit (1);
    }

    if (setgid(group_id) == -1) {
        log_error ("unable to set group id", server_conf);
	exit (1);
    }
  }
}

void restart() {
    signal (SIGALRM, SIG_IGN);
    alarm (0);
#ifdef NEXT
    longjmp(restart_buffer,1);
#else
    siglongjmp(restart_buffer,1);
#endif
}

void set_signals() {
	if(!one_process)
	{
	  signal(SIGSEGV,(void (*)())seg_fault);
	  signal(SIGBUS,(void (*)())bus_error);
	}
    signal(SIGTERM,(void (*)())sig_term);
    signal(SIGHUP,(void (*)())restart);
}

/*****************************************************************
 * Connection structures and accounting...
 * Should these be global?  Only to this file, at least...
 */

pool *pconf;			/* Pool for config stuff */
pool *ptrans;			/* Pool for per-transaction stuff */

server_rec *find_virtual_server (struct in_addr server_ip, server_rec *server)
{
    server_rec *virt;

    for (virt = server->next; virt; virt = virt->next)
	if (virt->host_addr.s_addr == server_ip.s_addr)
	    return virt;

    return server;
}

void default_server_hostnames(server_rec *s)
{
    /* Main host first */
    
    if (!s->server_hostname)
	s->server_hostname = get_local_host(pconf);

    /* Then virtual hosts */
    
    for (s = s->next; s; s = s->next)
	if (!s->server_hostname) {
	    struct hostent *h = gethostbyaddr ((char *)&(s->host_addr),
					       sizeof (struct in_addr),
					       AF_INET);
	    if (h != NULL) {
		s->server_hostname = pstrdup (pconf, (char *)h->h_name);
	    }
	}
}
	
void abort_connection (conn_rec *c)
{
    /* Make sure further I/O DOES NOT HAPPEN */
    os_conn_abort(c);
    signal (SIGPIPE, SIG_IGN);	/* Ignore further complaints */
    c->aborted = 1;
}

conn_rec *new_connection (pool *p, server_rec *server, os_conn_rec *os_conn,
			  const struct sockaddr_in *saddr)
{
    conn_rec *conn = (conn_rec *)pcalloc (p, sizeof(conn_rec));
    
    /* Get a connection structure, and initialize what fields we can
     * (the rest are zeroed out by pcalloc).
     */
    
    conn = (conn_rec *)pcalloc(p, sizeof(conn_rec));
    
    conn->pool = p;
    conn->server = find_virtual_server (saddr->sin_addr, server);
    memcpy(&conn->client,os_conn, sizeof *os_conn);
    
    get_remote_host(conn);
    
    return conn;
}

/*****************************************************************
 * Child process main loop.
 * The following vars are static to avoid getting clobbered by longjmp();
 * they are really private to child_main.
 */

static int csd;
static int dupped_csd;
static int requests_this_child;
static int child_num;

void child_main(int child_num_arg)
{
    int clen;
    struct sockaddr sa_server;
    struct sockaddr sa_client;

#ifdef ULTRIX_BRAIN_DEATH
    extern char *rfc931();
#else
    extern char *rfc931 (struct sockaddr_in *, struct sockaddr_in *);
#endif

    csd = -1;
    dupped_csd = -1;
    child_num = child_num_arg;
    requests_this_child = 0;
    reopen_scoreboard (pconf);
    update_child_status (child_num, SERVER_READY);

    /* Only try to switch if we're running as root */
    if(!geteuid() && setuid(user_id) == -1) {
        log_error ("unable to change uid", server_conf);
	exit (1);
    }

#ifdef NEXT
    setjmp(jmpbuffer);
#else
    sigsetjmp(jmpbuffer,1);
#endif
    signal(SIGURG, timeout);

    while (1) {
        os_conn_rec os_conn;
	request_rec *r;
      
        alarm(0);		/* Cancel any outstanding alarms. */
        timeout_req = NULL;	/* No request in progress */
	current_conn = NULL;
        signal(SIGPIPE, timeout);  
    
	clear_pool (ptrans);
	
	sync_scoreboard_image();
	
	if ((count_idle_servers() >= daemons_max_free)
	    || (max_requests_per_child > 0
	        && ++requests_this_child >= max_requests_per_child))
	{
	    exit(0);
	}

	clen=sizeof(sa_client);
	update_child_status (child_num, SERVER_READY);

	accept_mutex_on();  /* Lock around "accept", if necessary */

	while ((csd=accept(sd, &sa_client, &clen)) == -1) 
           if (errno != EINTR) 
		log_error("socket error: accept failed", server_conf);

	accept_mutex_off(); /* unlock after "accept" */

	clen = sizeof(sa_server);
	if(getsockname(csd, &sa_server, &clen) < 0) {
	    log_error("getsockname: failed", server_conf);
	    continue;
	}

#ifdef APACHE_SSL
	os_conn.ssl = (SSL *)
	  ApacheSSLNew(find_virtual_server(((struct sockaddr_in *) &sa_server)->sin_addr, server_conf));
	SSL_set_fd(os_conn.ssl,csd);
	update_child_status (child_num, SERVER_BUSY);
#else
	
	dupped_csd = csd;
#if defined(AUX) || defined(SCO)
	if ((dupped_csd = dup(csd)) < 0) {
	    log_error("couldn't duplicate csd", server_conf);
	    dupped_csd = csd;	/* Oh well... */
	}
#endif
	update_child_status (child_num, SERVER_BUSY);
	os_conn.in = pfdopen (ptrans, csd, "r");
	os_conn.out = pfdopen (ptrans, dupped_csd, "w");
#endif

	current_conn = new_connection (ptrans, server_conf, &os_conn,
				       (struct sockaddr_in *)&sa_server);

#ifdef  APACHE_SSL
	ApacheSSLSetupVerify(current_conn);
#endif

	if (current_conn->server->do_rfc931)
	    current_conn->remote_logname = 
		rfc931((struct sockaddr_in *)&sa_client,
		       (struct sockaddr_in *)&sa_server);
	
	r = read_request (current_conn);
	if (r) process_request (r); /* else premature EOF --- ignore */
		
	if (bytes_in_pool (ptrans) > 80000) {
	    char errstr[MAX_STRING_LEN];
	    sprintf (errstr, "Memory hog alert: allocated %ld bytes for %s",
	             bytes_in_pool (ptrans), r->the_request);
            log_error (errstr, r->server);
        }
		
	os_conn_flush(current_conn);
	os_conn_close(current_conn);
    }    
}

void make_child(server_rec *server_conf, int child_num)
{
    int pid;

    if (one_process) {
	signal (SIGHUP, (void (*)())just_die);
	signal (SIGTERM, (void (*)())just_die);
	child_main (child_num);
    }

    if ((pid = fork()) == -1) {
	log_error("Unable to fork new process", server_conf);
	return;
    } 
    
    if (!pid) {
	signal (SIGHUP, (void (*)())just_die);
	signal (SIGTERM, (void (*)())just_die);
	child_main (child_num);
    }
}

/*****************************************************************
 * Executive routines.
 */

static int keepalive_value = 1;  
static int one = 1;
static int num_children = 0;

void standalone_main(int argc, char **argv)
{
    struct sockaddr_in sa_server;
    static BOOL bFirst=TRUE;

    standalone = 1;
    sd = -1;
    
    if (!one_process) detach(); 
    
#ifdef NEXT
    setjmp(restart_buffer);
#else
    sigsetjmp(restart_buffer,1);
#endif

    signal (SIGHUP, SIG_IGN);	/* Until we're done (re)reading config */
    
    if(!one_process)
#ifndef NO_KILLPG
      killpg(pgrp,SIGHUP);	/* Kill 'em off */
#else
      kill(-pgrp,SIGHUP);
#endif
    
    if (sd != -1) {
	reclaim_child_processes(); /* Not when just starting up */
	log_error ("SIGHUP received.  Attempting to restart", server_conf);
    }
    
    if(!bFirst)
      {
	clear_pool (pconf);
	ptrans = make_sub_pool (pconf);
	server_conf = read_config(pconf, ptrans, server_confname); 
      }
    else
      bFirst=FALSE;

    open_logs(server_conf, pconf);
    set_group_privs();
    accept_mutex_init(pconf);
    reinit_scoreboard(pconf);
    
/*    default_server_hostnames (server_conf);*/

    if ((sd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == -1) {
        fprintf(stderr,"httpd: could not get socket\n");
        perror("socket");
        exit(1);
    }

    note_cleanups_for_fd (pconf, sd); /* arrange to close on exec or restart */
    
    if((setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,(const char *)&one,sizeof(one)))
       == -1) {
        fprintf(stderr,"httpd: could not set socket option\n");
        perror("setsockopt");
        exit(1);
    }
    if((setsockopt(sd,SOL_SOCKET,SO_KEEPALIVE,(const void *)&keepalive_value,
        sizeof(keepalive_value))) == -1) {
        fprintf(stderr,"httpd: could not set socket option SO_KEEPALIVE\n"); 
        perror("setsockopt"); 
        exit(1); 
    }

    memset((char *) &sa_server, 0, sizeof(sa_server));
    sa_server.sin_family=AF_INET;
    sa_server.sin_addr=bind_address;
    sa_server.sin_port=htons(server_conf->port);
    if(bind(sd,(struct sockaddr *) &sa_server,sizeof(sa_server)) == -1) {
	if (bind_address.s_addr != htonl(INADDR_ANY))
	    fprintf(stderr,"httpd: could not bind to address %s port %d\n",
		    inet_ntoa(bind_address), server_conf->port);
	else
	    fprintf(stderr,"httpd: could not bind to port %d\n",
		    server_conf->port);
        perror("bind");
        exit(1);
    }
    listen(sd, 512);

    set_signals();
    log_pid(pconf, pid_fname);

    num_children = 0;
    
    if (daemons_max_free < daemons_min_free + 1) /* Don't thrash... */
	daemons_max_free = daemons_min_free + 1;

    while (num_children < daemons_to_start) {
	make_child(server_conf, num_children++);
    }

    log_error ("Server configured -- resuming normal operations", server_conf);
    
    while (1) {
	int status, child_slot;
	int pid = wait_or_timeout(&status);
	
	if (pid >= 0) {
	    /* Child died... note that it's gone in the scoreboard. */
	    sync_scoreboard_image();
	    child_slot = find_child_by_pid (pid);
	    if (child_slot >= 0) update_child_status (child_slot, SERVER_DEAD);
        }

	sync_scoreboard_image();
	if ((count_idle_servers() < daemons_min_free)
	    && (child_slot = find_free_child_num()) >= 0
	    && child_slot <= daemons_limit)
	    {
	    update_child_status(child_slot,SERVER_STARTING);
	    make_child(server_conf, child_slot);
	    }
    }

} /* standalone_main */

extern char *optarg;
extern int optind;

int
main(int argc, char *argv[])
{
    int c;

    init_alloc();
    pconf = permanent_pool;
    ptrans = make_sub_pool(pconf);
    
    server_argv0 = argv[0];
    strcpy (server_root, HTTPD_ROOT);
    strcpy (server_confname, SERVER_CONFIG_FILE);

    while((c = getopt(argc,argv,"Xd:f:v")) != -1) {
        switch(c) {
          case 'd':
            strcpy (server_root, optarg);
            break;
          case 'f':
            strcpy (server_confname, optarg);
            break;
          case 'v':
            printf("Server version %s.\n",SERVER_VERSION);
            exit(1);
	  case 'X':
	    ++one_process;	/* Weird debugging mode. */
	    break;
          case '?':
            usage(argv[0]);
        }
    }

    setup_prelinked_modules();
    
    server_conf = read_config (pconf, ptrans, server_confname);

    if(standalone) {
        standalone_main(argc, argv);
    }
#ifndef APACHE_SSL
    else {
        conn_rec *conn;
	os_conn_rec os_conn;
	request_rec *r;
	struct sockaddr sa_server;
      
	open_logs(server_conf, pconf);
	set_group_privs();
	default_server_hostnames (server_conf);

        user_id = getuid();
        group_id = getgid();

	c = sizeof(sa_server);
	if(getsockname(csd, &sa_server, &c) < 0) {
	    perror("getsockname");
	    fprintf(stderr, "Error getting local address\n");
	    exit(1);
	}
	server_conf->port =ntohs(((struct sockaddr_in *)&sa_server)->sin_port);
	os_conn.in = stdin;
	os_conn.out = stdout;
	conn = new_connection (ptrans, server_conf, &os_conn,
			       (struct sockaddr_in *)&sa_server);
	r = read_request (conn);
	if (r) process_request (r); /* else premature EOF (ignore) */
    }
#endif
    exit (0);
}


