623 lines
12 KiB
C
623 lines
12 KiB
C
|
|
||
|
/* zodiac - advanced dns spoofer
|
||
|
*
|
||
|
* by scut / teso
|
||
|
*
|
||
|
* dns queue routines
|
||
|
*/
|
||
|
|
||
|
#define DNSQ_MAIN
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <pthread.h>
|
||
|
#include <semaphore.h>
|
||
|
#include "common.h"
|
||
|
#include "dns.h"
|
||
|
#include "dnsq.h"
|
||
|
#include "packet.h"
|
||
|
|
||
|
|
||
|
/* a maximum of 256 filters should be used
|
||
|
* raise this on demand
|
||
|
*/
|
||
|
|
||
|
#define DQ_MAX 256
|
||
|
pthread_mutex_t dqf_mutex = PTHREAD_MUTEX_INITIALIZER; /* mutex over this array */
|
||
|
dq_filter *dqf[DQ_MAX]; /* filter descriptor array */
|
||
|
int dq_count = 0; /* total filter count */
|
||
|
|
||
|
|
||
|
/* dq_match
|
||
|
*
|
||
|
* compare two filters, `real' is a real filter from the filter table,
|
||
|
* `pseudo' is a pseudo filter, that is just used for this comparing
|
||
|
* purposes.
|
||
|
*
|
||
|
* return 1 if they match
|
||
|
* return 0 if they don't match
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
dq_match (dq_filter *real, dq_filter *pseudo)
|
||
|
{
|
||
|
int n; /* temporary return value */
|
||
|
dns_hdr *dns; /* dns header pointer */
|
||
|
unsigned char *query_data; /* first query in dns packet */
|
||
|
|
||
|
/* compare the ip's, skipping INADDR_ANY records
|
||
|
*/
|
||
|
if (real->ip_src.s_addr != htonl (INADDR_ANY)) {
|
||
|
if (real->ip_src.s_addr != pseudo->ip_src.s_addr)
|
||
|
return (0);
|
||
|
}
|
||
|
if (real->ip_dst.s_addr != htonl (INADDR_ANY)) {
|
||
|
if (real->ip_dst.s_addr != pseudo->ip_dst.s_addr)
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* compare the source/destination ports, skipping zero ports
|
||
|
*/
|
||
|
if (real->port_src != 0) {
|
||
|
if (real->port_src != pseudo->port_src)
|
||
|
return (0);
|
||
|
}
|
||
|
if (real->port_dst != 0) {
|
||
|
if (real->port_dst != pseudo->port_dst)
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* finally, check the dns id range
|
||
|
*/
|
||
|
if (real->id_watch == 1) {
|
||
|
if (pseudo->id_start < real->id_start)
|
||
|
return (0);
|
||
|
if (pseudo->id_start > real->id_end)
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* query comparison
|
||
|
*/
|
||
|
|
||
|
if (real->query != NULL && pseudo->dns_packet != NULL) {
|
||
|
dns = (dns_hdr *) pseudo->dns_packet;
|
||
|
if (ntohs (dns->qdcount) >= 1) {
|
||
|
char label[256];
|
||
|
|
||
|
/* decode query label from incoming packet and then compare
|
||
|
* with given query
|
||
|
*/
|
||
|
query_data = pseudo->dns_packet + sizeof (dns_hdr);
|
||
|
memset (label, '\0', sizeof (label));
|
||
|
|
||
|
n = dns_dcd_label (pseudo->dns_packet, &query_data, label, sizeof (label) - 1, 5);
|
||
|
|
||
|
/* decoding failed
|
||
|
*/
|
||
|
if (n == 1)
|
||
|
return (0);
|
||
|
|
||
|
xstrupper (label);
|
||
|
|
||
|
if (strcmp (label, real->query) != 0)
|
||
|
return (0);
|
||
|
} else {
|
||
|
/* no query in the packet, but required by filter
|
||
|
*/
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* all aspects to check matched
|
||
|
*/
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_activate
|
||
|
*
|
||
|
* activate any dq_filter_wait () that may wait for filter activity from
|
||
|
* the filter pointed to by `filt'.
|
||
|
* assume that the calling function holds filt->dq_mutex.
|
||
|
*
|
||
|
* return in any case
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_activate (dq_filter *filt)
|
||
|
{
|
||
|
sem_post (&filt->dq_sem);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_handle
|
||
|
*
|
||
|
* check wether an incoming dns packet matches the filter table, then
|
||
|
* take the appropiate actions.
|
||
|
*
|
||
|
* return in any case
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_handle (ip_hdr *ip, udp_hdr *udp, dns_hdr *dns, unsigned int plen)
|
||
|
{
|
||
|
int slot, n; /* temporary slot counter */
|
||
|
dq_filter *tflt; /* temporary filter for matching purposes */
|
||
|
|
||
|
/* create new pseudo filter
|
||
|
*/
|
||
|
tflt = xcalloc (1, sizeof (dq_filter));
|
||
|
tflt->ip_src = ip->ip_src;
|
||
|
tflt->ip_dst = ip->ip_dst;
|
||
|
tflt->port_src = htons (udp->uh_sport);
|
||
|
tflt->port_dst = htons (udp->uh_dport);
|
||
|
tflt->id_watch = 0;
|
||
|
tflt->id_start = htons (dns->id);
|
||
|
tflt->id_end = 0;
|
||
|
tflt->dns_packet = (unsigned char *) dns;
|
||
|
|
||
|
/* go through all slots
|
||
|
*/
|
||
|
pthread_mutex_lock (&dqf_mutex);
|
||
|
|
||
|
n = dq_count;
|
||
|
|
||
|
for (slot = 0; n > 0 && slot < DQ_MAX; slot++) {
|
||
|
if (dqf[slot] == NULL)
|
||
|
continue;
|
||
|
|
||
|
n--;
|
||
|
|
||
|
/* check wether they match, then activate threads that may listen
|
||
|
* for activity on the descriptor
|
||
|
*/
|
||
|
if (dq_match (dqf[slot], tflt) == 1) {
|
||
|
pthread_mutex_lock (&dqf[slot]->dq_mutex);
|
||
|
|
||
|
dqf[slot]->dq_sem_real = 1;
|
||
|
dq_p_append (dqf[slot], (unsigned char *) ip, plen);
|
||
|
dq_activate (dqf[slot]);
|
||
|
|
||
|
pthread_mutex_unlock (&dqf[slot]->dq_mutex);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
pthread_mutex_unlock (&dqf_mutex);
|
||
|
|
||
|
/* free the pseudo filter
|
||
|
*/
|
||
|
free (tflt);
|
||
|
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_p_get
|
||
|
*
|
||
|
* get the first packet stored in queue on filter associated with `desc'
|
||
|
*
|
||
|
* return a pointer to the unlinked first packet
|
||
|
* return NULL on failure
|
||
|
*/
|
||
|
|
||
|
dq_packet *
|
||
|
dq_p_get (int desc)
|
||
|
{
|
||
|
dq_filter *df;
|
||
|
dq_packet *this;
|
||
|
|
||
|
pthread_mutex_lock (&dqf_mutex);
|
||
|
df = dqf[desc];
|
||
|
if (df != NULL) {
|
||
|
pthread_mutex_lock (&df->dq_mutex);
|
||
|
if (df->p_root == NULL)
|
||
|
return (NULL);
|
||
|
|
||
|
this = df->p_root;
|
||
|
df->p_root = this->next;
|
||
|
pthread_mutex_unlock (&df->dq_mutex);
|
||
|
}
|
||
|
|
||
|
pthread_mutex_unlock (&dqf_mutex);
|
||
|
|
||
|
return (this);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_p_append
|
||
|
*
|
||
|
* append a packet to a filter queue, where `packet' contains
|
||
|
* data consisting out of the ip header, udp header, dns header and dns data
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_p_append (dq_filter *df, unsigned char *packet, unsigned int packetlength)
|
||
|
{
|
||
|
dq_packet *this, *last;
|
||
|
|
||
|
this = df->p_root;
|
||
|
|
||
|
/* first packet
|
||
|
*/
|
||
|
if (this == NULL) {
|
||
|
df->p_root = xcalloc (1, sizeof (dq_packet));
|
||
|
this = df->p_root;
|
||
|
} else {
|
||
|
/* append to the list
|
||
|
*/
|
||
|
|
||
|
while (this != NULL) {
|
||
|
last = this;
|
||
|
this = this->next;
|
||
|
}
|
||
|
|
||
|
last->next = xcalloc (1, sizeof (dq_packet));
|
||
|
this = last->next;
|
||
|
|
||
|
}
|
||
|
|
||
|
this->next = NULL;
|
||
|
this->packet = xcalloc (1, packetlength);
|
||
|
memcpy (this->packet, packet, packetlength);
|
||
|
this->plen = packetlength;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_findslot
|
||
|
*
|
||
|
* find a free slot in the array `df', with a maximum array size of `dqmax'
|
||
|
*
|
||
|
* return -1 if no slot is free
|
||
|
* return slot if slot is found
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
dq_findslot (dq_filter *df[], int dq_max)
|
||
|
{
|
||
|
int n;
|
||
|
|
||
|
for (n = 0; n < dq_max; n++) {
|
||
|
if (df[n] == NULL)
|
||
|
return (n);
|
||
|
}
|
||
|
return (-1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_filter_install
|
||
|
*
|
||
|
* return -1 on failure
|
||
|
* return >=0 as a dq_filter descriptor
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
dq_filter_install (struct in_addr ip_src, struct in_addr ip_dst,
|
||
|
unsigned short int port_src, unsigned short int port_dst,
|
||
|
int id_watch, u_short id_start, u_short id_end, char *query)
|
||
|
{
|
||
|
dq_filter *nf;
|
||
|
int slot; /* free slot */
|
||
|
|
||
|
|
||
|
pthread_mutex_lock (&dqf_mutex);
|
||
|
|
||
|
slot = dq_findslot (dqf, DQ_MAX);
|
||
|
if (slot == -1)
|
||
|
return (-1);
|
||
|
|
||
|
nf = xcalloc (1, sizeof (dq_filter));
|
||
|
|
||
|
/* initialize thread variables
|
||
|
*/
|
||
|
pthread_mutex_init (&nf->dq_mutex, NULL);
|
||
|
pthread_mutex_lock (&nf->dq_mutex);
|
||
|
sem_init (&nf->dq_sem, 0, 0);
|
||
|
|
||
|
/* set up filter data
|
||
|
*/
|
||
|
nf->dq_sem_real = 0;
|
||
|
nf->dq_desc = slot; /* set descriptor */
|
||
|
nf->ip_src = ip_src;
|
||
|
nf->ip_dst = ip_dst;
|
||
|
nf->port_src = port_src;
|
||
|
nf->port_dst = port_dst;
|
||
|
nf->id_watch = id_watch;
|
||
|
nf->id_start = id_start;
|
||
|
nf->id_end = id_end;
|
||
|
nf->dns_packet = NULL;
|
||
|
nf->p_root = NULL;
|
||
|
|
||
|
if (query == NULL) {
|
||
|
nf->query = NULL;
|
||
|
} else {
|
||
|
nf->query = xstrdup (query);
|
||
|
xstrupper (nf->query);
|
||
|
}
|
||
|
|
||
|
dqf[slot] = nf;
|
||
|
|
||
|
dq_count++;
|
||
|
|
||
|
pthread_mutex_unlock (&nf->dq_mutex);
|
||
|
pthread_mutex_unlock (&dqf_mutex);
|
||
|
|
||
|
return (slot);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_filter_uninstall
|
||
|
*
|
||
|
* return 0 on success
|
||
|
* return 1 on failure
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
dq_filter_uninstall (int dq_desc)
|
||
|
{
|
||
|
dq_filter *this;
|
||
|
int n;
|
||
|
|
||
|
pthread_mutex_lock (&dqf_mutex);
|
||
|
|
||
|
for (n = 0; n < DQ_MAX; n++) {
|
||
|
if (dqf[n] != NULL) {
|
||
|
pthread_mutex_lock (&dqf[n]->dq_mutex);
|
||
|
|
||
|
/* if filter matches, uninstall it
|
||
|
*/
|
||
|
if (dqf[n]->dq_desc == dq_desc) {
|
||
|
|
||
|
this = dqf[n];
|
||
|
dqf[n] = NULL;
|
||
|
|
||
|
/* kill ALL waiting routines
|
||
|
*/
|
||
|
while (this->dq_wait_count > 0) {
|
||
|
|
||
|
/* no real activation
|
||
|
*/
|
||
|
this->dq_sem_real = 0;
|
||
|
sem_post (&this->dq_sem);
|
||
|
|
||
|
/* and let one waiter die
|
||
|
*/
|
||
|
pthread_mutex_unlock (&dqf[n]->dq_mutex);
|
||
|
pthread_mutex_lock (&dqf[n]->dq_mutex);
|
||
|
}
|
||
|
|
||
|
dq_p_free_all (this);
|
||
|
dq_filter_free (this);
|
||
|
|
||
|
dq_count--;
|
||
|
|
||
|
/* `dq_desc' should be unique, so we don't care
|
||
|
*/
|
||
|
pthread_mutex_unlock (&dqf_mutex);
|
||
|
return (0);
|
||
|
}
|
||
|
pthread_mutex_unlock (&dqf[n]->dq_mutex);
|
||
|
}
|
||
|
}
|
||
|
pthread_mutex_unlock (&dqf_mutex);
|
||
|
|
||
|
return (1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_p_free_all
|
||
|
*
|
||
|
* free's all resisting packets within one filter
|
||
|
*
|
||
|
* return in any case
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_p_free_all (dq_filter *dq)
|
||
|
{
|
||
|
dq_packet *this, *last;
|
||
|
|
||
|
for (this = dq->p_root; this != NULL;) {
|
||
|
last = this;
|
||
|
this = this->next;
|
||
|
dq_p_free (last);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_p_free
|
||
|
*
|
||
|
* free the packet pointed to by `dqp'
|
||
|
*
|
||
|
* return in any case
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_p_free (dq_packet *dqp)
|
||
|
{
|
||
|
if (dqp != NULL) {
|
||
|
if (dqp->packet != NULL)
|
||
|
free (dqp->packet);
|
||
|
free (dqp);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_filter_free
|
||
|
*
|
||
|
* return in any case
|
||
|
*/
|
||
|
|
||
|
void
|
||
|
dq_filter_free (dq_filter *dq)
|
||
|
{
|
||
|
if (dq == NULL)
|
||
|
return;
|
||
|
|
||
|
pthread_mutex_destroy (&dq->dq_mutex);
|
||
|
sem_destroy (&dq->dq_sem);
|
||
|
|
||
|
if (dq->query != NULL)
|
||
|
free (dq->query);
|
||
|
|
||
|
free (dq);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_filter_wait
|
||
|
*
|
||
|
* 'select' for filter descriptors.
|
||
|
* wait a maximum of time defined in `tv' to get packets for filter defined
|
||
|
* by `dq_desc'. if `tv' is { 0, 0 }, don't block, if `tv' is NULL, wait
|
||
|
* indefinitly.
|
||
|
*
|
||
|
* return 1 if packet was caught
|
||
|
* return 0 on timeout
|
||
|
*/
|
||
|
|
||
|
int
|
||
|
dq_filter_wait (int dq_desc, struct timeval *tv)
|
||
|
{
|
||
|
int rval = 0; /* return value */
|
||
|
int n = 0; /* temporary return value */
|
||
|
|
||
|
|
||
|
/* first, register us as a filter waiter
|
||
|
*/
|
||
|
pthread_mutex_lock (&dqf[dq_desc]->dq_mutex);
|
||
|
dqf[dq_desc]->dq_wait_count++;
|
||
|
pthread_mutex_unlock (&dqf[dq_desc]->dq_mutex);
|
||
|
|
||
|
/* if a timeout is required, fire up another subthread, that just
|
||
|
* will post the semaphore after a given timeout, but set dq_sem_real
|
||
|
* to zero, to tell us that it's just a timeout semaphore.
|
||
|
*
|
||
|
* in the other case, if a real packet intrudes, dq_activate will post
|
||
|
* the semaphore AND will notify us through dq_sem_real = 1 that it's
|
||
|
* a real packet.
|
||
|
*
|
||
|
* in the worst case, the filter is being uninstalled, and dq_sem_real
|
||
|
* will be "2", that means we should just return as if no packet has
|
||
|
* been caught.
|
||
|
*
|
||
|
* if no timeout is used it's just a sem_wait.
|
||
|
*/
|
||
|
|
||
|
/* check wether we have to wait indefinite
|
||
|
*/
|
||
|
if (tv != NULL) {
|
||
|
|
||
|
/* check wether it is a timeouting wait request
|
||
|
*/
|
||
|
if (tv->tv_sec != 0 || tv->tv_usec != 0) {
|
||
|
pthread_t tout_tid; /* timeout thread id */
|
||
|
dqtim_val *paa = xcalloc (1, sizeof (dqtim_val));
|
||
|
|
||
|
/* build up a pseudo structure, just for parameter passing
|
||
|
*/
|
||
|
paa->tv.tv_sec = tv->tv_sec;
|
||
|
paa->tv.tv_usec = tv->tv_usec;
|
||
|
paa->df = dqf[dq_desc];
|
||
|
|
||
|
/* start a timeouter thread
|
||
|
*/
|
||
|
n = pthread_create (&tout_tid, NULL, (void *) dq_timer, (void *) paa);
|
||
|
|
||
|
if (n != -1) {
|
||
|
sem_wait (&dqf[dq_desc]->dq_sem);
|
||
|
|
||
|
/* destroy the timeouting thread on real packet
|
||
|
* added pthread_join () call - 990925.
|
||
|
*/
|
||
|
if (dqf[dq_desc]->dq_sem_real != 0) {
|
||
|
pthread_cancel (tout_tid);
|
||
|
}
|
||
|
pthread_join (tout_tid, NULL);
|
||
|
}
|
||
|
|
||
|
/* clean the mess up and set the return value
|
||
|
*/
|
||
|
free (paa);
|
||
|
rval = dqf[dq_desc]->dq_sem_real;
|
||
|
|
||
|
} else {
|
||
|
|
||
|
/* non blocking check
|
||
|
*/
|
||
|
n = sem_trywait (&dqf[dq_desc]->dq_sem);
|
||
|
if (n == 0)
|
||
|
rval = 1;
|
||
|
}
|
||
|
} else {
|
||
|
/* wait indefinitly
|
||
|
*/
|
||
|
|
||
|
n = sem_wait (&dqf[dq_desc]->dq_sem);
|
||
|
|
||
|
if (n == 0) {
|
||
|
pthread_mutex_lock (&dqf[dq_desc]->dq_mutex);
|
||
|
n = dqf[dq_desc]->dq_sem_real;
|
||
|
if (n == 1)
|
||
|
rval = 1;
|
||
|
|
||
|
pthread_mutex_unlock (&dqf[dq_desc]->dq_mutex);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* decrease the listeners count
|
||
|
*/
|
||
|
pthread_mutex_lock (&dqf[dq_desc]->dq_mutex);
|
||
|
dqf[dq_desc]->dq_wait_count--;
|
||
|
pthread_mutex_unlock (&dqf[dq_desc]->dq_mutex);
|
||
|
|
||
|
return (rval);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* dq_timer
|
||
|
*
|
||
|
* timeout thread, that will just raise a semaphore after a given timeout
|
||
|
* the thread has to be cancelled if the timeout is not necessary anymore.
|
||
|
*
|
||
|
* return nothing (threaded)
|
||
|
*/
|
||
|
|
||
|
void *
|
||
|
dq_timer (dqtim_val *paa)
|
||
|
{
|
||
|
unsigned long long usec; /* microseconds to sleep */
|
||
|
|
||
|
/* added to allow immediate interruption.
|
||
|
* -smiler 990925
|
||
|
*/
|
||
|
pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL);
|
||
|
pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
|
||
|
|
||
|
/* calculate time to sleep, then sleep until either timeout
|
||
|
* or interruption
|
||
|
*/
|
||
|
usec = (paa->tv.tv_sec * 1000000) + paa->tv.tv_usec;
|
||
|
usleep (usec);
|
||
|
|
||
|
/* we survived, now be faster then the race condition ;-D
|
||
|
*/
|
||
|
|
||
|
paa->df->dq_sem_real = 0; /*0 = just a timeout*/
|
||
|
sem_post (&paa->df->dq_sem); /* post semaphore */
|
||
|
|
||
|
return (NULL);
|
||
|
}
|
||
|
|