ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/protocol/inspircd12.C
Revision: 1.9
Committed: Sat Sep 22 14:27:28 2007 UTC (16 years, 8 months ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.8: +6 -2 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# Content
1 /**
2 * inspircd12.C: This file contains protocol support for spanning tree 1.2 branch inspircd.
3 *
4 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
5 * Rights to this code are as documented in COPYING.
6 *
7 *
8 * Portions of this file were derived from sources bearing the following license:
9 * Copyright © 2005-2006 Atheme Development Group
10 * Rights to this code are documented in doc/pod/license.pod.
11 *
12 * $Id: inspircd12.C,v 1.8 2007-09-16 18:54:44 pippijn Exp $
13 */
14
15 #include <boost/foreach.hpp>
16
17 #include "atheme.h"
18 #include <util/time.h>
19 #include <libermyth.h>
20 #include "servers.h"
21 #include <account/mychan.h>
22 #include <account/myuser.h>
23 #include "uplink.h"
24 #include "pmodule.h"
25 #include "protocol/inspircd.h"
26
27 static char const rcsid[] = "$Id: inspircd12.C,v 1.8 2007-09-16 18:54:44 pippijn Exp $";
28
29 /* *INDENT-OFF* */
30
31 namespace protocol
32 {
33 static ircd_t InspIRCd = {
34 "InspIRCd 1.2.x", /* IRCd name */
35 "$", /* TLD Prefix, used by Global. */
36 true, /* Whether or not we use IRCNet/TS6 UID */
37 false, /* Whether or not we use RCOMMAND */
38 false, /* Whether or not we support channel owners. */
39 false, /* Whether or not we support channel protection. */
40 true, /* Whether or not we support halfops. */
41 false, /* Whether or not we use P10 */
42 true, /* Whether or not we use vHosts. */
43 CMODE_OPERONLY, /* Oper-only cmodes */
44 CMODE_OWNER, /* Integer flag for owner channel flag. */
45 CMODE_PROTECT, /* Integer flag for protect channel flag. */
46 CMODE_HALFOP, /* Integer flag for halfops. */
47 "+q", /* Mode we set for owner. */
48 "+a", /* Mode we set for protect. */
49 "+h", /* Mode we set for halfops. */
50 PROTOCOL_INSPIRCD, /* Protocol type */
51 0, /* Permanent cmodes */
52 "beIg", /* Ban-like cmodes */
53 'e', /* Except mchar */
54 'I', /* Invex mchar */
55 IRCD_CIDR_BANS /* Flags */
56 };
57
58 struct inspircd12_handler : handler
59 {
60 inspircd12_handler ();
61 virtual ~inspircd12_handler ();
62
63 virtual unsigned int server_login (void);
64 virtual void introduce_nick (user_t *u);
65 virtual void invite_sts (user_t *source, user_t *target, channel_t *channel);
66 virtual void quit_sts (user_t *u, char const * const reason);
67 virtual void wallops_sts (char const * const text);
68 virtual void join_sts (channel_t *c, user_t *u, bool isnew, char const * const modes);
69 virtual void chan_lowerts (channel_t *c, user_t *u);
70 virtual void kick (char const * const from, char const * const channel, char const * const to, char const * const reason);
71 virtual void mdchange (char const * const target, char const * const key, char const * const value);
72 virtual void privmsg (char const * const from, char const * const target, char const * const fmt, ...);
73 virtual void notice_user_sts (user_t *from, user_t *target, char const * const text);
74 virtual void notice_global_sts (user_t *from, char const * const mask, char const * const text);
75 virtual void notice_channel_sts (user_t *from, channel_t *target, char const * const text);
76 virtual void numeric_sts (char const * const from, int numeric, char const * const target, char const * const fmt, ...);
77 virtual void skill (char const * const from, char const * const nick, char const * const fmt, ...);
78 virtual void part_sts (channel_t *c, user_t *u);
79 virtual void kline_sts (char const * const server, char const * const user, char const * const host, long duration, char const * const reason);
80 virtual void unkline_sts (char const * const server, char const * const user, char const * const host);
81 virtual void topic_sts (channel_t *c, char const * const setter, time_t ts, time_t prevts, char const * const topic);
82 virtual void mode_sts (char const * const sender, channel_t *target, char const * const modes);
83 virtual void ping_sts (void);
84 virtual void ircd_on_login (char const * const origin, char const * const user, char const * const wantedhost);
85 virtual bool ircd_on_logout (char const * const origin, char const * const user, char const * const wantedhost);
86 virtual void jupe (char const * const server, char const * const reason);
87 virtual void sethost_sts (char const * const source, char const * const target, char const * const host);
88 virtual void fnc_sts (user_t *source, user_t *u, char const * const newnick, int type);
89 virtual void holdnick_sts (user_t *source, int duration, char const * const nick, myuser_t *account);
90 };
91
92 static cmode_t inspircd12_mode_list[] = {
93 { 'i', CMODE_INVITE },
94 { 'm', CMODE_MOD },
95 { 'n', CMODE_NOEXT },
96 { 'p', CMODE_PRIV },
97 { 's', CMODE_SEC },
98 { 't', CMODE_TOPIC },
99 { 'c', CMODE_NOCOLOR },
100 { 'M', CMODE_MODREG },
101 { 'R', CMODE_REGONLY },
102 { 'O', CMODE_OPERONLY },
103 { 'S', CMODE_STRIP },
104 { 'K', CMODE_NOKNOCK },
105 { 'V', CMODE_NOINVITE },
106 { 'C', CMODE_NOCTCP },
107 { 'N', CMODE_STICKY },
108 { 'G', CMODE_CENSOR },
109 { 'P', CMODE_NOCAPS },
110 { 'z', CMODE_SSLONLY },
111 { 'T', CMODE_NONOTICE },
112 { 'u', CMODE_HIDING },
113 { 'Q', CMODE_PEACE },
114 { '\0', 0 }
115 };
116
117 static bool check_flood(char const * const , channel_t *, mychan_t *, user_t *, myuser_t *);
118 static bool check_nickflood (char const * const , channel_t *, mychan_t *, user_t *, myuser_t *);
119 static bool check_jointhrottle(char const * const , channel_t *, mychan_t *, user_t *, myuser_t *);
120 static bool check_forward(char const * const , channel_t *, mychan_t *, user_t *, myuser_t *);
121 static bool check_rejoindelay(char const * const , channel_t *, mychan_t *, user_t *, myuser_t *);
122
123 static extmode_t inspircd12_ignore_mode_list[] = {
124 { 'f', check_flood },
125 { 'F', check_nickflood },
126 { 'j', check_jointhrottle },
127 { 'L', check_forward },
128 { 'J', check_rejoindelay },
129 { '\0', 0 }
130 };
131
132 static cmode_t inspircd12_status_mode_list[] = {
133 { 'q', CMODE_OWNER },
134 { 'a', CMODE_PROTECT },
135 { 'o', CMODE_OP },
136 { 'h', CMODE_HALFOP },
137 { 'v', CMODE_VOICE },
138 { '\0', 0 }
139 };
140
141 static cmode_t inspircd12_prefix_mode_list[] = {
142 { '~', CMODE_OWNER },
143 { '&', CMODE_PROTECT },
144 { '@', CMODE_OP },
145 { '%', CMODE_HALFOP },
146 { '+', CMODE_VOICE },
147 { '\0', 0 }
148 };
149
150 /* CAPABilities */
151 static bool has_servicesmod = false;
152 static bool has_globopsmod = false;
153 static bool has_svshold = false;
154 static int has_protocol = 0;
155
156 #define PROTOCOL_SNONOTICE 1102 /* has SNONOTICE and OPERNOTICE commands */
157 #define PROTOCOL_NEWTS 1104 /* does not send "confirming" FMODEs on TS changes and uses clear/ignore semantics also for FMODE */
158
159 /* *INDENT-ON* */
160
161 static bool
162 check_flood (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu)
163 {
164 /* +F doesn't support *, so don't bother checking for it -- w00t */
165 return check_jointhrottle (value, c, mc, u, mu);
166 }
167
168 static bool
169 check_nickflood (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu)
170 {
171 return *value == '*' ? check_jointhrottle(value + 1, c, mc, u, mu) : check_jointhrottle(value, c, mc, u, mu);
172 }
173
174 static bool
175 check_jointhrottle (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu)
176 {
177 char const *p;
178 char const *arg2;
179
180 p = value, arg2 = NULL;
181 while (*p != '\0')
182 {
183 if (*p == ':')
184 {
185 if (arg2 != NULL)
186 return false;
187 arg2 = p + 1;
188 }
189 else if (!isdigit (*p))
190 return false;
191 p++;
192 }
193 if (arg2 == NULL)
194 return false;
195 if (p - arg2 > 10 || arg2 - value - 1 > 10 || !atoi (value) || !atoi (arg2))
196 return false;
197 return true;
198 }
199
200 static bool
201 check_forward (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu)
202 {
203 channel_t *target_c;
204 mychan_t *target_mc;
205
206 if (*value != '#' || strlen (value) > 50)
207 return false;
208 if (u == NULL && mu == NULL)
209 return true;
210 target_c = channel_find (value);
211 target_mc = mychan_t::find (value);
212 if (target_c == NULL && target_mc == NULL)
213 return false;
214 return true;
215 }
216
217 static bool
218 check_rejoindelay (char const * const value, channel_t *c, mychan_t *mc, user_t *u, myuser_t *mu)
219 {
220 char const *ch = value;
221
222 while (*ch)
223 {
224 if (!isdigit (*ch))
225 return false;
226 ch++;
227 }
228
229 if (atoi (value) <= 0)
230 {
231 return false;
232 }
233 else
234 {
235 return true;
236 }
237 }
238
239
240 /* login to our uplink */
241 unsigned int
242 inspircd12_handler::server_login (void)
243 {
244 int ret;
245
246 /* Check if we have a numeric set. InspIRCd 1.2 protocol
247 * requires it. -nenolod
248 */
249 if (me.numeric == NULL)
250 {
251 slog (LG_ERROR, "inspircd12_handler::server_login(): inspircd 1.2 requires a unique identifier. set serverinfo::numeric.");
252 exit (EXIT_FAILURE);
253 }
254
255 ret = sts ("SERVER %s %s 0 %s :%s", me.name, curr_uplink->pass, me.numeric, me.desc);
256 if (ret == 1)
257 return 1;
258
259 me.bursting = true;
260 sts ("BURST");
261 /* XXX: Being able to get this data as a char* would be nice - Brain */
262 sts (":%s VERSION :atheme-%s. %s %s%s%s%s%s%s%s%s%s%s",
263 me.name,
264 version,
265 me.numeric,
266 match_mapping ? "A" : "",
267 log_debug_enabled () ? "d" : "",
268 me.auth ? "e" : "",
269 config_options.flood_msgs ? "F" : "",
270 config_options.leave_chans ? "l" : "",
271 config_options.join_chans ? "j" : "",
272 chansvs.changets ? "t" : "",
273 !match_mapping ? "R" : "",
274 config_options.raw ? "r" : "",
275 runflags & RF_LIVE ? "n" : "");
276 services_init ();
277 return 0;
278 }
279
280 /* introduce a client */
281 void
282 inspircd12_handler::introduce_nick (user_t *u)
283 {
284 /* :penguin.omega.org.za UID 497AAAAAB 1188302517 OperServ 127.0.0.1 127.0.0.1 OperServ +s 127.0.0.1 :Operator Server */
285 sts (":%s UID %s %ld %s %s %s %s +%s 0.0.0.0 %ld :%s", me.numeric, u->uid, u->ts, u->nick, u->host, u->host, u->user, "io", u->ts, u->gecos);
286 sts (":%s OPERTYPE Services", u->uid);
287 }
288
289 void
290 inspircd12_handler::quit_sts (user_t *u, char const * const reason)
291 {
292 if (!me.connected)
293 return;
294
295 sts (":%s QUIT :%s", u->uid, reason);
296 }
297
298 /* WALLOPS wrapper */
299 void
300 inspircd12_handler::wallops_sts (char const * const text)
301 {
302 char *sendernick = NULL;
303 user_t *u;
304 node_t *n;
305
306 if (has_protocol >= PROTOCOL_SNONOTICE)
307 {
308 /* XXX */
309 if (has_globopsmod)
310 sts (":%s SNONOTICE g :%s", me.numeric, text);
311 else
312 sts (":%s OPERNOTICE :%s", me.numeric, text);
313 return;
314 }
315
316 if (me.me == NULL)
317 return;
318
319 if (is_internal_client (user_find_named (opersvs.nick)))
320 {
321 sendernick = opersvs.nick;
322 }
323 else
324 {
325 LIST_FOREACH (n, me.me->userlist.head)
326 {
327 u = (user_t *) n->data;
328
329 sendernick = u->uid;
330 break;
331 }
332 }
333
334 if (sendernick == NULL)
335 {
336 /*
337 * this means we have no pseudoclients -- under present inspircd, servers cannot globops, and
338 * thus, we will need to bail -- slog, and let them know. --w00t
339 */
340 slog (LG_ERROR, "wallops_sts(): InspIRCD requires at least one pseudoclient module to be loaded to send wallops.");
341 return;
342 }
343
344 sts (":%s GLOBOPS :%s", sendernick, text);
345 }
346
347 /* join a channel */
348 void
349 inspircd12_handler::join_sts (channel_t *c, user_t *u, bool isnew, char const * const modes)
350 {
351 if (isnew)
352 {
353 sts (":%s FJOIN %s %ld :@,%s", me.numeric, c->name, c->ts, u->uid);
354 if (modes[0] && modes[1])
355 sts (":%s FMODE %s %ld %s", me.numeric, c->name, c->ts, modes);
356 }
357 else
358 {
359 sts (":%s FJOIN %s %ld :@,%s", me.numeric, c->name, c->ts, u->uid);
360 }
361 }
362
363 void
364 inspircd12_handler::chan_lowerts (channel_t *c, user_t *u)
365 {
366 slog (LG_DEBUG, "inspircd12_handler::chan_lowerts(): lowering TS for %s to %ld", c->name, (long) c->ts);
367
368 sts (":%s FJOIN %s %ld :@,%s", me.numeric, c->name, c->ts, u->uid);
369 sts (":%s FMODE %s %ld %s", me.numeric, c->name, c->ts, channel_modes (c, true));
370 }
371
372 /* kicks a user from a channel */
373 void
374 inspircd12_handler::kick (char const * const from, char const * const channel, char const * const to, char const * const reason)
375 {
376 channel_t *c = channel_find (channel);
377 user_t *u = user_find (to);
378 user_t *su = user_find (from);
379
380 if (!c || !u)
381 return;
382
383 sts (":%s KICK %s %s :%s", su->uid, channel, u->uid, reason);
384
385 chanuser_delete (c, u);
386 }
387
388 // changes ircd metadata
389 void
390 inspircd12_handler::mdchange (char const * const target, char const * const key, char const * const value)
391 {
392 sts (":%s METADATA %s %s :%s", me.numeric, target, key, value);
393 }
394
395 /* PRIVMSG wrapper */
396 void
397 inspircd12_handler::privmsg (char const * const from, char const * const target, char const * const fmt, ...)
398 {
399 va_list ap;
400 char buf[BUFSIZE];
401 user_t *u = user_find (from);
402 user_t *su = user_find (from);
403
404 va_start (ap, fmt);
405 vsnprintf (buf, BUFSIZE, fmt, ap);
406 va_end (ap);
407
408 sts (":%s PRIVMSG %s :%s", su->uid, *target != '#' ? u->uid : target, buf);
409 }
410
411 /* NOTICE wrapper */
412 void
413 inspircd12_handler::notice_user_sts (user_t *from, user_t *target, char const * const text)
414 {
415 sts (":%s NOTICE %s :%s", from ? from->uid : me.numeric, target->nick, text);
416 }
417
418 void
419 inspircd12_handler::notice_global_sts (user_t *from, char const * const mask, char const * const text)
420 {
421 sts (":%s NOTICE %s%s :%s", from ? from->uid : me.numeric, ircd->tldprefix, mask, text);
422 }
423
424 void
425 inspircd12_handler::notice_channel_sts (user_t *from, channel_t *target, char const * const text)
426 {
427 sts (":%s NOTICE %s :%s", from ? from->uid : me.numeric, target->name, text);
428 }
429
430 void
431 inspircd12_handler::numeric_sts (char const * const from, int numeric, char const * const target, char const * const fmt, ...)
432 {
433 va_list ap;
434 char buf[BUFSIZE];
435
436 va_start (ap, fmt);
437 vsnprintf (buf, BUFSIZE, fmt, ap);
438 va_end (ap);
439
440 sts (":%s PUSH %s ::%s %d %s %s", me.numeric, target, from, numeric, target, buf);
441 }
442
443 /* KILL wrapper */
444 void
445 inspircd12_handler::skill (char const * const from, char const * const nick, char const * const fmt, ...)
446 {
447 va_list ap;
448 char buf[BUFSIZE];
449
450 va_start (ap, fmt);
451 vsnprintf (buf, BUFSIZE, fmt, ap);
452 va_end (ap);
453
454 sts (":%s KILL %s :[%s] Killed (%s (%s))", from, nick, me.name, from, buf);
455 }
456
457 /* PART wrapper */
458 void
459 inspircd12_handler::part_sts (channel_t *c, user_t *u)
460 {
461 sts (":%s PART %s :Leaving", u->uid, c->name);
462 }
463
464 /* server-to-server KLINE wrapper */
465 void
466 inspircd12_handler::kline_sts (char const * const server, char const * const user, char const * const host, long duration, char const * const reason)
467 {
468 if (!me.connected)
469 return;
470
471 /* :services-dev.chatspike.net ADDLINE G test@test.com Brain 1133994664 0 :You are banned from this network */
472 sts (":%s ADDLINE G %s@%s %s %ld %ld :%s", me.numeric, user, host, opersvs.nick, NOW, duration, reason);
473 }
474
475 /* server-to-server UNKLINE wrapper */
476 void
477 inspircd12_handler::unkline_sts (char const * const server, char const * const user, char const * const host)
478 {
479 if (!me.connected)
480 return;
481
482 /* I know this looks wrong, but it's really not. Trust me. --w00t */
483 sts (":%s GLINE %s@%s", opersvs.nick, user, host);
484 }
485
486 /* topic wrapper */
487 void
488 inspircd12_handler::topic_sts (channel_t *c, char const * const setter, time_t ts, time_t prevts, char const * const topic)
489 {
490 if (!me.connected || !c)
491 return;
492
493 /* If possible, try to use FTOPIC
494 * Note that because TOPIC does not contain topicTS, it may be
495 * off a few seconds on other servers, hence the 60 seconds here.
496 * -- jilles */
497 /* Restoring old topic */
498 if (ts > prevts + 60 || prevts == 0)
499 {
500 sts (":%s FTOPIC %s %ld %s :%s", chansvs.me->me->uid, c->name, ts, setter, topic);
501 return;
502 }
503 /* Tweaking a topic */
504 else if (ts == prevts)
505 {
506 ts += 60;
507 sts (":%s FTOPIC %s %ld %s :%s", chansvs.me->me->uid, c->name, ts, setter, topic);
508 c->topicts = ts;
509 return;
510 }
511 sts (":%s TOPIC %s :%s", chansvs.me->me->uid, c->name, topic);
512 c->topicts = NOW;
513 }
514
515 /* mode wrapper */
516 void
517 inspircd12_handler::mode_sts (char const * const sender, channel_t *target, char const * const modes)
518 {
519 user_t *su = user_find (sender);
520
521 if (!me.connected)
522 return;
523
524 if (has_protocol >= PROTOCOL_NEWTS)
525 /* FMODE from user is ok, use it */
526 sts (":%s FMODE %s %ld %s", su->uid, target->name, target->ts, modes);
527 else
528 sts (":%s MODE %s %s", su->uid, target->name, modes);
529 }
530
531 /* ping wrapper */
532 void
533 inspircd12_handler::ping_sts (void)
534 {
535 if (!me.connected)
536 return;
537
538 sts (":%s PING %s", me.numeric, curr_uplink->name);
539 }
540
541 /* protocol-specific stuff to do on login */
542 void
543 inspircd12_handler::ircd_on_login (char const * const origin, char const * const user, char const * const wantedhost)
544 {
545 if (!me.connected)
546 return;
547
548 sts (":%s METADATA %s accountname :%s", me.numeric, origin, user);
549 }
550
551 /* protocol-specific stuff to do on logout */
552 bool
553 inspircd12_handler::ircd_on_logout (char const * const origin, char const * const user, char const * const wantedhost)
554 {
555 if (!me.connected)
556 return false;
557
558 sts (":%s METADATA %s accountname :", me.numeric, origin);
559 return false;
560 }
561
562 void
563 inspircd12_handler::jupe (char const * const server, char const * const reason)
564 {
565 server_t *s;
566 if (!me.connected)
567 return;
568
569 s = server_find (server);
570 if (s != NULL)
571 {
572 /* We need to wait for the RSQUIT to be processed -- jilles */
573 sts (":%s RSQUIT :%s", opersvs.nick, server);
574 s->flags |= SF_JUPE_PENDING;
575 }
576 else
577 {
578 /* Remove any previous jupe first */
579 sts (":%s SQUIT %s :%s", me.numeric, server, reason);
580 sts (":%s SERVER %s * 1 :%s", me.numeric, server, reason);
581 }
582 }
583
584 void
585 inspircd12_handler::sethost_sts (char const * const source, char const * const target, char const * const host)
586 {
587 if (!me.connected)
588 return;
589
590 notice (source, target, "Setting your host to \2%s\2.", host);
591 sts (":%s CHGHOST %s %s", source, target, host);
592 }
593
594 void
595 inspircd12_handler::fnc_sts (user_t *source, user_t *u, char const * const newnick, int type)
596 {
597 /* svsnick can only be sent by a server */
598 sts (":%s SVSNICK %s %s %lu", me.numeric, u->uid, newnick, NOW - 60);
599 }
600
601
602 /* invite a user to a channel */
603 void
604 inspircd12_handler::invite_sts (user_t *sender, user_t *target, channel_t *channel)
605 {
606 sts (":%s INVITE %s %s", sender->nick, target->nick, channel->name);
607 }
608
609 void
610 inspircd12_handler::holdnick_sts (user_t *source, int duration, char const * const nick, myuser_t *account)
611 {
612 if (duration == 0)
613 /* remove SVSHOLD */
614 sts (":%s SVSHOLD %s", source->nick, nick);
615 else
616 sts (":%s SVSHOLD %s %ds :Registered nickname.", source->nick, nick, duration);
617 }
618
619 static void
620 m_topic (sourceinfo_t *si, int parc, char *parv[])
621 {
622 channel_t *c = channel_find (parv[0]);
623
624 if (!c)
625 return;
626
627 handle_topic_from (si, c, si->su->nick, NOW, parv[1]);
628 }
629
630 static void
631 m_ftopic (sourceinfo_t *si, int parc, char *parv[])
632 {
633 channel_t *c = channel_find (parv[0]);
634 time_t ts = atol (parv[1]);
635
636 if (!c)
637 return;
638
639 if (c->topic != NULL && c->topicts >= ts)
640 {
641 slog (LG_DEBUG, "m_ftopic(): ignoring older topic on %s", c->name);
642 return;
643 }
644
645 handle_topic_from (si, c, parv[2], ts, parv[3]);
646 }
647
648 static void
649 m_ping (sourceinfo_t *si, int parc, char *parv[])
650 {
651 /* reply to PING's */
652 sts (":%s PONG %s", me.numeric, parv[0]);
653 }
654
655 static void
656 m_pong (sourceinfo_t *si, int parc, char *parv[])
657 {
658 handle_eob (si->s);
659
660 if (irccasecmp (me.actual, si->s->name))
661 return;
662 me.uplinkpong = NOW;
663
664 /* -> :test.projectxero.net PONG test.projectxero.net :shrike.malkier.net */
665 if (me.bursting)
666 {
667 #ifdef HAVE_GETTIMEOFDAY
668 e_time (burstime, &burstime);
669
670 slog (LG_INFO, "m_pong(): finished synching with uplink (%d %s)", (tv2ms (&burstime) > 1000) ? (tv2ms (&burstime) / 1000) : tv2ms (&burstime), (tv2ms (&burstime) > 1000) ? "s" : "ms");
671
672 wallops ("Finished synching to network in %d %s.", (tv2ms (&burstime) > 1000) ? (tv2ms (&burstime) / 1000) : tv2ms (&burstime), (tv2ms (&burstime) > 1000) ? "s" : "ms");
673 #else
674 slog (LG_INFO, "m_pong(): finished synching with uplink");
675 wallops ("Finished synching to network.");
676 #endif
677
678 me.bursting = false;
679 }
680 }
681
682 static void
683 m_privmsg (sourceinfo_t *si, int parc, char *parv[])
684 {
685 if (parc != 2)
686 return;
687
688 handle_message (si, parv[0], false, parv[1]);
689 }
690
691 static void
692 m_notice (sourceinfo_t *si, int parc, char *parv[])
693 {
694 if (parc != 2)
695 return;
696
697 handle_message (si, parv[0], true, parv[1]);
698 }
699
700 static void
701 m_fjoin (sourceinfo_t *si, int parc, char *parv[])
702 {
703 /* FJOIN #flaps 1234 :@,fanny +%,arse ,tits ,breasts &~,poontang */
704 channel_t *c;
705 unsigned int userc;
706 unsigned int i;
707 unsigned int j;
708 unsigned int nlen;
709 bool prefix = true;
710 bool keep_new_modes = true;
711 char *userv[256];
712 char prefixandnick[51];
713 time_t ts;
714
715 c = channel_find (parv[0]);
716 ts = atol (parv[1]);
717
718 if (!c)
719 {
720 slog (LG_DEBUG, "m_fjoin(): new channel: %s", parv[0]);
721 c = channel_add (parv[0], ts, si->s);
722 return_if_fail (c != NULL);
723 /* Tell the core to check mode locks now,
724 * otherwise it may only happen after the next
725 * mode change.
726 * Inspircd does not allow any redundant modes
727 * so this will not look ugly. -- jilles */
728 /* As discussed with Brain, if this is in a burst,
729 * an FMODE with the simple modes will follow so we
730 * can skip this. -- jilles */
731 if (!me.bursting)
732 channel_mode (NULL, c, "+");
733 }
734
735 if (ts < c->ts)
736 {
737 chanuser_t *cu;
738 node_t *n;
739
740 /* the TS changed. a TS change requires us to do
741 * bugger all except update the TS, because in InspIRCd
742 * remote servers enforce the TS change - Brain
743 *
744 * This is no longer the case with 1.1, we need to bounce their modes
745 * as well as lowering the channel ts. Do both. -- w00t
746 */
747
748 if (has_protocol >= PROTOCOL_NEWTS)
749 {
750 clear_simple_modes (c);
751 chanban_clear (c);
752 }
753 LIST_FOREACH (n, c->members.head)
754 {
755 cu = (chanuser_t *) n->data;
756 if (cu->user->server == me.me)
757 {
758 /* it's a service, reop */
759 sts (":%s FMODE %s %ld +o %s", me.numeric, c->name, ts, cu->user->nick);
760 cu->modes = CMODE_OP;
761 }
762 else
763 cu->modes = 0;
764 }
765
766 c->ts = ts;
767 c->callback.tschange (c);
768 }
769 else if (ts > c->ts)
770 {
771 keep_new_modes = false; /* ignore statuses */
772 }
773
774 /*
775 * ok, here's the difference from 1.0 -> 1.1:
776 * 1.0 sent p[3] and up as individual users, prefixed with their 'highest' prefix, @, % or +
777 * in 1.1, this is more complex. All prefixes are sent, with the additional caveat that modules
778 * can add their own prefixes (dangerous!) - therefore, don't just chanuser_add(), split the prefix
779 * out and ignore unknown prefixes (probably the safest option). --w00t
780 */
781 userc = sjtoken (parv[parc - 1], ' ', userv);
782
783 /* loop over all the users in this fjoin */
784 for (i = 0; i < userc; i++)
785 {
786 nlen = 0;
787 prefix = true;
788
789 slog (LG_DEBUG, "m_fjoin(): processing user: %s", userv[i]);
790
791 /*
792 * ok, now look at the chars in the nick.. we have something like "@%,w00t", but need @%w00t.. and
793 * we also want to ignore unknown prefixes.. loop through the chars
794 */
795 for (; *userv[i]; userv[i]++)
796 {
797 /* does this char match a known prefix? */
798 for (j = 0; prefix_mode_list[j].mode; j++)
799 {
800 /* yup. add it to the 'final' combination (@%w00t) */
801 if (*userv[i] == prefix_mode_list[j].mode)
802 {
803 prefixandnick[nlen++] = *userv[i];
804 continue;
805 }
806 }
807
808 /* it's not a known prefix char, have we reached the end of the prefixes? */
809 if (*userv[i] == ',')
810 {
811 /* yup, skip over the comma */
812 userv[i]++;
813
814 /* if we're ignoring status (keep_new_modes is false) then just add them to chan here.. */
815 if (keep_new_modes == false)
816 {
817 /* This ignores the @%, and just adds 'w00t' to the chan */
818 chanuser_add (c, userv[i]);
819 }
820 else
821 {
822 /* else, we do care about their prefixes.. add '@%w00t' to the chan */
823 strlcpy (prefixandnick + nlen, userv[i], sizeof (prefixandnick) - nlen);
824 chanuser_add (c, prefixandnick);
825 }
826
827 /* added them.. break out of this loop, which will move us to the next user */
828 break;
829 }
830 else
831 {
832 /* unknown prefix char */
833 }
834 }
835
836 /* go to the next user */
837 }
838
839 if (c->nummembers == 0 && !(c->modes & ircd->perm_mode))
840 channel_delete (c);
841 }
842
843 static void
844 m_part (sourceinfo_t *si, int parc, char *parv[])
845 {
846 int chanc;
847 char *chanv[256];
848 int i;
849
850 chanc = sjtoken (parv[0], ',', chanv);
851 for (i = 0; i < chanc; i++)
852 {
853 slog (LG_DEBUG, "m_part(): user left channel: %s -> %s", si->su->nick, chanv[i]);
854
855 chanuser_delete (channel_find (chanv[i]), si->su);
856 }
857 }
858
859 static void
860 m_uid (sourceinfo_t *si, int parc, char *parv[])
861 {
862 user_t *u;
863
864 /* :3ZZ UID 3ZZAAAAAB 1133994664 nenolod petrie.ipv6.nenolod.net petrie.ipv6.nenolod.net nenolod +i 0.0.0.0 1351531513 :William Pitcock */
865 if (parc == 10)
866 {
867 slog (LG_DEBUG, "m_uid(): new user on `%s': %s", si->s->name, parv[2]);
868
869 /* char *nick, char *user, char *host, char *vhost, char *ip, char *uid, char *gecos, server_t *server, unsigned int ts */
870 u = user_add (parv[2], parv[5], parv[3], parv[4], parv[7], parv[0], parv[9], si->s, atol (parv[9]));
871 user_mode (u, parv[5]);
872
873 /* If server is not yet EOB we will do this later.
874 * This avoids useless "please identify" -- jilles */
875 if (si->s->flags & SF_EOB)
876 handle_nickchange (u);
877 }
878 else
879 {
880 int i;
881 slog (LG_DEBUG, "m_uid(): got UID with wrong number of params");
882
883 for (i = 0; i < parc; i++)
884 slog (LG_DEBUG, "m_uid(): parv[%d] = %s", i, parv[i]);
885 }
886 }
887
888 static void
889 m_nick (sourceinfo_t *si, int parc, char *parv[])
890 {
891 /* if it's only 1 then it's a nickname change, if it's 2, it's a nickname change with a TS */
892 if (parc == 1 || parc == 2)
893 {
894 if (!si->su)
895 {
896 slog (LG_DEBUG, "m_nick(): server trying to change nick: %s", si->s != NULL ? si->s->name : "<none>");
897 return;
898 }
899
900 slog (LG_DEBUG, "m_nick(): nickname change from `%s': %s", si->su->nick, parv[0]);
901
902 /* No TS here for some reason, hmm */
903 user_changenick (si->su, parv[0], si->su->ts);
904
905 /* It could happen that our PING arrived late and the
906 * server didn't acknowledge EOB yet even though it is
907 * EOB; don't send double notices in that case -- jilles */
908 if (si->su->server->flags & SF_EOB)
909 handle_nickchange (si->su);
910 }
911 else
912 {
913 int i;
914 slog (LG_DEBUG, "m_nick(): got NICK with wrong number of params");
915
916 for (i = 0; i < parc; i++)
917 slog (LG_DEBUG, "m_nick(): parv[%d] = %s", i, parv[i]);
918 }
919 }
920
921 static void
922 m_quit (sourceinfo_t *si, int parc, char *parv[])
923 {
924 slog (LG_DEBUG, "m_quit(): user leaving: %s", si->su->nick);
925
926 /* user_delete() takes care of removing channels and so forth */
927 user_delete (si->su);
928 }
929
930 static void
931 m_saquit (sourceinfo_t *si, int parc, char *parv[])
932 {
933 user_t *u = user_find (parv[0]);
934
935 slog (LG_DEBUG, "m_saquit(): user leaving: %s", parv[0]);
936
937 /* user_delete() takes care of removing channels and so forth */
938 user_delete (u);
939 }
940
941 static void
942 m_mode (sourceinfo_t *si, int parc, char *parv[])
943 {
944 if (*parv[0] == '#')
945 channel_mode (NULL, channel_find (parv[0]), parc - 1, &parv[1]);
946 else
947 user_mode (user_find (parv[0]), parv[1]);
948 }
949
950 static void
951 m_fmode (sourceinfo_t *si, int parc, char *parv[])
952 {
953 channel_t *c;
954 bool onlydeop;
955 time_t ts;
956 char const *p;
957
958 /* :server.moo FMODE #blarp tshere +ntsklLg keymoo 1337 secks */
959 if (*parv[0] == '#')
960 {
961 c = channel_find (parv[0]);
962 if (c == NULL)
963 {
964 slog (LG_DEBUG, "m_fmode(): nonexistant channel: %s", parv[0]);
965 return;
966 }
967 ts = atoi (parv[1]);
968 if (ts == c->ts && has_protocol < PROTOCOL_NEWTS)
969 {
970 onlydeop = true;
971 p = parv[2];
972 while (*p != '\0')
973 {
974 if (!strchr ("-qaohv", *p))
975 onlydeop = false;
976 p++;
977 }
978 if (onlydeop && si->s != NULL)
979 {
980 /* ignore redundant deops generated
981 * if we lower the TS of a channel
982 * scenario: user with autoop privs recreates
983 * channel
984 * XXX could this ignore other stuff too?
985 * -- jilles */
986 slog (LG_DEBUG, "m_fmode(): ignoring %s %s: incoming TS %ld is equal to our TS %ld, and only deops", parv[0], parv[2], ts, c->ts);
987 return;
988 }
989 }
990 else if (ts > c->ts)
991 {
992 if (has_protocol < PROTOCOL_NEWTS)
993 slog (LG_DEBUG, "m_fmode(): accepting but should bounce %s %s: incoming TS %ld is newer than our TS %ld", parv[0], parv[2], ts, c->ts);
994 else
995 return;
996 }
997 else if (ts < c->ts)
998 slog (LG_DEBUG, "m_fmode(): %s %s: incoming TS %ld is older than our TS %ld, possible desync", parv[0], parv[2], ts, c->ts);
999 channel_mode (NULL, c, parc - 2, &parv[2]);
1000 }
1001 else
1002 user_mode (user_find (parv[0]), parv[2]);
1003 }
1004
1005 static void
1006 m_kick (sourceinfo_t *si, int parc, char *parv[])
1007 {
1008 user_t *u = user_find (parv[1]);
1009 channel_t *c = channel_find (parv[0]);
1010
1011 /* -> :rakaur KICK #shrike rintaun :test */
1012 slog (LG_DEBUG, "m_kick(): user was kicked: %s -> %s", parv[1], parv[0]);
1013
1014 if (!u)
1015 {
1016 slog (LG_DEBUG, "m_kick(): got kick for nonexistant user %s", parv[1]);
1017 return;
1018 }
1019
1020 if (!c)
1021 {
1022 slog (LG_DEBUG, "m_kick(): got kick in nonexistant channel: %s", parv[0]);
1023 return;
1024 }
1025
1026 if (!chanuser_find (c, u))
1027 {
1028 slog (LG_DEBUG, "m_kick(): got kick for %s not in %s", u->uid, c->name);
1029 return;
1030 }
1031
1032 chanuser_delete (c, u);
1033
1034 /* if they kicked us, let's rejoin */
1035 if (is_internal_client (u))
1036 {
1037 slog (LG_DEBUG, "m_kick(): %s got kicked from %s; rejoining", u->nick, parv[0]);
1038 join (parv[0], u->uid);
1039 }
1040 }
1041
1042 static void
1043 m_kill (sourceinfo_t *si, int parc, char *parv[])
1044 {
1045 handle_kill (si, parv[0], parc > 1 ? parv[1] : "<No reason given>");
1046 }
1047
1048 static void
1049 m_squit (sourceinfo_t *si, int parc, char *parv[])
1050 {
1051 slog (LG_DEBUG, "m_squit(): server leaving: %s", parv[0]);
1052 server_delete (parv[0]);
1053 }
1054
1055 static void
1056 m_server (sourceinfo_t *si, int parc, char *parv[])
1057 {
1058 server_t *s;
1059
1060 slog (LG_DEBUG, "m_server(): new server: %s", parv[0]);
1061 s = handle_server (si, parv[0], parv[3], atoi(parv[2]), parv[4]);
1062
1063 if (s != NULL && s->uplink != me.me)
1064 /* elicit PONG for EOB detection; pinging uplink is
1065 * already done elsewhere -- jilles
1066 */
1067 sts (":%s PING %s %s", me.numeric, me.numeric, s->name);
1068 }
1069
1070 static void
1071 m_stats (sourceinfo_t *si, int parc, char *parv[])
1072 {
1073 handle_stats (si->su, parv[0][0]);
1074 }
1075
1076 static void
1077 m_motd (sourceinfo_t *si, int parc, char *parv[])
1078 {
1079 handle_motd (si->su);
1080 }
1081
1082 static void
1083 m_admin (sourceinfo_t *si, int parc, char *parv[])
1084 {
1085 handle_admin (si->su);
1086 }
1087
1088 static void
1089 m_away (sourceinfo_t *si, int parc, char *parv[])
1090 {
1091 handle_away (si->su, parc >= 1 ? parv[0] : NULL);
1092 }
1093
1094 static void
1095 m_join (sourceinfo_t *si, int parc, char *parv[])
1096 {
1097 channel_t *c;
1098
1099 c = channel_find (parv[0]);
1100 if (!c)
1101 {
1102 slog (LG_DEBUG, "m_join(): new channel: %s (modes lost)", parv[0]);
1103 c = channel_add (parv[0], parc > 1 ? atol (parv[1]) : NOW, si->su->server);
1104 return_if_fail (c != NULL);
1105 channel_mode (NULL, c, "+");
1106 }
1107 chanuser_add (c, si->su->nick);
1108 }
1109
1110 static void
1111 m_sajoin (sourceinfo_t *si, int parc, char *parv[])
1112 {
1113 si->su = user_find (parv[0]);
1114 if (si->su == NULL)
1115 return;
1116 m_join (si, 1, &parv[1]);
1117 }
1118
1119 static void
1120 m_sapart (sourceinfo_t *si, int parc, char *parv[])
1121 {
1122 si->su = user_find (parv[0]);
1123 if (si->su == NULL)
1124 return;
1125 m_part (si, 1, &parv[1]);
1126 }
1127
1128 static void
1129 m_sanick (sourceinfo_t *si, int parc, char *parv[])
1130 {
1131 si->su = user_find (parv[0]);
1132 if (si->su == NULL)
1133 return;
1134 m_nick (si, 1, &parv[1]);
1135 }
1136
1137 static void
1138 m_samode (sourceinfo_t *si, int parc, char *parv[])
1139 {
1140 /* note that SAMODE is not checked in any way before propagation,
1141 * and only works on channels, not users */
1142 channel_mode (NULL, channel_find (parv[0]), parc - 1, &parv[1]);
1143 }
1144
1145 static void
1146 m_error (sourceinfo_t *si, int parc, char *parv[])
1147 {
1148 slog (LG_INFO, "m_error(): error from server: %s", parv[0]);
1149 }
1150
1151 static void
1152 m_idle (sourceinfo_t *si, int parc, char *parv[])
1153 {
1154 if (parc == 1 && si->su != NULL)
1155 {
1156 sts (":%s IDLE %s %ld 0", parv[0], si->su->nick, NOW);
1157 }
1158 else
1159 {
1160 slog (LG_INFO, "m_idle(): Received an IDLE response but we didn't WHOIS anybody!");
1161 }
1162 }
1163
1164 static void
1165 m_opertype (sourceinfo_t *si, int parc, char *parv[])
1166 {
1167 /*
1168 * Hope this works.. InspIRCd OPERTYPE means user is an oper, mark them so
1169 * Later, we may want to look at saving their OPERTYPE for informational
1170 * purposes, or not. --w00t
1171 */
1172 user_mode (si->su, "+o");
1173 }
1174
1175 static void
1176 m_fhost (sourceinfo_t *si, int parc, char *parv[])
1177 {
1178 strlcpy (si->su->vhost, parv[0], HOSTLEN);
1179 }
1180
1181 /*
1182 * :<source server> METADATA <channel|user> <key> :<value>
1183 * The sole piece of metadata we're interested in is 'accountname', set by Services,
1184 * and kept by ircd.
1185 *
1186 * :services.barafranca METADATA w00t accountname :w00t
1187 */
1188
1189 static void
1190 m_metadata (sourceinfo_t *si, int parc, char *parv[])
1191 {
1192 user_t *u;
1193
1194 if (!irccasecmp (parv[1], "accountname"))
1195 {
1196 /* find user */
1197 u = user_find (parv[0]);
1198
1199 if (u == NULL)
1200 return;
1201
1202 handle_burstlogin (u, parv[2]);
1203 }
1204 }
1205
1206 /*
1207 * rsquit:
1208 * remote/request squit
1209 * when squitting a remote server, inspircd sends RSQUIT along the tree until it reaches the server that has
1210 * the server to be squit as a local connection, which should then close it's connection and send SQUIT back
1211 * to the rest of the network.
1212 */
1213 static void
1214 m_rsquit (sourceinfo_t *si, int parc, char *parv[])
1215 {
1216 sts (":%s SQUIT %s :Jupe removed by %s", me.numeric, parv[0], si->su->nick);
1217 }
1218
1219 static void
1220 m_capab (sourceinfo_t *si, int parc, char *parv[])
1221 {
1222 int i, varc;
1223 char *varv[256];
1224
1225 if (strcasecmp (parv[0], "START") == 0)
1226 {
1227 /* reset all our previously recieved CAPAB stuff */
1228 has_servicesmod = false;
1229 has_globopsmod = false;
1230 has_svshold = false;
1231 has_protocol = 0;
1232 }
1233 else if (strcasecmp (parv[0], "CAPABILITIES") == 0 && parc > 1)
1234 {
1235 varc = sjtoken (parv[1], ' ', varv);
1236 for (i = 0; i < varc; i++)
1237 {
1238 if (!strncmp (varv[i], "PROTOCOL=", 9))
1239 has_protocol = atoi (varv[i] + 9);
1240 /* XXX check/store HALFOP/CHANMAX/IDENTMAX */
1241 }
1242 }
1243 else if (strcasecmp (parv[0], "MODULES") == 0 && parc > 1)
1244 {
1245 if (strstr (parv[1], "m_services_account.so"))
1246 has_servicesmod = true;
1247 if (strstr (parv[1], "m_globops.so"))
1248 has_globopsmod = true;
1249 if (strstr (parv[1], "m_svshold.so"))
1250 has_svshold = true;
1251 if (strstr (parv[1], "m_chanprotect.so"))
1252 {
1253 ircd->uses_owner = true;
1254 ircd->uses_protect = true;
1255 }
1256 }
1257 else if (strcasecmp (parv[0], "END") == 0)
1258 {
1259 if (has_globopsmod == false && has_protocol < PROTOCOL_SNONOTICE)
1260 {
1261 slog (LG_ERROR, "m_capab(): you didn't load m_globops into inspircd. " PACKAGE_NAME " support requires this module. exiting.");
1262 exit (EXIT_FAILURE);
1263 }
1264
1265 if (has_servicesmod == false)
1266 {
1267 slog (LG_ERROR, "m_capab(): you didn't load m_services_account into inspircd. " PACKAGE_NAME " support requires this module. exiting.");
1268 exit (EXIT_FAILURE);
1269 }
1270
1271 if (has_svshold == false)
1272 {
1273 slog (LG_INFO, "m_capab(): you didn't load m_svshold into inspircd. nickname enforcers will not work.");
1274 }
1275 }
1276 else
1277 {
1278 slog (LG_DEBUG, "m_capab(): unknown CAPAB type %s - out of date protocol module?", parv[0]);
1279 }
1280 }
1281
1282 /* Server ended their burst: warn all their users if necessary -- jilles */
1283 static void
1284 server_eob (server_t *s)
1285 {
1286 node_t *n;
1287
1288 LIST_FOREACH (n, s->userlist.head)
1289 {
1290 handle_nickchange ((user_t *) n->data);
1291 }
1292 }
1293
1294 static pcommand_t const pcommands[] = {
1295 { "PING", m_ping, 1, MSRC_USER | MSRC_SERVER },
1296 { "PONG", m_pong, 1, MSRC_SERVER },
1297 { "PRIVMSG", m_privmsg, 2, MSRC_USER },
1298 { "NOTICE", m_notice, 2, MSRC_USER | MSRC_SERVER },
1299 { "FJOIN", m_fjoin, 3, MSRC_SERVER },
1300 { "PART", m_part, 1, MSRC_USER },
1301 { "NICK", m_nick, 1, MSRC_USER | MSRC_SERVER },
1302 { "UID", m_uid, 9, MSRC_USER | MSRC_SERVER },
1303 { "QUIT", m_quit, 1, MSRC_USER },
1304 { "MODE", m_mode, 2, MSRC_USER | MSRC_SERVER },
1305 { "FMODE", m_fmode, 3, MSRC_USER | MSRC_SERVER },
1306 { "SAMODE", m_samode, 2, MSRC_USER },
1307 { "SAJOIN", m_sajoin, 2, MSRC_USER },
1308 { "SAPART", m_sapart, 2, MSRC_USER },
1309 { "SANICK", m_sanick, 2, MSRC_USER },
1310 { "SAQUIT", m_saquit, 1, MSRC_USER },
1311 { "KICK", m_kick, 2, MSRC_USER | MSRC_SERVER },
1312 { "KILL", m_kill, 1, MSRC_USER | MSRC_SERVER },
1313 { "SQUIT", m_squit, 1, MSRC_USER | MSRC_SERVER },
1314 { "RSQUIT", m_rsquit, 1, MSRC_USER },
1315 { "SERVER", m_server, 4, MSRC_UNREG | MSRC_SERVER },
1316 { "STATS", m_stats, 2, MSRC_USER },
1317 { "MOTD", m_motd, 1, MSRC_USER },
1318 { "ADMIN", m_admin, 1, MSRC_USER },
1319 { "FTOPIC", m_ftopic, 4, MSRC_SERVER },
1320 { "JOIN", m_join, 1, MSRC_USER },
1321 { "ERROR", m_error, 1, MSRC_UNREG | MSRC_SERVER },
1322 { "TOPIC", m_topic, 2, MSRC_USER },
1323 { "FHOST", m_fhost, 1, MSRC_USER },
1324 { "IDLE", m_idle, 1, MSRC_USER },
1325 { "AWAY", m_away, 0, MSRC_USER },
1326 { "OPERTYPE", m_opertype, 1, MSRC_USER },
1327 { "METADATA", m_metadata, 3, MSRC_SERVER },
1328 { "CAPAB", m_capab, 1, MSRC_UNREG | MSRC_SERVER },
1329 };
1330
1331 inspircd12_handler::inspircd12_handler ()
1332 {
1333 mode_list = inspircd12_mode_list;
1334 ignore_mode_list = inspircd12_ignore_mode_list;
1335 status_mode_list = inspircd12_status_mode_list;
1336 prefix_mode_list = inspircd12_prefix_mode_list;
1337
1338 ircd = &InspIRCd;
1339
1340 pcommand_add (&pcommands);
1341
1342 server_t::callback.eob.attach (server_eob);
1343 }
1344
1345 inspircd12_handler::~inspircd12_handler ()
1346 {
1347 server_t::callback.eob.detach (server_eob);
1348
1349 pcommand_delete (&pcommands);
1350
1351 ircd = NULL;
1352
1353 prefix_mode_list = NULL;
1354 status_mode_list = NULL;
1355 ignore_mode_list = NULL;
1356 mode_list = NULL;
1357 }
1358 } // namespace protocol
1359
1360 #define FACREG_TYPE protocol::inspircd12_handler
1361 #define FACREG_TYPE_NAME "inspircd12"
1362 #define FACREG_INTERFACE_TYPE protocol::handler
1363 #include <ermyth/factory_reg.h>