diff --git a/src/iodined.c b/src/iodined.c index 0ff8e25..1d420d8 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -68,6 +68,7 @@ static int running = 1; static char *topdomain; static char password[33]; static struct encoder *b32; +static struct encoder *b64; static int created_users; static int check_ip; @@ -85,7 +86,7 @@ static char *__progname; #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 @@ -119,7 +120,7 @@ check_user_and_ip(int userid, struct query *q) if (userid < 0 || userid >= created_users ) { return 1; } - if (!users[userid].active) { + if (!users[userid].active || users[userid].disabled) { return 1; } if (users[userid].last_pkt + 60 < time(NULL)) { @@ -225,7 +226,7 @@ send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, s 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 @@ -270,7 +271,7 @@ send_chunk(int dns_fd, int userid) { 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 && @@ -360,10 +361,21 @@ 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].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); @@ -382,7 +394,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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; @@ -401,14 +413,14 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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)); } @@ -422,7 +434,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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 */ } @@ -440,25 +452,26 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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 */ } @@ -468,15 +481,49 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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); + 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, users[userid].downenc); break; } return; @@ -486,20 +533,26 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) /* 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') { @@ -509,16 +562,16 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) /* 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') { @@ -529,7 +582,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) /* 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 */ } @@ -564,7 +617,7 @@ handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) 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; @@ -637,6 +690,10 @@ handle_ns_request(int dns_fd, struct query *q) } 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; @@ -659,6 +716,10 @@ forward_query(int bind_fd, struct query *q) 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); @@ -755,8 +816,14 @@ tunnel_dns(int tun_fd, int dns_fd, int bind_fd) 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: @@ -1044,12 +1111,91 @@ read_dns(int fd, int tun_fd, struct query *q) /* FIXME: tun_fd is because of raw } 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; - len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); + 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); + } + + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } if (debug >= 2) { struct sockaddr_in *tempin; @@ -1166,6 +1312,7 @@ main(int argc, char **argv) pidfile = NULL; b32 = get_base32_encoder(); + b64 = get_base64_encoder(); retval = 0;