hirc

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

handle.c (17296B)


      1 /*
      2  * src/handle.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 <stdio.h>
     21 #include <ctype.h>
     22 #include <string.h>
     23 #include <stdlib.h>
     24 #include "hirc.h"
     25 #include "data/handlers.h"
     26 
     27 HANDLER(
     28 handle_PING) {
     29 	assert_warn(param_len(msg->params) >= 2,);
     30 
     31 	serv_write(server, Sched_now, "PONG :%s\r\n", *(msg->params+1));
     32 }
     33 
     34 HANDLER(
     35 handle_PONG) {
     36 	int len;
     37 
     38 	assert_warn((len = param_len(msg->params)) >= 2,);
     39 
     40 	/* RFC1459 says that PONG should have a list of daemons,
     41 	 * but that's not how PONG seems to work in modern IRC. 
     42 	 * Therefore, consider the last parameter as the "message" */
     43 	if (strcmp_n(*(msg->params + len - 1), expect_get(server, Expect_pong)) == 0) {
     44 		hist_addp(server->history, msg, Activity_status, HIST_DFL);
     45 		expect_set(server, Expect_pong, NULL);
     46 	}
     47 }
     48 
     49 HANDLER(
     50 handle_JOIN) {
     51 	struct Channel *chan;
     52 	struct Nick *nick;
     53 	char *target;
     54 
     55 	assert_warn(msg->from && param_len(msg->params) >= 2,);
     56 
     57 	target = *(msg->params+1);
     58 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
     59 		chan = chan_add(server, &server->channels, target, 0);
     60 	chan_setold(chan, 0);
     61 
     62 	nick = msg->from;
     63 	if (nick_get(&chan->nicks, nick->nick) == NULL)
     64 		nick_add(&chan->nicks, msg->from->prefix, ' ', server);
     65 
     66 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
     67 	hist_addp(chan->history, msg, Activity_status, HIST_DFL);
     68 
     69 	if (nick_isself(nick)) {
     70 		if (strcmp_n(target, expect_get(server, Expect_join)) == 0)
     71 			ui_select(server, chan);
     72 		else
     73 			windows[Win_buflist].refresh = 1;
     74 		expect_set(server, Expect_join, NULL);
     75 	} else if (selected.channel == chan) {
     76 		windows[Win_nicklist].refresh = 1;
     77 	}
     78 }
     79 
     80 HANDLER(
     81 handle_PART) {
     82 	struct Channel *chan;
     83 	struct Nick *nick;
     84 	char *target;
     85 
     86 	assert_warn(msg->from && param_len(msg->params) >= 2,);
     87 
     88 	target = *(msg->params+1);
     89 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
     90 		return;
     91 
     92 	nick = msg->from;
     93 	if (nick_isself(nick)) {
     94 		chan_setold(chan, 1);
     95 		nick_free_list(&chan->nicks);
     96 		if (chan == selected.channel && strcmp_n(target, expect_get(server, Expect_part)) == 0) {
     97 			ui_select(selected.server, NULL);
     98 			expect_set(server, Expect_part, NULL);
     99 		}
    100 		windows[Win_buflist].refresh = 1;
    101 	} else {
    102 		nick_remove(&chan->nicks, nick->nick);
    103 		if (chan == selected.channel)
    104 			windows[Win_nicklist].refresh = 1;
    105 	}
    106 
    107 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    108 	hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    109 }
    110 
    111 HANDLER(
    112 handle_KICK) {
    113 	struct Channel *chan;
    114 	struct Nick *nick;
    115 	char *target;
    116 
    117 	assert_warn(msg->from && param_len(msg->params) >= 3,);
    118 
    119 	target = *(msg->params+1);
    120 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    121 		chan = chan_add(server, &server->channels, target, 0);
    122 
    123 	nick = nick_create(*(msg->params+2), ' ', server);
    124 	if (nick_isself(nick)) {
    125 		chan_setold(chan, 1);
    126 		nick_free_list(&chan->nicks);
    127 		if (chan == selected.channel)
    128 			ui_select(selected.server, NULL);
    129 		windows[Win_buflist].refresh = 1;
    130 	} else {
    131 		nick_remove(&chan->nicks, nick->nick);
    132 		if (chan == selected.channel)
    133 			windows[Win_nicklist].refresh = 1;
    134 	}
    135 
    136 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    137 	hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    138 	nick_free(nick);
    139 }
    140 
    141 HANDLER(
    142 handle_ERROR) {
    143 	char *lowered, *p;
    144 	int recon = 1;
    145 
    146 	if (param_len(msg->params) > 1) {
    147 		lowered = estrdup(*(msg->params+1));
    148 		for (p = lowered; *p; p++)
    149 			*p = tolower(*p);
    150 		if (strstr(lowered, "unauthorized") ||
    151 				strstr(lowered, "invalid") ||
    152 				strstr(lowered, "kill") ||
    153 				strstr(lowered, "ban") ||
    154 				strstr(lowered, "kline") ||
    155 				strstr(lowered, "gline") ||
    156 				strstr(lowered, "k-line") ||
    157 				strstr(lowered, "g-line"))
    158 			recon = 0;
    159 		pfree(&lowered);
    160 	}
    161 
    162 	serv_disconnect(server, recon, NULL);
    163 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    164 }
    165 
    166 HANDLER(
    167 handle_QUIT) {
    168 	struct Channel *chan;
    169 	struct Nick *nick;
    170 
    171 	assert_warn(msg->from && param_len(msg->params) >= 1,);
    172 
    173 	nick = msg->from;
    174 	if (nick_isself(nick)) {
    175 		serv_disconnect(server, 0, NULL);
    176 	}
    177 
    178 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    179 	for (chan = server->channels; chan; chan = chan->next) {
    180 		if (nick_get(&chan->nicks, nick->nick) != NULL) {
    181 			nick_remove(&chan->nicks, nick->nick);
    182 			hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    183 			if (chan == selected.channel)
    184 				windows[Win_nicklist].refresh = 1;
    185 		}
    186 	}
    187 }
    188 
    189 HANDLER(
    190 handle_MODE) {
    191 	struct Channel *chan;
    192 
    193 	assert_warn(msg->from && param_len(msg->params) >= 3,);
    194 
    195 	if (serv_ischannel(server, *(msg->params+1))) {
    196 		if ((chan = chan_get(&server->channels, *(msg->params+1), -1)) == NULL)
    197 			chan = chan_add(server, &server->channels, *(msg->params+1), 0);
    198 
    199 		expect_set(server, Expect_nosuchnick, NULL);
    200 		hist_addp(server->history, msg, Activity_status, HIST_LOG);
    201 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    202 		serv_write(server, Sched_now, "MODE %s\r\n", chan->name); /* Get full mode via RPL_CHANNELMODEIS
    203 						                           * instead of concatenating manually */
    204 		serv_write(server, Sched_now, "NAMES %s\r\n", chan->name); /* Also get updated priviledges */
    205 	} else {
    206 		hist_addp(server->history, msg, Activity_status, HIST_DFL);
    207 	}
    208 }
    209 
    210 HANDLER(
    211 handle_PRIVMSG) {
    212 	int act_direct = Activity_hilight, act_regular = Activity_message, act;
    213 	struct Channel *chan;
    214 	struct Nick *nick;
    215 	char *target;
    216 
    217 	assert_warn(msg->from && param_len(msg->params) >= 3,);
    218 
    219 	if (strcmp(*msg->params, "NOTICE") == 0)
    220 		act_direct = act_regular = Activity_notice;
    221 
    222 	target = *(msg->params + 1);
    223 	nick = msg->from;
    224 	if (strchr(nick->nick, '.')) {
    225 		/* it's a server */
    226 		hist_addp(server->history, msg, Activity_status, HIST_DFL);
    227 	} else if (strcmp_n(target, server->self->nick) == 0) {
    228 		/* it's messaging me */
    229 		if ((chan = chan_get(&server->queries, nick->nick, -1)) == NULL)
    230 			chan = chan_add(server, &server->queries, nick->nick, 1);
    231 		chan_setold(chan, 0);
    232 
    233 		hist_addp(chan->history, msg, act_direct, HIST_DFL);
    234 	} else if (nick_isself(nick) && !strchr("#&!+", *target)) {
    235 		/* i'm messaging someone */
    236 		if ((chan = chan_get(&server->queries, target, -1)) == NULL)
    237 			chan = chan_add(server, &server->queries, target, 1);
    238 		chan_setold(chan, 0);
    239 
    240 		hist_addp(chan->history, msg, act_regular, HIST_DFL);
    241 	} else {
    242 		/* message to a channel */
    243 		if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    244 			chan = chan_add(server, &server->channels, target, 0);
    245 
    246 		if (strstr(*(msg->params+2), server->self->nick))
    247 			act = act_direct;
    248 		else
    249 			act = act_regular;
    250 		hist_addp(chan->history, msg, act, HIST_DFL);
    251 	}
    252 }
    253 
    254 HANDLER(
    255 handle_INVITE) {
    256 	struct Channel *query;
    257 
    258 	assert_warn(msg->from && param_len(msg->params) >= 3,);
    259 
    260 	if ((query = chan_get(&server->queries, msg->from->nick, -1)) != NULL)
    261 		hist_addp(query->history, msg, Activity_status, HIST_DFL);
    262 	else
    263 		hist_addp(server->history, msg, Activity_status, HIST_DFL);
    264 }
    265 
    266 HANDLER(
    267 handle_RPL_ISUPPORT) {
    268 	char *key, *value;
    269 	char **params = msg->params;
    270 
    271 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    272 	assert_warn(param_len(msg->params) >= 4,);
    273 
    274 	params += 2;
    275 
    276 	/* skip the last param ".... :are supported by this server" */
    277 	for (; *params && *(params+1); params++) {
    278 		key = estrdup(*params);
    279 		if ((value = strchr(key, '=')) != NULL) {
    280 			*value = '\0';
    281 			if (*(value+1))
    282 				value++;
    283 			else
    284 				value = NULL;
    285 		}
    286 
    287 		support_set(server, key, value);
    288 		pfree(&key);
    289 	}
    290 }
    291 
    292 HANDLER(
    293 handle_RPL_AWAY) {
    294 	struct Channel *query;
    295 
    296 	if ((query = chan_get(&server->queries, *(msg->params+2), -1)) != NULL) {
    297 		hist_addp(query->history, msg, Activity_status, HIST_DFL);
    298 		hist_addp(server->history, msg, Activity_status, HIST_LOG);
    299 	} else {
    300 		hist_addp(server->history, msg, Activity_status, HIST_DFL);
    301 	}
    302 }
    303 
    304 HANDLER(
    305 handle_RPL_CHANNELMODEIS) {
    306 	struct Channel *chan;
    307 
    308 	assert_warn(param_len(msg->params) >= 4,);
    309 
    310 	if ((chan = chan_get(&server->channels, *(msg->params+2), -1)) == NULL)
    311 		chan = chan_add(server, &server->channels, *(msg->params+2), 0);
    312 
    313 	pfree(&chan->mode);
    314 	chan->mode = estrdup(*(msg->params+3));
    315 
    316 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    317 	if (expect_get(server, Expect_channelmodeis)) {
    318 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    319 		expect_set(server, Expect_channelmodeis, NULL);
    320 	} else {
    321 		hist_addp(chan->history, msg, Activity_status, HIST_LOG);
    322 	}
    323 }
    324 
    325 HANDLER(
    326 handle_RPL_INVITING) {
    327 	struct Channel *chan;
    328 
    329 	assert_warn(param_len(msg->params) >= 4,);
    330 
    331 	if ((chan = chan_get(&server->channels, *(msg->params+3), -1)) == NULL)
    332 		chan = chan_add(server, &server->channels, *(msg->params+3), 0);
    333 
    334 	hist_addp(chan->history, msg, Activity_status, HIST_DFL|HIST_SELF);
    335 }
    336 
    337 HANDLER(
    338 handle_RPL_NAMREPLY) {
    339 	struct Channel *chan;
    340 	struct Nick *oldnick;
    341 	char **params = msg->params;
    342 	char *nick, priv, *target;
    343 	char **nicks, **nicksref;
    344 	char *supportedprivs;
    345 
    346 	assert_warn(param_len(params) >= 5,);
    347 
    348 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    349 
    350 	params += 3;
    351 	target = *params;
    352 
    353 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    354 		chan = chan_add(server, &server->channels, target, 0);
    355 
    356 	if (strcmp_n(target, expect_get(server, Expect_names)) == 0)
    357 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    358 	else
    359 		hist_addp(chan->history, msg, Activity_status, HIST_LOG);
    360 
    361 	params++;
    362 	supportedprivs = strchr(support_get(server, "PREFIX"), ')');
    363 	if (supportedprivs == NULL || supportedprivs[0] == '\0')
    364 		supportedprivs = "";
    365 	else
    366 		supportedprivs++;
    367 
    368 	nicksref = nicks = param_create(*params);
    369 	for (; *nicks && **nicks; nicks++) {
    370 		priv = ' ';
    371 		nick = *nicks;
    372 		if (strchr(supportedprivs, **nicks)) {
    373 			priv = **nicks;
    374 			while (strchr(supportedprivs, *nick))
    375 				nick++;
    376 		}
    377 		if ((oldnick = nick_get(&chan->nicks, nick)) == NULL)
    378 			nick_add(&chan->nicks, nick, priv, server);
    379 		else
    380 			oldnick->priv = priv;
    381 	}
    382 
    383 	if (selected.channel == chan)
    384 		windows[Win_nicklist].refresh = 1;
    385 	param_free(nicksref);
    386 }
    387 
    388 HANDLER(
    389 handle_RPL_ENDOFNAMES) {
    390 	char *target;
    391 
    392 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    393 	assert_warn(param_len(msg->params) >= 3,);
    394 
    395 	target = *(msg->params+2);
    396 	if (strcmp_n(target, expect_get(server, Expect_names)) == 0)
    397 		expect_set(server, Expect_names, NULL);
    398 }
    399 
    400 HANDLER(
    401 handle_ERR_NOSUCHNICK) {
    402 	char *expectation;
    403 	struct Channel *chan = NULL;
    404 
    405 	if ((expectation = expect_get(server, Expect_nosuchnick)) != NULL) {
    406 		chan = chan_get(&server->channels, expectation, -1);
    407 		expect_set(server, Expect_nosuchnick, NULL);
    408 	}
    409 
    410 	hist_addp(chan ? chan->history : server->history, msg, Activity_error, HIST_DFL|HIST_SERR);
    411 }
    412 
    413 HANDLER(
    414 handle_ERR_NICKNAMEINUSE) {
    415 	char nick[64]; /* should be limited to 9 chars, but newer servers *shrug*/
    416 	struct Nick *nnick;
    417 
    418 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    419 
    420 	if (expect_get(server, Expect_nicknameinuse) == NULL) {
    421 		snprintf(nick, sizeof(nick), "%s_", server->self->nick);
    422 		nnick = nick_create(nick, ' ', server);
    423 		nick_free(server->self);
    424 		server->self = nnick;
    425 		server->self->self = 1;
    426 		serv_write(server, Sched_now, "NICK %s\r\n", nick);
    427 	} else {
    428 		expect_set(server, Expect_nicknameinuse, NULL);
    429 	}
    430 }
    431 
    432 HANDLER(
    433 handle_NICK) {
    434 	struct Nick *nick, *chnick;
    435 	struct Channel *chan;
    436 	char prefix[128];
    437 	char *newnick;
    438 	char priv;
    439 
    440 	assert_warn(msg->from && *msg->params && *(msg->params+1),);
    441 
    442 	nick = msg->from;
    443 	hist_addp(server->history, msg, Activity_status, msg->from->self ? HIST_DFL : HIST_LOG);
    444 	newnick = *(msg->params+1);
    445 
    446 	if (strcmp_n(nick->nick, newnick) == 0)
    447 		return;
    448 
    449 	if (nick_isself(nick)) {
    450 		nick_free(server->self);
    451 		server->self = nick_create(newnick, ' ', server);
    452 		server->self->self = 1;
    453 		expect_set(server, Expect_nicknameinuse, NULL);
    454 	}
    455 
    456 	for (chan = server->channels; chan; chan = chan->next) {
    457 		if ((chnick = nick_get(&chan->nicks, nick->nick)) != NULL) {
    458 			snprintf(prefix, sizeof(prefix), ":%s!%s@%s",
    459 					newnick, chnick->ident, chnick->host);
    460 			priv = chnick->priv;
    461 			nick_remove(&chan->nicks, nick->nick);
    462 			nick_add(&chan->nicks, prefix, priv, server);
    463 			hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    464 			if (selected.channel == chan)
    465 				windows[Win_nicklist].refresh = 1;
    466 		}
    467 	}
    468 }
    469 
    470 HANDLER(
    471 handle_TOPIC) {
    472 	struct Channel *chan;
    473 
    474 	assert_warn(param_len(msg->params) >= 3 && msg->from,);
    475 
    476 	if ((chan = chan_get(&server->channels, *(msg->params+1), -1)) != NULL) {
    477 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    478 		pfree(&chan->topic);
    479 		chan->topic = *(msg->params+2) ? estrdup(*(msg->params+2)) : NULL;
    480 	}
    481 }
    482 
    483 HANDLER(
    484 handle_RPL_NOTOPIC) {
    485 	struct Channel *chan;
    486 	char *target;
    487 
    488 	assert_warn(param_len(msg->params) >= 4,);
    489 
    490 	target = *(msg->params+2);
    491 
    492 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    493 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    494 		return;
    495 
    496 	if (strcmp_n(target, expect_get(server, Expect_topic)) == 0) {
    497 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    498 		expect_set(server, Expect_topic, NULL);
    499 	} else {
    500 		hist_addp(chan->history, msg, Activity_status, HIST_LOG);
    501 	}
    502 }
    503 
    504 HANDLER(
    505 handle_RPL_TOPIC) {
    506 	struct Channel *chan;
    507 	char *target, *topic;
    508 
    509 	assert_warn(param_len(msg->params) >= 4,);
    510 
    511 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    512 
    513 	target = *(msg->params+2);
    514 	topic = *(msg->params+3);
    515 
    516 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    517 		return;
    518 
    519 	pfree(&chan->topic);
    520 	chan->topic = topic ? estrdup(topic) : NULL;
    521 
    522 	if (strcmp_n(target, expect_get(server, Expect_topic)) == 0) {
    523 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    524 		expect_set(server, Expect_topic, NULL);
    525 		expect_set(server, Expect_topicwhotime, target);
    526 	} else {
    527 		hist_addp(chan->history, msg, Activity_status, HIST_LOG);
    528 	}
    529 }
    530 
    531 HANDLER(
    532 handle_RPL_TOPICWHOTIME) {
    533 	struct Channel *chan;
    534 	char *target;
    535 
    536 	assert_warn(param_len(msg->params) >= 5,);
    537 
    538 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    539 
    540 	target = *(msg->params+2);
    541 
    542 	if ((chan = chan_get(&server->channels, target, -1)) == NULL)
    543 		return;
    544 
    545 	if (strcmp_n(target, expect_get(server, Expect_topicwhotime)) == 0) {
    546 		hist_addp(chan->history, msg, Activity_status, HIST_DFL);
    547 		expect_set(server, Expect_topicwhotime, NULL);
    548 	} else {
    549 		hist_addp(chan->history, msg, Activity_status, HIST_LOG);
    550 	}
    551 }
    552 
    553 HANDLER(
    554 handle_RPL_WELCOME) {
    555 	if (server->status != ConnStatus_connected) {
    556 		/* XXX: unify this with RPL_ENDOFMOTD */
    557 		server->status = ConnStatus_connected;
    558 		serv_auto_send(server);
    559 		schedule_send(server, Sched_connected);
    560 	}
    561 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    562 	windows[Win_buflist].refresh = 1;
    563 }
    564 
    565 HANDLER(
    566 handle_RPL_MOTD) {
    567 	char *text;
    568 
    569 	if (config_getl("motd.removedash")) {
    570 		text = msg->raw;
    571 		if (*text == ':')
    572 			text++;
    573 		if ((text = strchr(text, ':'))) {
    574 			text++;
    575 			if (strncmp(text, "- ", CONSTLEN("- ")) == 0)
    576 				memmove(text, text + 2, strlen(text + 2) + 1);
    577 			else if (strncmp(text, "-", CONSTLEN("-")) == 0)
    578 				memmove(text, text + 1, strlen(text + 1) + 1);
    579 		}
    580 	}
    581 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    582 }
    583 
    584 HANDLER(
    585 handle_RPL_ENDOFMOTD) {
    586 	/* If server doesn't support RPL_WELCOME, use RPL_ENDOFMOTD to set status */
    587 	if (server->status != ConnStatus_connected) {
    588 		server->status = ConnStatus_connected;
    589 		serv_auto_send(server);
    590 		schedule_send(server, Sched_connected);
    591 	}
    592 	hist_addp(server->history, msg, Activity_status, HIST_DFL);
    593 	windows[Win_buflist].refresh = 1;
    594 }
    595 
    596 void
    597 handle_logonly(struct Server *server, struct History *msg) {
    598 	hist_addp(server->history, msg, Activity_status, HIST_LOG);
    599 }
    600 
    601 void
    602 handle(struct Server *server, char *msg) {
    603 	struct History *hist;
    604 	time_t timestamp;
    605 	char **params;
    606 	char *cmd;
    607 	int i;
    608 
    609 	timestamp = time(NULL);
    610 	params = param_create(msg);
    611 	if (!*params) {
    612 		pfree(&params);
    613 		return;
    614 	}
    615 
    616 	if (**params == ':' || **params == '|')
    617 		cmd = *(params + 1);
    618 	else
    619 		cmd = *(params);
    620 
    621 	for (i=0; cmd && handlers[i].cmd; i++) {
    622 		if (strcmp(handlers[i].cmd, cmd) == 0) {
    623 			if (handlers[i].func) {
    624 				/* histinfo set to the server's history
    625 				 * currently, but not actually appended */
    626 				hist = hist_create(server->history, NULL, msg, 0, timestamp, 0);
    627 				handlers[i].func(server, hist);
    628 				hist_free(hist);
    629 			}
    630 			/* NULL handlers will stop a message being added to server->history */
    631 			goto end;
    632 		}
    633 	}
    634 
    635 	/* add it to server->history if there is no handler */
    636 	if (*cmd == '4' && *cmd == '5')
    637 		hist_add(server->history, msg, Activity_error, timestamp, HIST_DFL|HIST_SERR);
    638 	else
    639 		hist_add(server->history, msg, Activity_status, timestamp, HIST_DFL);
    640 
    641 end:
    642 	param_free(params);
    643 }