
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#ifdef AIX
#include <sys/time.h>
#endif
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#ifdef AIX
#include <arpa/inet.h>
#endif
#include <netdb.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <pwd.h>

int             http_sock, con_sock;
int             http_port = 80;
struct sockaddr_in source;
char           *log_httpfile = NULL;
char           *homedir;
int             uid;
int				nouidcheck = 0;
int				debug = 0;
int				security = 1;

void broken(void)
{
	close(con_sock);
	log_http("SIGPIPE", "");
	exit(0);
}

void 
closesock(int n)
{
	close(con_sock);
	close(http_sock);

	log_http("SIGTERM", "");
	exit(0);
}

void
init_servconnection(void)
{
	struct sockaddr_in server;

	/* Creation socket */
	http_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (http_sock < 0) {
		perror("socket");
		exit(1);
	}
	signal(SIGTERM, closesock);
	signal(SIGPIPE, broken);
	signal(SIGCHLD, SIG_IGN);
    server.sin_family = AF_INET;
	server.sin_port = htons(http_port);
	server.sin_addr.s_addr = INADDR_ANY;
	if (bind(http_sock, (struct sockaddr *) & server, sizeof(server)) < 0) {
		perror("bind socket");
		exit(1);
	}
	if (listen(http_sock, 1) < 0) {
		perror("listen");
		exit(-1);
	}
}

void
waitconnection(void)
{
	int             lg;

	lg = sizeof(struct sockaddr_in);
	con_sock = accept(http_sock, (struct sockaddr *) & source, &lg);
	if (con_sock <= 0) {
		perror("accept");
		exit(-1);
	}
}

usage()
{
	fprintf(stderr, "usage httpd [-DUs] [-l log_httpfile][-p port][-d homedir][-u uid]\n");
	fprintf(stderr,"-d : default root http dir\n-s disable security\n");
	fprintf(stderr,"-U : disable uid check\n-D : increment debug mode\n");
	fprintf(stderr,"\n-s implies -U.  -s enables absolute pathnames and the use of ~username\n");
	fprintf(stderr,"Without -U (or -s) only files with the same UID is the process (or the one\nspecified by -u) can be read.\n");
	exit(-1);
}

char            rep_err_nget[] = "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 405</H1>\
This server answer only to GET requests\n</BODY></HTML>\n";
char            rep_err_acc[] = "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
Not found - file doesn't exist or is read protected\n</BODY></HTML>\n";
char            rep_err_user[] = "<HTML><HEAD><TITLE>Error</TITLE></HEAD><BODY><H1>Error 404</H1>\
Not found - username doesn't exist, or cannot be expanded\n</BODY></HTML>\n";

log_http(char *action, char *item)
{
	int             fd;
	char            logline[1024];
	time_t          tl;

	if (log_httpfile == NULL)
		return;

	tl = time(NULL);
	strcpy(logline, ctime(&tl));
	logline[strlen(logline) - 1] = ' ';
	strcat(logline, inet_ntoa(source.sin_addr));
	strcat(logline, " ");
	strcat(logline, action);
	strcat(logline, ":");
	strcat(logline, item);
	strcat(logline, "\n");


	fd = open(log_httpfile, O_WRONLY | O_APPEND | O_CREAT, 0600);
	if (fd < 0) {
		perror("open log file");
		exit(-1);
	}
	write(fd, logline, strlen(logline));
	close(fd);
}

