/* Upget.c - command line browser
   by zerucha@shell.portal.com

   0.8b
   more ssl cert handling (grab certs)

   0.8a
   nntp: and auth fixes for ftp
   ssl cert handling

   0.8
   SSLeay 5.1 support (peer)
   added file: url
   some work for large POST files

 */

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

#ifdef SOCKS
void                SOCKSinit(char *c);

#endif

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

#ifndef INADDR_NONE
#define INADDR_NONE -1
#endif

#define XBUFSIZ 65536
#define TBUFSIZ 4096

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

void                usage()
{
	fprintf(stderr, "usage: upget URL [options] \n"
		"\t-a string               Use this for User-Agent\n"
	    "\t-c formdata             POST Content message with formdata\n"
		"\t-g formdata             GET with appended form data\n"
		"\t                        (for formdata, use - for stdin)\n"
	"\t-d                      debug (print submitted text to stderr)\n"
		"\t-i interval             Repeat at an interval\n"
		"\t-l                      for ftp, get a directory\n"
	   "\t-t seconds              set timeout in seconds (default=60)\n"
		"\t-u                      Authenticate with userid from ~/.upgetauth\n"
	    "\t-z                      strip HTTP header (experimental)\n");
#ifdef USE_SSL
	fprintf(stderr,
		"\t-s                      use secure socket layer\n"
		"\t-S file                 use SSL, grab cert to pem file\n"
	     "\t-x                      use SSL ignoring bad certificates\n"
		"\t-v /path/cert.pem       Sent cert from file if asked\n");
#endif
	exit(-1);
}

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

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");
	if (sslflag >= 2)
		return 1;
	return (ok);
}
#endif

