~rcr/rirc

cfc8a82a857b2ba42521717ffe5a38364bab57fc — Richard Robbins 3 months ago b0f1188
cleanup args parsing, add check for valid --chans
M config.def.h => config.def.h +2 -2
@@ 3,11 3,11 @@
 * Colours can be set [0, 255], Any other value (e.g. -1) will set
 * the default terminal foreground/background */

/* Default comma separated set of Nicks to try on connection
/* Comma separated set of default nicks to try on connection
 *   String
 *   ("": defaults to effective user id name)
 */
#define DEFAULT_NICK_SET ""
#define DEFAULT_NICKS ""

/* Default Username and Realname sent during connection
 *   String

M src/components/server.c => src/components/server.c +83 -41
@@ 155,6 155,89 @@ server_free(struct server *s)
	free(s);
}

int
server_set_chans(struct server *s, const char *chans)
{
	char *dup;
	char *p;
	size_t n_chans;

	dup = strdup(chans);

	for (n_chans = 0, p = dup; p; n_chans++) {

		const char *chan = p;

		if ((p = strchr(p, ',')))
			*p++ = 0;

		if (!irc_ischan(chan)) {
			free(dup);
			return -1;
		}
	}

	for (const char *chan = dup; n_chans; n_chans--) {

		struct channel *c;

		if (!(c = channel_list_get(&s->clist, chan, s->casemapping))) {
			c = channel(chan, CHANNEL_T_CHANNEL);
			c->server = s;
			channel_list_add(&s->clist, c);
		}

		chan = strchr(chan, 0) + 1;
	}

	free(dup);

	return 0;
}

int
server_set_nicks(struct server *s, const char *nicks)
{
	char *p1;
	char *p2;
	char *base;
	size_t n_nicks = 0;

	p2 = base = strdup(nicks);

	do {
		n_nicks++;

		p1 = p2;
		p2 = strchr(p2, ',');

		if (p2)
			*p2++ = 0;

		if (!irc_isnick(p1)) {
			free(base);
			return -1;
		}
	} while (p2);

	free((void *)s->nicks.base);
	free((void *)s->nicks.set);

	s->nicks.next = 0;
	s->nicks.size = n_nicks;
	s->nicks.base = base;

	if ((s->nicks.set = malloc(sizeof(*s->nicks.set) * n_nicks)) == NULL)
		fatal("malloc: %s", strerror(errno));

	for (const char **set = s->nicks.set; n_nicks; n_nicks--, set++) {
		*set = base;
		base = strchr(base, 0) + 1;
	}

	return 0;
}

void
server_set_004(struct server *s, char *str)
{


@@ 221,47 304,6 @@ server_set_005(struct server *s, char *str)
	}
}

int
server_set_nicks(struct server *s, const char *nicks)
{
	char *p1, *p2, *base;
	size_t n = 0;

	p2 = base = strdup(nicks);

	do {
		n++;

		p1 = p2;
		p2 = strchr(p2, ',');

		if (p2)
			*p2++ = 0;

		if (!irc_isnick(p1)) {
			free(base);
			return -1;
		}
	} while (p2);

	free((void *)s->nicks.base);
	free((void *)s->nicks.set);

	s->nicks.next = 0;
	s->nicks.size = n;
	s->nicks.base = base;

	if ((s->nicks.set = malloc(sizeof(*s->nicks.set) * n)) == NULL)
		fatal("malloc: %s", strerror(errno));

	for (const char **set = s->nicks.set; n; n--, set++) {
		*set = base;
		base = strchr(base, 0) + 1;
	}

	return 0;
}

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

M src/components/server.h => src/components/server.h +2 -1
@@ 64,9 64,10 @@ struct server* server_list_add(struct server_list*, struct server*);
struct server* server_list_del(struct server_list*, struct server*);
struct server* server_list_get(struct server_list*, const char*, const char*);

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*);
int server_set_nicks(struct server*, const char*);

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

M src/draw.c => src/draw.c +19 -0
@@ 100,6 100,22 @@ static int bg_last = -1;
static int fg_last = -1;
static int nick_colours[] = NICK_COLOURS

static int drawing;

void
draw_init(void)
{
	drawing = 1;
}

void
draw_term(void)
{
	drawing = 0;

	draw(DRAW_CLEAR);
}

void
draw(enum draw_bit bit)
{


@@ 139,6 155,9 @@ draw(enum draw_bit bit)
static void
draw_bits(void)
{
	if (!drawing)
		return;

	if (draw_state.bell && BELL_ON_PINGED)
		putchar('\a');


M src/draw.h => src/draw.h +3 -0
@@ 14,6 14,9 @@ enum draw_bit
	DRAW_CLEAR,  /* clear the terminal */
};

void draw_init(void);
void draw_term(void);

void draw(enum draw_bit);

