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