~rcr/rirc

99c6c5926ea32adacf36d0d48ef4b3496afc0e71 — Richard Robbins 1 year, 2 months ago f29a53c
add --tls-verify flag
M .builds/alpine.yml => .builds/alpine.yml +1 -1
@@ 15,4 15,4 @@ tasks:
      git submodule update --recursive
  - build: |
      cd rirc
      make rirc debug test
      make rirc rirc.debug test

M .builds/debian.yml => .builds/debian.yml +2 -2
@@ 24,7 24,7 @@ tasks:
      git submodule update --recursive
  - build: |
      cd rirc
      make rirc debug test
      make rirc rirc.debug test
  - static-analysis: |
      cd rirc
      branch=$(git name-rev --name-only HEAD)


@@ 53,5 53,5 @@ tasks:
      echo >> sonar-project.properties "sonar.projectKey=rirc"
      echo >> sonar-project.properties "sonar.sources=src"
      echo >> sonar-project.properties "sonar.tests=test"
      ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw-output make clean debug test
      ./build-wrapper-linux-x86/build-wrapper-linux-x86-64 --out-dir bw-output make clean rirc.debug test
      ./sonar-scanner-$SONAR_VER-linux/bin/sonar-scanner

M Makefile => Makefile +18 -25
@@ 1,5 1,3 @@
.POSIX:

VERSION := 0.1.2

# Release and debug build executable names


@@ 7,24 5,20 @@ BIN_R := rirc
BIN_D := rirc.debug

# Install paths
BIN_DIR = /usr/local/bin
MAN_DIR = /usr/local/share/man/man1
BIN_DIR := /usr/local/bin
MAN_DIR := /usr/local/share/man/man1

STDS := \
	-std=c11 \
	-D_BSD_VISIBLE \
	-D_DARWIN_C_SOURCE \
	-D_POSIX_C_SOURCE=200809L
TLS_CONF := $(PWD)/lib/mbedtls.h
TLS_INCL := -I $(PWD)/lib/mbedtls/include/ -DMBEDTLS_CONFIG_FILE='<$(TLS_CONF)>'
TLS_LIBS := ./lib/mbedtls/library/libmbedtls.a \
            ./lib/mbedtls/library/libmbedx509.a \
            ./lib/mbedtls/library/libmbedcrypto.a

TLS_CONF := ./lib/mbedtls.h
TLS_LIBS := \
	./lib/mbedtls/library/libmbedtls.a \
	./lib/mbedtls/library/libmbedx509.a \
	./lib/mbedtls/library/libmbedcrypto.a
STDS := -std=c11 -D_POSIX_C_SOURCE=200809L

CC := cc
PP := cc -E
CFLAGS   := $(CC_EXT) -I. $(STDS) -DVERSION=\"$(VERSION)\" -Wall -Wextra -pedantic
CFLAGS   := $(CC_EXT) $(STDS) $(TLS_INCL) -I. -DVERSION=\"$(VERSION)\" -Wall -Wextra -pedantic
CFLAGS_R := $(CFLAGS) -O2 -flto -DNDEBUG
CFLAGS_D := $(CFLAGS) -O0 -g
LDFLAGS  := $(LD_EXT) -lpthread


@@ 71,6 65,10 @@ $(DIR_B)/%.db.o: $(DIR_S)/%.c config.h
	@$(PP) $(CFLAGS_D) -MM -MP -MT $@ -MF $(@:.o=.d) $<
	@$(CC) $(CFLAGS_D) -c -o $@ $<

# Default config file
config.h:
	cp config.def.h config.h

# Gperf generated source
%.gperf.out: %.gperf
	gperf --output-file=$@ $<


@@ 82,16 80,13 @@ $(DIR_B)/%.t: $(DIR_T)/%.c
	-@rm -f $(@:.t=.td) && $(TEST_EXT) ./$@ || mv $@ $(@:.t=.td)
	@[ ! -f $(@:.t=.td) ]

# TLS libraries
$(TLS_LIBS): $(TLS_CONF)
	@CFLAGS="-I$(PWD) -DMBEDTLS_CONFIG_FILE='<$(TLS_CONF)>'" $(MAKE) -C ./lib/mbedtls clean lib

# Build directories
$(DIR_B):
	@for dir in $(patsubst $(DIR_S)/%, %, $(SUBDIRS)); do mkdir -p $(DIR_B)/$$dir; done

config.h:
	cp config.def.h config.h
# TLS libraries
$(TLS_LIBS): $(TLS_CONF)
	@CFLAGS="$(TLS_INCL)" $(MAKE) -C ./lib/mbedtls clean lib

clean:
	rm -rf $(DIR_B) $(BIN_R) $(BIN_D)


