--- src/base32.c.orig	2009-04-27 10:23:40.000000000 +0200
+++ src/base32.c	2009-09-16 18:38:49.000000000 +0200
@@ -26,6 +26,8 @@
 
 static const char cb32[] = 
 	"abcdefghijklmnopqrstuvwxyz012345";
+static const char cb32_ucase[] = 
+	"ABCDEFGHIJKLMNOPQRSTUVWXYZ012345";
 static unsigned char rev32[128];
 
 static int base32_decode(void *, size_t *, const char *, size_t);
@@ -80,6 +82,8 @@
 		for (i = 0; i < 32; i++) {
 			c = cb32[i];
 			rev32[(int) c] = i;
+			c = cb32_ucase[i];
+			rev32[(int) c] = i;
 		}
 		reverse_init = 1;
 	}
--- src/dns.c.orig	2009-04-27 10:23:40.000000000 +0200
+++ src/dns.c	2009-09-16 18:57:03.000000000 +0200
@@ -38,6 +38,8 @@
 #include "encoding.h"
 #include "read.h"
 
+#define CHECKLEN(x) if (buflen - (p-buf) < (x))  return 0
+
 int
 dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_t datalen)
 {
@@ -46,6 +48,9 @@
 	char *p;
 	int len;
 
+	if (buflen < sizeof(HEADER))
+		return 0;
+
 	memset(buf, 0, buflen);
 	
 	header = (HEADER*)buf;
@@ -67,29 +72,77 @@
 	
 		name = 0xc000 | ((p - buf) & 0x3fff);
 
-		putname(&p, sizeof(q->name), q->name);
+		/* Question section */
+		putname(&p, buflen - (p - buf), q->name);
 
+		CHECKLEN(4);
 		putshort(&p, q->type);
 		putshort(&p, C_IN);
 
+		/* Answer section */
+		CHECKLEN(10);
 		putshort(&p, name);	
-		putshort(&p, q->type);
+		if (q->type == T_A)
+			putshort(&p, T_CNAME);	/* answer CNAME to A question */
+		else
+			putshort(&p, q->type);
 		putshort(&p, C_IN);
-		putlong(&p, 0);
+		putlong(&p, 0);		/* TTL */
+
+		if (q->type == T_CNAME || q->type == T_A || q->type == T_MX) {
+			/* data is expected to be like "Hblabla.host.name.com\0" */
+
+			char *startp = p;
+			int namelen;
+
+			p += 2;			/* skip 2 bytes length */
+			CHECKLEN(2);
+			if (q->type == T_MX)
+				putshort(&p, 10);	/* preference */
+			putname(&p, buflen - (p - buf), data);
+			CHECKLEN(0);
+			namelen = p - startp;
+			namelen -= 2;
+			putshort(&startp, namelen);
+		} else if (q->type == T_TXT) {
+			/* TXT has binary or base-X data */
+			char *startp = p;
+			int txtlen;
+
+			p += 2;			/* skip 2 bytes length */
+			puttxtbin(&p, buflen - (p - buf), data, datalen);
+			CHECKLEN(0);
+			txtlen = p - startp;
+			txtlen -= 2;
+			putshort(&startp, txtlen);
+		} else {
+			/* NULL has raw binary data */
+			datalen = MIN(datalen, buflen - (p - buf));
+			CHECKLEN(2);
+			putshort(&p, datalen);
+			CHECKLEN(datalen);
+			putdata(&p, data, datalen);
+			 CHECKLEN(0);
+		}
 
-		putshort(&p, datalen);
-		putdata(&p, data, datalen);
 		break;
 	case QR_QUERY:
+		/* Note that iodined also uses this for forward queries */
+
 		header->qdcount = htons(1);
 		header->arcount = htons(1);
 	
-		putname(&p, datalen, data);
+		putname(&p, buflen - (p - buf), data);
 
+		CHECKLEN(4);
 		putshort(&p, q->type);
 		putshort(&p, C_IN);
 
-		/* EDNS0 */
+		/*
+		 * EDNS0 to advertise maximum response length
+		 * (even CNAME/A/MX, 255+255+header would be >512)
+		 */
+		CHECKLEN(11);
 		putbyte(&p, 0x00);    /* Root */
 		putshort(&p, 0x0029); /* OPT */
 		putshort(&p, 0x1000); /* Payload size: 4096 */
@@ -116,10 +169,13 @@
 	int domain_len;
 	char *p;
 
+	if (buflen < sizeof(HEADER))
+		return 0;
+
 	memset(buf, 0, buflen);
-	
+
 	header = (HEADER*)buf;
-	
+
 	header->id = htons(q->id);
 	header->qr = 1;
 	header->opcode = 0;
@@ -147,33 +203,38 @@
 	topname = 0xc000 | ((p - buf + domain_len) & 0x3fff);
 
 	/* Query section */
-	putname(&p, sizeof(q->name), q->name);	/* Name */
-	putshort(&p, q->type);			/* Type */
-	putshort(&p, C_IN);			/* Class */
+	putname(&p, buflen - (p - buf), q->name);	/* Name */
+	CHECKLEN(4);
+	putshort(&p, q->type);				/* Type */
+	putshort(&p, C_IN);				/* Class */
 
 	/* Answer section */
-	putshort(&p, name);			/* Name */
-	putshort(&p, q->type);			/* Type */
-	putshort(&p, C_IN);			/* Class */
-	putlong(&p, 0x3ea7d011);		/* TTL */
-	putshort(&p, 5);			/* Data length */
+	CHECKLEN(12);
+	putshort(&p, name);				/* Name */
+	putshort(&p, q->type);				/* Type */
+	putshort(&p, C_IN);				/* Class */
+	putlong(&p, 3600);				/* TTL */
+	putshort(&p, 5);				/* Data length */
 
 	/* pointer to ns.topdomain */
 	nsname = 0xc000 | ((p - buf) & 0x3fff);
+	CHECKLEN(5);
 	putbyte(&p, 2);
 	putbyte(&p, 'n');
 	putbyte(&p, 's');
-	putshort(&p, topname);			/* Name Server */
+	putshort(&p, topname);				/* Name Server */
 
 	/* Additional data (A-record of NS server) */
-	putshort(&p, nsname);			/* Name Server */
-	putshort(&p, T_A);			/* Type */
-	putshort(&p, C_IN);			/* Class */
-	putlong(&p, 0x3ea7d011);		/* TTL */
-	putshort(&p, 4);			/* Data length */
+	CHECKLEN(12);
+	putshort(&p, nsname);				/* Name Server */
+	putshort(&p, T_A);				/* Type */
+	putshort(&p, C_IN);				/* Class */
+	putlong(&p, 3600);				/* TTL */
+	putshort(&p, 4);				/* Data length */
 
 	/* ugly hack to output IP address */
 	domain = (char *) &q->destination;
+	CHECKLEN(4);
 	putbyte(&p, *domain++);
 	putbyte(&p, *domain++);
 	putbyte(&p, *domain++);
@@ -183,6 +244,8 @@
 	return len;
 }
 
+#undef CHECKLEN
+
 unsigned short
 dns_get_id(char *packet, size_t packetlen)
 {
@@ -195,6 +258,8 @@
 	return ntohs(header->id);
 }
 
