/* Upput.c - command line server
   by zerucha@shell.portal.com

   TODO:
   prevent accessing parent directories (GET /../..)

   0.8b
   testing user certs
   other fixes

   0.8
   -v for logging the get/put request

   0.7c
   directories
   cleanup for errors
   first alpha
 */

#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <arpa/nameser.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <resolv.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/fcntl.h>

#ifdef ENABLE_DIRS
#include <dirent.h>
DIR                *dfile;
struct dirent      *dent;

#ifndef NAME_MAX
#define NAME_MAX 64
#endif
#endif

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif

#ifdef SOCKS
void                SOCKSinit(char *c);

#endif

#ifdef USE_SSL
#include "ssl.h"
#include "pem.h"
#endif

#define XBUFSIZ 32768
#define TBUFSIZ 4096
#define SADR (struct sockaddr *)

char                mimetab[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

void                usage()
{
	fprintf(stderr, "usage: upput [options] \n"
		"\t-a ip.address           use this address"
		"\t-d                      debug (print more messages)\n"
		"\t-l path/                prepend to posted filenames, normally a path\n"
		"\t-p port                 Use specific port\n"
		"\t-t content/type         use content (MIME) type when transmitting\n"
		"\t-u                      Require authentication with u:p from ~/.upputauth\n"
	     "\t-v                      Log the request headers to stderr\n"
	     "\t-z                      strip headers from POSTed files\n");
#ifdef USE_SSL
	fprintf(stderr, "\t-s                      use secure socket layer\n");
	fprintf(stderr, "\t-S                      use ssl and ask for cert\n");
#endif
	exit(-1);
}

static char         contbuf[TBUFSIZ];
static char         xferbuf[XBUFSIZ];
static char         headbuf[4096];

int                 net;
int                 debugflag = 0;

#ifdef USE_SSL
int                 sslflag = 0;
int                 sslverify(int ok, X509 * subc, X509 * issc, int depth, int err)
{
	char               *cp;
	char                cbuf[64];

	if (!debugflag && ok == 1 && err < 2)
		return (ok);

	fprintf(stderr, "SSL CERTIFICATE STATUS: ok=%d depth=%d err=%s\n", ok, depth, X509_cert_verify_error_string(err));
	if (issc)
		fprintf(stderr, "Issuer:  %s\n", cp = X509_NAME_oneline(X509_get_issuer_name(issc))), free(cp);

	fprintf(stderr, "Subject: %s\n", cp = X509_NAME_oneline(X509_get_subject_name(subc)));
	free(cp);
	strcpy(cbuf, subc->cert_info->validity->notBefore);
	fprintf(stderr, "Valid From: %2.2s/%2.2s/%2.2s %2.2s:%2.2s:%2.2s %s\n", cbuf, &cbuf[2], &cbuf[4], &cbuf[6], &cbuf[8], &cbuf[10], &cbuf[12]);
	strcpy(cbuf, subc->cert_info->validity->notAfter);
	fprintf(stderr, "     Until: %2.2s/%2.2s/%2.2s %2.2s:%2.2s:%2.2s %s\n", cbuf, &cbuf[2], &cbuf[4], &cbuf[6], &cbuf[8], &cbuf[10], &cbuf[12]);
	fprintf(stderr, "-------\n");
	return (1 | ok);
}
#endif

#ifdef LINUX
#include <sys/signal.h>
__sighandler_t      sighandle(int signum, __sighandler_t h)
{
	close(net);
	exit(-1);
}

#endif

int                 main(int argc, char *argv[])
{
	char                authstr[256];
	char                path[1024];
	char                postpath[1024];
	char               *ip, *op;
	char                contype[128];
	int                 n, c, argp;
	int                 net2;
	int                 siteport = 0;
	int                 headers = 1;
	time_t              now;
	struct timeval      tv;
	fd_set              fds;
	struct sockaddr_in  sin;
	unsigned long       temp;
	FILE               *fp = NULL;

	int                 logflag = 0;
	int                 tfile;

	struct stat         filestats;

#ifdef USE_SSL
	SSL_CTX            *ssl_ctx = NULL;
	SSL                *ssl_handle = NULL;

#endif

       /* set sane defaults */
	authstr[0] = 0;
	memset((char *) &sin, 0, sizeof(sin));
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_family = AF_INET;	/* host->h_addrtype; */
	strcpy(postpath, "./");
	strcpy(contype, "application/octet-stream");

	argp = 1;
	while (argp < argc) {
		if (argv[argp][0] == '-')
			switch (argv[argp][1]) {
			case 'a':
				sin.sin_addr.s_addr = inet_addr(argv[++argp]);
				argp++;
				break;
			case 'd':
				argp++;
				debugflag = 1;
				break;
			case 'h':
				usage();
				exit(0);
			case 'l':
				argp++;
				strcpy(postpath, argv[argp++]);
				break;
			case 'p':
				argp++;
				siteport = atoi(argv[argp++]);
				break;
#ifdef USE_SSL
			       /* set ssl flag */
			case 's':
				argp++;
				sslflag = 1;
				break;
			       /* set ssl flag for user cert verify */
			case 'S':
				argp++;
				sslflag = 2;
				break;
#endif
			case 't':
				argp++;
				strcpy(contype, argv[argp++]);
				break;
			case 'u':
			       /* create authorization string */
				argp++;
				ip = getenv("HOME");
				sprintf(authstr, "%s/.upputauth", ip);
				fp = fopen(authstr, "r");
				if (fp == NULL) {
					fprintf(stderr, "file %s not found\n", authstr);
					exit(-1);
				}
				fscanf(fp, "%s", authstr);
				fclose(fp);

			       /* convert into MIME bin64 */
#if 0
				ip = argv[argp++];
#else
				ip = authstr;
#endif
				op = contbuf;
				c = strlen(ip);

				for (temp = 0; temp < c; temp += 3) {
					*op++ = mimetab[*ip >> 2];
					*op++ = mimetab[((*ip << 4) & 0x30) | ((ip[1] >> 4) & 0x0f)];
					*op++ = mimetab[((ip[1] << 2) & 0x3c) | ((ip[2] >> 6) & 0x03)];
					*op++ = mimetab[ip[2] & 0x3f];
					ip += 3;
				}

				if (temp >= c + 1)
					op[-1] = '=';
				if (temp == c + 2)
					op[-2] = '=';
				*op = '\0';

			       /* create full auth string */
				strcpy(authstr, "Authorization: Basic ");
				strcat(authstr, contbuf);
				strcat(authstr, "\r\n");

				break;
			case 'v':
				argp++;
				logflag = 1;
				break;
			case 'z':
				argp++;
				headers = 0;
				break;
			default:

				usage();
			}
		else
			usage();
	}

#ifdef SOCKS
	SOCKSinit(argv[0]);
#endif
	sin.sin_port = htons(siteport);
	if (debugflag)
		fprintf(stderr, "Port: %d\n", htons(sin.sin_port));

       /* enable the secure socket layer */
#ifdef USE_SSL
	if (sslflag) {
		if (debugflag)
			fprintf(stderr, "Using SSL%s\n", sslflag == 2 ? " with client cert" : "");

		ssl_ctx = SSL_CTX_new();
		X509_set_default_verify_paths(ssl_ctx->cert);
		SSL_CTX_use_certificate_file(ssl_ctx, SSLCERT, SSL_FILETYPE_PEM);
		SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, SSLCERT, SSL_FILETYPE_PEM);
	}
#endif

	net = socket(AF_INET, SOCK_STREAM, 0);
	if (net < 0)
		return net;

	if (setsockopt(net, SOL_SOCKET, SO_REUSEADDR, &n, sizeof(int)) < 0)
		                    exit(-1);

	if (bind(net, SADR & sin, sizeof(sin)) < 0)
		exit(-1);

	n = sizeof(sin);
	getsockname(net, SADR & sin, &n);
	fprintf(stderr, "Using TCP Port %u\n", htons(sin.sin_port));
#ifdef LINUX
	signal(SIGINT, (__sighandler_t) sighandle);
#endif
	listen(net, 1);

	for (;;) {
		fprintf(stderr, "Waiting for connection...\n");
		net2 = accept(net, SADR & sin, &n);
#ifdef USE_SSL
		if (sslflag) {
			if (ssl_handle != NULL)
				SSL_free(ssl_handle);
			ssl_handle = SSL_new(ssl_ctx);
			SSL_set_fd(ssl_handle, net2);
			SSL_set_verify(ssl_handle, sslflag == 2 ? SSL_VERIFY_PEER : SSL_VERIFY_NONE, sslverify);
			SSL_accept(ssl_handle);
			SSL_set_read_ahead(ssl_handle, 1);
		}
#endif
		fprintf(stderr, "Connect from %s:%u\n",
			inet_ntoa(sin.sin_addr),
			htons(sin.sin_port)
		  );

		xferbuf[0] = 0;
		headbuf[0] = 0;
		c = 0;
		while (NULL == strstr(xferbuf, "\r\n\r\n")) {
			FD_ZERO(&fds);
			FD_SET(net2, &fds);
			tv.tv_sec = 15;
			tv.tv_usec = 0;
#ifdef USE_SSL
			if (sslflag) {
				if (!SSL_pending(ssl_handle)) {
					n = select(net2 + 1, &fds, NULL, NULL, &tv);
					if (n <= 0)
						break;
				}
				n = SSL_read(ssl_handle, &xferbuf[c], XBUFSIZ - c);
			}
			else
#endif
			{
				n = select(net2 + 1, &fds, NULL, NULL, &tv);
				if (n <= 0)
					break;
				n = read(net2, &xferbuf[c], XBUFSIZ - c);
			}
			if (n <= 0)
				break;
			c += n;
			xferbuf[c] = 0;
		}

		ip = strstr(xferbuf, "\r\n\r\n");
		if (ip != NULL) {
			if (logflag) {
				*ip = 0;
				fprintf(stderr, "Got Header -----\n%s\n\n", xferbuf);
				*ip = '\r';
			}
		}
		else {
			fprintf(stderr, "Connection broken before header received\n");
			headbuf[0] = 0;
			xferbuf[0] = 0;
		}

		if (!strncmp("GET /", xferbuf, 5) ||
		    !strncmp("POST /", xferbuf, 6)) {

			if (strlen(authstr) && strstr(xferbuf, authstr) == NULL) {
				fprintf(stderr, "authorization failed\n");

				sprintf(headbuf,
					"HTTP/1.0 401 Access Denied\r\n"
				"WWW-Authenticate: Basic realm=\"upput\"\r\n"
					"Content-Length: 24\r\n\r\n"
					"Error: Access is Denied.");
				xferbuf[0] = 0;
			}
		}
		else {
			fprintf(stderr, "Unknown Request %40.40s\n", xferbuf);
			sprintf(headbuf, "HTTP/1.0 400 Bad Request\r\n\r\n");
		}
		if (!strncmp("GET /", xferbuf, 5)) {
			strncpy(path, &xferbuf[5], 1024);
			op = strstr(path, " ");
			*op = 0;
			if (path[0] == 0)
				strcpy(path, ".");

			if (stat(path, &filestats) < 0)
				sprintf(headbuf, "HTTP/1.0 404 Not Found\r\n\r\n");
			else {
#ifdef ENABLE_DIRS
				if (S_ISDIR(filestats.st_mode)) {

					dfile = opendir(path);
					if (dfile == NULL)
						sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
					else {
						n = 0;
						sprintf(&xferbuf[n], "<HTML><HEAD><TITLE>Directory of %s</TITLE></HEAD><BODY><UL>\r\n", path);
						n = strlen(xferbuf);
						for (;;) {
							dent = readdir(dfile);
							if (dent == NULL)
								break;
							sprintf(&xferbuf[n], "<LI><A href=/%s/%s>%s</A>\r\n", path, dent->d_name, dent->d_name);
							n = strlen(xferbuf);
							if (n + NAME_MAX + 64 > XBUFSIZ)
								break;
						}
						sprintf(&xferbuf[n], "</UL></BODY></HTML>\r\n");
						n = strlen(xferbuf);
						closedir(dfile);
					}

					time(&now);
					sprintf(headbuf,
						"HTTP/1.0 200 OK\r\n"
						"Date: %s"
						"Server: UPPut 0.6\r\n"
						"Content-type: text/html\n"
						"Last-Modified: %s"
						"Content-length: %d\r\n\r\n",
						ctime(&now), ctime(&(filestats.st_mtime)), n);

					if (debugflag)
						fprintf(stderr, "Sending dir :\n%s", headbuf);
					else
						fprintf(stderr, "Sending dir : %s\n", path);
				       /* write the header to the remote site */
					headbuf[0] = 0;
#ifdef USE_SSL
					if (sslflag) {
						SSL_write(ssl_handle, headbuf, strlen(headbuf));
						SSL_write(ssl_handle, xferbuf, n);
					}
					else
#endif
					{
						write(net2, headbuf, strlen(headbuf));
						write(net2, xferbuf, n);
					}
				}
				else
#endif /* ENABLE_DIRS */
				{
					tfile = open(path, O_RDONLY);
					if (tfile < 0)
						sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
					else {
						time(&now);
						sprintf(headbuf,
							"HTTP/1.0 200 OK\r\n"
							"Date: %s"
						     "Server: UPPut 0.6\r\n"
							"Content-type: %s\n"
							"Last-Modified: %s"
						"Content-length: %d\r\n\r\n",
							ctime(&now), contype, ctime(&(filestats.st_mtime)), (int) filestats.st_size);

						if (debugflag)
							fprintf(stderr, "Sending:\n%s", headbuf);
						else
							fprintf(stderr, "Sending: %s\n", path);
					       /* write the header to the remote site */
#ifdef USE_SSL
						if (sslflag)
							SSL_write(ssl_handle, headbuf, strlen(headbuf));
						else
#endif
							write(net2, headbuf, strlen(headbuf));
						headbuf[0] = 0;
						n = XBUFSIZ;
						for (;;) {
							n = read(tfile, xferbuf, XBUFSIZ);
							if (n <= 0)
								break;
#ifdef USE_SSL
							if (sslflag)
								SSL_write(ssl_handle, xferbuf, n);
							else
#endif
								write(net2, xferbuf, n);
						}
						close(tfile);
					}
				}
			}
		}

		if (!strncmp("POST /", xferbuf, 6)) {
			strcpy(path, postpath);
			strncat(path, &xferbuf[6], 1024);
			op = strstr(path, " ");
			*op = 0;

			tfile = open(path, O_CREAT | O_WRONLY | O_EXCL, 0777);
			if (tfile < 0)
				sprintf(headbuf, "HTTP/1.0 403 Forbidden\r\n\r\n");
			else {
				n = c;
				ip = 6 + strstr(xferbuf, "ength:");
				sscanf(ip, " %d", &c);
				fprintf(stderr, "Receiving: %s (%d)\n", path, c);
				ip = 4 + strstr(xferbuf, "\r\n\r\n");
				c += ip - xferbuf;
				c -= n;
				if (headers)
					write(tfile, xferbuf, n);
				else
					write(tfile, ip, n - (ip - xferbuf));

				while (c) {
					FD_ZERO(&fds);
					FD_SET(net2, &fds);
					tv.tv_sec = 15;
					tv.tv_usec = 0;
#ifdef USE_SSL
					if (sslflag) {
						if (!SSL_pending(ssl_handle)) {
							n = select(net2 + 1, &fds, NULL, NULL, &tv);
							if (n <= 0)
								break;
						}
						n = SSL_read(ssl_handle, &xferbuf[c], XBUFSIZ - c);
					}
					else
#endif
					{
						n = select(net2 + 1, &fds, NULL, NULL, &tv);
						if (n <= 0)
							break;
						n = read(net2, &xferbuf[c], XBUFSIZ - c);
					}
					if (n <= 0)
						break;
					write(tfile, xferbuf, n);
					c -= n;
				}
				close(tfile);
				sprintf(headbuf, "HTTP/1.0 202 Accepted\r\n\r\n");
			}

		}

		if (headbuf[0]) {
#ifdef USE_SSL
			if (sslflag)
				SSL_write(ssl_handle, headbuf, strlen(headbuf));
			else
#endif
				write(net2, headbuf, strlen(headbuf));
		}

#ifdef USE_SSL
		if (sslflag) {
			SSL_free(ssl_handle);
			ssl_handle = NULL;
		}
#endif
		close(net2);

	}
	return 0;
}
