diff --git a/CHANGELOG b/CHANGELOG index 2335411..183ed68 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,8 @@ CHANGES: - iodine client now shuts down if it detects a server restart. - iodined now replies to NS request on its own domain, fixes issue #33. The destination IP address is sent as reply. + - Upstream data is now Base64 encoded if relay server preserves case and + supports the plus (+) character in domain names, fixes #16. 2008-08-06: 0.4.2 "Opened Zone" - Applied a few small patches from Maxim Bourmistrov and Gregor Herrmann diff --git a/src/iodine.c b/src/iodine.c index 7755f6a..c25b0ed 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -40,6 +40,7 @@ #include "common.h" #include "encoding.h" #include "base32.h" +#include "base64.h" #include "dns.h" #include "login.h" #include "tun.h" @@ -146,13 +147,13 @@ build_hostname(char *buf, size_t buflen, return space; } -int +static int is_sending() { return (packet.len != 0); } -int +static int read_dns(int fd, char *buf, int buflen) { struct sockaddr_in from; @@ -324,7 +325,7 @@ send_chunk(int fd) send_query(fd, buf); } -void +static void send_login(int fd, char *login, int len) { char data[19]; @@ -361,7 +362,7 @@ send_ping(int fd) send_packet(fd, 'P', data, sizeof(data)); } -void +static void send_version(int fd, uint32_t version) { char data[6]; @@ -379,15 +380,33 @@ send_version(int fd, uint32_t version) send_packet(fd, 'V', data, sizeof(data)); } -void +static void send_case_check(int fd) { - char buf[512] = "zZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyY123-4560789."; + /* The '+' plus character is not allowed according to RFC. + * Expect to get SERVFAIL or similar if it is rejected. + */ + char buf[512] = "zZ+-aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyY1234."; strncat(buf, topdomain, 512 - strlen(buf)); send_query(fd, buf); } +static void +send_codec_switch(int fd, int userid, int bits) +{ + char buf[512] = "S00."; + if (userid >= 0 && userid < 9) { + buf[1] += userid; + } + if (bits >= 0 && bits < 9) { + buf[2] += bits; + } + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + static int handshake(int dns_fd) { @@ -519,30 +538,30 @@ perform_case_check: if(r > 0) { read = read_dns(dns_fd, in, sizeof(in)); - if(read <= 0) { - warn("read"); - continue; - } - if (read > 0) { if (in[0] == 'z' || in[0] == 'Z') { - if (read < (26 * 2)) { - printf("Received short case reply...\n"); + if (read < (27 * 2)) { + printf("Received short case check reply. Will use base32 encoder\n"); + goto switch_codec; } else { int k; + /* TODO enhance this, base128 is probably also possible */ case_preserved = 1; - for (k = 0; k < 26 && case_preserved; k += 2) { + for (k = 0; k < 27 && case_preserved; k += 2) { if (in[k] == in[k+1]) { - /* test string: zZaAbBcCdD... */ + /* test string: zZ+-aAbBcCdDeE... */ case_preserved = 0; } } - return 0; + goto switch_codec; } } else { printf("Received bad case check reply\n"); } + } else { + printf("Got error on case check, will use base32\n"); + goto switch_codec; } } @@ -550,6 +569,52 @@ perform_case_check: } printf("No reply on case check, continuing\n"); +switch_codec: + if (!case_preserved) + return 0; + + dataenc = get_base64_encoder(); + printf("Switching 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; + tv.tv_sec = i + 1; + tv.tv_usec = 0; + + bits = 6; /* base64 = 6 bits per byte */ + + send_codec_switch(dns_fd, userid, bits); + + 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, in, sizeof(in)); + + if (read > 0) { + if (strncmp("BADLEN", in, 6) == 0) { + printf("Server got bad message length. "); + goto codec_revert; + } else if (strncmp("BADIP", in, 5) == 0) { + printf("Server rejected sender IP address. "); + goto codec_revert; + } else if (strncmp("BADCODEC", in, 8) == 0) { + printf("Server rejected the selected codec. "); + goto codec_revert; + } + in[read] = 0; /* zero terminate */ + printf("Server switched to codec %s\n", in); + return 0; + } + } + printf("Retrying codec switch...\n"); + } + printf("No reply from server on codec switch. "); +codec_revert: + printf("Falling back to base32\n"); + dataenc = get_base32_encoder(); return 0; } diff --git a/src/iodined.c b/src/iodined.c index 93cfde8..cbfdcd6 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -44,6 +44,7 @@ #include "dns.h" #include "encoding.h" #include "base32.h" +#include "base64.h" #include "user.h" #include "login.h" #include "tun.h" @@ -261,11 +262,43 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) memcpy(&(users[userid].q), q, sizeof(struct query)); users[userid].last_pkt = time(NULL); } else if(in[0] == 'Z' || in[0] == 'z') { - /* Case conservation check */ + /* 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); return; + } else if(in[0] == 'S' || in[0] == 's') { + int codec; + struct encoder *enc; + if (domain_len != 4) { /* len = 4, example: "S15." */ + write_dns(dns_fd, q, "BADLEN", 6); + return; + } + + userid = in[1] & 0x7; + + if (ip_cmp(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5); + return; /* illegal id */ + } + + codec = in[2] & 0xF; + switch (codec) { + 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)); + 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)); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8); + break; + } + return; } else if((in[0] >= '0' && in[0] <= '9') || (in[0] >= 'a' && in[0] <= 'f') || (in[0] >= 'A' && in[0] <= 'F')) { diff --git a/src/user.c b/src/user.c index 8b242d8..6b53e56 100644 --- a/src/user.c +++ b/src/user.c @@ -106,3 +106,11 @@ find_available_user() return ret; } +void +user_switch_codec(int userid, struct encoder *enc) +{ + if (userid < 0 || userid >= USERS) + return; + + users[userid].encoder = enc; +} diff --git a/src/user.h b/src/user.h index 20ea3c8..fdbcef5 100644 --- a/src/user.h +++ b/src/user.h @@ -38,5 +38,6 @@ void init_users(in_addr_t); int find_user_by_ip(uint32_t); int all_users_waiting_to_send(); int find_available_user(); +void user_switch_codec(int userid, struct encoder *enc); #endif