/*
 * chttpd/1.0
 * main program. Somewhat adapted from dhttpd
 * Copyright (c) 0x7d0 <noop@nwonknu.org>
 * Some parts
 * Copyright (c) 0x7cd David A. Bartold
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <string.h>
extern int      errno;


#include <autoconf.h>
#include <config.h>
#include <socket.h>
#define REALVERSION "chttpd/1.0"

#ifndef HAVE_DAEMON
#include <grp.h>
#include <pwd.h>
#endif




extern void     log_error(const char *fmt, ...);
extern void     close_log(void);

char           *webdirprefix;
int             num_proc,
                webdirprefixlen;

static struct listen_sock l_sock;

unsigned int    debug;

extern int
                read_config(char *configfilename, int *portnum, char **webdirprefix);


void
handle_sigchild(int unused)
{
    if (waitpid(0, NULL, WNOHANG) > 0)
	num_proc--;
    signal(SIGCHLD, handle_sigchild);
}

void
handle_sigterm(int unused)
{
    free(webdirprefix);
    shutdown_sock(l_sock);
    close_log();
    exit(0);
}

#ifndef HAVE_DAEMON
static void
spork(void)
{
    struct passwd  *pwent;
    int             user_id,
                    group_id;

    chdir("/");
    if (fork())
	exit(0);
    setsid();
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);
    if (dup(dup(open("/dev/null", O_APPEND))) == -1)
	exit(1);
    user_id = UID;
    group_id = GID;
    /*
     * If we're the super user, set user id, if new id is different
     */
    if (getuid() == 0 && user_id != 0) {
	/*
	 * Check password file
	 */
	if ((pwent = getpwuid(user_id)) == NULL)
	    exit(1);
	/*
	 * Reset groups attribute.
	 */
	if (initgroups(pwent->pw_name, group_id) == -1)
	    exit(1);
	/*
	 * Switch to our new user id.  We have to set the group first
	 */
	if (setgid(group_id) == -1)
	    exit(1);
	if (setuid(user_id) == -1)
	    exit(1);
    }
}
#endif

int
main(int argc, char *argv[])
{
  int o,
    s,
    configfile = 0,
    portnum = -1,
    status;
    pid_t           pid;
    char            *config_file_path = NULL;
    for (;;) {
	o = getopt(argc, argv, "p:hc:");
	if (o == -1)
	    break;
	switch (o) {
	case 0:
	    printf("Invalid option!\n");
	    return 1;
	case 'h':
	    printf("usage: %s [options]\n", argv[0]);
	    printf
		("  -p Use a different port than the default of %i\n",
		 DEFAULTPORT);
	    printf("  -h        Help\n");
	    printf("  -c Read options from config file\n");
	    return 0;
	case 'p':
	    sscanf(optarg, "%i", &portnum);
	    break;
	case 'c':
	    if (configfile) {
		fprintf(stderr, "--config called twice\n");
		return (1);
	    }
	    configfile = 1;
            config_file_path = optarg;
	    break;
	}
     }
    if (status = read_config(config_file_path
                             ? config_file_path : DEFAULT_CONFIG,
                             (portnum == -1 ? &portnum : 0),
                             &webdirprefix)) {
      if (configfile || status == 2) { return(1); }
    }

    if (portnum == -1)
	portnum = DEFAULTPORT;

    if (!configfile) {
	webdirprefix = malloc(strlen(WEBDIRPREFIX));
	strcpy(webdirprefix, WEBDIRPREFIX);
    }
    webdirprefixlen = strlen(webdirprefix);
    if (get_listen_sock(portnum, &l_sock) < 0) {
	fprintf(stderr,
                "\n%s failed to bind to port %d, exiting...\n",
                REALVERSION,
                portnum
                );
	return (1);
    }

    /*
     * now let's become a daemon
     */
#ifdef HAVE_DAEMON
    if (daemon(0, 0) == -1) {
	perror("couldn't become daemon:");
	return (1);
    }
#else
    spork();
#endif
    signal(SIGCHLD, handle_sigchild);
    signal(SIGHUP, SIG_IGN);
    signal(SIGTERM, handle_sigterm);
    for (;;) {
	if (num_proc < MAXCHILDPROC)
	    s = accept_sock(l_sock);
	else {
	    sleep(1);
	    continue;
	}
	if (s != -1) {
	    num_proc++;
	    pid = fork();
	    if (pid == 0) {
		struct active_sock ch_sock;
		/*
		 * Child process (fulfill request)
		 */
		close(l_sock.socket);
		get_active_sock(&ch_sock, s);
		handle_socket(ch_sock);
		fflush(ch_sock.io);
		fclose(ch_sock.io);
		shutdown(s, 2);
		exit(0);
	    } else if (pid == -1) {
		/*
		 * Error creating child
		 */
		num_proc--;
		close(s);
	    } else {
		/*
		 * If we're the parent, close connected socket.
		 */
		close(s);
	    }
	} else {		/*
				 * failed to accept
				 */

	    log_error("Failed to accept");
	}
	/*
	 * In case signals are lost
	 */
	while (waitpid(0, NULL, WNOHANG) > 0)
	    num_proc--;
    }

    /*
     * main returns int.
     */
    return 0;
}
