~rcr/rirc

860ec6247f0dee9a79dd8811074765180cf459e4 — Richard Robbins a month ago 35db64b
add SASL tests
M README.md => README.md +1 -1
@@ 88,7 88,7 @@ Server connection options:
   --tls-verify=MODE        Set server TLS peer certificate verification mode

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

M docs/rirc.1 => docs/rirc.1 +2 -2
@@ 70,8 70,8 @@ Set \fIserver\fP TLS peer certificate verification \fImode\fP:
.EE
.SS Server authentication options
.TP
.BI --sasl= method
Authenticate with SASL \fImethod\fP:
.BI --sasl= mechanism
Authenticate with SASL \fImechanism\fP:
.EX
\(bu \fIplain\fP - requires \fI--sasl-user\fP and \fI--sasl-pass\fP
.EE

M src/components/ircv3.c => src/components/ircv3.c +2 -2
@@ 48,8 48,8 @@ ircv3_sasl(struct ircv3_sasl *sasl)
{
	memset(sasl, 0, sizeof(*sasl));

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

void

M src/components/ircv3.h => src/components/ircv3.h +5 -5
@@ 47,13 47,13 @@ struct ircv3_caps
struct ircv3_sasl
{
	enum {
		IRCV3_SASL_METHOD_NONE,
		IRCV3_SASL_METHOD_EXTERNAL,
		IRCV3_SASL_METHOD_PLAIN,
	} method;
		IRCV3_SASL_MECH_NONE,
		IRCV3_SASL_MECH_EXTERNAL,
		IRCV3_SASL_MECH_PLAIN,
	} mech;
	enum {
		IRCV3_SASL_STATE_NONE,
		IRCV3_SASL_STATE_REQ_METHOD,
		IRCV3_SASL_STATE_REQ_MECH,
		IRCV3_SASL_STATE_AUTHENTICATED,
	} state;
	const char *user;

M src/components/server.c => src/components/server.c +5 -5
@@ 326,19 326,19 @@ server_set_005(struct server *s, char *str)
}

void
server_set_sasl(struct server *s, const char *method, const char *user, const char *pass)
server_set_sasl(struct server *s, const char *mech, const char *user, const char *pass)
{
	free((void *)s->ircv3_sasl.user);
	free((void *)s->ircv3_sasl.pass);

	if (!strcasecmp(method, "EXTERNAL")) {
		s->ircv3_sasl.method = IRCV3_SASL_METHOD_EXTERNAL;
	if (!strcasecmp(mech, "EXTERNAL")) {
		s->ircv3_sasl.mech = IRCV3_SASL_MECH_EXTERNAL;
		s->ircv3_sasl.user = NULL;
		s->ircv3_sasl.pass = NULL;
	}

	if (!strcasecmp(method, "PLAIN")) {
		s->ircv3_sasl.method = IRCV3_SASL_METHOD_PLAIN;
	if (!strcasecmp(mech, "PLAIN")) {
		s->ircv3_sasl.mech = IRCV3_SASL_MECH_PLAIN;
		s->ircv3_sasl.user = (user ? strdup(user) : NULL);
		s->ircv3_sasl.pass = (pass ? strdup(pass) : NULL);
	}

M src/handlers/ircv3.c => src/handlers/ircv3.c +39 -39
@@ 9,7 9,7 @@

#define failf(S, ...) \
	do { server_error((S), __VA_ARGS__); \
	     return 1; \
	     return -1; \
	} while (0)

#define sendf(S, ...) \


@@ 33,7 33,7 @@ IRCV3_RECV_HANDLERS

static int ircv3_cap_req_count(struct ircv3_caps*);
static int ircv3_cap_req_send(struct ircv3_caps*, struct server*);
static int ircv3_registered(struct server*);
static int ircv3_cap_end(struct server*);
static int ircv3_sasl_init(struct server*);

static int ircv3_recv_AUTHENTICATE_EXTERNAL(struct server*, struct irc_message*);


@@ 42,16 42,16 @@ static int ircv3_recv_AUTHENTICATE_PLAIN(struct server*, struct irc_message*);
int
ircv3_recv_AUTHENTICATE(struct server *s, struct irc_message *m)
{
	if (s->ircv3_sasl.method == IRCV3_SASL_METHOD_NONE)
		failf(s, "AUTHENTICATE: no SASL method");
	if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_NONE)
		failf(s, "AUTHENTICATE: no SASL mechanism");

	if (s->ircv3_sasl.method == IRCV3_SASL_METHOD_EXTERNAL)
	if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_EXTERNAL)
		return (ircv3_recv_AUTHENTICATE_EXTERNAL(s, m));

	if (s->ircv3_sasl.method == IRCV3_SASL_METHOD_PLAIN)
	if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_PLAIN)
		return (ircv3_recv_AUTHENTICATE_PLAIN(s, m));

	fatal("unknown SASL authentication method");
	fatal("unknown SASL authentication mechanism");

	return 0;
}


@@ 150,14 150,18 @@ ircv3_numeric_903(struct server *s, struct irc_message *m)
{
	/* :SASL authentication successful */

	UNUSED(m);
	char *message;

	s->ircv3_sasl.state = IRCV3_SASL_STATE_AUTHENTICATED;
	irc_message_param(m, &message);

	if (ircv3_registered(s))
		sendf(s, "CAP END");
	if (message && *message)
		server_error(s, "%s", message);
	else
		server_error(s, "SASL authentication successful");

	return 0;
	s->ircv3_sasl.state = IRCV3_SASL_STATE_AUTHENTICATED;

	return ircv3_cap_end(s);
}

int


@@ 262,9 266,9 @@ ircv3_numeric_908(struct server *s, struct irc_message *m)
	irc_message_param(m, &message);

	if (message && *message)
		server_info(s, "%s %s", message);
		server_info(s, "%s %s", mechanisms, message);
	else
		server_info(s, "%s are available SASL mechanisms");
		server_info(s, "%s are available SASL mechanisms", mechanisms);

	return 0;
}