+#define CHECKLEN(x) if (packetlen - (data-packet) < (x))  return 0
+
 int
 dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, size_t packetlen)
 {
@@ -234,27 +299,50 @@
 
 	switch (qr) {
 	case QR_ANSWER:
-		if(qdcount != 1 || ancount != 1) {
+		if(qdcount < 1 || ancount < 1) {
+			/* We may get both CNAME and A, then ancount=2 */
 			switch (header->rcode) {
-			case REFUSED:
-				warnx("Got REFUSED as reply");
+			case NOERROR:	/* 0 */
+				if (header->tc)
+					warnx("Got TRUNCATION as reply: response too long for DNS path");
+				else
+					warnx("Got reply without error, but also without question and/or answer");
+				break;
+
+			case FORMERR:	/* 1 */
+				warnx("Got FORMERR as reply: server does not understand our request");
 				break;
 
-			case NOTIMP:
-				warnx("Got NOTIMP as reply");
+			case SERVFAIL:	/* 2 */
+				if (qdcount >= 1
+				    && packetlen >= sizeof(HEADER) + 2
+				    && (data[1] == 'r' || data[1] == 'R'))
+					warnx("Got SERVFAIL as reply on earlier fragsize autoprobe");
+				else if (qdcount >= 1
+				    && packetlen >= sizeof(HEADER) + 2
+				    && (data[1] < '0' || data[1] > '9')
+				    && (data[1] < 'a' || data[1] > 'f')
+				    && (data[1] < 'A' || data[1] > 'F')
+				    && data[1] != 'p' && data[1] != 'P')
+					warnx("Got SERVFAIL as reply on earlier config setting");
+				else
+					warnx("Got SERVFAIL as reply: server failed or recursion timeout");
 				break;
 
-			case NXDOMAIN:
-				warnx("Got NXDOMAIN as reply");
+			case NXDOMAIN:	/* 3 */
+				warnx("Got NXDOMAIN as reply: domain does not exist");
 				break;
 
-			case SERVFAIL:
-				warnx("Got SERVFAIL as reply");
+			case NOTIMP:	/* 4 */
+				warnx("Got NOTIMP as reply: server does not support our request");
 				break;
 
-			case NOERROR:
+			case REFUSED:	/* 5 */
+				warnx("Got REFUSED as reply");
+                                break;
+
 			default:
-				warnx("no query or answer in reply packet");
+				warnx("Got RCODE %u as reply", (unsigned int) header->rcode);
 				break;
 			}
 			return -1;
@@ -264,33 +352,65 @@
 			q->id = id;
 
 		readname(packet, packetlen, &data, name, sizeof(name));
+		CHECKLEN(4);
 		readshort(packet, &data, &type);
 		readshort(packet, &data, &class);
-		
+
+		/* Assume that first answer is NULL/CNAME that we wanted */
 		readname(packet, packetlen, &data, name, sizeof(name));
+		CHECKLEN(10);
 		readshort(packet, &data, &type);
 		readshort(packet, &data, &class);
 		readlong(packet, &data, &ttl);
 		readshort(packet, &data, &rlen);
-		rv = MIN(rlen, sizeof(rdata));
-		rv = readdata(packet, &data, rdata, rv);
 
-		if(type == T_NULL && rv >= 2 && buf) {
-			rv = MIN(rv, buflen);
-			memcpy(buf, rdata, rv);
+		if (type == T_NULL) {
+			rv = MIN(rlen, sizeof(rdata));
+			rv = readdata(packet, &data, rdata, rv);
+			if (rv >= 2 && buf) {
+				rv = MIN(rv, buflen);
+				memcpy(buf, rdata, rv);
+			}
+			/* "else rv=0;" here? */
 		}
+		if ((type == T_CNAME || type == T_MX) && buf) {
+			if (type == T_MX)
+				data += 2;	/* skip preference */
+			memset(name, 0, sizeof(name));
+			readname(packet, packetlen, &data, name, sizeof(name) - 1);
+			name[sizeof(name)-1] = '\0';
+			strncpy(buf, name, buflen);
+			buf[buflen - 1] = '\0';
+			rv = strlen(buf);
+		}
+		if (type == T_TXT && buf) {
+			rv = readtxtbin(packet, &data, rlen, rdata, sizeof(rdata));
+			if (rv >= 1) {
+				rv = MIN(rv, buflen);
+				memcpy(buf, rdata, rv);
+			}
+		}
+		if (q != NULL)
+			q->type = type;
 		break;
 	case QR_QUERY:
-		if (qdcount != 1) {
+		if (qdcount < 1) {
 			warnx("no question section in name query");
 			return -1;
 		}
 
+		memset(name, 0, sizeof(name));
 		readname(packet, packetlen, &data, name, sizeof(name) - 1);
 		name[sizeof(name)-1] = '\0';
+		CHECKLEN(4);
 		readshort(packet, &data, &type);
 		readshort(packet, &data, &class);
 
+		if (q == NULL) {
+			rv = 0;
+			break;
+		}
+
 		strncpy(q->name, name, sizeof(q->name));
 		q->name[sizeof(q->name) - 1] = '\0';
 		q->type = type;
--- src/encoding.c.orig	2009-04-27 10:23:40.000000000 +0200
+++ src/encoding.c	2009-09-16 18:59:01.000000000 +0200
@@ -27,7 +27,7 @@
 }
 
 int 
-inline_dotify(char *buf, size_t buflen)
+inline_dotify(char *buf, size_t buflen, int intv)
 {
 	unsigned dots;
 	unsigned pos;
@@ -35,7 +35,7 @@
 	char *reader, *writer;
 
 	total = strlen(buf);
-	dots = total / 57;
+	dots = total / intv;
 
 	writer = buf;
 	writer += total;
@@ -54,7 +54,7 @@
 	while (dots) {
 		*writer-- = *reader--;
 		pos--;
-		if (pos % 57 == 0) {
+		if (pos % intv == 0) {
 			*writer-- = '.';
 			dots--;
 		}
--- src/encoding.h.orig	2009-01-14 20:44:41.000000000 +0100
+++ src/encoding.h	2009-09-16 18:59:29.000000000 +0200
@@ -28,7 +28,7 @@
 };
 
 int unpack_data(char *, size_t, char *, size_t, struct encoder *);
-int inline_dotify(char *, size_t);
+int inline_dotify(char *, size_t, int);
 int inline_undotify(char *, size_t);
 
 
--- src/read.c.orig	2009-01-14 20:44:41.000000000 +0100
+++ src/read.c	2009-09-16 20:35:44.000000000 +0200
@@ -126,6 +126,35 @@
 }
 
 int
+readtxtbin(char *packet, char **src, size_t srcremain, char *dst, size_t dstremain)
+{
+	unsigned char *uc;
+	int tocopy;
+	int dstused = 0;
+
+	while (srcremain > 0)
+	{
+		uc = (unsigned char*) (*src);
+		tocopy = *uc;
+		(*src)++;
+		srcremain--;
+
+		if (tocopy > srcremain)
+			return 0;	/* illegal, better have nothing */
+		if (tocopy > dstremain)
+			return 0;	/* doesn't fit, better have nothing */
+
+		memcpy(dst, *src, tocopy);
+		dst += tocopy;
+		(*src) += tocopy;
+		srcremain -= tocopy;
+		dstremain -= tocopy;
+		dstused += tocopy;
+	}
+	return dstused;
+}
+
+int
 putname(char **buf, size_t buflen, const char *host)
 {
 	char *word;
@@ -212,3 +241,35 @@
 	return len;
 }
 
+int
+puttxtbin(char **buf, size_t bufremain, char *from, size_t fromremain)
+{
+	unsigned char uc;
+	unsigned char *ucp = &uc;
+	char *cp = (char *) ucp;
+	int tocopy;
+	int bufused = 0;
+
+	while (fromremain > 0)
+	{
+		tocopy = fromremain;
+		if (tocopy > 252)
+			tocopy = 252;	/* allow off-by-1s in caches etc */
+		if (tocopy + 1 > bufremain)
+			return -1;	/* doesn't fit, better have nothing */
+
+		uc = tocopy;
+		**buf = *cp;
+		(*buf)++;
+		bufremain--;
+		bufused++;
+
+		memcpy(*buf, from, tocopy);
+		(*buf) += tocopy;
+		from += tocopy;
+		bufremain -= tocopy;
+		fromremain -= tocopy;
+		bufused += tocopy;
+	}
+	return bufused;
+}
--- src/read.h.orig	2009-01-14 20:44:41.000000000 +0100
+++ src/read.h	2009-09-16 20:36:02.000000000 +0200
@@ -21,11 +21,13 @@
 int readshort(char *, char **, short *);
 int readlong(char *, char **, uint32_t *);
 int readdata(char *, char **, char *, size_t);
+int readtxtbin(char *, char **, size_t, char *, size_t);
 
 int putname(char **, size_t, const char *);
 int putbyte(char **, unsigned char);
 int putshort(char **, unsigned short);
 int putlong(char **, uint32_t);
 int putdata(char **, char *, size_t);
+int puttxtbin(char **, size_t, char *, size_t);
 
 #endif
--- src/user.h.orig	2009-08-15 21:36:07.000000000 +0200
+++ src/user.h	2009-09-16 20:39:34.000000000 +0200
@@ -31,6 +31,7 @@
 	struct packet inpacket;
 	struct packet outpacket;
 	struct encoder *encoder;
+	char downenc;
 	int out_acked_seqno;
 	int out_acked_fragment;
 	int fragsize;
@@ -38,12 +39,13 @@
 };
 
 extern struct user users[USERS];
+extern time_t now;
 
 int init_users(in_addr_t, int);
 int users_waiting_on_reply();
 int find_user_by_ip(uint32_t);
 int all_users_waiting_to_send();
-int find_available_user();
+int find_available_user(int forced_userid);
 void user_switch_codec(int userid, struct encoder *enc);
 void user_set_conn_type(int userid, enum connection c);
 
--- src/util.c.orig	2009-09-15 10:58:38.000000000 +0200
+++ src/util.c	2009-09-16 19:45:50.000000000 +0200
@@ -29,7 +29,7 @@
 	rv = NULL;
 
 	if ((fp = fopen("/etc/resolv.conf", "r")) == NULL) 
-		err(1, "/etc/resolve.conf");
+		err(1, "/etc/resolv.conf");
 	
 	while (feof(fp) == 0) {
 		fgets(buf, sizeof(buf), fp);
--- src/windows.h.orig	2009-04-27 10:23:40.000000000 +0200
+++ src/windows.h	2009-09-16 20:40:04.000000000 +0200
@@ -28,9 +28,13 @@
 #define T_A DNS_TYPE_A
 #define T_NS DNS_TYPE_NS
 #define T_NULL DNS_TYPE_NULL
+#define T_CNAME DNS_TYPE_CNAME
+#define T_MX DNS_TYPE_MX
+#define T_TXT DNS_TYPE_TXT
 
 #define C_IN 1
 
+#define FORMERR 1
 #define SERVFAIL 2
 #define NXDOMAIN 3
 #define NOTIMP 4
--- src/iodine.c.orig	2009-09-15 11:22:05.000000000 +0200
+++ src/iodine.c	2009-09-16 21:53:14.000000000 +0200
@@ -48,6 +48,10 @@
 static char *__progname;
 #endif
 
+extern int forced_userid;
+extern void set_qtype(char *);
+extern void set_downenc(char *);
+
 static void
 sighandler(int sig) 
 {
@@ -59,7 +63,7 @@
 	extern char *__progname;
 
 	fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] "
-			"[-P password] [-m maxfragsize] [-z context] [-F pidfile] "
+			"[-P password] [-m maxfragsize] [-U userid] [-T type] [-O enc] [-z context] [-F pidfile] "
 			"[nameserver] topdomain\n", __progname);
 	exit(2);
 }
@@ -70,7 +74,7 @@
 
 	fprintf(stderr, "iodine IP over DNS tunneling client\n");
 	fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] "
-			"[-P password] [-m maxfragsize] [-z context] [-F pidfile] "
+			"[-P password] [-m maxfragsize] [-U userid] [-T type] [-O enc] [-z context] [-F pidfile] "
 			"[nameserver] topdomain\n", __progname);
 	fprintf(stderr, "  -v to print version info and exit\n");
 	fprintf(stderr, "  -h to print this help and exit\n");
@@ -81,6 +85,9 @@
 	fprintf(stderr, "  -d device to set tunnel device name\n");
 	fprintf(stderr, "  -P password used for authentication (max 32 chars will be used)\n");
 	fprintf(stderr, "  -m maxfragsize, to limit size of downstream packets\n");
+	fprintf(stderr, "  -U force userid (=IP address) on server, exit if unavailable\n");
+	fprintf(stderr, "  -T dns type: NULL (default, fastest), TXT, CNAME, A (CNAME answer), MX\n");
+	fprintf(stderr, "  -O downstream encoding (!NULL): Base32(default), Base64, or Raw (only TXT)\n");
 	fprintf(stderr, "  -z context, to apply specified SELinux context after initialization\n");
 	fprintf(stderr, "  -F pidfile to write pid to a file\n");
 	fprintf(stderr, "nameserver is the IP number of the relaying nameserver, if absent /etc/resolv.conf is used\n");
@@ -131,6 +138,7 @@
 #endif
 	username = NULL;
 	memset(password, 0, 33);
+	srand(time(NULL));
 	foreground = 0;
 	newroot = NULL;
 	context = NULL;
@@ -157,7 +165,7 @@
 		__progname++;
 #endif
 
-	while ((choice = getopt(argc, argv, "vfhru:t:d:P:m:F:")) != -1) {
+	while ((choice = getopt(argc, argv, "vfhru:t:d:P:m:F:U:T:O:")) != -1) {
 		switch(choice) {
 		case 'v':
 			version();
@@ -198,6 +206,37 @@
 		case 'F':
 			pidfile = optarg;
 			break;    
+		case 'U':
+			forced_userid = atoi(optarg);
+			if (forced_userid < 0)
+				forced_userid = -1;
+			break;
+		case 'T':
+			set_qtype(optarg);
+#if 0
+			if (!strcasecmp(optarg, "NULL"))
+				do_qtype = T_NULL;
+			else if (!strcasecmp(optarg, "CNAME"))
+				do_qtype = T_CNAME;
+			else if (!strcasecmp(optarg, "A"))
+				do_qtype = T_A;
+			else if (!strcasecmp(optarg, "MX"))
+				do_qtype = T_MX;
+			else if (!strcasecmp(optarg, "TXT"))
+				do_qtype = T_TXT;
+#endif
+			break;
+		case 'O':       /* not -D, is Debug in server */
+			set_downenc(optarg);
+#if 0
+			if (!strcasecmp(optarg, "base32"))
+				downenc = 'T';
+			else if (!strcasecmp(optarg, "base64"))
+				downenc = 'S';
+			else if (!strcasecmp(optarg, "raw"))
+				downenc = 'R';
+#endif
+			break;
 		default:
 			usage();
 			/* NOTREACHED */
@@ -232,6 +271,7 @@
 	if (nameserv_addr) {
 		client_set_nameserver(nameserv_addr);
 	} else {
+		warnx("No nameserver found - not connected to any network?\n");
 		usage();
 		/* NOTREACHED */
 	}	
--- src/client.c.orig	2009-09-15 21:35:16.000000000 +0200
+++ src/client.c	2009-09-16 21:58:01.000000000 +0200
@@ -14,6 +14,7 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <ctype.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
@@ -75,17 +76,24 @@
 
 /* My userid at the server */
 static char userid;
+int forced_userid = -1;
 
 /* DNS id for next packet */
 static uint16_t chunkid;
 
 /* Base32 encoder used for non-data packets */
 static struct encoder *b32;
+static struct encoder *b64;
 
 /* The encoder used for data packets
  * Defaults to Base32, can be changed after handshake */
 static struct encoder *dataenc;
 
+static char downenc = ' ';
+
+/* set query type to send */
+static unsigned short do_qtype = T_NULL;
+
 /* My connection mode */
 static enum connection conn;
 
@@ -99,10 +107,11 @@
 	downstream_fragment = 0;
 	down_ack_seqno = 0;
 	down_ack_fragment = 0;
-	chunkid = 0;
+	chunkid = ((unsigned int) rand()) & 0xFFFF;
 	b32 = get_base32_encoder();
+	b64 = get_base64_encoder();
 	dataenc = get_base32_encoder();
-	rand_seed = rand();
+	rand_seed = ((unsigned int) rand()) & 0xFFFF;
 	conn = CONN_DNS_NULL;
 }
 
@@ -157,10 +166,19 @@
 	struct query q;
 	size_t len;
 
-	q.id = ++chunkid;
-	q.type = T_NULL;
+	chunkid += 7727;
+	if (chunkid == 0)
+		/* 0 is used as "no-query" in iodined.c */
+		chunkid = 7727;
+
+	q.id = chunkid;
+	q.type = do_qtype;
 
 	len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(hostname));
+	if (len < 1) {
+		warnx("dns_encode doesn't fit");
+		return;
+	}
 
 	sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, sizeof(nameserv));
 }
@@ -208,8 +226,9 @@
 	encsize = encoder->encode(buf, &space, data, datalen);
 
 	if (!encoder->places_dots())
-		inline_dotify(buf, buflen);
+		inline_dotify(buf, buflen, 57);
 
+	/* Add dot (if it wasn't there already) and topdomain */
 	b = buf;
 	b += strlen(buf);
 
@@ -241,7 +260,7 @@
 static void
 send_chunk(int fd)
 {
-	char hex[] = "0123456789ABCDEF";
+	char hex[] = "0123456789abcdef";
 	char buf[4096];
 	int avail;
 	int code;
@@ -295,7 +314,7 @@
 		
 		rand_seed++;
 
-		send_packet(fd, 'P', data, sizeof(data));
+		send_packet(fd, 'p', data, sizeof(data));
 	} else {
 		send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING);
 	}
