#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

short int recivedCloseSignal = 0;
const int defaultPort = 6789;
short int debug = 0;
short int iAmDaemon = 0;

#define MAX_BUFFER	1024
#define MYNAME		"serwerHttp"

struct longLine
{
	char line[MAX_BUFFER];
	struct longLine * next;
};

void dumpLongLine (struct longLine * llPtr)
{
	int i = 0;

	while(llPtr != NULL)
	{
		printf("line %i (%s)\n", i++, llPtr->line);
		llPtr = llPtr->next;
	}
}

int sizeOfLongLine (struct longLine * llPtr)
{
	int i = 0;

	while(llPtr->next != NULL)
	{
		llPtr = llPtr->next;
		i++;
	}
	return i * (sizeof(llPtr->line) - 1) + strlen(llPtr->line);
}

char * joinLongLine(struct longLine * llPtr)
{
	char * joined;
	int i = 0;

	errno = (int) NULL;
	joined = calloc(sizeof(char), sizeOfLongLine(llPtr) + 1);
	if (joined == NULL)
	{
		perror("calloc()");
		return (char *) -1;
	}
	while(llPtr->next != NULL)
	{
		memcpy(joined + i * sizeof(char), llPtr->line, sizeof(llPtr->line) - 1);
		i += sizeof(llPtr->line) - 1;
		llPtr = llPtr->next;
	}
	memcpy(joined + i * sizeof(char), llPtr->line, strlen(llPtr->line));
	return joined;
}

char * readLongLine(int fd)
{
	struct longLine * currentLine;
	struct longLine * firstLine;
	int retVal, readedCounter;

	firstLine = calloc(sizeof(struct longLine), 1);
	firstLine->next = NULL;
	currentLine = firstLine;
	readedCounter = 0;

	if (sizeof(firstLine->line) < 2)
	{
		fprintf(stderr, "line size in longLine structure is to small\n");
		return (char *) -1;
	}

	while(1)
	{
		int freeSpace = sizeof(currentLine->line) - readedCounter - 1;

		errno = (int) NULL;
		retVal = read(fd, (currentLine->line) + readedCounter * sizeof(char), freeSpace);
		if (retVal == 0) /* end of file */
		{
			currentLine->line[readedCounter] = 0;
			break;
		} else if (retVal == -1) { /* error */
			perror("read()");
			return (char *) -1;
		} else if (retVal == freeSpace) {
			errno = (int) NULL;
			currentLine->next = calloc(sizeof(struct longLine), 1);
			if (currentLine->next == NULL)
			{
				perror("calloc()");
				return (char *) -1;
			}
			currentLine = currentLine->next;
			currentLine->next = NULL;
			readedCounter = 0;
		} else {
			readedCounter += retVal;
		}
	}
	return(joinLongLine(firstLine));
}

char * readLongLineUntil(int fd, char * delimeter)
{
	struct longLine * currentLine;
	struct longLine * firstLine;
	int retVal, readedCounter;
	int i = 0;

	firstLine = calloc(sizeof(struct longLine), 1);
	firstLine->next = NULL;
	currentLine = firstLine;
	readedCounter = 0;

	if (sizeof(firstLine->line) < 2)
	{
		fprintf(stderr, "line size in longLine structure is to small\n");
		return (char *) -1;
	}

	while(1)
	{
		errno = (int) NULL;
		retVal = read(fd, (currentLine->line) + readedCounter * sizeof(char), 1);
		if (retVal == 0) /* end of file */
		{
			currentLine->line[readedCounter] = 0;
			break;
		} else if (retVal == -1) { /* error */
			perror("read()");
			return (char *) -1;
		} else {
			if (*((currentLine->line) + readedCounter * sizeof(char)) == *(delimeter + i * sizeof(char)))
			{
				i++;
			} else {
				i = 0;
			}
			if (i == strlen(delimeter))
			{
				break;
			}
			if (readedCounter == sizeof(currentLine->line) - 2)
			{
				errno = (int) NULL;
				currentLine->next = calloc(sizeof(struct longLine), 1);
				if (currentLine->next == NULL)
				{
					perror("calloc()");
					return (char *) -1;
				}
				currentLine = currentLine->next;
				currentLine->next = NULL;
				readedCounter = 0;
			} else {
				readedCounter++;
			}
		}
	}
	return(joinLongLine(firstLine));
}

