ViewVC Help
View File | Revision Log | Show Annotations | View Changeset | Root Listing
root/svn/ircd-hybrid/trunk/src/hostmask.c
Revision: 9046
Committed: Fri May 31 09:59:57 2019 UTC (6 years, 2 months ago) by michael
Content type: text/x-csrc
File size: 19810 byte(s)
Log Message:
- hostmask.c:try_parse_v4_netmask(): made 'addb' of type uint8_t

File Contents

# Content
1 /*
2 * ircd-hybrid: an advanced, lightweight Internet Relay Chat Daemon (ircd)
3 *
4 * Copyright (c) 2001-2019 ircd-hybrid development team
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19 * USA
20 */
21
22 /*! \file hostmask.c
23 * \brief Code to efficiently find IP & hostmask based configs.
24 * \version $Id$
25 */
26
27 #include "stdinc.h"
28 #include "memory.h"
29 #include "ircd_defs.h"
30 #include "list.h"
31 #include "conf.h"
32 #include "hostmask.h"
33 #include "send.h"
34 #include "irc_string.h"
35 #include "ircd.h"
36
37
38 #define DigitParse(ch) do { \
39 if (ch >= '0' && ch <= '9') \
40 ch = ch - '0'; \
41 else if (ch >= 'A' && ch <= 'F') \
42 ch = ch - 'A' + 10; \
43 else if (ch >= 'a' && ch <= 'f') \
44 ch = ch - 'a' + 10; \
45 } while (false);
46
47 /* Hashtable stuff...now external as it's used in m_stats.c */
48 dlink_list atable[ATABLE_SIZE];
49
50 /* The mask parser/type determination code... */
51
52 /* int try_parse_v6_netmask(const char *, struct irc_ssaddr *, int *);
53 * Input: A possible IPV6 address as a string.
54 * Output: An integer describing whether it is an IPV6 or hostmask,
55 * an address(if it is IPV6), a bitlength(if it is IPV6).
56 * Side effects: None
57 * Comments: Called from parse_netmask
58 */
59 static int
60 try_parse_v6_netmask(const char *text, struct irc_ssaddr *addr, int *b)
61 {
62 char c;
63 int d[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
64 int dp = 0;
65 int nyble = 4;
66 int finsert = -1;
67 int bits = 128;
68 int deficit = 0;
69 uint16_t dc[8];
70 struct sockaddr_in6 *const v6 = (struct sockaddr_in6 *)addr;
71
72 for (const char *p = text; (c = *p); ++p)
73 {
74 if (IsXDigit(c))
75 {
76 if (nyble == 0)
77 return HM_HOST;
78 DigitParse(c);
79 d[dp] |= c << (4 * --nyble);
80 }
81 else if (c == ':')
82 {
83 if (p > text && *(p - 1) == ':')
84 {
85 if (finsert >= 0)
86 return HM_HOST;
87 finsert = dp;
88 }
89 else
90 {
91 /*
92 * If there were less than 4 hex digits, e.g. :ABC: shift right
93 * so we don't interpret it as ABC0 -A1kmm
94 */
95 d[dp] = d[dp] >> 4 * nyble;
96 nyble = 4;
97
98 if (++dp >= 8)
99 return HM_HOST;
100 }
101 }
102 else if (c == '*')
103 {
104 /* * must be last, and * is ambiguous if there is a ::... -A1kmm */
105 if (finsert >= 0 || *(p + 1) || dp == 0 || *(p - 1) != ':')
106 return HM_HOST;
107 bits = dp * 16;
108 }
109 else if (c == '/')
110 {
111 char *after;
112
113 bits = strtoul(p + 1, &after, 10);
114
115 if (bits < 0 || bits > 128 || *after)
116 return HM_HOST;
117 /* 16 bits for each hextet, plus 4 for each parsed nyble */
118 if (bits > dp * 16 + (4 - nyble) * 4 && !(finsert >= 0))
119 return HM_HOST;
120 break;
121 }
122 else
123 return HM_HOST;
124 }
125
126 d[dp] = d[dp] >> 4 * nyble;
127
128 if (c == 0)
129 ++dp;
130 if (finsert < 0 && bits == 0)
131 bits = dp * 16;
132
133 /* How many words are missing? -A1kmm */
134 deficit = bits / 16 + ((bits % 16) ? 1 : 0) - dp;
135
136 /* Now fill in the gaps(from ::) in the copied table... -A1kmm */
137 for (dp = 0, nyble = 0; dp < 8; ++dp)
138 {
139 if (nyble == finsert && deficit)
140 {
141 dc[dp] = 0;
142 deficit--;
143 }
144 else
145 dc[dp] = d[nyble++];
146 }
147
148 /* Set unused bits to 0... -A1kmm */
149 if (bits < 128 && (bits % 16 != 0))
150 dc[bits / 16] &= ~((1 << (15 - bits % 16)) - 1);
151 for (dp = bits / 16 + (bits % 16 ? 1 : 0); dp < 8; ++dp)
152 dc[dp] = 0;
153
154 /* And assign... -A1kmm */
155 if (addr)
156 {
157 v6->sin6_family = AF_INET6;
158
159 for (dp = 0; dp < 8; ++dp)
160 /* The cast is a kludge to make netbsd work. */
161 ((uint16_t *)&v6->sin6_addr)[dp] = htons(dc[dp]);
162 }
163
164 if (b)
165 *b = bits;
166 return HM_IPV6;
167 }
168
169 /* int try_parse_v4_netmask(const char *, struct irc_ssaddr *, int *);
170 * Input: A possible IPV4 address as a string.
171 * Output: An integer describing whether it is an IPV4 or hostmask,
172 * an address(if it is IPV4), a bitlength(if it is IPV4).
173 * Side effects: None
174 * Comments: Called from parse_netmask
175 */
176 static int
177 try_parse_v4_netmask(const char *text, struct irc_ssaddr *addr, int *b)
178 {
179 const char *digits[4];
180 uint8_t addb[4];
181 int n = 0, bits = 0;
182 char c;
183 struct sockaddr_in *const v4 = (struct sockaddr_in *)addr;
184
185 digits[n++] = text;
186
187 for (const char *p = text; (c = *p); ++p)
188 {
189 if (c >= '0' && c <= '9') /* empty */
190 ;
191 else if (c == '.')
192 {
193 if (n >= 4)
194 return HM_HOST;
195
196 digits[n++] = p + 1;
197 }
198 else if (c == '*')
199 {
200 if (*(p + 1) || n == 1 || *(p - 1) != '.')
201 return HM_HOST;
202
203 bits = (n - 1) * 8;
204 break;
205 }
206 else if (c == '/')
207 {
208 char *after;
209 bits = strtoul(p + 1, &after, 10);
210
211 if (bits < 0 || *after)
212 return HM_HOST;
213 if (bits > n * 8)
214 return HM_HOST;
215
216 break;
217 }
218 else
219 return HM_HOST;
220 }
221
222 if (n < 4 && bits == 0)
223 bits = n * 8;
224 if (bits)
225 while (n < 4)
226 digits[n++] = "0";
227
228 for (n = 0; n < 4; ++n)
229 addb[n] = strtoul(digits[n], NULL, 10);
230
231 if (bits == 0)
232 bits = 32;
233
234 /* Set unused bits to 0... -A1kmm */
235 if (bits < 32 && bits % 8)
236 addb[bits / 8] &= ~((1 << (8 - bits % 8)) - 1);
237 for (n = bits / 8 + (bits % 8 ? 1 : 0); n < 4; ++n)
238 addb[n] = 0;
239 if (addr)
240 {
241 v4->sin_family = AF_INET;
242 v4->sin_addr.s_addr =
243 htonl((unsigned int)addb[0] << 24 |
244 (unsigned int)addb[1] << 16 |
245 (unsigned int)addb[2] << 8 |
246 (unsigned int)addb[3]);
247 }
248
249 if (b)
250 *b = bits;
251 return HM_IPV4;
252 }
253
254 /* int parse_netmask(const char *, struct irc_ssaddr *, int *);
255 * Input: A hostmask, or an IPV4/6 address.
256 * Output: An integer describing whether it is an IPV4, IPV6 address or a
257 * hostmask, an address(if it is an IP mask),
258 * a bitlength(if it is IP mask).
259 * Side effects: None
260 */
261 int
262 parse_netmask(const char *text, struct irc_ssaddr *addr, int *b)
263 {
264 if (addr)
265 memset(addr, 0, sizeof(*addr));
266
267 if (strchr(text, '.'))
268 return try_parse_v4_netmask(text, addr, b);
269 if (strchr(text, ':'))
270 return try_parse_v6_netmask(text, addr, b);
271
272 return HM_HOST;
273 }
274
275 /* The address matching stuff... */
276 /* int match_ipv6(struct irc_ssaddr *, struct irc_ssaddr *, int)
277 * Input: An IP address, an IP mask, the number of bits in the mask.
278 * Output: if match, -1 else 0
279 * Side effects: None
280 */
281 bool
282 match_ipv6(const struct irc_ssaddr *addr, const struct irc_ssaddr *mask, int bits)
283 {
284 int i, m, n = bits / 8;
285 const struct sockaddr_in6 *const v6 = (const struct sockaddr_in6 *)addr;
286 const struct sockaddr_in6 *const v6mask = (const struct sockaddr_in6 *)mask;
287
288 for (i = 0; i < n; ++i)
289 if (v6->sin6_addr.s6_addr[i] != v6mask->sin6_addr.s6_addr[i])
290 return false;
291
292 if ((m = bits % 8) == 0)
293 return true;
294 if ((v6->sin6_addr.s6_addr[n] & ~((1 << (8 - m)) - 1)) ==
295 v6mask->sin6_addr.s6_addr[n])
296 return true;
297 return false;
298 }
299
300 /* int match_ipv4(struct irc_ssaddr *, struct irc_ssaddr *, int)
301 * Input: An IP address, an IP mask, the number of bits in the mask.
302 * Output: if match, -1 else 0
303 * Side Effects: None
304 */
305 bool
306 match_ipv4(const struct irc_ssaddr *addr, const struct irc_ssaddr *mask, int bits)
307 {
308 const struct sockaddr_in *const v4 = (const struct sockaddr_in *)addr;
309 const struct sockaddr_in *const v4mask = (const struct sockaddr_in *)mask;
310
311 if ((ntohl(v4->sin_addr.s_addr) & ~((1 << (32 - bits)) - 1)) ==
312 ntohl(v4mask->sin_addr.s_addr))
313 return true;
314 return false;
315 }
316
317 /* unsigned long hash_ipv4(struct irc_ssaddr*)
318 * Input: An IP address.
319 * Output: A hash value of the IP address.
320 * Side effects: None
321 */
322 static uint32_t
323 hash_ipv4(const struct irc_ssaddr *addr, int bits)
324 {
325 if (bits)
326 {
327 const struct sockaddr_in *const v4 = (const struct sockaddr_in *)addr;
328 uint32_t av = ntohl(v4->sin_addr.s_addr) & ~((1 << (32 - bits)) - 1);
329
330 return (av ^ (av >> 12) ^ (av >> 24)) & (ATABLE_SIZE - 1);
331 }
332
333 return 0;
334 }
335
336 /* unsigned long hash_ipv6(struct irc_ssaddr*)
337 * Input: An IP address.
338 * Output: A hash value of the IP address.
339 * Side effects: None
340 */
341 static uint32_t
342 hash_ipv6(const struct irc_ssaddr *addr, int bits)
343 {
344 uint32_t v = 0, n;
345 const struct sockaddr_in6 *const v6 = (const struct sockaddr_in6 *)addr;
346
347 for (n = 0; n < 16; ++n)
348 {
349 if (bits >= 8)
350 {
351 v ^= v6->sin6_addr.s6_addr[n];
352 bits -= 8;
353 }
354 else if (bits)
355 {
356 v ^= v6->sin6_addr.s6_addr[n] & ~((1 << (8 - bits)) - 1);
357 return v & (ATABLE_SIZE - 1);
358 }
359 else
360 return v & (ATABLE_SIZE - 1);
361 }
362
363 return v & (ATABLE_SIZE - 1);
364 }
365
366 /* int hash_text(const char *start)
367 * Input: The start of the text to hash.
368 * Output: The hash of the string between 1 and (TH_MAX-1)
369 * Side-effects: None.
370 */
371 static uint32_t
372 hash_text(const char *start)
373 {
374 uint32_t h = 0;
375
376 for (const char *p = start; *p; ++p)
377 h = (h << 4) - (h + ToLower(*p));
378
379 return h & (ATABLE_SIZE - 1);
380 }
381
382 /* unsigned long get_hash_mask(const char *)
383 * Input: The text to hash.
384 * Output: The hash of the string right of the first '.' past the last
385 * wildcard in the string.
386 * Side-effects: None.
387 */
388 static uint32_t
389 get_mask_hash(const char *text)
390 {
391 const char *hp = "", *p;
392
393 for (p = text + strlen(text) - 1; p >= text; --p)
394 if (IsMWildChar(*p))
395 return hash_text(hp);
396 else if (*p == '.')
397 hp = p + 1;
398 return hash_text(text);
399 }
400
401 /* struct MaskItem *find_conf_by_address(const char *, struct irc_ssaddr *,
402 * int type, int fam, const char *username)
403 * Input: The hostname, the address, the type of mask to find, the address
404 * family, the username.
405 * Output: The matching value with the highest precedence.
406 * Side-effects: None
407 * Note: Setting bit 0 of the type means that the username is ignored.
408 * Warning: IsNeedPassword for everything that is not an auth{} entry
409 * should always be true (i.e. conf->flags & CONF_FLAGS_NEED_PASSWORD == 0)
410 */
411 struct MaskItem *
412 find_conf_by_address(const char *name, const struct irc_ssaddr *addr, unsigned int type,
413 const char *username, const char *password, int do_match)
414 {
415 unsigned int hprecv = 0;
416 dlink_node *node;
417 struct MaskItem *hprec = NULL;
418 struct AddressRec *arec = NULL;
419 int (*cmpfunc)(const char *, const char *) = do_match ? match : irccmp;
420
421 if (addr)
422 {
423 /* Check for IPV6 matches... */
424 if (addr->ss.ss_family == AF_INET6)
425 {
426 for (int b = 128; b >= 0; b -= 16)
427 {
428 DLINK_FOREACH(node, atable[hash_ipv6(addr, b)].head)
429 {
430 arec = node->data;
431
432 if ((arec->type == type) &&
433 arec->precedence > hprecv &&
434 arec->masktype == HM_IPV6 &&
435 match_ipv6(addr, &arec->Mask.ipa.addr,
436 arec->Mask.ipa.bits) &&
437 (!username || !cmpfunc(arec->username, username)) &&
438 (IsNeedPassword(arec->conf) || arec->conf->passwd == NULL ||
439 match_conf_password(password, arec->conf)))
440 {
441 hprecv = arec->precedence;
442 hprec = arec->conf;
443 }
444 }
445 }
446 }
447 else if (addr->ss.ss_family == AF_INET)
448 {
449 for (int b = 32; b >= 0; b -= 8)
450 {
451 DLINK_FOREACH(node, atable[hash_ipv4(addr, b)].head)
452 {
453 arec = node->data;
454
455 if ((arec->type == type) &&
456 arec->precedence > hprecv &&
457 arec->masktype == HM_IPV4 &&
458 match_ipv4(addr, &arec->Mask.ipa.addr,
459 arec->Mask.ipa.bits) &&
460 (!username || !cmpfunc(arec->username, username)) &&
461 (IsNeedPassword(arec->conf) || arec->conf->passwd == NULL ||
462 match_conf_password(password, arec->conf)))
463 {
464 hprecv = arec->precedence;
465 hprec = arec->conf;
466 }
467 }
468 }
469 }
470 }
471
472 if (name)
473 {
474 const char *p = name;
475
476 while (true)
477 {
478 DLINK_FOREACH(node, atable[hash_text(p)].head)
479 {
480 arec = node->data;
481 if ((arec->type == type) &&
482 arec->precedence > hprecv &&
483 (arec->masktype == HM_HOST) &&
484 !cmpfunc(arec->Mask.hostname, name) &&
485 (!username || !cmpfunc(arec->username, username)) &&
486 (IsNeedPassword(arec->conf) || arec->conf->passwd == NULL ||
487 match_conf_password(password, arec->conf)))
488 {
489 hprecv = arec->precedence;
490 hprec = arec->conf;
491 }
492 }
493
494 if ((p = strchr(p, '.')) == NULL)
495 break;
496 ++p;
497 }
498
499 DLINK_FOREACH(node, atable[0].head)
500 {
501 arec = node->data;
502
503 if (arec->type == type &&
504 arec->precedence > hprecv &&
505 arec->masktype == HM_HOST &&
506 !cmpfunc(arec->Mask.hostname, name) &&
507 (!username || !cmpfunc(arec->username, username)) &&
508 (IsNeedPassword(arec->conf) || arec->conf->passwd == NULL ||
509 match_conf_password(password, arec->conf)))
510 {
511 hprecv = arec->precedence;
512 hprec = arec->conf;
513 }
514 }
515 }
516
517 return hprec;
518 }
519
520 /* struct MaskItem* find_address_conf(const char*, const char*,
521 * struct irc_ssaddr*, int, char *);
522 * Input: The hostname, username, address, address family.
523 * Output: The applicable MaskItem.
524 * Side-effects: None
525 */
526 struct MaskItem *
527 find_address_conf(const char *host, const char *user, const struct irc_ssaddr *addr, const char *password)
528 {
529 struct MaskItem *authcnf = NULL, *killcnf = NULL;
530
531 /* Find the best auth{} block... If none, return NULL -A1kmm */
532 if ((authcnf = find_conf_by_address(host, addr, CONF_CLIENT, user, password, 1)) == NULL)
533 return NULL;
534
535 /* If they are exempt from K-lines, return the best auth{} block. -A1kmm */
536 if (IsConfExemptKline(authcnf))
537 return authcnf;
538
539 /* Find the best K-line... -A1kmm */
540 killcnf = find_conf_by_address(host, addr, CONF_KLINE, user, NULL, 1);
541
542 /*
543 * If they are K-lined, return the K-line. Otherwise, return the
544 * auth {} block. -A1kmm
545 */
546 if (killcnf)
547 return killcnf;
548
549 return authcnf;
550 }
551
552 /* struct MaskItem* find_dline_conf(struct irc_ssaddr*, int)
553 *
554 * Input: An address, an address family.
555 * Output: The best matching D-line or exempt line.
556 * Side effects: None.
557 */
558 struct MaskItem *
559 find_dline_conf(const struct irc_ssaddr *addr)
560 {
561 struct MaskItem *eline;
562
563 eline = find_conf_by_address(NULL, addr, CONF_EXEMPT, NULL, NULL, 1);
564 if (eline)
565 return eline;
566
567 return find_conf_by_address(NULL, addr, CONF_DLINE, NULL, NULL, 1);
568 }
569
570 /* void add_conf_by_address(int, struct MaskItem *aconf)
571 * Input:
572 * Output: None
573 * Side-effects: Adds this entry to the hash table.
574 */
575 struct AddressRec *
576 add_conf_by_address(const unsigned int type, struct MaskItem *conf)
577 {
578 const char *const hostname = conf->host;
579 const char *const username = conf->user;
580 static unsigned int prec_value = UINT_MAX;
581 int bits = 0;
582
583 assert(type && !EmptyString(hostname));
584
585 struct AddressRec *arec = xcalloc(sizeof(*arec));
586 arec->masktype = parse_netmask(hostname, &arec->Mask.ipa.addr, &bits);
587 arec->Mask.ipa.bits = bits;
588 arec->username = username;
589 arec->conf = conf;
590 arec->precedence = prec_value--;
591 arec->type = type;
592
593 switch (arec->masktype)
594 {
595 case HM_IPV4:
596 /* We have to do this, since we do not re-hash for every bit -A1kmm. */
597 bits -= bits % 8;
598 dlinkAdd(arec, &arec->node, &atable[hash_ipv4(&arec->Mask.ipa.addr, bits)]);
599 break;
600 case HM_IPV6:
601 /* We have to do this, since we do not re-hash for every bit -A1kmm. */
602 bits -= bits % 16;
603 dlinkAdd(arec, &arec->node, &atable[hash_ipv6(&arec->Mask.ipa.addr, bits)]);
604 break;
605 default: /* HM_HOST */
606 arec->Mask.hostname = hostname;
607 dlinkAdd(arec, &arec->node, &atable[get_mask_hash(hostname)]);
608 break;
609 }
610
611 return arec;
612 }
613
614 /* void delete_one_address(const char*, struct MaskItem*)
615 * Input: An address string, the associated MaskItem.
616 * Output: None
617 * Side effects: Deletes an address record. Frees the MaskItem if there
618 * is nothing referencing it, sets it as illegal otherwise.
619 */
620 void
621 delete_one_address_conf(const char *address, struct MaskItem *conf)
622 {
623 int bits = 0;
624 uint32_t hv = 0;
625 dlink_node *node;
626 struct irc_ssaddr addr;
627
628 switch (parse_netmask(address, &addr, &bits))
629 {
630 case HM_IPV4:
631 /* We have to do this, since we do not re-hash for every bit -A1kmm. */
632 bits -= bits % 8;
633 hv = hash_ipv4(&addr, bits);
634 break;
635 case HM_IPV6:
636 /* We have to do this, since we do not re-hash for every bit -A1kmm. */
637 bits -= bits % 16;
638 hv = hash_ipv6(&addr, bits);
639 break;
640 default: /* HM_HOST */
641 hv = get_mask_hash(address);
642 break;
643 }
644
645 DLINK_FOREACH(node, atable[hv].head)
646 {
647 struct AddressRec *arec = node->data;
648
649 if (arec->conf == conf)
650 {
651 dlinkDelete(&arec->node, &atable[hv]);
652
653 if (conf->ref_count == 0)
654 conf_free(conf);
655
656 xfree(arec);
657 return;
658 }
659 }
660 }
661
662 /* void clear_out_address_conf(void)
663 * Input: None
664 * Output: None
665 * Side effects: Clears out all address records in the hash table,
666 * frees them, and frees the MaskItems if nothing references
667 * them, otherwise sets them as illegal.
668 */
669 void
670 clear_out_address_conf(void)
671 {
672 dlink_node *node, *node_next;
673
674 for (unsigned int i = 0; i < ATABLE_SIZE; ++i)
675 {
676 DLINK_FOREACH_SAFE(node, node_next, atable[i].head)
677 {
678 struct AddressRec *arec = node->data;
679
680 /*
681 * Destroy the ircd.conf items and keep those that are in the databases
682 */
683 if (IsConfDatabase(arec->conf))
684 continue;
685
686 dlinkDelete(&arec->node, &atable[i]);
687 arec->conf->active = false;
688
689 if (arec->conf->ref_count == 0)
690 conf_free(arec->conf);
691 xfree(arec);
692 }
693 }
694 }
695
696 static void
697 hostmask_send_expiration(const struct AddressRec *const arec)
698 {
699 char ban_type = '?';
700
701 switch (arec->type)
702 {
703 case CONF_KLINE:
704 ban_type = 'K';
705 break;
706 case CONF_DLINE:
707 ban_type = 'D';
708 break;
709 default: break;
710 }
711
712 sendto_realops_flags(UMODE_EXPIRATION, L_ALL, SEND_NOTICE,
713 "Temporary %c-line for [%s@%s] expired", ban_type,
714 (arec->conf->user) ? arec->conf->user : "*",
715 (arec->conf->host) ? arec->conf->host : "*");
716 }
717
718 void
719 hostmask_expire_temporary(void)
720 {
721 dlink_node *node, *node_next;
722
723 for (unsigned int i = 0; i < ATABLE_SIZE; ++i)
724 {
725 DLINK_FOREACH_SAFE(node, node_next, atable[i].head)
726 {
727 struct AddressRec *arec = node->data;
728
729 if (arec->conf->until == 0 || arec->conf->until > event_base->time.sec_real)
730 continue;
731
732 switch (arec->type)
733 {
734 case CONF_KLINE:
735 case CONF_DLINE:
736 hostmask_send_expiration(arec);
737
738 dlinkDelete(&arec->node, &atable[i]);
739 conf_free(arec->conf);
740 xfree(arec);
741 break;
742 default: break;
743 }
744 }
745 }
746 }

Properties

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