@@ -322,6 +341,88 @@
 
 		rv = dns_decode(buf, buflen, &q, QR_ANSWER, data, r);
 
+		if ((q.type == T_CNAME || q.type == T_MX || q.type == T_TXT)
+		    && rv >= 1)
+		/* CNAME an also be returned from an A (or MX) question */
+		{
+			size_t space;
+
+			/*
+			 * buf is a hostname or txt stream that we still need to
+			 * decode to binary
+			 * 
+			 * also update rv with the number of valid bytes
+			 * 
+			 * data is unused here, and will certainly hold the smaller binary
+			 */
+
+			switch (buf[0]) {
+			case 'h': /* Hostname with base32 */
+			case 'H':
+				if (rv < 5) {
+					/* 1 byte H, 3 bytes ".xy", >=1 byte data */
+					rv = 0;
+					break;
+				}
+
+				rv -= 3;	/* rv=strlen, strip ".xy" */
+				rv = unpack_data (data, sizeof(data), buf + 1, rv - 1, b32);
+				/* this also does undotify */
+
+				rv = MIN(rv, buflen);
+				memcpy(buf, data, rv);
+				break;
+			case 'i': /* Hostname++ with base64 */
+			case 'I':
+				if (rv < 5) {
+					/* 1 byte H, 3 bytes ".xy", >=1 byte data */
+					rv = 0;
+					break;
+				}
+
+				rv -= 3;	/* rv=strlen, strip ".xy" */
+				rv = unpack_data (data, sizeof(data), buf + 1, rv - 1, b64);
+				/* this also does undotify */
+
+				rv = MIN(rv, buflen);
+				memcpy(buf, data, rv);
+				break;
+			case 't': /* plain base32(Thirty-two) from TXT */
+			case 'T':
+				if (rv < 2) {
+					rv = 0;
+					break;
+				}
+
+				space = sizeof(data);
+				rv = b32->decode (data, &space, buf + 1, rv - 1);
+				rv = MIN(rv, buflen);
+				memcpy(buf, data, rv);
+				break;
+			case 's': /* plain base64(Sixty-four) from TXT */
+			case 'S':
+				if (rv < 2) {
+					rv = 0;
+					break;
+				}
+
+				space = sizeof(data);
+				rv = b64->decode (data, &space, buf + 1, rv - 1);
+				rv = MIN(rv, buflen);
+				memcpy(buf, data, rv);
+				break;
+			case 'r': /* Raw binary from TXT */
+			case 'R':
+				rv--;			/* rv>=1 already checked */
+				memmove(buf, buf+1, rv);
+				break;
+			default:
+				warnx("Received unsupported encoding");
+				rv = 0;
+				break;
+			}
+		}
+
 		/* decode the data header, update seqno and frag before next request */
 		if (rv >= 2) {
 			downstream_seqno = (buf[1] >> 5) & 7;
@@ -345,7 +446,7 @@
 						downstream_seqno != down_ack_seqno ||
 						downstream_fragment != down_ack_fragment
 						)) {
-						
+
 						send_ping(dns_fd);
 					}
 				} else {
@@ -521,7 +622,7 @@
 	
 	rand_seed++;
 
-	send_packet(fd, 'L', data, sizeof(data));
+	send_packet(fd, 'l', data, sizeof(data));
 }
 
 static void