@@ 449,10 453,7 @@ ircv3_recv_cap_ACK(struct server *s, struct irc_message *m)
	if (errors)
		failf(s, "CAP ACK: parameter errors");

	if (ircv3_registered(s))
		sendf(s, "CAP END");

	return 0;
	return ircv3_cap_end(s);
}

static int


@@ 483,10 484,7 @@ ircv3_recv_cap_NAK(struct server *s, struct irc_message *m)

	} while ((cap = irc_strsep(&(caps))));

	if (ircv3_registered(s))
		sendf(s, "CAP END");

	return 0;
	return ircv3_cap_end(s);
}

static int


@@ 582,11 580,11 @@ ircv3_recv_AUTHENTICATE_EXTERNAL(struct server *s, struct irc_message *m)
	 * C: +
	 */

	if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_METHOD)
		failf(s, "Invalid SASL state for method EXTERNAL: %d", s->ircv3_sasl.state);
	if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_MECH)
		failf(s, "Invalid SASL state for mechanism EXTERNAL: %d", s->ircv3_sasl.state);

	if (strcmp(m->params, "+"))
		failf(s, "Invalid SASL response for method EXTERNAL: '%s'", m->params);
		failf(s, "Invalid SASL response for mechanism EXTERNAL: '%s'", m->params);

	sendf(s, "AUTHENTICATE +");



