about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--enroll.c173
1 files changed, 168 insertions, 5 deletions
diff --git a/enroll.c b/enroll.c
index 4ea8289..8cb96c1 100644
--- a/enroll.c
+++ b/enroll.c
@@ -40,6 +40,7 @@
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sysexits.h>
+#include <tls.h>
 #include <unistd.h>
 
 char *readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags);
@@ -127,8 +128,15 @@ static struct {
 	char *host;
 	char *port;
 	struct addrinfo *addr;
+	struct tls_config *tls;
+	char *trust;
 } info;
 
+static void unset(char **resp) {
+	free(*resp);
+	*resp = NULL;
+}
+
 static int getNetwork(const char *def) {
 	return prompt(
 		&info.network, def,
@@ -158,7 +166,8 @@ static int getConfig(const char *configHome) {
 		"What would you like to name this configuration?\n"
 		"This is the name you will pass to catgirl to connect to %s.\n"
 		"Example: $ catgirl %s\n"
-		"The configuration will be written to: %s%s/catgirl\n",
+		"The configuration will be written to:\n"
+		"%s%s/catgirl\n",
 		info.network, prev, (suffix != configHome ? "~" : ""), suffix
 	);
 	free(name);
@@ -169,7 +178,8 @@ static int getConfig(const char *configHome) {
 	if (!access(path, F_OK)) {
 		printf(
 			"There is already a configuration named %s.\n"
-			"Please remove or rename the file: %s%s/catgirl/%s\n"
+			"Please remove or rename the file:\n"
+			"%s%s/catgirl/%s\n"
 			"Otherwise, enter a different name.\n",
 			info.config, (suffix != configHome ? "~" : ""), suffix, info.config
 		);
@@ -266,9 +276,151 @@ static int getAddr(void) {
 	return 0;
 }
 
-static void unset(char **resp) {
-	free(*resp);
-	*resp = NULL;
+static int connectSock(void) {
+	log("Connecting to %s:%s", info.host, info.port);
+	for (struct addrinfo *ai = info.addr; ai; ai = ai->ai_next) {
+		int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
+		if (sock < 0) err(EX_OSERR, "socket");
+
+		int error = connect(sock, ai->ai_addr, ai->ai_addrlen);
+		if (!error) return sock;
+
+		close(sock);
+	}
+	printf(
+		"Could not connect to %s:%s: %s.\n",
+		info.host, info.port, strerror(errno)
+	);
+	return -1;
+}
+
+static struct tls *connectTLS(int sock) {
+	struct tls *client = tls_client();
+	if (!client) errx(EX_SOFTWARE, "tls_client");
+
+	int error = tls_configure(client, info.tls);
+	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
+
+	error = tls_connect_socket(client, sock, info.host)
+		|| tls_handshake(client);
+	if (!error) return client;
+
+	printf(
+		"Could not establish TLS with %s:%s:\n"
+		"%s\n",
+		info.host, info.port, tls_error(client)
+	);
+	tls_close(client);
+	tls_free(client);
+	close(sock);
+	return NULL;
+}
+
+static int getTLS(const char *configHome) {
+	info.tls = tls_config_new();
+	if (!info.tls) errx(EX_SOFTWARE, "tls_config_new");
+
+	struct tls *client = tls_client();
+	if (!client) errx(EX_SOFTWARE, "tls_client");
+
+	int error = tls_configure(client, info.tls);
+	if (error) errx(EX_SOFTWARE, "tls_configure: %s", tls_error(client));
+
+	int sock = connectSock();
+	if (sock < 0) goto fail;
+
+	log("Performing TLS handshake");
+	error = tls_connect_socket(client, sock, info.host)
+		|| tls_handshake(client);
+	if (!error) {
+		log("TLS established");
+		tls_close(client);
+		tls_free(client);
+		close(sock);
+		return 0;
+	}
+	printf(
+		"Could not establish TLS with %s:%s:\n"
+		"%s\n",
+		info.host, info.port, tls_error(client)
+	);
+
+	// XXX: Comparing error strings, don't have a better way.
+	bool selfSigned = !strcmp(
+		tls_error(client),
+		"certificate verification failed: self-signed certificate"
+	);
+	tls_close(client);
+	tls_free(client);
+	close(sock);
+	if (!selfSigned) goto fail;
+
+	int n = asprintf(&info.trust, "%s/catgirl/%s.pem", configHome, info.host);
+	if (n < 0) err(EX_OSERR, "asprintf");
+
+	char *suffix = info.trust;
+	const char *home = getenv("HOME");
+	size_t homeLen = strlen(home);
+	if (!strncmp(suffix, home, homeLen)) {
+		suffix += homeLen;
+	}
+
+	bool trustIt;
+	int pop = yesno(
+		&trustIt, true,
+		"Trust the server's self-signed certificate?\n"
+		"If you are sure you are connecting to the right place, answer yes.\n"
+		"The current certificate will be saved and explicitly trusted for\n"
+		"future connections to this network.\n"
+		"The certificate will be written to:\n"
+		"%s%s\n",
+		(suffix != info.trust ? "~" : ""), suffix
+	);
+	if (pop || !trustIt) goto fail;
+
+	tls_config_insecure_noverifycert(info.tls);
+	tls_config_insecure_noverifyname(info.tls);
+	sock = connectSock();
+	if (sock < 0) goto fail;
+	client = connectTLS(sock);
+	if (!client) goto fail;
+
+	size_t pemLen;
+	const uint8_t *pem = tls_peer_cert_chain_pem(client, &pemLen);
+	if (!pem) {
+		printf(
+			"Could not obtain TLS certificate from %s:%s.\n",
+			info.host, info.port
+		);
+		goto fail;
+	}
+
+	log("Writing certificate to %s", info.trust);
+	FILE *file = fopen(info.trust, "w");
+	if (!file) err(EX_CANTCREAT, "%s", info.trust);
+	fprintf(file, "subject= %s\n", tls_peer_cert_subject(client));
+	fwrite(pem, pemLen, 1, file);
+	error = fclose(file);
+	if (error) err(EX_IOERR, "%s", info.trust);
+
+	tls_close(client);
+	tls_free(client);
+	close(sock);
+
+	tls_config_verify(info.tls);
+	tls_config_insecure_noverifyname(info.tls);
+	error = tls_config_set_ca_file(info.tls, info.trust);
+	if (error) {
+		errx(EX_SOFTWARE, "%s: %s", info.trust, tls_config_error(info.tls));
+	}
+
+	return 0;
+
+fail:
+	unset(&info.trust);
+	tls_config_free(info.tls);
+	info.tls = NULL;
+	return -1;
 }
 
 int main(int argc, char *argv[]) {
@@ -325,6 +477,14 @@ int main(int argc, char *argv[]) {
 				unset(&info.host);
 				unset(&info.port);
 			}
+		} else if (!info.tls) {
+			pop = getTLS(configHome);
+			if (pop) {
+				freeaddrinfo(info.addr);
+				info.addr = NULL;
+				unset(&info.host);
+				unset(&info.port);
+			}
 		} else {
 			break;
 		}
@@ -339,6 +499,9 @@ int main(int argc, char *argv[]) {
 	if (strcmp(info.port, "6697")) {
 		fprintf(file, "port = %s\n", info.port);
 	}
+	if (info.trust) {
+		fprintf(file, "trust = %s.pem", info.host);
+	}
 
 	error = fclose(file);
 	if (error) err(EX_IOERR, "%s", path);