@@ -530,9 +631,12 @@
 	char probedata[256];
 	char buf[4096];
 
-	/* build a large query domain which is random and maximum size */
-	memset(probedata, MIN(1, rand_seed & 0xff), sizeof(probedata));
-	probedata[1] = MIN(1, (rand_seed >> 8) & 0xff);
+	/*
+	 * build a large query domain which is random and maximum size,
+	 * will also take up maximal space in the return packet
+	 */
+	memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata));
+	probedata[1] = MAX(1, (rand_seed >> 8) & 0xff);
 	rand_seed++;
 	build_hostname(buf + 4, sizeof(buf) - 4, probedata, sizeof(probedata), topdomain, dataenc);
 
@@ -559,25 +663,34 @@
 	
 	rand_seed++;
 
-	send_packet(fd, 'N', data, sizeof(data));
+	send_packet(fd, 'n', data, sizeof(data));
 }
 
 static void 
 send_version(int fd, uint32_t version)
 {
-	char data[6];
+	char data[7];
+	int datalen;
 
 	data[0] = (version >> 24) & 0xff;
 	data[1] = (version >> 16) & 0xff;
 	data[2] = (version >> 8) & 0xff;
 	data[3] = (version >> 0) & 0xff;
 
-	data[4] = (rand_seed >> 8) & 0xff;
-	data[5] = (rand_seed >> 0) & 0xff;
+	if (forced_userid < 0) {
+		data[4] = (rand_seed >> 8) & 0xff;
+		data[5] = (rand_seed >> 0) & 0xff;
+		datalen = 6;
+	} else {
+		data[4] = (forced_userid + 1) & 0xff;
+		data[5] = (rand_seed >> 8) & 0xff;
+		data[6] = (rand_seed >> 0) & 0xff;
+		datalen = 7;
+	}
 	
 	rand_seed++;
 
-	send_packet(fd, 'V', data, sizeof(data));
+	send_packet(fd, 'V', data, datalen);
 }
 
 static void
@@ -619,7 +732,7 @@
 static void
 send_codec_switch(int fd, int userid, int bits)
 {
-	char buf[512] = "S_____.";
+	char buf[512] = "s_____.";
 	buf[1] = b32_5to8(userid);
 	buf[2] = b32_5to8(bits);
 	
@@ -631,6 +744,23 @@
 	strncat(buf, topdomain, 512 - strlen(buf));
 	send_query(fd, buf);
 }
+
+
+static void
+send_downenc_switch(int fd, int userid)
+{
+	char buf[512] = "o_____.";
+	buf[1] = b32_5to8(userid);
+	buf[2] = tolower(downenc);
+
+	buf[3] = b32_5to8((rand_seed >> 10) & 0x1f);
+	buf[4] = b32_5to8((rand_seed >> 5) & 0x1f);
+	buf[5] = b32_5to8((rand_seed ) & 0x1f);
+	rand_seed++;
+
+	strncat(buf, topdomain, 512 - strlen(buf));
+	send_query(fd, buf);
+}
 	
 static int
 handshake_version(int dns_fd, int *seed)
@@ -675,6 +805,12 @@
 					*seed = payload;
 					userid = in[8];
 
+					if (forced_userid >= 0 &&
+					    (int) userid != forced_userid) {
+						warnx("Got userid %d instead of requested %d, probably old server version. Try again immediately.", (int) userid, forced_userid);
+						return 1;
+					}
+
 					fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", VERSION, userid);
 					return 0;
 				} else if (strncmp("VNAK", in, 4) == 0) {
@@ -682,7 +818,10 @@
 							VERSION, payload);
 					return 1;
 				} else if (strncmp("VFUL", in, 4) == 0) {
-					warnx("Server full, all %d slots are taken. Try again later", payload);
+					if (forced_userid >= 0)
+						warnx("Forced userid %d not available. Try again later.", forced_userid);
+					else
+						warnx("Server full, all %d slots are taken. Try again later", payload);
 					return 1;
 				}
 			} else 
@@ -691,7 +830,7 @@
 		
 		fprintf(stderr, "Retrying version check...\n");
 	}
-	warnx("couldn't connect to server");
+	warnx("couldn't connect to server (maybe other -T options will work)");
 	return 1;
 }
 
@@ -924,7 +1063,7 @@
 	int read;
 
 	dataenc = get_base64_encoder();
-	fprintf(stderr, "Switching to %s codec\n", dataenc->name);
+	fprintf(stderr, "Switching upstream to %s codec\n", dataenc->name);
 	/* Send to server that this user will use base64 from now on */
 	for (i=0; running && i<5 ;i++) {
 		int bits;
@@ -955,7 +1094,7 @@
 					goto codec_revert;
 				}
 				in[read] = 0; /* zero terminate */
-				fprintf(stderr, "Server switched to codec %s\n", in);
+				fprintf(stderr, "Server switched upstream to codec %s\n", in);
 				return;
 			}
 		}
@@ -968,6 +1107,138 @@
 	dataenc = get_base32_encoder();
 }
 
+static void
+handshake_switch_downenc(int dns_fd)
+{
+	struct timeval tv;
+	char in[4096];
+	fd_set fds;
+	int i;
+	int r;
+	int read;
+	char *dname;
+
+	dname = "Base32";
+	if (downenc == 'S')
+		dname = "Base64";
+	else if (downenc == 'R')
+		dname = "Raw";
+
+	fprintf(stderr, "Switching downstream to codec %s\n", dname);
+	for (i=0; running && i<5 ;i++) {
+		tv.tv_sec = i + 1;
+		tv.tv_usec = 0;
+
+		send_downenc_switch(dns_fd, userid);
+
+		FD_ZERO(&fds);
+		FD_SET(dns_fd, &fds);
+
+		r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
+
+		if(r > 0) {
+			read = read_dns(dns_fd, 0, in, sizeof(in));
+
+			if (read > 0) {
+				if (strncmp("BADLEN", in, 6) == 0) {
+					fprintf(stderr, "Server got bad message length. ");
+					goto codec_revert;
+				} else if (strncmp("BADIP", in, 5) == 0) {
+					fprintf(stderr, "Server rejected sender IP address. ");
+					goto codec_revert;
+				} else if (strncmp("BADCODEC", in, 8) == 0) {
+					fprintf(stderr, "Server rejected the selected codec. ");
+					goto codec_revert;
+				}
+				in[read] = 0; /* zero terminate */
+				fprintf(stderr, "Server switched downstream to codec %s\n", in);
+				return;
+			}
+		}
+		fprintf(stderr, "Retrying codec switch...\n");
+	}
+	fprintf(stderr, "No reply from server on codec switch. ");
+
+codec_revert: 
+	fprintf(stderr, "Falling back to base32\n");
+}
+
+
+static int
+fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize)
+/* Returns: 0: keep checking, 1: break loop (either okay or definitely wrong) */
+{
+	int acked_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff);
+	static int nocheck_warned = 0;
+
+	if (read >= 5 && strncmp("BADIP", in, 5) == 0) {
+		fprintf(stderr, "got BADIP (Try iodined -c)..\n");
+		fflush(stderr);
+		return 0;		/* maybe temporary error */
+	}
+
+	if (acked_fragsize != proposed_fragsize) {
+		/*
+		 * got ack for wrong fragsize, maybe late response for
+		 * earlier query, or ack corrupted
+		 */
+		return 0;
+	}
+
+	if (read != proposed_fragsize) {
+		/*
+		 * correctly acked fragsize but read too little (or too
+		 * much): this fragsize is definitely not reliable
+		 */
+		return 1;
+	}
+
+	/* here: read == proposed_fragsize == acked_fragsize */
+
+	/* test: */
+	/* in[123] = 123; */
+
+	/* Check for corruption */
+	if ((in[2] & 0xff) == 107) {
+		int okay = 1;
+		int i;
+		unsigned int v = in[3] & 0xff;
+
+		for (i = 3; i < read; i++, v += 107)
+			if ((in[i] & 0xff) != (v & 0xff)) {
+				okay = 0;
+				break;
+			}
+
+		if (okay) {
+			fprintf(stderr, "%d ok.. ", acked_fragsize);
+			fflush(stderr);
+			*max_fragsize = acked_fragsize;
+			return 1;
+		} else {
+			if (downenc != ' ' && downenc != 'T')
+				fprintf(stderr, "%d corrupted at %d.. (Try -O Base32)\n", acked_fragsize, i);
+			else
+				fprintf(stderr, "%d corrupted at %d.. ", acked_fragsize, i);
+				fflush(stderr);
+			return 1;
+		}
+	}		/* always returns */
+
+	/* here when uncheckable, so assume correct */
+
+	if (read >= 3 && nocheck_warned == 0) {
+		fprintf(stderr, "(Old server version, cannot check for corruption)\n");
+		fflush(stderr);
+		nocheck_warned = 1;
+	}
+	fprintf(stderr, "%d ok.. ", acked_fragsize);
+	fflush(stderr);
+	*max_fragsize = acked_fragsize;
+	return 1;
+}
+
+
 static int
 handshake_autoprobe_fragsize(int dns_fd)
 {
@@ -979,11 +1250,12 @@
 	int read;
 	int proposed_fragsize = 768;
 	int range = 768;
-	int max_fragsize = 0;
+	int max_fragsize;
 
 	max_fragsize = 0;
 	fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)\n"); 