@@ 607,16 605,16 @@ ircv3_recv_AUTHENTICATE_PLAIN(struct server *s, struct irc_message *m)
	size_t len;

	if (!s->ircv3_sasl.user || !*s->ircv3_sasl.user)
		failf(s, "SASL method PLAIN requires a username");
		failf(s, "SASL mechanism PLAIN requires a username");

	if (!s->ircv3_sasl.pass || !*s->ircv3_sasl.pass)
		failf(s, "SASL method PLAIN requires a password");
		failf(s, "SASL mechanism PLAIN requires a password");

	if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_METHOD)
		failf(s, "Invalid SASL state for method PLAIN: %d", s->ircv3_sasl.state);
	if (s->ircv3_sasl.state != IRCV3_SASL_STATE_REQ_MECH)
		failf(s, "Invalid SASL state for mechanism PLAIN: %d", s->ircv3_sasl.state);

	if (strcmp(m->params, "+"))
		failf(s, "Invalid SASL response for method PLAIN: '%s'", m->params);
		failf(s, "Invalid SASL response for mechanism PLAIN: '%s'", m->params);

	len = snprintf((char *)resp_dec, sizeof(resp_dec), "%s%c%s%c%s",
		s->ircv3_sasl.user, 0,


@@ 669,7 667,7 @@ ircv3_cap_req_send(struct ircv3_caps *caps, struct server *s)
}

static int
ircv3_registered(struct server *s)
ircv3_cap_end(struct server *s)
{
	/* Previously registered or doesn't support IRCv3 */
	if (s->registered)


@@ 684,13 682,15 @@ ircv3_registered(struct server *s)
	 && s->ircv3_sasl.state != IRCV3_SASL_STATE_AUTHENTICATED)
		return 0;

	return 1;
	sendf(s, "CAP END");
	
	return 0;
}

static int
ircv3_sasl_init(struct server *s)
{
	if (s->ircv3_sasl.method == IRCV3_SASL_METHOD_NONE)
	if (s->ircv3_sasl.mech == IRCV3_SASL_MECH_NONE)
		return 0;

	switch (s->ircv3_sasl.state) {


@@ 700,7 700,7 @@ ircv3_sasl_init(struct server *s)
			break;

		/* Authentication in progress */
		case IRCV3_SASL_STATE_REQ_METHOD:
		case IRCV3_SASL_STATE_REQ_MECH:
			return 0;

		/* Previously authenticated */


@@ 711,18 711,18 @@ ircv3_sasl_init(struct server *s)
			fatal("unknown SASL state");
	}

	switch (s->ircv3_sasl.method) {
		case IRCV3_SASL_METHOD_EXTERNAL:
	switch (s->ircv3_sasl.mech) {
		case IRCV3_SASL_MECH_EXTERNAL:
			sendf(s, "AUTHENTICATE EXTERNAL");
			break;
		case IRCV3_SASL_METHOD_PLAIN:
		case IRCV3_SASL_MECH_PLAIN:
			sendf(s, "AUTHENTICATE PLAIN");
			break;
		default:
			fatal("unknown SASL authentication method");
			fatal("unknown SASL authentication mechanism");
	}

	s->ircv3_sasl.state = IRCV3_SASL_STATE_REQ_METHOD;
	s->ircv3_sasl.state = IRCV3_SASL_STATE_REQ_MECH;

	return 0;
}

M src/rirc.c => src/rirc.c +2 -2
@@ 92,7 92,7 @@ static const char *const rirc_help =
"\n   --tls-verify=MODE        Set server TLS peer certificate verification mode"
"\n"
"\nServer authentication options:"
"\n   --sasl=METHOD            Authenticate with SASL method"
"\n   --sasl=MECHANISM         Authenticate with SASL mechanism"
"\n   --sasl-user=USER         Authenticate with SASL user"
"\n   --sasl-pass=PASS         Authenticate with SASL pass"
"\n";


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

			case '7': /* Authenticate with SASL method */
			case '7': /* Authenticate with SASL mechanism */
				CHECK_SERVER_OPTARG(opt_c, 1);
				if (!strcasecmp(optarg, "EXTERNAL")) {
					cli_servers[n_servers - 1].sasl = optarg;

M test/handlers/ircv3.c => test/handlers/ircv3.c +438 -35
@@ 42,50 42,50 @@ mock_reset(void)
}

static void
test_ircv3_CAP(void)
test_ircv3_recv_CAP(void)
{
	mock_reset();
	IRC_MESSAGE_PARSE("CAP");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_strcmp(mock_line[0], "CAP: target is null");

	mock_reset();
	IRC_MESSAGE_PARSE("CAP *");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_strcmp(mock_line[0], "CAP: command is null");

	mock_reset();
	IRC_MESSAGE_PARSE("CAP * ack");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_strcmp(mock_line[0], "CAP: unrecognized subcommand 'ack'");

	mock_reset();
	IRC_MESSAGE_PARSE("CAP * xxx");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_strcmp(mock_line[0], "CAP: unrecognized subcommand 'xxx'");
}

static void
test_ircv3_CAP_LS(void)
test_ircv3_recv_CAP_LS(void)
{
	/* test empty LS, no parameter */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LS");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_strcmp(mock_line[0], "CAP LS: parameter is null");

	/* test empty LS, no parameter, multiline */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LS *");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_strcmp(mock_line[0], "CAP LS: parameter is null");

	/* test multiple parameters, no '*' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LS cap-1 cap-2");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_strcmp(mock_line[0], "CAP LS: invalid parameters");



@@ 178,12 178,12 @@ test_ircv3_CAP_LS(void)
}

static void
test_ircv3_CAP_LIST(void)
test_ircv3_recv_CAP_LIST(void)
{
	/* test empty LIST, no parameter */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LIST");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP LIST: parameter is null");


@@ 191,7 191,7 @@ test_ircv3_CAP_LIST(void)
	/* test empty LIST, no parameter, multiline */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LIST *");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP LIST: parameter is null");


