hirc

[archived] IRC client
git clone https://hhvn.uk/hirc
git clone git://hhvn.uk/hirc
Log | Files | Refs

commands.c (46049B)


      1 /*
      2  * src/commands.c from hirc
      3  *
      4  * Copyright (c) 2021-2022 hhvn <dev@hhvn.uk>
      5  *
      6  * Permission to use, copy, modify, and distribute this software for any
      7  * purpose with or without fee is hereby granted, provided that the above
      8  * copyright notice and this permission notice appear in all copies.
      9  *
     10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     17  *
     18  */
     19 
     20 #include <ncurses.h>
     21 #include <stdlib.h>
     22 #include <string.h>
     23 #include <unistd.h>
     24 #include <limits.h>
     25 #include <ctype.h>
     26 #include <regex.h>
     27 #include <errno.h>
     28 #include <pwd.h>
     29 #include <sys/types.h>
     30 #include "hirc.h"
     31 
     32 #define command_toofew(cmd) ui_error("/%s: too few arguments", cmd)
     33 #define command_toomany(cmd) ui_error("/%s: too many arguments", cmd)
     34 #define command_needselected(cmd, type) ui_error("/%s: no %s selected", cmd, type)
     35 
     36 /*
     37  * There are some commands that may be useful that I haven't bothered to implement.
     38  *
     39  * /notify may be useful but would require storing data in the server, and the
     40  * ability to perform actions at certain time intervals.
     41  *
     42  * I don't think I have ever used /knock
     43  *
     44  * A lot of commands related to server administration are nonstandard and/or
     45  * unwieldy, and as such aren't implemented here. These can be used via
     46  * aliases, eg: /alias /kline /quote kline.
     47  *
     48  */
     49 
     50 #include "data/commands.h"
     51 
     52 static char *command_optarg;
     53 enum {
     54 	opt_error = -2,
     55 	opt_done = -1,
     56 	CMD_NARG,
     57 	CMD_ARG,
     58 };
     59 
     60 struct Alias *aliases = NULL;
     61 
     62 COMMAND(
     63 command_away) {
     64 	struct Server *sp;
     65 	char *format;
     66 	int all = 1, ret;
     67 	enum { opt_one };
     68 	static struct CommandOpts opts[] = {
     69 		{"one", CMD_NARG, opt_one},
     70 		{"NULL", 0, 0},
     71 	};
     72 
     73 	while ((ret = command_getopt(&str, opts)) != opt_done) {
     74 		switch (ret) {
     75 		case opt_error:
     76 			return;
     77 		case opt_one:
     78 			all = 0;
     79 			break;
     80 		}
     81 	}
     82 
     83 	if (str)
     84 		format = "AWAY :%s\r\n";
     85 	else
     86 		format = "AWAY\r\n";
     87 
     88 	if (all) {
     89 		for (sp = servers; sp; sp = sp->next)
     90 			serv_write(sp, Sched_connected, format, str);
     91 	} else if (server) {
     92 		serv_write(server, Sched_connected, format, str);
     93 	} else {
     94 		ui_error("-one specified, but no server selected", NULL);
     95 	}
     96 }
     97 
     98 COMMAND(
     99 command_msg) {
    100 	struct Channel *chan = NULL;
    101 	char *target, *message;
    102 
    103 	if (!str) {
    104 		command_toofew("msg");
    105 		return;
    106 	}
    107 
    108 	target = strtok_r(str, " ", &message);
    109 
    110 	if (serv_ischannel(server, target))
    111 		chan = chan_get(&server->channels, target, -1);
    112 	else
    113 		chan = chan_get(&server->queries, target, -1);
    114 
    115 	serv_write(server, Sched_connected, "PRIVMSG %s :%s\r\n", target, message);
    116 	if (chan) {
    117 		hist_format(chan->history, Activity_self,
    118 				HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%s", target, message);
    119 	}
    120 }
    121 
    122 COMMAND(
    123 command_notice) {
    124 	struct Channel *chan = NULL;
    125 	char *target, *message;
    126 
    127 	if (!str) {
    128 		command_toofew("notice");
    129 		return;
    130 	}
    131 
    132 	target = strtok_r(str, " ", &message);
    133 
    134 	if (serv_ischannel(server, target))
    135 		chan = chan_get(&server->channels, target, -1);
    136 	else
    137 		chan = chan_get(&server->queries, target, -1);
    138 
    139 	serv_write(server, Sched_connected, "NOTICE %s :%s\r\n", target, message);
    140 	if (chan) {
    141 		hist_format(chan->history, Activity_self,
    142 				HIST_SHOW|HIST_LOG|HIST_SELF, "NOTICE %s :%s", target, message);
    143 	}
    144 }
    145 
    146 COMMAND(
    147 command_me) {
    148 	if (!str)
    149 		str = "";
    150 
    151 	serv_write(server, Sched_connected, "PRIVMSG %s :%cACTION %s%c\r\n", channel->name, 1, str, 1);
    152 	hist_format(channel->history, Activity_self,
    153 			HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%cACTION %s%c", channel->name, 1, str, 1);
    154 }
    155 
    156 COMMAND(
    157 command_ctcp) {
    158 	struct Channel *chan;
    159 	char *target, *ctcp;
    160 
    161 	if (!str) {
    162 		command_toofew("ctcp");
    163 		return;
    164 	}
    165 
    166 	target = strtok_r(str, " ", &ctcp);
    167 
    168 	if (!ctcp) {
    169 		ctcp = target;
    170 		target = channel->name;
    171 	}
    172 
    173 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    174 		chan = chan_get(&server->queries, target, -1);
    175 
    176 	/* XXX: if we CTCP a channel, responses should go to that channel.
    177 	 * This requires more than just expect_set, so might never be
    178 	 * implemented. */
    179 	serv_write(server, Sched_connected, "PRIVMSG %s :%c%s%c\r\n", target, 1, ctcp, 1);
    180 	if (chan) {
    181 		hist_format(channel->history, Activity_self,
    182 				HIST_SHOW|HIST_LOG|HIST_SELF, "PRIVMSG %s :%c%s%c",
    183 				target, 1, ctcp, 1);
    184 	}
    185 }
    186 
    187 COMMAND(
    188 command_query) {
    189 	struct Channel *query;
    190 
    191 	if (!str) {
    192 		command_toofew("query");
    193 		return;
    194 	}
    195 
    196 	if (strchr(str, ' ')) {
    197 		command_toomany("query");
    198 		return;
    199 	}
    200 
    201 	if (serv_ischannel(server, str)) {
    202 		ui_error("can't query a channel", NULL);
    203 		return;
    204 	}
    205 
    206 	if ((query = chan_get(&server->queries, str, -1)) == NULL)
    207 		query = chan_add(server, &server->queries, str, 1);
    208 
    209 	if (!nouich)
    210 		ui_select(server, query);
    211 }
    212 
    213 COMMAND(
    214 command_quit) {
    215 	cleanup(str ? str : config_gets("def.quitmessage"));
    216 	exit(EXIT_SUCCESS);
    217 }
    218 
    219 COMMAND(
    220 command_join) {
    221 	char msg[512];
    222 
    223 	if (!str) {
    224 		command_toofew("join");
    225 		return;
    226 	}
    227 
    228 	if (serv_ischannel(server, str))
    229 		snprintf(msg, sizeof(msg), "JOIN %s\r\n", str);
    230 	else
    231 		snprintf(msg, sizeof(msg), "JOIN %c%s\r\n", '#', str);
    232 
    233 	serv_write(server, Sched_connected, "%s", msg);
    234 	expect_set(server, Expect_join, str);
    235 }
    236 
    237 COMMAND(
    238 command_part) {
    239 	char *chan = NULL, *reason = NULL;
    240 	char msg[512];
    241 
    242 	if (str) {
    243 		if (serv_ischannel(server, str))
    244 			chan = strtok_r(str, " ", &reason);
    245 		else
    246 			reason = str;
    247 	}
    248 
    249 	if (!chan) {
    250 		if (channel) {
    251 			chan = channel->name;
    252 		} else {
    253 			command_toofew("part");
    254 			return;
    255 		}
    256 	}
    257 
    258 	snprintf(msg, sizeof(msg), "PART %s :%s\r\n", chan, reason ? reason : config_gets("def.partmessage"));
    259 
    260 	serv_write(server, Sched_connected, "%s", msg);
    261 	expect_set(server, Expect_part, chan);
    262 }
    263 
    264 COMMAND(
    265 command_cycle) {
    266 	char *chan = NULL;
    267 
    268 	if (str && serv_ischannel(server, str))
    269 		chan = strtok(str, " ");
    270 	if (!chan && channel) {
    271 		chan = channel->name;
    272 	} else if (!chan) {
    273 		command_toofew("cycle");
    274 		return;
    275 	}
    276 
    277 	command_part(server, channel, str);
    278 	command_join(server, channel, chan);
    279 }
    280 
    281 COMMAND(
    282 command_kick) {
    283 	char *chan, *nick, *reason;
    284 	char *s;
    285 
    286 	if (!str) {
    287 		command_toofew("kick");
    288 		return;
    289 	}
    290 
    291 	s = strtok_r(str,  " ", &reason);
    292 
    293 	if (serv_ischannel(server, s)) {
    294 		chan = s;
    295 		nick = strtok_r(NULL, " ", &reason);
    296 	} else {
    297 		if (channel == NULL) {
    298 			command_needselected("kick", "channel");
    299 			return;
    300 		}
    301 
    302 		chan = channel->name;
    303 		nick = s;
    304 	}
    305 
    306 	if (reason)
    307 		serv_write(server, Sched_connected, "KICK %s %s :%s\r\n", chan, nick, reason);
    308 	else
    309 		serv_write(server, Sched_connected, "KICK %s %s\r\n", chan, nick);
    310 }
    311 
    312 COMMAND(
    313 command_mode) {
    314 	char *chan, *modes;
    315 	char *s = NULL;
    316 
    317 	if (str)
    318 		s = strtok_r(str,  " ", &modes);
    319 
    320 	if (serv_ischannel(server, s)) {
    321 		chan = s;
    322 	} else {
    323 		if (channel == NULL) {
    324 			command_needselected("mode", "channel");
    325 			return;
    326 		}
    327 
    328 		chan = channel->name;
    329 		if (modes) {
    330 			*(modes - 1) = ' ';
    331 			modes = s;
    332 		}
    333 	}
    334 
    335 	if (modes) {
    336 		if (channel && chan == channel->name)
    337 			expect_set(server, Expect_nosuchnick, chan);
    338 		serv_write(server, Sched_connected, "MODE %s %s\r\n", chan, modes);
    339 	} else {
    340 		expect_set(server, Expect_channelmodeis, chan);
    341 		serv_write(server, Sched_connected, "MODE %s\r\n", chan);
    342 	}
    343 }
    344 
    345 COMMAND(
    346 command_nick) {
    347 	if (!str) {
    348 		command_toofew("nick");
    349 		return;
    350 	}
    351 
    352 	if (strchr(str, ' ')) {
    353 		command_toomany("nick");
    354 		return;
    355 	}
    356 
    357 	serv_write(server, Sched_now, "NICK %s\r\n", str);
    358 	expect_set(server, Expect_nicknameinuse, str);
    359 }
    360 
    361 COMMAND(
    362 command_list) {
    363 	if (str) {
    364 		command_toomany("list");
    365 		return;
    366 	}
    367 
    368 	serv_write(server, Sched_connected, "LIST\r\n", str);
    369 }
    370 
    371 COMMAND(
    372 command_whois) {
    373 	char *tserver, *nick;
    374 
    375 	if (!str) {
    376 		nick = server->self->nick;
    377 		tserver = NULL;
    378 	} else {
    379 		tserver = strtok_r(str, " ", &nick);
    380 		if (!nick || !*nick) {
    381 			nick = tserver;
    382 			tserver = NULL;
    383 		}
    384 	}
    385 
    386 	if (tserver)
    387 		serv_write(server, Sched_connected, "WHOIS %s :%s\r\n", tserver, nick);
    388 	else
    389 		serv_write(server, Sched_connected, "WHOIS %s\r\n", nick);
    390 }
    391 
    392 COMMAND(
    393 command_who) {
    394 	if (!str)
    395 		str = "*"; /* wildcard */
    396 
    397 	serv_write(server, Sched_connected, "WHO %s\r\n", str);
    398 }
    399 
    400 COMMAND(
    401 command_whowas) {
    402 	char *nick, *count, *tserver;
    403 
    404 	if (!str) {
    405 		nick = server->self->nick;
    406 		count = tserver = NULL;
    407 	} else {
    408 		nick = strtok_r(str, " ", &tserver);
    409 		count = strtok_r(NULL, " ", &tserver);
    410 	}
    411 
    412 	if (tserver)
    413 		serv_write(server, Sched_connected, "WHOWAS %s %s :%s\r\n", nick, count, tserver);
    414 	else if (count)
    415 		serv_write(server, Sched_connected, "WHOWAS %s %s\r\n", nick, count);
    416 	else
    417 		serv_write(server, Sched_connected, "WHOWAS %s 5\r\n", nick);
    418 }
    419 
    420 COMMAND(
    421 command_ping) {
    422 	if (!str) {
    423 		command_toofew("ping");
    424 		return;
    425 	}
    426 
    427 	serv_write(server, Sched_now, "PING :%s\r\n", str);
    428 	expect_set(server, Expect_pong, str);
    429 }
    430 
    431 COMMAND(
    432 command_quote) {
    433 	if (!str) {
    434 		command_toofew("quote");
    435 		return;
    436 	}
    437 
    438 	serv_write(server, Sched_connected, "%s\r\n", str);
    439 }
    440 
    441 COMMAND(
    442 command_connect) {
    443 	struct Server *tserver;
    444 	char *network	= NULL;
    445 	char *host	= NULL;
    446 	char *port	= NULL;
    447 	char *nick	= NULL;
    448 	char *username	= NULL;
    449 	char *realname	= NULL;
    450 	char *password  = NULL;
    451 	int tls = -1, tls_verify = -1; /* tell serv_update not to change */
    452 	int ret;
    453 	struct passwd *user;
    454 	enum {
    455 		opt_network,
    456 		opt_nick,
    457 		opt_username,
    458 		opt_realname,
    459 		opt_password,
    460 #ifdef TLS
    461 		opt_tls,
    462 		opt_tls_verify,
    463 #endif /* TLS */
    464 	};
    465 	static struct CommandOpts opts[] = {
    466 		{"network", CMD_ARG, opt_network},
    467 		{"nick", CMD_ARG, opt_nick},
    468 
    469 		{"username", CMD_ARG, opt_username},
    470 		{"user", CMD_ARG, opt_username},
    471 
    472 		{"realname", CMD_ARG, opt_realname},
    473 		{"real", CMD_ARG, opt_realname},
    474 		{"comment", CMD_ARG, opt_realname},
    475 
    476 		{"pass", CMD_ARG, opt_password},
    477 		{"password", CMD_ARG, opt_password},
    478 		{"auth", CMD_ARG, opt_password},
    479 #ifdef TLS
    480 		{"tls", CMD_NARG, opt_tls},
    481 		{"ssl", CMD_NARG, opt_tls},
    482 		{"verify", CMD_NARG, opt_tls_verify},
    483 #endif /* TLS */
    484 		{NULL, 0, 0},
    485 	};
    486 
    487 	while ((ret = command_getopt(&str, opts)) != opt_done) {
    488 		switch (ret) {
    489 		case opt_error:
    490 			return;
    491 		case opt_network:
    492 			network = command_optarg;
    493 			break;
    494 		case opt_nick:
    495 			nick = command_optarg;
    496 			break;
    497 		case opt_username:
    498 			username = command_optarg;
    499 			break;
    500 		case opt_realname:
    501 			realname = command_optarg;
    502 			break;
    503 		case opt_password:
    504 			password = command_optarg;
    505 			break;
    506 #ifdef TLS
    507 		case opt_tls:
    508 			tls = 1;
    509 			break;
    510 		case opt_tls_verify:
    511 			tls_verify = 1;
    512 			break;
    513 #endif /* TLS */
    514 		}
    515 	}
    516 
    517 	host = strtok(str,  " ");
    518 	port = strtok(NULL, " ");
    519 
    520 	if (!host) {
    521 		if (network) {
    522 			if (!(tserver = serv_get(&servers, network)))
    523 				ui_error("no such network", NULL);
    524 		} else {
    525 			if (!server)
    526 				ui_error("must specify host", NULL);
    527 			else
    528 				tserver = server;
    529 		}
    530 		if (server) {
    531 			serv_update(tserver, nick, username, realname, password, tls, tls_verify);
    532 			serv_connect(tserver);
    533 		}
    534 		return;
    535 	}
    536 
    537 	if (tls <= 0)
    538 		tls = 0;
    539 	if (tls_verify <= 0)
    540 		tls_verify = 0;
    541 
    542 	if (!nick && !(nick = config_gets("def.nick"))) {
    543 		user = getpwuid(geteuid());
    544 		nick = user ? user->pw_name : "null";
    545 	}
    546 	if (!username && !(username = config_gets("def.user")))
    547 		username = nick;
    548 	if (!realname && !(realname = config_gets("def.real")))
    549 		realname = nick;
    550 	if (!network)
    551 		network = host;
    552 	if (!port) {
    553 		/* no ifdef required, as -tls only works with -DTLS */
    554 		if (tls)
    555 			port = "6697";
    556 		else
    557 			port = "6667";
    558 	}
    559 
    560 	tserver = serv_add(&servers, network, host, port, nick,
    561 			username, realname, password, tls, tls_verify);
    562 	serv_connect(tserver);
    563 	if (!nouich)
    564 		ui_select(tserver, NULL);
    565 	return;
    566 }
    567 
    568 COMMAND(
    569 command_disconnect) {
    570 	struct Server *sp;
    571 	struct Channel *chp;
    572 	int len;
    573 	char *msg = NULL;
    574 
    575 	if (str) {
    576 		len = strcspn(str, " ");
    577 		for (sp = servers; sp; sp = sp->next) {
    578 			if (strlen(sp->name) == len && strncmp(sp->name, str, len) == 0) {
    579 				msg = strchr(str, ' ');
    580 				if (msg && *msg)
    581 					msg++;
    582 				break;
    583 			}
    584 		}
    585 
    586 		if (sp == NULL) {
    587 			sp = server;
    588 			msg = str;
    589 		}
    590 	} else sp = server;
    591 
    592 	if (!msg || !*msg)
    593 		msg = config_gets("def.quitmessage");
    594 
    595 	/* Add fake quit messages to history.
    596 	 * Theoretically, we could send QUIT and then wait for a
    597 	 * reply, but I don't see the advantage. (Unless the
    598 	 * server fucks with our quit messages somehow).
    599 	 *
    600 	 * Since HIST_SELF is used, there is no need to fake a prefix. */
    601 	hist_format(sp->history, Activity_self, HIST_DFL|HIST_SELF, "QUIT :%s", msg);
    602 	for (chp = sp->channels; chp; chp = chp->next)
    603 		hist_format(chp->history, Activity_self, HIST_DFL|HIST_SELF, "QUIT :%s", msg);
    604 	serv_disconnect(sp, 0, msg);
    605 }
    606 
    607 COMMAND(
    608 command_select) {
    609 	struct Server *sp;
    610 	struct Channel *chp;
    611 	char *tserver = NULL;
    612 	char *tchannel = NULL;
    613 	int ret, buf = 0;
    614 	enum {
    615 		opt_server,
    616 		opt_channel,
    617 		opt_test,
    618 	};
    619 	static struct CommandOpts opts[] = {
    620 		{"server", CMD_ARG, opt_server},
    621 		{"network", CMD_ARG, opt_server},
    622 		{"channel", CMD_ARG, opt_channel},
    623 		{NULL, 0, 0},
    624 	};
    625 
    626 	while ((ret = command_getopt(&str, opts)) != opt_done) {
    627 		switch (ret) {
    628 		case opt_error:
    629 			return;
    630 		case opt_server:
    631 			tserver = command_optarg;
    632 			break;
    633 		case opt_channel:
    634 			tchannel = command_optarg;
    635 			break;
    636 		}
    637 	}
    638 
    639 	if (tserver || tchannel) {
    640 		/* TODO: find closest match instead of perfect matches */
    641 		if (!tserver) {
    642 			ui_error("must specify server and channel, or just server", NULL);
    643 			return;
    644 		}
    645 
    646 		if (!(sp = serv_get(&servers, tserver))) {
    647 			ui_error("could not find server '%s'", tserver);
    648 			return;
    649 		}
    650 
    651 		if (tchannel) {
    652 			for (chp = sp->channels; chp; chp = chp->next)
    653 				if (strcmp(chp->name, tchannel) == 0)
    654 					break;
    655 
    656 			if (!chp) {
    657 				ui_error("could not find channel '%s'", tchannel);
    658 				return;
    659 			}
    660 		} else chp = NULL;
    661 
    662 		ui_select(sp, chp);
    663 
    664 		if (str)
    665 			ui_error("ignoring trailing arguments: '%s'", str);
    666 	} else if (str) {
    667 		buf = atoi(str);
    668 		if (!buf)
    669 			ui_error("invalid buffer index: '%s'", str);
    670 		if (ui_buflist_get(buf, &sp, &chp) != -1)
    671 			ui_select(sp, chp);
    672 	} else {
    673 		command_toofew("select");
    674 	}
    675 }
    676 
    677 COMMAND(
    678 command_set) {
    679 	char *name, *val;
    680 
    681 	if (!str) {
    682 		command_toofew("set");
    683 		return;
    684 	}
    685 	name = strtok_r(str, " ", &val);
    686 	config_set(name, val);
    687 }
    688 
    689 COMMAND(
    690 command_toggle) {
    691 	struct Config *conf;
    692 
    693 	if (!str) {
    694 		command_toofew("toggle");
    695 		return;
    696 	}
    697 	if (strchr(str, ' ')) {
    698 		command_toomany("toggle");
    699 		return;
    700 	}
    701 	if (!(conf = config_getp(str))) {
    702 		ui_error("no such configuration variable", NULL);
    703 		return;
    704 	}
    705 	if (conf->valtype != Val_bool) {
    706 		ui_error("%s is not a boolean variable", str);
    707 		return;
    708 	}
    709 
    710 	config_setl(conf, !conf->num);
    711 }
    712 
    713 COMMAND(
    714 command_format) {
    715 	char *newstr;
    716 	int len;
    717 
    718 	if (!str) {
    719 		command_toofew("format");
    720 		return;
    721 	}
    722 
    723 	len = strlen(str) + CONSTLEN("format.") + 1;
    724 	newstr = smprintf(len, "format.%s", str);
    725 	command_set(server, channel, newstr);
    726 	pfree(&newstr);
    727 }
    728 
    729 COMMAND(
    730 command_server) {
    731 	struct Server *nserver;
    732 	char *tserver, *cmd, *arg;
    733 	char **acmds;
    734 	int i, ret, mode;
    735 	enum { opt_norm, opt_auto, opt_clear };
    736 	struct CommandOpts opts[] = {
    737 		{"auto", CMD_NARG, opt_auto},
    738 		{"clear", CMD_NARG, opt_clear},
    739 		{NULL, 0, 0},
    740 	};
    741 
    742 	mode = opt_norm;
    743 	while ((ret = command_getopt(&str, opts)) != opt_done) {
    744 		switch (ret) {
    745 		case opt_error:
    746 			return;
    747 		case opt_auto:
    748 		case opt_clear:
    749 			if (mode != opt_norm) {
    750 				ui_error("conflicting flags", NULL);
    751 				return;
    752 			}
    753 			mode = ret;
    754 		}
    755 	}
    756 
    757 	tserver = strtok_r(str,  " ", &arg);
    758 	if (mode == opt_norm)
    759 		cmd     = strtok_r(NULL, " ", &arg);
    760 
    761 	if (!tserver) {
    762 		command_toofew("server");
    763 		return;
    764 	}
    765 
    766 	if ((nserver = serv_get(&servers, tserver)) == NULL) {
    767 		ui_error("no such server: '%s'", tserver);
    768 		return;
    769 	}
    770 
    771 	switch (mode) {
    772 	case opt_norm:
    773 		if (!cmd || !*cmd) {
    774 			command_toofew("server");
    775 			return;
    776 		}
    777 
    778 		if (*cmd == '/')
    779 			cmd++;
    780 
    781 		for (i=0; commands[i].name && commands[i].func; i++) {
    782 			if (strcmp(commands[i].name, cmd) == 0) {
    783 				commands[i].func(nserver, channel, arg);
    784 				return;
    785 			}
    786 		}
    787 		ui_error("no such commands: '%s'", cmd);
    788 		break;
    789 	case opt_auto:
    790 		if (!arg || !*arg) {
    791 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_START %s :Autocmds for %s:",
    792 					nserver->name, nserver->name);
    793 			for (acmds = nserver->autocmds; acmds && *acmds; acmds++)
    794 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_LIST %s :%s",
    795 						nserver->name, *acmds);
    796 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_AUTOCMDS_END %s :End of autocmds for %s",
    797 					nserver->name, nserver->name);
    798 		} else {
    799 			if (*arg == '/')
    800 				cmd = arg;
    801 			else
    802 				cmd = smprintf(strlen(arg) + 2, "/%s", arg);
    803 
    804 			serv_auto_add(nserver, cmd);
    805 		}
    806 		break;
    807 	case opt_clear:
    808 		if (*arg) {
    809 			command_toomany("server");
    810 			break;
    811 		}
    812 
    813 		serv_auto_free(nserver);
    814 		break;
    815 	}
    816 }
    817 
    818 COMMAND(
    819 command_names) {
    820 	char *chan, *save = NULL;
    821 
    822 	chan = strtok_r(str, " ", &save);
    823 	if (!chan)
    824 		chan = channel ? channel->name : NULL;
    825 
    826 	if (!chan) {
    827 		command_needselected("names", "channel");
    828 		return;
    829 	}
    830 
    831 	if (save && *save) {
    832 		command_toomany("names");
    833 		return;
    834 	}
    835 
    836 	serv_write(server, Sched_connected, "NAMES %s\r\n", chan);
    837 	expect_set(server, Expect_names, chan);
    838 }
    839 
    840 COMMAND(
    841 command_topic) {
    842 	char *chan, *topic = NULL;
    843 	int clear = 0, ret;
    844 	enum { opt_clear, };
    845 	struct CommandOpts opts[] = {
    846 		{"clear", CMD_NARG, opt_clear},
    847 		{NULL, 0, 0},
    848 	};
    849 
    850 	while ((ret = command_getopt(&str, opts)) != opt_done) {
    851 		switch (ret) {
    852 		case opt_error:
    853 			return;
    854 		case opt_clear:
    855 			clear = 1;
    856 			break;
    857 		}
    858 	}
    859 
    860 	if (str)
    861 		chan = strtok_r(str,  " ", &topic);
    862 	else
    863 		chan = topic = NULL;
    864 
    865 	if (chan && !serv_ischannel(server, chan)) {
    866 		topic = chan;
    867 		chan = NULL;
    868 	}
    869 
    870 	if (!chan && channel) {
    871 		chan = channel->name;
    872 	} else if (!chan) {
    873 		command_needselected("topic", "channel");
    874 		return;
    875 	}
    876 
    877 	if (clear) {
    878 		if (topic) {
    879 			command_toomany("topic");
    880 			return;
    881 		}
    882 		serv_write(server, Sched_connected, "TOPIC %s :\r\n", chan);
    883 		return;
    884 	}
    885 
    886 	if (!topic) {
    887 		serv_write(server, Sched_connected, "TOPIC %s\r\n", chan);
    888 		expect_set(server, Expect_topic, chan);
    889 	} else serv_write(server, Sched_connected, "TOPIC %s :%s\r\n", chan, topic);
    890 }
    891 
    892 COMMAND(
    893 command_oper) {
    894 	char *user, *pass;
    895 
    896 	if (!str) {
    897 		command_toofew("oper");
    898 		return;
    899 	}
    900 
    901 	user = strtok_r(str, " ", &pass);
    902 	if (pass && strchr(pass, ' ')) {
    903 		command_toomany("oper");
    904 		return;
    905 	}
    906 	if (!pass) {
    907 		pass = user;
    908 		user = server->self->nick;
    909 	}
    910 
    911 	serv_write(server, Sched_connected, "OPER %s %s\r\n", user, pass);
    912 }
    913 
    914 static void
    915 command_send0(struct Server *server, char *cmd, char *cmdname, char *str) {
    916 	if (str)
    917 		command_toomany(cmdname);
    918 	else
    919 		serv_write(server, Sched_connected, "%s\r\n", cmd);
    920 }
    921 
    922 COMMAND(
    923 command_lusers) {
    924 	command_send0(server, "LUSERS", "lusers", str);
    925 }
    926 
    927 COMMAND(
    928 command_map) {
    929 	command_send0(server, "MAP", "map", str);
    930 }
    931 
    932 static void
    933 command_send1(struct Server *server, char *cmd, char *cmdname, char *str) {
    934 	if (str && strchr(str, ' '))
    935 		command_toomany(cmdname);
    936 	else if (str)
    937 		serv_write(server, Sched_connected, "%s %s\r\n", cmd, str);
    938 	else
    939 		serv_write(server, Sched_connected, "%s\r\n", cmd);
    940 }
    941 
    942 COMMAND(
    943 command_motd) {
    944 	command_send1(server, "MOTD", "motd", str);
    945 }
    946 
    947 COMMAND(
    948 command_time) {
    949 	command_send1(server, "TIME", "time", str);
    950 }
    951 
    952 static void
    953 command_send2(struct Server *server, char *cmd, char *cmdname, char *str) {
    954 	if (str && strchr(str, ' ') != strrchr(str, ' '))
    955 		command_toomany(cmdname);
    956 	else if (str)
    957 		serv_write(server, Sched_connected, "%s %s\r\n", cmd, str);
    958 	else
    959 		serv_write(server, Sched_connected, "%s\r\n", cmd);
    960 }
    961 
    962 COMMAND(
    963 command_links) {
    964 	command_send2(server, "LINKS", "links", str);
    965 }
    966 
    967 COMMAND(
    968 command_stats) {
    969 	command_send2(server, "STATS", "stats", str);
    970 }
    971 
    972 COMMAND(
    973 command_kill) {
    974 	char *nick, *reason;
    975 
    976 	if (!str) {
    977 		command_toofew("kill");
    978 		return;
    979 	}
    980 
    981 	nick = strtok_r(str, " ", &reason);
    982 	if (!reason)
    983 		reason = config_gets("def.killmessage");
    984 	serv_write(server, Sched_connected, "KILL %s :%s\r\n", nick, reason);
    985 }
    986 
    987 COMMAND(
    988 command_bind) {
    989 	struct Keybind *p;
    990 	char *binding = NULL, *cmd = NULL;
    991 	int delete = 0, ret;
    992 	enum { opt_delete, };
    993 	struct CommandOpts opts[] = {
    994 		{"delete", CMD_NARG, opt_delete},
    995 		{NULL, 0, 0},
    996 	};
    997 
    998 	while ((ret = command_getopt(&str, opts)) != opt_done) {
    999 		switch (ret) {
   1000 		case opt_error:
   1001 			return;
   1002 		case opt_delete:
   1003 			delete = 1;
   1004 			break;
   1005 		}
   1006 	}
   1007 
   1008 	if (str)
   1009 		binding = strtok_r(str, " ", &cmd);
   1010 
   1011 	if (delete) {
   1012 		if (ui_unbind(binding) == -1)
   1013 			ui_error("no such keybind: '%s'", binding);
   1014 		return;
   1015 	}
   1016 
   1017 	if (!binding) {
   1018 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_START :Keybindings:");
   1019 		for (p = keybinds; p; p = p->next)
   1020 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd);
   1021 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_END :End of keybindings");
   1022 	} else if (!cmd) {
   1023 		for (p = keybinds; p; p = p->next) {
   1024 			if (strcmp(p->binding, binding) == 0) {
   1025 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_START :Keybindings:");
   1026 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_LIST %s :%s", ui_unctrl(p->binding), p->cmd);
   1027 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_KEYBIND_END :End of keybindings");
   1028 				return;
   1029 			}
   1030 		}
   1031 
   1032 		ui_error("no such keybind: '%s'", binding);
   1033 	} else {
   1034 		if (ui_bind(binding, cmd) == -1 && !nouich)
   1035 			ui_error("keybind already exists: '%s'", binding);
   1036 	}
   1037 }
   1038 
   1039 COMMAND(
   1040 command_alias) {
   1041 	struct Alias *p;
   1042 	char *alias = NULL, *cmd = NULL;
   1043 	int delete = 0, ret;
   1044 	enum { opt_delete, };
   1045 	struct CommandOpts opts[] = {
   1046 		{"delete", CMD_NARG, opt_delete},
   1047 		{NULL, 0, 0},
   1048 	};
   1049 
   1050 	while ((ret = command_getopt(&str, opts)) != opt_done) {
   1051 		switch (ret) {
   1052 		case opt_error:
   1053 			return;
   1054 		case opt_delete:
   1055 			delete = 1;
   1056 			break;
   1057 		}
   1058 	}
   1059 
   1060 	if (str)
   1061 		alias = strtok_r(str, " ", &cmd);
   1062 
   1063 	if (delete) {
   1064 		if (alias_remove(alias) == -1)
   1065 			ui_error("no such alias: '%s'", alias);
   1066 		return;
   1067 	}
   1068 
   1069 	if (!alias) {
   1070 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_START :Aliases:");
   1071 		for (p = aliases; p; p = p->next)
   1072 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_LIST %s :%s", p->alias, p->cmd);
   1073 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_END :End of aliases");
   1074 	} else if (!cmd) {
   1075 		for (p = aliases; p; p = p->next) {
   1076 			if (strcmp(p->alias, alias) == 0) {
   1077 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_START :Aliases:");
   1078 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_LIST %s :%s", p->alias, p->cmd);
   1079 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_ALIAS_END :End of aliases");
   1080 				return;
   1081 			}
   1082 		}
   1083 
   1084 		ui_error("no such alias: '%s'", alias);
   1085 	} else {
   1086 		if (alias_add(alias, cmd) == -1 && !nouich)
   1087 			ui_error("alias already exists: '%s'", alias);
   1088 	}
   1089 }
   1090 
   1091 
   1092 COMMAND(
   1093 command_help) {
   1094 	int cmdonly = 0;
   1095 	int found = 0;
   1096 	int i, j;
   1097 
   1098 	if (!str) {
   1099 		command_help(server, channel, "/help");
   1100 		return;
   1101 	}
   1102 
   1103 	if (strcmp(str, "commands") == 0) {
   1104 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", str);
   1105 		for (i=0; commands[i].name && commands[i].func; i++)
   1106 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP : /%s", commands[i].name);
   1107 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help");
   1108 		return;
   1109 	}
   1110 
   1111 	if (strcmp(str, "variables") == 0) {
   1112 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", str);
   1113 		for (i=0; config[i].name; i++)
   1114 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_UI : %s", config[i].name);
   1115 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help");
   1116 		return;
   1117 	}
   1118 
   1119 	if (*str == '/') {
   1120 		cmdonly = 1;
   1121 		str++;
   1122 	}
   1123 
   1124 	for (i=0; commands[i].name && commands[i].func; i++) {
   1125 		if (strncmp(commands[i].name, str, strlen(str)) == 0) {
   1126 			found = 1;
   1127 			hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", commands[i].name);
   1128 			for (j=0; commands[i].description[j]; j++)
   1129 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP :%s", commands[i].description[j]);
   1130 			if (strcmp(commands[i].name, str) == 0)
   1131 				goto end; /* only print one for an exact match, i,e, /help format should only print the command, not all formats. */
   1132 		}
   1133 	}
   1134 
   1135 	if (!cmdonly) {
   1136 		for (i=0; config[i].name; i++) {
   1137 			if (strncmp(config[i].name, str, strlen(str)) == 0) {
   1138 				found = 1;
   1139 				hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_START :%s", config[i].name);
   1140 				for (j=0; config[i].description[j]; j++)
   1141 					hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP :%s", config[i].description[j]);
   1142 				if (strcmp(config[i].name, str) == 0)
   1143 					goto end;
   1144 			}
   1145 		}
   1146 	}
   1147 
   1148 end:
   1149 	if (found)
   1150 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_HELP_END :end of help");
   1151 	else
   1152 		ui_error("no help on '%s'", str);
   1153 }
   1154 
   1155 COMMAND(
   1156 command_echo) {
   1157 	if (!str)
   1158 		str = "";
   1159 
   1160 	hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP, "SELF_UI :%s", str);
   1161 }
   1162 
   1163 COMMAND(
   1164 command_grep) {
   1165 	struct History *p;
   1166 	regex_t re;
   1167 	size_t i;
   1168 	int regopt = 0, ret, raw = 0;
   1169 	char errbuf[1024], *s;
   1170 	enum { opt_extended, opt_icase, opt_raw };
   1171 	static struct CommandOpts opts[] = {
   1172 		{"E", CMD_NARG, opt_extended},
   1173 		{"i", CMD_NARG, opt_icase},
   1174 		{"raw", CMD_NARG, opt_raw},
   1175 		{NULL, 0, 0},
   1176 	};
   1177 
   1178 	hist_purgeopt(selected.history, HIST_GREP);
   1179 	windows[Win_main].refresh = 1;
   1180 	if (!str) {
   1181 		return;
   1182 	}
   1183 
   1184 	while ((ret = command_getopt(&str, opts)) != opt_done) {
   1185 		switch (ret) {
   1186 		case opt_error:
   1187 			return;
   1188 		case opt_extended:
   1189 			regopt |= REG_EXTENDED;
   1190 			break;
   1191 		case opt_icase:
   1192 			regopt |= REG_ICASE;
   1193 			break;
   1194 		case opt_raw:
   1195 			raw = 1;
   1196 			break;
   1197 		}
   1198 	}
   1199 
   1200 	if (config_getl("regex.extended"))
   1201 		regopt |= REG_EXTENDED;
   1202 	if (config_getl("regex.icase"))
   1203 		regopt |= REG_ICASE;
   1204 
   1205 	if ((ret = regcomp(&re, str, regopt)) != 0) {
   1206 		regerror(ret, &re, errbuf, sizeof(errbuf));
   1207 		regfree(&re);
   1208 		ui_error("unable to compile regex '%s': %s", str, errbuf);
   1209 		return;
   1210 	}
   1211 
   1212 	hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_GREP, "SELF_GREP_START :%s", str);
   1213 
   1214 	/* Get oldest, but don't set p to NULL */
   1215 	for (p = selected.history->history; p && p->next; p = p->next);
   1216 
   1217 	/* Traverse until we hit a message already generated by /grep */
   1218 	for (; p && !(p->options & HIST_GREP); p = p->prev) {
   1219 		if (!raw && !p->format)
   1220 			p->format = estrdup(format(&windows[Win_main], NULL, p));
   1221 		if (!raw && !p->rformat) {
   1222 			p->rformat = emalloc(strlen(p->format) + 1);
   1223 			/* since only one or zero characters are added to
   1224 			 * rformat for each char in format, and both are the
   1225 			 * same size, there is no need to expand rformat or
   1226 			 * truncate it. */
   1227 			for (i = 0, s = p->format; s && *s; s++) {
   1228 				switch (*s) {
   1229 				case 2: case 9: case 15: case 18: case 21:
   1230 					break;
   1231 				case 3:
   1232 					if (*s && isdigit(*(s+1)))
   1233 						s += 1;
   1234 					if (*s && isdigit(*(s+1)))
   1235 						s += 1;
   1236 					if (*s && *(s+1) == ',' && isdigit(*(s+2)))
   1237 						s += 2;
   1238 					else
   1239 						break; /* break here to avoid needing to check back for comma in next if */
   1240 					if (isdigit(*(s+1)))
   1241 						s += 1;
   1242 					break;
   1243 				case '\n': case ' ':
   1244 					while (*(s+1) == ' ')
   1245 						s++;
   1246 					/* fallthrough */
   1247 				default:
   1248 					p->rformat[i++] = *s != '\n' ? *s : ' ';
   1249 				}
   1250 			}
   1251 		}
   1252 		if (regexec(&re, raw ? p->raw : p->rformat, 0, NULL, 0) == 0)
   1253 			hist_addp(selected.history, p, p->activity, p->options | HIST_GREP | HIST_TMP);
   1254 	}
   1255 
   1256 	hist_format(selected.history, Activity_none, HIST_SHOW|HIST_TMP|HIST_GREP, "SELF_GREP_END :end of /grep command");
   1257 }
   1258 
   1259 COMMAND(
   1260 command_clear) {
   1261 	int ret, cleared = 0;
   1262 	enum { opt_tmp, opt_err, opt_serr, opt_log };
   1263 	static struct CommandOpts opts[] = {
   1264 		{"tmp", CMD_NARG, opt_tmp},
   1265 		{"err", CMD_NARG, opt_err},
   1266 		{"serr", CMD_NARG, opt_serr},
   1267 		{"log", CMD_NARG, opt_log},
   1268 		{NULL, 0, 0},
   1269 	};
   1270 
   1271 	if (str) {
   1272 		while ((ret = command_getopt(&str, opts)) != opt_done) {
   1273 			switch (ret) {
   1274 			case opt_error:
   1275 				return;
   1276 			case opt_tmp:
   1277 				hist_purgeopt(selected.history, HIST_TMP);
   1278 				cleared = 1;
   1279 				break;
   1280 			case opt_err:
   1281 				hist_purgeopt(selected.history, HIST_ERR);
   1282 				cleared = 1;
   1283 				break;
   1284 			case opt_serr:
   1285 				hist_purgeopt(selected.history, HIST_SERR);
   1286 				cleared = 1;
   1287 				break;
   1288 			case opt_log:
   1289 				hist_purgeopt(selected.history, HIST_RLOG);
   1290 				cleared = 1;
   1291 				break;
   1292 			}
   1293 		}
   1294 
   1295 		if (*str) {
   1296 			command_toomany("clear");
   1297 			return;
   1298 		}
   1299 	}
   1300 
   1301 	if (!cleared)
   1302 		hist_purgeopt(selected.history, HIST_ALL);
   1303 	windows[Win_main].refresh = 1;
   1304 }
   1305 
   1306 COMMAND(
   1307 command_scroll) {
   1308 	int ret, winid = Win_main;
   1309 	long diff;
   1310 	enum { opt_buflist, opt_nicklist };
   1311 	static struct CommandOpts opts[] = {
   1312 		{"buflist", CMD_NARG, opt_buflist},
   1313 		{"nicklist", CMD_NARG, opt_nicklist},
   1314 		{NULL, 0, 0},
   1315 	};
   1316 
   1317 	if (!str)
   1318 		goto narg;
   1319 
   1320 	while (!(*str == '-' && isdigit(*(str+1))) && (ret = command_getopt(&str, opts)) != opt_done) {
   1321 		switch (ret) {
   1322 		case opt_error:
   1323 			return;
   1324 		case opt_buflist:
   1325 			winid = Win_buflist;
   1326 			break;
   1327 		case opt_nicklist:
   1328 			winid = Win_nicklist;
   1329 			break;
   1330 		}
   1331 
   1332 		if (*str == '-' && isdigit(*(str+1)))
   1333 			break;
   1334 	}
   1335 
   1336 	if (!*str)
   1337 		goto narg;
   1338 
   1339 	diff = strtol(str, NULL, 10);
   1340 	if (diff == 0 || diff == LONG_MIN)
   1341 		windows[winid].scroll = -1; /* no scroll, tracking */
   1342 	else if (windows[winid].scroll >= 0)
   1343 		windows[winid].scroll += diff;
   1344 	else
   1345 		windows[winid].scroll = diff;
   1346 
   1347 	windows[winid].refresh = 1;
   1348 	return;
   1349 
   1350 narg:
   1351 	command_toofew("scroll");
   1352 }
   1353 
   1354 COMMAND(
   1355 command_source) {
   1356 	if (!str) {
   1357 		command_toofew("source");
   1358 		return;
   1359 	}
   1360 	config_read(homepath(str));
   1361 }
   1362 
   1363 COMMAND(
   1364 command_dump) {
   1365 	FILE *file;
   1366 	int selected = 0;
   1367 	int def = 0, ret;
   1368 	int i;
   1369 	char **aup;
   1370 	struct Server *sp;
   1371 	struct Channel *chp;
   1372 	struct Alias *ap;
   1373 	struct Keybind *kp;
   1374 	struct Ignore *ip;
   1375 	enum {
   1376 		opt_aliases = 1,
   1377 		opt_bindings = 2,
   1378 		opt_formats = 4,
   1379 		opt_config = 8,
   1380 		opt_servers = 16,
   1381 		opt_channels = 32,
   1382 		opt_queries = 64,
   1383 		opt_autocmds = 128,
   1384 		opt_ignores = 256,
   1385 		opt_default = 512,
   1386 	};
   1387 	static struct CommandOpts opts[] = {
   1388 		{"aliases", CMD_NARG, opt_aliases},
   1389 		{"bindings", CMD_NARG, opt_bindings},
   1390 		{"formats", CMD_NARG, opt_formats},
   1391 		{"config", CMD_NARG, opt_config},
   1392 		{"servers", CMD_NARG, opt_servers},
   1393 		{"autocmds", CMD_NARG, opt_autocmds},
   1394 		{"channels", CMD_NARG, opt_channels},
   1395 		{"queries", CMD_NARG, opt_queries},
   1396 		{"ignores", CMD_NARG, opt_ignores},
   1397 		{"default", CMD_NARG, opt_default},
   1398 		{NULL, 0, 0},
   1399 	};
   1400 
   1401 	while ((ret = command_getopt(&str, opts)) != opt_done) {
   1402 		switch (ret) {
   1403 		case opt_error:
   1404 			return;
   1405 		case opt_aliases:
   1406 		case opt_bindings:
   1407 		case opt_formats:
   1408 		case opt_config:
   1409 		case opt_servers:
   1410 		case opt_channels:
   1411 		case opt_autocmds:
   1412 		case opt_ignores:
   1413 			selected |= ret;
   1414 			break;
   1415 		case opt_default:
   1416 			def = 1;
   1417 			break;
   1418 		}
   1419 	}
   1420 
   1421 	if (!selected)
   1422 		selected = opt_default - 1;
   1423 
   1424 	if (!str || !*str) {
   1425 		command_toofew("dump");
   1426 		return;
   1427 	}
   1428 	str = homepath(str);
   1429 
   1430 	if ((file = fopen(str, "wb")) == NULL) {
   1431 		ui_error("cannot open file '%s': %s", str, strerror(errno));
   1432 		return;
   1433 	}
   1434 
   1435 	if (selected & (opt_servers|opt_channels|opt_queries|opt_autocmds) && servers) {
   1436 		if (selected & opt_servers)
   1437 			fprintf(file, "Network connections\n");
   1438 
   1439 		for (sp = servers; sp; sp = sp->next) {
   1440 			if (selected & opt_servers) {
   1441 				fprintf(file, "/connect -network %s ", sp->name);
   1442 				if (strcmp_n(sp->self->nick, config_gets("def.nick")) != 0)
   1443 					fprintf(file, "-nick %s ", sp->self->nick);
   1444 				if (strcmp_n(sp->username, config_gets("def.user")) != 0)
   1445 					fprintf(file, "-user %s ", sp->username);
   1446 				if (strcmp_n(sp->realname, config_gets("def.real")) != 0)
   1447 					fprintf(file, "-real %s ", sp->realname);
   1448 #ifdef TLS
   1449 				if (sp->tls)
   1450 					fprintf(file, "-tls ");
   1451 #endif /* TLS */
   1452 				fprintf(file, "%s %s\n", sp->host, sp->port);
   1453 			}
   1454 			if (selected & opt_autocmds) {
   1455 				for (aup = sp->autocmds; *aup; aup++)
   1456 					fprintf(file, "/server -auto %s %s\n", sp->name, *aup);
   1457 			}
   1458 			if (selected & opt_channels) {
   1459 				for (chp = sp->channels; chp; chp = chp->next)
   1460 					if (!(selected & opt_autocmds) || !serv_auto_haschannel(sp, chp->name))
   1461 						fprintf(file, "/server %s /join %s\n", sp->name, chp->name);
   1462 			}
   1463 			if (selected & opt_queries) {
   1464 				for (chp = sp->queries; chp; chp = chp->next)
   1465 					fprintf(file, "/server %s /query %s\n", sp->name, chp->name);
   1466 			}
   1467 			fprintf(file, "\n");
   1468 		}
   1469 	}
   1470 
   1471 	if (selected & opt_aliases && aliases) {
   1472 		fprintf(file, "Aliases\n");
   1473 		for (ap = aliases; ap; ap = ap->next)
   1474 			fprintf(file, "/alias %s %s\n", ap->alias, ap->cmd);
   1475 		fprintf(file, "\n");
   1476 	}
   1477 
   1478 	if (selected & opt_bindings && keybinds) {
   1479 		fprintf(file, "Keybindings\n");
   1480 		for (kp = keybinds; kp; kp = kp->next)
   1481 			fprintf(file, "/bind %s %s\n", ui_unctrl(kp->binding), kp->cmd);
   1482 		fprintf(file, "\n");
   1483 	}
   1484 
   1485 	if (selected & opt_formats || selected & opt_config) {
   1486 		fprintf(file, "Configuration variables\n");
   1487 		for (i = 0; config[i].name; i++) {
   1488 			if (!config[i].isdef || def) {
   1489 				if (selected & opt_formats && strncmp(config[i].name, "format.", CONSTLEN("format.")) == 0) {
   1490 					fprintf(file, "/format %s %s\n", config[i].name + CONSTLEN("format."), config[i].str);
   1491 				} else if (selected & opt_config && strncmp(config[i].name, "format.", CONSTLEN("format.")) != 0) {
   1492 					fprintf(file, "/set %s %s\n", config[i].name, config_get_pretty(&config[i], 0));
   1493 				}
   1494 			}
   1495 		}
   1496 		fprintf(file, "\n");
   1497 	}
   1498 
   1499 	if (selected & opt_ignores && ignores) {
   1500 		fprintf(file, "Ignore rules\n");
   1501 		for (ip = ignores; ip; ip = ip->next) {
   1502 			if (ip->server)
   1503 				fprintf(file, "/server %s /ignore -server ", ip->server);
   1504 			else
   1505 				fprintf(file, "/ignore ");
   1506 
   1507 			if (ip->format)
   1508 				fprintf(file, "-format %s ", ip->format);
   1509 			if (ip->regopt & REG_EXTENDED)
   1510 				fprintf(file, "-E ");
   1511 			if (ip->regopt & REG_ICASE)
   1512 				fprintf(file, "-i ");
   1513 
   1514 			fprintf(file, "%s\n", ip->text);
   1515 		}
   1516 		fprintf(file, "\n");
   1517 	}
   1518 
   1519 	fclose(file);
   1520 }
   1521 
   1522 COMMAND(
   1523 command_close) {
   1524 	struct Server *sp;
   1525 	struct Channel *chp;
   1526 	int buf;
   1527 
   1528 	if (str) {
   1529 		buf = atoi(str);
   1530 		if (!buf)
   1531 			ui_error("invalid buffer index: '%s'", str);
   1532 		if (ui_buflist_get(buf, &sp, &chp) == -1)
   1533 			return;
   1534 	} else {
   1535 		sp = selected.server;
   1536 		chp = selected.channel;
   1537 	}
   1538 
   1539 	if (!sp) {
   1540 		ui_error("cannot close main buffer", NULL);
   1541 		return;
   1542 	}
   1543 
   1544 	if (chp) {
   1545 		if (serv_ischannel(sp, chp->name)) {
   1546 			serv_write(sp, Sched_connected, "PART %s\r\n", chp->name);
   1547 			chan_remove(&sp->channels, chp->name);
   1548 		} else {
   1549 			chan_remove(&sp->queries, chp->name);
   1550 		}
   1551 		ui_select(sp, NULL);
   1552 	} else {
   1553 		if (sp->status != ConnStatus_notconnected) {
   1554 			ui_error("can't close connected server", NULL);
   1555 			return;
   1556 		} else {
   1557 			serv_remove(&servers, sp->name);
   1558 		}
   1559 		ui_select(NULL, NULL);
   1560 	}
   1561 }
   1562 
   1563 static char *
   1564 strregopt(int regopt) {
   1565 	if (regopt & REG_EXTENDED && regopt & REG_ICASE)
   1566 		return "extended+icase";
   1567 	if (regopt & REG_EXTENDED)
   1568 		return "extended";
   1569 	if (regopt & REG_ICASE)
   1570 		return "basic+icase";
   1571 	return "basic";
   1572 }
   1573 
   1574 COMMAND(
   1575 command_ignore) {
   1576 	struct Ignore *ign, *p;
   1577 	char errbuf[BUFSIZ], *format = NULL;
   1578 	size_t len;
   1579 	long id;
   1580 	int ret, noact = 0, i, regopt = 0, serv = 0;
   1581 	enum { opt_show, opt_hide, opt_extended, opt_icase,
   1582 		opt_server, opt_delete, opt_format, opt_noact };
   1583 	static struct CommandOpts opts[] = {
   1584 		{"E", CMD_NARG, opt_extended},
   1585 		{"i", CMD_NARG, opt_icase},
   1586 		{"show", CMD_NARG, opt_show},
   1587 		{"hide", CMD_NARG, opt_hide},
   1588 		{"server", CMD_NARG, opt_server},
   1589 		{"noact", CMD_NARG, opt_noact},
   1590 		{"delete", CMD_NARG, opt_delete},
   1591 		{"format", CMD_ARG, opt_format},
   1592 		{NULL, 0, 0},
   1593 	};
   1594 
   1595 	if (!str) {
   1596 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_IGNORES_START :Ignoring:");
   1597 		for (p = ignores, i = 1; p; p = p->next, i++)
   1598 			if (!serv || !p->server || strcmp(server->name, p->server) == 0)
   1599 				hist_format(selected.history, Activity_none, HIST_UI|HIST_NIGN, "SELF_IGNORES_LIST %d %s %s %s %s :%s",
   1600 						i, p->server ? p->server : "ANY",
   1601 						p->noact ? "yes" : "no",
   1602 						p->format ? p->format : "ANY",
   1603 						strregopt(p->regopt), p->text);
   1604 		hist_format(selected.history, Activity_none, HIST_UI, "SELF_IGNORES_END :End of ignore list");
   1605 		return;
   1606 	}
   1607 
   1608 	while ((ret = command_getopt(&str, opts)) != opt_done) {
   1609 		switch (ret) {
   1610 		case opt_error:
   1611 			return;
   1612 		case opt_show: case opt_hide:
   1613 			if (*str) {
   1614 				command_toomany("ignore");
   1615 			} else {
   1616 				selected.showign = ret == opt_show;
   1617 				windows[Win_main].refresh = 1;
   1618 			}
   1619 			return;
   1620 		case opt_delete:
   1621 			if (!strisnum(str, 0)) {
   1622 				ui_error("invalid id: %s", str);
   1623 				return;
   1624 			}
   1625 			id = strtol(str, NULL, 10);
   1626 			if (!id || id > INT_MAX || id < INT_MIN)
   1627 				goto idrange;
   1628 			for (p = ignores, i = 1; p; p = p->next, i++) {
   1629 				if (i == id) {
   1630 					if (i == 1)
   1631 						ignores = p->next;
   1632 					if (p->next)
   1633 						p->next->prev = p->prev;
   1634 					if (p->prev)
   1635 						p->prev->next = p->next;
   1636 					regfree(&p->regex);
   1637 					pfree(&p->text);
   1638 					pfree(&p->server);
   1639 					free(p);
   1640 					return;
   1641 				}
   1642 			}
   1643 idrange:
   1644 			ui_error("id out of range: %s", str);
   1645 			return;
   1646 		case opt_format:
   1647 			if (strncmp(command_optarg, "format.", CONSTLEN("format.")) == 0) {
   1648 				format = strdup(command_optarg);
   1649 			} else {
   1650 				len = strlen(command_optarg) + 8;
   1651 				format = smprintf(len, "format.%s", command_optarg);
   1652 			}
   1653 
   1654 			if (!config_gets(format)) {
   1655 				ui_error("no such format: %s", format + CONSTLEN("format"));
   1656 				free(format);
   1657 				return;
   1658 			}
   1659 			break;
   1660 		case opt_noact:
   1661 			noact = 1;
   1662 			break;
   1663 		case opt_extended:
   1664 			regopt |= REG_EXTENDED;
   1665 			break;
   1666 		case opt_icase:
   1667 			regopt |= REG_ICASE;
   1668 			break;
   1669 		case opt_server:
   1670 			serv = 1;
   1671 			break;
   1672 		}
   1673 	}
   1674 
   1675 	if (config_getl("regex.extended"))
   1676 		regopt |= REG_EXTENDED;
   1677 	if (config_getl("regex.icase"))
   1678 		regopt |= REG_ICASE;
   1679 
   1680 	if (!*str) {
   1681 		command_toofew("ignore");
   1682 		return;
   1683 	}
   1684 
   1685 	ign = emalloc(sizeof(struct Ignore));
   1686 	ign->next = ign->prev = NULL;
   1687 	if ((ret = regcomp(&ign->regex, str, regopt)) != 0) {
   1688 		regerror(ret, &ign->regex, errbuf, sizeof(errbuf));
   1689 		ui_error("%s: %s", errbuf, str);
   1690 		free(ign);
   1691 		return;
   1692 	}
   1693 	ign->text   = strdup(str);
   1694 	ign->format = format;
   1695 	ign->regopt = regopt;
   1696 	ign->noact  = noact;
   1697 	ign->server = serv ? strdup(server->name) : NULL;
   1698 
   1699 	if (!nouich)
   1700 		hist_format(selected.history, Activity_none, HIST_UI|HIST_NIGN,
   1701 				"SELF_IGNORES_ADDED %s %s %s %s :%s",
   1702 				serv ? server->name : "ANY",
   1703 				noact ? "yes" : "no",
   1704 				format ? format : "ANY",
   1705 				strregopt(regopt), str);
   1706 
   1707 	if (!ignores) {
   1708 		ignores = ign;
   1709 	} else {
   1710 		for (p = ignores; p && p->next; p = p->next);
   1711 		ign->prev = p;
   1712 		p->next = ign;
   1713 	}
   1714 }
   1715 
   1716 static void
   1717 modelset(char *cmd, struct Server *server, struct Channel *channel,
   1718 		int remove, char mode, char *args) {
   1719 	char *percmds, *p;
   1720 	char *modes;
   1721 	int percmd;
   1722 	int i;
   1723 	size_t len;
   1724 
   1725 	if (!args) {
   1726 		command_toofew(cmd);
   1727 		return;
   1728 	}
   1729 
   1730 	percmds = support_get(server, "MODES");
   1731 	if (percmds)
   1732 		percmd = atoi(percmds);
   1733 	else
   1734 		percmd = config_getl("def.modes");
   1735 
   1736 	/*
   1737 	 * Now, I'd hope that servers do something clever like determining this
   1738 	 * value from the max length of channels, nicks and IRC messages
   1739 	 * overall, so that it is impossible to send messages that are too
   1740 	 * long, but I doubt it.
   1741 	 * TODO: some maths for limiting percmd based on lengths.
   1742 	 * 
   1743 	 * It'd be useful to be able to check if the desired priviledge is
   1744 	 * supported by the server. Theoretically some servers could also use
   1745 	 * different modes for priviledges, in this case it would make sense
   1746 	 * that 'char mode' be the symbol, i,e, ~&@%+ and then we would look up
   1747 	 * the mode in PREFIX.
   1748 	 * TODO: check PREFIX=...
   1749 	 *
   1750 	 */
   1751 
   1752 	/* 2 = null byte and +/- */
   1753 	len = percmd + 2;
   1754 	modes = emalloc(len);
   1755 	*modes = remove ? '-' : '+';
   1756 	for (i = 0; i < percmd; i++)
   1757 		*(modes + i + 1) = mode;
   1758 	*(modes + len - 1) = '\0';
   1759 
   1760 	while (*args) {
   1761 		for (i = 0, p = args; *p && i < percmd; p++)
   1762 			if (*p == ' ' && *(p+1) != ' ')
   1763 				i++;
   1764 		if (*p)
   1765 			*(p - 1) = '\0';
   1766 		else
   1767 			i++;
   1768 		*(modes + i + 1) = '\0';
   1769 
   1770 		serv_write(server, Sched_connected, "MODE %s %s %s\r\n", channel->name, modes, args);
   1771 
   1772 		args = p;
   1773 	}
   1774 
   1775 	expect_set(server, Expect_nosuchnick, channel->name);
   1776 	pfree(&modes);
   1777 }
   1778 
   1779 COMMAND(
   1780 command_op) {
   1781 	modelset("op", server, channel, 0, 'o', str);
   1782 }
   1783 
   1784 COMMAND(
   1785 command_voice) {
   1786 	modelset("voice", server, channel, 0, 'v', str);
   1787 }
   1788 
   1789 COMMAND(
   1790 command_halfop) {
   1791 	modelset("halfop", server, channel, 0, 'h', str);
   1792 }
   1793 
   1794 COMMAND(
   1795 command_admin) {
   1796 	modelset("admin", server, channel, 0, 'a', str);
   1797 }
   1798 
   1799 COMMAND(
   1800 command_owner) {
   1801 	modelset("owner", server, channel, 0, 'q', str);
   1802 }
   1803 
   1804 COMMAND(
   1805 command_deop) {
   1806 	modelset("deop", server, channel, 1, 'o', str);
   1807 }
   1808 
   1809 COMMAND(
   1810 command_devoice) {
   1811 	modelset("devoice", server, channel, 1, 'v', str);
   1812 }
   1813 
   1814 COMMAND(
   1815 command_dehalfop) {
   1816 	modelset("dehalfop", server, channel, 1, 'h', str);
   1817 }
   1818 
   1819 COMMAND(
   1820 command_deadmin) {
   1821 	modelset("deadmin", server, channel, 1, 'a', str);
   1822 }
   1823 
   1824 COMMAND(
   1825 command_deowner) {
   1826 	modelset("deowner", server, channel, 1, 'q', str);
   1827 }
   1828 
   1829 /* In most IRC clients /ban would create a mask from a nickname. I decided
   1830  * against doing this as there would have to be some way of templating a mask,
   1831  * eg, ${nick}*!*@*.{host}. This could've been done through splitting the
   1832  * variable handling out of ui_format and using that here as well, however,
   1833  * all though it could simplify ui_format by processing variables first, then
   1834  * formats, it could cause problems as the variables themselves could contain
   1835  * text that needs to be escaped or dealt with. Of course, there's nothing
   1836  * stopping me from leaving the one in ui_format and creating another, but at
   1837  * that point I decided it wasn't worth it for one command. Another approach I
   1838  * though of was having the ability to create templates with combinatorial
   1839  * options, but I think the mental overhead for doing so could be spent
   1840  * constructing the masks manually instead. *shrug*/
   1841 COMMAND(
   1842 command_ban) {
   1843 	modelset("ban", server, channel, 0, 'b', str);
   1844 }
   1845 
   1846 COMMAND(
   1847 command_unban) {
   1848 	modelset("unban", server, channel, 1, 'b', str);
   1849 }
   1850 
   1851 COMMAND(
   1852 command_invite) {
   1853 	char *nick, *chan;
   1854 
   1855 	if (!str) {
   1856 		command_toofew("invite");
   1857 		return;
   1858 	}
   1859 
   1860 	nick = strtok_r(str, " ", &chan);
   1861 	if (!chan)
   1862 		chan = channel->name;
   1863 
   1864 	serv_write(server, Sched_connected, "INVITE %s %s\r\n", nick, chan);
   1865 }
   1866 
   1867 int
   1868 command_getopt(char **str, struct CommandOpts *opts) {
   1869 	char *opt;
   1870 
   1871 	if (!str || !*str || **str != '-') {
   1872 		if (str && *str && **str == '\\' && *((*str)+1) == '-')
   1873 			(*str)++;
   1874 		return opt_done;
   1875 	}
   1876 
   1877 	opt = struntil((*str)+1, ' ');
   1878 
   1879 	for (; opts->opt; opts++) {
   1880 		if (strcmp(opts->opt, opt) == 0) {
   1881 			*str = strchr(*str, ' ');
   1882 			if (*str)
   1883 				(*str)++;
   1884 
   1885 			if (opts->arg) {
   1886 				command_optarg = *str;
   1887 				if (*str)
   1888 					*str = strchr(*str, ' ');
   1889 				if (*str && **str) {
   1890 					**str = '\0';
   1891 					(*str)++;
   1892 				}
   1893 			} else {
   1894 				if (*str && **str)
   1895 					*((*str)-1) = '\0';
   1896 			}
   1897 
   1898 			/* Always return something that's not NULL */
   1899 			if (!*str)
   1900 				*str = "";
   1901 
   1902 			return opts->ret;
   1903 		}
   1904 	}
   1905 
   1906 	ui_error("no such option '%s'", opt);
   1907 	return opt_error;
   1908 }
   1909 
   1910 int
   1911 alias_add(char *alias, char *cmd) {
   1912 	struct Alias *p;
   1913 	char *tmp;
   1914 
   1915 	if (!alias || !cmd)
   1916 		return -1;
   1917 
   1918 	if (*alias != '/') {
   1919 		tmp = smprintf(strlen(alias) + 2, "/%s", alias);
   1920 	}
   1921 
   1922 	for (p = aliases; p; p = p->next)
   1923 		if (strcmp(p->alias, tmp) == 0)
   1924 			return -1;
   1925 
   1926 	p = emalloc(sizeof(struct Alias));
   1927 	if (*alias != '/')
   1928 		p->alias = tmp;
   1929 	else
   1930 		p->alias = estrdup(alias);
   1931 
   1932 	if (*cmd != '/') {
   1933 		tmp = smprintf(strlen(cmd) + 2, "/%s", cmd);
   1934 		p->cmd = tmp;
   1935 	} else p->cmd = estrdup(cmd);
   1936 	p->prev = NULL;
   1937 	p->next = aliases;
   1938 	if (aliases)
   1939 		aliases->prev = p;
   1940 	aliases = p;
   1941 
   1942 	return 0;
   1943 }
   1944 
   1945 int
   1946 alias_remove(char *alias) {
   1947 	struct Alias *p;
   1948 	char *tmp = NULL;
   1949 
   1950 	if (!alias)
   1951 		return -1;
   1952 	if (*alias != '/') {
   1953 		tmp = smprintf(strlen(alias) + 2, "/%s", alias);
   1954 		alias = tmp;
   1955 		/* tmp is guaranteed NULL or freeable */
   1956 	};
   1957 
   1958 	for (p=aliases; p; p = p->next) {
   1959 		if (strcmp(p->alias, alias) == 0) {
   1960 			if (p->prev)
   1961 				p->prev->next = p->next;
   1962 			else
   1963 				aliases = p->next;
   1964 
   1965 			if (p->next)
   1966 				p->next->prev = p->prev;
   1967 
   1968 			pfree(&p->alias);
   1969 			pfree(&p->cmd);
   1970 			pfree(&p);
   1971 			pfree(&tmp);
   1972 			return 0;
   1973 		}
   1974 	}
   1975 
   1976 	return -1;
   1977 }
   1978 
   1979 char *
   1980 alias_eval(char *cmd) {
   1981 	static char ret[8192];
   1982 	struct Alias *p;
   1983 	int len, rc = 0;
   1984 	char *s;
   1985 
   1986 	if ((s = strchr(cmd, ' ')) != NULL)
   1987 		len = s - cmd;
   1988 	else
   1989 		len = strlen(cmd);
   1990 
   1991 	for (p = aliases; p; p = p->next) {
   1992 		if (p->cmd && strlen(p->alias) == len && strncmp(p->alias, cmd, len) == 0) {
   1993 			rc += strlcpy(&ret[rc], p->cmd, sizeof(ret) - rc);
   1994 			break;
   1995 		}
   1996 	}
   1997 
   1998 	if (!rc)
   1999 		return cmd;
   2000 
   2001 	rc += strlcpy(&ret[rc], cmd + len, sizeof(ret) - rc);
   2002 	return ret;
   2003 }
   2004 
   2005 void
   2006 command_eval(struct Server *server, char *str) {
   2007 	struct Command *cmdp;
   2008 	char msg[512];
   2009 	char *cmd;
   2010 	char *s, *dup;
   2011 
   2012 	s = dup = estrdup(alias_eval(str));
   2013 
   2014 	if (*s != '/' || strncmp(s, "/ /", CONSTLEN("/ /")) == 0) {
   2015 		/* Provide a way to escape commands
   2016 		 *      "/ /cmd" --> "/cmd"      */
   2017 		if (strncmp(s, "/ /", CONSTLEN("/ /")) == 0)
   2018 			s += 2;
   2019 
   2020 		if (selected.channel && selected.server) {
   2021 			// TODO: message splitting
   2022 			snprintf(msg, sizeof(msg), "PRIVMSG %s :%s", selected.channel->name, s);
   2023 			serv_write(selected.server, Sched_connected, "%s\r\n", msg);
   2024 			hist_format(selected.channel->history, Activity_self, HIST_SHOW|HIST_LOG|HIST_SELF, "%s", msg);
   2025 		} else {
   2026 			ui_error("channel not selected, message ignored", NULL);
   2027 		}
   2028 		goto end;
   2029 	} else {
   2030 		s++;
   2031 		cmd = s;
   2032 		s = strchr(s, ' ');
   2033 		if (s && *s) {
   2034 			*s = '\0';
   2035 			s++;
   2036 			if (*s == '\0')
   2037 				s = NULL;
   2038 		}
   2039 
   2040 		for (cmdp = commands; cmdp->name && cmdp->func; cmdp++) {
   2041 			if (strcmp(cmdp->name, cmd) == 0) {
   2042 				if (cmdp->need == 2 && !selected.channel)
   2043 					ui_error("/%s requires a channel to be selected", cmdp->name);
   2044 				else if (cmdp->need == 2 && selected.channel->server != server)
   2045 					ui_error("/%s cannot be run with /server", cmdp->name);
   2046 				else if (cmdp->need == 1 && !server)
   2047 					ui_error("/%s requires a server to be selected or provided by /server", cmdp->name);
   2048 				else
   2049 					cmdp->func(server, selected.channel, s);
   2050 
   2051 
   2052 				goto end;
   2053 			}
   2054 		}
   2055 
   2056 		ui_error("no such command: '%s'", cmd);
   2057 	}
   2058 end:
   2059 	pfree(&dup);
   2060 }