commit 402e1386334d70264a91e424cd43286658279bf9
parent d17527b115335a229754594c557db7cebea10eda
Author: Andrew Kloet <andrew@kloet.net>
Date: Sun, 29 Mar 2026 16:02:44 -0400
add tls hostname verification
Diffstat:
| M | nio.1 | | | 26 | ++++++++++++++------------ |
| M | nio.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;