Initial stab at DNS code, including:

* new check tool for querying MX records,
* dns test harness
This commit is contained in:
alexhudson
2007-08-17 20:11:44 +00:00
parent a05bf36889
commit 698cda51d7
9 changed files with 752 additions and 4 deletions
+2
View File
@@ -144,6 +144,7 @@ include_subdirs := \
src/apps/queuetool \
src/apps/storetool \
src/apps/storetool/demo \
src/apps/testtool \
src/www \
zoneinfo \
man \
@@ -212,6 +213,7 @@ include src/apps/mdbtool/Bongo.rules
include src/apps/queuetool/Bongo.rules
include src/apps/storetool/Bongo.rules
include src/apps/storetool/demo/Bongo.rules
include src/apps/testtool/Bongo.rules
include src/www/Bongo.rules
include zoneinfo/Bongo.rules
include man/Bongo.rules
+1
View File
@@ -670,6 +670,7 @@ src/apps/mdbtool/Makefile
src/apps/queuetool/Makefile
src/apps/storetool/Makefile
src/apps/storetool/demo/Makefile
src/apps/testtool/Makefile
src/libs/cal/Makefile
src/libs/cal/tests/Makefile
src/libs/calcmd/Makefile
+108
View File
@@ -0,0 +1,108 @@
//
#ifndef XPLDNS_H
#define XPLDNS_H
#include <xplutil.h>
#define XPLDNS_NAMELEN 256
typedef enum {
XPLDNS_INTERNAL_ERROR = -1, // internal error, see errno
XPLDNS_SUCCESS = 0,
XPLDNS_NOT_FOUND = 1, // authoritative host not found
XPLDNS_TRY_AGAIN = 2, // host not found or server failure
XPLDNS_DNS_ERROR = 3, // query refused/other non-recoverable
XPLDNS_NO_DATA = 4, // name valid, no records exist
XPLDNS_FAIL = 100,
XPLDNS_FAIL_CNAME_LOOP = 101
} XplDns_ResultCode;
typedef enum {
XPLDNS_RR_A = 0x0001,
XPLDNS_RR_CNAME = 0x0005,
XPLDNS_RR_SOA = 0x0006,
XPLDNS_RR_PTR = 0x000C,
XPLDNS_RR_MX = 0x000F,
XPLDNS_RR_TXT = 0x0010
} XplDns_RecordType;
typedef struct {
char name[XPLDNS_NAMELEN + 1]; // Domain name on MX record
char mxname[XPLDNS_NAMELEN + 1]; // address of record
unsigned short preference; // Preference of record
} XplDns_MxRecord;
typedef struct {
char name[XPLDNS_NAMELEN + 1]; // Domain name on A record
int address; // IP address of record
} XplDns_ARecord;
typedef struct {
char name[XPLDNS_NAMELEN + 1]; // Domain name on PTR record
} XplDns_PtrRecord;
typedef struct {
char name[XPLDNS_NAMELEN + 1]; // Domain name on CNAME
char cname[XPLDNS_NAMELEN + 1]; // what it points to
} XplDns_CnameRecord;
typedef struct {
char mname[XPLDNS_NAMELEN + 1]; // source host
char rname[XPLDNS_NAMELEN + 1]; // contact e-mail
unsigned long serial; // serial number
unsigned long refresh; // refresh time (secs)
unsigned long retry; // retry time (secs)
unsigned long expire; // expiry time (secs)
unsigned long minimum; // minimum TTL (secs)
} XplDns_SoaRecord;
typedef struct {
char txt[XPLDNS_NAMELEN + 1]; // Text attached to record
} XplDns_TxtRecord;
typedef union {
XplDns_ARecord A;
XplDns_CnameRecord CNAME;
XplDns_MxRecord MX;
XplDns_PtrRecord PTR;
XplDns_SoaRecord SOA;
XplDns_TxtRecord TXT;
} XplDns_Record;
typedef struct _XplDns_RecordList XplDns_RecordList;
struct _XplDns_RecordList {
XplDns_RecordList *next;
XplDns_RecordType type;
XplDns_Record record;
};
typedef struct {
XplDns_ResultCode status;
XplDns_RecordList *list;
} XplDns_Result;
#define XPLDNS_MAX_IP_IN_LIST 20
typedef struct{
XplDns_ResultCode status;
int ip_list[XPLDNS_MAX_IP_IN_LIST + 1];
int number;
} XplDns_IpList;
typedef struct {
XplDns_ResultCode status;
int preference;
char current_mx[XPLDNS_NAMELEN + 1];
XplDns_Result *_mx_result;
XplDns_RecordList *_mx_current;
} XplDns_MxLookup;
int XplDnsInit(void);
void XplDnsResultFree(XplDns_Result *result);
XplDns_Result * XplDnsLookupIP(const char *domain);
XplDns_MxLookup *XplDnsNewMxLookup(const char *domain);
XplDns_IpList * XplDnsNextMxLookupIpList(XplDns_MxLookup *mx);
void XplDnsFreeMxLookup(XplDns_MxLookup *mx);
#endif
+26
View File
@@ -0,0 +1,26 @@
# -*- Makefile -*-
sbin_PROGRAMS += bongo-testtool
bongo_testtool_SOURCES := \
$(conf_defaults) \
src/apps/testtool/config.h \
src/apps/testtool/testtool.c
bongo_testtool_CPPFLAGS := $(AM_CPPFLAGS) \
$(GNUTLS_CFLAGS)
bongo_testtool_LDADD := \
libbongonmap.la \
libbongomsgapi.la \
libbongoconnio.la \
libbongomemmgr.la \
libbongoutil.la \
libbongoxpl.la \
$(PTHREAD_LIBS) \
$(GNUTLS_LIBS) \
$(ALL_LIBS)
src/apps/testtool/all: bongo-testtool
src/apps/testtool/clean: clean
src/apps/testtool/install:
@cd $(top_builddir) && $(MAKE) $(MFLAGS) sbin_PROGRAMS=bongo-testtool install-sbinPROGRAMS
+17
View File
@@ -0,0 +1,17 @@
# -*- Makefile -*-
#
# Do not edit!
#
# (unless this is Makefile.am.subdir)
#
# This file is a copy of the toplevel Makefile.am.subdir. If changes
# need to be made, edit that file, and make update-makefiles, and
# check the new files in.
#
# This is a skeletal automake file. The real automake fules for
# building the targets for this directory can be found in the
# Bongo.rules file. This file is simply here so that 'make all' can
# work in each subdirectory.
all clean install:
@cd $(top_builddir) && $(MAKE) $(MFLAGS) $(subdir)/$@
+4
View File
@@ -0,0 +1,4 @@
// private header
void usage(void);
void LookupMxRecords(const char *domain);
+112
View File
@@ -0,0 +1,112 @@
/* This program is free software, licensed under the terms of the GNU GPL.
* See the Bongo COPYING file for full details
* Copyright (c) 2007 Alex Hudson
*/
#include <xpl.h>
#include <xpldns.h>
#include <msgapi.h>
#include <arpa/inet.h>
#include <bongo-buildinfo.h>
#include "config.h"
#include <libintl.h>
#define _(x) gettext(x)
void
usage(void) {
char *text =
"bongo-testtool - Test parts of the Bongo system.\n\n"
"Usage: bongo-testtool [command]\n\n"
"Commands:\n"
" checkmx <domain> Search for mail exchangers\n"
"";
XplConsolePrintf("%s", text);
}
void
LookupMxRecords(const char *domain)
{
XplDns_MxLookup *mx;
XplDns_IpList *list;
mx = XplDnsNewMxLookup(domain);
if (!mx || mx->status != XPLDNS_SUCCESS) {
XplConsolePrintf(_("ERROR: Unable to resolve MX records on %s"), domain);
return;
}
// FIXME. We shouldn't try to relay to MXes who are lower preference
// than we are, if we're in the list (to prevent mail loops).
// In practice, this means stopping as soon as come to an IP we
// recognise as being ours.
while((list = XplDnsNextMxLookupIpList(mx)) != NULL) {
int x;
XplConsolePrintf(_("%s (preference: %d):\n"),
mx->current_mx, mx->preference);
for (x = 0; x < list->number; x++) {
struct sockaddr_in addr;
addr.sin_addr.s_addr = list->ip_list[x];
XplConsolePrintf(" - %s\n", inet_ntoa(addr.sin_addr));
}
MemFree(list);
}
XplDnsFreeMxLookup(mx);
}
int
main(int argc, char *argv[]) {
int next_arg = 0;
int command = 0;
int startup = 0;
if (!MemoryManagerOpen("bongo-testtool")) {
XplConsolePrintf(_("ERROR: Failed to initialize memory manager\n"));
return 1;
}
// parse options
while (++next_arg < argc && argv[next_arg][0] == '-') {
char *arg = argv[next_arg];
printf(_("Unrecognized option: %s\n"), argv[next_arg]);
}
// parse command
if (next_arg < argc) {
if (!strcmp(argv[next_arg], "checkmx")) {
command = 1;
} else {
printf(_("Unrecognized command: %s\n"), argv[next_arg]);
}
} else {
printf(_("ERROR: No command specified\n"));
usage();
return 1;
}
XplInit();
if (XplSetRealUser(MsgGetUnprivilegedUser()) < 0) {
printf(_("bongo-testtool: Could not drop to unprivileged user '%s'\n"), MsgGetUnprivilegedUser());
exit(-1);
}
switch(command) {
case 1:
next_arg++;
if (next_arg >= argc) {
printf(_("Usage: checkmx <domain>\n"));
} else {
LookupMxRecords(argv[next_arg]);
}
break;
default:
break;
}
MemoryManagerClose("bongo-testtool");
exit(0);
}
+462 -2
View File
@@ -1,10 +1,470 @@
// Xpl DNS resolution routines.
#include <xpl.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include "dns.h"
#include "config.h"
BOOL
int
XplDnsInit()
{
return FALSE;
// res_init() may set errno, so we clear it and then test later
errno = 0;
(void) res_init();
if (errno != 0)
return -1;
return 0;
}
int
_XplDns_ParseName(const char *response, const int response_len, const char *name, char *buffer, int buffer_len)
{
const char *ptr;
int len = 0;
int result = 0;
if (buffer_len < 2) return -1; // out of space
len = buffer_len - strlen(buffer);
if (len <= 0) return -1; // out of space
ptr = name;
while ((response <= ptr) && (ptr < (response + response_len))) {
if (*ptr == '\0') {
// we've reached the end
ptr++;
break;
}
if ((*ptr & 0xc0) == 0xc0) {
int result;
int newptr;
// this is a pointer to a previous label
newptr = (*ptr++ & 0x3f) * 256;
newptr += *ptr++;
if (newptr == (ptr - response - 2))
return -4; // cyclical pointer; bad.
result = _XplDns_ParseName(response, response_len, (response + newptr), buffer, buffer_len);
if (result <= 0) return result;
break;
} else {
// append the next label if we have room in the buffer
if ((*ptr + 1) < len) {
strncat(buffer, ptr + 1, *ptr);
strncat(buffer, ".", 1);
len -= *ptr + 1;
ptr += *ptr + 1;
} else {
// out of room!
return -1;
}
}
}
result = ptr - name;
if ((result < 0) || (result > response_len))
// a big mistake?
return -2;
return result;
}
#define IS_MX(r) ((r)->type == XPLDNS_RR_MX)
void
_XplDnsResult_AppendRecord(XplDns_Result *result, XplDns_RecordList *item)
{
// this doesn't always append: if the item is an MX record, it will
// be inserted before MX records of higher preference, or appended.
// Thus, the record list always has MX records in order.
// TOOD: Equal preference MX records are sorted in reply order (e.g.,
// round-robin). We're supposed to randomise them.
XplDns_RecordList *end;
item->next = NULL;
if (result->list == NULL) {
// no items in list
result->list = item;
} else { //if (result->list->next == NULL) {
// one item already in list
end = result->list;
// if this is an MX RR, see if we can insert before the first
// item in our list
if (IS_MX(item) && IS_MX(end)) {
// TODO: if pref==pref, then we should randomise
// See RFC 2821 #5
if (end->record.MX.preference >= item->record.MX.preference) {
item->next = result->list;
result->list = item;
return;
}
}
while (end->next != NULL) {
// see if we can insert before a later MX record
if (IS_MX(item) && IS_MX(end->next)) {
// See above TODO re: randomisation.
if (end->next->record.MX.preference >=
item->record.MX.preference) {
// insert here!
item->next = end->next;
end->next = item;
return;
}
}
end = end->next;
}
end->next = item;
}
}
void
_XplDnsResult_PrintResults(XplDns_Result *result)
{
XplDns_RecordList *item;
if (result == NULL) return;
item = result->list;
printf ("\n== START OF DNS RECORD LIST\n");
while (item != NULL) {
struct sockaddr_in addr;
switch (item->type) {
case XPLDNS_RR_A:
addr.sin_addr.s_addr = item->record.A.address;
printf("%s IN A %s\n", item->record.A.name, inet_ntoa(addr.sin_addr));
break;
case XPLDNS_RR_CNAME:
printf("%s IN CNAME %s\n", item->record.CNAME.name, item->record.CNAME.cname);
break;
case XPLDNS_RR_MX:
printf("%s IN MX %d %s\n",
item->record.MX.name,
item->record.MX.preference,
item->record.MX.mxname);
break;
default:
printf("Unknown record.\n");
break;
}
item = item->next;
}
printf ("== END OF RESULTS\n");
}
void
XplDnsResultFree(XplDns_Result *result)
{
XplDns_RecordList *item = result->list;
XplDns_RecordList *last;
if (result == NULL) return;
while (item != NULL) {
last = item;
item = item->next;
MemFree(last);
}
MemFree(result);
}
XplDns_MxLookup *
XplDnsNewMxLookup(const char *domain)
{
// Lazy MX resolver.
// Should comply with RFC 2821 #5
XplDns_MxLookup *result;
XplDns_Result *mx_query;
char mail_domain[XPLDNS_NAMELEN + 1];
int cnames_found = 0;
result = MemMalloc0(sizeof(XplDns_MxLookup));
if (! result) return NULL;
strncpy(mail_domain, domain, XPLDNS_NAMELEN);
// we come back to this point if we encounter CNAMEs before MX RRs
restart_query:
mx_query = _XplDns_Query(mail_domain, XPLDNS_RR_MX);
if (mx_query->status == XPLDNS_NO_DATA) {
// domain exists, but no MX records
XplDns_Result *a_query;
a_query = _XplDns_Query(domain, XPLDNS_RR_A);
if (a_query->status == XPLDNS_SUCCESS) {
// A RR exists, so let's fake an MX record for it
XplDnsResultFree(mx_query);
XplDnsResultFree(a_query);
mx_query = _XplDns_QueryFakeMx(domain);
}
} else if (mx_query->status == XPLDNS_SUCCESS) {
// check we don't have any CNAME in results
XplDns_RecordList *record = mx_query->list;
while (record) {
if (record->type == XPLDNS_RR_CNAME) {
// restart our query on new domain
strncpy(mail_domain, record->record.CNAME.cname,
XPLDNS_NAMELEN);
XplDnsResultFree(mx_query);
cnames_found++;
if (cnames_found < 6) {
goto restart_query;
} else {
result->status = XPLDNS_FAIL_CNAME_LOOP;
return result;
}
} else {
record = record->next;
}
}
}
result->status = mx_query->status;
result->_mx_result = mx_query;
result->_mx_current = mx_query->list;
return result;
}
XplDns_IpList *
XplDnsNextMxLookupIpList(XplDns_MxLookup *mx)
{
XplDns_IpList *result;
XplDns_Result *lookup;
XplDns_RecordList *record;
const char *mail_exchanger = NULL;
if (mx->_mx_current == NULL) return NULL;
result = MemMalloc(sizeof(XplDns_IpList));
if (! result) return NULL;
result->status = XPLDNS_TRY_AGAIN;
result->number = 0;
if (mx->_mx_current->type == XPLDNS_RR_MX)
// want to make sure we're looking at the right records.
mail_exchanger = mx->_mx_current->record.MX.mxname;
// no decent records at this point?
if (! mail_exchanger) return result;
strncpy(mx->current_mx, mail_exchanger, XPLDNS_NAMELEN);
mx->preference = mx->_mx_current->record.MX.preference;
mx->_mx_current = mx->_mx_current->next;
// look up the mail server's IP address(es)
lookup = _XplDns_Query(mail_exchanger, XPLDNS_RR_A);
if (lookup->status != XPLDNS_SUCCESS) {
// more failure... :/
result->status = lookup->status;
return result;
}
record = lookup->list;
while (record) {
// FIXME: check for CNAME entries?
if (record->type != XPLDNS_RR_A) {
record = record->next;
continue; // not interested in non-A records
}
result->ip_list[result->number] = record->record.A.address;
result->number++;
if (result->number >= XPLDNS_MAX_IP_IN_LIST)
break;
record = record->next;
}
XplDnsResultFree(lookup);
return result;
}
void
XplDnsFreeMxLookup(XplDns_MxLookup *mx)
{
if (mx == NULL) return;
if (mx->_mx_result) {
XplDnsResultFree(mx->_mx_result);
mx->_mx_result = NULL;
}
MemFree(mx);
}
XplDns_Result *
_XplDns_QueryFakeMx(const char *domain)
{
// when a domain has no MX RR, RFC 2821 says treat any A record
// for that domain as having an implicit MX record. This creates
// that record, as if it existed in DNS.
XplDns_Result *result;
XplDns_RecordList *list;
list = MemMalloc(sizeof(XplDns_RecordList));
strncpy(list->record.MX.name, domain, XPLDNS_NAMELEN);
strncpy(list->record.MX.mxname, domain, XPLDNS_NAMELEN);
list->record.MX.preference = 0;
list->type = XPLDNS_RR_MX;
result = MemMalloc(sizeof(XplDns_Result));
result->list = list;
result->status = XPLDNS_SUCCESS;
return result;
}
XplDns_Result *
_XplDns_Query(const char *domain, XplDns_RecordType type)
{
XplDns_Result *result;
char answer_buffer[4096];
int answer_len = -1;
result = MemMalloc(sizeof(XplDns_Result));
result->list = NULL;
result->status = XPLDNS_FAIL;
// FIXME: is this re-entrant _everywhere_ ?
answer_len = res_query(domain, ns_c_any, type, answer_buffer, 4096);
if (answer_len < 0) {
// didn't get a sensible answer
if (h_errno) {
result->status = (XplDns_ResultCode) h_errno;
return result;
} else {
return result;
}
}
if (answer_len == 0) {
// no records in the answer
result->status = XPLDNS_NO_DATA;
return result;
}
_XplDns_ParseQuery(answer_buffer, answer_len, result);
return result;
}
void
_XplDns_ParseQuery(const char *answer_buffer, int answer_len, XplDns_Result *result)
{
XplDnsResponseHeader *header;
const char *response, *response_end;
int x;
int res;
int answers;
// try to parse the answer
header = (XplDnsResponseHeader *)answer_buffer;
response = answer_buffer + sizeof(XplDnsResponseHeader);
response_end = answer_buffer + answer_len;
for (x = 0; x < ntohs(header->qdcount); x++) {
int size;
char buffer[1024];
buffer[0] = '\0';
size = _XplDns_ParseName(answer_buffer, answer_len, response, buffer, 1000);
// did we find the name correctly?
if (size < 0) return;
// Skip QCODE / QCLASS
response += size + 4;
// does the buffer extent look right?
if (response >= response_end) return;
}
for (x = 0; x < ntohs(header->ancount); x++) {
XplDnsAnswer *dns_answer;
XplDns_RecordList *item;
char answer_domain[XPLDNS_NAMELEN + 1];
item = MemMalloc(sizeof(XplDns_RecordList));
answer_domain[0] = '\0';
res = _XplDns_ParseName(answer_buffer, answer_len,
response, answer_domain, XPLDNS_NAMELEN);
if (res < 0) return; // couldn't parse name...
response += res;
if (response >= response_end) return;
dns_answer = (XplDnsAnswer *)response;
response += sizeof(XplDnsAnswer);
if (response >= response_end) return;
// TODO : check ntohs(ans->class) is right
switch(ntohs(dns_answer->rtype)) {
case XPLDNS_RR_A: {
item->type = XPLDNS_RR_A;
XplDnsAnswerARecord *rec = (XplDnsAnswerARecord *)response;
strncpy(item->record.A.name, answer_domain, XPLDNS_NAMELEN);
item->record.A.address = rec->address;
_XplDnsResult_AppendRecord(result, item);
}
break;
case XPLDNS_RR_CNAME: {
item->type = XPLDNS_RR_CNAME;
strncpy(item->record.CNAME.name, answer_domain,
XPLDNS_NAMELEN);
item->record.CNAME.cname[0] = '\0';
_XplDns_ParseName(answer_buffer, answer_len, response, item->record.CNAME.cname, XPLDNS_NAMELEN);
_XplDnsResult_AppendRecord(result, item);
}
break;
case XPLDNS_RR_MX: {
XplDnsAnswerMxRecord *rec = (XplDnsAnswerMxRecord *)response;
int r;
strncpy(item->record.MX.name, answer_domain,
XPLDNS_NAMELEN);
item->type = XPLDNS_RR_MX;
item->record.MX.preference = ntohs(rec->pref);
item->record.MX.mxname[0] = '\0';
r = _XplDns_ParseName(answer_buffer,
answer_len,
response + sizeof(XplDnsAnswerMxRecord),
item->record.MX.mxname,
XPLDNS_NAMELEN);
if (r >= 0) {
_XplDnsResult_AppendRecord(result, item);
} else {
return;
}
}
break;
default:
// unknown type.
MemFree(item);
break;
}
response += ntohs(dns_answer->size);
if (response >= response_end) return;
}
// don't make the result 'success' until we've really done everything
result->status = XPLDNS_SUCCESS;
return;
}
+20 -2
View File
@@ -1,17 +1,35 @@
// Test the Xpl DNS resolution code
#include <xpl.h>
#include <xpldns.h>
#include <memmgr.h>
#include <bongocheck.h>
#ifdef BONGO_HAVE_CHECK
#define MEM_NAME "check-xpl-dns"
START_TEST(test1)
{
fail_unless(1==1);
XplDns_MxLookup *mx;
XplDns_IpList *list;
MemoryManagerOpen(MEM_NAME);
fail_unless(XplDnsInit() == 0);
mx = XplDnsNewMxLookup("bongo-project.org");
fail_unless(mx != NULL);
fail_unless(mx->status == XPLDNS_SUCCESS);
XplDnsFreeMxLookup(mx);
MemoryManagerClose(MEM_NAME);
}
END_TEST
START_CHECK_SUITE_SETUP("Testing the Xpl DNS routines")
START_CHECK_SUITE_SETUP("Xpl DNS routine tests")
CREATE_CHECK_CASE (tc_core , "Core" );
CHECK_SUITE_ADD_CASE(top_suite, tc_core );
CHECK_CASE_ADD_TEST (tc_core , test1 );