#endif

M src/rirc.c => src/rirc.c +112 -85
@@ 13,18 13,17 @@
#include <string.h>
#include <unistd.h>

#define MAX_CLI_SERVERS 16
#define MAX_CLI_SERVERS 64

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

static const char* opt_arg_str(char);
static const char* getpwuid_pw_name(void);
static int parse_args(int, char**);
static const char* rirc_opt_str(char);
static const char* rirc_pw_name(void);
static int rirc_parse_args(int, char**);

#ifdef CA_CERT_PATH
const char *ca_cert_path = CA_CERT_PATH;


@@ 32,10 31,10 @@ const char *ca_cert_path = CA_CERT_PATH;
#error "CA_CERT_PATH required"
#endif

#ifdef DEFAULT_NICK_SET
const char *default_nick_set = DEFAULT_NICK_SET;
#ifdef DEFAULT_NICKS
const char *default_nicks = DEFAULT_NICKS;
#else
const char *default_nick_set;
const char *default_nicks;
#endif

#ifdef DEFAULT_USERNAME


@@ 90,7 89,7 @@ static const char *const rirc_version =
#endif

static const char*
opt_arg_str(char c)
rirc_opt_str(char c)
{
	switch (c) {
		case 's': return "-s/--server";


@@ 111,9 110,9 @@ opt_arg_str(char c)
}

static const char*
getpwuid_pw_name(void)
rirc_pw_name(void)
{
	static struct passwd *passwd;
	static const struct passwd *passwd;

	errno = 0;



@@ 124,23 123,35 @@ getpwuid_pw_name(void)
}

static int
parse_args(int argc, char **argv)
rirc_parse_args(int argc, char **argv)
{
	int opt_c = 0,
	    opt_i = 0;
	int opt_c = 0;
	int opt_i = 0;

	size_t n_servers = 0;

	opterr = 0;
	struct cli_server {
		const char *host;
		const char *port;
		const char *pass;
		const char *username;
		const char *realname;
		const char *nicks;
		const char *chans;
		int ipv;
		int tls;
		int tls_vrfy;
		struct server *s;
	} cli_servers[MAX_CLI_SERVERS];

	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'},
		{"nicks",       required_argument, 0, 'n'},
		{"chans",       required_argument, 0, 'c'},
		{"help",        no_argument,       0, 'h'},
		{"version",     no_argument,       0, 'v'},
		{"ipv4",        no_argument,       0, '4'},


@@ 150,49 161,45 @@ parse_args(int argc, char **argv)
		{0, 0, 0, 0}
	};

	struct cli_server {
		const char *host;
		const char *port;
		const char *pass;
		const char *nicks;
		const char *chans;
		const char *username;
		const char *realname;
		int ipv;
		int tls;
		int tls_vrfy;
		struct server *s;
	} cli_servers[MAX_CLI_SERVERS];
	opterr = 0;

	while (0 < (opt_c = getopt_long(argc, argv, ":s:p:w:n:c:r:u:hv", long_opts, &opt_i))) {
	while (0 < (opt_c = getopt_long(argc, argv, ":s:p:w:r:u:n:c:hv", long_opts, &opt_i))) {

		switch (opt_c) {

			case 's': /* Connect to server */

				if (*optarg == '-')
				if (*optarg == '-') {
					arg_error("-s/--server requires an argument");
					return -1;
				}

				if (++n_servers == MAX_CLI_SERVERS)
				if (++n_servers == MAX_CLI_SERVERS) {
					arg_error("exceeded maximum number of servers (%d)", MAX_CLI_SERVERS);
					return -1;
				}

				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].username = default_username;
				cli_servers[n_servers - 1].realname = default_realname;
				cli_servers[n_servers - 1].nicks    = default_nicks;
				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      = IO_IPV_UNSPEC;
				cli_servers[n_servers - 1].tls      = IO_TLS_ENABLED;
				cli_servers[n_servers - 1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
				break;

			#define CHECK_SERVER_OPTARG(OPT_C, REQ) \
				if ((REQ) && *optarg == '-') \
					arg_error("option '%s' requires an argument", opt_arg_str((OPT_C))); \
				if (n_servers == 0) \
					arg_error("option '%s' requires a server argument first", opt_arg_str((OPT_C)));
				if ((REQ) && *optarg == '-') { \
					arg_error("option '%s' requires an argument", rirc_opt_str((OPT_C))); \
					return -1; \
				} \
				if (n_servers == 0) { \
					arg_error("option '%s' requires a server argument first", rirc_opt_str((OPT_C))); \
					return -1; \
				}

			case 'p': /* Connect using port */
				CHECK_SERVER_OPTARG(opt_c, 1);


@@ 204,24 211,24 @@ parse_args(int argc, char **argv)
				cli_servers[n_servers - 1].pass = optarg;
				break;

			case 'n': /* Comma separated list of nicks to use */
			case 'u': /* Connect using username */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].nicks = optarg;
				cli_servers[n_servers - 1].username = optarg;
				break;

			case 'c': /* Comma separated list of channels to join */
			case 'r': /* Connect using realname */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].chans = optarg;
				cli_servers[n_servers - 1].realname = optarg;
				break;

			case 'u': /* Connect using username */
			case 'n': /* Comma separated list of nicks to use */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].username = optarg;
				cli_servers[n_servers - 1].nicks = optarg;
				break;

			case 'r': /* Connect using realname */
			case 'c': /* Comma separated list of channels to join */
				CHECK_SERVER_OPTARG(opt_c, 1);
				cli_servers[n_servers - 1].realname = optarg;
				cli_servers[n_servers - 1].chans = optarg;
				break;

			case '4': /* Connect using ipv4 only */


@@ 253,7 260,8 @@ parse_args(int argc, char **argv)
					cli_servers[n_servers -1].tls_vrfy = IO_TLS_VRFY_REQUIRED;
					break;
				}
				arg_error("option '--tls-verify' mode must be 'disabled', 'optional', or 'required'");
				arg_error("invalid option for '--tls-verify' '%s'", optarg);
				return -1;

			#undef CHECK_SERVER_OPTARG



