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