diff --git a/configure b/configure

index 7b55a2580018cc69b2f52c9bc53832cc9faadc4f..6abccdd18d0b85e4b2d4b527d49727d360dec5c3 100755

--- a/configure

+++ b/configure

@@ -15,6 +15,7 @@ }

cgmnlm() {

genrules cgmnlm \

+ src/certs.c \

src/client.c \

src/escape.c \

src/gmnlm.c \

@@ -26,6 +27,7 @@ }

libgmni_a() {

genrules libgmni.a \

+ src/certs.c \

src/client.c \

src/escape.c \

src/tofu.c \

diff --git a/include/gmni/certs.h b/include/gmni/certs.h

index 22e226d6b4252dddd8526970cec0a947f12242d1..53b8aad27fa50649ddfdacbd08724d5d0b033120 100644

--- a/include/gmni/certs.h

+++ b/include/gmni/certs.h

@@ -20,7 +20,7 @@ };

unsigned char data[];

};

-// Returns nonzero on failure and sets errno

+// Returns nonzero on failure and sets errno. Closes both files.

int gmni_ccert_load(struct gmni_client_certificate *cert,

FILE *certin, FILE *skin);

diff --git a/src/gmnlm.c b/src/gmnlm.c

index aeb0c834d49c799fcdae54acfcbd3925dcedd392..12a6c971b7ae7d18bcdafc3b2b81f55539340095 100644

--- a/src/gmnlm.c

+++ b/src/gmnlm.c

@@ -4,6 +4,7 @@ #include <ctype.h>

#include <errno.h>

#include <fcntl.h>

#include <getopt.h>

+#include <gmni/certs.h>

#include <gmni/gmni.h>

#include <gmni/tofu.h>

#include <gmni/url.h>

@@ -434,13 +435,50 @@ {

int nredir = 0;

bool requesting = true;

enum gemini_result res;

- while (requesting) {

- char *scheme;

+

+ char *scheme;

+ CURLUcode uc = curl_url_get(browser->url,

+ CURLUPART_SCHEME, &scheme, 0);

+ assert(uc == CURLUE_OK); // Invariant

+

+ char *host = NULL;

+ struct gmni_client_certificate client_cert = {0};

+ const struct pathspec paths[] = {

+ {.var = "GMNIDATA", .path = "/certs/%s.%s"},

+ {.var = "XDG_DATA_HOME", .path = "/gmni/certs/%s.%s"},

+ {.var = "HOME", .path = "/.local/share/gmni/certs/%s.%s"}

+ };

+ char *path_fmt = getpath(paths, sizeof(paths) / sizeof(paths[0]));

+ char certpath[PATH_MAX+1], keypath[PATH_MAX+1];

+ size_t n = 0;

+

+ if (strcmp(scheme, "gemini") == 0) {

CURLUcode uc = curl_url_get(browser->url,

- CURLUPART_SCHEME, &scheme, 0);

- assert(uc == CURLUE_OK); // Invariant

+ CURLUPART_HOST, &host, 0);

+ assert(uc == CURLUE_OK);

+

+ n = snprintf(certpath, sizeof(certpath), path_fmt, host, "crt");

+ assert(n < sizeof(certpath));

+ FILE *certin = fopen(certpath, "r");

+ if (certin) {

+ n = snprintf(keypath, sizeof(keypath), path_fmt, host, "key");

+ assert(n < sizeof(keypath));

+

+ FILE *skin = fopen(keypath, "r");

+ if (gmni_ccert_load(&client_cert, certin, skin)) {

+ browser->opts.client_cert = NULL;

+ fprintf(stderr, "Unable to load client certificate for host %s", host);

+ } else {

+ browser->opts.client_cert = &client_cert;

+ }

+ } else {

+ browser->opts.client_cert = NULL;

+ }

+ free(host);

+ }

+

+ while (requesting) {

if (strcmp(scheme, "file") == 0) {

- free(scheme);

requesting = false;

char *path;

@@ -475,9 +513,9 @@ free(path);

resp->status = GEMINI_STATUS_SUCCESS;

resp->fd = fd;

resp->sc = NULL;

- return GEMINI_OK;

+ res = GEMINI_OK;

+ goto out;

}

- free(scheme);

res = gemini_request(browser->plain_url, &browser->opts,

&browser->tofu, resp);

@@ -519,7 +557,19 @@ }

set_url(browser, resp->meta, NULL);

break;

case GEMINI_STATUS_CLASS_CLIENT_CERTIFICATE_REQUIRED:

- assert(0); // TODO

+ requesting = false;

+ assert(host);

+ n = snprintf(certpath, sizeof(certpath), path_fmt, host, "crt");

+ assert(n < sizeof(certpath));

+ n = snprintf(keypath, sizeof(keypath), path_fmt, host, "key");

+ assert(n < sizeof(keypath));

+ fprintf(stderr, "The server requested a client certificate.\n"

+ "Presently, this process is not automated.\n"

+ "The following OpenSSL command will generate a certificate for this host:\n\n"

+ "openssl req -x509 -newkey rsa:4096 \\\n\t-keyout %s \\\n\t-out %s \\\n\t-days 36500 -nodes\n\n"

+ "Use the 'r' command to reload the page after creating this certificate.\n",

+ keypath, certpath);

+ break;

case GEMINI_STATUS_CLASS_TEMPORARY_FAILURE:

case GEMINI_STATUS_CLASS_PERMANENT_FAILURE:

requesting = false;

@@ -529,7 +579,7 @@ "TEMPORARY FAILURE" : "PERMANENT FAILURE",

resp->status, resp->meta);

break;

case GEMINI_STATUS_CLASS_SUCCESS:

- return res;

+ goto out;

}

if (requesting) {

@@ -537,6 +587,11 @@ gemini_response_finish(resp);

}

}

+out:

+ if (client_cert.key) {

+ free(client_cert.key);

+ }

+ free(scheme);

return res;

}