ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/ircd-hybrid/branches/8.2.x/modules/core/m_join.c
Revision: 1011
Committed: Fri Sep 18 10:14:09 2009 UTC (14 years, 7 months ago) by michael
Content type: text/x-csrc
Original Path: ircd-hybrid-7.2/modules/core/m_join.c
File size: 18642 byte(s)
Log Message:
- move list manipulation routines from tools.c to list.c
- mem_frob() goes to memory.c
- sort out redundant/unneeded header includes

File Contents

# User Rev Content
1 adx 30 /*
2     * ircd-hybrid: an advanced Internet Relay Chat Daemon(ircd).
3     * m_join.c: Joins a channel.
4     *
5     * Copyright (C) 2002 by the past and present ircd coders, and others.
6     *
7     * This program is free software; you can redistribute it and/or modify
8     * it under the terms of the GNU General Public License as published by
9     * the Free Software Foundation; either version 2 of the License, or
10     * (at your option) any later version.
11     *
12     * This program is distributed in the hope that it will be useful,
13     * but WITHOUT ANY WARRANTY; without even the implied warranty of
14     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15     * GNU General Public License for more details.
16     *
17     * You should have received a copy of the GNU General Public License
18     * along with this program; if not, write to the Free Software
19     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20     * USA
21     *
22 knight 31 * $Id$
23 adx 30 */
24    
25     #include "stdinc.h"
26 michael 1011 #include "list.h"
27 adx 30 #include "handlers.h"
28     #include "channel.h"
29     #include "channel_mode.h"
30     #include "client.h"
31     #include "hash.h"
32     #include "irc_string.h"
33     #include "sprintf_irc.h"
34     #include "ircd.h"
35     #include "numeric.h"
36     #include "send.h"
37     #include "s_serv.h"
38     #include "s_conf.h"
39     #include "msg.h"
40     #include "parse.h"
41     #include "modules.h"
42    
43    
44 michael 894 static void m_join(struct Client *, struct Client *, int, char *[]);
45     static void ms_join(struct Client *, struct Client *, int, char *[]);
46     static void do_join_0(struct Client *, struct Client *);
47 adx 30
48     static void set_final_mode(struct Mode *, struct Mode *);
49     static void remove_our_modes(struct Channel *, struct Client *);
50     static void remove_a_mode(struct Channel *, struct Client *, int, char);
51    
52     static char modebuf[MODEBUFLEN];
53     static char parabuf[MODEBUFLEN];
54     static char sendbuf[MODEBUFLEN];
55     static char *mbuf;
56    
57     struct Message join_msgtab = {
58     "JOIN", 0, 0, 2, 0, MFLG_SLOW, 0,
59 michael 894 { m_unregistered, m_join, ms_join, m_ignore, m_join, m_ignore }
60 adx 30 };
61    
62     #ifndef STATIC_MODULES
63     void
64     _modinit(void)
65     {
66     mod_add_cmd(&join_msgtab);
67     }
68    
69     void
70     _moddeinit(void)
71     {
72     mod_del_cmd(&join_msgtab);
73     }
74    
75 knight 31 const char *_version = "$Revision$";
76 adx 30 #endif
77    
78 michael 488 /* last0() stolen from ircu */
79     static char *
80     last0(struct Client *client_p, struct Client *source_p, char *chanlist)
81     {
82     char *p;
83     int join0 = 0;
84    
85     for (p = chanlist; *p; ++p) /* find last "JOIN 0" */
86     {
87     if (*p == '0' && (*(p + 1) == ',' || *(p + 1) == '\0'))
88     {
89     if ((*p + 1) == ',')
90     ++p;
91    
92     chanlist = p + 1;
93     join0 = 1;
94     }
95     else
96     {
97     while (*p != ',' && *p != '\0') /* skip past channel name */
98     ++p;
99    
100     if (*p == '\0') /* hit the end */
101     break;
102     }
103     }
104    
105     if (join0)
106     do_join_0(client_p, source_p);
107    
108     return chanlist;
109     }
110    
111 adx 30 /* m_join()
112     * parv[0] = sender prefix
113     * parv[1] = channel
114     * parv[2] = channel password (key)
115     */
116     static void
117     m_join(struct Client *client_p, struct Client *source_p,
118     int parc, char *parv[])
119     {
120 michael 488 char *p = NULL;
121     char *key_list = NULL;
122     char *chan_list = NULL;
123     char *chan = NULL;
124 adx 30 struct Channel *chptr = NULL;
125 michael 488 int i = 0;
126 adx 30 unsigned int flags = 0;
127    
128 michael 632 if (EmptyString(parv[1]))
129 adx 30 {
130     sendto_one(source_p, form_str(ERR_NEEDMOREPARAMS),
131     me.name, source_p->name, "JOIN");
132     return;
133     }
134    
135 michael 487 assert(client_p == source_p);
136 adx 30
137 michael 488 key_list = parv[2];
138     chan_list = last0(client_p, source_p, parv[1]);
139 michael 487
140 michael 488 for (chan = strtoken(&p, chan_list, ","); chan;
141     chan = strtoken(&p, NULL, ","))
142 adx 30 {
143 michael 488 char *key = NULL;
144 adx 30
145 michael 488 /* If we have any more keys, take the first for this channel. */
146     if (!EmptyString(key_list) && (key_list = strchr(key = key_list, ',')))
147     *key_list++ = '\0';
148    
149     /* Empty keys are the same as no keys. */
150     if (key && *key == '\0')
151     key = NULL;
152    
153 michael 632 if (!check_channel_name(chan, 1))
154 michael 488 {
155     sendto_one(source_p, form_str(ERR_BADCHANNAME),
156     me.name, source_p->name, chan);
157 adx 30 continue;
158 michael 488 }
159 adx 30
160 michael 488 if (ConfigChannel.disable_local_channels && (*chan == '&'))
161     {
162     sendto_one(source_p, form_str(ERR_NOSUCHCHANNEL),
163     me.name, source_p->name, chan);
164     continue;
165     }
166    
167     if (!IsExemptResv(source_p) &&
168     !(IsOper(source_p) && ConfigFileEntry.oper_pass_resv) &&
169     (!hash_find_resv(chan) == ConfigChannel.restrict_channels))
170     {
171     sendto_one(source_p, form_str(ERR_BADCHANNAME),
172     me.name, source_p->name, chan);
173     sendto_realops_flags(UMODE_SPY, L_ALL,
174     "User %s (%s@%s) is attempting to join locally juped channel %s",
175     source_p->name, source_p->username, source_p->host, chan);
176     continue;
177     }
178    
179     if ((dlink_list_length(&source_p->channel) >= ConfigChannel.max_chans_per_user) &&
180     (!IsOper(source_p) || (dlink_list_length(&source_p->channel) >=
181     ConfigChannel.max_chans_per_user * 3)))
182     {
183 michael 494 sendto_one(source_p, form_str(ERR_TOOMANYCHANNELS),
184     me.name, source_p->name, chan);
185     break;
186 michael 488 }
187    
188     if ((chptr = hash_find_channel(chan)) != NULL)
189     {
190     if (IsMember(source_p, chptr))
191     continue;
192    
193     if (splitmode && !IsOper(source_p) && (*chan != '&') &&
194     ConfigChannel.no_join_on_split)
195     {
196     sendto_one(source_p, form_str(ERR_UNAVAILRESOURCE),
197     me.name, source_p->name, chan);
198     continue;
199     }
200    
201     /*
202 michael 894 * can_join checks for +i key, bans.
203     */
204     if ((i = can_join(source_p, chptr, key)))
205     {
206     sendto_one(source_p, form_str(i), me.name,
207     source_p->name, chptr->chname);
208     continue;
209     }
210    
211     /*
212 michael 488 * This should never be the case unless there is some sort of
213     * persistant channels.
214     */
215     if (dlink_list_length(&chptr->members) == 0)
216     flags = CHFL_CHANOP;
217     else
218     flags = 0;
219     }
220     else
221     {
222     if (splitmode && !IsOper(source_p) && (*chan != '&') &&
223     (ConfigChannel.no_create_on_split || ConfigChannel.no_join_on_split))
224     {
225     sendto_one(source_p, form_str(ERR_UNAVAILRESOURCE),
226     me.name, source_p->name, chan);
227     continue;
228     }
229    
230     flags = CHFL_CHANOP;
231 michael 632 chptr = make_channel(chan);
232 michael 488 }
233    
234 adx 30 if (!IsOper(source_p))
235     check_spambot_warning(source_p, chptr->chname);
236    
237 michael 894 add_user_to_channel(chptr, source_p, flags, 1);
238 adx 30
239     /*
240 michael 488 * Set timestamp if appropriate, and propagate
241     */
242 adx 30 if (flags & CHFL_CHANOP)
243     {
244     chptr->channelts = CurrentTime;
245     chptr->mode.mode |= MODE_TOPICLIMIT;
246     chptr->mode.mode |= MODE_NOPRIVMSGS;
247    
248 michael 885 sendto_server(client_p, chptr, CAP_TS6, NOCAPS,
249 adx 30 ":%s SJOIN %lu %s +nt :@%s",
250     me.id, (unsigned long)chptr->channelts,
251     chptr->chname, source_p->id);
252 michael 885 sendto_server(client_p, chptr, NOCAPS, CAP_TS6,
253 adx 30 ":%s SJOIN %lu %s +nt :@%s",
254     me.name, (unsigned long)chptr->channelts,
255 michael 632 chptr->chname, source_p->name);
256 adx 30 /*
257     * notify all other users on the new channel
258     */
259 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s!%s@%s JOIN :%s",
260 adx 30 source_p->name, source_p->username,
261     source_p->host, chptr->chname);
262 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s MODE %s +nt",
263 adx 30 me.name, chptr->chname);
264     }
265     else
266     {
267 michael 885 sendto_server(client_p, chptr, CAP_TS6, NOCAPS,
268 adx 30 ":%s JOIN %lu %s +",
269     source_p->id, (unsigned long)chptr->channelts,
270     chptr->chname);
271 michael 885 sendto_server(client_p, chptr, NOCAPS, CAP_TS6,
272 adx 30 ":%s SJOIN %lu %s + :%s",
273     me.name, (unsigned long)chptr->channelts,
274     chptr->chname, source_p->name);
275    
276 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s!%s@%s JOIN :%s",
277 adx 30 source_p->name, source_p->username,
278     source_p->host, chptr->chname);
279     }
280    
281     del_invite(chptr, source_p);
282    
283     if (chptr->topic != NULL)
284     {
285     sendto_one(source_p, form_str(RPL_TOPIC), me.name,
286     source_p->name, chptr->chname, chptr->topic);
287    
288     sendto_one(source_p, form_str(RPL_TOPICWHOTIME),
289     me.name, source_p->name, chptr->chname,
290     chptr->topic_info, chptr->topic_time);
291     }
292    
293     channel_member_names(source_p, chptr, 1);
294    
295 michael 487 source_p->localClient->last_join_time = CurrentTime;
296 adx 30 }
297     }
298    
299     /* ms_join()
300     *
301     * inputs - parv[0] = uid
302     * parv[1] = ts
303     * parv[2] = channel name
304 db 853 * parv[3] = modes (Deprecated)
305 adx 30 * output - none
306     * side effects - handles remote JOIN's sent by servers. In TSora
307     * remote clients are joined using SJOIN, hence a
308     * JOIN sent by a server on behalf of a client is an error.
309     * here, the initial code is in to take an extra parameter
310     * and use it for the TimeStamp on a new channel.
311     */
312     static void
313     ms_join(struct Client *client_p, struct Client *source_p,
314     int parc, char *parv[])
315     {
316 michael 632 time_t newts = 0;
317     time_t oldts = 0;
318     int keep_our_modes = 1;
319     int keep_new_modes = 1;
320     int isnew = 0;
321     const char *servername = NULL;
322     struct Channel *chptr = NULL;
323     struct Mode mode, *oldmode;
324 adx 30
325 michael 632 if (parc == 2 && !irccmp(parv[1], "0"))
326 adx 30 {
327     do_join_0(client_p, source_p);
328     return;
329     }
330    
331 michael 632 if (parc < 4 || *parv[2] == '&')
332 adx 30 return;
333    
334 michael 632 if (!check_channel_name(parv[2], 0))
335     {
336     sendto_realops_flags(UMODE_DEBUG, L_ALL,
337     "*** Too long or invalid channel name from %s: %s",
338     client_p->name, parv[2]);
339 adx 30 return;
340 michael 632 }
341 adx 30
342     mbuf = modebuf;
343     mode.mode = mode.limit = 0;
344     mode.key[0] = '\0';
345    
346 michael 632 if ((chptr = hash_find_channel(parv[2])) == NULL)
347     {
348     isnew = 1;
349     chptr = make_channel(parv[2]);
350     }
351    
352     newts = atol(parv[1]);
353 adx 30 oldts = chptr->channelts;
354     oldmode = &chptr->mode;
355    
356     if (ConfigFileEntry.ignore_bogus_ts)
357     {
358     if (newts < 800000000)
359     {
360     sendto_realops_flags(UMODE_DEBUG, L_ALL,
361     "*** Bogus TS %lu on %s ignored from %s",
362     (unsigned long)newts, chptr->chname,
363     client_p->name);
364    
365     newts = (oldts == 0) ? 0 : 800000000;
366     }
367     }
368     else
369     {
370     if (!newts && !isnew && oldts)
371     {
372 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr,
373 michael 873 ":%s NOTICE %s :*** Notice -- TS for %s changed from %lu to 0",
374     me.name, chptr->chname, chptr->chname, (unsigned long)oldts);
375 adx 30 sendto_realops_flags(UMODE_ALL, L_ALL,
376     "Server %s changing TS on %s from %lu to 0",
377     source_p->name, chptr->chname, (unsigned long)oldts);
378     }
379     }
380    
381     if (isnew)
382     chptr->channelts = newts;
383     else if (newts == 0 || oldts == 0)
384     chptr->channelts = 0;
385     else if (newts == oldts)
386     ;
387     else if (newts < oldts)
388     {
389 michael 894 keep_our_modes = 0;
390 adx 30 chptr->channelts = newts;
391     }
392     else
393 michael 894 keep_new_modes = 0;
394 adx 30
395     if (!keep_new_modes)
396     mode = *oldmode;
397     else if (keep_our_modes)
398     {
399     mode.mode |= oldmode->mode;
400     if (oldmode->limit > mode.limit)
401     mode.limit = oldmode->limit;
402     if (strcmp(mode.key, oldmode->key) < 0)
403     strcpy(mode.key, oldmode->key);
404     }
405    
406     set_final_mode(&mode, oldmode);
407     chptr->mode = mode;
408    
409     /* Lost the TS, other side wins, so remove modes on this side */
410     if (!keep_our_modes)
411     {
412     remove_our_modes(chptr, source_p);
413 michael 873
414     if (chptr->topic)
415     {
416     set_channel_topic(chptr, NULL, NULL, 0);
417     chptr->topic_time = 0;
418 michael 896 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s TOPIC %s :",
419 michael 873 (IsHidden(source_p) ||
420     ConfigServerHide.hide_servers) ?
421     me.name : source_p->name, chptr->chname);
422     }
423    
424 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr,
425 adx 30 ":%s NOTICE %s :*** Notice -- TS for %s changed from %lu to %lu",
426     me.name, chptr->chname, chptr->chname,
427     (unsigned long)oldts, (unsigned long)newts);
428     }
429    
430     if (*modebuf != '\0')
431     {
432     servername = (ConfigServerHide.hide_servers || IsHidden(source_p)) ?
433     me.name : source_p->name;
434    
435     /* This _SHOULD_ be to ALL_MEMBERS
436     * It contains only +imnpstlk, etc */
437 michael 896 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s MODE %s %s %s",
438 adx 30 servername, chptr->chname, modebuf, parabuf);
439     }
440    
441     if (!IsMember(source_p, chptr))
442     {
443 michael 894 add_user_to_channel(chptr, source_p, 0, 1);
444     sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s!%s@%s JOIN :%s",
445 adx 30 source_p->name, source_p->username,
446     source_p->host, chptr->chname);
447     }
448    
449 michael 885 sendto_server(client_p, chptr, CAP_TS6, NOCAPS,
450 adx 30 ":%s JOIN %lu %s +",
451     ID(source_p), (unsigned long)chptr->channelts, chptr->chname);
452 michael 885 sendto_server(client_p, chptr, NOCAPS, CAP_TS6,
453 adx 30 ":%s SJOIN %lu %s + :%s",
454     source_p->servptr->name, (unsigned long)chptr->channelts,
455     chptr->chname, source_p->name);
456     }
457    
458     /* do_join_0()
459     *
460     * inputs - pointer to client doing join 0
461     * output - NONE
462     * side effects - Use has decided to join 0. This is legacy
463     * from the days when channels were numbers not names. *sigh*
464     * There is a bunch of evilness necessary here due to
465     * anti spambot code.
466     */
467     static void
468     do_join_0(struct Client *client_p, struct Client *source_p)
469     {
470     struct Channel *chptr = NULL;
471 michael 488 dlink_node *ptr = NULL, *ptr_next = NULL;
472 adx 30
473 michael 488 if (source_p->channel.head && MyConnect(source_p) && !IsOper(source_p))
474 adx 30 check_spambot_warning(source_p, NULL);
475    
476     DLINK_FOREACH_SAFE(ptr, ptr_next, source_p->channel.head)
477     {
478     chptr = ((struct Membership *)ptr->data)->chptr;
479    
480 michael 885 sendto_server(client_p, chptr, CAP_TS6, NOCAPS,
481 michael 488 ":%s PART %s", ID(source_p), chptr->chname);
482 michael 885 sendto_server(client_p, chptr, NOCAPS, CAP_TS6,
483 michael 488 ":%s PART %s", source_p->name, chptr->chname);
484 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr, ":%s!%s@%s PART %s",
485 michael 488 source_p->name, source_p->username,
486     source_p->host, chptr->chname);
487 adx 30
488 michael 488 remove_user_from_channel(ptr->data);
489 adx 30 }
490     }
491    
492     /* set_final_mode()
493     *
494     * inputs - pointer to mode to setup
495     * - pointer to old mode
496     * output - NONE
497     * side effects -
498     */
499     static const struct mode_letter
500     {
501     unsigned int mode;
502     unsigned char letter;
503     } flags[] = {
504     { MODE_NOPRIVMSGS, 'n' },
505     { MODE_TOPICLIMIT, 't' },
506     { MODE_SECRET, 's' },
507     { MODE_MODERATED, 'm' },
508     { MODE_INVITEONLY, 'i' },
509     { MODE_PRIVATE, 'p' },
510     { 0, '\0' }
511     };
512    
513     static void
514     set_final_mode(struct Mode *mode, struct Mode *oldmode)
515     {
516     char *pbuf = parabuf;
517     int what = 0;
518     int len;
519     int i;
520    
521     for (i = 0; flags[i].letter; i++)
522     {
523     if ((flags[i].mode & mode->mode) &&
524     !(flags[i].mode & oldmode->mode))
525     {
526     if (what != 1)
527     {
528     *mbuf++ = '+';
529     what = 1;
530     }
531     *mbuf++ = flags[i].letter;
532     }
533     }
534    
535     for (i = 0; flags[i].letter; i++)
536     {
537     if ((flags[i].mode & oldmode->mode) &&
538     !(flags[i].mode & mode->mode))
539     {
540     if (what != -1)
541     {
542     *mbuf++ = '-';
543     what = -1;
544     }
545     *mbuf++ = flags[i].letter;
546     }
547     }
548    
549     if (oldmode->limit != 0 && mode->limit == 0)
550     {
551     if (what != -1)
552     {
553     *mbuf++ = '-';
554     what = -1;
555     }
556     *mbuf++ = 'l';
557     }
558    
559     if (oldmode->key[0] && !mode->key[0])
560     {
561     if (what != -1)
562     {
563     *mbuf++ = '-';
564     what = -1;
565     }
566     *mbuf++ = 'k';
567     len = ircsprintf(pbuf, "%s ", oldmode->key);
568     pbuf += len;
569     }
570    
571     if (mode->limit != 0 && oldmode->limit != mode->limit)
572     {
573     if (what != 1)
574     {
575     *mbuf++ = '+';
576     what = 1;
577     }
578     *mbuf++ = 'l';
579     len = ircsprintf(pbuf, "%d ", mode->limit);
580     pbuf += len;
581     }
582    
583     if (mode->key[0] && strcmp(oldmode->key, mode->key))
584     {
585     if (what != 1)
586     {
587     *mbuf++ = '+';
588     what = 1;
589     }
590     *mbuf++ = 'k';
591     len = ircsprintf(pbuf, "%s ", mode->key);
592     pbuf += len;
593     }
594     *mbuf = '\0';
595     }
596    
597     /* remove_our_modes()
598     *
599     * inputs - pointer to channel to remove modes from
600     * - client pointer
601     * output - NONE
602     * side effects - Go through the local members, remove all their
603     * chanop modes etc., this side lost the TS.
604     */
605     static void
606     remove_our_modes(struct Channel *chptr, struct Client *source_p)
607     {
608     remove_a_mode(chptr, source_p, CHFL_CHANOP, 'o');
609     #ifdef HALFOPS
610     remove_a_mode(chptr, source_p, CHFL_HALFOP, 'h');
611     #endif
612     remove_a_mode(chptr, source_p, CHFL_VOICE, 'v');
613     }
614    
615     /* remove_a_mode()
616     *
617     * inputs -
618     * output - NONE
619     * side effects - remove ONE mode from a channel
620     */
621     static void
622     remove_a_mode(struct Channel *chptr, struct Client *source_p,
623     int mask, char flag)
624     {
625     dlink_node *ptr;
626     struct Membership *ms;
627     char lmodebuf[MODEBUFLEN];
628     const char *lpara[MAXMODEPARAMS];
629     int count = 0;
630     int lcount;
631    
632     mbuf = lmodebuf;
633     *mbuf++ = '-';
634    
635     for (lcount = 0; lcount < MAXMODEPARAMS; lcount++)
636     lpara[lcount] = "";
637     sendbuf[0] = '\0';
638    
639     DLINK_FOREACH(ptr, chptr->members.head)
640     {
641     ms = ptr->data;
642    
643     if ((ms->flags & mask) == 0)
644     continue;
645    
646     ms->flags &= ~mask;
647    
648     lpara[count++] = ms->client_p->name;
649    
650     *mbuf++ = flag;
651    
652     if (count >= MAXMODEPARAMS)
653     {
654     for (lcount = 0; lcount < MAXMODEPARAMS; lcount++)
655     {
656     if (*lpara[lcount] == '\0')
657     break;
658    
659     strlcat(sendbuf, " ", sizeof(sendbuf));
660     strlcat(sendbuf, lpara[lcount], sizeof(sendbuf));
661     lpara[lcount] = "";
662     }
663    
664     *mbuf = '\0';
665 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr,
666 adx 30 ":%s MODE %s %s%s",
667     (IsHidden(source_p) ||
668     ConfigServerHide.hide_servers) ?
669     me.name : source_p->name,
670     chptr->chname, lmodebuf, sendbuf);
671     mbuf = lmodebuf;
672     *mbuf++ = '-';
673     count = 0;
674     sendbuf[0] = '\0';
675     }
676     }
677    
678     if (count != 0)
679     {
680     *mbuf = '\0';
681     for (lcount = 0; lcount < MAXMODEPARAMS; lcount++)
682     {
683     if (*lpara[lcount] == '\0')
684     break;
685    
686     strlcat(sendbuf, " ", sizeof(sendbuf));
687     strlcat(sendbuf, lpara[lcount], sizeof(sendbuf));
688     }
689 michael 894 sendto_channel_local(ALL_MEMBERS, 0, chptr,
690 adx 30 ":%s MODE %s %s%s",
691     (IsHidden(source_p) || ConfigServerHide.hide_servers) ?
692     me.name : source_p->name,
693     chptr->chname, lmodebuf, sendbuf);
694     }
695     }
696    

Properties

Name Value
svn:eol-style native
svn:keywords Id Revision