~rcr/rirc

f7cb926d7ea285980caf5e4f4e88e3739e673a57 — Richard Robbins 2 months ago 1c14557
add --sasl, --sasl-user, --sasl-pass flags
M README.md => README.md +5 -0
@@ 86,6 86,11 @@ Server connection options:
   --tls-ca-path=PATH       Set server TLS CA cert directory path
   --tls-cert=PATH          Set server TLS client cert file path
   --tls-verify=MODE        Set server TLS peer certificate verification mode

Server authentication options:
   --sasl=METHOD            Authenticate with SASL method
   --sasl-user=USER         Authenticate with SASL user
   --sasl-pass=PASS         Authenticate with SASL pass
```

Commands:

M docs/rirc.1 => docs/rirc.1 +13 -0
@@ 68,6 68,19 @@ Set \fIserver\fP TLS peer certificate verification \fImode\fP:
\(bu \fIoptional\fP - cert is verified, handshake continues on error
\(bu \fIrequired\fP - cert is verified, handshake is aborted on error (default)
.EE
.SS Server authentication options
.TP
.BI --sasl= method
Authenticate with SASL \fImethod\fP:
.EX
\(bu \fIplain\fP - requires \fI--sasl-user\fP and \fI--sasl-pass\fP
.EE
.TP
.BI --sasl-user= user
Authenticate with SASL \fIuser\fP
.TP
.BI --sasl-pass= pass
Authenticate with SASL \fIpass\fP
.SH USAGE
.TS
l .

M src/components/ircv3.c => src/components/ircv3.c +18 -0
@@ 42,3 42,21 @@ ircv3_caps_reset(struct ircv3_caps *caps)
	IRCV3_CAPS
	#undef X
}

void
ircv3_sasl(struct ircv3_sasl *sasl)
{
	memset(sasl, 0, sizeof(*sasl));

	sasl->method = IRCV3_SASL_METHOD_NONE;
	sasl->state  = IRCV3_SASL_STATE_NONE;
}

void
ircv3_sasl_reset(struct ircv3_sasl *sasl)
{
	free((void *)sasl->user);
	free((void *)sasl->pass);

	ircv3_sasl(sasl);
}

M src/components/ircv3.h => src/components/ircv3.h +18 -0
@@ 43,9 43,27 @@ struct ircv3_caps
	#undef X
};

struct ircv3_sasl
{
	enum {
		IRCV3_SASL_METHOD_NONE,
		IRCV3_SASL_METHOD_PLAIN,
	} method;
	enum {
		IRCV3_SASL_STATE_NONE,
		IRCV3_SASL_STATE_REQ_METHOD,
		IRCV3_SASL_STATE_AUTHENTICATED,
	} state;
	const char *user;
	const char *pass;
};

struct ircv3_cap* ircv3_cap_get(struct ircv3_caps*, const char*);

void ircv3_caps(struct ircv3_caps*);
void ircv3_caps_reset(struct ircv3_caps*);

void ircv3_sasl(struct ircv3_sasl*);
void ircv3_sasl_reset(struct ircv3_sasl*);

#endif

M src/components/server.c => src/components/server.c +17 -0
@@ 7,6 7,7 @@
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

#define HANDLED_005 \
	X(CASEMAPPING)  \


@@ 52,6 53,7 @@ server(
	s->casemapping = CASEMAPPING_RFC1459;
	s->mode_str.type = MODE_STR_USERMODE;
	ircv3_caps(&(s->ircv3_caps));
	ircv3_sasl(&(s->ircv3_sasl));
	mode_cfg(&(s->mode_cfg), NULL, MODE_CFG_DEFAULTS);

	s->channel = channel(host, CHANNEL_T_SERVER);


@@ 139,6 141,7 @@ void
server_reset(struct server *s)
{
	ircv3_caps_reset(&(s->ircv3_caps));
	ircv3_sasl_reset(&(s->ircv3_sasl));
	mode_reset(&(s->usermodes), &(s->mode_str));
	s->ping = 0;
	s->quitting = 0;


@@ 322,6 325,20 @@ server_set_005(struct server *s, char *str)
	}
}

void
server_set_sasl(struct server *s, const char *method, const char *user, const char *pass)
{
	if (!strcasecmp(method, "PLAIN")) {

		free((void *)s->ircv3_sasl.user);
		free((void *)s->ircv3_sasl.pass);

		s->ircv3_sasl.method = IRCV3_SASL_METHOD_PLAIN;
		s->ircv3_sasl.user = (user ? strdup(user) : NULL);
		s->ircv3_sasl.pass = (pass ? strdup(pass) : NULL);
	}
}

static int
server_cmp(const struct server *s, const char *host, const char *port)
{

M src/components/server.h => src/components/server.h +2 -0
@@ 28,6 28,7 @@ struct server
	struct channel *channel;
	struct channel_list clist;
	struct ircv3_caps ircv3_caps;
	struct ircv3_sasl ircv3_sasl;
	struct mode usermodes;
	struct mode_str mode_str;
	struct mode_cfg mode_cfg;


@@ 70,6 71,7 @@ int server_set_chans(struct server*, const char*);
int server_set_nicks(struct server*, const char*);
void server_set_004(struct server*, char*);
void server_set_005(struct server*, char*);
void server_set_sasl(struct server*, const char*, const char*, const char*);

void server_nick_set(struct server*, const char*);
void server_nicks_next(struct server*);

M src/rirc.c => src/rirc.c +43 -0
@@ 89,6 89,11 @@ static const char *const rirc_help =
"\n   --tls-ca-path=PATH       Set server TLS CA cert directory path"
"\n   --tls-cert=PATH          Set server TLS client cert file path"
"\n   --tls-verify=MODE        Set server TLS peer certificate verification mode"
"\n"
"\nServer authentication options:"
"\n   --sasl=METHOD            Authenticate with SASL method"
"\n   --sasl-user=USER         Authenticate with SASL user"
"\n   --sasl-pass=PASS         Authenticate with SASL pass"
"\n";

static const char *const rirc_version =


@@ 117,6 122,9 @@ rirc_opt_str(char c)
		case '4': return "--tls-ca-path";
		case '5': return "--tls-cert";
		case '6': return "--tls-verify";
		case '7': return "--sasl";
		case '8': return "--sasl-user";
		case '9': return "--sasl-pass";
		default:
			fatal("unknown option flag '%c'", c);
	}


@@ 156,6 164,9 @@ rirc_parse_args(int argc, char **argv)
		const char *tls_ca_file;
		const char *tls_ca_path;
		const char *tls_cert;
		const char *sasl;
		const char *sasl_user;
		const char *sasl_pass;
		int ipv;
		int tls;
		int tls_vrfy;


@@ 180,6 191,9 @@ rirc_parse_args(int argc, char **argv)
		{"tls-ca-path", required_argument, 0, '4'},
		{"tls-cert",    required_argument, 0, '5'},
		{"tls-verify",  required_argument, 0, '6'},
		{"sasl",        required_argument, 0, '7'},
		{"sasl-user",   required_argument, 0, '8'},
		{"sasl-pass",   required_argument, 0, '9'},
		{0, 0, 0, 0}
	};



@@ 212,6 226,9 @@ rirc_parse_args(int argc, char **argv)
				cli_servers[n_servers - 1].tls_ca_file = NULL;
				cli_servers[n_servers - 1].tls_ca_path = NULL;
				cli_servers[n_servers - 1].tls_cert    = NULL;
				cli_servers[n_servers - 1].sasl        = NULL;
				cli_servers[n_servers - 1].sasl_user   = NULL;
				cli_servers[n_servers - 1].sasl_pass   = NULL;
				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;


@@ 309,6 326,24 @@ rirc_parse_args(int argc, char **argv)
				arg_error("invalid option for '--tls-verify' '%s'", optarg);
				return -1;

			case '7': /* Authenticate with SASL method */
				CHECK_SERVER_OPTARG(opt_c, 1);
				if (!strcmp(optarg, "PLAIN")) {
					cli_servers[n_servers - 1].sasl = optarg;
					break;
				}
				arg_error("invalid option for '--sasl' '%s'", optarg);
				break;

			case '8': /* Authenticate with SASL user */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].sasl_user = optarg;
				break;

			case '9': /* Authenticate with SASL pass */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].sasl_pass = optarg;
				break;

			#undef CHECK_SERVER_OPTARG



@@ 379,6 414,14 @@ rirc_parse_args(int argc, char **argv)
			return -1;
		}

		if (cli_servers[i].sasl) {
			server_set_sasl(
				cli_servers[i].s,
				cli_servers[i].sasl,
				cli_servers[i].sasl_user,
				cli_servers[i].sasl_pass);
		}

		channel_set_current(cli_servers[i].s->channel);
	}


M src/state.c => src/state.c +24 -0
@@ 641,6 641,9 @@ command(struct channel *c, char *buf)
			const char *tls_ca_file = NULL;
			const char *tls_ca_path = NULL;
			const char *tls_cert    = NULL;
			const char *sasl        = NULL;
			const char *sasl_user   = NULL;
			const char *sasl_pass   = NULL;
			int ipv                 = IO_IPV_UNSPEC;
			int tls                 = IO_TLS_ENABLED;
			int tls_vrfy            = IO_TLS_VRFY_REQUIRED;


@@ 720,6 723,24 @@ command(struct channel *c, char *buf)
						action(action_error, "connect: invalid option for '--tls-verify' '%s'", str);
						return;
					}
				} else if (!strcmp(str, "--sasl")) {
					if (!(sasl = irc_strsep(&buf))) {
						action(action_error, "connect: '--sasl' requires an argument");
						return;
					} else if (strcasecmp(sasl, "PLAIN")) {
						action(action_error, "connect: invalid option for '--sasl' '%s'", sasl);
						return;
					}
				} else if (!strcmp(str, "--sasl-user")) {
					if (!(sasl_user = irc_strsep(&buf))) {
						action(action_error, "connect: '--sasl-user' requires an argument");
						return;
					}
				} else if (!strcmp(str, "--sasl-pass")) {
					if (!(sasl_pass = irc_strsep(&buf))) {
						action(action_error, "connect: '--sasl-pass' requires an argument");
						return;
					}
				} else {
					action(action_error, "connect: unknown option '%s'", str);
					return;


@@ 749,6 770,9 @@ command(struct channel *c, char *buf)
				return;
			}

			if (sasl)
				server_set_sasl(s, sasl, sasl_user, sasl_pass);

			s->connection = connection(
				s,
				host,

M test/components/server.c => test/components/server.c +7 -0
@@ 213,6 213,12 @@ test_server_set_nicks(void)
}

static void
test_server_set_sasl(void)
{
	// TODO
}

static void
test_parse_005(void)
{
	/* Test numeric 005 parsing  */


@@ 301,6 307,7 @@ main(void)
		TESTCASE(test_server_list),
		TESTCASE(test_server_set_chans),
		TESTCASE(test_server_set_nicks),
		TESTCASE(test_server_set_sasl),
		TESTCASE(test_parse_005)
	};


M test/state.c => test/state.c +28 -0
@@ 261,6 261,21 @@ test_command_connect(void)
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	INP_COMMAND(":connect host --sasl");
	assert_strcmp(action_message(), "connect: '--sasl' requires an argument");
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	INP_COMMAND(":connect host --sasl-user");
	assert_strcmp(action_message(), "connect: '--sasl-user' requires an argument");
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	INP_COMMAND(":connect host --sasl-pass");
	assert_strcmp(action_message(), "connect: '--sasl-pass' requires an argument");
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	/* Test invalid arguments */
	INP_COMMAND(":connect host xyz");
	assert_strcmp(action_message(), ":connect [hostname [options]]");


@@ 277,6 292,11 @@ test_command_connect(void)
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	INP_COMMAND(":connect host --sasl xyz");
	assert_strcmp(action_message(), "connect: invalid option for '--sasl' 'xyz'");
	assert_strcmp(current_channel()->name, "host-1");
	INP_C(0x0A);

	INP_COMMAND(":connect host-1");
	assert_strcmp(action_message(), "connect: duplicate server: host-1:6697");
	assert_strcmp(current_channel()->name, "host-1");


@@ 328,8 348,13 @@ test_command_connect(void)
		" --tls-verify disabled"
		" --tls-verify optional"
		" --tls-verify required"
		" --sasl plain"
		" --sasl-user sasl_user"
		" --sasl-pass sasl_pass"
	);

	assert_strcmp(action_message(), NULL);

	s = current_channel()->server;

	assert_strcmp(s->host, "host-3");


@@ 341,6 366,9 @@ test_command_connect(void)
	assert_strcmp(s->nicks.set[0], "x0");
	assert_strcmp(s->nicks.set[1], "y1");
	assert_strcmp(s->nicks.set[2], "z2");
	assert_eq(s->ircv3_sasl.method, IRCV3_SASL_METHOD_PLAIN);
	assert_strcmp(s->ircv3_sasl.user, "sasl_user");
	assert_strcmp(s->ircv3_sasl.pass, "sasl_pass");
	assert_ptr_not_null(channel_list_get(&(s->clist), "#a1", s->casemapping));
	assert_ptr_not_null(channel_list_get(&(s->clist), "b2", s->casemapping));
	assert_ptr_not_null(channel_list_get(&(s->clist), "#c3", s->casemapping));