about summary refs log tree commit diff stats
path: root/bonus/gmifetch
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-12-10 18:29:12 +0100
committerbptato <nincsnevem662@gmail.com>2023-12-10 18:34:27 +0100
commitf4d895c0c619dd098239fd30c4ba9d68b9caebc6 (patch)
treea8815e9a9de29c5690270e803d82ad2a365e6c9b /bonus/gmifetch
parentc85dfb6a369ea1e9fc4b1f4cd7dba2b9d7b75483 (diff)
downloadchawan-f4d895c0c619dd098239fd30c4ba9d68b9caebc6.tar.gz
Add support for gemini by default, update readme
No need to leave gemini support in the bonus folder.
Still TODO: proxy support.
Diffstat (limited to 'bonus/gmifetch')
-rw-r--r--bonus/gmifetch/.gitignore1
-rw-r--r--bonus/gmifetch/Makefile11
-rw-r--r--bonus/gmifetch/gmifetch.c683
3 files changed, 0 insertions, 695 deletions
diff --git a/bonus/gmifetch/.gitignore b/bonus/gmifetch/.gitignore
deleted file mode 100644
index 99270cf6..00000000
--- a/bonus/gmifetch/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-gmifetch
diff --git a/bonus/gmifetch/Makefile b/bonus/gmifetch/Makefile
deleted file mode 100644
index 68d6a930..00000000
--- a/bonus/gmifetch/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-CFLAGS = -Wall -Wextra -std=c89 -pedantic -lcrypto -lssl -g -O3
-
-gmifetch: gmifetch.c
-	$(CC) $(CFLAGS) gmifetch.c -o gmifetch
-
-.PHONY: clean
-clean:
-	rm -f gmifetch
-
-.PHONY: all
-all: gmifetch
diff --git a/bonus/gmifetch/gmifetch.c b/bonus/gmifetch/gmifetch.c
deleted file mode 100644
index d161cf5c..00000000
--- a/bonus/gmifetch/gmifetch.c
+++ /dev/null
@@ -1,683 +0,0 @@
-/* This file is dedicated to the public domain.
- *
- * Gemini protocol adapter for Chawan.
- * Intended to be used through local CGI (by redirection in scheme-map).
- *
- * (FWIW, it should work with normal CGI or w3m's local CGI too. However,
- * it does not rewrite URLs, so you would have to figure out something for
- * that, e.g. by setting the base href or rewriting URLs in another layer.)
- *
- * Usage: gmifetch [URL]
- *
- * Environment variables:
- * - QUERY_STRING is used if no URL arguments are passed.
- * - GMIFETCH_KNOWN_HOSTS is used for setting the known_hosts file. If not set,
- *   we use $XDG_CONFIG_HOME/gmifetch/known_hosts, where $XDG_CONFIG_HOME falls
- *   back to $HOME/.config/gmifetch if not set.
- */
-
-#include <ctype.h>
-#include <errno.h>
-#include <openssl/err.h>
-#include <openssl/pem.h>
-#include <openssl/ssl.h>
-#include <pwd.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <unistd.h>
-
-static SSL_CTX* ssl_ctx;
-static SSL *ssl;
-static BIO *conn;
-
-/* CGI responses */
-#define INPUT_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>" \
-	"<title>Input required</title>" \
-	"<base href='%s'>" \
-	"<h1>Input required</h1>" \
-	"<p>" \
-	"%s" \
-	"<p>" \
-	"<form method=POST><input type='%s' name='input'></form>"
-
-#define SUCCESS_RESPONSE "Content-Type: %s\r\n" \
-	"\r\n"
-
-#define REDIRECT_RESPONSE "Status: 30%c\r\n" \
-	"Location: %s\r\n" \
-	"\r\n"
-
-#define TEMPFAIL_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>" \
-	"<title>Temporary failure</title>" \
-	"<h1>%s</h1>" \
-	"<p>" \
-	"%s"
-
-#define PERMFAIL_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>" \
-	"<title>Permanent failure</title>" \
-	"<h1>%s</h1>" \
-	"<p>" \
-	"%s"
-
-#define CERTFAIL_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>" \
-	"<title>Certificate failure</title>" \
-	"<h1>%s</h1>" \
-	"<p>" \
-	"%s"
-
-#define INVALID_CERT_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>\n" \
-	"<title>Invalid certificate</title>\n" \
-	"<h1>Invalid certificate</h1>\n" \
-	"<p>\n" \
-	"The certificate received from the server does not match the\n" \
-	"stored certificate (expected %s, but got %s). Somebody may be\n" \
-	"tampering with your connection.\n" \
-	"<p>\n" \
-	"If you are sure that this is not a man-in-the-middle attack,\n" \
-	"please remove this host from %s.\n"
-
-#define UNKNOWN_CERT_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>" \
-	"<title>Unknown certificate</title>" \
-	"<h1>Unknown certificate</h1>" \
-	"<p>\n" \
-	"The hostname of the server you are visiting could not be found\n" \
-	"in your list of known hosts (%s).\n" \
-	"<p>\n" \
-	"The server has sent us a certificate with the following\n" \
-	"fingerprint:\n" \
-	"<pre>%s</pre>\n" \
-	"<p>Trust it?\n" \
-	"<form method=POST>" \
-	"<input type=submit name=trust_cert value=always>\n" \
-	"<input type=submit name=trust_cert value=once>" \
-	"<input type=hidden name=entry value='%s sha256 %s %lu'>" \
-	"</form>"
-
-#define UPDATED_CERT_RESPONSE "Content-Type: text/html\r\n" \
-	"\r\n" \
-	"<!DOCTYPE html>\n" \
-	"<title>Certificate date changed</title>\n" \
-	"<h1>Certificate date changed</h1>\n" \
-	"<p>\n" \
-	"The received certificate's date did not match the date in your\n" \
-	"list of known hosts (%s).\n" \
-	"<p>\n" \
-	"The new expiration date is: %s.\n" \
-	"<p>\n" \
-	"Update it?\n" \
-	"<form method=POST>" \
-	"<input type=submit name=trust_cert value=always>" \
-	"<input type=submit name=trust_cert value=once>\n" \
-	"<input type=hidden name=entry value='%s sha256 %s %lu'>" \
-	"</form>\n"
-
-#define PDIE(x) \
-	do { \
-		puts("Content-Type: text/plain\r\n"); \
-		puts(x); \
-		puts(strerror(errno)); \
-		exit(1); \
-	} while (0)
-
-#define SDIE(x) \
-	do { \
-		puts("Content-Type: text/plain\r\n"); \
-		puts(x); \
-		ERR_print_errors_fp(stdout); \
-		exit(1); \
-	} while (0)
-
-#define DIE(x) \
-	do { \
-		puts("Content-Type: text/plain\r\n\r\n" x); \
-		exit(1); \
-	} while (0)
-
-#define BUFSIZE 1024
-#define PUBKEY_BUF_SIZE 8192
-
-static void setup_ssl(void)
-{
-	SSL_library_init();
-	SSL_load_error_strings();
-	ssl_ctx = SSL_CTX_new(TLS_client_method());
-	SSL_CTX_set_min_proto_version(ssl_ctx, TLS1_2_VERSION);
-	if (!(conn = BIO_new_ssl_connect(ssl_ctx)))
-		SDIE("Error creating BIO");
-#undef FLAGS
-}
-
-static void extract_hostname(const char *s, char **hostp, char **portp,
-	char **pathp, char **endp, char *urlbuf)
-{
-	const char *p;
-	size_t i, schlen;
-
-	if (!(p = strstr(s, "gemini://")))
-		DIE("Invalid URL: scheme delimiter not found");
-	p += strlen("gemini://");
-	schlen = p - s;
-	if (schlen >= BUFSIZE)
-		DIE("Scheme too long");
-#define SCHEME "gemini://"
-	schlen = strlen(SCHEME);
-	strcpy(urlbuf, SCHEME);
-	*hostp = &urlbuf[schlen];
-	for (i = schlen; *p && *p != ':' && *p != '/' && i < BUFSIZE; ++p, ++i)
-		urlbuf[i] = *p;
-	if (i + 2 >= BUFSIZE) /* +2 for CRLF */
-		DIE("Host too long");
-	*portp = &urlbuf[i];
-	if (*p != ':') {
-		if (i + 5 >= BUFSIZE)
-			DIE("Host too long");
-		strcpy(&urlbuf[i], ":1965");
-		i += 5;
-	} else {
-		for (; *p && *p != '/' && i < BUFSIZE; ++i, ++p)
-			urlbuf[i] = *p;
-	}
-	*pathp = &urlbuf[i];
-	if (i < BUFSIZE)
-		urlbuf[i++] = '/';
-	if (*p == '/')
-		++p;
-	for (; *p && i < BUFSIZE; ++i, ++p)
-		urlbuf[i] = *p;
-	if (i + 2 >= BUFSIZE) /* +2 for CRLF */
-		DIE("Host too long");
-	*endp = &urlbuf[i];
-	urlbuf[i] = '\0';
-}
-
-int check_cert(const char *theirs, char *hostp, char **stored_digestp,
-	char *linebuf, time_t their_time, FILE *known_hosts)
-{
-	char *p, *q, *hashp, *timep;
-	int found;
-	time_t our_time;
-
-	rewind(known_hosts);
-	found = 0;
-	while (!found && fgets(linebuf, BUFSIZE, known_hosts)) {
-		p = strstr(linebuf, " ");
-		if (!p)
-			DIE("Incorrectly formatted known_hosts file");
-		*p = '\0';
-		found = !strcmp(linebuf, hostp);
-	}
-	if (!found)
-		return -1;
-	hashp = p + 1;
-	if (!(q = strstr(hashp, " ")))
-		DIE("Incorrectly formatted known_hosts file");
-	*q = '\0';
-	if (strcmp(hashp, "sha256") && strcmp(hashp, "SHA256"))
-		DIE("Unsupported digest format");
-	*stored_digestp = q + 1;
-	if (!(q = strstr(*stored_digestp, " "))) {
-		timep = NULL;
-		if ((q = strstr(*stored_digestp, "\n")))
-			*q = '\0';
-	} else {
-		timep = q + 1;
-		*q = '\0';
-	}
-	if (strcmp(theirs, *stored_digestp))
-		return 0;
-	if (!timep)
-		return -2;
-	our_time = (time_t)atol(timep);
-	if (their_time != our_time)
-		return -2;
-	return 1;
-}
-
-static char HexTable[] = "0123456789ABCDEF";
-
-void hex_encode(const unsigned char *inp, char *outbuf, int len)
-{
-	const unsigned char *p;
-	char *q;
-
-	for (p = inp, q = outbuf; p < &inp[len]; ++p) {
-		if (p != inp)
-			*q++ = ':';
-		*q++ = HexTable[(*p >> 4) & 0xF];
-		*q++ = HexTable[*p & 0xF];
-	}
-	*q++ = '\0';
-}
-
-static void hash_buf(const unsigned char *ibuf, int len, char *obuf2)
-{
-	unsigned int len2;
-	EVP_MD_CTX* mdctx;
-	unsigned char hashbuf[EVP_MAX_MD_SIZE];
-
-	if (!(mdctx = EVP_MD_CTX_new()))
-		SDIE("Failed to initialize MD_CTX");
-	if (!EVP_DigestInit_ex(mdctx, EVP_sha256(), NULL))
-		SDIE("Failed to initialize sha256");
-	if (!EVP_DigestUpdate(mdctx, ibuf, len))
-		SDIE("Failed to update digest");
-	len2 = 0;
-	if (!EVP_DigestFinal_ex(mdctx, hashbuf, &len2))
-		SDIE("Failed to finalize digest");
-	EVP_MD_CTX_free(mdctx);
-	hex_encode(hashbuf, obuf2, len2);
-}
-
-/* 1: cert found & valid
- * 0: cert found & invalid
- * -1: cert not found
- * -2: cert found, but notAfter updated
- */
-static int connect(char *hostp, char *portp, char *pathp, char *endp,
-	char *linebuf, char **stored_digestp, time_t *their_time,
-	char *hashbuf2, FILE *known_hosts)
-{
-	X509 *cert;
-	const EVP_PKEY *pkey;
-	unsigned char pubkey_buf[PUBKEY_BUF_SIZE + 1], *r;
-	int len, res;
-	const ASN1_TIME *notAfter;
-	struct tm their_tm;
-
-	*pathp = '\0';
-	if (!BIO_set_conn_hostname(conn, hostp))
-		SDIE("Error setting BIO hostname");
-	*pathp = '/';
-	BIO_get_ssl(conn, &ssl);
-#define PREFERRED_CIPHERS "HIGH:!aNULL:!kRSA:!PSK:!SRP:!MD5:!RC4"
-	if (!SSL_set_cipher_list(ssl, PREFERRED_CIPHERS))
-		SDIE("Error failed to set cipher list");
-	*portp = '\0';
-	if (!SSL_set_tlsext_host_name(ssl, hostp))
-		SDIE("Error failed to set tlsext host name");
-	if (BIO_do_connect(conn) <= 0)
-		SDIE("Failed to connect");
-	if (!BIO_do_handshake(conn))
-		SDIE("Failed handshake");
-	if (!(cert = SSL_get_peer_certificate(ssl)))
-		DIE("Failed to get certificate");
-	if (!(pkey = X509_get0_pubkey(cert)))
-		SDIE("Failed to decode public key");
-	len = i2d_PUBKEY(pkey, NULL);
-	if (len * 3 > PUBKEY_BUF_SIZE)
-		DIE("Public key too long");
-	r = pubkey_buf;
-	if (i2d_PUBKEY(pkey, &r) != len)
-		DIE("wat");
-	hash_buf(pubkey_buf, len, hashbuf2);
-	notAfter = X509_get0_notAfter(cert);
-	if (!ASN1_TIME_to_tm(notAfter, &their_tm))
-		DIE("Failed to parse time");
-	if (X509_cmp_current_time(X509_get0_notBefore(cert)) >= 0)
-		DIE("Wrong time");
-	if (X509_cmp_current_time(notAfter) <= 0)
-		DIE("Wrong time");
-	*their_time = mktime(&their_tm);
-	res = check_cert(hashbuf2, hostp, stored_digestp, linebuf, *their_time,
-		known_hosts);
-	*portp = ':';
-	X509_free(cert);
-	strcpy(endp, "\r\n");
-	return res;
-}
-
-static void read_response(const char *urlbuf)
-{
-	int bytes, total;
-	const char *tmp;
-	char *q, status0, status1;
-	char buffer[BUFSIZE + 1];
-
-	/* Read response */
-	total = 0;
-	/* Status code */
-	while (((bytes = BIO_read(conn, buffer, 3 - total)) > 0 ||
-			BIO_should_retry(conn)) && total < 3)
-		total += bytes;
-	if (total < 3 || !isdigit(status0 = buffer[0]) ||
-			!isdigit(status1 = buffer[1]) || buffer[2] != ' ')
-		DIE("Invalid status code");
-	/* Meta */
-	#define METALEN (total - 3)
-	while (((bytes = BIO_read(conn, &buffer[METALEN], 1024 - METALEN)) > 0 ||
-			BIO_should_retry(conn)) && METALEN < BUFSIZE)
-		total += bytes;
-	q = strstr(buffer, "\r\n");
-	if (!q)
-		DIE("Invalid status line");
-	*q = '\0';
-	/* buffer is now META. */
-	switch (status0) {
-	case '1': /* input */
-		/* META is the prompt. */
-		printf(INPUT_RESPONSE, urlbuf, buffer, status1 == '1' ?
-			"password" /* sensitive input */ :
-			"search" /* input */);
-		break;
-	case '2': /* success */
-		/* META is the content type. */
-		printf(SUCCESS_RESPONSE, *buffer ?
-			buffer :
-			"text/gemini; charset=utf-8" /* fallback */);
-		/* Body */
-		/* flush any data remaining in buffer */
-		total -= 5 + (q - buffer); /* code + space + meta + \r\n len */
-		if (total > 0)
-			fwrite(&q[2], 1, total, stdout);
-		while ((bytes = BIO_read(conn, buffer, BUFSIZE)) > 0 ||
-				BIO_should_retry(conn))
-			fwrite(buffer, 1, bytes, stdout);
-		break;
-	case '3': /* redirect */
-		/* META is the redirection URL. */
-		printf(REDIRECT_RESPONSE, status1 == '0' ?
-			'7' /* temporary */ :
-			'1' /* permanent */, buffer);
-		break;
-	case '4': /* temporary failure */
-		/* META is additional information. */
-		/* TODO maybe set status code too? */
-		switch (status1) {
-		case '1':
-			tmp = "Server unavailable";
-			break;
-		case '2':
-			tmp = "CGI error";
-			break;
-		case '3':
-			tmp = "Proxy error";
-			break;
-		case '4':
-			tmp = "Slow down!";
-			break;
-		case '0':
-		default: /* no additional information provided in the code */
-			tmp = "Temporary failure";
-			break;
-		}
-		printf(TEMPFAIL_RESPONSE, tmp, buffer);
-		break;
-	case '5': /* permanent failure */
-		/* TODO maybe set status code too? */
-		switch (status1) {
-		case '1':
-			tmp = "Not found";
-			break;
-		case '2':
-			tmp = "Gone";
-			break;
-		case '3':
-			tmp = "Proxy request refused";
-			break;
-		case '9':
-			tmp = "Bad request";
-			break;
-		case '0':
-		default: /* no additional information provided in the code */
-			tmp = "Permanent failure";
-			break;
-		}
-		printf(PERMFAIL_RESPONSE, tmp, buffer);
-		break;
-	case '6': /* permanent failure */
-		/* TODO maybe set status code too? */
-		switch (status1) {
-		case '1':
-			tmp = "Certificate not authorized";
-			break;
-		case '2':
-			tmp = "Certificate not valid";
-			break;
-		case '0':
-		default: /* no additional information provided in the code */
-			tmp = "Certificate failure";
-			break;
-		}
-		printf(CERTFAIL_RESPONSE, tmp, buffer);
-	}
-}
-
-void decode_query(const char *input_url, char *output_buffer)
-{
-	const char *p;
-	char *q, *endp, c;
-
-	endp = &output_buffer[BUFSIZE];
-	for (p = input_url, q = output_buffer; *p && q < endp; ++p, ++q) {
-		if (*p != '%') {
-			*q = *p;
-		} else {
-			if (!isxdigit(p[1] & 0xFF) || !isxdigit(p[2] & 0xFF))
-				DIE("Invalid percent encoding");
-			c = tolower(p[1] & 0xFF);
-			*q = ('a' <= c && c <= 'z') ?
-				c - 'a' + 10 :
-				c - '0';
-			c = tolower(p[2] & 0xFF);
-			*q = (*q << 4) | (('a' <= c || c <= 'z') ?
-				c - 'a' + 10 :
-				c - '0');
-			p += 2;
-		}
-	}
-	if (q >= endp)
-		DIE("Query too long");
-	*q = '\0';
-}
-
-
-void read_post(const char *hostp, char *portp, char *pathp, const char *urlbuf,
-	char *khsbuf, FILE **known_hosts)
-{
-	/* TODO move query strings here */
-	size_t n;
-	char *p, *q;
-	FILE *known_hosts_tmp;
-	long last_pos, len, total;
-	size_t khslen;
-	char inbuf[BUFSIZE + 1], buffer[BUFSIZE + 1];
-
-	n = fread(inbuf, 1, BUFSIZE, stdin);
-	inbuf[n] = '\0';
-	if ((p = strstr(inbuf, "input="))) {
-		decode_query(p + 6, buffer);
-		if (!(q = strstr(pathp, "?"))) /* no query string */
-			q = &pathp[strlen(pathp)];
-		for (; *p && q < &urlbuf[BUFSIZE]; ++p, ++q)
-			*q = *p;
-		if (q >= &urlbuf[BUFSIZE])
-			DIE("Query too long");
-	} else if (!(p = strstr(inbuf, "trust_cert="))) {
-		DIE("Invalid POST request: trust_cert missing");
-	}
-	p += sizeof("trust_cert=") - 1;
-	if (!strncmp(p, "always", 6)) {
-		/* move to file end */
-		fseek(*known_hosts, 0L, SEEK_END);
-		last_pos = ftell(*known_hosts);
-		if (!(p = strstr(p, "entry=")))
-			DIE("Invalid POST request: missing entry");
-		p += sizeof("entry=") - 1;
-		decode_query(p, buffer);
-		/* replace plus signs */
-		p = buffer;
-		while ((p = strstr(p, "+")))
-			*p = ' ';
-		fwrite(buffer, 1, strlen(buffer), *known_hosts);
-		fwrite("\n", 1, 1, *known_hosts);
-		khslen = strlen(khsbuf);
-		khsbuf[khslen] = '~';
-		khsbuf[khslen + 1] = '\0';
-		if (!(known_hosts_tmp = fopen(khsbuf, "w+")))
-			PDIE("Error opening temporary hosts file");
-		rewind(*known_hosts);
-		*portp = '\0';
-		total = 0;
-		while (fgets(buffer, BUFSIZE, *known_hosts)) {
-			len = strlen(buffer);
-			if (!len)
-				continue;
-			if ((total += len) > last_pos) {
-				/* finished */
-				fwrite(buffer, 1, len, known_hosts_tmp);
-				break;
-			}
-			if (buffer[len - 1] != '\n') {
-				/* clean up */
-				fclose(known_hosts_tmp);
-				unlink(khsbuf);
-				DIE("Line too long");
-			}
-			if (!(p = strstr(buffer, " ")))
-				DIE("Invalid entry in known_hosts file");
-			*p = '\0';
-			if (strcmp(buffer, hostp)) {
-				*p = ' ';
-				fwrite(buffer, 1, len, known_hosts_tmp);
-			}
-		}
-		*portp = ':';
-		memcpy(buffer, khsbuf, BUFSIZE + 1);
-		buffer[khslen] = '\0';
-		fclose(*known_hosts);
-		fclose(known_hosts_tmp);
-		if (rename(khsbuf, buffer))
-			PDIE("Failed to rename temporary file");
-		khsbuf[khslen] = '\0';
-		if (!(*known_hosts = fopen(khsbuf, "a+")))
-			PDIE("Failed to re-open known hosts file");
-	} else if (strncmp(p, "once", 4)) {
-		DIE("Invalid POST request");
-	}
-}
-
-FILE *open_known_hosts(char *khsbuf)
-{
-	const char *known_hosts_path, *xdg_dir, *home_dir;
-	char *p;
-	size_t len;
-	struct stat s;
-	FILE *known_hosts;
-
-	known_hosts_path = getenv("GMIFETCH_KNOWN_HOSTS");
-	if (!known_hosts_path) {
-		xdg_dir = getenv("XDG_CONFIG_HOME");
-		if ((xdg_dir = getenv("XDG_CONFIG_HOME"))) {
-			len = strlen(xdg_dir);
-#define CONFIG_REL "/gmifetch/known_hosts"
-			if (len + sizeof(CONFIG_REL) > BUFSIZE)
-				DIE("Error: config directory path too long");
-			memcpy(khsbuf, xdg_dir, len);
-			memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL));
-		} else {
-			if (!(home_dir = getenv("HOME")))
-				home_dir = getpwuid(getuid())->pw_dir;
-			if (!home_dir)
-				DIE("Error: failed to get HOME directory");
-#undef CONFIG_REL
-#define CONFIG_REL "/.config/gmifetch/known_hosts"
-			len = strlen(home_dir);
-			if (len + sizeof(CONFIG_REL) > BUFSIZE)
-				DIE("Error: home directory path too long");
-			memcpy(khsbuf, home_dir, len);
-			memcpy(&khsbuf[len], CONFIG_REL, sizeof(CONFIG_REL));
-		}
-	} else {
-		len = strlen(known_hosts_path);
-		if (len > BUFSIZE)
-			DIE("Error: known hosts path too long");
-		memcpy(khsbuf, known_hosts_path, len);
-	}
-	p = khsbuf;
-	if (*p == '/')
-		++p;
-	for (; *p; ++p) {
-		if (*p == '/') {
-			*p = '\0';
-			if (stat(khsbuf, &s) == -1) {
-				if (errno != ENOENT)
-					PDIE("Error calling stat");
-				if (mkdir(khsbuf, 0755) == -1)
-					PDIE("Error calling mkdir");
-			} else if (!S_ISDIR(s.st_mode)) {
-				if (mkdir(khsbuf, 0755) == -1)
-					PDIE("Error calling mkdir");
-			}
-			*p = '/';
-		}
-	}
-	if (!(known_hosts = fopen(khsbuf, "a+")))
-		PDIE("Error opening known hosts file");
-	return known_hosts;
-}
-
-int main(int argc, const char *argv[])
-{
-	const char *input_url, *method;
-	char *hostp, *portp, *pathp, *endp, *stored_digestp;
-	int connect_res;
-	time_t their_time;
-	char hashbuf2[EVP_MAX_MD_SIZE * 3 + 1];
-	char urlbuf[BUFSIZE + 1], khsbuf[BUFSIZE + 2], linebuf[BUFSIZE + 1];
-	FILE *known_hosts;
-
-	if (argc != 2) {
-		input_url = getenv("QUERY_STRING");
-		if (!input_url)
-			DIE("Usage: gmifetch [url] (or set QUERY_STRING)");
-	} else {
-		input_url = argv[1];
-	}
-	known_hosts = open_known_hosts(khsbuf);
-	setup_ssl();
-	extract_hostname(input_url, &hostp, &portp, &pathp, &endp, urlbuf);
-	method = getenv("REQUEST_METHOD");
-	if (method && !strcmp(method, "POST"))
-		read_post(hostp, portp, pathp, urlbuf, khsbuf, &known_hosts);
-	connect_res = connect(hostp, portp, pathp, endp, linebuf, &stored_digestp,
-		&their_time, hashbuf2, known_hosts);
-	if (connect_res == 1) { /* valid certificate */
-		/* I really wish this was explicitly mentioned in the
-		 * standard. Something like:
-		 *
-		 * !!!WARNING WARNING WARNING some gemini servers will
-		 * not accept URLs containing the default port number!!!
-		 */
-		if (!strncmp(portp, ":1965", 5))
-			/* move including null terminator */
-			memmove(portp, &portp[5], strlen(&portp[5]) + 1);
-		BIO_puts(conn, urlbuf);
-		read_response(urlbuf);
-	} else if (connect_res == 0) { /* invalid certificate */
-		printf(INVALID_CERT_RESPONSE, stored_digestp, hashbuf2,
-			khsbuf);
-	} else if (connect_res == -1) { /* no certificate */
-		*portp = '\0';
-		printf(UNKNOWN_CERT_RESPONSE, khsbuf, hashbuf2, hostp,
-			hashbuf2, (unsigned long)their_time);
-	} else { /* -2: updated expiration date */
-		*portp = '\0';
-		printf(UPDATED_CERT_RESPONSE, khsbuf,
-			ctime(&their_time), hostp, hashbuf2,
-			(unsigned long)their_time);
-	}
-	BIO_free_all(conn);
-	exit(0);
-}