void myprint(char * message)
{
	printf("%s\n", message);
}

short int isDir(char * path)
{
	char buffer[MAX_BUFFER];
	struct stat metadata;

	errno = (int)NULL;
	if (stat(path, &metadata) == -1)
	{
		snprintf(buffer, sizeof(buffer), "can't stat() '%s': %s", path, strerror(errno));
		myprint(buffer);
		return -1;
	}
	return S_ISDIR(metadata.st_mode);
}

void blad(char * message)
{
	fprintf(stderr, "%s\n", message);
	exit(1);
}

int isNumber(char * string)
{
	int i = 0;
	for (i = 0; string[i] != 0; ++i)
	{
		if (string[i] < '0' || string[i] > '9')
		{
			return 0;
		}
	}
	return 1;
}

int anyPort_to_int(char * string)
{
	FILE * potok;
	char buffer[MAX_BUFFER];
	int readed;
	char * portStart;
	char * cp;

	if (isNumber(string))
	{
		portStart = string;
	} else {
		snprintf(buffer, sizeof(buffer), "getent services %s", string);
		potok = popen(buffer, "r");
		if (potok == NULL) blad("popen() error");
		if (feof(potok)) blad("can't find given port number");
		readed = fread(buffer, sizeof(char), sizeof(buffer), potok);
		if (ferror(potok)) blad("fread() error");
		if (readed == 0) blad("can't find given port number");
		buffer[readed - 1] = 0;

		for (cp = buffer; *cp != '/'; cp += sizeof(char))
		{
			if (*(cp - sizeof(char)) == ' ' && *cp >= '0' && *cp <= '9')
				portStart = cp;
		}
		cp = 0;
	}
	return htons(atoi(portStart));
}

void usage(char * name)
{
	fprintf(stderr, "Usage: %s [-h] [-c DIR] [-d] [-s] [-u USER] [-p PORT]\n", name);
	fprintf(stderr, "-h\tthis help\n");
	fprintf(stderr, "-c DIR\tchroot's server to DIR\n");
	fprintf(stderr, "-d\tturns debug output on\n");
	fprintf(stderr, "-s\trun as daemon (all messages will be sent to syslog\n\n");
	fprintf(stderr, "-u USER\trun server as USER\n");
	fprintf(stderr, "Default port is %i.\n", defaultPort);
	fprintf(stderr, "You can close it by sending SIGINT ([Ctlr]+[C]) or SIGKILL (kill PID).\nPid will be written to ./%s.pid.\n", MYNAME);
	exit(1);
}

void writePid(void)
{
	char buffer[MAX_BUFFER];
	FILE * pidFile;

	snprintf(buffer, MAX_BUFFER, "./%s.pid", MYNAME);
	errno = (int)NULL;
	pidFile = fopen(buffer, "w");

	if (pidFile == NULL)
	{
		snprintf(buffer, sizeof(buffer), "can't open pid file: %s", strerror(errno));
		myprint(buffer);
		exit(1);
	}
	
	snprintf(buffer, MAX_BUFFER, "%i\n", getpid());
	if (fwrite(buffer, strlen(buffer), sizeof(char), pidFile) == 0)
	{
		snprintf(buffer, sizeof(buffer), "can't write to pid file: %s", strerror(errno));
		myprint(buffer);
		exit(1);
	}
	fclose(pidFile);
}

int myWrite(int fd, char * str)
{
	errno = (int) NULL;
	if (write(fd, str, strlen(str)) == -1)
	{
		myprint(strerror(errno));
		myprint("was write() error");
		return -1;
	}
	return 1;
}