@@ 199,7 199,7 @@ test_ircv3_CAP_LIST(void)
	/* test multiple parameters, no '*' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * LIST cap-1 cap-2");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP LIST: invalid parameters");


@@ 250,12 250,12 @@ test_ircv3_CAP_LIST(void)
}

static void
test_ircv3_CAP_ACK(void)
test_ircv3_recv_CAP_ACK(void)
{
	/* test empty ACK */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * ACK");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP ACK: parameter is null");


@@ 263,7 263,7 @@ test_ircv3_CAP_ACK(void)
	/* test empty ACK, with leading ':' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * ACK :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP ACK: parameter is empty");


@@ 291,7 291,7 @@ test_ircv3_CAP_ACK(void)
	s->ircv3_caps.cap_1.req = 1;
	s->ircv3_caps.cap_2.req = 1;
	IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-aaa cap-2 cap-bbb");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 5);
	assert_strcmp(mock_line[0], "capability change accepted: cap-1");


@@ 335,7 335,7 @@ test_ircv3_CAP_ACK(void)
	s->ircv3_caps.cap_3.req = 1;
	s->ircv3_caps.cap_4.req = 0;
	IRC_MESSAGE_PARSE("CAP * ACK :cap-1 cap-2 -cap-3 -cap-4");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 5);
	assert_strcmp(mock_line[0], "capability change accepted: cap-1");


@@ 362,12 362,12 @@ test_ircv3_CAP_ACK(void)
}

static void
test_ircv3_CAP_NAK(void)
test_ircv3_recv_CAP_NAK(void)
{
	/* test empty NAK */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * NAK");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP NAK: parameter is null");


@@ 375,7 375,7 @@ test_ircv3_CAP_NAK(void)
	/* test empty NAK, with leading ':' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * NAK :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP NAK: parameter is empty");


@@ 436,12 436,12 @@ test_ircv3_CAP_NAK(void)
}

static void
test_ircv3_CAP_DEL(void)
test_ircv3_recv_CAP_DEL(void)
{
	/* test empty DEL */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * DEL");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP DEL: parameter is null");


