nio

a simple irc client
Download | Log | Files | Refs | README

commit 402e1386334d70264a91e424cd43286658279bf9
parent d17527b115335a229754594c557db7cebea10eda
Author: Andrew Kloet <andrew@kloet.net>
Date:   Sun, 29 Mar 2026 16:02:44 -0400

add tls hostname verification

Diffstat:
Mnio.1 | 26++++++++++++++------------
Mnio.c | 72++++++++++++++++++++++++++++++++++++++++++++----------------------------
2 files changed, 58 insertions(+), 40 deletions(-)

diff --git a/nio.1 b/nio.1 @@ -1,4 +1,4 @@ -.Dd March 28, 2026 +.Dd March 29, 2026 .Dt NIO 1 .Os .Sh NAME @@ -7,7 +7,7 @@ . .Sh SYNOPSIS .Nm nio -.Op Fl hT +.Op Fl TV .Op Fl l Ar logfile .Op Fl n Ar nick .Op Fl p Ar port @@ -19,15 +19,13 @@ .Nm is a multiplexing curses interface for IRC featuring SASL authentication, infinite scrollback, and automatic reconnection. -. -.Ss Options + +The options are as follows: .Bl -tag -width Ds -.It Fl c Ar certfile -Use -.Ar certfile -as a TLS client certificate. -This is used for both establishing the encrypted connection and for SASL -EXTERNAL (CertFP) authentication. +.It Fl T +Disable TLS and connect using a plaintext socket. +.It Fl V +Disable TLS hostname validation. .It Fl l Ar logfile Append all IRC activity, including timestamps and channel names, to .Ar logfile . @@ -48,13 +46,17 @@ Defaults to 6697. Connect to the IRC server at .Ar server . Defaults to irc.oftc.net. -.It Fl T -Disable SSL/TLS and connect using a plaintext socket. .It Fl u Ar user Set the IRC username (ident). Defaults to the value of the .Ev USER environment variable, or "anonymous". +.It Fl c Ar certfile +Use +.Ar certfile +as a TLS client certificate. +This is used for both establishing the encrypted connection and for SASL +EXTERNAL (CertFP) authentication. .El . .Sh COMMANDS diff --git a/nio.c b/nio.c @@ -22,6 +22,7 @@ #include <locale.h> #include <wchar.h> #include <openssl/ssl.h> +#include <openssl/x509v3.h> #undef CTRL #define CTRL(x) (x & 037) @@ -53,15 +54,15 @@ struct { }; enum { - ChanLen = 64, - LineLen = 512, - MaxChans = 16, - BufSz = 2048, - LogSz = 4096, - MaxRecons = 10, /* -1 for infinitely many */ - PingDelay = 6, - UtfSz = 4, - RuneInvalid = 0xFFFD, + ChanLen = 64, + LineLen = 512, + MaxChans = 16, + BufSz = 2048, + LogSz = 4096, + MaxRecons = 10, /* -1 for infinitely many */ + PingDelay = 6, + UtfSz = 4, + RuneInvalid = 0xFFFD, }; typedef wchar_t Rune; @@ -82,6 +83,7 @@ static struct Chan { } chl[MaxChans]; static int ssl = 1; +static int sslverify = 1; static struct { int fd; SSL *ssl; @@ -118,8 +120,7 @@ utf8validate(Rune *u, size_t i) { if (*u < utfmin[i] || *u > utfmax[i] || (0xD800 <= *u && *u <= 0xDFFF)) *u = RuneInvalid; - for (i = 1; *u > utfmax[i]; ++i) - ; + for (i = 1; *u > utfmax[i]; ++i); return i; } @@ -209,8 +210,7 @@ srd(void) rd = SSL_read(srv.ssl, p, BufSz - (p - l)); else rd = read(srv.fd, p, BufSz - (p - l)); - if (rd <= 0) - return 0; + if (rd <= 0) return 0; p += rd; for (;;) { /* Cycle on all received lines. */ if (!(s = memchr(l, '\n', p - l))) @@ -272,25 +272,38 @@ dial(const char *host, const char *service) if ((srv.fd = fd) == -1) return "Cannot connect to host."; if (ssl) { - SSL_load_error_strings(); SSL_library_init(); + SSL_load_error_strings(); srv.ctx = SSL_CTX_new(TLS_client_method()); - if (!srv.ctx) - return "Could not initialize ssl context."; + if (!srv.ctx) return "Could not initialize ssl context."; + if (sslverify) { + SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_PEER, NULL); + if (SSL_CTX_set_default_verify_paths(srv.ctx) != 1) + return "Could not load default system trust store."; + } srv.ssl = SSL_new(srv.ctx); + if (!srv.ssl) return "Could not create SSL object."; + if (sslverify) { + SSL_set_hostflags(srv.ssl, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + if (!SSL_set1_host(srv.ssl, host)) + return "Could not set expected hostname."; + SSL_set_tlsext_host_name(srv.ssl, host); + } else { + SSL_CTX_set_verify(srv.ctx, SSL_VERIFY_NONE, NULL); + } if (!is_empty(cert)) { - if (access(cert, R_OK) != 0) - return "Certificate file does not exist or permission denied"; - if (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0) - return "Failed to load certificate chain from file"; + if (SSL_use_certificate_chain_file(srv.ssl, cert) <= 0) + return "Failed to load certificate chain."; if (SSL_use_PrivateKey_file(srv.ssl, cert, SSL_FILETYPE_PEM) <= 0) - return "Failed to load private key from file"; - if (!SSL_check_private_key(srv.ssl)) - return "Private key does not match the certificate"; + return "Failed to load private key."; + } + SSL_set_fd(srv.ssl, srv.fd); + if (SSL_connect(srv.ssl) <= 0) { + long verify_err = SSL_get_verify_result(srv.ssl); + if (verify_err != X509_V_OK) + return (char *)X509_verify_cert_error_string(verify_err); + return "SSL handshake failed."; } - if (SSL_set_fd(srv.ssl, srv.fd) == 0 - || SSL_connect(srv.ssl) != 1) - return "Could not connect with ssl."; } return 0; } @@ -863,12 +876,12 @@ main(int argc, char *argv[]) int o, reconn, ping; signal(SIGPIPE, SIG_IGN); - while ((o = getopt(argc, argv, "hT:n:c:u:s:p:l:")) >= 0) + while ((o = getopt(argc, argv, "hTVn:c:u:s:p:l:")) >= 0) switch (o) { case 'h': case '?': usage: - fputs("usage: nio [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-c certficiate] [-hT]\n", stderr); + fputs("usage: nio [-n NICK] [-u USER] [-s SERVER] [-p PORT] [-l LOGFILE ] [-c certficiate] [-hTV]\n", stderr); exit(0); case 'l': if (!(logfp = fopen(optarg, "a"))) @@ -881,6 +894,9 @@ main(int argc, char *argv[]) case 'T': ssl = 0; break; + case 'V': + sslverify = 0; + break; case 'u': user = optarg; break;