int myLs(int fd, char * path)
{
	DIR * dd;
	char buffer[MAX_BUFFER];
	struct dirent * metadata;
	int ret = 0;
	
	errno = (int)NULL;
	dd = opendir(path);
	if (dd == NULL)
	{
		snprintf(buffer, sizeof(buffer), "error opening directory '%s': open(): %s", path, strerror(errno));
		myprint(buffer);

		errno = (int)NULL;
		if (myWrite(fd, "HTTP/1.1 500 Internal Server Error\r\nContent-type: text/html\r\n\r\n<h1>Can't open directory.</h1>") == -1)
		{
			snprintf(buffer, sizeof(buffer), "Could not send status response: write(): %s", strerror(errno));
			myprint(buffer);
		}
		return 0;
	}

	snprintf(buffer, sizeof(buffer), "HTTP/1.1 200 OK\r\nContent-type: text/html\r\n\r\n<html><head><title>Directory listing for %s (Maciej Korzen)</title></head><body><h1>Directory listing for %s:</h1><pre><ul>", path, path);
	if (myWrite(fd, buffer) == -1)
	{
		myprint("error sending directory listing");
		return -1;
	}
	errno = (int)NULL;
	while ((metadata = readdir(dd)) != NULL)
	{
		errno = (int)NULL;
		snprintf(buffer, sizeof(buffer), "<li>%s</li>", metadata->d_name);
		if (write(fd, buffer, strlen(buffer)) == -1)
		{
			snprintf(buffer, sizeof(buffer), "error while sending directory content to client: write(): %s", strerror(errno));
			myprint(buffer);
			return 0;
		}
	}
	if (errno != (int)NULL)
	{
		snprintf(buffer, sizeof(buffer), "error listing directory '%s': readdir(): %s", path, strerror(errno));
		myprint(buffer);
		ret = 0;
	}

	errno = (int)NULL;
	if (closedir(dd) == -1)
	{
		snprintf(buffer, sizeof(buffer), "error closing directory '%s': close(): %s", path, strerror(errno));
		myprint(buffer);
		ret = 0;
	}
	snprintf(buffer, sizeof(buffer), "</ul></pre><br /><hr />%s v0.0.1, Maciej Korzen #9114</body></html>", MYNAME);
	if (myWrite(fd, buffer) == -1)
	{
		myprint("error sending directory listing");
		return -1;
	}
	return ret;
}

int checkPerms(char * path)
{
	struct stat metadata;
	char buffer[MAX_BUFFER];

	errno = (int)NULL;
	if (stat(path, &metadata) == -1)
	{
		snprintf(buffer, sizeof(buffer), "can't stat() '%s': %s", path, strerror(errno));
		myprint(buffer);
		return -1;
	}
	
	if (S_ISDIR(metadata.st_mode))
	{
		if((S_IROTH & metadata.st_mode) && (S_IXOTH & metadata.st_mode))
		{
			return 1;
		}
	} else {
		if(S_IROTH & metadata.st_mode)
		{
			return 1;
		}
	}
	return 0;
}

void handleEndSignal(int s)
{
	char buffer[MAX_BUFFER];
	snprintf(buffer, MAX_BUFFER, "Recived signal %i. Program will shut down after ending current connections. Please wait.", s);
	myprint(buffer);
	recivedCloseSignal = 1;
}

