#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <unistd.h>
#include <sys/stat.h>

#define VERSION "0.10"

char *password;

#define PICTURE_ROOT		"/var/www/pictures"
#define SELECT_TIMEOUT_SEC	90

#define SNEAK_OK		0
#define SNEAK_ERROR	-1

int handle_connection(int clientfd, char *dir_prefix)
{
	char directory[256], filename[256], buff[1024];
	long size, resp_len, total_read = 0;

	u_char *filedata = NULL;
	FILE *fp = NULL;

	mode_t mode;
	fd_set read_set;
	struct timeval timeout = {SELECT_TIMEOUT_SEC , 0};

	// get password
	resp_len = recv(clientfd, buff, 256, 0);
	if(resp_len < 1)
	{
		close(clientfd);
		return SNEAK_ERROR;
	}

	buff[resp_len] = 0;

	// check password
	if(strcmp(buff, password) != 0)
	{
		// send response
		send(clientfd, "err", 3, 0);
		close(clientfd);
		return SNEAK_ERROR;
	}

	// send response
	send(clientfd, "ok", 2, 0);
	
	// get directory name
	resp_len = recv(clientfd, buff, 256, 0);
	if(resp_len < 1)
	{
		close(clientfd);
		return SNEAK_ERROR;
	}

	buff[resp_len] = '\0';

	strncpy(directory, dir_prefix, 255);

	strncat(directory, buff, 255 - strlen(directory));
	strncat(directory, "/", 255 - strlen(directory));

//	printf("directory: %s\n", directory);

	mode = S_IXOTH | S_IROTH | S_IRGRP | S_IXGRP | S_IRUSR | S_IXUSR | S_IWUSR;
	if(mkdir(directory, mode) < 0)
	{
		// error - directory exists
//		send(clientfd, "err", 3, 0);
//		close(clientfd);
//		return SNEAK_ERROR;
	}

	// send response
	send(clientfd, "ok", 2, 0);

	while(1)
	{
		total_read = 0;
		filename[0] = '\0';
		strncpy(filename, directory, 255);

		// get filename
		resp_len = recv(clientfd, buff, 256, 0);
		if(resp_len < 1)
		{
			// client closed connection - no more files
			close(clientfd);
			break;
		}

		buff[resp_len] = '\0';
		strncat(filename, buff, 255 - strlen(filename));

		// check if file exists
		if((fp = fopen(filename, "r")) == NULL)
		{
			fp = fopen(filename, "wb");
			if(fp == NULL)
			{
				// send response - couldn't open file for writing
				send(clientfd, "err", 3, 0);
				//close(clientfd);
				//return SNEAK_ERROR;
				continue;
			}

			// send response
			send(clientfd, "ok", 2, 0);

			// get file size
			resp_len = recv(clientfd, (char*)&size, 4, 0);
			if(resp_len < 4)
			{
				fclose(fp);
				close(clientfd);
				return SNEAK_ERROR;
			}
		}
		else
		{
			// send response
			send(clientfd, "ok", 2, 0);

			// file exists - check file size
			resp_len = recv(clientfd, (char*)&size, 4, 0);
			if(resp_len < 4)
			{
				fclose(fp);
				close(clientfd);
				return SNEAK_ERROR;
			}

			/* get file size */
			fseek(fp, 0, SEEK_END);
			if(ftell(fp) < size)
			{
				fclose(fp);
				fp = fopen(filename, "wb");
				if(fp == NULL)
				{
					// send response - couldn't open file for writing
					send(clientfd, "err", 3, 0);
					//close(clientfd);
					//return SNEAK_ERROR;
					continue;
				}

//				printf("filename %s exists but is smaller, overwriting.\n", filename);
			}
			else
			{
				// send response - don't upload, file already exists
				send(clientfd, "err", 3, 0);
//				printf("file %s already exists, skipping\n", filename);
				fclose(fp);
				//close(clientfd);
				//return SNEAK_ERROR;
				continue;
			}
		}

		// send response - we're ready for upload
		send(clientfd, "ok", 2, 0);

		filedata = (u_char*)malloc(size * sizeof(u_char));
		if(filedata == NULL) {
			// memory error
//			printf("couldn't allocate %d bytes of memory.\n", size);
			fclose(fp);
			close(clientfd);
			return SNEAK_ERROR;
		}

		// close the file while we are receiving the data
		// so we don't lock it if we crash
		fclose(fp);

//		printf("receiving: %s...", buff);
		fflush(stdout);

		// read file data
		while((resp_len = read(clientfd, buff, 1)) > 0)
		{
			FD_ZERO(&read_set);
			FD_SET(clientfd, &read_set);

			memcpy(filedata + total_read, buff, resp_len);
			total_read += resp_len;
			if(total_read == size)
				break;

			// check for read
/*			if(select(clientfd+1, &read_set, NULL, NULL, &timeout) < 1)
			{
				// timeout
//				printf("select failed.\n");
				break;
			}*/
		}

		if(total_read != size)
		{
//			printf("didn't get all of the picture: %d of %d bytes.\n", total_read, size);
			free(filedata);
			fclose(fp);
			close(clientfd);
			return SNEAK_ERROR;
		}

//			printf("trying to open %s\n", filename);
		fp = fopen(filename, "wb");
		if(fp == NULL)
		{
//			printf("failed.\n");
			// send response - couldn't open file for writing
			send(clientfd, "err", 3, 0);
			//close(clientfd);
			//return SNEAK_ERROR;
			continue;
		}

		// send response
		send(clientfd, "ok", 2, 0);

		fwrite(filedata, 1, size, fp);
		fclose(fp);
		free(filedata);

//		printf("done.\n");
	}

	close(clientfd);

	return SNEAK_OK;
}

