Initial import (again)
[srvx.git] / src / proto-bahamut.c
1 /* proto-bahamut.c - IRC protocol output
2  * Copyright 2000-2004 srvx Development Team
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.  Important limitations are
8  * listed in the COPYING file that accompanies this software.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, email srvx-maintainers@srvx.net.
17  */
18
19 #include "proto-common.c"
20
21 #define CAPAB_TS3       0x01
22 #define CAPAB_NOQUIT    0x02
23 #define CAPAB_SSJOIN    0x04
24 #define CAPAB_BURST     0x08
25 #define CAPAB_UNCONNECT 0x10
26 #define CAPAB_NICKIP    0x20
27 #define CAPAB_TSMODE    0x40
28 #define CAPAB_ZIP       0x80
29
30 struct service_message_info {
31     privmsg_func_t on_privmsg;
32     privmsg_func_t on_notice;
33 };
34
35 static dict_t service_msginfo_dict; /* holds service_message_info structs */
36 static int uplink_capab;
37 static void privmsg_user_helper(struct userNode *un, void *data);
38
39 void irc_svsmode(struct userNode *target, char *modes, unsigned long stamp);
40
41 struct server *
42 AddServer(struct server *uplink, const char *name, int hops, time_t boot, time_t link, UNUSED_ARG(const char *numeric), const char *description) {
43     struct server* sNode;
44
45     sNode = calloc(1, sizeof(*sNode));
46     sNode->uplink = uplink;
47     safestrncpy(sNode->name, name, sizeof(sNode->name));
48     sNode->hops = hops;
49     sNode->boot = boot;
50     sNode->link = link;
51     sNode->users = dict_new();
52     safestrncpy(sNode->description, description, sizeof(sNode->description));
53     serverList_init(&sNode->children);
54     if (sNode->uplink) {
55         /* uplink may be NULL if we're just building ourself */
56         serverList_append(&sNode->uplink->children, sNode);
57     }
58     dict_insert(servers, sNode->name, sNode);
59
60     if (hops && !self->burst) {
61         unsigned int n;
62         for (n=0; n<slf_used; n++) {
63             slf_list[n](sNode);
64         }
65     }
66
67     return sNode;
68 }
69
70 void
71 DelServer(struct server* serv, int announce, const char *message) {
72     unsigned int nn;
73     dict_iterator_t it, next;
74
75     if (!serv) return;
76     if (announce && (serv->uplink == self) && (serv != self->uplink)) {
77         irc_squit(serv, message, NULL);
78     }
79     for (nn=serv->children.used; nn>0;) {
80         if (serv->children.list[--nn] != self) {
81             DelServer(serv->children.list[nn], false, "uplink delinking");
82         }
83     }
84     for (it=dict_first(serv->users); it; it=next) {
85         next = iter_next(it);
86         DelUser(iter_data(it), NULL, false, "server delinking");
87     }
88     if (serv->uplink) serverList_remove(&serv->uplink->children, serv);
89     if (serv == self->uplink) self->uplink = NULL;
90     dict_remove(servers, serv->name);
91     serverList_clean(&serv->children);
92     dict_delete(serv->users);
93     free(serv);
94 }
95
96 int
97 is_valid_nick(const char *nick) {
98     /* IRC has some of The Most Fucked-Up ideas about character sets
99      * in the world.. */
100     if ((*nick < 'A') || (*nick >= '~')) return 0;
101     for (++nick; *nick; ++nick) {
102         if (!((*nick >= 'A') && (*nick < '~'))
103             && !isdigit(*nick)
104             && (*nick != '-')) {
105             return 0;
106         }
107     }
108     if (strlen(nick) > nicklen) return 0;
109     return 1;
110 }
111
112 struct userNode *
113 AddUser(struct server* uplink, const char *nick, const char *ident, const char *hostname, const char *modes, const char *userinfo, time_t timestamp, struct in_addr realip, const char *stamp) {
114     struct userNode *uNode, *oldUser;
115     unsigned int nn;
116
117     if (!uplink) {
118         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): server does not exist!", uplink, nick);
119         return NULL;
120     }
121
122     if (!is_valid_nick(nick)) {
123         log_module(MAIN_LOG, LOG_WARNING, "AddUser(%p, %s, ...): invalid nickname detected.", uplink, nick);
124         return NULL;
125     }
126
127     if ((oldUser = GetUserH(nick))) {
128         if (IsLocal(oldUser) && IsService(oldUser)) {
129             /* The service should collide the new user off. */
130             oldUser->timestamp = timestamp - 1;
131             irc_user(oldUser);
132         }
133         if (oldUser->timestamp > timestamp) {
134             /* "Old" user is really newer; remove them */
135             DelUser(oldUser, 0, 1, "Overruled by older nick");
136         } else {
137             /* User being added is too new */
138             return NULL;
139         }
140     }
141
142     uNode = calloc(1, sizeof(*uNode));
143     uNode->nick = strdup(nick);
144     safestrncpy(uNode->ident, ident, sizeof(uNode->ident));
145     safestrncpy(uNode->info, userinfo, sizeof(uNode->info));
146     safestrncpy(uNode->hostname, hostname, sizeof(uNode->hostname));
147     uNode->ip = realip;
148     uNode->timestamp = timestamp;
149     modeList_init(&uNode->channels);
150     uNode->uplink = uplink;
151     dict_insert(uplink->users, uNode->nick, uNode);
152     if (++uNode->uplink->clients > uNode->uplink->max_clients) {
153         uNode->uplink->max_clients = uNode->uplink->clients;
154     }
155
156     dict_insert(clients, uNode->nick, uNode);
157     if (dict_size(clients) > max_clients) {
158         max_clients = dict_size(clients);
159         max_clients_time = now;
160     }
161
162     mod_usermode(uNode, modes);
163     if (stamp) call_account_func(uNode, stamp);
164     if (IsLocal(uNode)) irc_user(uNode);
165     for (nn=0; nn<nuf_used; nn++) {
166         if (nuf_list[nn](uNode)) break;
167     }
168     return uNode;
169 }
170
171 struct userNode *
172 AddService(const char *nick, const char *desc) {
173     time_t timestamp = now;
174     struct userNode *old_user = GetUserH(nick);
175     struct in_addr ipaddr = { INADDR_LOOPBACK };
176     if (old_user) {
177         if (IsLocal(old_user))
178             return old_user;
179         timestamp = old_user->timestamp - 1;
180     }
181     return AddUser(self, nick, nick, self->name, "+oikr", desc, timestamp, ipaddr, 0);
182 }
183
184 struct userNode *
185 AddClone(const char *nick, const char *ident, const char *hostname, const char *desc) {
186     time_t timestamp = now;
187     struct userNode *old_user = GetUserH(nick);
188     struct in_addr ipaddr = { INADDR_LOOPBACK };
189     if (old_user) {
190         if (IsLocal(old_user))
191             return old_user;
192         timestamp = old_user->timestamp - 1;
193     }
194     return AddUser(self, nick, ident, hostname, "+ir", desc, timestamp, ipaddr, 0);
195 }
196
197 void
198 free_user(struct userNode *user)
199 {
200     free(user->nick);
201     free(user);
202 }
203
204 void
205 DelUser(struct userNode* user, struct userNode *killer, int announce, const char *why) {
206     unsigned int nn;
207
208     for (nn=user->channels.used; nn>0;) {
209         DelChannelUser(user, user->channels.list[--nn]->channel, false, 0);
210     }
211     for (nn=duf_used; nn>0; ) duf_list[--nn](user, killer, why);
212     user->uplink->clients--;
213     dict_remove(user->uplink->users, user->nick);
214     if (IsOper(user)) userList_remove(&curr_opers, user);
215     if (IsInvisible(user)) invis_clients--;
216     if (user == dict_find(clients, user->nick, NULL)) dict_remove(clients, user->nick);
217     if (announce) {
218         if (IsLocal(user)) {
219             irc_quit(user, why);
220         } else {
221             irc_kill(killer, user, why);
222         }
223     }
224     modeList_clean(&user->channels);
225     user->dead = 1;
226     if (dead_users.size) {
227         userList_append(&dead_users, user);
228     } else {
229         free_user(user);
230     }
231 }
232
233 void
234 irc_server(struct server *srv) {
235     if (srv == self) {
236         putsock("SERVER %s %d :%s", srv->name, srv->hops, srv->description);
237     } else {
238         putsock(":%s SERVER %s %d :%s", self->name, srv->name, srv->hops, srv->description);
239     }
240 }
241
242 void
243 irc_user(struct userNode *user) {
244     char modes[32];
245     int modelen = 0;
246     if (IsOper(user)) modes[modelen++] = 'o';
247     if (IsInvisible(user)) modes[modelen++] = 'i';
248     if (IsWallOp(user)) modes[modelen++] = 'w';
249     if (IsService(user)) modes[modelen++] = 'k';
250     if (IsServNotice(user)) modes[modelen++] = 's';
251     if (IsDeaf(user)) modes[modelen++] = 'd';
252     if (IsReggedNick(user)) modes[modelen++] = 'r';
253     if (IsGlobal(user)) modes[modelen++] = 'g';
254     modes[modelen] = 0;
255     putsock("NICK %s %d "FMT_TIME_T" +%s %s %s %s %d %u :%s",
256             user->nick, user->uplink->hops+2, user->timestamp, modes,
257             user->ident, user->hostname, user->uplink->name, 0, ntohl(user->ip.s_addr), user->info);
258 }
259
260 void
261 irc_account(struct userNode *user, const char *stamp)
262 {
263     if (IsReggedNick(user)) {
264         irc_svsmode(user, "+rd", base64toint(stamp, IDLEN));
265     } else {
266         irc_svsmode(user, "+d", base64toint(stamp, IDLEN));
267     }
268 }
269
270 void
271 irc_regnick(struct userNode *user)
272 {
273     if (IsReggedNick(user)) {
274         irc_svsmode(user, "+r", 0);
275     } else {
276         irc_svsmode(user, "-r", 0);
277     }
278 }
279
280 void
281 irc_nick(struct userNode *user, const char *old_nick) {
282     if (user->uplink == self) {
283         /* update entries in PRIVMSG/NOTICE handlers (if they exist) */
284         struct service_message_info *smi = dict_find(service_msginfo_dict, user->nick, NULL);
285         if (smi) {
286             dict_remove2(service_msginfo_dict, old_nick, 1);
287             dict_insert(service_msginfo_dict, user->nick, smi);
288         }
289     }
290     putsock(":%s NICK %s :"FMT_TIME_T, old_nick, user->nick, user->timestamp);
291 }
292
293 void
294 irc_pass(const char *passwd) {
295     putsock("PASS %s :TS", passwd);
296 }
297
298 void
299 irc_capab() {
300     putsock("CAPAB TS3 NOQUIT SSJOIN BURST UNCONNECT NICKIP TSMODE");
301 }
302
303 void
304 irc_svinfo() {
305     putsock("SVINFO 3 3 0 :"FMT_TIME_T, now);
306 }
307
308 void
309 irc_burst() {
310     putsock("BURST");
311 }
312
313 void
314 irc_introduce(const char *passwd) {
315     extern time_t burst_begin;
316
317     irc_pass(passwd);
318     irc_capab();
319     irc_server(self);
320     irc_svinfo();
321     burst_length = 0;
322     burst_begin = now;
323     timeq_add(now + ping_freq, timed_send_ping, 0);
324 }
325
326 void
327 irc_ping(const char *something) {
328     putsock("PING :%s", something);
329 }
330
331 void
332 irc_pong(const char *who, const char *data) {
333     putsock(":%s PONG %s :%s", self->name, who, data);
334 }
335
336 void
337 irc_quit(struct userNode *user, const char *message) {
338     putsock(":%s QUIT :%s", user->nick, message);
339 }
340
341 void
342 irc_squit(struct server *srv, const char *message, const char *service_message) {
343     if (!service_message) service_message = message;
344     /* If we're leaving the network, QUIT all our clients. */
345     if ((srv == self) && (cManager.uplink->state == CONNECTED)) {
346         dict_iterator_t it;
347         for (it = dict_first(srv->users); it; it = iter_next(it)) {
348             irc_quit(iter_data(it), service_message);
349         }
350     }
351     putsock(":%s SQUIT %s 0 :%s", self->name, srv->name, message);
352     if (srv == self) {
353         /* Reconnect to the currently selected server. */
354         cManager.uplink->tries = 0;
355         log_module(MAIN_LOG, LOG_INFO, "Squitting from uplink: %s", message);
356         close_socket();
357     }
358 }
359
360 void
361 irc_privmsg(struct userNode *from, const char *to, const char *message) {
362     putsock(":%s PRIVMSG %s :%s", from->nick, to, message);
363 }
364
365 void
366 irc_notice(struct userNode *from, const char *to, const char *message) {
367     putsock(":%s NOTICE %s :%s", from->nick, to, message);
368 }
369
370 void
371 irc_wallchops(UNUSED_ARG(struct userNode *from), UNUSED_ARG(const char *to), UNUSED_ARG(const char *message)) {
372 }
373
374 void
375 irc_join(struct userNode *who, struct chanNode *what) {
376     if (what->members.used == 1) {
377         putsock(":%s SJOIN "FMT_TIME_T" %s + :@%s", self->name, what->timestamp, what->name, who->nick);
378     } else {
379         putsock(":%s SJOIN "FMT_TIME_T" %s", who->nick, what->timestamp, what->name);
380     }
381 }
382
383 void
384 irc_invite(struct userNode *from, struct userNode *who, struct chanNode *to) {
385     putsock(":%s INVITE %s %s", from->nick, who->nick, to->name);
386 }
387
388 void
389 irc_mode(struct userNode *who, struct chanNode *target, const char *modes) {
390     putsock(":%s MODE %s "FMT_TIME_T" %s", who->nick, target->name, target->timestamp, modes);
391 }
392
393 void
394 irc_svsmode(struct userNode *target, char *modes, unsigned long stamp) {
395     extern struct userNode *nickserv;
396     if (stamp) {
397         putsock(":%s SVSMODE %s "FMT_TIME_T" %s %lu", nickserv->nick, target->nick, target->timestamp, modes, stamp);
398     } else {
399         putsock(":%s SVSMODE %s "FMT_TIME_T" %s", nickserv->nick, target->nick, target->timestamp, modes);
400     }
401 }
402
403 void
404 irc_kick(struct userNode *who, struct userNode *target, struct chanNode *from, const char *msg) {
405     putsock(":%s KICK %s %s :%s", who->nick, from->name, target->nick, msg);
406     ChannelUserKicked(who, target, from);
407 }
408
409 void
410 irc_part(struct userNode *who, struct chanNode *what, const char *reason) {
411     if (reason) {
412         putsock(":%s PART %s :%s", who->nick, what->name, reason);
413     } else {
414         putsock(":%s PART %s", who->nick, what->name);
415     }
416 }
417
418 void
419 irc_topic(struct userNode *who, struct chanNode *what, const char *topic) {
420     putsock(":%s TOPIC %s :%s", who->nick, what->name, topic);
421 }
422
423 void
424 irc_fetchtopic(struct userNode *from, const char *to) {
425     if (!from || !to) return;
426     putsock(":%s TOPIC %s", from->nick, to);
427 }
428
429 void
430 irc_gline(struct server *srv, struct gline *gline) {
431     char host[HOSTLEN+1], ident[USERLEN+1], *sep;
432     unsigned int len;
433     if (srv) {
434         log_module(MAIN_LOG, LOG_WARNING, "%s tried to send a targeted G-line for %s (not supported by protocol!)", gline->issuer, gline->target);
435         return;
436     }
437     if (!(sep = strchr(gline->target, '@'))) {
438         log_module(MAIN_LOG, LOG_ERROR, "%s tried to add G-line with bad mask %s", gline->issuer, gline->target);
439         return;
440     }
441     len = sep - gline->target + 1;
442     if (len > ArrayLength(ident)) len = ArrayLength(ident);
443     safestrncpy(ident, gline->target, len);
444     safestrncpy(host, sep+1, ArrayLength(host));
445     putsock(":%s AKILL %s %s "FMT_TIME_T" %s "FMT_TIME_T" :%s", self->name, host, ident, gline->expires-gline->issued, gline->issuer, gline->issued, gline->reason);
446 }
447
448 void
449 irc_settime(UNUSED_ARG(const char *srv_name_mask), UNUSED_ARG(time_t new_time))
450 {
451     /* Bahamut has nothing like this, so ignore it. */
452 }
453
454 void
455 irc_ungline(const char *mask) {
456     char host[HOSTLEN+1], ident[USERLEN+1], *sep;
457     unsigned int len;
458     if (!(sep = strchr(mask, '@'))) {
459         log_module(MAIN_LOG, LOG_ERROR, "Tried to remove G-line with bad mask %s", mask);
460         return;
461     }
462     len = sep - mask + 1;
463     if (len > ArrayLength(ident)) len = ArrayLength(ident);
464     safestrncpy(ident, mask, len);
465     safestrncpy(host, sep+1, ArrayLength(host));
466     putsock(":%s RAKILL %s %s", self->name, host, ident);
467 }
468
469 void
470 irc_error(const char *to, const char *message) {
471     if (to) {
472         putsock("%s ERROR :%s", to, message);
473     } else {
474         putsock(":%s ERROR :%s", self->name, message);
475     }
476 }
477
478 void
479 irc_kill(struct userNode *from, struct userNode *target, const char *message) {
480     if (from) {
481         putsock(":%s KILL %s :%s!%s (%s)", from->nick, target->nick, self->name, from->nick, message);
482     } else {
483         putsock(":%s KILL %s :%s (%s)", self->name, target->nick, self->name, message);
484     }
485 }
486
487 void
488 irc_raw(const char *what) {
489     putsock("%s", what);
490 }
491
492 void
493 irc_stats(struct userNode *from, struct server *target, char type) {
494     putsock(":%s STATS %c :%s", from->nick, type, target->name);
495 }
496
497 void
498 irc_svsnick(struct userNode *from, struct userNode *target, const char *newnick)
499 {
500     putsock(":%s SVSNICK %s %s :"FMT_TIME_T, from->nick, target->nick, newnick, now);
501 }
502
503 void
504 irc_numeric(struct userNode *user, unsigned int num, const char *format, ...) {
505     va_list arg_list;
506     char buffer[MAXLEN];
507     va_start(arg_list, format);
508     vsnprintf(buffer, MAXLEN-2, format, arg_list);
509     buffer[MAXLEN-1] = 0;
510     putsock(":%s %03d %s %s", self->name, num, user->nick, buffer);
511 }
512
513 static void
514 parse_foreach(char *target_list, foreach_chanfunc cf, foreach_nonchan nc, foreach_userfunc uf, foreach_nonuser nu, void *data) {
515     char *j, old;
516     do {
517         j = target_list;
518         while (*j != 0 && *j != ',') j++;
519         old = *j;
520         *j = 0;
521         if (IsChannelName(target_list)) {
522             struct chanNode *chan = GetChannel(target_list);
523             if (chan) {
524                 if (cf) cf(chan, data);
525             } else {
526                 if (nc) nc(target_list, data);
527             }
528         } else {
529             struct userNode *user;
530             struct privmsg_desc *pd = data;
531
532             pd->is_qualified = 0;
533             if (*target_list == '@') {
534                 user = NULL;
535             } else if (strchr(target_list, '@')) {
536                 struct server *server;
537
538                 pd->is_qualified = 1;
539                 user = GetUserH(strtok(target_list, "@"));
540                 server = GetServerH(strtok(NULL, "@"));
541
542                 if (user && (user->uplink != server)) {
543                     /* Don't attempt to index into any arrays
544                        using a user's numeric on another server. */
545                     user = NULL;
546                 }
547             } else {
548                 user = GetUserH(target_list);
549             }
550
551             if (user) {
552                 if (uf) uf(user, data);
553             } else {
554                 if (nu) nu(target_list, data);
555             }
556         }
557         target_list = j+1;
558     } while (old == ',');
559 }
560
561 static CMD_FUNC(cmd_notice) {
562     struct privmsg_desc pd;
563     if ((argc < 3) || !origin) return 0;
564     if (!(pd.user = GetUserH(origin))) return 1;
565     if (IsGagged(pd.user) && !IsOper(pd.user)) return 1;
566     pd.is_notice = 1;
567     pd.text = argv[2];
568     parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
569     return 1;
570 }
571
572 static CMD_FUNC(cmd_privmsg) {
573     struct privmsg_desc pd;
574     if ((argc < 3) || !origin) return 0;
575     if (!(pd.user = GetUserH(origin))) return 1;
576     if (IsGagged(pd.user) && !IsOper(pd.user)) return 1;
577     pd.is_notice = 0;
578     pd.text = argv[2];
579     parse_foreach(argv[1], privmsg_chan_helper, NULL, privmsg_user_helper, privmsg_invalid, &pd);
580     return 1;
581 }
582
583 static CMD_FUNC(cmd_capab) {
584     static const struct {
585         const char *name;
586         unsigned int mask;
587     } capabs[] = {
588         { "TS3", CAPAB_TS3 },
589         { "NOQUIT", CAPAB_NOQUIT },
590         { "SSJOIN", CAPAB_SSJOIN },
591         { "BURST", CAPAB_BURST },
592         { "UNCONNECT", CAPAB_UNCONNECT },
593         { "NICKIP", CAPAB_NICKIP },
594         { "TSMODE", CAPAB_TSMODE },
595         { "ZIP", CAPAB_ZIP },
596         { NULL, 0 }
597     };
598     unsigned int nn, mm;
599
600     uplink_capab = 0;
601     for(nn=1; nn<argc; nn++) {
602         for (mm=0; capabs[mm].name && irccasecmp(capabs[mm].name, argv[nn]); mm++) ;
603         if (capabs[mm].name) {
604             uplink_capab |= capabs[mm].mask;
605         } else {
606             log_module(MAIN_LOG, LOG_INFO, "Saw unrecognized/unhandled capability %s.  Please notify srvx developers so they can add it.", argv[nn]);
607         }
608     }
609     return 1;
610 }
611
612 static void burst_channel(struct chanNode *chan) {
613     char line[510];
614     int pos, base_len, len, queued;
615     unsigned int nn;
616
617     if (!chan->members.used) return;
618     /* send list of users in the channel.. */
619     base_len = sprintf(line, ":%s SJOIN "FMT_TIME_T" %s ", self->name, chan->timestamp, chan->name);
620     len = irc_make_chanmode(chan, line+base_len);
621     pos = base_len + len;
622     line[pos++] = ' ';
623     line[pos++] = ':';
624     for (nn=0; nn<chan->members.used; nn++) {
625         struct modeNode *mn = chan->members.list[nn];
626         len = strlen(mn->user->nick);
627         if (pos + len > 500) {
628             line[pos-1] = 0;
629             putsock("%s", line);
630             pos = base_len;
631             line[pos++] = '0';
632             line[pos++] = ' ';
633             line[pos++] = ':';
634         }
635         if (mn->modes & MODE_CHANOP) line[pos++] = '@';
636         if (mn->modes & MODE_VOICE) line[pos++] = '+';
637         memcpy(line+pos, mn->user->nick, len);
638         pos = pos + len;
639         line[pos++] = ' ';
640     }
641     /* print the last line */
642     line[pos] = 0;
643     putsock("%s", line);
644     /* now send the bans.. */
645     base_len = sprintf(line, ":%s MODE "FMT_TIME_T" %s +", self->name, chan->timestamp, chan->name);
646     pos = sizeof(line)-1;
647     line[pos] = 0;
648     for (nn=queued=0; nn<chan->banlist.used; nn++) {
649         struct banNode *bn = chan->banlist.list[nn];
650         len = strlen(bn->ban);
651         if (pos < base_len+queued+len+4) {
652             while (queued) {
653                 line[--pos] = 'b';
654                 queued--;
655             }
656             putsock("%s%s", line, line+pos);
657             pos = sizeof(line)-1;
658         }
659         pos -= len;
660         memcpy(line+pos, bn->ban, len);
661         line[--pos] = ' ';
662         queued++;
663     }
664     if (queued) {
665         while (queued) {
666             line[--pos] = 'b';
667             queued--;
668         }
669         putsock("%s%s", line, line+pos);
670     }
671 }
672
673 static void send_burst() {
674     dict_iterator_t it;
675     for (it = dict_first(servers); it; it = iter_next(it)) {
676         struct server *serv = iter_data(it);
677         if ((serv != self) && (serv != self->uplink)) irc_server(serv);
678     }
679     putsock("BURST");
680     for (it = dict_first(clients); it; it = iter_next(it)) {
681         irc_user(iter_data(it));
682     }
683     for (it = dict_first(channels); it; it = iter_next(it)) {
684         burst_channel(iter_data(it));
685     }
686     /* Uplink will do AWAY and TOPIC bursts before sending BURST 0, but we don't */
687     putsock("BURST 0");
688 }
689
690 static CMD_FUNC(cmd_server) {
691     if (argc < 4) return 0;
692     if (origin) {
693         AddServer(GetServerH(origin), argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
694     } else {
695         self->uplink = AddServer(self, argv[1], atoi(argv[2]), 0, now, 0, argv[3]);
696         send_burst();
697     }
698     return 1;
699 }
700
701 static CMD_FUNC(cmd_svinfo) {
702     if (argc < 5) return 0;
703     if ((atoi(argv[1]) < 3) || (atoi(argv[2]) > 3)) return 0;
704     /* TODO: something with the timestamp we get from the other guy */
705     return 1;
706 }
707
708 static CMD_FUNC(cmd_ping)
709 {
710     irc_pong(self->name, argc > 1 ? argv[1] : origin);
711     timeq_del(0, timed_send_ping, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
712     timeq_del(0, timed_ping_timeout, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
713     timeq_add(now + ping_freq, timed_send_ping, 0);
714     received_ping();
715     return 1;
716 }
717
718 static CMD_FUNC(cmd_burst) {
719     struct server *sender = GetServerH(origin);
720     if (!sender) return 0;
721     if (argc == 1) return 1;
722     if (sender == self->uplink) {
723         cManager.uplink->state = CONNECTED;
724     }
725     sender->self_burst = 0;
726     recalc_bursts(sender);
727     return 1;
728 }
729
730 static CMD_FUNC(cmd_nick) {
731     struct userNode *un;
732     if ((un = GetUserH(origin))) {
733         /* nick change */
734         if (argc < 2) return 0;
735         NickChange(un, argv[1], 1);
736     } else {
737         /* new nick from a server */
738         char id[8];
739         unsigned long stamp;
740         struct in_addr ip;
741
742         if (argc < 10) return 0;
743         stamp = strtoul(argv[8], NULL, 0);
744         if (stamp) inttobase64(id, stamp, IDLEN);
745         ip.s_addr = (argc > 10) ? atoi(argv[9]) : 0;
746         un = AddUser(GetServerH(argv[7]), argv[1], argv[5], argv[6], argv[4], argv[argc-1], atoi(argv[3]), ip, (stamp ? id : 0));
747     }
748     return 1;
749 }
750
751 static CMD_FUNC(cmd_sjoin) {
752     struct chanNode *cNode;
753     struct userNode *uNode;
754     struct modeNode *mNode;
755     unsigned int next = 4, last;
756     char *nick, *nickend;
757
758     if ((argc == 3) && (uNode = GetUserH(origin))) {
759         /* normal JOIN */
760         if (!(cNode = GetChannel(argv[2]))) {
761             log_module(MAIN_LOG, LOG_ERROR, "Unable to find SJOIN target %s", argv[2]);
762             return 0;
763         }
764         AddChannelUser(uNode, cNode);
765         return 1;
766     }
767     if (argc < 5) return 0;
768     if (argv[3][0] == '+') {
769         char modes[MAXLEN], *pos;
770         int n_modes;
771         for (pos = argv[3], n_modes = 1; *pos; pos++) {
772             if ((*pos == 'k') || (*pos == 'l')) n_modes++;
773         }
774         unsplit_string(argv+3, n_modes, modes);
775         cNode = AddChannel(argv[2], atoi(argv[1]), modes, NULL);
776     } else if (argv[3][0] == '0') {
777         cNode = GetChannel(argv[2]);
778     } else {
779         log_module(MAIN_LOG, LOG_ERROR, "Unsure how to handle SJOIN when arg 3 is %s", argv[3]);
780         return 0;
781     }
782
783     /* argv[next] is now the space-delimited, @+-prefixed list of
784      * nicks in the channel.  Split it and add the users. */
785     for (last = 0, nick = argv[next]; !last; nick = nickend + 1) {
786         int mode = 0;
787         for (nickend = nick; *nickend && (*nickend != ' '); nickend++) ;
788         if (!*nickend) last = 1;
789         *nickend = 0;
790         if (*nick == '@') { mode |= MODE_CHANOP; nick++; }
791         if (*nick == '+') { mode |= MODE_VOICE; nick++; }
792         if ((uNode = GetUserH(nick)) && (mNode = AddChannelUser(uNode, cNode))) {
793             mNode->modes = mode;
794         }
795     }
796     return 1;
797 }
798
799 static CMD_FUNC(cmd_mode) {
800     struct userNode *un;
801
802     if (argc < 2) {
803         log_module(MAIN_LOG, LOG_ERROR, "Illegal MODE from %s (no arguments).", origin);
804         return 0;
805     } else if (IsChannelName(argv[1])) {
806         struct chanNode *cn;
807         struct modeNode *mn;
808
809         if (!(cn = GetChannel(argv[1]))) {
810             log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s whose mode is changing", argv[1]);
811             return 0;
812         }
813
814         if ((un = GetUserH(origin))) {
815             /* Update idle time for the user */
816             if ((mn = GetUserMode(cn, un)))
817                 mn->idle_since = now;
818         } else {
819             /* Must be a server in burst or something.  Make sure we're using the right timestamp. */
820             cn->timestamp = atoi(argv[2]);
821         }
822
823         return mod_chanmode(un, cn, argv+3, argc-3, MCP_ALLOW_OVB|MCP_FROM_SERVER|MC_ANNOUNCE);
824     } else if ((un = GetUserH(argv[1]))) {
825         mod_usermode(un, argv[2]);
826         return 1;
827     } else {
828         log_module(MAIN_LOG, LOG_ERROR, "Not sure what MODE %s is affecting (not a channel name and no such user)", argv[1]);
829         return 0;
830     }
831 }
832
833 static CMD_FUNC(cmd_topic) {
834     struct chanNode *cn;
835     if (argc < 5) return 0;
836     if (!(cn = GetChannel(argv[1]))) {
837         log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s whose topic is being set", argv[1]);
838         return 0;
839     }
840     if (irccasecmp(origin, argv[2])) {
841         /* coming from a topic burst; the origin is a server */
842         safestrncpy(cn->topic, argv[4], sizeof(cn->topic));
843         safestrncpy(cn->topic_nick, argv[2], sizeof(cn->topic_nick));
844         cn->topic_time = atoi(argv[3]);
845     } else {
846         SetChannelTopic(cn, GetUserH(argv[2]), argv[4], 0);
847     }
848     return 1;
849 }
850
851 static CMD_FUNC(cmd_part) {
852     if (argc < 2) return 0;
853     parse_foreach(argv[1], part_helper, NULL, NULL, NULL, GetUserH(origin));
854     return 1;
855 }
856
857 static CMD_FUNC(cmd_away) {
858     struct userNode *un;
859
860     if (!(un = GetUserH(origin))) {
861         log_module(MAIN_LOG, LOG_ERROR, "Unable to find user %s sending AWAY", origin);
862         return 0;
863     }
864     if (argc > 1) {
865         un->modes |= FLAGS_AWAY;
866     } else {
867         un->modes &= ~FLAGS_AWAY;
868     }
869     return 1;
870 }
871
872 static CMD_FUNC(cmd_kick) {
873     if (argc < 3) return 0;
874     ChannelUserKicked(GetUserH(origin), GetUserH(argv[2]), GetChannel(argv[1]));
875     return 1;
876 }
877
878 static CMD_FUNC(cmd_kill) {
879     struct userNode *user;
880     if (argc < 3) return 0;
881     if (!(user = GetUserH(argv[1]))) {
882         log_module(MAIN_LOG, LOG_ERROR, "Unable to find kill victim %s", argv[1]);
883         return 0;
884     }
885     if (IsLocal(user) && IsService(user)) {
886         ReintroduceUser(user);
887     } else {
888         DelUser(user, GetUserH(origin), false, ((argc >= 3) ? argv[2] : NULL));
889     }
890     return 1;
891 }
892
893 static CMD_FUNC(cmd_pong)
894 {
895     if (argc < 3) return 0;
896     if (!strcmp(argv[2], self->name)) {
897         timeq_del(0, timed_send_ping, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
898         timeq_del(0, timed_ping_timeout, 0, TIMEQ_IGNORE_WHEN|TIMEQ_IGNORE_DATA);
899         timeq_add(now + ping_freq, timed_send_ping, 0);
900         received_ping();
901     }
902     return 1;
903 }
904
905 static CMD_FUNC(cmd_num_topic)
906 {
907     static struct chanNode *cn;
908
909     if (!argv[0])
910         return 0; /* huh? */
911     if (argv[2]) {
912         cn = GetChannel(argv[2]);
913         if (!cn) {
914             log_module(MAIN_LOG, LOG_ERROR, "Unable to find channel %s in topic reply", argv[2]);
915             return 0;
916         }
917     } else
918         return 0;
919
920     switch (atoi(argv[0])) {
921     case 331:
922         cn->topic_time = 0;
923         break;  /* no topic */
924     case 332:
925         if (argc < 4)
926             return 0;
927         safestrncpy(cn->topic, unsplit_string(argv+3, argc-3, NULL), sizeof(cn->topic));
928         break;
929     case 333:
930         if (argc < 5)
931             return 0;
932         safestrncpy(cn->topic_nick, argv[3], sizeof(cn->topic_nick));
933         cn->topic_time = atoi(argv[4]);
934         break;
935     default:
936         return 0; /* should never happen */
937     }
938     return 1;
939 }
940
941 static CMD_FUNC(cmd_quit)
942 {
943     struct userNode *user;
944     if (argc < 2) return 0;
945     /* Sometimes we get a KILL then a QUIT or the like, so we don't want to
946      * call DelUser unless we have the user in our grasp. */
947     if ((user = GetUserH(origin))) {
948         DelUser(user, NULL, false, argv[1]);
949     }
950     return 1;
951 }
952
953 static CMD_FUNC(cmd_squit)
954 {
955     struct server *server;
956     if (argc < 3)
957         return 0;
958     if (!(server = GetServerH(argv[1])))
959         return 0;
960     if (server == self->uplink) {
961         /* Force a reconnect to the currently selected server. */
962         cManager.uplink->tries = 0;
963         log_module(MAIN_LOG, LOG_INFO, "Squitting from uplink: %s", argv[3]);
964         close_socket();
965         return 1;
966     }
967
968     DelServer(server, 0, argv[3]);
969     return 1;
970 }
971
972 static CMD_FUNC(cmd_svsnick)
973 {
974     struct userNode *target, *dest;
975     if (argc < 4) return 0;
976     if (!(target = GetUserH(argv[1]))) return 0;
977     if (!IsLocal(target)) return 0;
978     if ((dest = GetUserH(argv[2]))) return 0; /* Note: Bahamut will /KILL instead. */
979     NickChange(target, argv[2], 0);
980     return 1;
981 }
982
983 static oper_func_t *of_list;
984 static unsigned int of_size = 0, of_used = 0;
985
986 void parse_cleanup(void) {
987     unsigned int nn;
988     if (of_list) free(of_list);
989     dict_delete(irc_func_dict);
990     dict_delete(service_msginfo_dict);
991     free(mcf_list);
992     for (nn=0; nn<dead_users.used; nn++) free_user(dead_users.list[nn]);
993     userList_clean(&dead_users);
994 }
995
996 void init_parse(void) {
997     const char *str, *desc;
998
999     str = conf_get_data("server/ping_freq", RECDB_QSTRING);
1000     ping_freq = str ? ParseInterval(str) : 120;
1001     str = conf_get_data("server/ping_timeout", RECDB_QSTRING);
1002     ping_timeout = str ? ParseInterval(str) : 30;
1003     str = conf_get_data("server/hostname", RECDB_QSTRING);
1004     desc = conf_get_data("server/description", RECDB_QSTRING);
1005     if (!str || !desc) {
1006         log_module(MAIN_LOG, LOG_ERROR, "No server/hostname entry in config file.");
1007         exit(1);
1008     }
1009     self = AddServer(NULL, str, 0, boot_time, now, NULL, desc);
1010
1011     str = conf_get_data("server/ping_freq", RECDB_QSTRING);
1012     ping_freq = str ? ParseInterval(str) : 120;
1013     str = conf_get_data("server/ping_timeout", RECDB_QSTRING);
1014     ping_timeout = str ? ParseInterval(str) : 30;
1015
1016     service_msginfo_dict = dict_new();
1017     dict_set_free_data(service_msginfo_dict, free);
1018     irc_func_dict = dict_new();
1019     dict_insert(irc_func_dict, "ADMIN", cmd_admin);
1020     dict_insert(irc_func_dict, "AWAY", cmd_away);
1021     dict_insert(irc_func_dict, "BURST", cmd_burst);
1022     dict_insert(irc_func_dict, "CAPAB", cmd_capab);
1023     dict_insert(irc_func_dict, "ERROR", cmd_error);
1024     dict_insert(irc_func_dict, "GNOTICE", cmd_dummy);
1025     dict_insert(irc_func_dict, "INVITE", cmd_dummy);
1026     dict_insert(irc_func_dict, "KICK", cmd_kick);
1027     dict_insert(irc_func_dict, "KILL", cmd_kill);
1028     dict_insert(irc_func_dict, "LUSERSLOCK", cmd_dummy);
1029     dict_insert(irc_func_dict, "MODE", cmd_mode);
1030     dict_insert(irc_func_dict, "NICK", cmd_nick);
1031     dict_insert(irc_func_dict, "NOTICE", cmd_notice);
1032     dict_insert(irc_func_dict, "PART", cmd_part);
1033     dict_insert(irc_func_dict, "PASS", cmd_pass);
1034     dict_insert(irc_func_dict, "PING", cmd_ping);
1035     dict_insert(irc_func_dict, "PONG", cmd_pong);
1036     dict_insert(irc_func_dict, "PRIVMSG", cmd_privmsg);
1037     dict_insert(irc_func_dict, "QUIT", cmd_quit);
1038     dict_insert(irc_func_dict, "SERVER", cmd_server);
1039     dict_insert(irc_func_dict, "SJOIN", cmd_sjoin);
1040     dict_insert(irc_func_dict, "SQUIT", cmd_squit);
1041     dict_insert(irc_func_dict, "STATS", cmd_stats);
1042     dict_insert(irc_func_dict, "SVSNICK", cmd_svsnick);
1043     dict_insert(irc_func_dict, "SVINFO", cmd_svinfo);
1044     dict_insert(irc_func_dict, "TOPIC", cmd_topic);
1045     dict_insert(irc_func_dict, "VERSION", cmd_version);
1046     dict_insert(irc_func_dict, "WHOIS", cmd_whois);
1047     dict_insert(irc_func_dict, "331", cmd_num_topic);
1048     dict_insert(irc_func_dict, "332", cmd_num_topic);
1049     dict_insert(irc_func_dict, "333", cmd_num_topic);
1050     dict_insert(irc_func_dict, "413", cmd_num_topic);
1051
1052     userList_init(&dead_users);
1053     reg_exit_func(parse_cleanup);
1054 }
1055
1056 int parse_line(char *line, int recursive) {
1057     char *argv[MAXNUMPARAMS];
1058     int argc, cmd, res;
1059     cmd_func_t *func;
1060
1061     argc = split_line(line, true, ArrayLength(argv), argv);
1062     cmd = line[0] == ':';
1063     if ((argc > cmd) && (func = dict_find(irc_func_dict, argv[cmd], NULL))) {
1064         char *origin;
1065         if (cmd) {
1066             origin = argv[0] + 1;
1067         } else if (self->uplink) {
1068             origin = self->uplink->name;
1069         } else {
1070             origin = NULL;
1071         }
1072         res = func(origin, argc-cmd, argv+cmd);
1073     } else {
1074         res = 0;
1075     }
1076     if (!res) {
1077         log_module(MAIN_LOG, LOG_ERROR, "PARSE ERROR on line: %s", unsplit_string(argv, argc, NULL));
1078     } else if (!recursive) {
1079         unsigned int i;
1080         for (i=0; i<dead_users.used; i++) {
1081             free_user(dead_users.list[i]);
1082         }
1083         dead_users.used = 0;
1084     }
1085     return res;
1086 }
1087
1088 static void
1089 privmsg_user_helper(struct userNode *un, void *data)
1090 {
1091     struct privmsg_desc *pd = data;
1092     struct service_message_info *info = dict_find(service_msginfo_dict, un->nick, 0);
1093     if (info) {
1094         if (pd->is_notice) {
1095             if (info->on_notice) info->on_notice(pd->user, un, pd->text, pd->is_qualified);
1096         } else {
1097             if (info->on_privmsg) info->on_privmsg(pd->user, un, pd->text, pd->is_qualified);
1098         }
1099     }
1100 }
1101
1102 void
1103 reg_privmsg_func(struct userNode *user, privmsg_func_t handler) {
1104     struct service_message_info *info = dict_find(service_msginfo_dict, user->nick, NULL);
1105     if (!info) {
1106         info = calloc(1, sizeof(*info));
1107         dict_insert(service_msginfo_dict, user->nick, info);
1108     }
1109     info->on_privmsg = handler;
1110 }
1111
1112 void
1113 reg_notice_func(struct userNode *user, privmsg_func_t handler) {
1114     struct service_message_info *info = dict_find(service_msginfo_dict, user->nick, NULL);
1115     if (!info) {
1116         info = calloc(1, sizeof(*info));
1117         dict_insert(service_msginfo_dict, user->nick, info);
1118     }
1119     info->on_notice = handler;
1120 }
1121
1122 void
1123 reg_oper_func(oper_func_t handler)
1124 {
1125     if (of_used == of_size) {
1126         if (of_size) {
1127             of_size <<= 1;
1128             of_list = realloc(of_list, of_size*sizeof(oper_func_t));
1129         } else {
1130             of_size = 8;
1131             of_list = malloc(of_size*sizeof(oper_func_t));
1132         }
1133     }
1134     of_list[of_used++] = handler;
1135 }
1136
1137 static void
1138 call_oper_funcs(struct userNode *user)
1139 {
1140     unsigned int n;
1141     if (IsLocal(user)) return;
1142     for (n=0; n<of_used; n++)
1143     {
1144         of_list[n](user);
1145     }
1146 }
1147
1148 void mod_usermode(struct userNode *user, const char *mode_change) {
1149     int add = 1;
1150
1151     if (!user || !mode_change) return;
1152     while (1) {
1153 #define do_user_mode(FLAG) do { if (add) user->modes |= FLAG; else user->modes &= ~FLAG; } while (0)
1154         switch (*mode_change++) {
1155         case 0: return;
1156         case '+': add = 1; break;
1157         case '-': add = 0; break;
1158         case 'o':
1159             do_user_mode(FLAGS_OPER);
1160             if (add) {
1161                 userList_append(&curr_opers, user);
1162                 call_oper_funcs(user);
1163             } else {
1164                 userList_remove(&curr_opers, user);
1165             }
1166             break;
1167         case 'O': do_user_mode(FLAGS_LOCOP); break;
1168         case 'i': do_user_mode(FLAGS_INVISIBLE);
1169             if (add) invis_clients++; else invis_clients--;
1170             break;
1171         case 'w': do_user_mode(FLAGS_WALLOP); break;
1172         case 's': do_user_mode(FLAGS_SERVNOTICE); break;
1173         case 'd': do_user_mode(FLAGS_DEAF); break;
1174         case 'r': do_user_mode(FLAGS_REGNICK); break;
1175         case 'k': do_user_mode(FLAGS_SERVICE); break;
1176         case 'g': do_user_mode(FLAGS_GLOBAL); break;
1177         case 'h': do_user_mode(FLAGS_HELPER); break;
1178         }
1179 #undef do_user_mode
1180     }
1181 }
1182
1183 struct mod_chanmode *
1184 mod_chanmode_parse(struct chanNode *channel, char **modes, unsigned int argc, unsigned int flags)
1185 {
1186     struct mod_chanmode *change;
1187     unsigned int ii, in_arg, ch_arg, add;
1188
1189     if (argc == 0)
1190         return NULL;
1191     if (!(change = mod_chanmode_alloc(argc)))
1192         return NULL;
1193
1194     for (ii = ch_arg = 0, in_arg = add = 1;
1195          (modes[0][ii] != '\0') && (modes[0][ii] != ' ');
1196          ++ii) {
1197         switch (modes[0][ii]) {
1198         case '+':
1199             add = 1;
1200             break;
1201         case '-':
1202             add = 0;
1203             break;
1204 #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)
1205         case 'R': do_chan_mode(MODE_REGONLY); break;
1206         case 'D': do_chan_mode(MODE_DELAYJOINS); break;
1207         case 'c': do_chan_mode(MODE_NOCOLORS); break;
1208         case 'i': do_chan_mode(MODE_INVITEONLY); break;
1209         case 'm': do_chan_mode(MODE_MODERATED); break;
1210         case 'n': do_chan_mode(MODE_NOPRIVMSGS); break;
1211         case 'p': do_chan_mode(MODE_PRIVATE); break;
1212         case 's': do_chan_mode(MODE_SECRET); break;
1213         case 't': do_chan_mode(MODE_TOPICLIMIT); break;
1214 #undef do_chan_mode
1215         case 'l':
1216             if (add) {
1217                 if (in_arg >= argc)
1218                     goto error;
1219                 change->modes_set |= MODE_LIMIT;
1220                 change->new_limit = atoi(modes[in_arg++]);
1221             } else {
1222                 change->modes_clear |= MODE_LIMIT;
1223             }
1224             break;
1225         case 'k':
1226             if (add) {
1227                 if (in_arg >= argc)
1228                     goto error;
1229                 change->modes_set |= MODE_KEY;
1230                 safestrncpy(change->new_key, modes[in_arg++], sizeof(change->new_key));
1231             } else {
1232                 change->modes_clear |= MODE_KEY;
1233                 if (!(flags & MCP_KEY_FREE)) {
1234                     if (in_arg >= argc)
1235                         goto error;
1236                     in_arg++;
1237                 }
1238             }
1239             break;
1240         case 'b':
1241             if (!(flags & MCP_ALLOW_OVB))
1242                 goto error;
1243             if (in_arg >= argc)
1244                 goto error;
1245             change->args[ch_arg].mode = MODE_BAN;
1246             if (!add)
1247                 change->args[ch_arg].mode |= MODE_REMOVE;
1248             change->args[ch_arg++].hostmask = modes[in_arg++];
1249             break;
1250         case 'o': case 'v':
1251         {
1252             struct userNode *victim;
1253             if (!(flags & MCP_ALLOW_OVB))
1254                 goto error;
1255             if (in_arg >= argc)
1256                 goto error;
1257             change->args[ch_arg].mode = (modes[0][ii] == 'o') ? MODE_CHANOP : MODE_VOICE;
1258             if (!add)
1259                 change->args[ch_arg].mode |= MODE_REMOVE;
1260             victim = GetUserH(modes[in_arg++]);
1261             if ((change->args[ch_arg].member = GetUserMode(channel, victim)))
1262                 ch_arg++;
1263             break;
1264         }
1265         }
1266     }
1267     change->argc = argc; /* in case any turned out to be ignored */
1268     return change;
1269   error:
1270     mod_chanmode_free(change);
1271     return NULL;
1272 }
1273
1274 struct chanmode_buffer {
1275     char modes[MAXLEN];
1276     char args[MAXLEN];
1277     struct chanNode *channel;
1278     struct userNode *actor;
1279     unsigned int modes_used;
1280     unsigned int args_used;
1281     size_t chname_len;
1282     unsigned int is_add : 1;
1283 };
1284
1285 static void
1286 mod_chanmode_append(struct chanmode_buffer *buf, char ch, const char *arg)
1287 {
1288     size_t arg_len = strlen(arg);
1289     if (buf->modes_used + buf->args_used + buf->chname_len + arg_len > 450) {
1290         memcpy(buf->modes + buf->modes_used, buf->args, buf->args_used);
1291         buf->modes[buf->modes_used + buf->args_used] = '\0';
1292         irc_mode(buf->actor, buf->channel, buf->modes);
1293         buf->modes[0] = buf->is_add ? '+' : '-';
1294         buf->modes_used = 1;
1295         buf->args_used = 0;
1296     }
1297     buf->modes[buf->modes_used++] = ch;
1298     buf->args[buf->args_used++] = ' ';
1299     memcpy(buf->args + buf->args_used, arg, arg_len);
1300     buf->args_used += arg_len;
1301 }
1302
1303 void
1304 mod_chanmode_announce(struct userNode *who, struct chanNode *channel, struct mod_chanmode *change)
1305 {
1306     struct chanmode_buffer chbuf;
1307     char int_buff[32];
1308     unsigned int arg;
1309
1310     memset(&chbuf, 0, sizeof(chbuf));
1311     chbuf.channel = channel;
1312     chbuf.actor = who;
1313     chbuf.chname_len = strlen(channel->name);
1314
1315     /* First remove modes */
1316     chbuf.is_add = 0;
1317     if (change->modes_clear) {
1318         chbuf.modes[chbuf.modes_used++] = '-';
1319 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_clear & MODE_##BIT) chbuf.modes[chbuf.modes_used++] = CHAR;
1320         DO_MODE_CHAR(PRIVATE, 'p');
1321         DO_MODE_CHAR(SECRET, 's');
1322         DO_MODE_CHAR(MODERATED, 'm');
1323         DO_MODE_CHAR(TOPICLIMIT, 't');
1324         DO_MODE_CHAR(INVITEONLY, 'i');
1325         DO_MODE_CHAR(NOPRIVMSGS, 'n');
1326         DO_MODE_CHAR(LIMIT, 'l');
1327         DO_MODE_CHAR(DELAYJOINS, 'D');
1328         DO_MODE_CHAR(REGONLY, 'R');
1329         DO_MODE_CHAR(NOCOLORS, 'c');
1330 #undef DO_MODE_CHAR
1331         if (change->modes_clear & channel->modes & MODE_KEY)
1332             mod_chanmode_append(&chbuf, 'k', channel->key);
1333     }
1334     for (arg = 0; arg < change->argc; ++arg) {
1335         if (!(change->args[arg].mode & MODE_REMOVE))
1336             continue;
1337         switch (change->args[arg].mode & ~MODE_REMOVE) {
1338         case MODE_BAN:
1339             mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
1340             break;
1341         default:
1342             if (change->args[arg].mode & MODE_CHANOP)
1343                 mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->nick);
1344             if (change->args[arg].mode & MODE_VOICE)
1345                 mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->nick);
1346             break;
1347         }
1348     }
1349
1350     /* Then set them */
1351     chbuf.is_add = 1;
1352     if (change->modes_set) {
1353         chbuf.modes[chbuf.modes_used++] = '+';
1354 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_set & MODE_##BIT) chbuf.modes[chbuf.modes_used++] = CHAR;
1355         DO_MODE_CHAR(PRIVATE, 'p');
1356         DO_MODE_CHAR(SECRET, 's');
1357         DO_MODE_CHAR(MODERATED, 'm');
1358         DO_MODE_CHAR(TOPICLIMIT, 't');
1359         DO_MODE_CHAR(INVITEONLY, 'i');
1360         DO_MODE_CHAR(NOPRIVMSGS, 'n');
1361         DO_MODE_CHAR(DELAYJOINS, 'D');
1362         DO_MODE_CHAR(REGONLY, 'R');
1363         DO_MODE_CHAR(NOCOLORS, 'c');
1364 #undef DO_MODE_CHAR
1365         if (change->modes_set & MODE_KEY)
1366             mod_chanmode_append(&chbuf, 'k', change->new_key);
1367         if (change->modes_set & MODE_LIMIT)
1368         {
1369             sprintf(int_buff, "%d", change->new_limit);
1370             mod_chanmode_append(&chbuf, 'l', int_buff);
1371         }
1372     }
1373     for (arg = 0; arg < change->argc; ++arg) {
1374         if (change->args[arg].mode & MODE_REMOVE)
1375             continue;
1376         switch (change->args[arg].mode) {
1377         case MODE_BAN:
1378             mod_chanmode_append(&chbuf, 'b', change->args[arg].hostmask);
1379             break;
1380         default:
1381             if (change->args[arg].mode & MODE_CHANOP)
1382                 mod_chanmode_append(&chbuf, 'o', change->args[arg].member->user->nick);
1383             if (change->args[arg].mode & MODE_VOICE)
1384                 mod_chanmode_append(&chbuf, 'v', change->args[arg].member->user->nick);
1385             break;
1386         }
1387     }
1388
1389     /* Flush the buffer and apply changes locally */
1390     if (chbuf.modes_used > 0) {
1391         memcpy(chbuf.modes + chbuf.modes_used, chbuf.args, chbuf.args_used);
1392         chbuf.modes[chbuf.modes_used + chbuf.args_used] = '\0';
1393         irc_mode(chbuf.actor, chbuf.channel, chbuf.modes);
1394     }
1395     mod_chanmode_apply(who, channel, change);
1396 }
1397
1398 char *
1399 mod_chanmode_format(struct mod_chanmode *change, char *outbuff)
1400 {
1401     unsigned int used = 0;
1402     if (change->modes_clear) {
1403         outbuff[used++] = '-';
1404 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_clear & MODE_##BIT) outbuff[used++] = CHAR
1405         DO_MODE_CHAR(PRIVATE, 'p');
1406         DO_MODE_CHAR(SECRET, 's');
1407         DO_MODE_CHAR(MODERATED, 'm');
1408         DO_MODE_CHAR(TOPICLIMIT, 't');
1409         DO_MODE_CHAR(INVITEONLY, 'i');
1410         DO_MODE_CHAR(NOPRIVMSGS, 'n');
1411         DO_MODE_CHAR(LIMIT, 'l');
1412         DO_MODE_CHAR(KEY, 'k');
1413         DO_MODE_CHAR(DELAYJOINS, 'D');
1414         DO_MODE_CHAR(REGONLY, '$');
1415         DO_MODE_CHAR(NOCOLORS, 'c');
1416 #undef DO_MODE_CHAR
1417     }
1418     if (change->modes_set) {
1419         outbuff[used++] = '+';
1420 #define DO_MODE_CHAR(BIT, CHAR) if (change->modes_set & MODE_##BIT) outbuff[used++] = CHAR
1421         DO_MODE_CHAR(PRIVATE, 'p');
1422         DO_MODE_CHAR(SECRET, 's');
1423         DO_MODE_CHAR(MODERATED, 'm');
1424         DO_MODE_CHAR(TOPICLIMIT, 't');
1425         DO_MODE_CHAR(INVITEONLY, 'i');
1426         DO_MODE_CHAR(NOPRIVMSGS, 'n');
1427         DO_MODE_CHAR(DELAYJOINS, 'D');
1428         DO_MODE_CHAR(REGONLY, 'R');
1429         DO_MODE_CHAR(NOCOLORS, 'c');
1430 #undef DO_MODE_CHAR
1431         switch (change->modes_set & (MODE_KEY|MODE_LIMIT)) {
1432         case MODE_KEY|MODE_LIMIT:
1433             used += sprintf(outbuff+used, "lk %d %s", change->new_limit, change->new_key);
1434             break;
1435         case MODE_KEY:
1436             used += sprintf(outbuff+used, "k %s", change->new_key);
1437             break;
1438         case MODE_LIMIT:
1439             used += sprintf(outbuff+used, "l %d", change->new_limit);
1440             break;
1441         }
1442     }
1443     outbuff[used] = 0;
1444     return outbuff;
1445 }