ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/protocol/sorcery.C
Revision: 1.2
Committed: Sat Jul 21 01:29:10 2007 UTC (19 years, 1 month ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.1: +2 -2 lines
Log Message:
- moved to new documentation system
- fixed small build error

File Contents

# User Rev Content
1 pippijn 1.1 /*
2     * Copyright © 2003-2004 E. Will et al.
3     * Copyright © 2005-2006 Atheme Development Group
4 pippijn 1.2 * Rights to this code are documented in doc/pod/license.pod.
5 pippijn 1.1 *
6     * This file contains protocol support for bahamut-based ircd.
7     *
8 pippijn 1.2 * $Id: sorcery.C,v 1.1 2007-07-19 08:24:55 pippijn Exp $
9 pippijn 1.1 */
10    
11     #include "atheme.h"
12     #include "uplink.h"
13     #include "pmodule.h"
14     #include "protocol/sorcery.h"
15    
16     static char const rcsid[] = "$Id";
17    
18     struct sorcery_listeners : callback::has_listeners
19     {
20     };
21    
22     sorcery_listeners listeners;
23    
24     DECLARE_MODULE_V1 ("protocol/sorcery", true, _modinit, NULL, rcsid, "The Ermyth Team <http://ermyth.schmorp.de>");
25    
26     /* *INDENT-OFF* */
27    
28     ircd_t Sorcery = {
29     "SorceryNet IRCd 1.3.1+", /* IRCd name */
30     "$", /* TLD Prefix, used by Global. */
31     false, /* Whether or not we use IRCNet/TS6 UID */
32     false, /* Whether or not we use RCOMMAND */
33     false, /* Whether or not we support channel owners. */
34     false, /* Whether or not we support channel protection. */
35     false, /* Whether or not we support halfops. */
36     false, /* Whether or not we use P10 */
37     false, /* Whether or not we use vHosts. */
38     CMODE_OPERONLY, /* Oper-only cmodes */
39     0, /* Integer flag for owner channel flag. */
40     0, /* Integer flag for protect channel flag. */
41     0, /* Integer flag for halfops. */
42     "+", /* Mode we set for owner. */
43     "+", /* Mode we set for protect. */
44     "+", /* Mode we set for halfops. */
45     PROTOCOL_SORCERY, /* Protocol type */
46     0, /* Permanent cmodes */
47     "b", /* Ban-like cmodes */
48     0, /* Except mchar */
49     0, /* Invex mchar */
50     0 /* Flags */
51     };
52    
53     cmode_t sorcery_mode_list[] = {
54     { 'i', CMODE_INVITE },
55     { 'm', CMODE_MOD },
56     { 'n', CMODE_NOEXT },
57     { 'p', CMODE_PRIV },
58     { 's', CMODE_SEC },
59     { 't', CMODE_TOPIC },
60     { 'c', CMODE_NOCOLOR },
61     { 'R', CMODE_REGONLY },
62     { 'O', CMODE_OPERONLY },
63     { '\0', 0 }
64     };
65    
66     extmode sorcery_ignore_mode_list[] = {
67     { '\0', 0 }
68     };
69    
70     cmode_t sorcery_status_mode_list[] = {
71     { 'o', CMODE_OP },
72     { 'v', CMODE_VOICE },
73     { '\0', 0 }
74     };
75    
76     cmode_t sorcery_prefix_mode_list[] = {
77     { '@', CMODE_OP },
78     { '+', CMODE_VOICE },
79     { '\0', 0 }
80     };
81    
82     /* *INDENT-ON* */
83    
84     /* login to our uplink */
85     static unsigned int
86     sorcery_server_login (void)
87     {
88     int ret;
89    
90     ret = sts ("PASS %s", curr_uplink->pass);
91     if (ret == 1)
92     return 1;
93    
94     me.bursting = true;
95    
96     sts ("PROTOCTL NOQUIT WATCH=128 SAFELIST");
97     sts ("SERVER %s 1 :%s", me.name, me.desc);
98    
99     services_init ();
100    
101     return 0;
102     }
103    
104     /* introduce a client */
105     static void
106     sorcery_introduce_nick (user_t *u)
107     {
108     sts ("NICK %s 1 %ld %s %s %s :%s", u->nick, u->ts, u->user, u->host, me.name, u->gecos);
109     sts (":%s MODE %s +%s", u->nick, u->nick, "io");
110     }
111    
112     /* invite a user to a channel */
113     static void
114     sorcery_invite_sts (user_t *sender, user_t *target, channel_t *channel)
115     {
116     sts (":%s INVITE %s %s", sender->nick, target->nick, channel->name);
117     }
118    
119     static void
120     sorcery_quit_sts (user_t *u, const char *reason)
121     {
122     if (!me.connected)
123     return;
124    
125     sts (":%s QUIT :%s", u->nick, reason);
126     }
127    
128     /* WALLOPS wrapper */
129     static void
130     sorcery_wallops_sts (const char *text)
131     {
132     sts (":%s GLOBOPS :%s", me.name, text);
133     }
134    
135     /* join a channel */
136     static void
137     sorcery_join_sts (channel_t *c, user_t *u, bool isnew, char *modes)
138     {
139     if (isnew)
140     {
141     sts (":%s JOIN %s", u->nick, c->name);
142     if (modes[0] && modes[1])
143     sts (":%s MODE %s %s", u->nick, c->name, modes);
144     }
145     else
146     {
147     sts (":%s JOIN %s", u->nick, c->name);
148     }
149     sts (":%s MODE %s +o %s %ld", u->nick, c->name, u->nick, c->ts);
150     }
151    
152     /* kicks a user from a channel */
153     static void
154     sorcery_kick (char *from, char *channel, char *to, char *reason)
155     {
156     channel_t *chan = channel_find (channel);
157     user_t *user = user_find (to);
158    
159     if (!chan || !user)
160     return;
161    
162     sts (":%s KICK %s %s :%s", from, channel, to, reason);
163    
164     chanuser_delete (chan, user);
165     }
166    
167     /* PRIVMSG wrapper */
168     static void
169     sorcery_msg (const char *from, const char *target, const char *fmt, ...)
170     {
171     va_list ap;
172     char buf[BUFSIZE];
173    
174     va_start (ap, fmt);
175     vsnprintf (buf, BUFSIZE, fmt, ap);
176     va_end (ap);
177    
178     sts (":%s PRIVMSG %s :%s", from, target, buf);
179     }
180    
181     /* NOTICE wrapper */
182     static void
183     sorcery_notice_user_sts (user_t *from, user_t *target, const char *text)
184     {
185     sts (":%s NOTICE %s :%s", from ? from->nick : me.name, target->nick, text);
186     }
187    
188     static void
189     sorcery_notice_global_sts (user_t *from, const char *mask, const char *text)
190     {
191     node_t *n;
192     tld_t *tld;
193    
194     if (!strcmp (mask, "*"))
195     {
196     LIST_FOREACH (n, tldlist.head)
197     {
198     tld = static_cast<tld_t *> (n->data);
199     sts (":%s NOTICE %s*%s :%s", from ? from->nick : me.name, ircd->tldprefix, tld->name, text);
200     }
201     }
202     else
203     sts (":%s NOTICE %s%s :%s", from ? from->nick : me.name, ircd->tldprefix, mask, text);
204     }
205    
206     static void
207     sorcery_notice_channel_sts (user_t *from, channel_t *target, const char *text)
208     {
209     sts (":%s NOTICE %s :%s", from ? from->nick : me.name, target->name, text);
210     }
211    
212     static void
213     sorcery_numeric_sts (char *from, int numeric, char *target, char *fmt, ...)
214     {
215     va_list ap;
216     char buf[BUFSIZE];
217    
218     va_start (ap, fmt);
219     vsnprintf (buf, BUFSIZE, fmt, ap);
220     va_end (ap);
221    
222     sts (":%s %d %s %s", from, numeric, target, buf);
223     }
224    
225     /* KILL wrapper */
226     static void
227     sorcery_skill (char *from, char *nick, char *fmt, ...)
228     {
229     va_list ap;
230     char buf[BUFSIZE];
231    
232     va_start (ap, fmt);
233     vsnprintf (buf, BUFSIZE, fmt, ap);
234     va_end (ap);
235    
236     sts (":%s KILL %s :%s!%s!%s (%s)", from, nick, from, from, from, buf);
237     }
238    
239     /* PART wrapper */
240     static void
241     sorcery_part_sts (channel_t *c, user_t *u)
242     {
243     sts (":%s PART %s", u->nick, c->name);
244     }
245    
246     /* server-to-server KLINE wrapper */
247     static void
248     sorcery_kline_sts (char *server, char *user, char *host, long duration, char *reason)
249     {
250     if (!me.connected)
251     return;
252    
253     sts (":%s AKILL %s %s %ld %s %ld :%s", me.name, host, user, duration, opersvs.nick, time (NULL), reason);
254     }
255    
256     /* server-to-server UNKLINE wrapper */
257     static void
258     sorcery_unkline_sts (char *server, char *user, char *host)
259     {
260     if (!me.connected)
261     return;
262    
263     sts (":%s RAKILL %s %s", me.name, host, user);
264     }
265    
266     /* topic wrapper */
267     static void
268     sorcery_topic_sts (channel_t *c, const char *setter, time_t ts, time_t prevts, const char *topic)
269     {
270     if (!me.connected)
271     return;
272    
273     if (ts < prevts || prevts == 0)
274     sts (":%s TOPIC %s %s %ld :%s", chansvs.nick, c->name, setter, ts, topic);
275     else if (prevts > 1)
276     {
277     ts = prevts - 1;
278     sts (":%s TOPIC %s %s %ld :%s", chansvs.nick, c->name, "topictime.wrong", ts, topic);
279     c->topicts = ts;
280     }
281     else
282     {
283     notice (chansvs.nick, c->name, "Unable to change topic to: %s", topic);
284     c->topicts = 1;
285     }
286     }
287    
288     /* mode wrapper */
289     static void
290     sorcery_mode_sts (char *sender, channel_t *target, char *modes)
291     {
292     if (!me.connected)
293     return;
294    
295     sts (":%s MODE %s %s", sender, target->name, modes);
296     }
297    
298     /* ping wrapper */
299     static void
300     sorcery_ping_sts (void)
301     {
302     if (!me.connected)
303     return;
304    
305     sts ("PING :%s", me.name);
306     }
307    
308     /* protocol-specific stuff to do on login */
309     static void
310     sorcery_on_login (char *origin, char *user, char *wantedhost)
311     {
312     if (!me.connected)
313     return;
314    
315     /* nothing to do here */
316     }
317    
318     /* protocol-specific stuff to do on login */
319     static bool
320     sorcery_on_logout (char *origin, char *user, char *wantedhost)
321     {
322     if (!me.connected)
323     return false;
324    
325     /* nothing to do here */
326     return false;
327     }
328    
329     static void
330     sorcery_jupe (const char *server, const char *reason)
331     {
332     if (!me.connected)
333     return;
334    
335     server_delete (server);
336     sts (":%s SQUIT %s :%s", opersvs.nick, server, reason);
337     sts (":%s SERVER %s 2 :%s", me.name, server, reason);
338     }
339    
340     static void
341     m_topic (sourceinfo_t *si, int parc, char *parv[])
342     {
343     channel_t *c = channel_find (parv[0]);
344    
345     if (!c)
346     return;
347    
348     handle_topic_from (si, c, parv[1], atol (parv[2]), parv[3]);
349     }
350    
351     static void
352     m_ping (sourceinfo_t *si, int parc, char *parv[])
353     {
354     /* reply to PING's */
355     sts (":%s PONG %s %s", me.name, me.name, parv[0]);
356     }
357    
358     static void
359     m_pong (sourceinfo_t *si, int parc, char *parv[])
360     {
361     server_t *s;
362    
363     /* someone replied to our PING */
364     if (!parv[0])
365     return;
366     s = server_find (parv[0]);
367     if (s == NULL)
368     return;
369     handle_eob (s);
370    
371     if (irccasecmp (me.actual, parv[0]))
372     return;
373    
374     me.uplinkpong = NOW;
375    
376     /* -> :test.projectxero.net PONG test.projectxero.net :shrike.malkier.net */
377     if (me.bursting)
378     {
379     #ifdef HAVE_GETTIMEOFDAY
380     e_time (burstime, &burstime);
381    
382     slog (LG_INFO, "m_pong(): finished synching with uplink (%d %s)", (tv2ms (&burstime) > 1000) ? (tv2ms (&burstime) / 1000) : tv2ms (&burstime), (tv2ms (&burstime) > 1000) ? "s" : "ms");
383    
384     wallops ("Finished synching to network in %d %s.", (tv2ms (&burstime) > 1000) ? (tv2ms (&burstime) / 1000) : tv2ms (&burstime), (tv2ms (&burstime) > 1000) ? "s" : "ms");
385     #else
386     slog (LG_INFO, "m_pong(): finished synching with uplink");
387     wallops ("Finished synching to network.");
388     #endif
389    
390     me.bursting = false;
391     }
392     }
393    
394     static void
395     m_privmsg (sourceinfo_t *si, int parc, char *parv[])
396     {
397     if (parc != 2)
398     return;
399    
400     handle_message (si, parv[0], false, parv[1]);
401     }
402    
403     static void
404     m_notice (sourceinfo_t *si, int parc, char *parv[])
405     {
406     if (parc != 2)
407     return;
408    
409     handle_message (si, parv[0], true, parv[1]);
410     }
411    
412     static void
413     m_part (sourceinfo_t *si, int parc, char *parv[])
414     {
415     int chanc;
416     char *chanv[256];
417     int i;
418    
419     chanc = sjtoken (parv[0], ',', chanv);
420     for (i = 0; i < chanc; i++)
421     {
422     slog (LG_DEBUG, "m_part(): user left channel: %s -> %s", si->su->nick, chanv[i]);
423    
424     chanuser_delete (channel_find (chanv[i]), si->su);
425     }
426     }
427    
428     static void
429     m_nick (sourceinfo_t *si, int parc, char *parv[])
430     {
431     user_t *u;
432     server_t *s;
433    
434     if (parc == 7)
435     {
436     s = server_find (parv[5]);
437     if (!s)
438     {
439     slog (LG_DEBUG, "m_nick(): new user on nonexistant server: %s", parv[5]);
440     return;
441     }
442    
443     slog (LG_DEBUG, "m_nick(): new user on `%s': %s", s->name, parv[0]);
444    
445     u = user_add (parv[0], parv[3], parv[4], NULL, NULL, NULL, parv[6], s, atoi (parv[2]));
446    
447     handle_nickchange (u);
448     }
449    
450     /* if it's only 2 then it's a nickname change */
451     else if (parc == 2)
452     {
453     if (!si->su)
454     {
455     slog (LG_DEBUG, "m_nick(): server trying to change nick: %s", si->s != NULL ? si->s->name : "<none>");
456     return;
457     }
458    
459     slog (LG_DEBUG, "m_nick(): nickname change from `%s': %s", si->su->nick, parv[0]);
460    
461     user_changenick (si->su, parv[0], atoi (parv[1]));
462    
463     handle_nickchange (si->su);
464     }
465     else
466     {
467     int i;
468     slog (LG_DEBUG, "m_nick(): got NICK with wrong number of params");
469    
470     for (i = 0; i < parc; i++)
471     slog (LG_DEBUG, "m_nick(): parv[%d] = %s", i, parv[i]);
472     }
473     }
474    
475     static void
476     m_quit (sourceinfo_t *si, int parc, char *parv[])
477     {
478     slog (LG_DEBUG, "m_quit(): user leaving: %s", si->su->nick);
479    
480     /* user_delete() takes care of removing channels and so forth */
481     user_delete (si->su);
482     }
483    
484     static void
485     m_mode (sourceinfo_t *si, int parc, char *parv[])
486     {
487     if (*parv[0] == '#')
488     channel_mode (NULL, channel_find (parv[0]), parc - 1, &parv[1]);
489     else
490     user_mode (user_find (parv[0]), parv[1]);
491     }
492    
493     static void
494     m_kick (sourceinfo_t *si, int parc, char *parv[])
495     {
496     user_t *u = user_find (parv[1]);
497     channel_t *c = channel_find (parv[0]);
498    
499     /* -> :rakaur KICK #shrike rintaun :test */
500     slog (LG_DEBUG, "m_kick(): user was kicked: %s -> %s", parv[1], parv[0]);
501    
502     if (!u)
503     {
504     slog (LG_DEBUG, "m_kick(): got kick for nonexistant user %s", parv[1]);
505     return;
506     }
507    
508     if (!c)
509     {
510     slog (LG_DEBUG, "m_kick(): got kick in nonexistant channel: %s", parv[0]);
511     return;
512     }
513    
514     if (!chanuser_find (c, u))
515     {
516     slog (LG_DEBUG, "m_kick(): got kick for %s not in %s", u->nick, c->name);
517     return;
518     }
519    
520     chanuser_delete (c, u);
521    
522     /* if they kicked us, let's rejoin */
523     if (is_internal_client (u))
524     {
525     slog (LG_DEBUG, "m_kick(): %s got kicked from %s; rejoining", u->nick, parv[0]);
526     join (parv[0], u->nick);
527     }
528     }
529    
530     static void
531     m_kill (sourceinfo_t *si, int parc, char *parv[])
532     {
533     handle_kill (si, parv[0], parc > 1 ? parv[1] : "<No reason given>");
534     }
535    
536     static void
537     m_squit (sourceinfo_t *si, int parc, char *parv[])
538     {
539     slog (LG_DEBUG, "m_squit(): server leaving: %s from %s", parv[0], parv[1]);
540     server_delete (parv[0]);
541     }
542    
543     static void
544     m_server (sourceinfo_t *si, int parc, char *parv[])
545     {
546     server_t *s;
547    
548     slog (LG_DEBUG, "m_server(): new server: %s", parv[0]);
549     s = handle_server (si, parv[0], NULL, atoi (parv[1]), parv[2]);
550    
551     if (s != NULL && s->uplink != me.me)
552     {
553     /* elicit PONG for EOB detection; pinging uplink is
554     * already done elsewhere -- jilles
555     */
556     sts (":%s PING %s %s", me.name, me.name, s->name);
557     }
558     }
559    
560     static void
561     m_stats (sourceinfo_t *si, int parc, char *parv[])
562     {
563     handle_stats (si->su, parv[0][0]);
564     }
565    
566     static void
567     m_admin (sourceinfo_t *si, int parc, char *parv[])
568     {
569     handle_admin (si->su);
570     }
571    
572     static void
573     m_version (sourceinfo_t *si, int parc, char *parv[])
574     {
575     handle_version (si->su);
576     }
577    
578     static void
579     m_info (sourceinfo_t *si, int parc, char *parv[])
580     {
581     handle_info (si->su);
582     }
583    
584     static void
585     m_whois (sourceinfo_t *si, int parc, char *parv[])
586     {
587     handle_whois (si->su, parv[1]);
588     }
589    
590     static void
591     m_trace (sourceinfo_t *si, int parc, char *parv[])
592     {
593     handle_trace (si->su, parv[0], parc >= 2 ? parv[1] : NULL);
594     }
595    
596     static void
597     m_away (sourceinfo_t *si, int parc, char *parv[])
598     {
599     handle_away (si->su, parc >= 1 ? parv[0] : NULL);
600     }
601    
602     static void
603     m_join (sourceinfo_t *si, int parc, char *parv[])
604     {
605     chanuser_t *cu;
606     node_t *n, *tn;
607     int chanc;
608     char *chanv[256];
609     int i;
610    
611     /* JOIN 0 is really a part from all channels */
612     if (parv[0][0] == '0')
613     {
614     LIST_FOREACH_SAFE (n, tn, si->su->channels.head)
615     {
616     cu = (chanuser_t *) n->data;
617     chanuser_delete (cu->chan, si->su);
618     }
619     }
620     else
621     {
622     chanc = sjtoken (parv[0], ',', chanv);
623     for (i = 0; i < chanc; i++)
624     {
625     channel_t *c = channel_find (chanv[i]);
626    
627     if (!c)
628     {
629     slog (LG_DEBUG, "m_join(): new channel: %s", parv[0]);
630     c = channel_add (chanv[i], NOW, si->su->server);
631     /* Tell the core to check mode locks now,
632     * otherwise it may only happen after the next
633     * mode change.
634     * DreamForge does not allow any redundant modes
635     * so this will not look ugly. -- jilles */
636     /* If this is in a burst, a MODE with the
637     * simple modes will follow so we can skip
638     * this. -- jilles */
639     if (!me.bursting)
640     channel_mode_va (NULL, c, 1, "+");
641     }
642     chanuser_add (c, si->su->nick);
643     }
644     }
645     }
646    
647     static void
648     m_pass (sourceinfo_t *si, int parc, char *parv[])
649     {
650     if (strcmp (curr_uplink->pass, parv[0]))
651     {
652     slog (LG_INFO, "m_pass(): password mismatch from uplink; aborting");
653     runflags |= RF_SHUTDOWN;
654     }
655     }
656    
657     static void
658     m_error (sourceinfo_t *si, int parc, char *parv[])
659     {
660     slog (LG_INFO, "m_error(): error from server: %s", parv[0]);
661     }
662    
663     void
664     _modinit (module_t *m)
665     {
666     /* Symbol relocation voodoo. */
667     server_login = &sorcery_server_login;
668     introduce_nick = &sorcery_introduce_nick;
669     quit_sts = &sorcery_quit_sts;
670     wallops_sts = &sorcery_wallops_sts;
671     join_sts = &sorcery_join_sts;
672     kick = &sorcery_kick;
673     msg = &sorcery_msg;
674     notice_user_sts = &sorcery_notice_user_sts;
675     notice_global_sts = &sorcery_notice_global_sts;
676     notice_channel_sts = &sorcery_notice_channel_sts;
677     numeric_sts = &sorcery_numeric_sts;
678     skill = &sorcery_skill;
679     part_sts = &sorcery_part_sts;
680     kline_sts = &sorcery_kline_sts;
681     unkline_sts = &sorcery_unkline_sts;
682     topic_sts = &sorcery_topic_sts;
683     mode_sts = &sorcery_mode_sts;
684     ping_sts = &sorcery_ping_sts;
685     ircd_on_login = &sorcery_on_login;
686     ircd_on_logout = &sorcery_on_logout;
687     jupe = &sorcery_jupe;
688     invite_sts = &sorcery_invite_sts;
689    
690     mode_list = sorcery_mode_list;
691     ignore_mode_list = sorcery_ignore_mode_list;
692     status_mode_list = sorcery_status_mode_list;
693     prefix_mode_list = sorcery_prefix_mode_list;
694    
695     ircd = &Sorcery;
696    
697     pcommand_add ("PING", m_ping, 1, MSRC_USER | MSRC_SERVER);
698     pcommand_add ("PONG", m_pong, 1, MSRC_SERVER);
699     pcommand_add ("PRIVMSG", m_privmsg, 2, MSRC_USER);
700     pcommand_add ("NOTICE", m_notice, 2, MSRC_UNREG | MSRC_USER | MSRC_SERVER);
701     pcommand_add ("PART", m_part, 1, MSRC_USER);
702     pcommand_add ("NICK", m_nick, 2, MSRC_USER | MSRC_SERVER);
703     pcommand_add ("QUIT", m_quit, 1, MSRC_USER);
704     pcommand_add ("MODE", m_mode, 2, MSRC_USER | MSRC_SERVER);
705     pcommand_add ("KICK", m_kick, 2, MSRC_USER | MSRC_SERVER);
706     pcommand_add ("KILL", m_kill, 1, MSRC_USER | MSRC_SERVER);
707     pcommand_add ("SQUIT", m_squit, 1, MSRC_USER | MSRC_SERVER);
708     pcommand_add ("SERVER", m_server, 3, MSRC_UNREG | MSRC_SERVER);
709     pcommand_add ("STATS", m_stats, 2, MSRC_USER);
710     pcommand_add ("ADMIN", m_admin, 1, MSRC_USER);
711     pcommand_add ("VERSION", m_version, 1, MSRC_USER);
712     pcommand_add ("INFO", m_info, 1, MSRC_USER);
713     pcommand_add ("WHOIS", m_whois, 2, MSRC_USER);
714     pcommand_add ("TRACE", m_trace, 1, MSRC_USER);
715     pcommand_add ("AWAY", m_away, 0, MSRC_USER);
716     pcommand_add ("JOIN", m_join, 1, MSRC_USER);
717     pcommand_add ("PASS", m_pass, 1, MSRC_UNREG);
718     pcommand_add ("ERROR", m_error, 1, MSRC_UNREG | MSRC_SERVER);
719     pcommand_add ("TOPIC", m_topic, 4, MSRC_USER | MSRC_SERVER);
720    
721     m->mflags = MODTYPE_CORE;
722    
723     pmodule_loaded = true;
724     }
725    
726     /* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
727     * vim:ts=8
728     * vim:sw=8
729     * vim:noexpandtab
730     */