char * getFilenameFromRequest(char * req)
{
	char delimeter[] = " HTTP/1.1\r\n";
	int i = 0, j = 0;
	char * fn;
	char d;
	char e;

	req += 4 * sizeof(char);

	while(1)
	{
		d = *(req + i * sizeof(char));
		e = *(delimeter + j * sizeof(char));
		if (d == (char)0)
		{
			if (debug) printf("Delimeter not found\n");
			return (char *) -1;
		}
		if (d == e)
		{
			j++;
		} else {
			j = 0;
		}
		if (j == (sizeof(delimeter) - 1))
		{
			i -= 10;
			break;
		}
		i++;
	}
	errno = (int)NULL;
	fn = calloc(sizeof(char), i + 1);
	if (fn == NULL)
	{
		myprint("calloc() error:");
		myprint(strerror(errno));
		if (debug) printf("something bad with getFilenameFromRequest()\n");
		return (char *) -1;
	}
	memcpy(fn, req, i);

	if (debug) printf("\ngetFilenameFromRequest(): allocated address: %i\n", (int)fn);
	return fn;
}

int mySendFile(int sd, char * filename)
{
	char buffer[MAX_BUFFER];
	int readCounter, writeCounter;
	char * bufptr;
	int fd;

	snprintf(buffer, sizeof(buffer), "Reading file '%s'.", filename);
	myprint(buffer);

	errno = (int)NULL;
	fd = open(filename, O_RDONLY);

	if (fd == -1)
	{
		snprintf(buffer, sizeof(buffer), "Could not open file for reading: open(): %s", strerror(errno));
		myprint(buffer);
		myWrite(sd, "HTTP/1.1 500 500 Internal Server Error\r\nContent-type: text/html\r\n\r\n<h1>Can't open file.</h1>\r\n");
		return -1;
	}

	if (myWrite(sd, "HTTP/1.1 200 OK\r\n\r\n") == -1)
	{
		myprint("error sending file to client");
		return -1;
	}

	readCounter = 0;
	while((readCounter = read(fd, buffer, MAX_BUFFER)) > 0)
	{
		writeCounter = 0;
		bufptr = buffer;

		while (writeCounter < readCounter)
		{
			readCounter -= writeCounter;
			bufptr += writeCounter;
			writeCounter = write(sd, bufptr, readCounter);

			if (writeCounter == -1)
			{
				myprint("Could not write file to client!");
				return -1;
			}
		}
	}
	return 0;
}

int handleClient(int fd)
{
	char * filename;
	char * request;
	char buffer[MAX_BUFFER];
	char * bufPtr;
	int returnValue;

	request = readLongLineUntil(fd, "\r\n\r\n");
	if (request == (char *) -1)
	{
		return -1;
	}
	
	if (debug) printf("readed from client(%s)\n", request);
	filename = getFilenameFromRequest(request);
	if (debug) printf("getFilenameFromRequest(): %i\n", (int)filename);
	if (filename == (char *) -1)
	{
		if (debug) printf("getFilenameFromRequest(): %i\n", (int)*filename);
		return -1;
	}
	if (debug) printf("client requested filename(%s)\n", filename);

	if (checkPerms(filename) == 0)
	{
		snprintf(buffer, sizeof(buffer), "Permissions do not allow to transfer (%s)", filename);
		myprint(buffer);
		bufPtr = "HTTP/1.1 403 Forbidden\r\nContent-type: text/html\r\n\r\n<h1>No permissions.<h1>\r\n";
		write(fd, bufPtr, strlen(bufPtr));
		return -1;
	}

	returnValue = isDir(filename);
	if (returnValue == 1)
	{
		snprintf(buffer, sizeof(buffer), "Listing directory '%s'.", filename);
		myprint(buffer);
		myLs(fd, filename);
	} else if (returnValue == -1) {
		myWrite(fd, "HTTP/1.1 404 Not Found\r\nContent-type: text/html\r\n\r\n<h1>No such file or directory in our cyberspace.</h1>");
	} else {
		return(mySendFile(fd, filename));
	}
	return 0;
}

