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