@@ 266,29 274,23 @@ parse_args(int argc, char **argv)
				exit(EXIT_SUCCESS);

			case '?':
				arg_error("unknown options '%s'", argv[optind - 1]);
				arg_error("unknown option '%s'", argv[optind - 1]);
				return -1;

			case ':':
				arg_error("option '%s' requires an argument", opt_arg_str(optopt));
				arg_error("option '%s' requires an argument", rirc_opt_str(optopt));
				return -1;

			default:
				arg_error("unknown opt error");
				return -1;
		}
	}

	if (optind < argc)
	if (optind < argc) {
		arg_error("unused option '%s'", argv[optind]);

	if (!default_nick_set || !default_nick_set[0])
		default_nick_set = getpwuid_pw_name();

	if (!default_username || !default_username[0])
		default_username = getpwuid_pw_name();

	if (!default_realname || !default_realname[0])
		default_realname = getpwuid_pw_name();

	state_init();
		return -1;
	}

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



@@ 300,34 302,45 @@ parse_args(int argc, char **argv)
		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].s = server(
			cli_servers[i].host,
			cli_servers[i].port,
			cli_servers[i].pass,
			(cli_servers[i].username ? cli_servers[i].username : default_username),
			(cli_servers[i].realname ? cli_servers[i].realname : default_realname)
			cli_servers[i].username,
			cli_servers[i].realname
		);

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

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

		if (cli_servers[i].chans && state_server_set_chans(s, cli_servers[i].chans))
			arg_error("invalid chans: '%s'", cli_servers[i].chans);

		if (server_set_nicks(s, (cli_servers[i].nicks ? cli_servers[i].nicks : default_nick_set)))
			arg_error("invalid nicks: '%s'", cli_servers[i].nicks);
		if (cli_servers[i].nicks && server_set_nicks(cli_servers[i].s, cli_servers[i].nicks)) {
			arg_error("invalid %s: '%s'", rirc_opt_str('n'), cli_servers[i].nicks);
			return -1;
		}

		cli_servers[i].s = s;
		if (cli_servers[i].chans && server_set_chans(cli_servers[i].s, cli_servers[i].chans)) {
			arg_error("invalid %s: '%s'", rirc_opt_str('c'), cli_servers[i].chans);
			return -1;
		}

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

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

	for (size_t i = 0; i < n_servers; i++)
		io_cx(cli_servers[i].s->connection);
		int ret;

		if ((ret = io_cx(cli_servers[i].s->connection)))
			server_error(cli_servers[i].s, "failed to connect: %s", io_err(ret));
	}

	return 0;
}


@@ 336,19 349,33 @@ parse_args(int argc, char **argv)
int
main(int argc, char **argv)
{
	int ret;

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

	if (!default_username || !default_username[0])
		default_username = rirc_pw_name();

	if (!default_realname || !default_realname[0])
		default_realname = rirc_pw_name();

	if (!default_nicks || !default_nicks[0])
		default_nicks = rirc_pw_name();

	srand(time(NULL));

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

	if (rirc_parse_args(argc, argv)) {
		state_term();
		draw(DRAW_CLEAR);
		return EXIT_FAILURE;
	}

	return ret;
	draw_init();
	io_start();
	draw_term();
	state_term();

	return EXIT_SUCCESS;
}
#endif

