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