1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-01 11:26:38 +02:00
Files
unrealircd/src/dns.c
T
Bram Matthys f437593b8d Rewrite and expand notices+logging with regards to server linking / lost link.
When connecting, use slightly different wording (and use it consistently):
"Trying to activate link with server xyz"

When the connection is lost before synced:
"Unable to link with server xyz"

When the connection is lost after fully synced (eg: minutes later):
"Lost server link to xyz"

Important small changes (other than text):
* Log ERRORs from remote servers to the log (previously only shown to ircops)
* Some link errors could have been previously suppressed due to
  old code assuming other parts of the code would send or log the error
  (this would be the case for an error when calling SSL/TLS write functions)
* More?
2020-04-13 13:36:58 +02:00

743 lines
19 KiB
C

/************************************************************************
* 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:
sendto_ops_and_log("Trying to activate link with server %s[%s]...", r->linkblock->servername, ip);
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);
}