int main(int argc, char *argv[])
{
	int sockfd, clientfd;
	struct sockaddr_in addr, remote_addr;
	int yes=1, sin_size;
	int listenport;
	char dir_prefix[256];

	if(argc < 3)
	{
		printf("photoreceiver version %s\n", VERSION);
		printf("copyright 2001-2004 donn morrison\n");
		printf("usage: photoreceiver <listen port> <access password> [<picture_root>]\n");
		exit(1);
	}
	else if(argc == 4)
		strncpy(dir_prefix, argv[3], 255);
	else
		strncpy(dir_prefix, PICTURE_ROOT, 255);

	// add trailing slash if it doesn't exist
	if(dir_prefix[strlen(dir_prefix)-1] != '/' && strlen(dir_prefix) < 256)
	{
		dir_prefix[strlen(dir_prefix)+1] = '\0';
		dir_prefix[strlen(dir_prefix)] = '/';
	}

	listenport = atoi(argv[1]);
	password = argv[2];

//	printf("server port: %d\n", listenport);
//	printf("picture root: %s\n", dir_prefix);
	
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror("server: socket");
		exit(1);
	}
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons((u_short)listenport);
	addr.sin_addr.s_addr = htonl(INADDR_ANY);
	
	if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) < 0)
	{
		perror("setsockopt");
		exit(1);
	}
	
	if(bind(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) < 0)
	{
		perror("bind");
		exit(1);
	}
	
	if(listen(sockfd, 1) < 0)
	{
		perror("listen");
		exit(1);
	}
	
	sin_size = sizeof(struct sockaddr_in);

//	printf("waiting for connection...");

	while(1)
	{
		fflush(stdout);
		clientfd = accept(sockfd, (struct sockaddr_in*)&remote_addr, &sin_size);
		if(clientfd > 0)
		{
//			printf("connect accepted from %s.\n", inet_ntoa(remote_addr.sin_addr));
			if(!fork())
			{
				close(sockfd);
				handle_connection(clientfd, dir_prefix);
				close(clientfd);
				exit(0);
			}
			close(clientfd);
		}
	}

	close(sockfd);

	return 0;
}