int serverLoop(int * socket1)
{
	struct sigaction sigactionEnd;

	sigactionEnd.sa_handler = handleEndSignal;

	sigaction(SIGINT, &sigactionEnd, NULL);
	sigaction(SIGTERM, &sigactionEnd, NULL);

	myprint("Server running.");

	for(;;)
	{
		int socket2 = 0;
		unsigned int addrlen;
		struct sockaddr_in xferClient;

		if (recivedCloseSignal == 1)
		{
			if (socket2 > 0) close(socket2);
			return(3);
		}

		addrlen = sizeof(xferClient);

		errno = (int)NULL;
		socket2 = accept(*socket1, (struct sockaddr*)&xferClient, &addrlen);

		if (socket2 == -1)
		{
			if (recivedCloseSignal != 1)
			{
				myprint("Error accept()'ing connection from client:");
				myprint(strerror(errno));
			}
			continue;
		}

		handleClient(socket2);
		close(socket2);
	}
}

uid_t getUid(char * name)
{
	struct passwd * entry;
	char buffer[MAX_BUFFER];

	entry = getpwnam(name);
	if (entry == NULL)
	{
		perror("getpwnam()");
		snprintf(buffer, sizeof(buffer), "can't find user named '%s'", name);
		fprintf(stderr, buffer);
		exit(1);
	}
	return entry->pw_uid;
}

int main(int argc, char *argv[])
{
	int socket1;
	struct sockaddr_in xferServer;
	int returnStatus;
	int c;
	char buffer[MAX_BUFFER];
	char * chrootDir = NULL;
	char * runAs = NULL;

	xferServer.sin_port = htons(defaultPort);

	while((c = getopt(argc, argv, "sdhp:c:u:")) != -1)
	{
		switch (c)
		{
			case 's':
				iAmDaemon = 1;
				break;
			case 'd':
				debug = 1;
				break;
			case 'p':
				xferServer.sin_port = anyPort_to_int(optarg);
				break;
			case 'c':
				chrootDir = optarg;
				break;
			case 'u':
				runAs = optarg;
				break;
			default:
				usage(argv[0]);
				break;
		}
	}

	if (debug)
	{
		printf("port: %i\n", ntohs(xferServer.sin_port));
	}

	if (iAmDaemon) openlog(MYNAME, LOG_PID, LOG_DAEMON);
	snprintf(buffer, MAX_BUFFER, "Running %s in %s mode. Will listen on port %i.", MYNAME, iAmDaemon ? "daemon" : "standard", ntohs(xferServer.sin_port));
	myprint(buffer);

	/* utworz gniazdo */
	errno = (int)NULL;
	socket1 = socket(AF_INET, SOCK_STREAM, 0);

	if (socket1 == -1)
	{
		perror("socket()");
		exit(1);
	}

	/* przypisz adres gniazdu */
	xferServer.sin_family = AF_INET;
	xferServer.sin_addr.s_addr = INADDR_ANY;
	/*
	errno = (int)NULL;
	returnStatus = bind(socket1, (struct sockaddr*)&xferServer, sizeof(xferServer));

	if (returnStatus == -1)
	{
		perror("connect()");
		exit(1);
	}
*/
	errno = (int)NULL;
	returnStatus = listen(socket1, 5);

	if (returnStatus == -1)
	{
		perror("listen()");
		exit(1);
	}
	if (chrootDir != NULL)
	{
		if (chroot(chrootDir) == -1)
		{
			fprintf(stderr, "can't chroot\n");
			perror("chroot()");
		}
	}

	if (runAs != NULL)
		if (setuid(getUid(runAs)) == -1)
		{
			perror("setuid()");
			blad("could not setuid() to a given user");
		}

        if (iAmDaemon)
        {
                if (fork() == 0) /* child */
                {
                        writePid();
                        fclose(stdin);
                        fclose(stderr);
                        fclose(stdout);
                        chdir("/");
                        serverLoop(&socket1);
                        snprintf(buffer, MAX_BUFFER, "%s shutting down", MYNAME);
                        myprint(buffer);
                } else {
                        _exit(0);
                }
        } else {
                writePid();
                serverLoop(&socket1);
                snprintf(buffer, MAX_BUFFER, "%s shutting down", MYNAME);
                myprint(buffer);
        }

	close (socket1);
	return 0;
}