-	while (running && range > 0 && (range >= 8 || !max_fragsize)) {
+	while (running && range > 0 && (range >= 8 || max_fragsize < 300)) {
+		/* stop the slow probing early when we have enough bytes anyway */
 		for (i=0; running && i<3 ;i++) {
 			tv.tv_sec = 1;
 			tv.tv_usec = 0;
@@ -994,24 +1266,13 @@
 
 			r = select(dns_fd + 1, &fds, NULL, NULL, &tv);
 
-			if(r > 0) {
+			if(r >= 2) {
 				read = read_dns(dns_fd, 0, in, sizeof(in));
 				
 				if (read > 0) {
 					/* We got a reply */
-					int acked_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff);
-					if (acked_fragsize == proposed_fragsize) {
-						if (read == proposed_fragsize) {
-							fprintf(stderr, "%d ok.. ", acked_fragsize);
-							fflush(stderr);
-							max_fragsize = acked_fragsize;
-						}
-					}
-					if (strncmp("BADIP", in, 5) == 0) {
-						fprintf(stderr, "got BADIP.. ");
-						fflush(stderr);
-					}
-					break;
+					if (fragsize_check(in, read, proposed_fragsize, &max_fragsize) == 1)
+						break;
 				}
 			}
 			fprintf(stderr, ".");
@@ -1030,17 +1291,22 @@
 	}
 	if (!running) {
 		fprintf(stderr, "\n");
-		warnx("stopped while autodetecting fragment size (Try probing manually with -m)");
+		warnx("stopped while autodetecting fragment size (Try setting manually with -m)");
 		return 0;
 	}
-	if (range == 0) {
+	if (max_fragsize <= 2) {
 		/* Tried all the way down to 2 and found no good size */
 		fprintf(stderr, "\n");
-		warnx("found no accepted fragment size. (Try probing manually with -m)");
+		warnx("found no accepted fragment size. (Try forcing with -m, or try other -T or -O options)");
 		return 0;
 	}
-	fprintf(stderr, "will use %d\n", max_fragsize);
-	return max_fragsize;
+	/* data header adds 2 bytes */
+	fprintf(stderr, "will use %d-2=%d\n", max_fragsize, max_fragsize - 2);
+
+	if (do_qtype != T_NULL && downenc == ' ')
+		fprintf(stderr, "(Maybe other -O options will increase throughput)\n");
+
+	return max_fragsize - 2;
 }
 
 static void
@@ -1117,6 +1383,10 @@
 			handshake_switch_codec(dns_fd);
 		}
 
+		if (downenc != ' ') {
+			handshake_switch_downenc(dns_fd);
+		}
+
 		if (autodetect_frag_size) {
 			fragsize = handshake_autoprobe_fragsize(dns_fd);
 			if (!fragsize) {
@@ -1130,3 +1400,28 @@
 	return 0;
 }
 
+void
+set_qtype(char *qtype)
+{
+	if (!strcasecmp(qtype, "NULL"))
+       		do_qtype = T_NULL;
+	else if (!strcasecmp(qtype, "CNAME"))
+		do_qtype = T_CNAME;
+	else if (!strcasecmp(qtype, "A"))
+		do_qtype = T_A;
+	else if (!strcasecmp(qtype, "MX"))
+		do_qtype = T_MX;
+	else if (!strcasecmp(qtype, "TXT"))
+		do_qtype = T_TXT;
+}
+
+void
+set_downenc(char *encoding)
+{
+	if (!strcasecmp(encoding, "base32"))
+		downenc = 'T';
+	else if (!strcasecmp(encoding, "base64"))
+		downenc = 'S';
+	else if (!strcasecmp(encoding, "raw"))
+		downenc = 'R';
+}
--- src/iodined.c.orig	2009-09-15 10:58:38.000000000 +0200
+++ src/iodined.c	2009-09-16 21:58:30.000000000 +0200
@@ -69,6 +69,7 @@
 static char *topdomain;
 static char password[33];
 static struct encoder *b32;
+static struct encoder *b64;
 static int created_users;
 
 static int check_ip;
@@ -86,7 +87,7 @@
 #endif
 
 static int read_dns(int, int, struct query *);
-static void write_dns(int, struct query *, char *, int);
+static void write_dns(int, struct query *, char *, int, char);
 static void handle_full_packet(int, int);
 
 static void
@@ -120,7 +121,8 @@
 	if (userid < 0 || userid >= created_users ) {
 		return 1; 
 	}
-	if (!users[userid].active) {
+	if (!users[userid].active || users[userid].disabled ||
+	    users[userid].last_pkt + 60 < now) {
 		return 1;
 	}
 	if (users[userid].last_pkt + 60 < time(NULL)) {
@@ -226,7 +228,7 @@
 	out[7] = ((payload) & 0xff);
 	out[8] = userid & 0xff;
 
-	write_dns(fd, q, out, sizeof(out));
+	write_dns(fd, q, out, sizeof(out), users[userid].downenc);
 }
 
 static void
@@ -271,7 +273,7 @@
 			users[userid].outpacket.seqno & 7, users[userid].outpacket.fragment & 15, 
 			last, users[userid].outpacket.offset, datalen, users[userid].outpacket.len, userid);
 	}
-	write_dns(dns_fd, &users[userid].q, pkt, datalen + 2);
+	write_dns(dns_fd, &users[userid].q, pkt, datalen + 2, users[userid].downenc);
 	users[userid].q.id = 0;
 
 	if (users[userid].outpacket.len > 0 && 
@@ -339,6 +341,7 @@
 	if(in[0] == 'V' || in[0] == 'v') {
 		int version = 0;
 
+		int forced_userid = -1;
 		read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32);
 		/* Version greeting, compare and send ack/nak */
 		if (read > 4) { 
@@ -348,9 +351,14 @@
 					   ((unpacked[2] & 0xff) << 8) |
 					   ((unpacked[3] & 0xff)));
 		}
+		if (read >= 7) {
+			/* Received V + 32bits version + 8bits userid + 16bits CMC */
+			forced_userid = unpacked[4];
+			forced_userid--;
+		}
 
 		if (version == VERSION) {
-			userid = find_available_user();
+			userid = find_available_user(forced_userid);
 			if (userid >= 0) {
 				struct sockaddr_in *tempin;
 
@@ -361,15 +369,30 @@
 				
 				memcpy(&(users[userid].q), q, sizeof(struct query));
 				users[userid].encoder = get_base32_encoder();
+				users[userid].downenc = 'T';
 				send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q);
 				syslog(LOG_INFO, "accepted version for user #%d from %s",
 					userid, inet_ntoa(tempin->sin_addr));
 				users[userid].q.id = 0;
+				users[userid].outpacket.len = 0;
+				users[userid].outpacket.offset = 0;
+				users[userid].outpacket.sentlen = 0;
+				users[userid].outpacket.seqno = 0;
+				users[userid].outpacket.fragment = 0;
+				users[userid].inpacket.len = 0;
+				users[userid].inpacket.offset = 0;
+				users[userid].inpacket.seqno = 0;
+				users[userid].inpacket.fragment = 0;
+				users[userid].fragsize = 100; /* very safe */
 			} else {
 				/* No space for another user */
 				send_version_response(dns_fd, VERSION_FULL, created_users, 0, q);
-				syslog(LOG_INFO, "dropped user from %s, server full", 
-					inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr));
+				if (forced_userid != -1)
+					syslog(LOG_INFO, "dropped user from %s, forced userid %d not available",
+						inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr), forced_userid);
+				else
+					syslog(LOG_INFO, "dropped user from %s, server full",
+						inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr));
 			}
 		} else {
 			send_version_response(dns_fd, VERSION_NACK, VERSION, 0, q);
@@ -383,12 +406,12 @@
 		userid = unpacked[0];
 
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s",
 				userid, inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr));
 			return;
 		} else {
-			users[userid].last_pkt = time(NULL);
+			users[userid].last_pkt = now;
 			login_calculate(logindata, 16, password, users[userid].seed);
 
 			if (read >= 18 && (memcmp(logindata, unpacked+1, 16) == 0)) {
@@ -402,14 +425,14 @@
 				read = snprintf(out, sizeof(out), "%s-%s-%d-%d", 
 						tmp[0], tmp[1], my_mtu, netmask);
 
-				write_dns(dns_fd, q, out, read);
+				write_dns(dns_fd, q, out, read, users[userid].downenc);
 				q->id = 0;
 				syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]);
 
 				free(tmp[1]);
 				free(tmp[0]);
 			} else {
-				write_dns(dns_fd, q, "LNAK", 4);
+				write_dns(dns_fd, q, "LNAK", 4, 'T');
 				syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password",
 					userid, inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr));
 			}
