]> jfr.im git - solanum.git/blob - ircd/s_newconf.c
ircd: add irc_radixtree, which is like irc_dictionary but uses a radix tree as the...
[solanum.git] / ircd / s_newconf.c
1 /*
2 * ircd-ratbox: an advanced Internet Relay Chat Daemon(ircd).
3 * s_newconf.c - code for dealing with conf stuff
4 *
5 * Copyright (C) 2004 Lee Hardy <lee@leeh.co.uk>
6 * Copyright (C) 2004-2005 ircd-ratbox development team
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are
10 * met:
11 *
12 * 1.Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 * 2.Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3.The name of the author may not be used to endorse or promote products
18 * derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
29 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 *
32 * $Id: s_newconf.c 3508 2007-06-04 16:04:49Z jilles $
33 */
34
35 #include "stdinc.h"
36 #include "ircd_defs.h"
37 #include "common.h"
38 #include "s_conf.h"
39 #include "s_newconf.h"
40 #include "client.h"
41 #include "s_serv.h"
42 #include "send.h"
43 #include "hostmask.h"
44 #include "newconf.h"
45 #include "hash.h"
46 #include "irc_dictionary.h"
47 #include "s_assert.h"
48 #include "logger.h"
49 #include "dns.h"
50
51 rb_dlink_list shared_conf_list;
52 rb_dlink_list cluster_conf_list;
53 rb_dlink_list oper_conf_list;
54 rb_dlink_list hubleaf_conf_list;
55 rb_dlink_list server_conf_list;
56 rb_dlink_list xline_conf_list;
57 rb_dlink_list resv_conf_list; /* nicks only! */
58 rb_dlink_list nd_list; /* nick delay */
59 rb_dlink_list tgchange_list;
60
61 rb_patricia_tree_t *tgchange_tree;
62
63 static rb_bh *nd_heap = NULL;
64
65 static void expire_temp_rxlines(void *unused);
66 static void expire_nd_entries(void *unused);
67
68 struct ev_entry *expire_nd_entries_ev = NULL;
69 struct ev_entry *expire_temp_rxlines_ev = NULL;
70
71 void
72 init_s_newconf(void)
73 {
74 tgchange_tree = rb_new_patricia(PATRICIA_BITS);
75 nd_heap = rb_bh_create(sizeof(struct nd_entry), ND_HEAP_SIZE, "nd_heap");
76 expire_nd_entries_ev = rb_event_addish("expire_nd_entries", expire_nd_entries, NULL, 30);
77 expire_temp_rxlines_ev = rb_event_addish("expire_temp_rxlines", expire_temp_rxlines, NULL, 60);
78 }
79
80 void
81 clear_s_newconf(void)
82 {
83 struct server_conf *server_p;
84 rb_dlink_node *ptr;
85 rb_dlink_node *next_ptr;
86
87 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, shared_conf_list.head)
88 {
89 /* ptr here is ptr->data->node */
90 rb_dlinkDelete(ptr, &shared_conf_list);
91 free_remote_conf(ptr->data);
92 }
93
94 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, cluster_conf_list.head)
95 {
96 rb_dlinkDelete(ptr, &cluster_conf_list);
97 free_remote_conf(ptr->data);
98 }
99
100 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, hubleaf_conf_list.head)
101 {
102 rb_dlinkDelete(ptr, &hubleaf_conf_list);
103 free_remote_conf(ptr->data);
104 }
105
106 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, oper_conf_list.head)
107 {
108 free_oper_conf(ptr->data);
109 rb_dlinkDestroy(ptr, &oper_conf_list);
110 }
111
112 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, server_conf_list.head)
113 {
114 server_p = ptr->data;
115
116 if(!server_p->servers)
117 {
118 rb_dlinkDelete(ptr, &server_conf_list);
119 free_server_conf(ptr->data);
120 }
121 else
122 server_p->flags |= SERVER_ILLEGAL;
123 }
124 }
125
126 void
127 clear_s_newconf_bans(void)
128 {
129 struct ConfItem *aconf;
130 rb_dlink_node *ptr, *next_ptr;
131
132 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, xline_conf_list.head)
133 {
134 aconf = ptr->data;
135
136 if(aconf->hold)
137 continue;
138
139 free_conf(aconf);
140 rb_dlinkDestroy(ptr, &xline_conf_list);
141 }
142
143 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, resv_conf_list.head)
144 {
145 aconf = ptr->data;
146
147 /* temporary resv */
148 if(aconf->hold)
149 continue;
150
151 free_conf(aconf);
152 rb_dlinkDestroy(ptr, &resv_conf_list);
153 }
154
155 clear_resv_hash();
156 }
157
158 struct remote_conf *
159 make_remote_conf(void)
160 {
161 struct remote_conf *remote_p = rb_malloc(sizeof(struct remote_conf));
162 return remote_p;
163 }
164
165 void
166 free_remote_conf(struct remote_conf *remote_p)
167 {
168 s_assert(remote_p != NULL);
169 if(remote_p == NULL)
170 return;
171
172 rb_free(remote_p->username);
173 rb_free(remote_p->host);
174 rb_free(remote_p->server);
175 rb_free(remote_p);
176 }
177
178 int
179 find_shared_conf(const char *username, const char *host,
180 const char *server, int flags)
181 {
182 struct remote_conf *shared_p;
183 rb_dlink_node *ptr;
184
185 RB_DLINK_FOREACH(ptr, shared_conf_list.head)
186 {
187 shared_p = ptr->data;
188
189 if(match(shared_p->username, username) &&
190 match(shared_p->host, host) &&
191 match(shared_p->server, server))
192 {
193 if(shared_p->flags & flags)
194 return YES;
195 else
196 return NO;
197 }
198 }
199
200 return NO;
201 }
202
203 void
204 propagate_generic(struct Client *source_p, const char *command,
205 const char *target, int cap, const char *format, ...)
206 {
207 char buffer[BUFSIZE];
208 va_list args;
209
210 va_start(args, format);
211 rb_vsnprintf(buffer, sizeof(buffer), format, args);
212 va_end(args);
213
214 sendto_match_servs(source_p, target, cap, NOCAPS,
215 "%s %s %s",
216 command, target, buffer);
217 sendto_match_servs(source_p, target, CAP_ENCAP, cap,
218 "ENCAP %s %s %s",
219 target, command, buffer);
220 }
221
222 void
223 cluster_generic(struct Client *source_p, const char *command,
224 int cltype, int cap, const char *format, ...)
225 {
226 char buffer[BUFSIZE];
227 struct remote_conf *shared_p;
228 va_list args;
229 rb_dlink_node *ptr;
230
231 va_start(args, format);
232 rb_vsnprintf(buffer, sizeof(buffer), format, args);
233 va_end(args);
234
235 RB_DLINK_FOREACH(ptr, cluster_conf_list.head)
236 {
237 shared_p = ptr->data;
238
239 if(!(shared_p->flags & cltype))
240 continue;
241
242 sendto_match_servs(source_p, shared_p->server, cap, NOCAPS,
243 "%s %s %s",
244 command, shared_p->server, buffer);
245 sendto_match_servs(source_p, shared_p->server, CAP_ENCAP, cap,
246 "ENCAP %s %s %s",
247 shared_p->server, command, buffer);
248 }
249 }
250
251 struct oper_conf *
252 make_oper_conf(void)
253 {
254 struct oper_conf *oper_p = rb_malloc(sizeof(struct oper_conf));
255 return oper_p;
256 }
257
258 void
259 free_oper_conf(struct oper_conf *oper_p)
260 {
261 s_assert(oper_p != NULL);
262 if(oper_p == NULL)
263 return;
264
265 rb_free(oper_p->username);
266 rb_free(oper_p->host);
267 rb_free(oper_p->name);
268 rb_free(oper_p->certfp);
269
270 if(oper_p->passwd)
271 {
272 memset(oper_p->passwd, 0, strlen(oper_p->passwd));
273 rb_free(oper_p->passwd);
274 }
275
276 #ifdef HAVE_LIBCRYPTO
277 rb_free(oper_p->rsa_pubkey_file);
278
279 if(oper_p->rsa_pubkey)
280 RSA_free(oper_p->rsa_pubkey);
281 #endif
282
283 rb_free(oper_p);
284 }
285
286 struct oper_conf *
287 find_oper_conf(const char *username, const char *host, const char *locip, const char *name)
288 {
289 struct oper_conf *oper_p;
290 struct rb_sockaddr_storage ip, cip;
291 char addr[HOSTLEN+1];
292 int bits, cbits;
293 rb_dlink_node *ptr;
294
295 parse_netmask(locip, &cip, &cbits);
296
297 RB_DLINK_FOREACH(ptr, oper_conf_list.head)
298 {
299 oper_p = ptr->data;
300
301 /* name/username doesnt match.. */
302 if(irccmp(oper_p->name, name) || !match(oper_p->username, username))
303 continue;
304
305 rb_strlcpy(addr, oper_p->host, sizeof(addr));
306
307 if(parse_netmask(addr, &ip, &bits) != HM_HOST)
308 {
309 if(ip.ss_family == cip.ss_family &&
310 comp_with_mask_sock((struct sockaddr *)&ip, (struct sockaddr *)&cip, bits))
311 return oper_p;
312 }
313
314 /* we have to compare against the host as well, because its
315 * valid to set a spoof to an IP, which if we only compare
316 * in ip form to sockhost will not necessarily match --anfl
317 */
318 if(match(oper_p->host, host))
319 return oper_p;
320 }
321
322 return NULL;
323 }
324
325 struct server_conf *
326 make_server_conf(void)
327 {
328 struct server_conf *server_p = rb_malloc(sizeof(struct server_conf));
329 server_p->aftype = AF_INET;
330 return server_p;
331 }
332
333 void
334 free_server_conf(struct server_conf *server_p)
335 {
336 s_assert(server_p != NULL);
337 if(server_p == NULL)
338 return;
339
340 if(!EmptyString(server_p->passwd))
341 {
342 memset(server_p->passwd, 0, strlen(server_p->passwd));
343 rb_free(server_p->passwd);
344 }
345
346 if(!EmptyString(server_p->spasswd))
347 {
348 memset(server_p->spasswd, 0, strlen(server_p->spasswd));
349 rb_free(server_p->spasswd);
350 }
351
352 rb_free(server_p->name);
353 rb_free(server_p->host);
354 rb_free(server_p->class_name);
355 rb_free(server_p);
356 }
357
358 /*
359 * conf_dns_callback
360 * inputs - pointer to struct ConfItem
361 * - pointer to adns reply
362 * output - none
363 * side effects - called when resolver query finishes
364 * if the query resulted in a successful search, hp will contain
365 * a non-null pointer, otherwise hp will be null.
366 * if successful save hp in the conf item it was called with
367 */
368 static void
369 conf_dns_callback(const char *result, int status, int aftype, void *data)
370 {
371 struct server_conf *server_p = data;
372
373 if(status == 1)
374 rb_inet_pton_sock(result, (struct sockaddr *)&server_p->my_ipnum);
375
376 server_p->dns_query = 0;
377 }
378
379 void
380 add_server_conf(struct server_conf *server_p)
381 {
382 if(EmptyString(server_p->class_name))
383 {
384 server_p->class_name = rb_strdup("default");
385 server_p->class = default_class;
386 return;
387 }
388
389 server_p->class = find_class(server_p->class_name);
390
391 if(server_p->class == default_class)
392 {
393 conf_report_error("Warning connect::class invalid for %s",
394 server_p->name);
395
396 rb_free(server_p->class_name);
397 server_p->class_name = rb_strdup("default");
398 }
399
400 if(strpbrk(server_p->host, "*?"))
401 return;
402
403 server_p->dns_query =
404 lookup_hostname(server_p->host, GET_SS_FAMILY(&server_p->my_ipnum), conf_dns_callback, server_p);
405 }
406
407 struct server_conf *
408 find_server_conf(const char *name)
409 {
410 struct server_conf *server_p;
411 rb_dlink_node *ptr;
412
413 RB_DLINK_FOREACH(ptr, server_conf_list.head)
414 {
415 server_p = ptr->data;
416
417 if(ServerConfIllegal(server_p))
418 continue;
419
420 if(match(name, server_p->name))
421 return server_p;
422 }
423
424 return NULL;
425 }
426
427 void
428 attach_server_conf(struct Client *client_p, struct server_conf *server_p)
429 {
430 /* already have an attached conf */
431 if(client_p->localClient->att_sconf)
432 {
433 /* short circuit this special case :) */
434 if(client_p->localClient->att_sconf == server_p)
435 return;
436
437 detach_server_conf(client_p);
438 }
439
440 CurrUsers(server_p->class)++;
441
442 client_p->localClient->att_sconf = server_p;
443 server_p->servers++;
444 }
445
446 void
447 detach_server_conf(struct Client *client_p)
448 {
449 struct server_conf *server_p = client_p->localClient->att_sconf;
450
451 if(server_p == NULL)
452 return;
453
454 client_p->localClient->att_sconf = NULL;
455 server_p->servers--;
456 CurrUsers(server_p->class)--;
457
458 if(ServerConfIllegal(server_p) && !server_p->servers)
459 {
460 /* the class this one is using may need destroying too */
461 if(MaxUsers(server_p->class) < 0 && CurrUsers(server_p->class) <= 0)
462 free_class(server_p->class);
463
464 rb_dlinkDelete(&server_p->node, &server_conf_list);
465 free_server_conf(server_p);
466 }
467 }
468
469 void
470 set_server_conf_autoconn(struct Client *source_p, const char *name, int newval)
471 {
472 struct server_conf *server_p;
473
474 if((server_p = find_server_conf(name)) != NULL)
475 {
476 if(newval)
477 server_p->flags |= SERVER_AUTOCONN;
478 else
479 server_p->flags &= ~SERVER_AUTOCONN;
480
481 sendto_realops_snomask(SNO_GENERAL, L_ALL,
482 "%s has changed AUTOCONN for %s to %i",
483 get_oper_name(source_p), name, newval);
484 }
485 else
486 sendto_one_notice(source_p, ":Can't find %s", name);
487 }
488
489 void
490 disable_server_conf_autoconn(const char *name)
491 {
492 struct server_conf *server_p;
493
494 server_p = find_server_conf(name);
495 if(server_p != NULL && server_p->flags & SERVER_AUTOCONN)
496 {
497 server_p->flags &= ~SERVER_AUTOCONN;
498
499 sendto_realops_snomask(SNO_GENERAL, L_ALL,
500 "Disabling AUTOCONN for %s because of error",
501 name);
502 ilog(L_SERVER, "Disabling AUTOCONN for %s because of error",
503 name);
504 }
505 }
506
507 struct ConfItem *
508 find_xline(const char *gecos, int counter)
509 {
510 struct ConfItem *aconf;
511 rb_dlink_node *ptr;
512
513 RB_DLINK_FOREACH(ptr, xline_conf_list.head)
514 {
515 aconf = ptr->data;
516
517 if(match_esc(aconf->host, gecos))
518 {
519 if(counter)
520 aconf->port++;
521 return aconf;
522 }
523 }
524
525 return NULL;
526 }
527
528 struct ConfItem *
529 find_xline_mask(const char *gecos)
530 {
531 struct ConfItem *aconf;
532 rb_dlink_node *ptr;
533
534 RB_DLINK_FOREACH(ptr, xline_conf_list.head)
535 {
536 aconf = ptr->data;
537
538 if(!irccmp(aconf->host, gecos))
539 return aconf;
540 }
541
542 return NULL;
543 }
544
545 struct ConfItem *
546 find_nick_resv(const char *name)
547 {
548 struct ConfItem *aconf;
549 rb_dlink_node *ptr;
550
551 RB_DLINK_FOREACH(ptr, resv_conf_list.head)
552 {
553 aconf = ptr->data;
554
555 if(match_esc(aconf->host, name))
556 {
557 aconf->port++;
558 return aconf;
559 }
560 }
561
562 return NULL;
563 }
564
565 struct ConfItem *
566 find_nick_resv_mask(const char *name)
567 {
568 struct ConfItem *aconf;
569 rb_dlink_node *ptr;
570
571 RB_DLINK_FOREACH(ptr, resv_conf_list.head)
572 {
573 aconf = ptr->data;
574
575 if(!irccmp(aconf->host, name))
576 return aconf;
577 }
578
579 return NULL;
580 }
581
582 /* clean_resv_nick()
583 *
584 * inputs - nick
585 * outputs - 1 if nick is vaild resv, 0 otherwise
586 * side effects -
587 */
588 int
589 clean_resv_nick(const char *nick)
590 {
591 char tmpch;
592 int as = 0;
593 int q = 0;
594 int ch = 0;
595
596 if(*nick == '-' || IsDigit(*nick))
597 return 0;
598
599 while ((tmpch = *nick++))
600 {
601 if(tmpch == '?' || tmpch == '@' || tmpch == '#')
602 q++;
603 else if(tmpch == '*')
604 as++;
605 else if(IsNickChar(tmpch))
606 ch++;
607 else
608 return 0;
609 }
610
611 if(!ch && as)
612 return 0;
613
614 return 1;
615 }
616
617 /* valid_wild_card_simple()
618 *
619 * inputs - "thing" to test
620 * outputs - 1 if enough wildcards, else 0
621 * side effects -
622 */
623 int
624 valid_wild_card_simple(const char *data)
625 {
626 const char *p;
627 char tmpch;
628 int nonwild = 0;
629 int wild = 0;
630
631 /* check the string for minimum number of nonwildcard chars */
632 p = data;
633
634 while((tmpch = *p++))
635 {
636 /* found an escape, p points to the char after it, so skip
637 * that and move on.
638 */
639 if(tmpch == '\\' && *p)
640 {
641 p++;
642 if(++nonwild >= ConfigFileEntry.min_nonwildcard_simple)
643 return 1;
644 }
645 else if(!IsMWildChar(tmpch))
646 {
647 /* if we have enough nonwildchars, return */
648 if(++nonwild >= ConfigFileEntry.min_nonwildcard_simple)
649 return 1;
650 }
651 else
652 wild++;
653 }
654
655 /* strings without wilds are also ok */
656 return wild == 0;
657 }
658
659 time_t
660 valid_temp_time(const char *p)
661 {
662 time_t result = 0;
663
664 while(*p)
665 {
666 if(IsDigit(*p))
667 {
668 result *= 10;
669 result += ((*p) & 0xF);
670 p++;
671 }
672 else
673 return -1;
674 }
675
676 if(result > (60 * 24 * 7 * 52))
677 result = (60 * 24 * 7 * 52);
678
679 return(result * 60);
680 }
681
682 /* Propagated bans are expired elsewhere. */
683 static void
684 expire_temp_rxlines(void *unused)
685 {
686 struct ConfItem *aconf;
687 rb_dlink_node *ptr;
688 rb_dlink_node *next_ptr;
689 int i;
690
691 HASH_WALK_SAFE(i, R_MAX, ptr, next_ptr, resvTable)
692 {
693 aconf = ptr->data;
694
695 if(aconf->lifetime != 0)
696 continue;
697 if(aconf->hold && aconf->hold <= rb_current_time())
698 {
699 if(ConfigFileEntry.tkline_expire_notices)
700 sendto_realops_snomask(SNO_GENERAL, L_ALL,
701 "Temporary RESV for [%s] expired",
702 aconf->host);
703
704 free_conf(aconf);
705 rb_dlinkDestroy(ptr, &resvTable[i]);
706 }
707 }
708 HASH_WALK_END
709
710 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, resv_conf_list.head)
711 {
712 aconf = ptr->data;
713
714 if(aconf->lifetime != 0)
715 continue;
716 if(aconf->hold && aconf->hold <= rb_current_time())
717 {
718 if(ConfigFileEntry.tkline_expire_notices)
719 sendto_realops_snomask(SNO_GENERAL, L_ALL,
720 "Temporary RESV for [%s] expired",
721 aconf->host);
722 free_conf(aconf);
723 rb_dlinkDestroy(ptr, &resv_conf_list);
724 }
725 }
726
727 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, xline_conf_list.head)
728 {
729 aconf = ptr->data;
730
731 if(aconf->lifetime != 0)
732 continue;
733 if(aconf->hold && aconf->hold <= rb_current_time())
734 {
735 if(ConfigFileEntry.tkline_expire_notices)
736 sendto_realops_snomask(SNO_GENERAL, L_ALL,
737 "Temporary X-line for [%s] expired",
738 aconf->host);
739 free_conf(aconf);
740 rb_dlinkDestroy(ptr, &xline_conf_list);
741 }
742 }
743 }
744
745 unsigned long
746 get_nd_count(void)
747 {
748 return(rb_dlink_list_length(&nd_list));
749 }
750
751 void
752 add_nd_entry(const char *name)
753 {
754 struct nd_entry *nd;
755
756 if(irc_dictionary_find(nd_dict, name) != NULL)
757 return;
758
759 nd = rb_bh_alloc(nd_heap);
760
761 rb_strlcpy(nd->name, name, sizeof(nd->name));
762 nd->expire = rb_current_time() + ConfigFileEntry.nick_delay;
763
764 /* this list is ordered */
765 rb_dlinkAddTail(nd, &nd->lnode, &nd_list);
766
767 irc_dictionary_add(nd_dict, nd->name, nd);
768 }
769
770 void
771 free_nd_entry(struct nd_entry *nd)
772 {
773 irc_dictionary_delete(nd_dict, nd->name);
774
775 rb_dlinkDelete(&nd->lnode, &nd_list);
776 rb_bh_free(nd_heap, nd);
777 }
778
779 void
780 expire_nd_entries(void *unused)
781 {
782 struct nd_entry *nd;
783 rb_dlink_node *ptr;
784 rb_dlink_node *next_ptr;
785
786 RB_DLINK_FOREACH_SAFE(ptr, next_ptr, nd_list.head)
787 {
788 nd = ptr->data;
789
790 /* this list is ordered - we can stop when we hit the first
791 * entry that doesnt expire..
792 */
793 if(nd->expire > rb_current_time())
794 return;
795
796 free_nd_entry(nd);
797 }
798 }
799
800 void
801 add_tgchange(const char *host)
802 {
803 tgchange *target;
804 rb_patricia_node_t *pnode;
805
806 if(find_tgchange(host))
807 return;
808
809 target = rb_malloc(sizeof(tgchange));
810 pnode = make_and_lookup(tgchange_tree, host);
811
812 pnode->data = target;
813 target->pnode = pnode;
814
815 target->ip = rb_strdup(host);
816 target->expiry = rb_current_time() + (60*60*12);
817
818 rb_dlinkAdd(target, &target->node, &tgchange_list);
819 }
820
821 tgchange *
822 find_tgchange(const char *host)
823 {
824 rb_patricia_node_t *pnode;
825
826 if((pnode = rb_match_exact_string(tgchange_tree, host)))
827 return pnode->data;
828
829 return NULL;
830 }
831