@@ 449,7 449,7 @@ test_ircv3_CAP_DEL(void)
	/* test empty DEL, with leading ':' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * DEL :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP DEL: parameter is empty");


@@ 457,7 457,7 @@ test_ircv3_CAP_DEL(void)
	/* test cap-7 doesn't support DEL */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * DEL :cap-7");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP DEL: 'cap-7' doesn't support DEL");


@@ 492,12 492,12 @@ test_ircv3_CAP_DEL(void)
}

static void
test_ircv3_CAP_NEW(void)
test_ircv3_recv_CAP_NEW(void)
{
	/* test empty NEW */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * NEW");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP NEW: parameter is null");


@@ 505,7 505,7 @@ test_ircv3_CAP_NEW(void)
	/* test empty DEL, with leading ':' */
	mock_reset();
	IRC_MESSAGE_PARSE("CAP * NEW :");
	assert_eq(irc_recv(s, &m), 1);
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "CAP NEW: parameter is empty");


@@ 580,6 580,300 @@ test_ircv3_CAP_NEW(void)
	assert_eq(s->ircv3_caps.cap_7.supported, 0);
}


static void
test_ircv3_recv_AUTHENTICATE(void)
{
	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "AUTHENTICATE: no SASL mechanism");

	s->ircv3_sasl.mech = -1;

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_fatal(irc_recv(s, &m));
}

static void
test_ircv3_recv_AUTHENTICATE_EXTERNAL(void)
{
	server_set_sasl(s, "external", NULL, NULL);

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "Invalid SASL state for mechanism EXTERNAL: 0");

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_send[0], "AUTHENTICATE EXTERNAL");

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "Invalid SASL response for mechanism EXTERNAL: '*'");

	IRC_MESSAGE_PARSE("AUTHENTICATE +");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_send[1], "AUTHENTICATE +");
}

static void
test_ircv3_recv_AUTHENTICATE_PLAIN(void)
{
	server_set_sasl(s, "plain", NULL, "pass");

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "SASL mechanism PLAIN requires a username");

	server_set_sasl(s, "plain", "user", NULL);

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "SASL mechanism PLAIN requires a password");

	server_set_sasl(s, "plain", "user", "pass");

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 3);
	assert_strcmp(mock_line[2], "Invalid SASL state for mechanism PLAIN: 0");

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 3);
	assert_strcmp(mock_send[0], "AUTHENTICATE PLAIN");

	IRC_MESSAGE_PARSE("AUTHENTICATE *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 4);
	assert_strcmp(mock_line[3], "Invalid SASL response for mechanism PLAIN: '*'");

	IRC_MESSAGE_PARSE("AUTHENTICATE +");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 4);
	assert_strcmp(mock_send[1], "AUTHENTICATE dXNlcgB1c2VyAHBhc3M=");

	char user1[150] = {0};
	char user2[147] = {0};

	memset(user1, 'x', sizeof(user1) - 1);
	memset(user2, 'x', sizeof(user2) - 1);

	server_set_sasl(s, "plain", user1, "pass");

	IRC_MESSAGE_PARSE("AUTHENTICATE +");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 5);
	assert_strcmp(mock_line[4], "SASL decoded auth message too long");

	server_set_sasl(s, "plain", user2, "pass");

	IRC_MESSAGE_PARSE("AUTHENTICATE +");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 6);
	assert_strcmp(mock_line[5], "SASL encoded auth message too long");
}

static void
test_ircv3_numeric_900(void)
{
	IRC_MESSAGE_PARSE("900 *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "RPL_LOGGEDIN: missing nick");

	IRC_MESSAGE_PARSE("900 * nick!ident@host");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "RPL_LOGGEDIN: missing account");

	IRC_MESSAGE_PARSE("900 * nick!ident@host account");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 3);
	assert_strcmp(mock_line[2], "SASL success: you are logged in as account");

	IRC_MESSAGE_PARSE("900 * nick!ident@host account :test 900 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 4);
	assert_strcmp(mock_line[3], "SASL success: test 900 message");
}