@@ -423,7 +446,7 @@
 		
 		userid = b32_8to5(in[1]);
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			return; /* illegal id */
 		}
 
@@ -441,25 +464,26 @@
 		reply[2] = (addr >> 16) & 0xFF;
 		reply[3] = (addr >>  8) & 0xFF;
 		reply[4] = (addr >>  0) & 0xFF;
-		write_dns(dns_fd, q, reply, sizeof(reply));
+		write_dns(dns_fd, q, reply, sizeof(reply), 'T');
 	} else if(in[0] == 'Z' || in[0] == 'z') {
 		/* Check for case conservation and chars not allowed according to RFC */
 
 		/* Reply with received hostname as data */
-		write_dns(dns_fd, q, in, domain_len);
+		/* No userid here, reply with lowest-grade downenc */
+		write_dns(dns_fd, q, in, domain_len, 'T');
 		return;
 	} else if(in[0] == 'S' || in[0] == 's') {
 		int codec;
 		struct encoder *enc;
 		if (domain_len < 3) { /* len at least 3, example: "S15" */
-			write_dns(dns_fd, q, "BADLEN", 6);
+			write_dns(dns_fd, q, "BADLEN", 6, 'T');
 			return;
 		}
 
 		userid = b32_8to5(in[1]);
 		
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			return; /* illegal id */
 		}
 		
@@ -469,15 +493,49 @@
 		case 5: /* 5 bits per byte = base32 */
 			enc = get_base32_encoder();
 			user_switch_codec(userid, enc);
-			write_dns(dns_fd, q, enc->name, strlen(enc->name));
+			write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc);
 			break;
 		case 6: /* 6 bits per byte = base64 */
 			enc = get_base64_encoder();
 			user_switch_codec(userid, enc);
-			write_dns(dns_fd, q, enc->name, strlen(enc->name));
+			write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc);
+			break;
+		default:
+			write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc);
+			break;
+		}
+		return;
+	} else if(in[0] == 'O' || in[0] == 'o') {
+		if (domain_len != 4) { /* len = 4, example: "O1T." */
+			write_dns(dns_fd, q, "BADLEN", 6, 'T');
+			return;
+		}
+
+		userid = b32_8to5(in[1]);
+
+		if (check_user_and_ip(userid, q) != 0) {
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
+			return; /* illegal id */
+		}
+
+		switch (in[2]) {
+		case 'T':
+		case 't':
+			users[userid].downenc = 'T';
+			write_dns(dns_fd, q, "Base32", 6, users[userid].downenc);
+			break;
+		case 'S':
+		case 's':
+			users[userid].downenc = 'S';
+			write_dns(dns_fd, q, "Base64", 6, users[userid].downenc);
+			break;
+		case 'R':
+		case 'r':
+			users[userid].downenc = 'R';
+			write_dns(dns_fd, q, "Raw", 3, users[userid].downenc);
 			break;
 		default:
-			write_dns(dns_fd, q, "BADCODEC", 8);
+			write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc);
 			break;
 		}
 		return;
@@ -487,20 +545,26 @@
 		/* Downstream fragsize probe packet */
 		userid = (b32_8to5(in[1]) >> 1) & 15;
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			return; /* illegal id */
 		}
 				
 		req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31);
 		if (req_frag_size < 2 || req_frag_size > 2047) {	
-			write_dns(dns_fd, q, "BADFRAG", 7);
+			write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc);
 		} else {
 			char buf[2048];
+			int i;
+			unsigned int v = (unsigned int) rand();
 
 			memset(buf, 0, sizeof(buf));
 			buf[0] = (req_frag_size >> 8) & 0xff;
 			buf[1] = req_frag_size & 0xff;
-			write_dns(dns_fd, q, buf, req_frag_size);
+			/* make checkable pseudo-random sequence */
+			buf[2] = 107;
+			for (i = 3; i < 2048; i++, v += 107)
+				buf[i] = (char) (v & 0xff);
+			write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc);
 		}
 		return;
 	} else if(in[0] == 'N' || in[0] == 'n') {
@@ -510,16 +574,16 @@
 		/* Downstream fragsize packet */
 		userid = unpacked[0];
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			return; /* illegal id */
 		}
 				
 		max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff);
 		if (max_frag_size < 2) {	
-			write_dns(dns_fd, q, "BADFRAG", 7);
+			write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc);
 		} else {
 			users[userid].fragsize = max_frag_size;
-			write_dns(dns_fd, q, &unpacked[1], 2);
+			write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc);
 		}
 		return;
 	} else if(in[0] == 'P' || in[0] == 'p') {
@@ -530,7 +594,7 @@
 		/* Ping packet, store userid */
 		userid = unpacked[0];
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 			return; /* illegal id */
 		}
 				
@@ -546,7 +610,7 @@
 		dn_seq = unpacked[1] >> 4;
 		dn_frag = unpacked[1] & 15;
 		memcpy(&(users[userid].q), q, sizeof(struct query));
-		users[userid].last_pkt = time(NULL);
+		users[userid].last_pkt = now;
 
 		/* Update seqno and maybe send immediate response packet */
 		update_downstream_seqno(dns_fd, userid, dn_seq, dn_frag);
@@ -565,7 +629,7 @@
 		userid = code;
 		/* Check user and sending ip number */
 		if (check_user_and_ip(userid, q) != 0) {
-			write_dns(dns_fd, q, "BADIP", 5);
+			write_dns(dns_fd, q, "BADIP", 5, 'T');
 		} else {
 			/* Decode data header */
 			int up_seq = (b32_8to5(in[1]) >> 2) & 7;
@@ -580,7 +644,7 @@
 			}
 
 			/* Update query and time info for user */
-			users[userid].last_pkt = time(NULL);
+			users[userid].last_pkt = now;
 			memcpy(&(users[userid].q), q, sizeof(struct query));
 
 			if (up_seq == users[userid].inpacket.seqno && 
@@ -638,6 +702,10 @@
 	}
 
 	len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain);