@@ 110,12 105,10 @@ uninstall:
	rm -f $(BIN_DIR)/rirc
	rm -f $(MAN_DIR)/rirc.1

all:   $(BIN_R)
debug: $(BIN_D)
test:  $(DIR_B) $(OBJS_G) $(OBJS_T)
test: $(DIR_B) $(OBJS_G) $(OBJS_T)

-include $(OBJS_R:.o=.d)
-include $(OBJS_D:.o=.d)
-include $(OBJS_T:.t=.d)

.PHONY: all clean default install uninstall test
.PHONY: clean install uninstall test

M README.md => README.md +7 -6
@@ 66,10 66,10 @@ make install
### Usage:

```
rirc [-hv] [-s server [-p port] [-w pass] [-u user] [-r real] [-n nicks] [-c chans]], ...]
rirc [-hv] [-s server [...]]

Help:
  -h, --help      Print this message and exit
Info:
  -h, --help      Print help message and exit
  -v, --version   Print rirc version and exit

Server options:


@@ 81,9 81,10 @@ Server options:
  -n, --nicks=NICKS         Comma separated list of nicks to use for SERVER
  -c, --chans=CHANNELS      Comma separated list of channels to join for SERVER

Connection options:
      --ipv4                Connect to only ipv4 addresses
      --ipv6                Connect to only ipv6 addresses
Server connection options:
   --ipv4                   Connect to server using only ipv4 addresses
   --ipv6                   Connect to server using only ipv6 addresses
   --tls-verify=<mode>      Set server TLS peer certificate verify mode
```

Commands:

M lib/mbedtls.h => lib/mbedtls.h +1 -1
@@ 111,6 111,6 @@
/* Error strings */
#define MBEDTLS_ERROR_C

#include "lib/mbedtls/include/mbedtls/check_config.h"
#include "mbedtls/check_config.h"

#endif