static void
test_ircv3_numeric_901(void)
{
	IRC_MESSAGE_PARSE("901 *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "RPL_LOGGEDOUT: missing nick");

	IRC_MESSAGE_PARSE("901 * nick!ident@host");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "You are now logged out");

	IRC_MESSAGE_PARSE("901 * nick!ident@host :test 901 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 3);
	assert_strcmp(mock_line[2], "test 901 message");
}

static void
test_ircv3_numeric_902(void)
{
	IRC_MESSAGE_PARSE("902 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "You must use a nick assigned to you");

	IRC_MESSAGE_PARSE("902 * :test 902 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "test 902 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_NONE);
}

static void
test_ircv3_numeric_903(void)
{
	IRC_MESSAGE_PARSE("903 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_send[0], "CAP END");
	assert_strcmp(mock_line[0], "SASL authentication successful");

	IRC_MESSAGE_PARSE("903 * :test 903 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_send[1], "CAP END");
	assert_strcmp(mock_line[1], "test 903 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_AUTHENTICATED);
}

static void
test_ircv3_numeric_904(void)
{
	IRC_MESSAGE_PARSE("904 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "SASL authentication failed");

	IRC_MESSAGE_PARSE("904 * :test 904 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "test 904 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_NONE);
}

static void
test_ircv3_numeric_905(void)
{
	IRC_MESSAGE_PARSE("905 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "SASL message too long");

	IRC_MESSAGE_PARSE("905 * :test 905 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "test 905 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_NONE);
}

static void
test_ircv3_numeric_906(void)
{
	IRC_MESSAGE_PARSE("906 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "SASL authentication aborted");

	IRC_MESSAGE_PARSE("906 * :test 906 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "test 906 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_NONE);
}

static void
test_ircv3_numeric_907(void)
{
	IRC_MESSAGE_PARSE("907 *");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "You have already authenticated using SASL");

	IRC_MESSAGE_PARSE("907 * :test 907 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "test 907 message");

	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_NONE);
}

static void
test_ircv3_numeric_908(void)
{
	IRC_MESSAGE_PARSE("908 *");
	assert_eq(irc_recv(s, &m), -1);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 1);
	assert_strcmp(mock_line[0], "RPL_SASLMECHS: missing mechanisms");

	IRC_MESSAGE_PARSE("908 * m1,m2,m3");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 2);
	assert_strcmp(mock_line[1], "m1,m2,m3 are available SASL mechanisms");

	IRC_MESSAGE_PARSE("908 * m1,m2,m3 :test 908 message");
	assert_eq(irc_recv(s, &m), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 3);
	assert_strcmp(mock_line[2], "m1,m2,m3 test 908 message");
}

static void
test_ircv3_cap_req_count(void)
{


@@ 625,12 919,107 @@ test_ircv3_cap_req_send(void)
	assert_strcmp(mock_send[0], "CAP REQ :cap-1 cap-3 cap-5");
}

static void
test_ircv3_cap_end(void)
{
	/* test previously registered or doesn't support IRCv3 */
	s->registered = 1;

	assert_eq(ircv3_cap_end(s), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 0);

	/* test IRCv3 CAP negotiation in progress */
	server_reset(s);
	s->ircv3_caps.cap_1.req = 1;

	assert_eq(ircv3_cap_end(s), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 0);

	/* test IRCv3 SASL authentication in progress */
	server_reset(s);
	s->ircv3_sasl.state = IRCV3_SASL_STATE_REQ_MECH;

	assert_eq(ircv3_cap_end(s), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 0);

	/* test send CAP END */
	server_reset(s);

	assert_eq(ircv3_cap_end(s), 0);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 0);
	assert_strcmp(mock_send[0], "CAP END");
}

static void
test_ircv3_sasl_init(void)
{
	/* test no sasl auth mechanism */
	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 0);
	assert_eq(mock_line_n, 0);

	/* test start authentication process, external */
	s->ircv3_sasl.mech = IRCV3_SASL_MECH_EXTERNAL;
	s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 1);
	assert_eq(mock_line_n, 0);
	assert_strcmp(mock_send[0], "AUTHENTICATE EXTERNAL");
	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_REQ_MECH);

	/* test start authentication process, plain */
	s->ircv3_sasl.mech = IRCV3_SASL_MECH_PLAIN;
	s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 0);
	assert_strcmp(mock_send[1], "AUTHENTICATE PLAIN");
	assert_eq(s->ircv3_sasl.state, IRCV3_SASL_STATE_REQ_MECH);

	/* test authentication in progress */
	s->ircv3_sasl.state = IRCV3_SASL_STATE_REQ_MECH;

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 0);

	/* test previously authenticated */
	s->ircv3_sasl.state = IRCV3_SASL_STATE_AUTHENTICATED;

	assert_eq(ircv3_sasl_init(s), 0);
	assert_eq(mock_send_n, 2);
	assert_eq(mock_line_n, 0);

	/* test invalid mechanism */
	s->ircv3_sasl.mech = -1;
	s->ircv3_sasl.state = IRCV3_SASL_STATE_NONE;

	assert_fatal(ircv3_sasl_init(s));

	/* test invalid state */
	s->ircv3_sasl.mech = IRCV3_SASL_MECH_PLAIN;
	s->ircv3_sasl.state = -1;

	assert_fatal(ircv3_sasl_init(s));
}