+	if (len < 1) {
+		warnx("dns_encode_ns_response doesn't fit");
+		return;
+	}
 	
 	if (debug >= 2) {
 		struct sockaddr_in *tempin;
@@ -660,6 +728,10 @@
 	in_addr_t newaddr;
 
 	len = dns_encode(buf, sizeof(buf), q, QR_QUERY, q->name, strlen(q->name));
+	if (len < 1) {
+		warnx("dns_encode doesn't fit");
+		return;
+	}
 
 	/* Store sockaddr for q->id */
 	memcpy(&(fwq.addr), &(q->from), q->fromlen);
@@ -756,8 +828,14 @@
 	
 	if (inside_topdomain) {
 		/* This is a query we can handle */
+
 		switch (q.type) {
 		case T_NULL:
+		case T_CNAME:
+		case T_A:
+		case T_MX:
+		case T_TXT:
+			/* encoding is "transparent" here */
 			handle_null_request(tun_fd, dns_fd, &q, domain_len);
 			break;
 		case T_NS:
@@ -782,6 +860,8 @@
 	fd_set fds;
 	int i;
 
+	now = time(NULL);
+
 	while (running) {
 		int maxfd;
  		if (users_waiting_on_reply()) {
@@ -810,6 +890,8 @@
 		}
 
 		i = select(maxfd + 1, &fds, NULL, NULL, &tv);
+
+		now = time(NULL);
 		
 		if(i < 0) {
 			if (running) 
@@ -1045,12 +1127,91 @@
 }
 
 static void
-write_dns(int fd, struct query *q, char *data, int datalen)
+write_dns(int fd, struct query *q, char *data, int datalen, char downenc)
 {
 	char buf[64*1024];
-	int len;
+	int len = 0;
+
+	if (q->type == T_CNAME || q->type == T_A || q->type == T_MX) {
+		static int td1 = 0;
+		static int td2 = 0;
+		char cnamebuf[1024];		/* max 255 */
+		size_t space;
+		char *b;
+
+		/* Make a rotating topdomain to prevent filtering */
+		td1+=3;
+		td2+=7;
+		if (td1>=26) td1-=26;
+		if (td2>=25) td2-=25;
+
+		/* encode data,datalen to CNAME/MX answer */
+		/* (adapted from build_hostname() in iodine.c) */
+
+		space = MIN(0xFF, sizeof(cnamebuf)) - 4 - 2;
+		/* -1 encoding type, -3 ".xy", -2 for safety */
+
+		memset(cnamebuf, 0, sizeof(cnamebuf));
+
+		if (downenc == 'S') {
+			cnamebuf[0] = 'I';
+			if (!b64->places_dots())
+				space -= (space / 57);	/* space for dots */
+			b64->encode(cnamebuf+1, &space, data, datalen);
+			if (!b64->places_dots())
+				inline_dotify(cnamebuf, sizeof(cnamebuf), 57);
+		} else {
+			cnamebuf[0] = 'H';
+			if (!b32->places_dots())
+				space -= (space / 57);	/* space for dots */
+			b32->encode(cnamebuf+1, &space, data, datalen);
+			if (!b32->places_dots())
+				inline_dotify(cnamebuf, sizeof(cnamebuf), 57);
+		}
+
+		/* Add dot (if it wasn't there already) and topdomain */
+		b = cnamebuf;
+		b += strlen(cnamebuf);
+		if (*b != '.') 
+			*b++ = '.';
+
+		*b = 'a' + td1;
+		b++;
+		*b = 'a' + td2;
+		b++;
+		*b = '\0';
+
+		len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, sizeof(cnamebuf));
+	}
+	else if (q->type == T_TXT) {
+		/* TXT with base32 */
+		char txtbuf[64*1024];
+		size_t space = sizeof(txtbuf) - 1;;
+
+		memset(txtbuf, 0, sizeof(txtbuf));
+
+		if (downenc == 'S') {
+			txtbuf[0] = 'S';	/* plain base64(Sixty-four) */
+			len = b64->encode(txtbuf+1, &space, data, datalen);
+		}
+		else if (downenc == 'R') {
+			txtbuf[0] = 'R';	/* Raw binary data */
+			len = MIN(datalen, sizeof(txtbuf) - 1);
+			memcpy(txtbuf + 1, data, len);
+		} else {
+			txtbuf[0] = 'T';	/* plain base32(Thirty-two) */
+			len = b32->encode(txtbuf+1, &space, data, datalen);
+		}
+		len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1);
+	} else {
+		/* Normal NULL-record encode */
+		len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen);
+	}
 
-	len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen);
+	if (len < 1) {
+		warnx("dns_encode doesn't fit");
+		return;
+	}
 	
 	if (debug >= 2) {
 		struct sockaddr_in *tempin;
@@ -1167,6 +1328,7 @@
 	pidfile = NULL;
 
 	b32 = get_base32_encoder();
+	b64 = get_base64_encoder();
 	
 	retval = 0;
 
--- src/user.c.orig	2009-08-15 21:36:07.000000000 +0200
+++ src/user.c	2009-09-16 22:01:22.000000000 +0200
@@ -39,6 +39,7 @@
 #include "user.h"
 
 struct user users[USERS];
+time_t now = 0;					/* set in iodined.c after select() */
 
 int
 init_users(in_addr_t my_ip, int netbits)
@@ -83,6 +84,7 @@
 			users[i].disabled = 0;
 			created_users++;
 		}
+		users[i].active = 0;
 		users[i].inpacket.len = 0;
 		users[i].inpacket.offset = 0;
 		users[i].outpacket.len = 0;
@@ -104,8 +106,8 @@
 
 	ret = 0;
 	for (i = 0; i < USERS; i++) {
-		if (users[i].active && !users[i].disabled && 
-			users[i].last_pkt + 60 > time(NULL) &&
+		if (users[i].active && !users[i].disabled &&
+			users[i].last_pkt + 60 > now &&
 			users[i].q.id != 0 && users[i].conn == CONN_DNS_NULL) {
 			ret++;
 		}
@@ -123,7 +125,7 @@
 	ret = -1;
 	for (i = 0; i < USERS; i++) {
 		if (users[i].active && !users[i].disabled &&
-			users[i].last_pkt + 60 > time(NULL) &&
+			users[i].last_pkt + 60 > now &&
 			ip == users[i].tun_ip) {
 			ret = i;
 			break;
@@ -135,12 +137,10 @@
 int
 all_users_waiting_to_send()
 {
-	time_t now;
 	int ret;
 	int i;
 
 	ret = 1;
-	now = time(NULL);
 	for (i = 0; i < USERS; i++) {
 		if (users[i].active && !users[i].disabled &&
 			users[i].last_pkt + 60 > now &&
@@ -155,15 +155,31 @@
 }
 
 int
-find_available_user()
+find_available_user(int forced_userid)
 {
 	int ret = -1;
 	int i;
+
+	if (forced_userid != -1) {
+		if (forced_userid < 0 || forced_userid >= USERS)
+			return -1;
+
+		if ((users[forced_userid].active &&
+		    users[forced_userid].last_pkt + 60 > now) ||
+		    users[forced_userid].disabled)
+			return -1;
+
+		users[forced_userid].active = 1;
+		users[forced_userid].last_pkt = now;
+
+		return forced_userid;
+	}
+
 	for (i = 0; i < USERS; i++) {
 		/* Not used at all or not used in one minute */
-		if ((!users[i].active || users[i].last_pkt + 60 < time(NULL)) && !users[i].disabled) {
+		if ((!users[i].active || users[i].last_pkt + 60 < now) && !users[i].disabled) {
 			users[i].active = 1;
-			users[i].last_pkt = time(NULL);
+			users[i].last_pkt = now;
 			users[i].fragsize = 4096;
 			users[i].conn = CONN_DNS_NULL;
 			ret = i;
--- README.orig	2009-08-15 21:36:07.000000000 +0200
+++ README	2009-09-16 22:46:22.000000000 +0200
@@ -90,6 +90,20 @@
 So your domain name and subdomain should be as short as possible to allow
 maximum upstream throughput.
 
+The default is to use DNS NULL-type queries, as this provides the largest
+downstream bandwidth. If your DNS server blocks NULL requests, try TXT or
+CNAME queries via the -T option. Also supported are A (returning CNAME) and
+MX requests, but these may/will cause additional lookups by "smart" caching
+nameservers to get an actual IP address, which may either slow down or fail
+completely. DNS responses for non-NULL are Base32 encoded by default, which
+should always work. For more bandwidth, try Base64 or Raw (TXT only) via the
+-O option. If Base64/Raw doesn't work, you'll see many failures in the
+fragment size autoprobe.
+
+If you have problems, try inspecting the traffic with network monitoring tools
+and make sure that the relaying DNS server has not cached the response. A
+cached error message could mean that you started the client before the server.
+
 
 TIPS & TRICKS:
 
@@ -99,6 +113,13 @@
 iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to :5353
 (Sent in by Tom Schouten)
 
+Iodined will reject data from clients that have not been active (data/pings)
+for more than 60 seconds. In case of a long network outage or similar, just
+stop iodine and restart (re-login), possibly multiple times until you get
+your old IP address back. Once that's done, just wait a while, and you'll
+eventually see the tunneled TCP traffic continue to flow from where it left
+off before the outage. Use iodine -U to force getting a specific IP address.
+
 
 PORTABILITY:
 
--- /dev/null	2009-09-16 22:44:47.000000000 +0200
+++ doc/proto_00000502.txt	2009-09-16 22:50:37.000000000 +0200
@@ -0,0 +1,193 @@
+Detailed specification of protocol in version 00000501
+======================================================
+
+Note: work in progress!!
+
+======================================================
+1. DNS protocol
+======================================================
+
+CMC = 2 byte Cache Miss Counter, increased every time it is used
+
+Version:
+Client sends:
+	First byte v or V
+	Rest encoded with base32:
+	4 bytes big endian protocol version
+	Optional: 1 byte forced userid + 1: 0 = dynamically assigned userid;
+	          1 = force userid 0; etc (ignored by earlier server versions)
+	CMC
+Server replies:
+	4 chars:
+		VACK (version ok), followed by login challenge
+		VNAK (version differs), followed by server protocol version
+		VFUL (server has no free slots or forced userid not available),
+		     followed by max users
+	4 byte value: means login challenge/server protocol version/max users
+	1 byte userid of the new user, or any byte if not VACK
+	
+Login:
+Client sends:
+	First byte l or L
+	Rest encoded with base32:
+	1 byte userid
+	16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge)
+	CMC
+Server replies:
+	LNAK means not accepted
+	x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits)
+
+IP Request:
+Client sends:
+	First byte i or I
+	5 bits coded as Base32 char, meaning userid
+	CMC
+Server replies
+	BADIP if bad userid, or
+	I and then 4 bytes network order external IP address of iodined server
+
+Case check:
+Client sends: 
+	First byte z or Z
+	Lots of data that should not be decoded
+Server replies:
+	The requested domain copied raw
+
+Switch codec:
+Client sends:
+	First byte s or S
+	5 bits coded as Base32 char, meaning userid
+	5 bits coded as Base32 char, with value 5 or 6, representing number of raw
+	bits per encoded byte
+	CMC
+Server sends:
+	Name of codec if accepted. After this all upstream data packets must 
+	be encoded with the new codec.
+	BADCODEC if not accepted. Client must then revert to Base32
+	BADLEN if length of query is too short
+
+Options:
+Client sends:
+	First byte o or O
+	5 bits coded as Base32 char, meaning userid
+	1 char, meaning option
+Server sends:
+	Full name of option if accepted. After this, option immediately takes
+	effect in server.
+	BADCODEC if not accepted. Previous situation remains.
+	All options affect only the requesting client.
+
+	Option chars:
+	t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default)
+	s or S: Downstream encoding Base64, for TXT/CNAME/A/MX
+	r or R: Downstream encoding Raw, for TXT/NULL (default for NULL)
+	If codec unsupported for request type, server will use Base32; note
+	that server will answer any mix of request types that a client sends.
+	Server may disregard this option; client must always use the downstream
+	encoding type indicated in every downstream DNS packet.
+
+Probe downstream fragment size:
+Client sends:
+	First byte r or R
+	15 bits coded as 3 Base32 chars: UUUUF FFFFF FFFFF
+		meaning 4 bits userid, 11 bits fragment size
+	Then follows a long random query which contents does not matter
+Server sends:
+	Requested number of bytes as a response. The first two bytes contains
+	the requested length. Rest of message can be any data.
+	BADFRAG if requested length not accepted.
+
+Set downstream fragment size:
+Client sends:
+	First byte n or N
+	Rest encoded with base32:
+	1 byte userid
+	2 bytes new downstream fragment size
+	CMC
+Server sends:
+	2 bytes new downstream fragment size. After this all downstream
+	payloads will be max (fragsize + 2) bytes long.
+	BADFRAG if not accepted.
+
+Data:
+Upstream data header:
+	 3210 432 10 43 210 4321 0
+	+----+---+--+--+---+----+-+
+	|UUUU|SSS|FF|FF|DDD|GGGG|L|
+	+----+---+--+--+---+----+-+
+
+Downstream data header:
+	 7 654 3210 765 4321 0
+	+-+---+----+---+----+-+
+	|C|SSS|FFFF|DDD|GGGG|L|
+	+-+---+----+---+----+-+
+
+UUUU = Userid
+L = Last fragment in packet flag
+SS = Upstream packet sequence number
+FFFF = Upstream fragment number
+DDD = Downstream packet sequence number
+GGGG = Downstream fragment number
+C = Compression enabled for downstream packet
+
+Upstream data packet starts with 1 byte ASCII hex coded user byte, then 3 bytes 
+Base32 encoded header, then comes the payload data, encoded with chosen codec.
+
+Downstream data starts with 2 byte header. Then payload data, which may be
+compressed.
+
+In NULL responses, downstream data is always raw. In all other response types,
+downstream data is encoded (see Options above).
+Encoding type is indicated by 1 prefix char:
+TXT:
+	End result is always DNS-chopped (series of len-prefixed strings
+	<=255 bytes)
+	t or T: Base32   encoded before chop, decoded after un-chop
+	s or S: Base64   encoded before chop, decoded after un-chop
+	r or R: Raw      no encoding, only DNS-chop
+CNAME/A/MX:
+	h or H: Hostname encoded with Base32
+	i or I: Hostname encoded with Base64
+
+Ping:
+Client sends:
+	First byte p or P
+	Rest encoded with Base32:
+	1 byte with 4 bits userid
+	1 byte with:
+		3 bits downstream seqno
+		4 bits downstream fragment
+	CMC
+
+The server response to Ping and Data packets is a DNS NULL type response:
+If server has nothing to send, data length is 0 bytes.
+If server has something to send, it will send a downstream data packet, 
+prefixed with 2 bytes header as shown above.
+
+
+======================================================
+2. Raw UDP protocol
+======================================================
+
+All Raw UDP protcol messages start with a 3 byte header: 0x10d19e
+This is not the start of a valid DNS message so it is easy to identify.
+The fourth byte contains the command and the user id.
+
+	 7654 3210
+	+----+----+
+	|CCCC|UUUU|
+	+----+----+
+
+Login message (command = 1):
+The header is followed by a MD5 hash with the same password as in the DNS
+login. The client starts the raw mode by sending this message, and uses
+the login challenge +1, and the server responds using the login challenge -1.
+After the login message has been exchanged, both the server and the client
+switch to raw udp mode for the rest of the connection.
+
+Data message (command = 2):
+After the header comes the payload data, which may be compressed.
+
+Ping message (command = 3):
+Sent from client to server and back to keep session open. Has no payload.
+
--- man/iodine.8.orig	2009-09-16 22:07:09.000000000 +0200
+++ man/iodine.8	2009-09-16 22:57:48.000000000 +0200
@@ -13,6 +13,12 @@
 .I password
 .B ] [-m
 .I fragsize
+.B ] [-U
+.I userid
+.B ] [-T
+.I dnstype
+.B ] [-O
+.I downenc
 .B ] [-t
 .I chrootdir
 .B ] [-d
@@ -46,7 +52,7 @@
 .B ] [-p
 .I port
 .B ] [-n
-.I external ip
+.I external_ip
 .B ] [-b
 .I dnsport
 .B ] [-P
@@ -111,40 +117,85 @@
 will be sent to the server instead of the DNS relay.
 .TP
 .B -m fragsize
-Maximum downstream fragsize. Not setting this will cause the client to probe
-the maximum accepted downstream packet size.
+Force maximum downstream fragsize. Not setting this will cause the
+client to automatically probe the maximum accepted downstream fragment size.
+.TP
+.B -U userid
+Force userid on server, to get predictable IP address. Userid is 0 \- 15
+on current servers, or less if server is started with small subnet.
+Normally, userids are dynamically assigned from 0 upwards; suggestion for
+static userids is to use from 15 downwards.
+.TP
+.B -T dnstype
+DNS request type.
+.I NULL
+is default. If this doesn't work, try
+.I TXT
+(some less bandwidth) or
+.I CNAME
+(much less bandwidth). Also supported are
+.I A
+(returning CNAME) and
+.I MX
+requests, but these may/will cause additional lookups by "smart" caching
+nameservers to get an actual IP address, which may either slow down or fail
+completely.
+.TP
+.B -O downenc
+Downstream encoding for all query type responses except NULL.
+.I Base32
+is default and should always work.
+.I Base64
+provides more bandwidth, but may not work on all nameservers.
+For TXT queries,
+.I Raw
+will provide maximum performance. This will only work if the nameserver
++path is fully 8-bit-clean for responses that are assumed to be
+"legible text".
 .SS Server Options:
 .TP
 .B -c
-Disable checks on client IP on all incoming requests.
+Disable checking the client IP on all incoming requests.
+By default, requests originating from non-matching IP adresses will be
+rejected, however this will cause problems when requests are routed
+via a cluster of DNS servers.
 .TP
 .B -s
-Don't try to configure IP address or MTU. This should only be used if
+Don't try to configure IP address and MTU for the tun device.
+This should only be used if
 you have already configured the device that will be used.
 .TP
 .B -D
 Increase debug level. Level 1 prints info about each RX/TX packet.
+Implies the
+.B -f
+option.
 .TP
 .B -m mtu
-Set 'mtu' as mtu size for the tunnel device. This will be sent to the client
-on connect, and the client will use the same mtu.
+Set 'mtu' as mtu size for the tun device. This will be sent to the client
+on login, and the client will use the same mtu for its tun device.
+Default 1200.
+Note that the DNS traffic will be automatically fragmented when needed.
 .TP
 .B -l listen_ip
-Make the server listen only on 'listen_ip' instead of on 0.0.0.0 for incoming
-connections.
+Make the server listen only on 'listen_ip' for incoming requests.
+By default, incoming requests are accepted from all interfaces.
 .TP
 .B -p port
 Make the server listen on 'port' instead of 53 for traffic. 
 .B Note:
 You must make sure the dns requests are forwarded to this port yourself.
 .TP
-.B -n external ip
+.B -n external_ip
 The IP address to return in NS responses. Default is to return the address used
 as destination in the query.
 .TP
 .B -b dnsport
 If this port is specified, all incoming requests not inside the tunnel domain
 will be forwarded to this port on localhost, to be handled by a real dns.
+.B Note:
+The forwarding is not fully transparent, and not advised for use
+in production environments.
 .SS Client Arguments:
 .TP
 .B nameserver
@@ -156,7 +207,7 @@
 file.
 .TP
 .B topdomain
-The dns traffic will be sent as querys of type NULL for subdomains under
+The dns traffic will be sent as queries for subdomains under
 \'topdomain'. This is normally a subdomain to a domain you own. Use a short
 domain name to get better throughput. If 
 .B nameserver
@@ -165,17 +216,19 @@
 .SS Server Arguments:
 .TP
 .B tunnel_ip[/netmask]
-This is the servers ip address on the tunnel interface. The client will be
+This is the server's ip address on the tun interface. The client will be
 given the next ip number in the range. It is recommended to use the 
 10.0.0.0 or 172.16.0.0 ranges. The default netmask is /27, can be overriden
 by specifying it here. Using a smaller network will limit the number of
 concurrent users.
 .TP
 .B topdomain
-The dns traffic will is expected to be sent as querys of type NULL for 
+The dns traffic is expected to arrive as querys for
 subdomains under 'topdomain'. This is normally a subdomain to a domain you 
 own. Use a short domain name to get better throughput. This argument must be 
-the same on both the client and the server.
+the same on both the client and the server. Queries for domains other
+than 'topdomain' will be forwarded if the \-b option is given, otherwise
+they will be dropped.
 .SH EXAMPLES
 .SS Quickstart:
 .TP
@@ -254,6 +307,18 @@
 .B MTU issues:
 These issues should be solved now, with automatic fragmentation of downstream 
 packets. There should be no need to set the MTU explicitly on the server.
+.SH SECURITY
+Login is a relatively secure challenge-response MD5 hash, with the
+password never passing the wire.
+However, all other data is
+.B NOT
+encrypted in any way. The DNS traffic is also vulnerable to replay,
+injection and man-in-the-middle attacks, especially when iodined is used
+with the \-c option. Use of ssh or vpn tunneling is strongly recommended.
+On both server and client, use
+.I iptables
+to block all traffic coming in from the tun interfaces,
+except to the used ssh or vpn ports.
 .SH ENVIRONMENT
 .SS IODINE_PASS
 If the environment variable
@@ -272,5 +337,8 @@
 .El
 .SH BUGS
 File bugs at http://dev.kryo.se/iodine/
+.SH SEE ALSO
+The README file in the source distribution contains some more elaborate
+information.
 .SH AUTHORS
 Erik Ekman <yarrick@kryo.se> and Bjorn Andersson <flex@kryo.se>

