~rcr/rirc

9df210287397c1d342518f9a6faf5fac7fe346dc — Richard Robbins 1 year, 1 month ago 99c6c59
add --tls-disable flag
8 files changed, 113 insertions(+), 87 deletions(-)

M CHANGELOG
M README.md
M rirc.1
M src/handlers/ircv3.h
M src/io.c
M src/io.h
M src/rirc.c
M test/io.mock.c
M CHANGELOG => CHANGELOG +2 -0
@@ 6,6 6,8 @@ Summary of notable changes and features
 - add command :disconnect
 - add cli option `--ipv4`
 - add cli option `--ipv6`
 - add cli option `--tls-disable`
 - add cli option `--tls-verify`
 - add SSL support
    - add mbedtls git submodule
    - add CA_CERT_PATH define to config.h

M README.md => README.md +3 -2
@@ 28,7 28,7 @@

A minimalistic irc client written in C.

rirc supports only TLS connections, the default port is 6697
Connections are TLS enabled over port 6697 by default.

### Configuring:



@@ 84,7 84,8 @@ Server options:
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
   --tls-disable            Set server TLS disabled
   --tls-verify=<mode>      Set server TLS peer certificate verification mode
```

Commands:

M rirc.1 => rirc.1 +13 -14
@@ 5,13 5,10 @@ rirc \- a minimalistic irc client written in C
.SH SYNOPSIS
\fBrirc\fR [ \fB-hv\fR ] [ \fB-s\fR \fIserver\fR [ \fB...\fR ]]
.SH DESCRIPTION
.P
.PP
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 \fIconfig.h\fP file
and (re)compiling the source code.
.PP
Connections are TLS enabled over port 6697 by default.
.SH OPTIONS
.TP 5
.B "-h, --help"


@@ 49,14 46,16 @@ Connect to \fIserver\fP using only ipv4 addresses
.B --ipv6
Connect to \fIserver\fP using only ipv6 addresses
.TP
.B --tls-disable
Set \fIserver\fP TLS disabled, default port to 6667
.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
Set \fIserver\fP TLS peer certificate verification \fImode\fP:
.EX
\(bu \fIdisabled\fP - cert is not verified
\(bu \fIoptional\fP - cert is verified, handshake continues on error
\(bu \fIrequired\fP - cert is verified, handshake is aborted on error (default)
.EE
.SH USAGE
.TS
l .


@@ 96,7 95,7 @@ Commands:
  :clear;
  :close;
  :connect;[host [port] [pass] [user] [real]]
  :disconnect
  :disconnect;
  :quit;
.TE


M src/handlers/ircv3.h => src/handlers/ircv3.h +0 -13
@@ 1,16 1,3 @@
/* TODO
 *  - cap commands (after registration)
 *      - /cap-req [-]cap [...]
 *  - cap setting for auto on connect/NEW
 *      - :set, set/unset auto, and for printing state
 *  - LS caps:
 *      [name] [set/unset] [auto]
 *      [name] [unsupported]
 *  - LIST caps:
 *      [name] [auto]
 *  - CAP args, e.g. cap=foo,bar
 */

#ifndef IRCV3_H
#define IRCV3_H


M src/io.c => src/io.c +44 -23
@@ 125,7 125,7 @@ struct connection
	mbedtls_ssl_context tls_ctx;
	pthread_mutex_t mtx;
	pthread_t tid;
	uint8_t flags;
	uint32_t flags;
	unsigned ping;
	unsigned rx_sleep;
};


@@ 166,7 166,7 @@ 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)
connection(const void *obj, const char *host, const char *port, uint32_t flags)
{
	struct connection *cx;



@@ 280,20 280,34 @@ io_sendf(struct connection *cx, const char *fmt, ...)
	ret = 0;
	written = 0;

	do {
		if ((ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret)) < 0) {
			switch (ret) {
				case MBEDTLS_ERR_SSL_WANT_READ:
				case MBEDTLS_ERR_SSL_WANT_WRITE:
					ret = 0;
					continue;
				default:
					io_dx(cx);
					io_cx(cx);
					return IO_ERR_SSL_WRITE;
	if (cx->flags & IO_TLS_ENABLED) {
		do {
			if ((ret = mbedtls_ssl_write(&(cx->tls_ctx), sendbuf + ret, len - ret)) < 0) {
				switch (ret) {
					case MBEDTLS_ERR_SSL_WANT_READ:
					case MBEDTLS_ERR_SSL_WANT_WRITE:
						ret = 0;
						continue;
					default:
						io_dx(cx);
						io_cx(cx);
						return IO_ERR_SSL_WRITE;
				}
			}
		}
	} while ((written += ret) < len);
		} while ((written += ret) < len);
	} else {
		do {
			if ((ret = send(cx->soc, sendbuf + ret, len - ret, 0)) == -1) {
				switch (errno) {
					case EINTR:
						ret = 0;
						continue;
					default:
						return IO_ERR_SSL_WRITE;
				}
			}
		} while ((written += ret) < len);
	}

	return IO_ERR_NONE;
}


@@ 413,7 427,7 @@ io_state_cxng(struct connection *cx)
	if ((cx->soc = io_net_connect(cx)) < 0)
		return IO_ST_RXNG;

	if (io_tls_establish(cx) < 0)
	if ((cx->flags & IO_TLS_ENABLED) && io_tls_establish(cx) < 0)
		return IO_ST_RXNG;

	return IO_ST_CXED;


@@ 584,14 598,21 @@ static int
io_cx_read(struct connection *cx, unsigned timeout)
{
	int ret;
	unsigned char ssl_readbuf[1024];

	mbedtls_ssl_conf_read_timeout(&(cx->tls_conf), SEC_IN_MS(timeout));
	unsigned char buf[1024];

	if ((ret = mbedtls_ssl_read(&(cx->tls_ctx), ssl_readbuf, sizeof(ssl_readbuf))) > 0) {
		PT_LK(&io_cb_mutex);
		io_cb_read_soc((char *)ssl_readbuf, (size_t)ret,  cx->obj);
		PT_UL(&io_cb_mutex);
	if (cx->flags & IO_TLS_ENABLED) {
		mbedtls_ssl_conf_read_timeout(&(cx->tls_conf), SEC_IN_MS(timeout));
		if ((ret = mbedtls_ssl_read(&(cx->tls_ctx), buf, sizeof(buf))) > 0) {
			PT_LK(&io_cb_mutex);
			io_cb_read_soc((char *)buf, (size_t)ret,  cx->obj);
			PT_UL(&io_cb_mutex);
		}
	} else {
		while ((ret = recv(cx->soc, buf, sizeof(buf), 0)) > 0) {
			PT_LK(&io_cb_mutex);
			io_cb_read_soc((char *)buf, (size_t)ret,  cx->obj);
			PT_UL(&io_cb_mutex);
		}
	}

	return ret;

M src/io.h => src/io.h +9 -6
@@ 108,18 108,21 @@ enum io_sig_t
	IO_SIG_SIZE
};

#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)
#define IO_IPV_UNSPEC        (1 << 1)
#define IO_IPV_4             (1 << 2)
#define IO_IPV_6             (1 << 3)
#define IO_TLS_ENABLED       (1 << 4)
#define IO_TLS_DISABLED      (1 << 5)
#define IO_TLS_VRFY_DISABLED (1 << 6)
#define IO_TLS_VRFY_OPTIONAL (1 << 7)
#define IO_TLS_VRFY_REQUIRED (1 << 8)

/* Returns a connection, or NULL if limit is reached */
struct connection* connection(
	const void*, /* callback object */
	const char*, /* host */
	const char*, /* port */
	uint8_t);    /* flags */
	uint32_t);   /* flags */

void connection_free(struct connection*);


M src/rirc.c => src/rirc.c +41 -28
@@ 17,7 17,7 @@
	do { fprintf(stderr, "%s ", runtime_name); \
	     fprintf(stderr, __VA_ARGS__); \
	     fprintf(stderr, "\n%s --help for usage\n", runtime_name); \
	     return 1; \
	     return -1; \
	} while (0)

static const char* opt_arg_str(char);


@@ 76,7 76,8 @@ static const char *const rirc_help =
"\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   --tls-disable            Set server TLS disabled"
"\n   --tls-verify=<mode>      Set server TLS peer certificate verification mode"
"\n";

static const char *const rirc_version =


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


@@ 127,26 129,22 @@ parse_args(int argc, char **argv)

	size_t n_servers = 0;

	if (argc > 0)
		runtime_name = argv[0];

	srand(time(NULL));

	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'},
		{"tls-verify", required_argument, 0, 'y'},
		{"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-disable", no_argument,       0, 'x'},
		{"tls-verify",  required_argument, 0, 'y'},
		{0, 0, 0, 0}
	};



@@ 159,11 157,11 @@ parse_args(int argc, char **argv)
		const char *username;
		const char *realname;
		int ipv;
		int tls;
		int tls_vrfy;
		struct server *s;
	} cli_servers[MAX_CLI_SERVERS];

	/* FIXME: getopt_long is a GNU extension */
	while (0 < (opt_c = getopt_long(argc, argv, ":s:p:w:n:c:r:u:hv", long_opts, &opt_i))) {

		switch (opt_c) {


@@ 176,14 174,15 @@ parse_args(int argc, char **argv)
				if (++n_servers == MAX_CLI_SERVERS)
					arg_error("exceeded maximum number of servers (%d)", MAX_CLI_SERVERS);

				cli_servers[n_servers - 1].host = optarg;
				cli_servers[n_servers - 1].port = "6697";
				cli_servers[n_servers - 1].pass = NULL;
				cli_servers[n_servers - 1].nicks = NULL;
				cli_servers[n_servers - 1].chans = NULL;
				cli_servers[n_servers - 1].host     = optarg;
				cli_servers[n_servers - 1].port     = NULL;
				cli_servers[n_servers - 1].pass     = NULL;
				cli_servers[n_servers - 1].nicks    = NULL;
				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].ipv = 0;
				cli_servers[n_servers - 1].ipv      = IO_IPV_UNSPEC;
				cli_servers[n_servers - 1].tls      = IO_TLS_ENABLED;
				cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
				break;



@@ 233,7 232,12 @@ parse_args(int argc, char **argv)
				cli_servers[n_servers -1].ipv = IO_IPV_6;
				break;

			case 'y': /* Set TLS peer certificate verification mode */
			case 'x': /* Set server TLS disabled */
				CHECK_SERVER_OPTARG(opt_c, 0);
				cli_servers[n_servers -1].tls = IO_TLS_DISABLED;
				break;

			case 'y': /* Set server 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;


@@ 287,10 291,14 @@ parse_args(int argc, char **argv)

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

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

		if (cli_servers[i].port == NULL)
			cli_servers[i].port = (cli_servers[i].tls == IO_TLS_ENABLED) ? "6697" : "6667";

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


@@ 329,6 337,11 @@ main(int argc, char **argv)
{
	int ret;

	if (argc > 0)
		runtime_name = argv[0];

	srand(time(NULL));

	if ((ret = parse_args(argc, argv)) == 0) {
		io_start();
		draw_term();

M test/io.mock.c => test/io.mock.c +1 -1
@@ 35,7 35,7 @@ io_sendf(struct connection *c, const char *fmt, ...)
}

struct connection*
connection(const void *o, const char *h, const char *p, uint8_t f)
connection(const void *o, const char *h, const char *p, uint32_t f)
{
	UNUSED(o);
	UNUSED(h);