int                 main(int argc, char *argv[])
{
	char                authstr[256], user[256];
	char                protocol[16], site[64], path[1024], reqtype[16];
	char                agent[128], formtype[64];
	char               *ip, *op, *ap;
	int                 c, argp;
	int                 net;
	int                 siteport;
	int                 timeout;
	int                 headers = 1;
	struct timeval      tv;

	int                 putsiz;
	struct sockaddr_in  sin;
	unsigned long       temp;
	struct hostent     *host;
	struct servent     *serv;

	int                 formflag = 0;
	int                 listflag = 0;
	int                 interval = 0;
	FILE               *ff = NULL;
	FILE               *fp = NULL;

	int                 netd = (-1);
	char               *a, *p;
	int                 val[6];

#ifdef USE_SSL
	SSL_CTX            *ssl_ctx = NULL;
	SSL                *ssl_handle = NULL, *ssl_dhandle = NULL;
	char               *user_cert = NULL;
	FILE               *certfile = NULL;

#endif
	fd_set              fds;

	if (argc == 1)
		usage();

       /* set sane defaults */

	strcpy(protocol, "http");
	strcpy(reqtype, "GET");
	strcpy(path, "/index.html");
	strcpy(agent, "Mozilla/3.0 (Compatible; U; Upget 0.9)");
	strcpy(formtype, "x-www-form-urlencoded");
	siteport = 80;
	authstr[0] = 0;
	user[0] = 0;
	timeout = 30;	/* in seconds */

       /* */
       /* parse URL - get protocol */
       /* */

	ap = argv[1];

       /* extract protocol string */

	if ((ip = strstr(ap, "://")) != NULL) {
		siteport = 0;
		ap = ip + 3;
		strncpy(protocol, argv[1], 15);
		op = strstr(protocol, "://");
		*op = 0;
#ifdef USE_SSL
		if (!strcmp(protocol, "https"))
			sslflag = 1;
#endif
	}

	if (!strncmp(ap, "//", 2))
		ap++, ap++;

       /* extract site name (or IP:port) and path */
	strncpy(site, ap, 63);
	if ((ip = strstr(ap, "/")) != NULL) {
		strcpy(path, ip);
		op = strstr(site, "/");
		*op = 0;

	}
       /* look for explicit port number */
	if ((ip = strstr(site, ":")) != NULL) {
		*ip++ = 0;
		siteport = atoi(ip);
	}

       /* */
       /* parse command line options */
       /* */

	argp = 2;
	while (argp < argc) {

		if (argv[argp][0] == '-')
			switch (argv[argp++][1]) {
			case 'a':
			       /* Some sites aren't cooperative with non-netscape */
				strcpy(agent, argv[argp++]);
				break;
			       /* form content - type */
			case 'C':
				strcpy(formtype, argv[argp++]);
				break;
			       /* form content - file */
			case 'c':
				formflag = 2;
				if (!strcmp(argv[argp], "-"))
					ff = stdin;
				else
					ff = fopen(argv[argp], "r");

				if (ff == NULL) {
					fprintf(stderr, "Could not open form data file\n");
					exit(-2);
				}
				strcpy(reqtype, "POST");
				argp++;
				break;
			case 'd':
				debugflag = 1;
				fprintf(stderr, "proto=%s, site=%s, path=%s, port=%s\n", protocol, site, path, ip);
				break;
			       /* whole form */
			case 'f':
				formflag = 1;
				if (!strcmp(argv[argp], "-"))
					ff = stdin;
				else
					ff = fopen(argv[argp], "r");

				if (ff == NULL) {
					fprintf(stderr, "Could not open form file\n");
					exit(-2);
				}
				argp++;
				break;
			case 'g':
				formflag = 3;
				if (!strcmp(argv[argp], "-"))
					ff = stdin;
				else
					ff = fopen(argv[argp], "r");

				if (ff == NULL) {
					fprintf(stderr, "Could not open form data file\n");
					exit(-2);
				}
				strcpy(reqtype, "GET");
				argp++;
				break;
			case 'i':
			       /* get repeat interval */
				interval = atoi(argv[argp++]);
				break;
			case 'l':
				listflag = 1;
				break;
			case 'r':
			       /* copy request type */
				strcpy(reqtype, argv[argp++]);
				break;
#ifdef USE_SSL
			       /* set ssl flag */
			case 's':
				sslflag = 1;
				break;
			case 'S':
				sslflag = 3;
				certfile = fopen(argv[argp++], "a");
				break;
			case 'v':
				user_cert = argv[argp++];
				break;
			case 'x':
				sslflag = 2;
				break;
#endif
			case 't':
			       /* get timeout */
				timeout = atoi(argv[argp++]);
				break;
			case 'u':
			       /* create authorization string */

				ip = getenv("HOME");
				sprintf(authstr, "%s/.upgetauth", ip);
				fp = fopen(authstr, "r");
				for (;;) {
					fscanf(fp, "%s %s", xferbuf, user);
					if (feof(fp)) {
						fprintf(stderr, "Cannot find authorization\n");
						exit(-1);
					}
					if (!strcmp(xferbuf, site))
						break;
				}
				fclose(fp);

			       /* convert into MIME bin64 */
				ip = user;
				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 'z':
				headers = 0;
				break;
			default:
				usage();
			}
		else
			usage();
	}

       /* */
       /* compose HTTP or FTP request */
       /* */

	contbuf[0] = 0;
	if (!strncmp(protocol, "http", 4)) {
		if (formflag == 3) {
			putsiz = fread(&contbuf[1], 1, XBUFSIZ, ff);
			contbuf[putsiz + 1] = 0;
			contbuf[0] = '?';
			fclose(ff);
			ff = NULL;
		}
		sprintf(headbuf,
			"%s %s%s HTTP/1.0\r\n"
			"User-Agent: %s\r\n"
			"%s",
			reqtype, path, contbuf, agent,
			authstr);
	}
	else if (!strncmp(protocol, "nntp", 4)) {
		op = getenv("NNTPSERVER");
		op = strchr(&path[1], '/');
		*op++ = 0;;
		if (user[0] == 0) {
			sprintf(headbuf,
				"GROUP %s\r\n"
				"ARTICLE %s\r\n"
				"QUIT\r\n",
				&path[1], op);
		}
		else {
			strcpy(authstr, user);
			ip = strchr(authstr, ':');
			*ip++ = 0;
			sprintf(headbuf,
				"AUTHINFO USER %s\r\n"
				"AUTHINFO PASS %s\r\n"
				"GROUP %s\r\n"
				"ARTICLE %s\r\n"
				"QUIT\r\n",
				authstr, ip, &path[1], op);
		}
	}
	else if (!strncmp(protocol, "file", 4)) {
		fp = fopen(path, "r");
		if (fp == NULL)
			exit(-1);
		putsiz = 1;
		while (putsiz) {
			putsiz = fread(xferbuf, 1, XBUFSIZ, fp);
			fwrite(xferbuf, 1, putsiz, stdout);
		}
		fclose(fp);
		exit(0);
	}
	else if (!strncmp(protocol, "ftp", 3)) {
		op = path;
		op++;

		if (listflag) {
			strcpy(contbuf, "CWD ");
			strcat(contbuf, op);
			strcat(contbuf, "\r\nLIST");
		}
		else {
			strcpy(contbuf, "RETR ");
			strcat(contbuf, op);
		}

		if (user[0] == 0) {
			sprintf(headbuf,
		       /* get user from user later */
				"USER anonymous\r\n"
				"PASS upget@nowhere.com\r\n"
				"TYPE I\r\n"
				"PASV\r\n"
				"%s\r\n"
				"QUIT\r\n",
				contbuf);
		}
		else {
			strcpy(authstr, user);
			ip = strchr(authstr, ':');
			*ip++ = 0;
			sprintf(headbuf,
		       /* get user from user later */
				"USER %s\r\n"
				"PASS %s\r\n"
				"TYPE I\r\n"
				"PASV\r\n"
				"%s\r\n"
				"QUIT\r\n",
				authstr, ip, contbuf);
		}

	}
	else if (!strncmp(protocol, "gopher", 6)) {
		strcpy(headbuf, &path[1]);
	}
	else if (!strncmp(protocol, "finger", 6)) {
		strcpy(headbuf, &path[1]);
	}
	else
		headbuf[0] = 0;

       /* append any form data to the request */
	putsiz = 0;
	if (formflag == 1) {
		putsiz = fread(contbuf, 1, XBUFSIZ, ff);
		fclose(ff);
		ff = NULL;
	}

	if (formflag == 2) {
		fseek(ff, 0, SEEK_END);
		putsiz = ftell(ff);
		rewind(ff);
	       /* need to use better sizing */
		sprintf(&headbuf[strlen(headbuf)],
			"Content-type: application/%s\n"
			"Content-length: %d\r\n\r\n", formtype, putsiz);
	}
	else
	       /* final linefeed */
		strcat(headbuf, "\r\n");

       /* */
       /* lookup site and port for IP connection */
       /* */
	if (debugflag)
		fprintf(stderr, "%s", headbuf);

	memset((char *) &sin, 0, sizeof(sin));
	temp = inet_addr(site);

	if (temp != INADDR_NONE)
		memcpy(&sin.sin_addr, &temp, sizeof(temp));
	else {
		host = gethostbyname(site);
		if (host == NULL)
			return (-1);
		memcpy((char *) &sin.sin_addr, host->h_addr, host->h_length);
	}
	if (debugflag)
		fprintf(stderr, "Address: %ld.%ld.%ld.%ld\n",
			(htonl(sin.sin_addr.s_addr) >> 24) & 0xff,
			(htonl(sin.sin_addr.s_addr) >> 16) & 0xff,
			(htonl(sin.sin_addr.s_addr) >> 8) & 0xff,
			htonl(sin.sin_addr.s_addr) & 0xff
		  );

	if (siteport)
		sin.sin_port = htons(siteport);
	else {
		serv = getservbyname(protocol, "tcp");
		if (serv == NULL)
			return (-2);
		sin.sin_port = serv->s_port;
	}
	if (debugflag)
		fprintf(stderr, "Port: %d\n", htons(sin.sin_port));

	sin.sin_family = AF_INET;	/* host->h_addrtype; */

#ifdef SOCKS
	SOCKSinit(argv[0]);
#endif

       /* enable the secure socket layer */
#ifdef USE_SSL
	if (sslflag) {
		if (debugflag)
			fprintf(stderr, "Using SSL with cert %s\n", user_cert);
		ssl_ctx = SSL_CTX_new();
		if (user_cert != NULL) {
			X509_set_default_verify_paths(ssl_ctx->cert);
			SSL_CTX_use_certificate_file(ssl_ctx, user_cert, SSL_FILETYPE_PEM);
			SSL_CTX_use_RSAPrivateKey_file(ssl_ctx, user_cert, SSL_FILETYPE_PEM);
		}
	}
#endif

       /* interval loop */
	do {

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

	       /* */
	       /* connect to remote site and do transaction */
	       /* */

		if ((c = connect(net, (struct sockaddr *) &sin, sizeof(sin))) < 0) {
			if (!interval)
				return c;
			else {
				sleep(interval);
				continue;
			}
		}

#if 0
		if (!strncmp(protocol, "telnet", 6)) {
			while (1) {
				FD_ZERO(&fds);
				FD_SET(net, &fds);
				FD_SET(0, &fds);
				select(net + 1, &fds, NULL, NULL, NULL);
				if (FD_ISSET(0, &fds)) {
					c = read(0, xferbuf, XBUFSIZ);
#ifdef USE_SSL
					if (sslflag)
						SSL_write(ssl_handle, xferbuf, c);
					else
#endif
						write(net, xferbuf, c);
				}
				if (FD_ISSET(net, &fds)) {
#ifdef USE_SSL
					if (sslflag)
						c = SSL_read(ssl_handle, xferbuf, XBUFSIZ);
					else
#endif
						c = read(net, xferbuf, XBUFSIZ);
					write(1, xferbuf, c);
				}
			}
		}
#endif

	       /* write request string; dump any response to stdout */
#ifdef USE_SSL
		if (sslflag) {

		       /* ftp needs to be switched into SSL mode */

			if (!strncmp(protocol, "ftp", 3)) {
				if (debugflag)
					fprintf(stderr, "Attempting to switch FTP into SSL mode\n");

				write(net, "AUTH SSL\r\n", 10);
				c = 0;
				xferbuf[0] = 0;

			       /* really should add select() calls */

			       /* wait for the OK */
				while (!(ip = strstr(xferbuf, "334 AUTH SSL OK."))) {
					c += read(net, &xferbuf[c], 1);
					xferbuf[c] = 0;
				}
			       /* wait for EOL so it doesn't go into the ssl negotiation */
				while (!strchr(ip, '\n')) {
					c += read(net, &xferbuf[c], 1);
					xferbuf[0] = c;
				}
			}

			ssl_handle = SSL_new(ssl_ctx);
			X509_set_default_verify_paths(ssl_ctx->cert);
			SSL_set_verify(ssl_handle, sslflag >= 2 ? SSL_VERIFY_NONE : SSL_VERIFY_FAIL_IF_NO_PEER_CERT /*PEER */ , sslverify);
			SSL_set_fd(ssl_handle, net);

			if ((argp = SSL_connect(ssl_handle)) < 0) {
				SSL_load_error_strings();
				fprintf(stderr, "SSL could not connect %s\n", ERR_error_string(ERR_get_error(), xferbuf));
				exit(-1);
			}
			if (sslflag == 1) {
				ip = X509_NAME_oneline(X509_get_subject_name(SSL_get_peer_certificate(ssl_handle)));
				op = strstr(ip, "/CN=");
				op += 4;
				if (strcmp(op, site)) {
					fprintf(stderr, "Site name (%s) is not cert name (%s)\n", site, op);
					exit(-1);
				}
				free(ip);
			}
			if (sslflag == 3) {
				PEM_write_X509(certfile, SSL_get_peer_certificate(ssl_handle));
			}
			if (debugflag) {
				ip = SSL_get_cipher(ssl_handle);
				fprintf(stderr, "SSL Using Cipher %s\n", ip == NULL ? "(unknown)" : ip);
			}
		       /* write the request to the remote site */

			SSL_write(ssl_handle, headbuf, strlen(headbuf));
			if (ff != NULL) {
				putsiz = 1;
				while (putsiz) {
					putsiz = fread(contbuf, 1, XBUFSIZ, ff);
					if (!putsiz)
						break;
					SSL_write(ssl_handle, contbuf, putsiz);
				}
				rewind(ff);
			}
		}
		else
#endif
		{
		       /* write the request to the remote site */

			write(net, headbuf, strlen(headbuf));
			if (ff != NULL) {
				putsiz = 1;
				while (putsiz) {
					putsiz = fread(contbuf, 1, XBUFSIZ, ff);
					if (!putsiz)
						break;
					write(net, contbuf, putsiz);
				}
				rewind(ff);
			}
		}

	       /* */
	       /* FTP protocol handler */
	       /* */

		if (!strncmp(protocol, "ftp", 3)) {

			netd = socket(AF_INET, SOCK_STREAM, 0);
			if (netd < 0)
				return -1;

			c = 0;
		       /* find entire PASV response line */
			while (!(ip = strstr(xferbuf, "\n227")) || !strchr(&ip[5], '\n')) {

			       /* really should add select() calls */

#ifdef USE_SSL
				if (sslflag)
					temp = SSL_read(ssl_handle, &xferbuf[c], XBUFSIZ - c);
				else
#endif
					temp = read(net, &xferbuf[c], XBUFSIZ - c);
				xferbuf[c + temp] = 0;
				if (debugflag)
					fprintf(stderr, "%s", &xferbuf[c]);
				c += temp;
			}

			if (!ip)
				return -1;

			ip += 5;

			a = (char *) &sin.sin_addr;
			p = (char *) &sin.sin_port;

			while (*ip++ != '(');	/* find start of pasv ip/port message */

		       /* extract ip and port into sin structure */
			sscanf(ip, "%d,%d,%d,%d,%d,%d", &val[0], &val[1], &val[2], &val[3], &val[4], &val[5]);
			a[0] = val[0], a[1] = val[1], a[2] = val[2], a[3] = val[3], p[0] = val[4], p[1] = val[5];

		       /* connect to data port */
			if ((c = (connect(netd, (struct sockaddr *) &sin, sizeof(sin)) < 0)))
				return c;

			if (debugflag)
				fprintf(stderr, "FTP data connection opened\n");

#ifdef USE_SSL
			if (sslflag) {
				ssl_dhandle = SSL_new(ssl_ctx);
				SSL_set_fd(ssl_dhandle, netd);
				SSL_copy_session_id(ssl_dhandle, ssl_handle);
				if (SSL_connect(ssl_dhandle) < 0)
					exit(0);

				if (debugflag)
					fprintf(stderr, "FTP data connection using SSL\n");

			}

		       /* swap data and control */
			ssl_handle = ssl_dhandle;
#endif
		       /* swap data and control */
			net = netd;
		}

	       /* FTP data and everything else */

		c = XBUFSIZ;
		ip = NULL;
		while (c > 0) {

			FD_ZERO(&fds);
			FD_SET(net, &fds);
			tv.tv_sec = timeout;
			tv.tv_usec = 0;
			if (!select(net + 1, &fds, NULL, NULL, &tv))
				break;
#ifdef USE_SSL
			if (sslflag)
				c = SSL_read(ssl_handle, xferbuf, XBUFSIZ);
			else
#endif
				c = read(net, xferbuf, XBUFSIZ);
			if (!headers && ip == NULL && !strncmp(protocol, "http", 4)) {
				xferbuf[c] = 0;

				while ((ip = strstr(xferbuf, "\r\n\r\n")) == NULL) {
#ifdef USE_SSL
					if (sslflag)
						c += SSL_read(ssl_handle, &xferbuf[c], XBUFSIZ - c);
					else
#endif
						c += read(net, &xferbuf[c], XBUFSIZ - c);
					xferbuf[c] = 0;

				}
				if (ip != NULL) {
					ip += 4;
					c -= ip - xferbuf;
					if (!c) {
						c = 1;
						continue;
					}
					memcpy(xferbuf, ip, c);
				}
			}
			if (c != write(1, xferbuf, c))
				break;	/* did someone close the pipe */
		}

		close(net);
		if (netd > 0)
			close(netd);
#ifdef USE_SSL
		if (ssl_handle != NULL)
			SSL_free(ssl_handle);
		if (ssl_dhandle != NULL)
			SSL_free(ssl_dhandle);
	       /* ssl destroy ... */
#endif
		if (interval > 0)
			sleep(interval);

	} while (interval);

       /* end of interval loop */

	return 0;
}
