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