diff options
Diffstat (limited to 'enroll.c')
-rw-r--r-- | enroll.c | 173 |
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); |