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