/*
   Pews - Personal Web Server
   --------------------------
   Written by d1s4st3r


   http://xoomer.alice.it/mental_insomnia/
   http://d1s4st3r.blogspot.com/
   http://d1s4st3r.deviantart.com/


   To compile on GNU/Linux:
      gcc -o pews pews.c

   To compile on Sun Solaris:
      gcc -o pews pews.c -lsocket -lnsl -lresolv
*/


#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netdb.h>


#define	PROGRAMNAME		"Pews"
#define	PROGRAMVERS		"0.1-alpha1"

#define MAXMSG			1024
#define MAXPATH			1024

#define ERRORHEAD		"<!doctype HTML public \"-//IETF//DTD HTML 2.0//EN\">\n<head><title>Pews v0.1 - Error</title></head>\n<body><h1>Error!</h1>\n<p>\n"
#define ERRORTAIL		"\n</p>\n<hr>\n<a href=\"about:Mozilla\">Pews v0.1</a></body></html>"

#define INDEX			"/index.html"


static char *progname;	/* name of the running executable file */

static char *BASEADDR;	/* server's root directory (the one which contains index.html) */
static int SERVER_PORT;	/* local port the server should listen on */
static int MAXLISTEN;	/* maximum number of allowed connections at the same time */
static int VERBOSITY;	/* verbosity (0=off, 1=on) */

typedef struct
{
	int s, len, curr;
	char msg[MAXMSG];
} conn;

struct globalArgs_t
{
	const char *rootdir;	/* -r option: server's root directory (the one which contains index.html) */
	int port;				/* -p option: local port the server should listen on */
	int maxlisten;			/* -m option: maximum number of allowed connections at the same time */
	int verbosity;			/* -v option: verbosity (0=off, 1=on) */
} globalArgs;

static const char *optString = "r:p:m:vh?V"; /* command line option flags */


/* this function shows the program usage */
static void display_usage()
{
		printf("Usage:    %s [<options>]\n", progname);
		printf("where <options> are:\n");
		printf("    -r <dir>     the root directory which contains index.html\n");
		printf("    -p <port>    the port the server should listen on\n");
		printf("    -m <number>  max number of allowed connections\n");
		printf("    -h|-?        this help\n");
		printf("    -v           verbose mode\n");
		printf("    -V           program version\n");
		fflush(stdout);

		exit(EXIT_SUCCESS);
}


/* this function shows the program version */
static void display_version()
{
	printf("%s v%s\n", PROGRAMNAME, PROGRAMVERS);
 
	exit(EXIT_SUCCESS);
}


/* returns next character in stream, skipping '\r' */
static int next(conn *c)
{
	char retval;

	do
	{
		if (c->len==c->curr)
        {
			c->len = read(c->s, c->msg, MAXMSG);

			if (c->len<0) 
			{
				perror("read");

				exit(EXIT_FAILURE);
			}

			if (c->len == 0)
				return EOF;

			c->curr = 0;
		}

		retval = c->msg[c->curr++];
	}
	while (retval == '\r');

	return retval;
}


/* returns standard error messages */
static void error(int s, int num)
{
	static char *errors[] = {"Your browser sent a query that this server does not understand.", "The URL you requested was not found on this server.", "The URL you requested was too long! Sorry."};

	if ((num<0)||(num>sizeof(errors)))
	{
		fprintf(stderr, "Server: illegal error message requested: %d\n", num);

		exit(EXIT_FAILURE);
	}

	if (write(s, ERRORHEAD, strlen(ERRORHEAD))<0)
	{
		perror("server: errorhead write failed\n");

		exit(EXIT_FAILURE);
	}

	if (write(s, errors[num], strlen(errors[num]))<0)
	{
		perror("server: errormsg write failed\n");

		exit(EXIT_FAILURE);
	}

	if (write(s, ERRORTAIL, strlen(ERRORTAIL))<0)
	{
		perror("server: errortail write failed\n");

		exit(EXIT_FAILURE);
	}
}


