#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.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 MAXBUF		1024
#define MYNAME		"serwerPliki"

void myprint(char * message)
{
	if (iAmDaemon)
	{
		syslog(LOG_INFO, "%s", message);
	} else {
		printf("%s\n", message);
	}
}

short int isDir(char * path)
{
	char buffer[MAXBUF];
	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[MAXBUF];
	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 atoi(portStart);
}

void usage(char * name)
{
	fprintf(stderr, "Usage: %s [-h] [-c DIR] [-d] [-s] [-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, "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[MAXBUF];
	FILE * pidFile;

	snprintf(buffer, MAXBUF, "./%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, MAXBUF, "%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 myLs(int fd, char * path)
{
	DIR * dd;
	char buffer[MAXBUF];
	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);
		return 0;
	}

	errno = (int)NULL;
	while ((metadata = readdir(dd)) != NULL)
	{
		errno = (int)NULL;
		snprintf(buffer, sizeof(buffer), "%s\n", 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;
	}
	return ret;
}

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

	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[MAXBUF];
	snprintf(buffer, MAXBUF, "Recived signal %i. Program will shut down after ending current connections. Please wait.", s);
	myprint(buffer);
	recivedCloseSignal = 1;
}

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

	sigactionEnd.sa_handler = handleEndSignal;

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

	myprint("Server running.");

	for(;;)
	{
		int fd;
		int i, readCounter, writeCounter;
		char* bufptr;
		char buf[MAXBUF];
		char filename[MAXBUF];
		int addrlen;
		struct sockaddr_in xferClient;
		int socket2;

		if (recivedCloseSignal == 1)
		{
			close(fd);
			close(socket2);
			exit(3);
		}

		/* czekaj na klienta */
		addrlen = sizeof(xferClient);

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

		if (recivedCloseSignal == 1)
		{
			close(fd);
			close(socket2);
			exit(3);
		}

		if (socket2 == -1)
		{
			if (errno != EINTR) myprint("Could not accept connection! Exiting!");
			exit(1);
		}

		/* pobierz nazwe pliku */
		i = 0;

		errno = (int)NULL;
		if ((readCounter = read(socket2, filename + i, MAXBUF)) > 0)
		{
			i += readCounter;
		}

		if (readCounter == -1)
		{
			if (errno == EINTR) exit(0);
			snprintf(buf, sizeof(buf), "Could not read filename from socket: read(): %s", strerror(errno));
			myprint(buf);
			close(socket2);
			continue;
		}

		filename[i] = '\0';

		if (!checkPerms(filename))
		{
			snprintf(buf, sizeof(buf), "Permissions do not allow to transfer %s", filename);
			myprint(buf);
			snprintf(buf, sizeof(buf), "ERROR Filesystem permissions do not allow to transfer file.\n");
			write(socket2, buf, strlen(buf));
			close(socket2);
			continue;
		}

		returnValue = isDir(filename);
		if (returnValue == 1)
		{
			snprintf(buf, sizeof(buf), "Listing directory '%s'.", filename);
			myprint(buf);

			errno = (int)NULL;
			if (write(socket2, "DIR\n", 4) == -1)
			{
				snprintf(buf, sizeof(buf), "Could not send status response: write(): %s", strerror(errno));
				myprint(buf);
				close(socket2);
				continue;
			}
			myLs(socket2, filename);
		} else if (returnValue == -1) {
			close(socket2);
			continue;
		} else {
			snprintf(buf, sizeof(buf), "Reading file '%s'.", filename);
			myprint(buf);

			/* otworz plik do czytania */
			errno = (int)NULL;
			fd = open(filename, O_RDONLY);

			if (fd == -1)
			{
				snprintf(buf, sizeof(buf), "Could not open file for reading: open(): %s", strerror(errno));
				myprint(buf);
				snprintf(buf, sizeof(buf), "ERROR Could not open file.\n");
				write(socket2, buf, strlen(buf));
				close(socket2);
				continue;
			}

			errno = (int)NULL;
			if (write(socket2, "OK\n", 3) == -1)
			{
				snprintf(buf, sizeof(buf), "Could not send status response: write(): %s", strerror(errno));
				myprint(buf);
				close(socket2);
				continue;
			}

			readCounter = 0;
			/* czytaj plik i przesylaj do klienta */
			while((readCounter = read(fd, buf, MAXBUF)) > 0)
			{
				writeCounter = 0;
				bufptr = buf;

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

					if (writeCounter == -1)
					{
						myprint("Could not write file to client!");
						close(socket2);
						continue;
					}
				}
			}
		}
		close(socket2);
		close(fd);
	}
}

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

	xferServer.sin_port = defaultPort;

	while((c = getopt(argc, argv, "sdhp:c:")) != -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;
			default:
				usage(argv[0]);
				break;
		}
	}

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

	if (iAmDaemon) openlog(MYNAME, LOG_PID, LOG_DAEMON);
	snprintf(buffer, MAXBUF, "Running %s in %s mode. Will listen on port %i.", MYNAME, iAmDaemon ? "daemon" : "standard", 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 (iAmDaemon)
        {
                if (fork() == 0) /* child */
                {
                        writePid();
                        fclose(stdin);
                        fclose(stderr);
                        fclose(stdout);
                        chdir("/");
                        serverLoop(&socket1);
                        snprintf(buffer, MAXBUF, "%s shutting down", MYNAME);
                        myprint(buffer);
                } else {
                        _exit(0);
                }
        } else {
                writePid();
                serverLoop(&socket1);
                snprintf(buffer, MAXBUF, "%s shutting down", MYNAME);
                myprint(buffer);
        }

	close (socket1);
	return 0;
}
