494 lines
13 KiB
C
494 lines
13 KiB
C
/************
|
|
* cdns.c *
|
|
************
|
|
*
|
|
* A threaded DNS lookup implementation.
|
|
*
|
|
* This was written exclusively to get around the fact that
|
|
* gethostbyaddr()/gethostbyname() will BLOCK until the resolver either
|
|
* gets an answer, or times out. So we create a thread, and allow
|
|
* the calls to block as long as they want, as the main app's thread
|
|
* will keep on chugging along regardless.
|
|
*
|
|
* BTW, even tho the file is .cc, it really is only C code, I just like
|
|
* C++'s strict type checking.
|
|
*
|
|
* Written by Scott H Kilau
|
|
*
|
|
* Copyright(c) 1999
|
|
*
|
|
* See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT
|
|
* $Id$
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include "cdns.h"
|
|
#include "irc.h" /* To pick up our next #define checks */
|
|
static char cvsrevision[] = "$Id$";
|
|
CVS_REVISION(cdns_c)
|
|
#include "commands.h"
|
|
|
|
#include "struct.h"
|
|
#include "newio.h"
|
|
#define MAIN_SOURCE
|
|
#include "modval.h"
|
|
|
|
#if defined(THREAD) && defined(WANT_NSLOOKUP)
|
|
|
|
/* Our static functions */
|
|
static void init_dns_mutexes(void);
|
|
static void *start_dns_thread(void *);
|
|
static void do_dns_lookup(DNS_QUEUE *);
|
|
static void cleanup_dns(void);
|
|
static void destroy_dns_queue(DNS_QUEUE **, DNS_QUEUE **);
|
|
static void free_dns_entry(DNS_QUEUE *);
|
|
static void kill_dns_thread(int);
|
|
static DNS_QUEUE *build_dns_entry(char *, void (*)(DNS_QUEUE *), char *, void *);
|
|
static DNS_QUEUE *dns_dequeue(DNS_QUEUE **, DNS_QUEUE **);
|
|
static DNS_QUEUE *dns_enqueue(DNS_QUEUE **, DNS_QUEUE **, DNS_QUEUE *);
|
|
static DNS_QUEUE *dns_enqueue_urgent(DNS_QUEUE **, DNS_QUEUE **, DNS_QUEUE *);
|
|
|
|
/* Our static globals */
|
|
static pthread_t dns_thread = {0};
|
|
static pthread_mutex_t pending_queue_mutex = {0};
|
|
static pthread_mutex_t finished_queue_mutex = {0};
|
|
static pthread_mutex_t quit_mutex = {0};
|
|
static pthread_cond_t pending_queue_cond;
|
|
static DNS_QUEUE *PendingQueueHead = NULL, *PendingQueueTail = NULL;
|
|
static DNS_QUEUE *FinishedQueueHead = NULL, *FinishedQueueTail = NULL;
|
|
static int cdns_write = -1;
|
|
static int cdns_read = -1;
|
|
|
|
/*
|
|
* start_dns : This should be called by the main app thread,
|
|
* whenever it wants to start up the dns stuff
|
|
*/
|
|
void start_dns(void)
|
|
{
|
|
int fd_array[2];
|
|
/* Init our queues. */
|
|
Q_OPEN(&PendingQueueHead, &PendingQueueTail);
|
|
Q_OPEN(&FinishedQueueHead, &FinishedQueueTail);
|
|
/* Create our mutexes */
|
|
init_dns_mutexes();
|
|
/* init our pending queue condition, and set to default attributes (NULL) */
|
|
pthread_cond_init(&pending_queue_cond, NULL);
|
|
/* lock up the quit mutex */
|
|
pthread_mutex_lock(&quit_mutex);
|
|
/* create our pipe [0] = main to read from, [1] = cdns to write to */
|
|
pipe(fd_array);
|
|
cdns_read = fd_array[0];
|
|
cdns_write = fd_array[1];
|
|
new_open(cdns_read);
|
|
new_open(cdns_write);
|
|
/* create our dns thread */
|
|
pthread_create(&dns_thread, NULL, start_dns_thread, NULL);
|
|
}
|
|
|
|
/*
|
|
* stop_dns : This should be called by the main app thread,
|
|
* whenever it wants to stop the dns stuff.
|
|
*/
|
|
void stop_dns(void)
|
|
{
|
|
void *ptr;
|
|
if (!dns_thread)
|
|
return;
|
|
/* Unlock the quit mutex */
|
|
pthread_mutex_unlock(&quit_mutex);
|
|
/* lock up pending queue mutex */
|
|
pthread_mutex_lock(&pending_queue_mutex);
|
|
/* signal thread to wake up */
|
|
pthread_cond_signal(&pending_queue_cond);
|
|
/* Give lock back, so dns thread can react to signal */
|
|
pthread_mutex_unlock(&pending_queue_mutex);
|
|
/* Wait until the thread kills itself. */
|
|
pthread_join(dns_thread, &ptr);
|
|
cleanup_dns();
|
|
}
|
|
|
|
/*
|
|
* kill_dns : This should be called by the main app thread,
|
|
* when it wants the dns thread to go away forever. This function
|
|
* kills the thread uncleanly, and probably should only be used
|
|
* when the main app is about to exit()
|
|
*/
|
|
void kill_dns(void)
|
|
{
|
|
sigset_t set, oldset;
|
|
|
|
/* Fill set with all known signals */
|
|
sigfillset(&set);
|
|
/* Remove the few signals that POSIX says we should */
|
|
sigdelset(&set, SIGFPE);
|
|
sigdelset(&set, SIGILL);
|
|
sigdelset(&set, SIGSEGV);
|
|
/* Tell our thread (main) to block against the above signals */
|
|
sigprocmask(SIG_BLOCK, &set, &oldset);
|
|
/* lock up pending queue mutex */
|
|
pthread_mutex_lock(&pending_queue_mutex);
|
|
/* signal thread to wake up */
|
|
pthread_cond_signal(&pending_queue_cond);
|
|
/* Kill the dns thread, using the SIGQUIT signal */
|
|
pthread_kill(dns_thread, SIGQUIT);
|
|
/* Put back the previous blocking signals */
|
|
sigprocmask(SIG_BLOCK, &oldset, &set);
|
|
/* cleanup anything dealing with the dns stuff */
|
|
cleanup_dns();
|
|
}
|
|
|
|
/*
|
|
* add_to_dns_queue : This should be called by the main app thread,
|
|
* whenever it wants us to resolve something... host <---> ip.
|
|
*/
|
|
void
|
|
add_to_dns_queue(char *userhost, void (*callback)(DNS_QUEUE *), char *cmd, void *data, int urgency)
|
|
{
|
|
char *split = (char *) 0;
|
|
DNS_QUEUE *tmp;
|
|
|
|
if (userhost && *userhost) {
|
|
/* Look for user@host */
|
|
split = index(userhost, '@');
|
|
if (split)
|
|
split++;
|
|
else
|
|
split = userhost;
|
|
if (split && *split) {
|
|
/* Build dns entry */
|
|
tmp = build_dns_entry(split, callback, cmd, data);
|
|
/* Wait until we can get mutex lock for queue */
|
|
pthread_mutex_lock(&pending_queue_mutex);
|
|
/* Enqueue the entry, checking urgency */
|
|
if (urgency == DNS_URGENT)
|
|
dns_enqueue_urgent(&PendingQueueHead,
|
|
&PendingQueueTail, tmp);
|
|
else
|
|
dns_enqueue(&PendingQueueHead,
|
|
&PendingQueueTail, tmp);
|
|
/* signal dns thread, its got work to do */
|
|
pthread_cond_signal(&pending_queue_cond);
|
|
/* Give lock back, so dns thread can react to signal */
|
|
pthread_mutex_unlock(&pending_queue_mutex);
|
|
}
|
|
else
|
|
fprintf(stderr, "%s:%d error: No host!", __FILE__, __LINE__);
|
|
}
|
|
else
|
|
fprintf(stderr, "%s:%d error: NULL!", __FILE__, __LINE__);
|
|
}
|
|
|
|
/* set_dns_output_fd : This makes check_dns_queue much better.
|
|
* This will add our piped fd into the main's select() loop, so
|
|
* we will know right away when the output queue has data waiting.
|
|
*/
|
|
void set_dns_output_fd(fd_set *rd)
|
|
{
|
|
if (cdns_read >= 0)
|
|
FD_SET(cdns_read, rd);
|
|
}
|
|
|
|
/* dns_check : See above. */
|
|
void dns_check(fd_set *rd)
|
|
{
|
|
char blah[2];
|
|
if (cdns_read >= 0 && FD_ISSET(cdns_read, rd)) {
|
|
read(cdns_read, &blah, 1);
|
|
check_dns_queue();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* check_dns_queue : This should be called by the main app thread, at
|
|
* periodic intervals, ie, whenever its convenient.
|
|
*/
|
|
void check_dns_queue()
|
|
{
|
|
DNS_QUEUE *dns = (DNS_QUEUE *) 0;
|
|
while (pthread_mutex_trylock(&finished_queue_mutex) == 0) {
|
|
dns = dns_dequeue(&FinishedQueueHead, &FinishedQueueTail);
|
|
pthread_mutex_unlock(&finished_queue_mutex);
|
|
if (dns) {
|
|
#ifdef MY_DEBUG
|
|
fprintf(stderr, "DNS IN: %s: %s <-> %s",
|
|
dns->in ? dns->in : "<NULL>",
|
|
dns->in ? dns->in : "<NULL>",
|
|
dns->out ? dns->out : "<NULL>");
|
|
#endif
|
|
if (dns->alias)
|
|
{
|
|
char buffer[BIG_BUFFER_SIZE+1];
|
|
snprintf(buffer, BIG_BUFFER_SIZE, "%s %s ", dns->in ? dns->in : "<NULL>", dns->out ? dns->out : "<NULL>");
|
|
parse_line("NSLOOKUP", dns->alias, buffer, 0, 0, 1);
|
|
}
|
|
else if (dns->callback)
|
|
dns->callback(dns);
|
|
else
|
|
fprintf(stderr, "%s:%d error: No callback!",
|
|
__FILE__, __LINE__);
|
|
free_dns_entry(dns);
|
|
}
|
|
else
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Give back memory that we allocated for this entry */
|
|
static void free_dns_entry(DNS_QUEUE *tmp)
|
|
{
|
|
if (tmp->in)
|
|
free(tmp->in);
|
|
if (tmp->out)
|
|
free(tmp->out);
|
|
if (tmp->alias)
|
|
free(tmp->alias);
|
|
if (tmp->hostentr)
|
|
freemyhostent(tmp->hostentr);
|
|
free(tmp);
|
|
}
|
|
|
|
/* init our dns mutexes */
|
|
static void init_dns_mutexes(void)
|
|
{
|
|
pthread_mutex_init(&pending_queue_mutex, NULL);
|
|
pthread_mutex_init(&finished_queue_mutex, NULL);
|
|
pthread_mutex_init(&quit_mutex, NULL);
|
|
}
|
|
|
|
static void kill_dns_thread(int notused)
|
|
{
|
|
/* Empty */
|
|
}
|
|
|
|
static void kill_dns_thread2(int notused)
|
|
{
|
|
int ecode = 0;
|
|
pthread_exit(&ecode);
|
|
}
|
|
|
|
static void dns_thread_signal_setup(void)
|
|
{
|
|
sigset_t set;
|
|
|
|
/* Create SIGQUIT signal handler for this thread */
|
|
#if 0
|
|
signal(SIGQUIT, kill_dns_thread);
|
|
#endif
|
|
/* Use ircii's portable implementation of signal() instead */
|
|
my_signal(SIGQUIT, (sigfunc *) kill_dns_thread, 0);
|
|
/* Fill set with every signal */
|
|
sigfillset(&set);
|
|
/* Remove the SIGQUIT signal from the set */
|
|
sigdelset(&set, SIGQUIT);
|
|
/* Apply the mask on this thread */
|
|
pthread_sigmask(SIG_BLOCK, &set, NULL);
|
|
}
|
|
|
|
/* Our main function of the dns thread */
|
|
static void *start_dns_thread(void *args)
|
|
{
|
|
DNS_QUEUE *dns = NULL;
|
|
/* Set up the thread's signal handlers and mask */
|
|
dns_thread_signal_setup();
|
|
/* loop */
|
|
while(1) {
|
|
/* Try the quit mutex, if we get it, that means
|
|
* main() wants us to die.
|
|
*/
|
|
if (pthread_mutex_trylock(&quit_mutex) == 0) {
|
|
kill_dns_thread2(0);
|
|
}
|
|
/* Lock the queue */
|
|
pthread_mutex_lock(&pending_queue_mutex);
|
|
dns = dns_dequeue(&PendingQueueHead, &PendingQueueTail);
|
|
if (!dns) {
|
|
/*
|
|
* Give back the cpu, and go to sleep until cond gets signalled.
|
|
* Note: the mutex MUST be locked, before going into the wait!
|
|
*/
|
|
pthread_cond_wait(&pending_queue_cond, &pending_queue_mutex);
|
|
/* We have been woken up. Thus, 2 conditions are present.
|
|
* 1) We have been signalled that data is waiting.
|
|
* 2) The mutex is locked.
|
|
*/
|
|
pthread_mutex_unlock(&pending_queue_mutex);
|
|
}
|
|
else {
|
|
char c = ' ';
|
|
pthread_mutex_unlock(&pending_queue_mutex);
|
|
do_dns_lookup(dns);
|
|
/* block until we get the lock */
|
|
pthread_mutex_lock(&finished_queue_mutex);
|
|
dns_enqueue(&FinishedQueueHead, &FinishedQueueTail, dns);
|
|
pthread_mutex_unlock(&finished_queue_mutex);
|
|
/* Write to our pipe, this will wake up mains select loop */
|
|
write(cdns_write, &c, 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Makes a malloced duplicate of the hostent returned. */
|
|
my_hostent *duphostent(struct hostent *orig)
|
|
{
|
|
my_hostent *tmp = new_malloc(sizeof(my_hostent));
|
|
int z;
|
|
|
|
tmp->h_name = m_strdup(orig->h_name);
|
|
tmp->h_length = orig->h_length;
|
|
tmp->h_addrtype = orig->h_addrtype;
|
|
|
|
for(z=0;z<MAXALIASES && orig->h_aliases[z];z++)
|
|
tmp->h_aliases[z] = m_strdup(orig->h_aliases[z]);
|
|
|
|
for(z=0;z<MAXADDRS && orig->h_addr_list[z];z++)
|
|
memcpy(&tmp->h_addr_list[z], orig->h_addr_list[z], sizeof(*orig->h_addr_list));
|
|
|
|
return (my_hostent *)tmp;
|
|
}
|
|
|
|
/* Free's the replica hostent. */
|
|
void freemyhostent(my_hostent *freeme)
|
|
{
|
|
int z;
|
|
|
|
if(!freeme)
|
|
return;
|
|
|
|
new_free(&freeme->h_name);
|
|
|
|
for(z=0;z<MAXALIASES;z++)
|
|
if(freeme->h_aliases[z])
|
|
new_free(&freeme->h_aliases[z]);
|
|
|
|
new_free(&freeme);
|
|
}
|
|
|
|
/* Does the actual DNS lookup.... host <---> ip */
|
|
static void do_dns_lookup(DNS_QUEUE *dns)
|
|
{
|
|
struct hostent *temp;
|
|
struct in_addr temp1;
|
|
int ip = 0;
|
|
|
|
/* If nothing, give back nothing */
|
|
if (!dns->in)
|
|
return;
|
|
if (isdigit(*(dns->in + strlen(dns->in) - 1))) {
|
|
ip = 1;
|
|
temp1.s_addr = inet_addr(dns->in);
|
|
temp = gethostbyaddr((char*) &temp1,
|
|
sizeof (struct in_addr), AF_INET);
|
|
}
|
|
else {
|
|
temp = gethostbyname(dns->in);
|
|
if (temp)
|
|
#if defined(_Windows)
|
|
memcpy(&temp1, temp->h_addr, temp->h_length);
|
|
#else
|
|
memcpy((caddr_t)&temp1, temp->h_addr, temp->h_length);
|
|
#endif
|
|
else
|
|
return;
|
|
}
|
|
if (!temp)
|
|
return;
|
|
if (ip) {
|
|
dns->ip = 1;
|
|
if (temp->h_name && *temp->h_name) {
|
|
dns->out = (char *) malloc(strlen(temp->h_name) + 1);
|
|
strcpy(dns->out, temp->h_name);
|
|
dns->hostentr = duphostent(temp);
|
|
}
|
|
}
|
|
else {
|
|
dns->ip = 0;
|
|
dns->out = (char *) malloc(strlen(inet_ntoa(temp1)) + 1);
|
|
strcpy(dns->out, inet_ntoa(temp1));
|
|
dns->hostentr = duphostent(temp);
|
|
}
|
|
}
|
|
|
|
/* dequeue an entry from the passed in queue */
|
|
DNS_QUEUE *
|
|
dns_dequeue(DNS_QUEUE **headp, DNS_QUEUE **tailp)
|
|
{
|
|
DNS_QUEUE *tmp = NULL;
|
|
|
|
if (*headp == NULL)
|
|
return NULL;
|
|
tmp = *headp;
|
|
*headp = Q_NEXT(tmp);
|
|
if (*headp == NULL)
|
|
*tailp = NULL;
|
|
Q_NEXT(tmp) = NULL;
|
|
return tmp;
|
|
}
|
|
|
|
/* enqueue a request onto the passed in queue */
|
|
DNS_QUEUE *
|
|
dns_enqueue(DNS_QUEUE **headp, DNS_QUEUE **tailp, DNS_QUEUE *tmp)
|
|
{
|
|
Q_NEXT(tmp) = NULL;
|
|
if (*headp == NULL)
|
|
*headp = *tailp = tmp;
|
|
else {
|
|
Q_NEXT(*tailp) = tmp;
|
|
*tailp = tmp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* enqueue a request onto the passed in queue, putting it at the front
|
|
* of the queue. This means it will be the next requested dequeue.
|
|
*/
|
|
DNS_QUEUE *
|
|
dns_enqueue_urgent(DNS_QUEUE **headp, DNS_QUEUE **tailp, DNS_QUEUE *tmp)
|
|
{
|
|
Q_NEXT(tmp) = *headp;
|
|
*headp = tmp;
|
|
if (*tailp == NULL)
|
|
*tailp = tmp;
|
|
return NULL;
|
|
}
|
|
|
|
/* build a dns entry struct */
|
|
static DNS_QUEUE *
|
|
build_dns_entry(char *text, void (*callback) (DNS_QUEUE *), char *cmd, void *data)
|
|
{
|
|
DNS_QUEUE *tmp = (DNS_QUEUE *) malloc(sizeof(DNS_QUEUE));
|
|
bzero(tmp, sizeof(DNS_QUEUE));
|
|
tmp->in = (char *) malloc(strlen(text) + 1);
|
|
strcpy(tmp->in, text);
|
|
tmp->callback = callback;
|
|
tmp->callinfo = data;
|
|
if (cmd)
|
|
tmp->alias = strdup(cmd);
|
|
return tmp;
|
|
}
|
|
|
|
/* cleanup_dns : cleanup anything regarding the dns thread */
|
|
static void cleanup_dns(void)
|
|
{
|
|
pthread_mutex_destroy(&pending_queue_mutex);
|
|
pthread_mutex_destroy(&finished_queue_mutex);
|
|
pthread_mutex_destroy(&quit_mutex);
|
|
pthread_cond_destroy(&pending_queue_cond);
|
|
destroy_dns_queue(&PendingQueueHead, &PendingQueueTail);
|
|
destroy_dns_queue(&FinishedQueueHead, &FinishedQueueTail);
|
|
close(cdns_read);
|
|
close(cdns_write);
|
|
cdns_read = cdns_write = -1;
|
|
}
|
|
|
|
/* destroy_dns_queue : Walks the queue, blowing away each node */
|
|
static void destroy_dns_queue(DNS_QUEUE **QueueHead, DNS_QUEUE **QueueTail)
|
|
{
|
|
DNS_QUEUE *dns;
|
|
|
|
while((dns = dns_dequeue(QueueHead, QueueTail)) != NULL)
|
|
free_dns_entry(dns);
|
|
}
|
|
#endif /* THREAD && NSLOOKUP */
|