/*
 * httpd.c: simple http daemon for answering WWW file requests
 *
 * 
 * Rob McCool 3/21/93
 * 
 */


#include "httpd.h"

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);
}

void htexit(int status, FILE *out) {
#ifdef PEM_AUTH
    pem_cleanup(status,out);
#endif
    exit(status);
}

int sd;
pid_t pgrp;

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((pgrp=setpgrp(getpid(),0)) == -1) {
        fprintf(stderr,"httpd: setpgrp failed\n");
        perror("setpgrp");
        exit(1);
    }
#endif    
}

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

#ifdef BSD
void ign() {
#ifndef NEXT
    int status;
#else
    union wait status;
#endif
    pid_t pid;

    while( (pid = wait3(&status, WNOHANG, NULL)) > 0);
}
#endif

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

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

void set_signals();

void restart() {
    log_error_noclose("httpd: caught SIGHUP, restarting");
    kill_mime();
    kill_security();
    kill_indexing();
    if(server_hostname) {
        free(server_hostname);
        server_hostname = NULL;
    }
    read_config(error_log);
    close_logs();
    open_logs();
    log_error_noclose("httpd: successful restart");
    get_local_host();
    set_signals();
}

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

#ifdef BSD
    signal(SIGCHLD,(void (*)())ign);
#else
    signal(SIGCHLD,SIG_IGN);
#endif
}


void standalone_main() {
    int csd, clen,pid, one=1;
#ifdef NEXT
    struct sockaddr_in sa_server;
    struct sockaddr sa_client;
#else
    struct sockaddr_in sa_server,sa_client;
#endif

    detach();

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

    if((setsockopt(sd,SOL_SOCKET,SO_REUSEADDR,&one,sizeof(one))) == -1) {
        fprintf(stderr,"httpd: could not set socket option\n");
        perror("setsockopt");
        exit(1);
    }
    bzero((char *) &sa_server, sizeof(sa_server));
    sa_server.sin_family=AF_INET;
    sa_server.sin_addr.s_addr=htonl(INADDR_ANY);
    sa_server.sin_port=htons(port);
    if(bind(sd,(struct sockaddr *) &sa_server,sizeof(sa_server)) == -1) {
        fprintf(stderr,"httpd: could not bind to port %d\n",port);
        perror("bind");
        exit(1);
    }
    listen(sd,5);

    set_signals();
    log_pid();

    while(1) {
      retry:
        clen=sizeof(sa_client);
        if((csd=accept(sd,&sa_client,&clen)) == -1) {
            if(errno == EINTR)  {
#ifdef BSD
                ign();
#endif
                goto retry;
            }
            log_error("socket error: accept failed");
            goto retry;
        }
	/* we do this here so that logs can be opened as root */
        if((pid = fork()) == -1)
            log_error("unable to fork new process");
        else if(!pid) {
	    struct passwd* pwent;
            struct linger sl;

            sl.l_onoff = 1;
            sl.l_linger = 600; /* currently ignored anyway */
            /* this should check error status, but it's not crucial */
            setsockopt(csd,SOL_SOCKET,SO_LINGER,&sl,sizeof(sl));

            close(0);
            close(1);
            dup2(csd,0);
            dup2(csd,1);
            close(sd);

            remote_logname = (!do_rfc931 ? NULL :
                              rfc931((struct sockaddr_in *)&sa_client,
                                     &sa_server));

            /* Only try to switch if we're running as root */
            if(!getuid()) {
                /* Now, make absolutely certain we don't have any privileges
                 * except those mentioned in the configuration file. */
                if ((pwent = getpwuid(user_id)) == NULL)
                    die(SERVER_ERROR,"couldn't determine user name from uid",
                        stdout);
                /* Reset `groups' attribute. */
                if (initgroups(pwent->pw_name, group_id) == -1)
                    die(SERVER_ERROR,"unable to setgroups",stdout);

                /* Note the order, first setgid() and then setuid(), it
                 * wouldn't work the other way around. */
                if (setgid(group_id) == -1)
                    die(SERVER_ERROR,"unable to change gid",stdout);
                if (setuid(user_id) == -1)
                    die(SERVER_ERROR,"unable to change uid",stdout);
            }
            process_request(0,stdout);
            fclose(stdin);
            fclose(stdout);
            shutdown(csd,2);
            close(csd);
            exit(0);
        }
        close(csd);
    }
}

extern char *optarg;
extern int optind;

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

    strcpy(server_root,HTTPD_ROOT);
    make_full_path(server_root,SERVER_CONFIG_FILE,server_confname);

    while((c = getopt(argc,argv,"d:f:v")) != -1) {
        switch(c) {
          case 'd':
            strcpy(server_root,optarg);
	  make_full_path(server_root,SERVER_CONFIG_FILE,server_confname);
            break;
          case 'f':
            strcpy(server_confname,optarg);
            break;
          case 'v':
            printf("NCSA httpd version %s.\n",SERVER_VERSION);
            exit(1);
          case '?':
            usage(argv[0]);
        }
    }
    read_config(stderr);
    open_logs();
    get_local_host();

    if(standalone)
        standalone_main();
    else {
        user_id = getuid();
        group_id = getgid();

        port = get_portnum(fileno(stdout),stdout);
        if(do_rfc931)
            remote_logname = get_remote_logname(stdout);
        process_request(0,stdout);
    }
    fclose(stdin);
    fclose(stdout);
    exit(0);
}
