angiosperm/ircd/s_newconf.c

889 lines
19 KiB
C

/*
* ircd-ratbox: an advanced Internet Relay Chat Daemon(ircd).
* s_newconf.c - code for dealing with conf stuff
*
* Copyright (C) 2004 Lee Hardy <lee@leeh.co.uk>
* Copyright (C) 2004-2005 ircd-ratbox development team
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1.Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2.Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3.The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "stdinc.h"
#include "ircd_defs.h"
#include "s_conf.h"
#include "s_newconf.h"
#include "client.h"
#include "s_serv.h"
#include "send.h"
#include "hostmask.h"
#include "newconf.h"
#include "hash.h"
#include "rb_dictionary.h"
#include "rb_radixtree.h"
#include "s_assert.h"
#include "logger.h"
#include "dns.h"
rb_dlink_list cluster_conf_list;
rb_dlink_list oper_conf_list;
rb_dlink_list server_conf_list;
rb_dlink_list xline_conf_list;
rb_dlink_list resv_conf_list; /* nicks only! */
rb_dlink_list nd_list; /* nick delay */
rb_dlink_list tgchange_list;
rb_patricia_tree_t *tgchange_tree;
static rb_bh *nd_heap = NULL;
static void expire_temp_rxlines(void *unused);
static void expire_nd_entries(void *unused);
struct ev_entry *expire_nd_entries_ev = NULL;
struct ev_entry *expire_temp_rxlines_ev = NULL;
void
init_s_newconf(void)
{
tgchange_tree = rb_new_patricia(PATRICIA_BITS);
nd_heap = rb_bh_create(sizeof(struct nd_entry), ND_HEAP_SIZE, "nd_heap");
expire_nd_entries_ev = rb_event_addish("expire_nd_entries", expire_nd_entries, NULL, 30);
expire_temp_rxlines_ev = rb_event_addish("expire_temp_rxlines", expire_temp_rxlines, NULL, 60);
}
void
clear_s_newconf(void)
{
struct server_conf *server_p;
rb_dlink_node *ptr;
rb_dlink_node *next_ptr;
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, cluster_conf_list.head)
{
rb_dlinkDelete(ptr, &cluster_conf_list);
free_remote_conf(ptr->data);
}
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, oper_conf_list.head)
{
free_oper_conf(ptr->data);
rb_dlinkDestroy(ptr, &oper_conf_list);
}
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, server_conf_list.head)
{
server_p = ptr->data;
if(!server_p->servers)
{
rb_dlinkDelete(ptr, &server_conf_list);
free_server_conf(ptr->data);
}
else
server_p->flags |= SERVER_ILLEGAL;
}
}
void
clear_s_newconf_bans(void)
{
struct ConfItem *aconf;
rb_dlink_node *ptr, *next_ptr;
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, xline_conf_list.head)
{
aconf = ptr->data;
if(aconf->hold)
continue;
free_conf(aconf);
rb_dlinkDestroy(ptr, &xline_conf_list);
}
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, resv_conf_list.head)
{
aconf = ptr->data;
/* temporary resv */
if(aconf->hold)
continue;
free_conf(aconf);
rb_dlinkDestroy(ptr, &resv_conf_list);
}
clear_resv_hash();
}
struct remote_conf *
make_remote_conf(void)
{
struct remote_conf *remote_p = rb_malloc(sizeof(struct remote_conf));
return remote_p;
}
void
free_remote_conf(struct remote_conf *remote_p)
{
s_assert(remote_p != NULL);
if(remote_p == NULL)
return;
rb_free(remote_p->username);
rb_free(remote_p->host);
rb_free(remote_p->server);
rb_free(remote_p);
}
void
propagate_generic(struct Client *source_p, const char *command,
const char *target, int cap, const char *format, ...)
{
char buffer[BUFSIZE];
va_list args;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
sendto_match_servs(source_p, target, cap, NOCAPS,
"%s %s %s",
command, target, buffer);
sendto_match_servs(source_p, target, CAP_ENCAP, cap,
"ENCAP %s %s %s",
target, command, buffer);
}
void
cluster_generic(struct Client *source_p, const char *command,
int cltype, int cap, const char *format, ...)
{
char buffer[BUFSIZE];
struct remote_conf *shared_p;
va_list args;
rb_dlink_node *ptr;
va_start(args, format);
vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
RB_DLINK_FOREACH(ptr, cluster_conf_list.head)
{
shared_p = ptr->data;
if(!(shared_p->flags & cltype))
continue;
sendto_match_servs(source_p, shared_p->server, cap, NOCAPS,
"%s %s %s",
command, shared_p->server, buffer);
sendto_match_servs(source_p, shared_p->server, CAP_ENCAP, cap,
"ENCAP %s %s %s",
shared_p->server, command, buffer);
}
}
struct oper_conf *
make_oper_conf(void)
{
struct oper_conf *oper_p = rb_malloc(sizeof(struct oper_conf));
return oper_p;
}
void
free_oper_conf(struct oper_conf *oper_p)
{
s_assert(oper_p != NULL);
if(oper_p == NULL)
return;
rb_free(oper_p->username);
rb_free(oper_p->host);
rb_free(oper_p->name);
rb_free(oper_p->certfp);
if(oper_p->passwd)
{
memset(oper_p->passwd, 0, strlen(oper_p->passwd));
rb_free(oper_p->passwd);
}
#ifdef HAVE_LIBCRYPTO
rb_free(oper_p->rsa_pubkey_file);
if(oper_p->rsa_pubkey)
RSA_free(oper_p->rsa_pubkey);
#endif
rb_free(oper_p);
}
struct oper_conf *
find_oper_conf(const char *username, const char *host, const char *locip, const char *name)
{
struct oper_conf *oper_p;
struct rb_sockaddr_storage ip, cip;
char addr[HOSTLEN+1];
int bits, cbits;
rb_dlink_node *ptr;
parse_netmask(locip, &cip, &cbits);
RB_DLINK_FOREACH(ptr, oper_conf_list.head)
{
oper_p = ptr->data;
/* name/username doesnt match.. */
if(irccmp(oper_p->name, name) || !match(oper_p->username, username))
continue;
rb_strlcpy(addr, oper_p->host, sizeof(addr));
if(parse_netmask(addr, &ip, &bits) != HM_HOST)
{
if(GET_SS_FAMILY(&ip) == GET_SS_FAMILY(&cip) &&
comp_with_mask_sock((struct sockaddr *)&ip, (struct sockaddr *)&cip, bits))
return oper_p;
}
/* we have to compare against the host as well, because its
* valid to set a spoof to an IP, which if we only compare
* in ip form to sockhost will not necessarily match --anfl
*/
if(match(oper_p->host, host))
return oper_p;
}
return NULL;
}
struct server_conf *
make_server_conf(void)
{
struct server_conf *server_p = rb_malloc(sizeof(struct server_conf));
SET_SS_FAMILY(&server_p->connect4, AF_UNSPEC);
SET_SS_LEN(&server_p->connect4, sizeof(struct sockaddr_in));
SET_SS_FAMILY(&server_p->bind4, AF_UNSPEC);
SET_SS_LEN(&server_p->bind4, sizeof(struct sockaddr_in));
SET_SS_FAMILY(&server_p->connect6, AF_UNSPEC);
SET_SS_LEN(&server_p->connect6, sizeof(struct sockaddr_in6));
SET_SS_FAMILY(&server_p->bind6, AF_UNSPEC);
SET_SS_LEN(&server_p->bind6, sizeof(struct sockaddr_in6));
server_p->aftype = AF_UNSPEC;
return server_p;
}
void
free_server_conf(struct server_conf *server_p)
{
s_assert(server_p != NULL);
if(server_p == NULL)
return;
if(!EmptyString(server_p->passwd))
{
memset(server_p->passwd, 0, strlen(server_p->passwd));
rb_free(server_p->passwd);
}
if(!EmptyString(server_p->spasswd))
{
memset(server_p->spasswd, 0, strlen(server_p->spasswd));
rb_free(server_p->spasswd);
}
rb_free(server_p->name);
rb_free(server_p->connect_host);
rb_free(server_p->bind_host);
rb_free(server_p->class_name);
rb_free(server_p->certfp);
rb_free(server_p);
}
/*
* conf_connect_dns_callback
* inputs - pointer to struct ConfItem
* - pointer to adns reply
* output - none
* side effects - called when resolver query finishes
* if the query resulted in a successful search, hp will contain
* a non-null pointer, otherwise hp will be null.
* if successful save hp in the conf item it was called with
*/
static void
conf_connect_dns_callback(const char *result, int status, int aftype, void *data)
{
struct server_conf *server_p = data;
if(aftype == AF_INET)
{
if(status == 1)
rb_inet_pton_sock(result, &server_p->connect4);
server_p->dns_query_connect4 = 0;
}
else if(aftype == AF_INET6)
{
if(status == 1)
rb_inet_pton_sock(result, &server_p->connect6);
server_p->dns_query_connect6 = 0;
}
}
/*
* conf_bind_dns_callback
* inputs - pointer to struct ConfItem
* - pointer to adns reply
* output - none
* side effects - called when resolver query finishes
* if the query resulted in a successful search, hp will contain
* a non-null pointer, otherwise hp will be null.
* if successful save hp in the conf item it was called with
*/
static void
conf_bind_dns_callback(const char *result, int status, int aftype, void *data)
{
struct server_conf *server_p = data;
if(aftype == AF_INET)
{
if(status == 1)
rb_inet_pton_sock(result, &server_p->bind4);
server_p->dns_query_bind4 = 0;
}
else if(aftype == AF_INET6)
{
if(status == 1)
rb_inet_pton_sock(result, &server_p->bind6);
server_p->dns_query_bind6 = 0;
}
}
void
add_server_conf(struct server_conf *server_p)
{
if(EmptyString(server_p->class_name))
{
server_p->class_name = rb_strdup("default");
server_p->class = default_class;
return;
}
server_p->class = find_class(server_p->class_name);
if(server_p->class == default_class)
{
conf_report_error("Warning connect::class invalid for %s",
server_p->name);
rb_free(server_p->class_name);
server_p->class_name = rb_strdup("default");
}
if(server_p->connect_host && !strpbrk(server_p->connect_host, "*?"))
{
server_p->dns_query_connect4 =
lookup_hostname(server_p->connect_host, AF_INET, conf_connect_dns_callback, server_p);
server_p->dns_query_connect6 =
lookup_hostname(server_p->connect_host, AF_INET6, conf_connect_dns_callback, server_p);
}
if(server_p->bind_host)
{
server_p->dns_query_bind4 =
lookup_hostname(server_p->bind_host, AF_INET, conf_bind_dns_callback, server_p);
server_p->dns_query_bind6 =
lookup_hostname(server_p->bind_host, AF_INET6, conf_bind_dns_callback, server_p);
}
}
struct server_conf *
find_server_conf(const char *name)
{
struct server_conf *server_p;
rb_dlink_node *ptr;
RB_DLINK_FOREACH(ptr, server_conf_list.head)
{
server_p = ptr->data;
if(ServerConfIllegal(server_p))
continue;
if(match(name, server_p->name))
return server_p;
}
return NULL;
}
void
attach_server_conf(struct Client *client_p, struct server_conf *server_p)
{
/* already have an attached conf */
if(client_p->localClient->att_sconf)
{
/* short circuit this special case :) */
if(client_p->localClient->att_sconf == server_p)
return;
detach_server_conf(client_p);
}
CurrUsers(server_p->class)++;
client_p->localClient->att_sconf = server_p;
server_p->servers++;
}
void
detach_server_conf(struct Client *client_p)
{
struct server_conf *server_p = client_p->localClient->att_sconf;
if(server_p == NULL)
return;
client_p->localClient->att_sconf = NULL;
server_p->servers--;
CurrUsers(server_p->class)--;
if(ServerConfIllegal(server_p) && !server_p->servers)
{
/* the class this one is using may need destroying too */
if(MaxUsers(server_p->class) < 0 && CurrUsers(server_p->class) <= 0)
free_class(server_p->class);
rb_dlinkDelete(&server_p->node, &server_conf_list);
free_server_conf(server_p);
}
}
void
set_server_conf_autoconn(struct Client *source_p, const char *name, int newval)
{
struct server_conf *server_p;
if((server_p = find_server_conf(name)) != NULL)
{
if(newval)
server_p->flags |= SERVER_AUTOCONN;
else
server_p->flags &= ~SERVER_AUTOCONN;
sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"%s has changed AUTOCONN for %s to %i",
get_oper_name(source_p), name, newval);
}
else
sendto_one_notice(source_p, ":Can't find %s", name);
}
void
disable_server_conf_autoconn(const char *name)
{
struct server_conf *server_p;
server_p = find_server_conf(name);
if(server_p != NULL && server_p->flags & SERVER_AUTOCONN)
{
server_p->flags &= ~SERVER_AUTOCONN;
sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"Disabling AUTOCONN for %s because of error",
name);
ilog(L_SERVER, "Disabling AUTOCONN for %s because of error",
name);
}
}
struct ConfItem *
find_xline(const char *gecos, int counter)
{
struct ConfItem *aconf;
rb_dlink_node *ptr;
RB_DLINK_FOREACH(ptr, xline_conf_list.head)
{
aconf = ptr->data;
if(match_esc(aconf->host, gecos))
{
if(counter)
aconf->port++;
return aconf;
}
}
return NULL;
}
struct ConfItem *
find_xline_mask(const char *gecos)
{
struct ConfItem *aconf;
rb_dlink_node *ptr;
RB_DLINK_FOREACH(ptr, xline_conf_list.head)
{
aconf = ptr->data;
if(!irccmp(aconf->host, gecos))
return aconf;
}
return NULL;
}
struct ConfItem *
find_nick_resv(const char *name)
{
struct ConfItem *aconf;
rb_dlink_node *ptr;
RB_DLINK_FOREACH(ptr, resv_conf_list.head)
{
aconf = ptr->data;
if(match_esc(aconf->host, name))
{
aconf->port++;
return aconf;
}
}
return NULL;
}
struct ConfItem *
find_nick_resv_mask(const char *name)
{
struct ConfItem *aconf;
rb_dlink_node *ptr;
RB_DLINK_FOREACH(ptr, resv_conf_list.head)
{
aconf = ptr->data;
if(!irccmp(aconf->host, name))
return aconf;
}
return NULL;
}
/* clean_resv_nick()
*
* inputs - nick
* outputs - 1 if nick is vaild resv, 0 otherwise
* side effects -
*/
int
clean_resv_nick(const char *nick)
{
char tmpch;
int as = 0;
int q = 0;
int ch = 0;
if(*nick == '-' || IsDigit(*nick))
return 0;
while ((tmpch = *nick++))
{
if(tmpch == '?' || tmpch == '@' || tmpch == '#')
q++;
else if(tmpch == '*')
as++;
else if(IsNickChar(tmpch))
ch++;
else
return 0;
}
if(!ch && as)
return 0;
return 1;
}
/* valid_wild_card_simple()
*
* inputs - "thing" to test
* outputs - 1 if enough wildcards, else 0
* side effects -
*/
int
valid_wild_card_simple(const char *data)
{
const char *p;
char tmpch;
int nonwild = 0;
int wild = 0;
/* check the string for minimum number of nonwildcard chars */
p = data;
while((tmpch = *p++))
{
/* found an escape, p points to the char after it, so skip
* that and move on.
*/
if(tmpch == '\\' && *p)
{
p++;
if(++nonwild >= ConfigFileEntry.min_nonwildcard_simple)
return 1;
}
else if(!IsMWildChar(tmpch))
{
/* if we have enough nonwildchars, return */
if(++nonwild >= ConfigFileEntry.min_nonwildcard_simple)
return 1;
}
else
wild++;
}
/* strings without wilds are also ok */
return wild == 0;
}
time_t
valid_temp_time(const char *p)
{
time_t result = 0;
long current = 0;
time_t max_time = (uintmax_t) (~(time_t)0) >> 1;
while (*p) {
char *endp;
int mul;
errno = 0;
current = strtol(p, &endp, 10);
if (errno == ERANGE)
return -1;
if (endp == p)
return -1;
if (current < 0)
return -1;
switch (*endp) {
case '\0': /* No unit was given so send it back as minutes */
case 'm':
mul = 60;
break;
case 'h':
mul = 3600;
break;
case 'd':
mul = 86400;
break;
case 'w':
mul = 604800;
break;
default:
return -1;
}
if (current > LONG_MAX / mul)
return MAX_TEMP_TIME;
current *= mul;
if (current > max_time - result)
return MAX_TEMP_TIME;
result += current;
if (*endp == '\0')
break;
p = endp + 1;
}
return MIN(result, MAX_TEMP_TIME);
}
/* Propagated bans are expired elsewhere. */
static void
expire_temp_rxlines(void *unused)
{
struct ConfItem *aconf;
rb_dlink_node *ptr;
rb_dlink_node *next_ptr;
rb_radixtree_iteration_state state;
RB_RADIXTREE_FOREACH(aconf, &state, resv_tree)
{
if(aconf->lifetime != 0)
continue;
if(aconf->hold && aconf->hold <= rb_current_time())
{
if(ConfigFileEntry.tkline_expire_notices)
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"Temporary RESV for [%s] expired",
aconf->host);
rb_radixtree_delete(resv_tree, aconf->host);
free_conf(aconf);
}
}
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, resv_conf_list.head)
{
aconf = ptr->data;
if(aconf->lifetime != 0)
continue;
if(aconf->hold && aconf->hold <= rb_current_time())
{
if(ConfigFileEntry.tkline_expire_notices)
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"Temporary RESV for [%s] expired",
aconf->host);
free_conf(aconf);
rb_dlinkDestroy(ptr, &resv_conf_list);
}
}
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, xline_conf_list.head)
{
aconf = ptr->data;
if(aconf->lifetime != 0)
continue;
if(aconf->hold && aconf->hold <= rb_current_time())
{
if(ConfigFileEntry.tkline_expire_notices)
sendto_realops_snomask(SNO_GENERAL, L_ALL,
"Temporary X-line for [%s] expired",
aconf->host);
free_conf(aconf);
rb_dlinkDestroy(ptr, &xline_conf_list);
}
}
}
unsigned long
get_nd_count(void)
{
return(rb_dlink_list_length(&nd_list));
}
void
add_nd_entry(const char *name)
{
struct nd_entry *nd;
if(rb_dictionary_find(nd_dict, name) != NULL)
return;
nd = rb_bh_alloc(nd_heap);
rb_strlcpy(nd->name, name, sizeof(nd->name));
nd->expire = rb_current_time() + ConfigFileEntry.nick_delay;
/* this list is ordered */
rb_dlinkAddTail(nd, &nd->lnode, &nd_list);
rb_dictionary_add(nd_dict, nd->name, nd);
}
void
free_nd_entry(struct nd_entry *nd)
{
rb_dictionary_delete(nd_dict, nd->name);
rb_dlinkDelete(&nd->lnode, &nd_list);
rb_bh_free(nd_heap, nd);
}
void
expire_nd_entries(void *unused)
{
struct nd_entry *nd;
rb_dlink_node *ptr;
rb_dlink_node *next_ptr;
RB_DLINK_FOREACH_SAFE(ptr, next_ptr, nd_list.head)
{
nd = ptr->data;
/* this list is ordered - we can stop when we hit the first
* entry that doesnt expire..
*/
if(nd->expire > rb_current_time())
return;
free_nd_entry(nd);
}
}
void
add_tgchange(const char *host)
{
tgchange *target;
rb_patricia_node_t *pnode;
if(find_tgchange(host))
return;
target = rb_malloc(sizeof(tgchange));
pnode = make_and_lookup(tgchange_tree, host);
pnode->data = target;
target->pnode = pnode;
target->ip = rb_strdup(host);
target->expiry = rb_current_time() + (60*60*12);
rb_dlinkAdd(target, &target->node, &tgchange_list);
}
tgchange *
find_tgchange(const char *host)
{
rb_patricia_node_t *pnode;
if((pnode = rb_match_exact_string(tgchange_tree, host)))
return pnode->data;
return NULL;
}