M rirc.1 => rirc.1 +40 -65
@@ 3,78 3,60 @@
.SH NAME
rirc \- a minimalistic irc client written in C
.SH SYNOPSIS
.B rirc
.RB [ -hv ]
.RB [ -s
.I server
.RB [ -p
.IR port ]
.RB [ -w
.IR pass ]
.RB [ -u
.IR username ]
.RB [ -r
.IR realname ]
.RB [ -n
.IR nicks ]
.RB [ -c
.IR chans "]], ...]"
\fBrirc\fR [ \fB-hv\fR ] [ \fB-s\fR \fIserver\fR [ \fB...\fR ]]
.SH DESCRIPTION
.P
rirc is a lightweight Internet Relay Chat client.
.P
rirc only supports TLS connections, the default port is 6697
.P
Customization of rirc can be accomplished by editing the
.I config.h
file and (re)compiling the source code.
Customization of rirc can be accomplished by editing the \fIconfig.h\fP file
and (re)compiling the source code.
.SH OPTIONS
.TP 5
.B "-h, --help"
Print help message and exit
.TP
.B -h, --help
Print this message and exit
.TP
.B -v, --version
.B "-v, --version"
Print rirc version and exit
.TP
.SS Server options
.TP 5
.BI "-s, --server=" server
Connect to
.I server
Connect to \fIserver\fP
.TP
.BI "-p, --port=" port
Connect to
.I server
using
.I port
Connect to \fIserver\fP using \fIport\fP
.TP
.BI "-w, --pass=" pass
Connect to
.I server
using
.I pass
Connect to \fIserver\fP using \fIpass\fP
.TP
.BI "-u, --username=" username
Connect to
.I server
using
.I username
Connect to \fIserver\fP using \fIusername\fP
.TP
.BI "-r, --realname=" realname
Connect to
.I server
using
.I realname
Connect to \fIserver\fP using \fIrealname\fP
.TP
.BI "-n, --nicks=" nicks
Comma separated list of
.I nicks
to use for
.I server
Comma separated list of \fInicks\fP to use for \fIserver\fP
.TP
.BI "-c, --chans=" chans
Comma separated list of
.I chans
to join on
.I server
Comma separated list of \fIchannels\fP to join for \fIserver\fP
.SS Server connection options
.TP 5
.B --ipv4
Connect to \fIserver\fP using only ipv4 addresses
.TP
.B --ipv6
Connect to \fIserver\fP using only ipv6 addresses
.TP
.BI --tls-verify= mode
Set \fIserver\fP TLS peer certificate verify \fImode\fP (default: \fIrequired\fP)
.TQ
     \(bu \fIdisabled\fP - peer certificate is not verified
.TQ
     \(bu \fIoptional\fP - peer certificate is verified
.TQ
     \(bu \fIrequired\fP - peer certificate is verified, handshake is aborted on error
.SH USAGE
.TS
l .


@@ 95,10 77,10 @@ Keys:
  ^X;close current channel
  ^U;scroll current buffer up
  ^D;scroll current buffer down
   >;input cursor back
   <;input cursor forward
   ^;input history back
   v;input history forward
  <left>;input cursor back
  <right>;input cursor forward
  <up>;input history back
  <down>;input history forward
.TE

.TS


@@ 136,18 118,11 @@ IRC commands:
  /quit;[quit message]
  /raw;<message>
.TE

.SH AUTHORS
.MT mail@rcr.io
Richard 'rcr' Robbins
.ME
.SH LICENSE
See the
.I LICENSE
file in the source directory for the terms of redistribution.
.ME .
.SH SEE ALSO
See
.UR
http://rcr.io/rirc/
.UE
for additional documentation.
.UR http://rcr.io/rirc/
Additional documentation
.UE .

M scripts/compile_commands.sh => scripts/compile_commands.sh +1 -1
@@ 7,4 7,4 @@ rm -f compile_commands.json
export CC=clang
export CC_EXT="-Wno-empty-translation-unit"

bear make clean debug
bear make clean rirc.debug

M scripts/coverity_run.sh => scripts/coverity_run.sh +1 -1
@@ 21,7 21,7 @@ COVERITY_TAR="cov-int.tgz"

VERSION=$(git rev-parse --short HEAD)

PATH=$(pwd)/$1/bin:$PATH cov-build --dir "$COVERITY_OUT" make clean rirc debug test
PATH=$(pwd)/$1/bin:$PATH cov-build --dir "$COVERITY_OUT" make clean rirc rirc.debug test

tar czf "$COVERITY_TAR" "$COVERITY_OUT"


M scripts/sanitizers_build.sh => scripts/sanitizers_build.sh +2 -2
@@ 7,13 7,13 @@ export CC=clang
export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
export LD_EXT="-fsanitize=address,undefined"

make -e clean debug
make -e clean rirc.debug

mv rirc.debug rirc.debug.address

export CC_EXT="-fsanitize=address,undefined -fno-omit-frame-pointer"
export LD_EXT="-fsanitize=thread,undefined"

make -e clean debug
make -e clean rirc.debug

mv rirc.debug rirc.debug.thread

M scripts/stack_usage.sh => scripts/stack_usage.sh +1 -1
@@ 5,6 5,6 @@ set -e
export CC=gcc
export CC_EXT="-fstack-usage"

make -e clean debug
make -e clean rirc.debug

find . -name "*.su" -exec cat "{}" ";" | sort -n -k2 | column -t

M src/io.c => src/io.c +219 -176
@@ 17,15 17,12 @@
#include "rirc.h"
#include "utils/utils.h"

/* lib/mbedtls.h is the compile time config
 * and must precede the other mbedtls headers */
#include "lib/mbedtls.h"
#include "lib/mbedtls/include/mbedtls/ctr_drbg.h"
#include "lib/mbedtls/include/mbedtls/entropy.h"
#include "lib/mbedtls/include/mbedtls/error.h"
#include "lib/mbedtls/include/mbedtls/net_sockets.h"
#include "lib/mbedtls/include/mbedtls/ssl.h"
#include "lib/mbedtls/include/mbedtls/x509_crt.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/entropy.h"
#include "mbedtls/error.h"
#include "mbedtls/net_sockets.h"
#include "mbedtls/ssl.h"
#include "mbedtls/x509_crt.h"

/* RFC 2812, section 2.3 */
#ifndef IO_MESG_LEN


@@ 122,6 119,7 @@ struct connection
		IO_ST_PING, /* Socket connected, network state in question */
	} st_cur, /* current thread state */
	  st_new; /* new thread state */
	int soc;
	mbedtls_net_context tls_fd;
	mbedtls_ssl_config  tls_conf;
	mbedtls_ssl_context tls_ctx;


@@ 132,7 130,6 @@ struct connection
	unsigned rx_sleep;
};

static const char* io_tls_strerror(int, char*, size_t);
static enum io_state_t io_state_cxed(struct connection*);
static enum io_state_t io_state_cxng(struct connection*);
static enum io_state_t io_state_ping(struct connection*);


@@ 141,8 138,6 @@ static int io_cx_read(struct connection*, unsigned);
static void io_fatal(const char*, int);
static void io_sig_handle(int);
static void io_sig_init(void);
static void io_tls_init(void);
static void io_tls_term(void);
static void io_tty_init(void);
static void io_tty_term(void);
static void io_tty_winsize(void);


@@ 163,6 158,13 @@ static const char* io_strerror(char*, size_t);
static int io_net_connect(struct connection*);
static void io_net_close(int);

/* TLS */
static const char* io_tls_err(int);
static int io_tls_establish(struct connection*);
static int io_tls_x509_vrfy(struct connection*);
static void io_tls_init(void);
static void io_tls_term(void);

struct connection*
connection(const void *obj, const char *host, const char *port, uint8_t flags)
{


@@ 337,18 339,6 @@ io_stop(void)
	io_running = 0;
}

void
io_cb_lk(void)
{
	PT_LK(&io_cb_mutex);
}

void
io_cb_ul(void)
{
	PT_UL(&io_cb_mutex);
}

static void
io_tty_winsize(void)
{


@@ 396,13 386,6 @@ io_err(int err)
	}
}

static const char*
io_tls_strerror(int err, char *buf, size_t len)
{
	mbedtls_strerror(err, buf, len);
	return buf;
}

static enum io_state_t
io_state_rxng(struct connection *cx)
{


@@ 427,101 410,13 @@ io_state_rxng(struct connection *cx)
static enum io_state_t
io_state_cxng(struct connection *cx)
{
	char buf[MAX(INET6_ADDRSTRLEN, 512)];
	int ret;
	int soc;
	uint32_t cert_ret;

	io_cb_info(cx, "Connecting to %s:%s", cx->host, cx->port);

	if ((soc = io_net_connect(cx)) < 0)
		goto err_net;
	if ((cx->soc = io_net_connect(cx)) < 0)
		return IO_ST_RXNG;

	io_cb_info(cx, " ... Establishing TLS connection");

	mbedtls_net_init(&(cx->tls_fd));
	mbedtls_ssl_init(&(cx->tls_ctx));
	mbedtls_ssl_config_init(&(cx->tls_conf));

	cx->tls_fd.fd = soc;

	if ((ret = mbedtls_ssl_config_defaults(
			&(cx->tls_conf),
			MBEDTLS_SSL_IS_CLIENT,
			MBEDTLS_SSL_TRANSPORT_STREAM,
			MBEDTLS_SSL_PRESET_DEFAULT)) != 0) {
		io_cb_err(cx, " ... mbedtls_ssl_config_defaults: %s", io_tls_strerror(ret, buf, sizeof(buf)));
		goto err;
	}

	mbedtls_ssl_conf_max_version(
			&(cx->tls_conf),
			MBEDTLS_SSL_MAJOR_VERSION_3,
			MBEDTLS_SSL_MINOR_VERSION_3);

	mbedtls_ssl_conf_min_version(
			&(cx->tls_conf),
			MBEDTLS_SSL_MAJOR_VERSION_3,
			MBEDTLS_SSL_MINOR_VERSION_3);

	mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &tls_x509_crt, NULL);
	mbedtls_ssl_conf_rng(&(cx->tls_conf), mbedtls_ctr_drbg_random, &tls_ctr_drbg);

	if ((ret = mbedtls_net_set_block(&(cx->tls_fd))) != 0) {
		io_cb_err(cx, " ... mbedtls_net_set_block: %s", io_tls_strerror(ret, buf, sizeof(buf)));
		goto err;
	}

	if ((ret = mbedtls_ssl_setup(&(cx->tls_ctx), &(cx->tls_conf))) != 0) {
		io_cb_err(cx, " ... mbedtls_ssl_setup: %s", io_tls_strerror(ret, buf, sizeof(buf)));
		goto err;
	}

	if ((ret = mbedtls_ssl_set_hostname(&(cx->tls_ctx), cx->host)) != 0) {
		io_cb_err(cx, " ... mbedtls_ssl_set_hostname: %s", io_tls_strerror(ret, buf, sizeof(buf)));
		goto err;
	}

	mbedtls_ssl_set_bio(
		&(cx->tls_ctx),
		&(cx->tls_fd),
		mbedtls_net_send,
		NULL,
		mbedtls_net_recv_timeout);

	while ((ret = mbedtls_ssl_handshake(&(cx->tls_ctx))) != 0) {
		if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
			io_cb_err(cx, " ... mbedtls_ssl_handshake: %s", io_tls_strerror(ret, buf, sizeof(buf)));
			goto err;
		}
	}

	if ((cert_ret = mbedtls_ssl_get_verify_result(&(cx->tls_ctx))) != 0) {
		if (mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", cert_ret) <= 0) {
			io_cb_err(cx, " ... failed to verify cert: unknown failure");
		} else {
			io_cb_err(cx, " ... failed to verify cert: %s", buf);
		}
		goto err;
	}

	io_cb_info(cx, " ... TLS connection established");
	io_cb_info(cx, " ...   - version:     %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
	io_cb_info(cx, " ...   - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));
	if (io_tls_establish(cx) < 0)
		return IO_ST_RXNG;

	return IO_ST_CXED;

err:

	io_cb_err(cx, " ... TLS connection failure");

	mbedtls_ssl_config_free(&(cx->tls_conf));
	mbedtls_ssl_free(&(cx->tls_ctx));
	mbedtls_net_free(&(cx->tls_fd));

err_net:

	return IO_ST_RXNG;
}

static enum io_state_t


@@ 638,12 533,13 @@ io_thread(void *arg)
		switch (ST_X(st_cur, st_new)) {
			case ST_X(IO_ST_DXED, IO_ST_CXNG): /* A1 */
			case ST_X(IO_ST_RXNG, IO_ST_CXNG): /* A2,C */
				io_cb_info(cx, "Connecting to %s:%s", cx->host, cx->port);
				break;
			case ST_X(IO_ST_CXED, IO_ST_CXNG): /* F1 */
				io_cb_dxed(cx);
				break;
			case ST_X(IO_ST_PING, IO_ST_CXNG): /* F2 */
				io_cb_err(cx, "connection timeout (%u)", cx->ping);
				io_cb_err(cx, "Connection timeout (%u)", cx->ping);
				io_cb_dxed(cx);
				break;
			case ST_X(IO_ST_RXNG, IO_ST_DXED): /* B1 */


@@ 656,12 552,12 @@ io_thread(void *arg)
				io_cb_dxed(cx);
				break;
			case ST_X(IO_ST_CXNG, IO_ST_CXED): /* D */
				io_cb_info(cx, " ... Connection successful");
				io_cb_info(cx, " .. Connection successful");
				io_cb_cxed(cx);
				cx->rx_sleep = 0;
				break;
			case ST_X(IO_ST_CXNG, IO_ST_RXNG): /* E */
				io_cb_err(cx, " ... Connection failed -- retrying");
				io_cb_err(cx, " .. Connection failed -- retrying");
				break;
			case ST_X(IO_ST_CXED, IO_ST_PING): /* G */
				cx->ping = IO_PING_MIN;


@@ 739,49 635,6 @@ io_sig_init(void)
}

static void
io_tls_init(void)
{
	char buf[512];
	int err;
	struct timespec ts;

	mbedtls_ctr_drbg_init(&tls_ctr_drbg);
	mbedtls_entropy_init(&tls_entropy);
	mbedtls_x509_crt_init(&tls_x509_crt);

	if (atexit(io_tls_term) != 0)
		fatal("atexit");

	if (timespec_get(&ts, TIME_UTC) != TIME_UTC)
		fatal("timespec_get");

	if (snprintf(buf, sizeof(buf), "rirc-%lu-%lu", ts.tv_sec, ts.tv_nsec) < 0)
		fatal("snprintf");

	if ((err = mbedtls_ctr_drbg_seed(
			&tls_ctr_drbg,
			mbedtls_entropy_func,
			&tls_entropy,
			(const unsigned char *)buf,
			strlen(buf))) != 0) {
		fatal("mbedtls_ctr_drbg_seed: %s", io_tls_strerror(err, buf, sizeof(buf)));
	}

	if ((err = mbedtls_x509_crt_parse_path(&tls_x509_crt, ca_cert_path)) < 0)
		fatal("mbedtls_x509_crt_parse_path: %s", io_tls_strerror(err, buf, sizeof(buf)));
}

static void
io_tls_term(void)
{
	/* Exit handler, must return normally */

	mbedtls_ctr_drbg_free(&tls_ctr_drbg);
	mbedtls_entropy_free(&tls_entropy);
	mbedtls_x509_crt_free(&tls_x509_crt);
}

static void
io_tty_init(void)
{
	struct termios nterm;


@@ 800,7 653,7 @@ io_tty_init(void)
	if (tcsetattr(STDIN_FILENO, TCSANOW, &nterm) < 0)
		fatal("tcsetattr: %s", strerror(errno));

	if (atexit(io_tty_term) != 0)
	if (atexit(io_tty_term))
		fatal("atexit");
}



@@ 836,16 689,16 @@ io_net_connect(struct connection *cx)

	errno = 0;

	if ((ret = getaddrinfo(cx->host, cx->port, &hints, &res)) != 0) {
	if ((ret = getaddrinfo(cx->host, cx->port, &hints, &res))) {

		if (ret == EAI_SYSTEM && errno == EINTR)
			return -1;

		if (ret == EAI_SYSTEM) {
			io_cb_err(cx, " ... Failed to resolve host: %s",
			io_cb_err(cx, " .. Failed to resolve host: %s",
				io_strerror(buf, sizeof(buf)));
		} else {
			io_cb_err(cx, " ... Failed to resolve host: %s",
			io_cb_err(cx, " .. Failed to resolve host: %s",
				gai_strerror(ret));
		}



@@ 869,12 722,12 @@ io_net_connect(struct connection *cx)
	}

	if (!p && soc == -1) {
		io_cb_err(cx, " ... Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
		io_cb_err(cx, " .. Failed to obtain socket: %s", io_strerror(buf, sizeof(buf)));
		goto err;
	}

	if (!p && soc >= 0) {
		io_cb_err(cx, " ... Failed to connect: %s", io_strerror(buf, sizeof(buf)));
		io_cb_err(cx, " .. Failed to connect: %s", io_strerror(buf, sizeof(buf)));
		goto err;
	}



@@ 884,7 737,7 @@ io_net_connect(struct connection *cx)
		addr = &(((struct sockaddr_in6*)p->ai_addr)->sin6_addr);

	if (inet_ntop(p->ai_family, addr, buf, sizeof(buf)))
		io_cb_info(cx, " ... Connected [%s]", buf);
		io_cb_info(cx, " .. Connected [%s]", buf);

	ret = soc;



@@ 913,3 766,193 @@ io_strerror(char *buf, size_t buflen)

	return buf;
}

static int
io_tls_establish(struct connection *cx)
{
	int ret;

	io_cb_info(cx, " .. Establishing TLS connection");

	mbedtls_net_init(&(cx->tls_fd));
	mbedtls_ssl_init(&(cx->tls_ctx));
	mbedtls_ssl_config_init(&(cx->tls_conf));

	cx->tls_fd.fd = cx->soc;

	if ((ret = mbedtls_ssl_config_defaults(
			&(cx->tls_conf),
			MBEDTLS_SSL_IS_CLIENT,
			MBEDTLS_SSL_TRANSPORT_STREAM,
			MBEDTLS_SSL_PRESET_DEFAULT))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	mbedtls_ssl_conf_max_version(
			&(cx->tls_conf),
			MBEDTLS_SSL_MAJOR_VERSION_3,
			MBEDTLS_SSL_MINOR_VERSION_3);

	mbedtls_ssl_conf_min_version(
			&(cx->tls_conf),
			MBEDTLS_SSL_MAJOR_VERSION_3,
			MBEDTLS_SSL_MINOR_VERSION_3);

	mbedtls_ssl_conf_rng(&(cx->tls_conf), mbedtls_ctr_drbg_random, &tls_ctr_drbg);

	if (cx->flags & IO_TLS_VRFY_DISABLED) {
		mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_NONE);
	} else {
		mbedtls_ssl_conf_ca_chain(&(cx->tls_conf), &tls_x509_crt, NULL);

		if (cx->flags & IO_TLS_VRFY_OPTIONAL)
			mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_OPTIONAL);

		if (cx->flags & IO_TLS_VRFY_REQUIRED)
			mbedtls_ssl_conf_authmode(&(cx->tls_conf), MBEDTLS_SSL_VERIFY_REQUIRED);
	}

	if ((ret = mbedtls_net_set_block(&(cx->tls_fd)))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if ((ret = mbedtls_ssl_setup(&(cx->tls_ctx), &(cx->tls_conf)))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if ((ret = mbedtls_ssl_set_hostname(&(cx->tls_ctx), cx->host))) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	mbedtls_ssl_set_bio(
		&(cx->tls_ctx),
		&(cx->tls_fd),
		mbedtls_net_send,
		NULL,
		mbedtls_net_recv_timeout);

	while ((ret = mbedtls_ssl_handshake(&(cx->tls_ctx)))) {
		if (ret != MBEDTLS_ERR_SSL_WANT_READ
		 && ret != MBEDTLS_ERR_SSL_WANT_WRITE)
			break;
	}

	if (ret && cx->flags & IO_TLS_VRFY_DISABLED) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	if (io_tls_x509_vrfy(cx) < 0)
		io_cb_err(cx, " .... Unknown x509 error");

	if (ret) {
		io_cb_err(cx, " .. %s ", io_tls_err(ret));
		goto err;
	}

	io_cb_info(cx, " .. TLS connection established");
	io_cb_info(cx, " ..   - version:     %s", mbedtls_ssl_get_version(&(cx->tls_ctx)));
	io_cb_info(cx, " ..   - ciphersuite: %s", mbedtls_ssl_get_ciphersuite(&(cx->tls_ctx)));

	return 0;

err:

	io_cb_err(cx, " .. TLS connection failure");

	mbedtls_ssl_config_free(&(cx->tls_conf));
	mbedtls_ssl_free(&(cx->tls_ctx));
	mbedtls_net_free(&(cx->tls_fd));

	return -1;
}

static int
io_tls_x509_vrfy(struct connection *cx)
{
	char *s, *p;
	char buf[1024];
	uint32_t ret;

	if (!(ret = mbedtls_ssl_get_verify_result(&(cx->tls_ctx))))
		return 0;

	if (ret == (uint32_t)(-1))
		return -1;

	if (mbedtls_x509_crt_verify_info(buf, sizeof(buf), "", ret) < 0)
		return -1;

	s = buf;

	do {
		if ((p = strchr(buf, '\n')))
			*p++ = 0;

		io_cb_err(cx, " .... %s", s);

	} while ((s = p));

	return 0;
}

static const char*
io_tls_err(int err)
{
	const char *str;

	if ((str = mbedtls_high_level_strerr(err)))
		return str;

	if ((str = mbedtls_low_level_strerr(err)))
		return str;

	return "Unknown error";
}

static void
io_tls_init(void)
{
	char buf[512];
	int ret;
	struct timespec ts;

	mbedtls_ctr_drbg_init(&tls_ctr_drbg);
	mbedtls_entropy_init(&tls_entropy);
	mbedtls_x509_crt_init(&tls_x509_crt);

	if (atexit(io_tls_term))
		fatal("atexit");

	if (timespec_get(&ts, TIME_UTC) != TIME_UTC)
		fatal("timespec_get");

	if (snprintf(buf, sizeof(buf), "rirc-%lu-%lu", ts.tv_sec, ts.tv_nsec) < 0)
		fatal("snprintf");

	if ((ret = mbedtls_ctr_drbg_seed(
			&tls_ctr_drbg,
			mbedtls_entropy_func,
			&tls_entropy,
			(const unsigned char *)buf,
			strlen(buf)))) {
		fatal("mbedtls_ctr_drbg_seed: %s", io_tls_err(ret));
	}

	if ((ret = mbedtls_x509_crt_parse_path(&tls_x509_crt, ca_cert_path)) < 0)
		fatal("mbedtls_x509_crt_parse_path: %s", io_tls_err(ret));
}

static void
io_tls_term(void)
{
	/* Exit handler, must return normally */

	mbedtls_ctr_drbg_free(&tls_ctr_drbg);
	mbedtls_entropy_free(&tls_entropy);
	mbedtls_x509_crt_free(&tls_x509_crt);
}

M src/io.h => src/io.h +5 -7
@@ 108,8 108,11 @@ enum io_sig_t
	IO_SIG_SIZE
};

#define IO_IPV_4 (1 << 1)
#define IO_IPV_6 (1 << 2)
#define IO_IPV_4             (1 << 1)
#define IO_IPV_6             (1 << 2)
#define IO_TLS_VRFY_DISABLED (1 << 3)
#define IO_TLS_VRFY_OPTIONAL (1 << 4)
#define IO_TLS_VRFY_REQUIRED (1 << 5)

/* Returns a connection, or NULL if limit is reached */
struct connection* connection(


@@ 149,9 152,4 @@ void io_cb_read_soc(char*, size_t, const void*);
/* Log message callback */
void io_cb_log(const void*, enum io_log_level, const char*, ...);

#ifdef IO_INTERNAL
void io_cb_lk(void);
void io_cb_ul(void);
#endif

#endif

M src/rirc.c => src/rirc.c +54 -25
@@ 14,7 14,7 @@
#define MAX_CLI_SERVERS 16

#define arg_error(...) \
	do { fprintf(stderr, "%s: ", runtime_name); \
	do { fprintf(stderr, "%s ", runtime_name); \
	     fprintf(stderr, __VA_ARGS__); \
	     fprintf(stderr, "\n%s --help for usage\n", runtime_name); \
	     return 1; \


@@ 54,17 54,17 @@ const char *runtime_name = "rirc.debug";
const char *runtime_name = "rirc";
#endif

static const char *const rirc_usage =
static const char *const rirc_help =
"\nrirc v"VERSION" ~ Richard C. Robbins <mail@rcr.io>"
"\n"
"\nUsage:"
"\n  rirc [-hv] [-s server [-p port] [-w pass] [-n nicks] [-c chans] [-u user] [-r real]], ...]"
"\n  rirc [-hv] [-s server [...]]"
"\n"
"\nHelp:"
"\n  -h, --help      Print this message and exit"
"\nInfo:"
"\n  -h, --help      Print help message and exit"
"\n  -v, --version   Print rirc version and exit"
"\n"
"\nOptions:"
"\nServer options:"
"\n  -s, --server=SERVER       Connect to SERVER"
"\n  -p, --port=PORT           Connect to SERVER using PORT"
"\n  -w, --pass=PASS           Connect to SERVER using PASS"


@@ 72,6 72,11 @@ static const char *const rirc_usage =
"\n  -r, --realname=REALNAME   Connect to SERVER using REALNAME"
"\n  -n, --nicks=NICKS         Comma separated list of nicks to use for SERVER"
"\n  -c, --chans=CHANNELS      Comma separated list of channels to join for SERVER"
"\n"
"\nServer connection options:"
"\n   --ipv4                   Connect to server using only ipv4 addresses"
"\n   --ipv6                   Connect to server using only ipv6 addresses"
"\n   --tls-verify=<mode>      Set server TLS peer certificate verify mode"
"\n";

static const char *const rirc_version =


@@ 94,6 99,7 @@ opt_arg_str(char c)
		case 'r': return "-r/--realname";
		case '4': return "--ipv4";
		case '6': return "--ipv6";
		case 'y': return "--tls-verify";
		default:
			fatal("unknown option flag '%c'", c);
	}


@@ 129,17 135,18 @@ parse_args(int argc, char **argv)
	opterr = 0;

	struct option long_opts[] = {
		{"server",   required_argument, 0, 's'},
		{"port",     required_argument, 0, 'p'},
		{"pass",     required_argument, 0, 'w'},
		{"nicks",    required_argument, 0, 'n'},
		{"chans",    required_argument, 0, 'c'},
		{"username", required_argument, 0, 'u'},
		{"realname", required_argument, 0, 'r'},
		{"help",     no_argument,       0, 'h'},
		{"version",  no_argument,       0, 'v'},
		{"ipv4",     no_argument,       0, '4'},
		{"ipv6",     no_argument,       0, '6'},
		{"server",     required_argument, 0, 's'},
		{"port",       required_argument, 0, 'p'},
		{"pass",       required_argument, 0, 'w'},
		{"nicks",      required_argument, 0, 'n'},
		{"chans",      required_argument, 0, 'c'},
		{"username",   required_argument, 0, 'u'},
		{"realname",   required_argument, 0, 'r'},
		{"help",       no_argument,       0, 'h'},
		{"version",    no_argument,       0, 'v'},
		{"ipv4",       no_argument,       0, '4'},
		{"ipv6",       no_argument,       0, '6'},
		{"tls-verify", required_argument, 0, 'y'},
		{0, 0, 0, 0}
	};



@@ 151,7 158,8 @@ parse_args(int argc, char **argv)
		const char *chans;
		const char *username;
		const char *realname;
		uint8_t flags;
		int ipv;
		int tls_vrfy;
		struct server *s;
	} cli_servers[MAX_CLI_SERVERS];



@@ 175,7 183,8 @@ parse_args(int argc, char **argv)
				cli_servers[n_servers - 1].chans = NULL;
				cli_servers[n_servers - 1].username = NULL;
				cli_servers[n_servers - 1].realname = NULL;
				cli_servers[n_servers - 1].flags = 0;
				cli_servers[n_servers - 1].ipv = 0;
				cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
				break;

			#define CHECK_SERVER_OPTARG(OPT_C, REQ) \


@@ 214,20 223,36 @@ parse_args(int argc, char **argv)
				cli_servers[n_servers - 1].realname = optarg;
				break;

			case '4':
			case '4': /* Connect using ipv4 only */
				CHECK_SERVER_OPTARG(opt_c, 0);
				cli_servers[n_servers -1].flags |= IO_IPV_4;
				cli_servers[n_servers -1].ipv = IO_IPV_4;
				break;

			case '6':
			case '6': /* Connect using ipv6 only */
				CHECK_SERVER_OPTARG(opt_c, 0);
				cli_servers[n_servers -1].flags |= IO_IPV_6;
				cli_servers[n_servers -1].ipv = IO_IPV_6;
				break;

			case 'y': /* Set TLS peer certificate verification mode */
				CHECK_SERVER_OPTARG(opt_c, 1);
				if (!strcmp(optarg, "0") || !strcmp(optarg, "disabled")) {
					cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_DISABLED;
					break;
				}
				if (!strcmp(optarg, "1") || !strcmp(optarg, "optional")) {
					cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_OPTIONAL;
					break;
				}
				if (!strcmp(optarg, "2") || !strcmp(optarg, "required")) {
					cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
					break;
				}
				arg_error("option '--tls-verify' mode must be 'disabled', 'optional', or 'required'");

			#undef CHECK_SERVER_OPTARG

			case 'h':
				puts(rirc_usage);
				puts(rirc_help);
				exit(EXIT_SUCCESS);

			case 'v':


@@ 262,6 287,10 @@ parse_args(int argc, char **argv)

	for (size_t i = 0; i < n_servers; i++) {

		uint8_t flags =
			cli_servers[i].ipv |
			cli_servers[i].tls_vrfy;

		struct server *s = server(
			cli_servers[i].host,
			cli_servers[i].port,


@@ 270,7 299,7 @@ parse_args(int argc, char **argv)
			(cli_servers[i].realname ? cli_servers[i].realname : default_realname)
		);

		s->connection = connection(s, cli_servers[i].host, cli_servers[i].port, cli_servers[i].flags);
		s->connection = connection(s, cli_servers[i].host, cli_servers[i].port, flags);

		if (server_list_add(state_server_list(), s))
			arg_error("duplicate server: %s:%s", cli_servers[i].host, cli_servers[i].port);