1 /* proto-bahamut.c - IRC protocol output
2 * Copyright 2000-2006 srvx Development Team
4 * This file is part of srvx.
6 * srvx is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
21 #include "proto-common.c"
23 #define CAPAB_TS3 0x01
24 #define CAPAB_NOQUIT 0x02
25 #define CAPAB_SSJOIN 0x04
26 #define CAPAB_BURST 0x08
27 #define CAPAB_UNCONNECT 0x10
28 #define CAPAB_NICKIP 0x20
29 #define CAPAB_TSMODE 0x40
30 #define CAPAB_ZIP 0x80
32 struct service_message_info {
33 privmsg_func_t on_privmsg;
34 privmsg_func_t on_notice;
37 static dict_t service_msginfo_dict; /* holds service_message_info structs */
38 static int uplink_capab;
39 static void privmsg_user_helper(struct userNode *un, void *data);
41 void irc_svsmode(struct userNode *target, char *modes, unsigned long stamp);
44 AddServer(struct server *uplink, const char *name, int hops, unsigned long boot, unsigned long link, UNUSED_ARG(const char *numeric), const char *description) {
47 sNode = calloc(1, sizeof(*sNode));
48 sNode->uplink = uplink;
49 safestrncpy(sNode->name, name, sizeof(sNode->name));
53 sNode->users = dict_new();
54 safestrncpy(sNode->description, description, sizeof(sNode->description));
55 serverList_init(&sNode->children);
57 /* uplink may be NULL if we're just building ourself */
58 serverList_append(&sNode->uplink->children, sNode);
60 dict_insert(servers, sNode->name, sNode);
62 if (hops && !self->burst) {
64 for (n=0; n<slf_used; n++) {
73 DelServer(struct server* serv, int announce, const char *message) {
75 dict_iterator_t it, next;
78 if (announce && (serv->uplink == self) && (serv != self->uplink)) {
79 irc_squit(serv, message, NULL);
81 for (nn=serv->children.used; nn>0;) {
82 if (serv->children.list[--nn] != self) {
83 DelServer(serv->children.list[nn], false, "uplink delinking");
86 for (it=dict_first(serv->users); it; it=next) {
88 DelUser(iter_data(it), NULL, false, "server delinking");
90 if (serv->uplink) serverList_remove(&serv->uplink->children, serv);
91 if (serv == self->uplink) self->uplink = NULL;
92 dict_remove(servers, serv->name);
93 serverList_clean(&serv->children);
94 dict_delete(serv->users);
99 is_valid_nick(const char *nick) {
100 /* IRC has some of The Most Fucked-Up ideas about character sets
102 if ((*nick < 'A') || (*nick >= '~')) return 0;
103 for (++nick; *nick; ++nick) {
104 if (!((*nick >= 'A') && (*nick < '~'))
110 if (strlen(nick) > nicklen) return 0;
115 AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *userinfo, unsigned long timestamp, irc_in_addr_t realip, unsigned long stamp) {
116 struct userNode *uNode, *oldUser;
117 unsigned int nn, dummy;
120 log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server does not exist!", uplink, nick);
124 dummy = modes && modes[0] == '*';
127 } else if (!is_valid_nick(nick)) {
128 log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): invalid nickname detected.", uplink, nick);
132 if ((oldUser = GetUserH(nick))) {
133 if (IsLocal(oldUser) && IsService(oldUser)) {
134 /* The service should collide the new user off. */
135 oldUser->timestamp = timestamp - 1;
138 if (oldUser->timestamp > timestamp) {
139 /* "Old" user is really newer; remove them */
140 DelUser(oldUser, 0, 1, "Overruled by older nick");
142 /* User being added is too new */
147 uNode = calloc(1, sizeof(*uNode));
148 uNode->nick = strdup(nick);
149 safestrncpy(uNode->ident, ident, sizeof(uNode->ident));
150 safestrncpy(uNode->info, userinfo, sizeof(uNode->info));
151 safestrncpy(uNode->hostname, hostname, sizeof(uNode->hostname));
153 uNode->timestamp = timestamp;
154 modeList_init(&uNode->channels);
155 uNode->uplink = uplink;
156 dict_insert(uplink->users, uNode->nick, uNode);
157 if (++uNode->uplink->clients > uNode->uplink->max_clients) {
158 uNode->uplink->max_clients = uNode->uplink->clients;
161 dict_insert(clients, uNode->nick, uNode);
162 if (dict_size(clients) > max_clients) {
163 max_clients = dict_size(clients);
164 max_clients_time = now;
167 mod_usermode(uNode, modes);
168 if (dummy) uNode->modes |= FLAGS_DUMMY;
169 if (stamp) call_account_func(uNode, NULL, 0, stamp);
170 if (IsLocal(uNode)) irc_user(uNode);
171 for (nn=0; nn<nuf_used; nn++) {
172 if (nuf_list[nn](uNode)) break;
178 AddLocalUser(const char *nick, const char *ident, const char *hostname, const char *desc, const char *modes)
180 unsigned long timestamp = now;
181 struct userNode *old_user = GetUserH(nick);
182 static const irc_in_addr_t ipaddr;
187 if (IsLocal(old_user))
189 timestamp = old_user->timestamp - 1;
192 hostname = self->name;
193 return AddUser(self, nick, ident, hostname, modes, desc, timestamp, ipaddr, 0);
197 free_user(struct userNode *user)
204 DelUser(struct userNode* user, struct userNode *killer, int announce, const char *why) {
207 for (nn=user->channels.used; nn>0;) {
208 DelChannelUser(user, user->channels.list[--nn]->channel, NULL, false);
210 for (nn=duf_used; nn>0; ) duf_list[--nn](user, killer, why);
211 user->uplink->clients--;
212 dict_remove(user->uplink->users, user->nick);
213 if (IsOper(user)) userList_remove(&curr_opers, user);
214 if (IsInvisible(user)) invis_clients--;
215 if (user == dict_find(clients, user->nick, NULL)) dict_remove(clients, user->nick);
220 irc_kill(killer, user, why);
223 dict_remove(service_msginfo_dict, user->nick);
224 modeList_clean(&user->channels);
226 if (dead_users.size) {
227 userList_append(&dead_users, user);
234 irc_server(struct server *srv) {
236 putsock("SERVER %s %d :%s", srv->name, srv->hops, srv->description);
238 putsock(":%s SERVER %s %d :%s", self->name, srv->name, srv->hops, srv->description);
243 irc_user(struct userNode *user) {
246 if (!user || user->nick[0] != ' ') return;
247 if (IsOper(user)) modes[modelen++] = 'o';
248 if (IsInvisible(user)) modes[modelen++] = 'i';
249 if (IsWallOp(user)) modes[modelen++] = 'w';
250 if (IsService(user)) modes[modelen++] = 'k';
251 if (IsDeaf(user)) modes[modelen++] = 'd';
252 if (IsReggedNick(user)) modes[modelen++] = 'r';
253 if (IsGlobal(user)) modes[modelen++] = 'g';
255 putsock("NICK %s %d %lu +%s %s %s %s %d %u :%s",
256 user->nick, user->uplink->hops+2, (unsigned long)user->timestamp,
257 modes, user->ident, user->hostname, user->uplink->name, 0,
258 ntohl(user->ip.in6_32[3]), user->info);
262 irc_account(struct userNode *user, UNUSED_ARG(const char *stamp), UNUSED_ARG(unsigned long timestamp), unsigned long serial)
264 if (IsReggedNick(user)) {
265 irc_svsmode(user, "+rd", serial);
267 irc_svsmode(user, "+d", serial);
272 irc_fakehost(UNUSED_ARG(struct userNode *user), UNUSED_ARG(const char *host))
274 /* not supported in bahamut */
278 irc_regnick(struct userNode *user)
280 if (IsReggedNick(user)) {
281 irc_svsmode(user, "+r", 0);
283 irc_svsmode(user, "-r", 0);
288 irc_nick(struct userNode *user, const char *old_nick) {
289 if (user->uplink == self) {
290 /* update entries in PRIVMSG/NOTICE handlers (if they exist) */
291 struct service_message_info *smi = dict_find(service_msginfo_dict, user->nick, NULL);
293 dict_remove2(service_msginfo_dict, old_nick, 1);
294 dict_insert(service_msginfo_dict, user->nick, smi);
297 putsock(":%s NICK %s :%lu", old_nick, user->nick, (unsigned long)user->timestamp);
301 irc_pass(const char *passwd) {
302 putsock("PASS %s :TS", passwd);
307 putsock("CAPAB TS3 NOQUIT SSJOIN BURST UNCONNECT NICKIP TSMODE");
312 putsock("SVINFO 3 3 0 :%lu", (unsigned long)now);
316 irc_introduce(const char *passwd) {
317 extern unsigned long burst_begin;
325 timeq_add(now + ping_freq, timed_send_ping, 0);
329 irc_ping(const char *something) {
330 putsock("PING :%s", something);
334 irc_pong(const char *who, const char *data) {
335 putsock(":%s PONG %s :%s", self->name, who, data);
339 irc_quit(struct userNode *user, const char *message) {
340 putsock(":%s QUIT :%s", user->nick, message);
344 irc_squit(struct server *srv, const char *message, const char *service_message) {
345 if (!service_message) service_message = message;
346 /* If we're leaving the network, QUIT all our clients. */
347 if ((srv == self) && (cManager.uplink->state == CONNECTED)) {
349 for (it = dict_first(srv->users); it; it = iter_next(it)) {
350 irc_quit(iter_data(it), service_message);
353 putsock(":%s SQUIT %s 0 :%s", self->name, srv->name, message);
355 /* Reconnect to the currently selected server. */
356 cManager.uplink->tries = 0;
357 log_module(MAIN_LOG, LOG_INFO, "Squitting from uplink: %s", message);
363 deliver_to_dummy(struct userNode *source, struct userNode *dest, const char *message, int type)
365 struct service_message_info *smi;
367 if (!dest || !IsDummy(dest) || !IsLocal(dest))
369 smi = dict_find(service_msginfo_dict, dest->nick, NULL);
372 if (smi && smi->on_privmsg)
374 smi->on_privmsg(source, dest, message, 0);
379 if (smi && smi->on_notice)
381 smi->on_notice(source, dest, message, 0);
390 irc_privmsg(struct userNode *from, const char *to, const char *message) {
391 if (!deliver_to_dummy(from, GetUserH(to), message, 1))
392 putsock(":%s PRIVMSG %s :%s", from->nick, to, message);
396 irc_notice(struct userNode *from, const char *to, const char *message) {
397 if (!deliver_to_dummy(from, GetUserH(to), message, 0))
398 putsock(":%s NOTICE %s :%s", from->nick, to, message);
402 irc_notice_user(struct userNode *from, struct userNode *to, const char *message) {
403 if (!deliver_to_dummy(from, to, message, 0))
404 putsock(":%s NOTICE %s :%s", from->nick, to->nick, message);
408 irc_wallchops(UNUSED_ARG(struct userNode *from), UNUSED_ARG(const char *to), UNUSED_ARG(const char *message)) {
412 irc_join(struct userNode *who, struct chanNode *what) {
413 if (what->members.used == 1) {
414 putsock(":%s SJOIN %lu %s + :@%s", self->name, (unsigned long)what->timestamp, what->name, who->nick);
416 putsock(":%s SJOIN %lu %s", who->nick, (unsigned long)what->timestamp, what->name);
421 irc_invite(struct userNode *from, struct userNode *who, struct chanNode *to) {
422 putsock(":%s INVITE %s %s", from->nick, who->nick, to->name);
426 irc_mode(struct userNode *who, struct chanNode *target, const char *modes) {
427 putsock(":%s MODE %s %lu %s", who->nick, target->name, (unsigned long)target->timestamp, modes);
431 irc_svsmode(struct userNode *target, char *modes, unsigned long stamp) {
432 extern struct userNode *nickserv;
434 putsock(":%s SVSMODE %s %lu %s %lu", nickserv->nick, target->nick, (unsigned long)target->timestamp, modes, stamp);
436 putsock(":%s SVSMODE %s %lu %s", nickserv->nick, target->nick, (unsigned long)target->timestamp, modes);
441 irc_kick(struct userNode *who, struct userNode *target, struct chanNode *from, const char *msg) {
442 putsock(":%s KICK %s %s :%s", who->nick, from->name, target->nick, msg);
443 ChannelUserKicked(who, target, from);
447 irc_part(struct userNode *who, struct chanNode *what, const char *reason) {
449 putsock(":%s PART %s :%s", who->nick, what->name, reason);
451 putsock(":%s PART %s", who->nick, what->name);
456 irc_topic(struct userNode *who, struct chanNode *what, const char *topic) {
457 putsock(":%s TOPIC %s :%s", who->nick, what->name, topic);
461 irc_fetchtopic(struct userNode *from, const char *to) {
462 if (!from || !to) return;
463 putsock(":%s TOPIC %s", from->nick, to);
467 irc_gline(struct server *srv, struct gline *gline) {
468 char host[HOSTLEN+1], ident[USERLEN+1], *sep;
471 log_module(MAIN_LOG, LOG_WARNING, "%s tried to send a targeted G-line for %s (not supported by protocol!)", gline->issuer, gline->target);
474 if (!(sep = strchr(gline->target, '@'))) {
475 log_module(MAIN_LOG, LOG_ERROR, "%s tried to add G-line with bad mask %s", gline->issuer, gline->target);
478 len = sep - gline->target + 1;
479 if (len > ArrayLength(ident)) len = ArrayLength(ident);
480 safestrncpy(ident, gline->target, len);
481 safestrncpy(host, sep+1, ArrayLength(host));
482 putsock(":%s AKILL %s %s %lu %s %lu :%s", self->name, host, ident, (unsigned long)(gline->expires-gline->issued), gline->issuer, (unsigned long)gline->issued, gline->reason);
486 irc_settime(UNUSED_ARG(const char *srv_name_mask), UNUSED_ARG(unsigned long new_time))
488 /* Bahamut has nothing like this, so ignore it. */
492 irc_ungline(const char *mask) {
493 char host[HOSTLEN+1], ident[USERLEN+1], *sep;
495 if (!(sep = strchr(mask, '@'))) {
496 log_module(MAIN_LOG, LOG_ERROR, "Tried to remove G-line with bad mask %s", mask);
499 len = sep - mask + 1;
500 if (len > ArrayLength(ident)) len = ArrayLength(ident);
501 safestrncpy(ident, mask, len);
502 safestrncpy(host, sep+1, ArrayLength(host));
503 putsock(":%s RAKILL %s %s", self->name, host, ident);
507 irc_error(const char *to, const char *message) {
509 putsock("%s ERROR :%s", to, message);
511 putsock(":%s ERROR :%s", self->name, message);
516 irc_kill(struct userNode *from, struct userNode *target, const char *message) {
518 putsock(":%s KILL %s :%s!%s (%s)", from->nick, target->nick, self->name, from->nick, message);
520 putsock(":%s KILL %s :%s (%s)", self->name, target->nick, self->name, message);
525 irc_raw(const char *what) {
530 irc_stats(struct userNode *from, struct server *target, char type) {
531 putsock(":%s STATS %c :%s", from->nick, type, target->name);
535 irc_svsnick(struct userNode *from, struct userNode *target, const char *newnick)
537 putsock(":%s SVSNICK %s %s :%lu", from->nick, target->nick, newnick, (unsigned long)now);
541 irc_numeric(struct userNode *user, unsigned int num, const char *format, ...) {
544 va_start(arg_list, format);
545 vsnprintf(buffer, MAXLEN-2, format, arg_list);
546 buffer[MAXLEN-1] = 0;
547 putsock(":%s %03d %s %s", self->name, num, user->nick, buffer);
551 parse_foreach(char *target_list, foreach_chanfunc cf, foreach_nonchan nc, foreach_userfunc uf, foreach_nonuser nu, void *data) {
555 while (*j != 0 && *j != ',') j++;
558 if (IsChannelName(target_list)) {
559 struct chanNode *chan = GetChannel(target_list);
561 if (cf) cf(chan, data);
563 if (nc) nc(target_list, data);
566 struct userNode *user;
567 struct privmsg_desc *pd = data;
569 pd->is_qualified = 0;
570 if (*target_list == '@') {
572 } else if (strchr(target_list, '@')) {
573 struct server *server;
575 pd->is_qualified = 1;
576 user = GetUserH(strtok(target_list, "@"));
577 server = GetServerH(strtok(NULL, "@"));
579 if (user && (user->uplink != server)) {
580 /* Don't attempt to index into any arrays
581 using a user's numeric on another server. */
585 user = GetUserH(target_list);
589 if (uf) uf(user, data);
591 if (nu) nu(target_list, data);
595 } while (old == ',');
598 static CMD_FUNC(cmd_notice) {
599 struct privmsg_desc pd;
600 if ((argc < 3) || !origin) return 0;
601 if (!(pd.user = GetUserH(origin))) return 1;
602 if (IsGagged(pd.user) && !IsOper(pd.user)) return 1;
605 parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
609 static CMD_FUNC(cmd_privmsg) {
610 struct privmsg_desc pd;
611 if ((argc < 3) || !origin) return 0;
612 if (!(pd.user = GetUserH(origin))) return 1;
613 if (IsGagged(pd.user) && !IsOper(pd.user)) return 1;
616 parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
620 static CMD_FUNC(cmd_whois) {
621 struct userNode *from;
622 struct userNode *who;
626 if (!(from = GetUserH(origin))) {
627 log_module(MAIN_LOG, LOG_ERROR, "Could not find WHOIS origin user %s", origin);
630 if(!(who = GetUserH(argv[2]))) {
631 irc_numeric(from, ERR_NOSUCHNICK, "%s@%s :No such nick", argv[2], self->name);
634 if (IsHiddenHost(who) && !IsOper(from)) {
635 /* Just stay quiet. */
638 irc_numeric(from, RPL_WHOISUSER, "%s %s %s * :%s", who->nick, who->ident, who->hostname, who->info);
639 #ifdef WITH_PROTOCOL_P10
640 if (his_servername && his_servercomment)
641 irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, his_servername, his_servercomment);
644 irc_numeric(from, RPL_WHOISSERVER, "%s %s :%s", who->nick, who->uplink->name, who->uplink->description);
647 irc_numeric(from, RPL_WHOISOPERATOR, "%s :is a megalomaniacal power hungry tyrant", who->nick);
649 irc_numeric(from, RPL_ENDOFWHOIS, "%s :End of /WHOIS list", who->nick);
653 static CMD_FUNC(cmd_capab) {
654 static const struct {
658 { "TS3", CAPAB_TS3 },
659 { "NOQUIT", CAPAB_NOQUIT },
660 { "SSJOIN", CAPAB_SSJOIN },
661 { "BURST", CAPAB_BURST },
662 { "UNCONNECT", CAPAB_UNCONNECT },
663 { "NICKIP", CAPAB_NICKIP },
664 { "TSMODE", CAPAB_TSMODE },
665 { "ZIP", CAPAB_ZIP },
671 for(nn=1; nn<argc; nn++) {
672 for (mm=0; capabs[mm].name && irccasecmp(capabs[mm].name, argv[nn]); mm++) ;
673 if (capabs[mm].name) {
674 uplink_capab |= capabs[mm].mask;
676 log_module(MAIN_LOG, LOG_INFO, "Saw unrecognized/unhandled capability %s. Please notify srvx developers so they can add it.", argv[nn]);
682 static void burst_channel(struct chanNode *chan) {
684 int pos, base_len, len, queued;
687 if (!chan->members.used) return;
688 /* send list of users in the channel.. */
689 base_len = sprintf(line, ":%s SJOIN %lu %s ", self->name, (unsigned long)chan->timestamp, chan->name);
690 len = irc_make_chanmode(chan, line+base_len);
691 pos = base_len + len;
694 for (nn=0; nn<chan->members.used; nn++) {
695 struct modeNode *mn = chan->members.list[nn];
696 len = strlen(mn->user->nick);
697 if (pos + len > 500) {
705 if (mn->modes & MODE_CHANOP) line[pos++] = '@';
706 if (mn->modes & MODE_VOICE) line[pos++] = '+';
707 memcpy(line+pos, mn->user->nick, len);
711 /* print the last line */
714 /* now send the bans.. */
715 base_len = sprintf(line, ":%s MODE %lu %s +", self->name, (unsigned long)chan->timestamp, chan->name);
716 pos = sizeof(line)-1;
718 for (nn=queued=0; nn<chan->banlist.used; nn++) {
719 struct banNode *bn = chan->banlist.list[nn];
720 len = strlen(bn->ban);
721 if (pos < base_len+queued+len+4) {
726 putsock("%s%s", line, line+pos);
727 pos = sizeof(line)-1;
730 memcpy(line+pos, bn->ban, len);
739 putsock("%s%s", line, line+pos);
743 static void send_burst() {
745 for (it = dict_first(servers); it; it = iter_next(it)) {
746 struct server *serv = iter_data(it);
747 if ((serv != self) && (serv != self->uplink)) irc_server(serv);
750 for (it = dict_first(clients); it; it = iter_next(it)) {
751 irc_user(iter_data(it));
753 for (it = dict_first(channels); it; it = iter_next(it)) {
754 burst_channel(iter_data(it));
756 /* Uplink will do AWAY and TOPIC bursts before sending BURST 0, but we don't */
760 static CMD_FUNC(cmd_server) {
761 if (argc < 4) return 0;
763 AddServer(GetServerH(origin), argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
765 self->uplink = AddServer(self, argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
770 static CMD_FUNC(cmd_svinfo) {
771 if (argc < 5) return 0;
772 if ((atoi(argv[1]) < 3) || (atoi(argv[2]) > 3)) return 0;
773 /* TODO: something with the timestamp we get from the other guy */
778 static CMD_FUNC(cmd_ping)
780 irc_pong(self->name, argc > 1 ? argv[1] : origin);
781 timeq_del(0, timed_send_ping, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
782 timeq_del(0, timed_ping_timeout, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
783 timeq_add(now + ping_freq, timed_send_ping, 0);
788 static CMD_FUNC(cmd_burst) {
789 struct server *sender = GetServerH(origin);
790 if (!sender) return 0;
791 if (argc == 1) return 1;
792 if (sender == self->uplink) {
793 cManager.uplink->state = CONNECTED;
795 sender->self_burst = 0;
796 recalc_bursts(sender);
800 static CMD_FUNC(cmd_nick) {
802 if ((un = GetUserH(origin))) {
804 if (argc < 2) return 0;
805 NickChange(un, argv[1], 1);
807 /* new nick from a server */
811 if (argc < 10) return 0;
812 stamp = strtoul(argv[8], NULL, 0);
814 ip.in6_32[3] = htonl(atoi(argv[9]));
815 un = AddUser(GetServerH(argv[7]), argv[1], argv[5], argv[6], argv[4], argv[argc-1], atoi(argv[3]), ip, stamp);
820 static CMD_FUNC(cmd_sjoin) {
821 struct chanNode *cNode;
822 struct userNode *uNode;
823 struct modeNode *mNode;
824 unsigned int next = 4, last;
825 char *nick, *nickend;
827 if ((argc == 3) && (uNode = GetUserH(origin))) {
829 if (!(cNode = GetChannel(argv[2]))) {
830 log_module(MAIN_LOG, LOG_ERROR, "Unable to find SJOIN target %s", argv[2]);
833 AddChannelUser(uNode, cNode);
836 if (argc < 5) return 0;
837 if (argv[3][0] == '+') {
838 char modes[MAXLEN], *pos;
840 for (pos = argv[3], n_modes = 1; *pos; pos++) {
841 if ((*pos == 'k') || (*pos == 'l')) n_modes++;
843 unsplit_string(argv+3, n_modes, modes);
844 cNode = AddChannel(argv[2], atoi(argv[1]), modes, NULL);
845 } else if (argv[3][0] == '0') {
846 cNode = GetChannel(argv[2]);
848 log_module(MAIN_LOG, LOG_ERROR, "Unsure how to handle SJOIN when arg 3 is %s", argv[3]);
852 /* argv[next] is now the space-delimited, @+-prefixed list of
853 * nicks in the channel. Split it and add the users. */
854 for (last = 0, nick = argv[next]; !last; nick = nickend + 1) {
856 for (nickend = nick; *nickend && (*nickend != ' '); nickend++) ;
857 if (!*nickend) last = 1;
859 if (*nick == '@') { mode |= MODE_CHANOP; nick++; }
860 if (*nick == '+') { mode |= MODE_VOICE; nick++; }
861 if ((uNode = GetUserH(nick)) && (mNode = AddChannelUser(uNode, cNode))) {
868 static CMD_FUNC(cmd_mode) {
872 log_module(MAIN_LOG, LOG_ERROR, "Illegal MODE from %s (no arguments).", origin);
874 } else if (IsChannelName(argv[1])) {
878 if (!(cn = GetChannel(argv[1]))) {
879 log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s whose mode is changing", argv[1]);
883 if ((un = GetUserH(origin))) {
884 /* Update idle time for the user */
885 if ((mn = GetUserMode(cn, un)))
886 mn->idle_since = now;
888 /* Must be a server in burst or something. Make sure we're using the right timestamp. */
889 cn->timestamp = atoi(argv[2]);
892 return mod_chanmode(un, cn, argv+3, argc-3, MCP_ALLOW_OVB|MCP_FROM_SERVER|MC_ANNOUNCE);
893 } else if ((un = GetUserH(argv[1]))) {
894 mod_usermode(un, argv[2]);
897 log_module(MAIN_LOG, LOG_ERROR, "Not sure what MODE %s is affecting (not a channel name and no such user)", argv[1]);
902 static CMD_FUNC(cmd_topic) {
904 if (argc < 5) return 0;
905 if (!(cn = GetChannel(argv[1]))) {
906 log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s whose topic is being set", argv[1]);
909 if (irccasecmp(origin, argv[2])) {
910 /* coming from a topic burst; the origin is a server */
911 safestrncpy(cn->topic, argv[4], sizeof(cn->topic));
912 safestrncpy(cn->topic_nick, argv[2], sizeof(cn->topic_nick));
913 cn->topic_time = atoi(argv[3]);
915 SetChannelTopic(cn, GetUserH(argv[2]), argv[4], 0);
920 static CMD_FUNC(cmd_away) {
923 if (!(un = GetUserH(origin))) {
924 log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s sending AWAY", origin);
928 un->modes |= FLAGS_AWAY;
930 un->modes &= ~FLAGS_AWAY;
935 static CMD_FUNC(cmd_kick) {
936 if (argc < 3) return 0;
937 ChannelUserKicked(GetUserH(origin), GetUserH(argv[2]), GetChannel(argv[1]));
941 static CMD_FUNC(cmd_kill) {
942 struct userNode *user;
943 if (argc < 3) return 0;
944 if (!(user = GetUserH(argv[1]))) {
945 log_module(MAIN_LOG, LOG_ERROR, "Unable to find kill victim %s", argv[1]);
948 if (IsLocal(user) && IsService(user)) {
949 ReintroduceUser(user);
951 DelUser(user, GetUserH(origin), false, ((argc >= 3) ? argv[2] : NULL));
956 static CMD_FUNC(cmd_pong)
958 if (argc < 3) return 0;
959 if (!strcmp(argv[2], self->name)) {
960 timeq_del(0, timed_send_ping, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
961 timeq_del(0, timed_ping_timeout, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
962 timeq_add(now + ping_freq, timed_send_ping, 0);
968 static CMD_FUNC(cmd_num_topic)
970 static struct chanNode *cn;
975 cn = GetChannel(argv[2]);
977 log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s in topic reply", argv[2]);
983 switch (atoi(argv[0])) {
986 break; /* no topic */
990 safestrncpy(cn->topic, unsplit_string(argv+3, argc-3, NULL), sizeof(cn->topic));
995 safestrncpy(cn->topic_nick, argv[3], sizeof(cn->topic_nick));
996 cn->topic_time = atoi(argv[4]);
999 return 0; /* should never happen */
1004 static CMD_FUNC(cmd_quit)
1006 struct userNode *user;
1007 if (argc < 2) return 0;
1008 /* Sometimes we get a KILL then a QUIT or the like, so we don't want to
1009 * call DelUser unless we have the user in our grasp. */
1010 if ((user = GetUserH(origin))) {
1011 DelUser(user, NULL, false, argv[1]);
1016 static CMD_FUNC(cmd_squit)
1018 struct server *server;
1021 if (!(server = GetServerH(argv[1])))
1023 if (server == self->uplink) {
1024 /* Force a reconnect to the currently selected server. */
1025 cManager.uplink->tries = 0;
1026 log_module(MAIN_LOG, LOG_INFO, "Squitting from uplink: %s", argv[3]);
1031 DelServer(server, 0, argv[3]);
1035 static CMD_FUNC(cmd_svsnick)
1037 struct userNode *target, *dest;
1038 if (argc < 4) return 0;
1039 if (!(target = GetUserH(argv[1]))) return 0;
1040 if (!IsLocal(target)) return 0;
1041 if ((dest = GetUserH(argv[2]))) return 0; /* Note: Bahamut will /KILL instead. */
1042 NickChange(target, argv[2], 0);
1046 static oper_func_t *of_list;
1047 static unsigned int of_size = 0, of_used = 0;
1049 void parse_cleanup(void) {
1051 if (of_list) free(of_list);
1052 dict_delete(irc_func_dict);
1053 dict_delete(service_msginfo_dict);
1055 for (nn=0; nn<dead_users.used; nn++) free_user(dead_users.list[nn]);
1056 userList_clean(&dead_users);
1059 void init_parse(void) {
1060 const char *str, *desc;
1062 str = conf_get_data("server/ping_freq", RECDB_QSTRING);
1063 ping_freq = str ? ParseInterval(str) : 120;
1064 str = conf_get_data("server/ping_timeout", RECDB_QSTRING);
1065 ping_timeout = str ? ParseInterval(str) : 30;
1066 str = conf_get_data("server/hostname", RECDB_QSTRING);
1067 desc = conf_get_data("server/description", RECDB_QSTRING);
1068 if (!str || !desc) {
1069 log_module(MAIN_LOG, LOG_ERROR, "No server/hostname entry in config file.");
1072 self = AddServer(NULL, str, 0, boot_time, now, NULL, desc);
1074 str = conf_get_data("server/ping_freq", RECDB_QSTRING);
1075 ping_freq = str ? ParseInterval(str) : 120;
1076 str = conf_get_data("server/ping_timeout", RECDB_QSTRING);
1077 ping_timeout = str ? ParseInterval(str) : 30;
1079 service_msginfo_dict = dict_new();
1080 dict_set_free_data(service_msginfo_dict, free);
1081 irc_func_dict = dict_new();
1082 dict_insert(irc_func_dict, "ADMIN", cmd_admin);
1083 dict_insert(irc_func_dict, "AWAY", cmd_away);
1084 dict_insert(irc_func_dict, "BURST", cmd_burst);
1085 dict_insert(irc_func_dict, "CAPAB", cmd_capab);
1086 dict_insert(irc_func_dict, "ERROR", cmd_error);
1087 dict_insert(irc_func_dict, "GNOTICE", cmd_dummy);
1088 dict_insert(irc_func_dict, "INVITE", cmd_dummy);
1089 dict_insert(irc_func_dict, "KICK", cmd_kick);
1090 dict_insert(irc_func_dict, "KILL", cmd_kill);
1091 dict_insert(irc_func_dict, "LUSERSLOCK", cmd_dummy);
1092 dict_insert(irc_func_dict, "MODE", cmd_mode);
1093 dict_insert(irc_func_dict, "NICK", cmd_nick);
1094 dict_insert(irc_func_dict, "NOTICE", cmd_notice);
1095 dict_insert(irc_func_dict, "PART", cmd_part);
1096 dict_insert(irc_func_dict, "PASS", cmd_pass);
1097 dict_insert(irc_func_dict, "PING", cmd_ping);
1098 dict_insert(irc_func_dict, "PONG", cmd_pong);
1099 dict_insert(irc_func_dict, "PRIVMSG", cmd_privmsg);
1100 dict_insert(irc_func_dict, "QUIT", cmd_quit);
1101 dict_insert(irc_func_dict, "SERVER", cmd_server);
1102 dict_insert(irc_func_dict, "SJOIN", cmd_sjoin);
1103 dict_insert(irc_func_dict, "SQUIT", cmd_squit);
1104 dict_insert(irc_func_dict, "STATS", cmd_stats);
1105 dict_insert(irc_func_dict, "SVSNICK", cmd_svsnick);
1106 dict_insert(irc_func_dict, "SVINFO", cmd_svinfo);
1107 dict_insert(irc_func_dict, "TOPIC", cmd_topic);
1108 dict_insert(irc_func_dict, "VERSION", cmd_version);
1109 dict_insert(irc_func_dict, "WHOIS", cmd_whois);
1110 dict_insert(irc_func_dict, "331", cmd_num_topic);
1111 dict_insert(irc_func_dict, "332", cmd_num_topic);
1112 dict_insert(irc_func_dict, "333", cmd_num_topic);
1113 dict_insert(irc_func_dict, "413", cmd_num_topic);
1115 userList_init(&dead_users);
1116 reg_exit_func(parse_cleanup);
1119 int parse_line(char *line, int recursive) {
1120 char *argv[MAXNUMPARAMS];
1124 argc = split_line(line, true, ArrayLength(argv), argv);
1125 cmd = line[0] == ':';
1126 if ((argc > cmd) && (func = dict_find(irc_func_dict, argv[cmd], NULL))) {
1129 origin = argv[0] + 1;
1130 } else if (self->uplink) {
1131 origin = self->uplink->name;
1135 res = func(origin, argc-cmd, argv+cmd);
1140 log_module(MAIN_LOG, LOG_ERROR, "PARSE ERROR on line: %s", unsplit_string(argv, argc, NULL));
1141 } else if (!recursive) {
1143 for (i=0; i<dead_users.used; i++) {
1144 free_user(dead_users.list[i]);
1146 dead_users.used = 0;
1152 privmsg_user_helper(struct userNode *un, void *data)
1154 struct privmsg_desc *pd = data;
1155 struct service_message_info *info = dict_find(service_msginfo_dict, un->nick, 0);
1157 if (pd->is_notice) {
1158 if (info->on_notice) info->on_notice(pd->user, un, pd->text, pd->is_qualified);
1160 if (info->on_privmsg) info->on_privmsg(pd->user, un, pd->text, pd->is_qualified);
1166 reg_privmsg_func(struct userNode *user, privmsg_func_t handler) {
1167 struct service_message_info *info = dict_find(service_msginfo_dict, user->nick, NULL);
1169 info = calloc(1, sizeof(*info));
1170 dict_insert(service_msginfo_dict, user->nick, info);
1172 info->on_privmsg = handler;
1176 unreg_privmsg_func(struct userNode *user) {
1177 struct service_message_info *info;
1178 info = dict_find(service_msginfo_dict, user->nick, NULL);
1180 info->on_privmsg = NULL;
1181 if (info->on_notice == NULL) {
1182 dict_remove(service_msginfo_dict, user->nick);
1188 reg_notice_func(struct userNode *user, privmsg_func_t handler) {
1189 struct service_message_info *info = dict_find(service_msginfo_dict, user->nick, NULL);
1191 info = calloc(1, sizeof(*info));
1192 dict_insert(service_msginfo_dict, user->nick, info);
1194 info->on_notice = handler;
1198 unreg_notice_func(struct userNode *user) {
1199 struct service_message_info *info;
1200 info = dict_find(service_msginfo_dict, user->nick, NULL);
1202 info->on_notice = NULL;
1203 if (info->on_privmsg == NULL) {
1204 dict_remove(service_msginfo_dict, user->nick);
1210 reg_oper_func(oper_func_t handler)
1212 if (of_used == of_size) {
1215 of_list = realloc(of_list, of_size*sizeof(oper_func_t));
1218 of_list = malloc(of_size*sizeof(oper_func_t));
1221 of_list[of_used++] = handler;
1225 call_oper_funcs(struct userNode *user)
1228 if (IsLocal(user)) return;
1229 for (n=0; n<of_used; n++)
1235 void mod_usermode(struct userNode *user, const char *mode_change) {
1238 if (!user || !mode_change) return;
1240 #define do_user_mode(FLAG) do { if (add) user->modes |= FLAG; else user->modes &= ~FLAG; } while (0)
1241 switch (*mode_change++) {
1243 case '+': add = 1; break;
1244 case '-': add = 0; break;
1246 do_user_mode(FLAGS_OPER);
1248 userList_append(&curr_opers, user);
1249 call_oper_funcs(user);
1251 userList_remove(&curr_opers, user);
1254 case 'i': do_user_mode(FLAGS_INVISIBLE);
1255 if (add) invis_clients++; else invis_clients--;
1257 case 'w': do_user_mode(FLAGS_WALLOP); break;
1258 case 'd': do_user_mode(FLAGS_DEAF); break;
1259 case 'r': do_user_mode(FLAGS_REGNICK); break;
1260 case 'k': do_user_mode(FLAGS_SERVICE); break;
1261 case 'g': do_user_mode(FLAGS_GLOBAL); break;
1267 struct mod_chanmode *
1268 mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags, short base_oplevel)
1270 struct mod_chanmode *change;
1271 unsigned int ii, in_arg, ch_arg, add;
1275 if (!(change = mod_chanmode_alloc(argc)))
1278 for (ii = ch_arg = 0, in_arg = add = 1;
1279 (modes[0][ii] != '\0') && (modes[0][ii] != ' ');
1281 switch (modes[0][ii]) {
1288 #define do_chan_mode(FLAG) do { if (add) change->modes_set |= FLAG, change->modes_clear &= ~FLAG; else change->modes_clear |= FLAG, change->modes_set &= ~FLAG; } while(0)
1289 case 'R': do_chan_mode(MODE_REGONLY); break;
1290 case 'D': do_chan_mode(MODE_DELAYJOINS); break;
1291 case 'c': do_chan_mode(MODE_NOCOLORS); break;
1292 case 'i': do_chan_mode(MODE_INVITEONLY); break;
1293 case 'm': do_chan_mode(MODE_MODERATED); break;
1294 case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
1295 case 'p': do_chan_mode(MODE_PRIVATE); break;
1296 case 's': do_chan_mode(MODE_SECRET); break;
1297 case 't': do_chan_mode(MODE_TOPICLIMIT); break;
1299 if (!(flags & MCP_REGISTERED)) {
1300 do_chan_mode(MODE_REGISTERED);
1302 mod_chanmode_free(change);
1311 change->modes_set |= MODE_LIMIT;
1312 change->new_limit = atoi(modes[in_arg++]);
1314 change->modes_clear |= MODE_LIMIT;
1321 change->modes_set |= MODE_KEY;
1322 safestrncpy(change->new_key, modes[in_arg++], sizeof(change->new_key));
1324 change->modes_clear |= MODE_KEY;
1325 if (!(flags & MCP_KEY_FREE)) {
1333 if (!(flags & MCP_ALLOW_OVB))
1337 change->args[ch_arg].mode = MODE_BAN;
1339 change->args[ch_arg].mode |= MODE_REMOVE;
1340 change->args[ch_arg++].u.hostmask = modes[in_arg++];
1344 struct userNode *victim;
1345 if (!(flags & MCP_ALLOW_OVB))
1349 change->args[ch_arg].mode = (modes[0][ii] == 'o') ? MODE_CHANOP : MODE_VOICE;
1351 change->args[ch_arg].mode |= MODE_REMOVE;
1352 victim = GetUserH(modes[in_arg++]);
1355 if ((change->args[ch_arg].u.member = GetUserMode(channel, victim)))
1360 if (!(flags & MCP_FROM_SERVER))
1365 change->argc = ch_arg; /* in case any turned out to be ignored */
1366 if (change->modes_set & MODE_SECRET) {
1367 change->modes_set &= ~(MODE_PRIVATE);
1368 change->modes_clear |= MODE_PRIVATE;
1369 } else if (change->modes_set & MODE_PRIVATE) {
1370 change->modes_set &= ~(MODE_SECRET);
1371 change->modes_clear |= MODE_SECRET;
1375 mod_chanmode_free(change);
1380 struct chanmode_buffer {
1383 struct chanNode *channel;
1384 struct userNode *actor;
1385 unsigned int modes_used;
1386 unsigned int args_used;
1388 unsigned int is_add : 1;
1392 mod_chanmode_append(struct chanmode_buffer *buf, char ch, const char *arg)
1394 size_t arg_len = strlen(arg);
1395 if (buf->modes_used + buf->args_used + buf->chname_len + arg_len > 450) {
1396 memcpy(buf->modes + buf->modes_used, buf->args, buf->args_used);
1397 buf->modes[buf->modes_used + buf->args_used] = '\0';
1398 irc_mode(buf->actor, buf->channel, buf->modes);
1399 buf->modes[0] = buf->is_add ? '+' : '-';
1400 buf->modes_used = 1;
1403 buf->modes[buf->modes_used++] = ch;
1404 buf->args[buf->args_used++] = ' ';
1405 memcpy(buf->args + buf->args_used, arg, arg_len);
1406 buf->args_used += arg_len;
1410 mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
1412 struct chanmode_buffer chbuf;
1416 assert(change->argc <= change->alloc_argc);
1417 memset(&chbuf, 0, sizeof(chbuf));
1418 chbuf.channel = channel;
1420 chbuf.chname_len = strlen(channel->name);
1422 /* First remove modes */
1424 if (change->modes_clear) {
1425 chbuf.modes[chbuf.modes_used++] = '-';
1426 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_clear & MODE_##BIT) chbuf.modes[chbuf.modes_used++] = CHAR;
1427 DO_MODE_CHAR(PRIVATE, 'p');
1428 DO_MODE_CHAR(SECRET, 's');
1429 DO_MODE_CHAR(MODERATED, 'm');
1430 DO_MODE_CHAR(TOPICLIMIT, 't');
1431 DO_MODE_CHAR(INVITEONLY, 'i');
1432 DO_MODE_CHAR(NOPRIVMSGS, 'n');
1433 DO_MODE_CHAR(LIMIT, 'l');
1434 DO_MODE_CHAR(DELAYJOINS, 'D');
1435 DO_MODE_CHAR(REGONLY, 'R');
1436 DO_MODE_CHAR(NOCOLORS, 'c');
1437 DO_MODE_CHAR(REGISTERED, 'r');
1439 if (change->modes_clear & channel->modes & MODE_KEY)
1440 mod_chanmode_append(&chbuf, 'k', channel->key);
1442 for (arg = 0; arg < change->argc; ++arg) {
1443 if (!(change->args[arg].mode & MODE_REMOVE))
1445 switch (change->args[arg].mode & ~MODE_REMOVE) {
1447 mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
1450 if (change->args[arg].mode & MODE_CHANOP)
1451 mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->nick);
1452 if (change->args[arg].mode & MODE_VOICE)
1453 mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->nick);
1460 if (change->modes_set) {
1461 chbuf.modes[chbuf.modes_used++] = '+';
1462 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_set & MODE_##BIT) chbuf.modes[chbuf.modes_used++] = CHAR;
1463 DO_MODE_CHAR(PRIVATE, 'p');
1464 DO_MODE_CHAR(SECRET, 's');
1465 DO_MODE_CHAR(MODERATED, 'm');
1466 DO_MODE_CHAR(TOPICLIMIT, 't');
1467 DO_MODE_CHAR(INVITEONLY, 'i');
1468 DO_MODE_CHAR(NOPRIVMSGS, 'n');
1469 DO_MODE_CHAR(DELAYJOINS, 'D');
1470 DO_MODE_CHAR(REGONLY, 'R');
1471 DO_MODE_CHAR(NOCOLORS, 'c');
1472 DO_MODE_CHAR(REGISTERED, 'r');
1474 if (change->modes_set & MODE_KEY)
1475 mod_chanmode_append(&chbuf, 'k', change->new_key);
1476 if (change->modes_set & MODE_LIMIT)
1478 sprintf(int_buff, "%d", change->new_limit);
1479 mod_chanmode_append(&chbuf, 'l', int_buff);
1482 for (arg = 0; arg < change->argc; ++arg) {
1483 if (change->args[arg].mode & MODE_REMOVE)
1485 switch (change->args[arg].mode) {
1487 mod_chanmode_append(&chbuf, 'b', change->args[arg].u.hostmask);
1490 if (change->args[arg].mode & MODE_CHANOP)
1491 mod_chanmode_append(&chbuf, 'o', change->args[arg].u.member->user->nick);
1492 if (change->args[arg].mode & MODE_VOICE)
1493 mod_chanmode_append(&chbuf, 'v', change->args[arg].u.member->user->nick);
1498 /* Flush the buffer and apply changes locally */
1499 if (chbuf.modes_used > 0) {
1500 memcpy(chbuf.modes + chbuf.modes_used, chbuf.args, chbuf.args_used);
1501 chbuf.modes[chbuf.modes_used + chbuf.args_used] = '\0';
1502 irc_mode(chbuf.actor, chbuf.channel, chbuf.modes);
1504 mod_chanmode_apply(who, channel, change);
1508 mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
1510 unsigned int used = 0;
1511 assert(change->argc <= change->alloc_argc);
1512 if (change->modes_clear) {
1513 outbuff[used++] = '-';
1514 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_clear & MODE_##BIT) outbuff[used++] = CHAR
1515 DO_MODE_CHAR(PRIVATE, 'p');
1516 DO_MODE_CHAR(SECRET, 's');
1517 DO_MODE_CHAR(MODERATED, 'm');
1518 DO_MODE_CHAR(TOPICLIMIT, 't');
1519 DO_MODE_CHAR(INVITEONLY, 'i');
1520 DO_MODE_CHAR(NOPRIVMSGS, 'n');
1521 DO_MODE_CHAR(LIMIT, 'l');
1522 DO_MODE_CHAR(KEY, 'k');
1523 DO_MODE_CHAR(DELAYJOINS, 'D');
1524 DO_MODE_CHAR(REGONLY, 'R');
1525 DO_MODE_CHAR(NOCOLORS, 'c');
1526 DO_MODE_CHAR(REGISTERED, 'r');
1529 if (change->modes_set) {
1530 outbuff[used++] = '+';
1531 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_set & MODE_##BIT) outbuff[used++] = CHAR
1532 DO_MODE_CHAR(PRIVATE, 'p');
1533 DO_MODE_CHAR(SECRET, 's');
1534 DO_MODE_CHAR(MODERATED, 'm');
1535 DO_MODE_CHAR(TOPICLIMIT, 't');
1536 DO_MODE_CHAR(INVITEONLY, 'i');
1537 DO_MODE_CHAR(NOPRIVMSGS, 'n');
1538 DO_MODE_CHAR(DELAYJOINS, 'D');
1539 DO_MODE_CHAR(REGONLY, 'R');
1540 DO_MODE_CHAR(NOCOLORS, 'c');
1541 DO_MODE_CHAR(REGISTERED, 'r');
1543 switch (change->modes_set & (MODE_KEY|MODE_LIMIT)) {
1544 case MODE_KEY|MODE_LIMIT:
1545 used += sprintf(outbuff+used, "lk %d %s", change->new_limit, change->new_key);
1548 used += sprintf(outbuff+used, "k %s", change->new_key);
1551 used += sprintf(outbuff+used, "l %d", change->new_limit);