/* actual server code */
static void server(int s)
{
	conn c;
	int i = 0;
	int ncount = 0;
	int infile;
	struct stat stbuf;
	char path[MAXPATH+11], ch, *p, buff[MAXMSG]; /* +11 here is to cover "/index.html" */

	c.s = s;
	c.len = 0;
	c.curr = 0;

	/* verify that the "GET " portion of the request is okay */
	if (!((next(&c)=='G')&&(next(&c)=='E')&&(next(&c)=='T')&&(next(&c)==' ')))
	{
		error(c.s, 0);

		return;
	}

	/* start the path with the base address */
	p = BASEADDR;

	while (*p!=0)
		path[i++] = *(p++);

	/* collect the requested path up to the space (if the path is terminated by a \n, don't bother getting the other newline) */
	while (1)
	{
		ch = next(&c);

		if (ch==' ')
			break;

		if (ch=='\n')
		{
			ncount = 2;
			break;
		}

		path[i++] = ch;

		if (i==MAXPATH)	/* oh no, the path is too long! */
		{
			error(c.s, 2);

			return;
		}
	}

	path[i] = 0;

	/* wait for two \n's to go by */
	while (ncount<2)
	{
		if (next(&c)=='\n')
			ncount++;
		else
			ncount = 0;
	}

	/* try to open the generated path */
	infile = open(path, O_RDONLY);

	if (infile<0)
	{
		if ((errno==EACCES)||(errno==ENAMETOOLONG)||(errno==ENOENT)||(errno==ENOTDIR)||(errno==ELOOP))
		{
			/* URL not found... */
			error(c.s, 1);
			return;
		}

		perror("server: path open failed badly\n");

		exit(EXIT_FAILURE);
	}

	if (fstat(infile, &stbuf)<0)
	{
		perror("server: fstat failed\n");

		exit(EXIT_FAILURE);
	}

	/* if it's not a regular file or directory, it's no good */
	if (!(S_ISREG(stbuf.st_mode)||S_ISDIR(stbuf.st_mode)))
	{
		error(c.s, 1);

		return;
	}

	/* directory kludge; if it's a directory, try adding /index.html */
	if (S_ISDIR(stbuf.st_mode))
	{
		p = INDEX;

		while (*p!=0)
			path[i++] = *(p++);

		path[i] = 0;

		close(infile);
		infile = open(path, O_RDONLY);

		if (infile<0)
		{
			if ((errno==EACCES)||(errno==ENAMETOOLONG)||(errno==ENOENT)||(errno==ENOTDIR)||(errno==ELOOP))
			{
				/* URL not found... */
				error(c.s, 1);
				return;
			}

			perror("server: path open index.html failed badly\n");

			exit(EXIT_FAILURE);
		}
	}

	/* now send the opened file over the wire */
	do
	{
		i = read(infile, buff, MAXMSG);

		if (i>0)
			if (write(c.s, buff, i)<0)
			{
				perror("server: sending file failed\n");

				exit(EXIT_FAILURE);
			}
	}
	while (i>0);

	if (i<0)
	{
		perror("server: reading file failed\n");

		exit(EXIT_FAILURE);
	}
}


/* main */
int main(int argc, char *argv[])
{
    int s; /* socket */
    struct sockaddr_in from; /* structure for the remote clients */
    static struct sockaddr_in me_in; /* structure for the local machine */
    int fromlen;
    int news;
    int pid;
	int opt;

    progname = argv[0];

	/* initialize globalArgs with default values */
	/*globalArgs.rootdir = "/";*/
	if ((globalArgs.rootdir=getcwd(NULL, 64))==NULL){perror("pwd");exit(2);} /* sets the root dir to the current directory */
	globalArgs.port = 8080;
	globalArgs.maxlisten = 10;
	globalArgs.verbosity = 0;

	opt = getopt(argc, argv, optString);

	/* the following while loop examines the command line arguments */
	while (opt!=(-1))
	{
		switch(opt)
		{
			case 'r':
                globalArgs.rootdir = optarg;
				break;
			case 'p':
				globalArgs.port = atoi(optarg);
                break;
			case 'm':
				globalArgs.maxlisten = atoi(optarg);
				break;
			case 'v':
				globalArgs.verbosity++;
				break;
			case 'h':
			case '?':
				display_usage();
				break;
			case 'V':
				display_version();
				break;
			default:
				/* you will never get here... */
				break;
		}

		opt = getopt(argc, argv, optString);
	}

	BASEADDR = globalArgs.rootdir; /* global variable for root dir (needed by child processes) */
	SERVER_PORT = globalArgs.port;
	MAXLISTEN = globalArgs.maxlisten;
	VERBOSITY = globalArgs.verbosity;

	if (VERBOSITY) printf("Local root directory set to: %s\n", BASEADDR);

    /* this creates the socket */
    if ((s=socket(PF_INET, SOCK_STREAM, 0))<0) /* TCP connection */
    {
		perror("socket");

		exit(EXIT_FAILURE);
    }

    /* this binds the socket address to a connection */
    me_in.sin_family = AF_INET;
    me_in.sin_addr.s_addr = htonl(INADDR_ANY); /* anyone can connect */
    me_in.sin_port = htons(SERVER_PORT);

    if (bind(s, (struct sockaddr *)&me_in, sizeof(me_in))<0)
    {
		perror("bind");

		exit(EXIT_FAILURE);
    }

	if (VERBOSITY) printf("Listening on port %d...\n", SERVER_PORT);

    /* this makes the server accept MAXLISTEN connection requests at the same time */
    if (listen(s, MAXLISTEN)<0)
    {
		perror("listen");

		exit(EXIT_FAILURE);
    }

	if (VERBOSITY) printf("Accepting %d connections.\n", MAXLISTEN);

    /* loop forever accepting connections from anyone */
    for (;;) 
    {
		/* waiting for someone to connect; accept() returns a new connection for the established connection */
		fromlen = sizeof(from);

		if ((news=accept(s, (struct sockaddr *)&from, &fromlen))<0)
		{
			perror("accept");

			exit(EXIT_FAILURE);
		}

		/* some client connected; fork off a process to deal with the client (the main program can continue waiting for new connections) */
		if ((pid=fork())<0)
		{
			perror("fork");

			exit(EXIT_FAILURE);
		}
		else if (pid==0) /* child process that will handle the connection */
		{
			struct hostent *hp = gethostbyaddr((char *)&from.sin_addr.s_addr, sizeof(from.sin_addr.s_addr), AF_INET);
			char *remote_ip = (char *)inet_ntoa(from.sin_addr);
			char *remote_host = hp->h_name;
			int remote_port = ntohs(from.sin_port);

			if (VERBOSITY) printf("New connection from: %s (%s:%d)\n", remote_host, remote_ip, remote_port);

			close(s);		/* child has no need for the parent's socket */
			server(news);	/* serve the established connection */

			exit(EXIT_SUCCESS);
		}

		close(news);
	}
}

