bitchx/source/cdns.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 */