M src/state.c => src/state.c +5 -39
@@ 94,7 94,7 @@ static const char *cmd_list[] = {
void
state_init(void)
{
	state.default_channel = state.current_channel = channel("rirc", CHANNEL_T_RIRC);
	state.default_channel = channel("rirc", CHANNEL_T_RIRC);

	newlinef(state.default_channel, 0, FROM_INFO, "      _");
	newlinef(state.default_channel, 0, FROM_INFO, " _ __(_)_ __ ___");


@@ 107,6 107,8 @@ state_init(void)
#ifndef NDEBUG
	newlinef(state.default_channel, 0, FROM_INFO, " - compiled with DEBUG flags");
#endif

	channel_set_current(state.default_channel);
}

void


@@ 215,42 217,6 @@ _newline(struct channel *c, enum buffer_line_type type, const char *from, const 
	}
}

int
state_server_set_chans(struct server *s, const char *chans)
{
	char *p1, *p2, *base;
	size_t n = 0;

	p2 = base = strdup(chans);

	do {
		n++;

		p1 = p2;
		p2 = strchr(p2, ',');

		if (p2)
			*p2++ = 0;

		if (!irc_ischan(p1)) {
			free(base);
			return -1;
		}
	} while (p2);

	for (const char *chan = base; n; n--) {
		struct channel *c;
		c = channel(chan, CHANNEL_T_CHANNEL);
		c->server = s;
		channel_list_add(&s->clist, c);
		chan = strchr(chan, 0) + 1;
	}

	free(base);

	return 0;
}

static int
state_input_action(const char *input, size_t len)
{


@@ 374,7 340,7 @@ state_channel_close(int action_confirm)

		if (s->connected && c->type == CHANNEL_T_CHANNEL && !c->parted) {
			if ((ret = io_sendf(s->connection, "PART %s :%s", c->name, DEFAULT_PART_MESG)))
				newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
				server_error(s, "sendf fail: %s", io_err(ret));
		}

		channel_set_current(c->next);


@@ 387,7 353,7 @@ state_channel_close(int action_confirm)

		if (s->connected) {
			if ((ret = io_sendf(s->connection, "QUIT :%s", DEFAULT_QUIT_MESG)))
				newlinef(s->channel, 0, FROM_ERROR, "sendf fail: %s", io_err(ret));
				server_error(s, "sendf fail: %s", io_err(ret));
			io_dx(s->connection);
		}


M src/state.h => src/state.h +0 -1
@@ 26,7 26,6 @@ unsigned state_cols(void);
unsigned state_rows(void);

const char *action_message(void);
int state_server_set_chans(struct server*, const char*);
struct channel* channel_get_first(void);
struct channel* channel_get_last(void);
struct channel* channel_get_next(struct channel*);

M src/utils/utils.c => src/utils/utils.c +15 -5
@@ 399,11 399,21 @@ irc_ischanchar(char c, int first)
	 * channelid  = 5( %x41-5A / digit )   ; 5( A-Z / 0-9 )
	 */

	/* TODO: CHANTYPES */
	(void)c;
	(void)first;

	return 1;
	if (first)
		return (c == '#' || c == '+' || c == '&');

	switch (c) {
		case 0x00: /* NUL */
		case 0x07: /* BEL */
		case 0x0D: /* CR */
		case 0x0A: /* LF */
		case ' ':
		case ',':
		case ':':
			return 0;
		default:
			return 1;
	}
}

static inline int

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

static void
test_server_set_chans(void)
{
	/* TODO */
}

static void
test_server_set_nicks(void)
{
	struct server *s = server("host", "port", NULL, "", "");


@@ 251,6 257,7 @@ main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_server_list),
		TESTCASE(test_server_set_chans),
		TESTCASE(test_server_set_nicks),
		TESTCASE(test_parse_005)
	};

M test/rirc.c => test/rirc.c +5 -4
@@ 17,19 17,20 @@
#include "test/io.mock.c"

static void
test_STUB(void)
test_rirc_parse_args(void)
{
	; /* TODO */
	/* TODO */
	(void)rirc_parse_args;
}

int
main(void)
{
	/* FIXME: */
	(void)parse_args;
	(void)rirc_pw_name;

	struct testcase tests[] = {
		TESTCASE(test_STUB)
		TESTCASE(test_rirc_parse_args)
	};

	return run_tests(tests);

M test/utils/utils.c => test/utils/utils.c +14 -0
@@ 2,6 2,18 @@
#include "src/utils/utils.c"

static void
test_irc_ischan(void)
{
	/* TODO */
}

static void
test_irc_isnick(void)
{
	/* TODO */
}

static void
test_irc_message_param(void)
{
	char *param;


@@ 646,6 658,8 @@ int
main(void)
{
	struct testcase tests[] = {
		TESTCASE(test_irc_ischan),
		TESTCASE(test_irc_isnick),
		TESTCASE(test_irc_message_param),
		TESTCASE(test_irc_message_parse),
		TESTCASE(test_irc_message_split),