2020-03-29 09:16:53 +00:00
|
|
|
/************************************************************************
|
|
|
|
* IRC - Internet Relay Chat, src/dns.c
|
|
|
|
* (C) 2005 Bram Matthys (Syzop) and the UnrealIRCd Team
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 1, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "unrealircd.h"
|
|
|
|
#include "dns.h"
|
|
|
|
|
|
|
|
#if !defined(UNREAL_VERSION_TIME)
|
|
|
|
#error "YOU MUST RUN ./Config WHENEVER YOU ARE UPGRADING UNREAL!!!!"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/* Prevent crashes due to invalid prototype/ABI.
|
|
|
|
* And force the use of at least the version shipped with Unreal
|
|
|
|
* (or at least one without known security issues).
|
|
|
|
*/
|
|
|
|
#if ARES_VERSION < 0x010600
|
|
|
|
#error "You have an old c-ares version on your system and/or Unreals c-ares failed to compile!"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* Forward declerations */
|
|
|
|
void unrealdns_cb_iptoname(void *arg, int status, int timeouts, struct hostent *he);
|
|
|
|
void unrealdns_cb_nametoip_verify(void *arg, int status, int timeouts, struct hostent *he);
|
|
|
|
void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct hostent *he);
|
|
|
|
void unrealdns_delasyncconnects(void);
|
|
|
|
static uint64_t unrealdns_hash_ip(const char *ip);
|
|
|
|
static void unrealdns_addtocache(char *name, char *ip);
|
|
|
|
static char *unrealdns_findcache_ip(char *ip);
|
|
|
|
struct hostent *unreal_create_hostent(char *name, char *ip);
|
|
|
|
static void unrealdns_freeandremovereq(DNSReq *r);
|
|
|
|
void unrealdns_removecacherecord(DNSCache *c);
|
|
|
|
|
|
|
|
/* Externs */
|
|
|
|
extern void proceed_normal_client_handshake(Client *client, struct hostent *he);
|
|
|
|
|
|
|
|
/* Global variables */
|
|
|
|
|
|
|
|
ares_channel resolver_channel; /**< The resolver channel. */
|
|
|
|
|
|
|
|
DNSStats dnsstats;
|
|
|
|
|
|
|
|
static DNSReq *requests = NULL; /**< Linked list of requests (pending responses). */
|
|
|
|
|
|
|
|
static DNSCache *cache_list = NULL; /**< Linked list of cache */
|
|
|
|
static DNSCache *cache_hashtbl[DNS_HASH_SIZE]; /**< Hash table of cache */
|
|
|
|
|
|
|
|
static unsigned int unrealdns_num_cache = 0; /**< # of cache entries in memory */
|
|
|
|
|
|
|
|
static char siphashkey_dns_ip[SIPHASH_KEY_LENGTH];
|
|
|
|
|
|
|
|
static void unrealdns_io_cb(int fd, int revents, void *data)
|
|
|
|
{
|
|
|
|
ares_socket_t read_fd, write_fd;
|
|
|
|
FDEntry *fde;
|
|
|
|
|
|
|
|
read_fd = write_fd = ARES_SOCKET_BAD;
|
|
|
|
fde = &fd_table[fd];
|
|
|
|
|
|
|
|
if (revents & FD_SELECT_READ)
|
|
|
|
read_fd = fde->fd;
|
|
|
|
|
|
|
|
if (revents & FD_SELECT_WRITE)
|
|
|
|
write_fd = fde->fd;
|
|
|
|
|
|
|
|
ares_process_fd(resolver_channel, read_fd, write_fd);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unrealdns_sock_state_cb(void *data, ares_socket_t fd, int read, int write)
|
|
|
|
{
|
|
|
|
int selflags = 0;
|
|
|
|
|
|
|
|
if (!read && !write)
|
|
|
|
{
|
|
|
|
/* Socket is going to be closed *BY C-ARES*..
|
|
|
|
* so don't call fd_close() but fd_unmap().
|
|
|
|
*/
|
|
|
|
fd_unmap(fd);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (read)
|
|
|
|
selflags |= FD_SELECT_READ;
|
|
|
|
|
|
|
|
if (write)
|
|
|
|
selflags |= FD_SELECT_WRITE;
|
|
|
|
|
|
|
|
fd_setselect(fd, selflags, unrealdns_io_cb, data);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Who thought providing a socket OPEN callback without a socket CLOSE callback was
|
|
|
|
* a good idea...? --nenolod
|
|
|
|
*/
|
|
|
|
static int unrealdns_sock_create_cb(ares_socket_t fd, int type, void *data)
|
|
|
|
{
|
|
|
|
fd_open(fd, "DNS Resolver Socket");
|
|
|
|
return ARES_SUCCESS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static EVENT(unrealdns_timeout)
|
|
|
|
{
|
|
|
|
ares_process_fd(resolver_channel, ARES_SOCKET_BAD, ARES_SOCKET_BAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
static Event *unrealdns_timeout_hdl = NULL;
|
|
|
|
|
|
|
|
void init_resolver(int firsttime)
|
|
|
|
{
|
|
|
|
struct ares_options options;
|
|
|
|
int n;
|
|
|
|
int optmask;
|
|
|
|
|
|
|
|
if (requests)
|
|
|
|
abort(); /* should never happen */
|
|
|
|
|
|
|
|
if (firsttime)
|
|
|
|
{
|
|
|
|
memset(&cache_hashtbl, 0, sizeof(cache_hashtbl));
|
|
|
|
memset(&dnsstats, 0, sizeof(dnsstats));
|
|
|
|
siphash_generate_key(siphashkey_dns_ip);
|
|
|
|
ares_library_init(ARES_LIB_INIT_ALL);
|
|
|
|
}
|
|
|
|
|
|
|
|
memset(&options, 0, sizeof(options));
|
|
|
|
options.timeout = 1500; /* 1.5 seconds */
|
|
|
|
options.tries = 2;
|
|
|
|
/* Note that the effective DNS timeout is NOT simply 1500*2=3000.
|
|
|
|
* This is because c-ares does some incremental timeout stuff itself
|
|
|
|
* that may add up to twice the timeout in the second round,
|
|
|
|
* so effective max is 1500ms first and then up to 3000s, so 4500ms in total
|
|
|
|
* (until they change the algorithm again, that is...).
|
|
|
|
*/
|
|
|
|
options.flags |= ARES_FLAG_NOALIASES|ARES_FLAG_IGNTC;
|
|
|
|
options.sock_state_cb = unrealdns_sock_state_cb;
|
|
|
|
optmask = ARES_OPT_TIMEOUTMS|ARES_OPT_TRIES|ARES_OPT_FLAGS|ARES_OPT_SOCK_STATE_CB;
|
|
|
|
#ifndef _WIN32
|
|
|
|
/* on *NIX don't use the hosts file, since it causes countless useless reads.
|
|
|
|
* on Windows we use it for now, this could be changed in the future.
|
|
|
|
*/
|
|
|
|
options.lookups = "b";
|
|
|
|
optmask |= ARES_OPT_LOOKUPS;
|
|
|
|
#endif
|
|
|
|
n = ares_init_options(&resolver_channel, &options, optmask);
|
|
|
|
if (n != ARES_SUCCESS)
|
|
|
|
{
|
|
|
|
/* FATAL */
|
|
|
|
config_error("resolver: ares_init_options() failed with error code %d [%s]", n, ares_strerror(n));
|
|
|
|
#ifdef _WIN32
|
|
|
|
win_error();
|
|
|
|
#endif
|
|
|
|
exit(-7);
|
|
|
|
}
|
|
|
|
|
|
|
|
ares_set_socket_callback(resolver_channel, unrealdns_sock_create_cb, NULL);
|
|
|
|
unrealdns_timeout_hdl = EventAdd(NULL, "unrealdns_timeout", unrealdns_timeout, NULL, 500, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void reinit_resolver(Client *client)
|
|
|
|
{
|
|
|
|
EventDel(unrealdns_timeout_hdl);
|
|
|
|
|
|
|
|
sendto_ops_and_log("%s requested reinitalization of resolver!", client->name);
|
|
|
|
sendto_realops("Destroying resolver channel, along with all currently pending queries...");
|
|
|
|
ares_destroy(resolver_channel);
|
|
|
|
sendto_realops("Initializing resolver again...");
|
|
|
|
init_resolver(0);
|
|
|
|
sendto_realops("Reinitalization finished successfully.");
|
|
|
|
}
|
|
|
|
|
|
|
|
void unrealdns_addreqtolist(DNSReq *r)
|
|
|
|
{
|
|
|
|
if (requests)
|
|
|
|
{
|
|
|
|
r->next = requests;
|
|
|
|
requests->prev = r;
|
|
|
|
}
|
|
|
|
requests = r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get (and verify) the host for an incoming client.
|
|
|
|
* - it checks the cache first, returns the host if found (and valid).
|
|
|
|
* - if not found in cache it does ip->name and then name->ip, if both resolve
|
|
|
|
* to the same name it is accepted, otherwise not.
|
|
|
|
* We return NULL in this case and an asynchronic request is done.
|
|
|
|
* When done, proceed_normal_client_handshake() is called.
|
|
|
|
*/
|
|
|
|
struct hostent *unrealdns_doclient(Client *client)
|
|
|
|
{
|
|
|
|
DNSReq *r;
|
|
|
|
char *cache_name;
|
|
|
|
|
|
|
|
cache_name = unrealdns_findcache_ip(client->ip);
|
|
|
|
if (cache_name)
|
|
|
|
return unreal_create_hostent(cache_name, client->ip);
|
|
|
|
|
|
|
|
/* Create a request */
|
|
|
|
r = safe_alloc(sizeof(DNSReq));
|
|
|
|
r->client = client;
|
|
|
|
r->ipv6 = IsIPV6(client);
|
|
|
|
unrealdns_addreqtolist(r);
|
|
|
|
|
|
|
|
/* Execute it */
|
|
|
|
if (r->ipv6)
|
|
|
|
{
|
|
|
|
struct in6_addr addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
inet_pton(AF_INET6, client->ip, &addr);
|
|
|
|
ares_gethostbyaddr(resolver_channel, &addr, 16, AF_INET6, unrealdns_cb_iptoname, r);
|
|
|
|
} else {
|
|
|
|
struct in_addr addr;
|
|
|
|
memset(&addr, 0, sizeof(addr));
|
|
|
|
inet_pton(AF_INET, client->ip, &addr);
|
|
|
|
ares_gethostbyaddr(resolver_channel, &addr, 4, AF_INET, unrealdns_cb_iptoname, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Resolve a name to an IP, for a link block.
|
|
|
|
*/
|
|
|
|
void unrealdns_gethostbyname_link(char *name, ConfigItem_link *conf, int ipv4_only)
|
|
|
|
{
|
|
|
|
DNSReq *r;
|
|
|
|
|
|
|
|
/* Create a request */
|
|
|
|
r = safe_alloc(sizeof(DNSReq));
|
|
|
|
r->linkblock = conf;
|
|
|
|
safe_strdup(r->name, name);
|
|
|
|
if (!DISABLE_IPV6 && !ipv4_only)
|
|
|
|
{
|
|
|
|
/* We try an IPv6 lookup first, and if that fails we try IPv4. */
|
|
|
|
r->ipv6 = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
unrealdns_addreqtolist(r);
|
|
|
|
|
|
|
|
/* Execute it */
|
|
|
|
ares_gethostbyname(resolver_channel, r->name, r->ipv6 ? AF_INET6 : AF_INET, unrealdns_cb_nametoip_link, r);
|
|
|
|
}
|
|
|
|
|
|
|
|
void unrealdns_cb_iptoname(void *arg, int status, int timeouts, struct hostent *he)
|
|
|
|
{
|
|
|
|
DNSReq *r = (DNSReq *)arg;
|
|
|
|
DNSReq *newr;
|
|
|
|
Client *client = r->client;
|
|
|
|
char ipv6 = r->ipv6;
|
|
|
|
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
|
|
|
|
if (!client)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Check for status and null name (yes, we must) */
|
|
|
|
if ((status != 0) || !he->h_name || !*he->h_name)
|
|
|
|
{
|
|
|
|
/* Failed */
|
|
|
|
proceed_normal_client_handshake(client, NULL);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Good, we got a valid response, now prepare for name -> ip */
|
|
|
|
newr = safe_alloc(sizeof(DNSReq));
|
|
|
|
newr->client = client;
|
|
|
|
newr->ipv6 = ipv6;
|
|
|
|
safe_strdup(newr->name, he->h_name);
|
|
|
|
unrealdns_addreqtolist(newr);
|
|
|
|
|
|
|
|
ares_gethostbyname(resolver_channel, he->h_name, ipv6 ? AF_INET6 : AF_INET, unrealdns_cb_nametoip_verify, newr);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
returns:
|
|
|
|
1 = good hostname
|
|
|
|
0 = bad hostname
|
|
|
|
*/
|
|
|
|
int verify_hostname(char *name)
|
|
|
|
{
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
if (strlen(name) > HOSTLEN)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* No underscores or other illegal characters */
|
|
|
|
for (p = name; *p; p++)
|
|
|
|
if (!isalnum(*p) && !strchr(".-", *p))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void unrealdns_cb_nametoip_verify(void *arg, int status, int timeouts, struct hostent *he)
|
|
|
|
{
|
|
|
|
DNSReq *r = (DNSReq *)arg;
|
|
|
|
Client *client = r->client;
|
|
|
|
char ipv6 = r->ipv6;
|
|
|
|
int i;
|
|
|
|
struct hostent *he2;
|
|
|
|
|
|
|
|
if (!client)
|
|
|
|
goto bad;
|
|
|
|
|
|
|
|
if ((status != 0) || (ipv6 && (he->h_length != 16)) || (!ipv6 && (he->h_length != 4)))
|
|
|
|
{
|
|
|
|
/* Failed: error code, or data length is incorrect */
|
|
|
|
proceed_normal_client_handshake(client, NULL);
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Verify ip->name and name->ip mapping... */
|
|
|
|
for (i = 0; he->h_addr_list[i]; i++)
|
|
|
|
{
|
|
|
|
if (r->ipv6)
|
|
|
|
{
|
|
|
|
struct in6_addr addr;
|
|
|
|
if (inet_pton(AF_INET6, client->ip, &addr) != 1)
|
|
|
|
continue; /* something fucked */
|
|
|
|
if (!memcmp(he->h_addr_list[i], &addr, 16))
|
|
|
|
break; /* MATCH */
|
|
|
|
} else {
|
|
|
|
struct in_addr addr;
|
|
|
|
if (inet_pton(AF_INET, client->ip, &addr) != 1)
|
|
|
|
continue; /* something fucked */
|
|
|
|
if (!memcmp(he->h_addr_list[i], &addr, 4))
|
|
|
|
break; /* MATCH */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!he->h_addr_list[i])
|
|
|
|
{
|
|
|
|
/* Failed name <-> IP mapping */
|
|
|
|
proceed_normal_client_handshake(client, NULL);
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!verify_hostname(r->name))
|
|
|
|
{
|
|
|
|
/* Hostname is bad, don't cache and consider unresolved */
|
|
|
|
proceed_normal_client_handshake(client, NULL);
|
|
|
|
goto bad;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Entry was found, verified, and can be added to cache */
|
|
|
|
|
|
|
|
unrealdns_addtocache(r->name, client->ip);
|
|
|
|
|
|
|
|
he2 = unreal_create_hostent(r->name, client->ip);
|
|
|
|
proceed_normal_client_handshake(client, he2);
|
|
|
|
|
|
|
|
bad:
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
void unrealdns_cb_nametoip_link(void *arg, int status, int timeouts, struct hostent *he)
|
|
|
|
{
|
|
|
|
DNSReq *r = (DNSReq *)arg;
|
|
|
|
int n;
|
|
|
|
struct hostent *he2;
|
|
|
|
char ipbuf[HOSTLEN+1];
|
|
|
|
char *ip = NULL;
|
|
|
|
|
|
|
|
if (!r->linkblock)
|
|
|
|
{
|
|
|
|
/* Possible if deleted due to rehash async removal */
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((status != 0) || !he->h_addr_list || !he->h_addr_list[0])
|
|
|
|
{
|
|
|
|
if (r->ipv6)
|
|
|
|
{
|
|
|
|
/* Retry for IPv4... */
|
|
|
|
r->ipv6 = 0;
|
|
|
|
ares_gethostbyname(resolver_channel, r->name, AF_INET, unrealdns_cb_nametoip_link, r);
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* fatal error while resolving */
|
|
|
|
sendto_ops_and_log("Unable to resolve hostname '%s', when trying to connect to server %s.",
|
|
|
|
r->name, r->linkblock->servername);
|
|
|
|
r->linkblock->refcount--;
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
r->linkblock->refcount--;
|
|
|
|
|
|
|
|
if (!he->h_addr_list[0] || (he->h_length != (r->ipv6 ? 16 : 4)) ||
|
|
|
|
!(ip = inetntop(r->ipv6 ? AF_INET6 : AF_INET, he->h_addr_list[0], ipbuf, sizeof(ipbuf))))
|
|
|
|
{
|
|
|
|
/* Illegal response -- fatal */
|
|
|
|
sendto_ops_and_log("Unable to resolve hostname '%s', when trying to connect to server %s.",
|
|
|
|
r->name, r->linkblock->servername);
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Ok, since we got here, it seems things were actually succesfull */
|
|
|
|
|
|
|
|
/* Fill in [linkblockstruct]->ipnum */
|
|
|
|
safe_strdup(r->linkblock->connect_ip, ip);
|
|
|
|
he2 = unreal_create_hostent(he->h_name, ip);
|
|
|
|
|
|
|
|
switch ((n = connect_server(r->linkblock, r->client, he2)))
|
|
|
|
{
|
|
|
|
case 0:
|
2020-04-20 19:12:33 +00:00
|
|
|
sendto_ops_and_log("Trying to activate link with server %s[%s]...", r->linkblock->servername, ip);
|
2020-03-29 09:16:53 +00:00
|
|
|
break;
|
|
|
|
case -1:
|
|
|
|
sendto_ops_and_log("Couldn't connect to server %s[%s].", r->linkblock->servername, ip);
|
|
|
|
break;
|
|
|
|
case -2:
|
|
|
|
/* Should not happen since he is not NULL */
|
|
|
|
sendto_ops_and_log("Hostname %s is unknown for server %s (!?).", r->linkblock->outgoing.hostname, r->linkblock->servername);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
sendto_ops_and_log("Connection to server %s failed: %s", r->linkblock->servername, STRERROR(n));
|
|
|
|
}
|
|
|
|
|
|
|
|
unrealdns_freeandremovereq(r);
|
|
|
|
/* DONE */
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint64_t unrealdns_hash_ip(const char *ip)
|
|
|
|
{
|
|
|
|
return siphash(ip, siphashkey_dns_ip) % DNS_HASH_SIZE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unrealdns_addtocache(char *name, char *ip)
|
|
|
|
{
|
|
|
|
unsigned int hashv;
|
|
|
|
DNSCache *c;
|
|
|
|
|
|
|
|
dnsstats.cache_adds++;
|
|
|
|
|
|
|
|
hashv = unrealdns_hash_ip(ip);
|
|
|
|
|
|
|
|
/* Check first if it is already present in the cache.
|
|
|
|
* This is possible, when 2 clients connect at the same time.
|
|
|
|
*/
|
|
|
|
for (c = cache_hashtbl[hashv]; c; c = c->hnext)
|
|
|
|
if (!strcmp(ip, c->ip))
|
|
|
|
return; /* already present in cache */
|
|
|
|
|
|
|
|
/* Remove last item, if we got too many entries.. */
|
|
|
|
if (unrealdns_num_cache >= DNS_MAX_ENTRIES)
|
|
|
|
{
|
|
|
|
for (c = cache_list; c->next; c = c->next);
|
|
|
|
unrealdns_removecacherecord(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create record */
|
|
|
|
c = safe_alloc(sizeof(DNSCache));
|
|
|
|
safe_strdup(c->name, name);
|
|
|
|
safe_strdup(c->ip, ip);
|
|
|
|
c->expires = TStime() + DNSCACHE_TTL;
|
|
|
|
|
|
|
|
/* Add to hash table */
|
|
|
|
if (cache_hashtbl[hashv])
|
|
|
|
{
|
|
|
|
cache_hashtbl[hashv]->hprev = c;
|
|
|
|
c->hnext = cache_hashtbl[hashv];
|
|
|
|
}
|
|
|
|
cache_hashtbl[hashv] = c;
|
|
|
|
|
|
|
|
/* Add to linked list */
|
|
|
|
if (cache_list)
|
|
|
|
{
|
|
|
|
cache_list->prev = c;
|
|
|
|
c->next = cache_list;
|
|
|
|
}
|
|
|
|
cache_list = c;
|
|
|
|
|
|
|
|
unrealdns_num_cache++;
|
|
|
|
/* DONE */
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Search the cache for a confirmed ip->name and name->ip match, by address.
|
|
|
|
* @returns The resolved hostname, or NULL if not found in cache.
|
|
|
|
*/
|
|
|
|
static char *unrealdns_findcache_ip(char *ip)
|
|
|
|
{
|
|
|
|
unsigned int hashv;
|
|
|
|
DNSCache *c;
|
|
|
|
|
|
|
|
hashv = unrealdns_hash_ip(ip);
|
|
|
|
|
|
|
|
for (c = cache_hashtbl[hashv]; c; c = c->hnext)
|
|
|
|
if (!strcmp(ip, c->ip))
|
|
|
|
{
|
|
|
|
dnsstats.cache_hits++;
|
|
|
|
return c->name;
|
|
|
|
}
|
|
|
|
|
|
|
|
dnsstats.cache_misses++;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Removes dns cache record from list (and frees it).
|
|
|
|
*/
|
|
|
|
void unrealdns_removecacherecord(DNSCache *c)
|
|
|
|
{
|
|
|
|
unsigned int hashv;
|
|
|
|
|
|
|
|
/* We basically got 4 pointers to update:
|
|
|
|
* <previous listitem>->next
|
|
|
|
* <next listitem>->previous
|
|
|
|
* <previous hashitem>->next
|
|
|
|
* <next hashitem>->prev.
|
|
|
|
* And we need to update 'cache_list' and 'cache_hash[]' if needed.
|
|
|
|
*/
|
|
|
|
if (c->prev)
|
|
|
|
c->prev->next = c->next;
|
|
|
|
else
|
|
|
|
cache_list = c->next; /* new list HEAD */
|
|
|
|
|
|
|
|
if (c->next)
|
|
|
|
c->next->prev = c->prev;
|
|
|
|
|
|
|
|
if (c->hprev)
|
|
|
|
c->hprev->hnext = c->hnext;
|
|
|
|
else {
|
|
|
|
/* new hash HEAD */
|
|
|
|
hashv = unrealdns_hash_ip(c->ip);
|
|
|
|
if (cache_hashtbl[hashv] != c)
|
|
|
|
abort(); /* impossible */
|
|
|
|
cache_hashtbl[hashv] = c->hnext;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c->hnext)
|
|
|
|
c->hnext->hprev = c->hprev;
|
|
|
|
|
|
|
|
safe_free(c->name);
|
|
|
|
safe_free(c->ip);
|
|
|
|
safe_free(c);
|
|
|
|
|
|
|
|
unrealdns_num_cache--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/** This regulary removes old dns records from the cache */
|
|
|
|
EVENT(unrealdns_removeoldrecords)
|
|
|
|
{
|
|
|
|
DNSCache *c, *next;
|
|
|
|
|
|
|
|
for (c = cache_list; c; c = next)
|
|
|
|
{
|
|
|
|
next = c->next;
|
|
|
|
if (c->expires < TStime())
|
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
sendto_realops(client, "[Syzop/DNS] Expire: %s [%s] (%ld < %ld)",
|
|
|
|
c->name, c->ip, c->expires, TStime());
|
|
|
|
#endif
|
|
|
|
unrealdns_removecacherecord(c);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct hostent *unreal_create_hostent(char *name, char *ip)
|
|
|
|
{
|
|
|
|
struct hostent *he;
|
|
|
|
|
|
|
|
/* Create a hostent structure (I HATE HOSTENTS) and return it.. */
|
|
|
|
he = safe_alloc(sizeof(struct hostent));
|
|
|
|
safe_strdup(he->h_name, name);
|
|
|
|
if (strchr(ip, ':'))
|
|
|
|
{
|
|
|
|
/* IPv6 */
|
|
|
|
he->h_addrtype = AF_INET6;
|
|
|
|
he->h_length = sizeof(struct in6_addr);
|
|
|
|
he->h_addr_list = safe_alloc(sizeof(char *) * 2); /* alocate an array of 2 pointers */
|
|
|
|
he->h_addr_list[0] = safe_alloc(sizeof(struct in6_addr));
|
|
|
|
inet_pton(AF_INET6, ip, he->h_addr_list[0]);
|
|
|
|
} else {
|
|
|
|
he->h_addrtype = AF_INET;
|
|
|
|
he->h_length = sizeof(struct in_addr);
|
|
|
|
he->h_addr_list = safe_alloc(sizeof(char *) * 2); /* alocate an array of 2 pointers */
|
|
|
|
he->h_addr_list[0] = safe_alloc(sizeof(struct in_addr));
|
|
|
|
inet_pton(AF_INET, ip, he->h_addr_list[0]);
|
|
|
|
}
|
|
|
|
|
|
|
|
return he;
|
|
|
|
}
|
|
|
|
|
|
|
|
void unreal_free_hostent(struct hostent *he)
|
|
|
|
{
|
|
|
|
safe_free(he->h_name);
|
|
|
|
safe_free(he->h_addr_list[0]);
|
|
|
|
safe_free(he->h_addr_list);
|
|
|
|
safe_free(he);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void unrealdns_freeandremovereq(DNSReq *r)
|
|
|
|
{
|
|
|
|
if (r->prev)
|
|
|
|
r->prev->next = r->next;
|
|
|
|
else
|
|
|
|
requests = r->next; /* new HEAD */
|
|
|
|
|
|
|
|
if (r->next)
|
|
|
|
r->next->prev = r->prev;
|
|
|
|
|
|
|
|
safe_free(r->name);
|
|
|
|
safe_free(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Delete requests for client 'client'.
|
|
|
|
* Actually we DO NOT (and should not) delete them, but simply mark them as 'dead'.
|
|
|
|
*/
|
|
|
|
void unrealdns_delreq_bycptr(Client *client)
|
|
|
|
{
|
|
|
|
DNSReq *r;
|
|
|
|
|
|
|
|
for (r = requests; r; r = r->next)
|
|
|
|
if (r->client == client)
|
|
|
|
r->client = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
void unrealdns_delasyncconnects(void)
|
|
|
|
{
|
|
|
|
DNSReq *r;
|
|
|
|
|
|
|
|
for (r = requests; r; r = r->next)
|
|
|
|
if (r->type == DNSREQ_CONNECT)
|
|
|
|
r->linkblock = NULL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
CMD_FUNC(cmd_dns)
|
|
|
|
{
|
|
|
|
DNSCache *c;
|
|
|
|
DNSReq *r;
|
|
|
|
char *param;
|
|
|
|
|
|
|
|
if (!ValidatePermissionsForPath("server:dns",client,NULL,NULL,NULL))
|
|
|
|
{
|
|
|
|
sendnumeric(client, ERR_NOPRIVILEGES);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((parc > 1) && !BadPtr(parv[1]))
|
|
|
|
param = parv[1];
|
|
|
|
else
|
|
|
|
param = "";
|
|
|
|
|
|
|
|
if (*param == 'l') /* LIST CACHE */
|
|
|
|
{
|
|
|
|
sendtxtnumeric(client, "DNS CACHE List (%u items):", unrealdns_num_cache);
|
|
|
|
for (c = cache_list; c; c = c->next)
|
|
|
|
sendtxtnumeric(client, " %s [%s]", c->name, c->ip);
|
|
|
|
} else
|
|
|
|
if (*param == 'r') /* LIST REQUESTS */
|
|
|
|
{
|
|
|
|
sendtxtnumeric(client, "DNS Request List:");
|
|
|
|
for (r = requests; r; r = r->next)
|
|
|
|
sendtxtnumeric(client, " %s", r->client ? r->client->ip : "<client lost>");
|
|
|
|
} else
|
|
|
|
if (*param == 'c') /* CLEAR CACHE */
|
|
|
|
{
|
|
|
|
sendto_realops("%s (%s@%s) cleared the DNS cache list (/QUOTE DNS c)",
|
|
|
|
client->name, client->user->username, client->user->realhost);
|
|
|
|
|
|
|
|
while (cache_list)
|
|
|
|
{
|
|
|
|
c = cache_list->next;
|
|
|
|
safe_free(cache_list->name);
|
|
|
|
safe_free(cache_list->ip);
|
|
|
|
safe_free(cache_list);
|
|
|
|
cache_list = c;
|
|
|
|
}
|
|
|
|
memset(&cache_hashtbl, 0, sizeof(cache_hashtbl));
|
|
|
|
unrealdns_num_cache = 0;
|
|
|
|
sendnotice(client, "DNS Cache has been cleared");
|
|
|
|
} else
|
|
|
|
if (*param == 'i') /* INFORMATION */
|
|
|
|
{
|
|
|
|
struct ares_options inf;
|
|
|
|
struct ares_addr_node *serverlist = NULL, *ns;
|
|
|
|
int i;
|
|
|
|
int optmask;
|
|
|
|
|
|
|
|
sendtxtnumeric(client, "****** DNS Configuration Information ******");
|
|
|
|
sendtxtnumeric(client, " c-ares version: %s",ares_version(NULL));
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
ares_get_servers(resolver_channel, &serverlist);
|
|
|
|
for (ns = serverlist; ns; ns = ns->next)
|
|
|
|
{
|
|
|
|
char ipbuf[128], *ip;
|
|
|
|
i++;
|
|
|
|
|
|
|
|
ip = inetntop(ns->family, &ns->addr, ipbuf, sizeof(ipbuf));
|
|
|
|
sendtxtnumeric(client, " server #%d: %s", i, ip ? ip : "<error>");
|
|
|
|
}
|
|
|
|
ares_free_data(serverlist);
|
|
|
|
|
|
|
|
ares_save_options(resolver_channel, &inf, &optmask);
|
|
|
|
if (optmask & ARES_OPT_TIMEOUTMS)
|
|
|
|
sendtxtnumeric(client, " timeout: %d", inf.timeout);
|
|
|
|
if (optmask & ARES_OPT_TRIES)
|
|
|
|
sendtxtnumeric(client, " tries: %d", inf.tries);
|
|
|
|
if (optmask & ARES_OPT_DOMAINS)
|
|
|
|
{
|
|
|
|
sendtxtnumeric(client, " # of search domains: %d", inf.ndomains);
|
|
|
|
for (i = 0; i < inf.ndomains; i++)
|
|
|
|
sendtxtnumeric(client, " domain #%d: %s", i+1, inf.domains[i]);
|
|
|
|
}
|
|
|
|
sendtxtnumeric(client, "****** End of DNS Configuration Info ******");
|
|
|
|
|
|
|
|
ares_destroy_options(&inf);
|
|
|
|
} else /* STATISTICS */
|
|
|
|
{
|
|
|
|
sendtxtnumeric(client, "DNS CACHE Stats:");
|
|
|
|
sendtxtnumeric(client, " hits: %d", dnsstats.cache_hits);
|
|
|
|
sendtxtnumeric(client, " misses: %d", dnsstats.cache_misses);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Little helper function for dnsbl module.
|
|
|
|
* No we will NOT copy the entire c-ares api, just this one.
|
|
|
|
*/
|
|
|
|
void unreal_gethostbyname(const char *name, int family, ares_host_callback callback, void *arg)
|
|
|
|
{
|
|
|
|
ares_gethostbyname(resolver_channel, name, family, callback, arg);
|
|
|
|
}
|