serve_req(char *req)
{
	char            buff[1024];
	char *username;
	struct passwd *spws;
	int             fd, lg;
	char           *filename, *c;
	char urlbuf[256];
	char urlbuf2[256];
	struct stat     statres;
	int nfds;
	fd_set rfd, wfd, xfd;
	struct timeval timeout;
	int count=0;
	char file_default[32];

	sprintf(file_default,  "index.html");

	FD_ZERO(&wfd);
	FD_ZERO(&xfd);
	FD_ZERO(&rfd);
	FD_SET(con_sock,&rfd );
	nfds = FD_SETSIZE;
	timeout.tv_sec = 0;
	timeout.tv_usec = 100000;

	select(nfds, &rfd, &wfd, &xfd, &timeout);

	if (strcmp(strtok(req, " "), "GET") != 0) {
		write(con_sock, rep_err_nget, strlen(rep_err_nget));
		log_http("error 405", req);
		return;
	}
	filename = strtok(NULL, " ");
	/* skip the initial slash */
	filename++;

	/* if there isn't a file name, use the default of index.html */

	if(!*filename) filename = file_default;

	strcpy(urlbuf, filename);

	if(security) {
	/* forbid ".." in the path */
		c = filename;
		while (*c != '\0')
			if (c[0] == '.' && c[1] == '.') {
				write(con_sock, rep_err_acc, strlen(rep_err_acc));
				log_http("error 404", filename);
				return;
			} else
				c++;
	} else {
		/* try to expand tildes to full usernames */
		if(*filename=='~') {
			username = filename+1;
			c = filename;
			while(*c && (*c!='/')) c++;
			if(*c) {
				*c='\0'; c++;
				if(!*c) c = file_default;
			} else {
				c = file_default;
			}
			setpwent();
			spws = getpwnam(username);
			
			if(!spws) {
				write(con_sock, rep_err_user, strlen(rep_err_user));
				log_http("error 404", filename);
				return;
			}

			sprintf(urlbuf,"%s/%s",spws->pw_dir, c);
			filename = urlbuf;
		}
	}
	if(urlbuf[strlen(urlbuf)-1]=='/') {
		log_http("adding default",urlbuf);
		strcat(urlbuf,file_default);
	}
	fd = open(urlbuf, O_RDONLY);
	if (fd < 0) {
		write(con_sock, rep_err_acc, strlen(rep_err_acc));
		log_http("cant open", urlbuf);
		return;
	}
	if (fstat(fd, &statres) < 0) {
		write(con_sock, rep_err_acc, strlen(rep_err_acc));
		log_http("cant stat", urlbuf);
		close(fd);
		return;
	}
	if (S_ISDIR(statres.st_mode)) {
		log_http("is directory",urlbuf);
		strcat(urlbuf,"/"); strcat(urlbuf,file_default);
		close(fd); 
		fd = open(urlbuf, O_RDONLY);
		if (fd < 0) {
			write(con_sock, rep_err_acc, strlen(rep_err_acc));
			log_http("cant open",urlbuf);
			return;
		}
		if (fstat(fd, &statres) < 0) {
			write(con_sock, rep_err_acc, strlen(rep_err_acc));
			log_http("cant stat", urlbuf);
			close(fd);
			return;
		}
	}
	if (!S_ISREG(statres.st_mode)) {
		write(con_sock, rep_err_acc, strlen(rep_err_acc));
		log_http("not regular file", urlbuf);
		close(fd);
		return;
	}
	if(!nouidcheck) {
		if (statres.st_uid != uid ||
		    !S_ISREG(statres.st_mode) ||
		    !(statres.st_mode & S_IROTH)) {
			write(con_sock, rep_err_acc, strlen(rep_err_acc));
			log_http("bad uid", urlbuf);
			close(fd);
			return;
		}
	}
   while (lg = read(fd, buff, 1024))  {
			write(con_sock, buff, lg);
   }
	close(fd);

	log_http("GET", urlbuf);
}

void do_main_loop(void)
{
	char request[1024];
	int lg;

	do {
		if(debug) log_http("INFO", "Waiting for connection");

		waitconnection();
		if(fork() == 0) {		/* I am the child */
			close(http_sock);
			lg = read(con_sock, request, 1024);
			serve_req(request);
			close(con_sock);
			exit(0);
		}
		else {					/* I am the parent */
			close(con_sock);
		}
	} while(1);
}

main(int argc, char **argv)
{
	uid = getuid();

	argc--;
	argv++;
	while (argc > 0) {
		if (argv[0][0] != '-')
			usage();
		switch (argv[0][1]) {
		case 'D': debug++; argv += 1; argc -= 1; break;
		case 's': nouidcheck=1; security=0; argv += 1; argc -= 1; break;
		case 'l':
			log_httpfile = argv[1];
			argv += 2;
			argc -= 2;
			break;
		case 'p':
			http_port = atoi(argv[1]);
			argv += 2;
			argc -= 2;
			break;
		case 'u':
			uid = atoi(argv[1]);
			argv += 2;
			argc -= 2;
			break;
		case 'd':
			homedir = argv[1];
			argv += 2;
			argc -= 2;
			break;
		case 'U':
			nouidcheck++; argv += 1; argc -= 1;
			break;
		default:
			usage();
		}
	}

	if (homedir == NULL)
		homedir = getenv("HOME");

	if (chdir(homedir) < 0) {
		perror("chdir");
		exit(-1);
	}
	log_http("INFO","Starting...");
	init_servconnection();
	if(debug) log_http("INFO","Bound socket");

	fprintf(stderr, "HTTP daemon started on port %d.\n", http_port);
	if(!debug) {
		if(fork()) exit(0);
		setsid();
	}

	do_main_loop();
}