static int
test_init(void)
{
	mock_reset_io();
	mock_reset_state();

	if (!(s = server("host", "post", NULL, "user", "real", NULL)))
		return -1;

	server_nick_set(s, "me");

	return 0;
}



@@ 646,15 1035,29 @@ int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_ircv3_CAP),
		TESTCASE(test_ircv3_CAP_LS),
		TESTCASE(test_ircv3_CAP_LIST),
		TESTCASE(test_ircv3_CAP_ACK),
		TESTCASE(test_ircv3_CAP_NAK),
		TESTCASE(test_ircv3_CAP_DEL),
		TESTCASE(test_ircv3_CAP_NEW),
		TESTCASE(test_ircv3_recv_CAP),
		TESTCASE(test_ircv3_recv_CAP_LS),
		TESTCASE(test_ircv3_recv_CAP_LIST),
		TESTCASE(test_ircv3_recv_CAP_ACK),
		TESTCASE(test_ircv3_recv_CAP_NAK),
		TESTCASE(test_ircv3_recv_CAP_DEL),
		TESTCASE(test_ircv3_recv_CAP_NEW),
		TESTCASE(test_ircv3_recv_AUTHENTICATE),
		TESTCASE(test_ircv3_recv_AUTHENTICATE_EXTERNAL),
		TESTCASE(test_ircv3_recv_AUTHENTICATE_PLAIN),
		TESTCASE(test_ircv3_numeric_900),
		TESTCASE(test_ircv3_numeric_901),
		TESTCASE(test_ircv3_numeric_902),
		TESTCASE(test_ircv3_numeric_903),
		TESTCASE(test_ircv3_numeric_904),
		TESTCASE(test_ircv3_numeric_905),
		TESTCASE(test_ircv3_numeric_906),
		TESTCASE(test_ircv3_numeric_907),
		TESTCASE(test_ircv3_numeric_908),
		TESTCASE(test_ircv3_cap_req_count),
		TESTCASE(test_ircv3_cap_req_send),
		TESTCASE(test_ircv3_cap_end),
		TESTCASE(test_ircv3_sasl_init),
	};

	return run_tests(test_init, test_term, tests);

M test/state.c => test/state.c +1 -1
@@ 366,7 366,7 @@ 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_eq(s->ircv3_sasl.mech, IRCV3_SASL_MECH_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));