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;
}