1
0
Fork 0
mirror of https://github.com/yarrick/iodine.git synced 2025-04-10 04:21:01 +00:00
iodine/src/server.c
2016-12-09 09:57:02 +08:00

2019 lines
57 KiB
C

/*
* Copyright (c) 2006-2015 Erik Ekman <yarrick@kryo.se>,
* 2006-2009 Bjorn Andersson <flex@kryo.se>,
* 2015 Frekk van Blagh <frekk@frekkworks.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/time.h>
#include <fcntl.h>
#include <time.h>
#include <zlib.h>
#include <ctype.h>
#include <errno.h>
#include "common.h"
#include "version.h"
#include "dns.h"
#include "encoding.h"
#include "base32.h"
#include "base64.h"
#include "base64u.h"
#include "base128.h"
#include "user.h"
#include "login.h"
#include "tun.h"
#include "fw_query.h"
#include "util.h"
#include "server.h"
#include "window.h"
#ifdef HAVE_SYSTEMD
# include <systemd/sd-daemon.h>
#endif
#ifdef WINDOWS32
WORD req_version = MAKEWORD(2, 2);
WSADATA wsa_data;
#else
#include <err.h>
#endif
static void
send_raw(int fd, uint8_t *buf, size_t buflen, int user, int cmd, struct sockaddr_storage *from, socklen_t fromlen)
{
char packet[buflen + RAW_HDR_LEN];
int len = buflen;
memcpy(packet, raw_header, RAW_HDR_LEN);
if (len) {
memcpy(&packet[RAW_HDR_LEN], buf, len);
}
len += RAW_HDR_LEN;
packet[RAW_HDR_CMD] = cmd | (user & 0x0F);
DEBUG(3, "TX-raw: client %s (user %d), cmd %d, %d bytes",
format_addr(from, fromlen), user, cmd, len);
sendto(fd, packet, len, 0, (struct sockaddr *) from, fromlen);
}
/* Ringbuffer Query Handling (qmem) and DNS Cache:
This is used to make the handling duplicates and query timeouts simpler
and all handled in one place.
Using this, lazy mode is possible with n queries (n <= windowsize)
New queries are placed consecutively in the buffer, replacing any old
queries (already responded to) if length == QMEM_LEN. Old queries are kept
as a record for duplicate requests. If a dupe is found and USE_DNSCACHE is
defined, the previous answer is sent (if it exists), otherwise an invalid
response is sent.
On the DNS cache:
This cache is implemented to better handle the aggressively impatient DNS
servers that very quickly re-send requests when we choose to not
immediately answer them in lazy mode. This cache works much better than
pruning(=dropping) the improper requests, since the DNS server will
actually get an answer instead of silence.
Because of the CMC in both ping and upstream data, unwanted cache hits
are prevented. Due to the combination of CMC and varying sequence IDs, it
is extremely unlikely that any duplicate answers will be incorrectly sent
during a session (given QMEM_LEN is not very large). */
#define QMEM_DEBUG(l, u, ...) \
if (server.debug >= l) {\
TIMEPRINT("[QMEM u%d (%" L "u/%u)] ", u, users[u].qmem.num_pending, users[u].outgoing->windowsize); \
fprintf(stderr, __VA_ARGS__);\
fprintf(stderr, "\n");\
}
static void
qmem_init(int userid)
/* initialize user QMEM and DNS cache (if enabled) */
{
memset(&users[userid].qmem, 0, sizeof(struct qmem_buffer));
for (size_t i = 0; i < QMEM_LEN; i++) {
users[userid].qmem.queries[i].q.id = -1;
}
}
static int
qmem_is_cached(int dns_fd, int userid, struct query *q)
/* Check if an answer for a particular query is cached in qmem
* If so, sends an "invalid" answer or one from DNS cache
* Returns 0 if new query (ie. not cached), 1 if cached (and then answered) */
{
struct qmem_buffer *buf;
struct query *pq;
char *data = "x";
char dataenc = 'T';
size_t len = 1;
int dnscache = 0;
buf = &users[userid].qmem;
/* Check if this is a duplicate query */
for (size_t p = buf->start; p != buf->end; p = (p + 1) % QMEM_LEN) {
pq = &buf->queries[p].q;
if (pq->id != q->id)
continue;
if (pq->type != q->type)
continue;
if (strcasecmp(pq->name, q->name))
continue;
/* Aha! A match! */
#ifdef USE_DNSCACHE
/* Check if answer is in DNS cache */
if (buf->queries[p].a.len) {
data = (char *)buf->queries[p].a.data;
len = buf->queries[p].a.len;
dataenc = users[userid].downenc;
dnscache = 1;
}
#endif
QMEM_DEBUG(2, userid, "OUT from qmem for '%s', %s", q->name,
dnscache ? "answer from DNS cache" : "sending invalid response");
write_dns(dns_fd, q, data, len, dataenc);
return 1;
}
return 0;
}
static int
qmem_append(int userid, struct query *q)
/* Appends incoming query to the buffer. */
{
struct qmem_buffer *buf;
buf = &users[userid].qmem;
if (buf->num_pending >= QMEM_LEN) {
/* this means we have QMEM_LEN *pending* queries; respond to oldest
* one to make space for new query */
QMEM_DEBUG(2, userid, "Full of pending queries! Replacing old query %d with new %d.",
buf->queries[buf->start].q.id, q->id);
send_data_or_ping(userid, &buf->queries[buf->start].q, 0, 0, NULL);
}
if (buf->length < QMEM_LEN) {
buf->length++;
} else {
/* will replace oldest query (in buf->queries[buf->start]) */
buf->start = (buf->start + 1) % QMEM_LEN;
}
QMEM_DEBUG(5, userid, "add query ID %d, timeout %" L "u ms", q->id, timeval_to_ms(&users[userid].dns_timeout));
/* Copy query into end of buffer */
memcpy(&buf->queries[buf->end].q, q, sizeof(struct query));
#ifdef USE_DNSCACHE
buf->queries[buf->end].a.len = 0;
#endif
buf->end = (buf->end + 1) % QMEM_LEN;
buf->num_pending += 1;
return 1;
}
static void
qmem_answered(int userid, uint8_t *data, size_t len)
/* Call when oldest/first/earliest query added has been answered */
{
struct qmem_buffer *buf;
size_t answered;
buf = &users[userid].qmem;
if (buf->num_pending == 0) {
/* Most likely caused by bugs somewhere else. */
QMEM_DEBUG(1, userid, "Query answered with 0 in qmem! Fix bugs.");
return;
}
answered = buf->start_pending;
buf->start_pending = (buf->start_pending + 1) % QMEM_LEN;
buf->num_pending -= 1;
#ifdef USE_DNSCACHE
/* Add answer to query entry */
if (len && data) {
if (len > 4096) {
QMEM_DEBUG(1, userid, "got answer with length >4096!");
}
memcpy(&buf->queries[answered].a.data, data, MIN(len, 4096));
buf->queries[answered].a.len = len;
}
#endif
QMEM_DEBUG(3, userid, "query ID %d answered", buf->queries[answered].q.id);
}
struct query *
qmem_get_next_response(int userid)
/* Gets oldest query to be responded to (for lazy mode) or NULL if none available
* The query is NOT marked as "answered" since that is done later. */
{
struct qmem_buffer *buf;
struct query *q;
buf = &users[userid].qmem;
if (buf->length == 0 || buf->num_pending == 0)
return NULL;
q = &buf->queries[buf->start_pending].q;
QMEM_DEBUG(3, userid, "next response using cached query: ID %d", q->id);
return q;
}
static struct timeval
qmem_max_wait(int *touser, struct query **sendq)
/* Gets max interval before the next query has to be responded to
* Response(s) are sent automatically for queries if:
* - the query has timed out
* - the user has data to send or pending ACKs, and spare pending queries
* - the user has excess pending queries (>downstream window size)
* Returns largest safe time to wait before next timeout */
{
struct timeval now, timeout, soonest, tmp, age, nextresend;
soonest.tv_sec = 10;
soonest.tv_usec = 0;
int userid, qnum, nextuser = -1, immediate, resend = 0;
struct query *q = NULL, *nextq = NULL;
size_t sending, total, sent;
time_t age_ms;
struct tun_user *u;
gettimeofday(&now, NULL);
for (userid = 0; userid < created_users; userid++) {
if (!user_active(userid))
continue;
u = &users[userid];
if (u->qmem.num_pending == 0)
continue;
/* Keep track of how many fragments we can send */
if (u->lazy) {
total = window_sending(u->outgoing, &nextresend);
if ((nextresend.tv_sec != 0 || nextresend.tv_usec != 0)
&& u->qmem.num_pending >= 1) {
/* will use nextresend as max wait time if it is smallest
* and if user has spare queries */
resend = 1;
soonest = nextresend;
}
if (u->qmem.num_pending > u->outgoing->windowsize) {
/* calculate number of "excess" queries */
total = MAX(total, u->qmem.num_pending - u->outgoing->windowsize);
}
} else {
/* User in immediate mode, must answer all pending queries */
total = u->qmem.num_pending;
}
sending = total;
sent = 0;
qnum = u->qmem.start_pending;
for (; qnum != u->qmem.end; qnum = (qnum + 1) % QMEM_LEN) {
q = &u->qmem.queries[qnum].q;
/* queries will always be in time order */
timeradd(&q->time_recv, &u->dns_timeout, &timeout);
if (sending > 0 || !timercmp(&now, &timeout, <) || u->next_upstream_ack >= 0) {
/* respond to a query with ping/data if:
* - query has timed out (ping, or data if available)
* - user has pending data (always data)
* - user has pending ACK (either) */
timersub(&now, &q->time_recv, &age);
age_ms = timeval_to_ms(&age);
/* only consider "immediate" when age is negligible */
immediate = llabs(age_ms) <= 10;
QMEM_DEBUG(3, userid, "Auto response to cached query: ID %d, %ld ms old (%s), timeout %ld ms",
q->id, age_ms, immediate ? "immediate" : "lazy", timeval_to_ms(&u->dns_timeout));
sent++;
QMEM_DEBUG(4, userid, "ANSWER q id %d, ACK %d; sent %" L "u of %" L "u + sending another %" L "u",
q->id, u->next_upstream_ack, sent, total, sending);
send_data_or_ping(userid, q, 0, immediate, NULL);
if (sending > 0)
sending--;
continue;
}
timersub(&timeout, &now, &tmp);
if (timercmp(&tmp, &soonest, <)) {
/* the oldest non-timed-out query in the buffer will be the
* soonest to timeout for this user; we can skip the rest */
soonest = tmp;
nextuser = userid;
nextq = q;
break;
}
}
}
if (server.debug >= 5) {
time_t soonest_ms = timeval_to_ms(&soonest);
if (nextq && nextuser >= 0) {
QMEM_DEBUG(5, nextuser, "can wait for %" L "u ms, will send id %d", soonest_ms, nextq->id);
} else {
if (nextuser < 0)
nextuser = 0;
if (soonest_ms != 10000 && resend) {
/* only if resending some frags */
QMEM_DEBUG(5, nextuser, "Resending some fragments")
} else {
QMEM_DEBUG(2, nextuser, "Don't need to send anything to any users, waiting %" L "u ms", soonest_ms);
}
}
}
if (sendq)
*sendq = nextq;
if (touser)
*touser = nextuser;
return soonest;
}
static int
get_dns_fd(struct dnsfd *fds, struct sockaddr_storage *addr)
{
if (addr->ss_family == AF_INET6) {
return fds->v6fd;
}
return fds->v4fd;
}
static void
forward_query(int bind_fd, struct query *q)
{
char buf[64*1024];
int len;
struct fw_query fwq;
struct sockaddr_in *myaddr;
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);
fwq.addrlen = q->fromlen;
fwq.id = q->id;
fw_query_put(&fwq);
newaddr = inet_addr("127.0.0.1");
myaddr = (struct sockaddr_in *) &(q->from);
memcpy(&(myaddr->sin_addr), &newaddr, sizeof(in_addr_t));
myaddr->sin_port = htons(server.bind_port);
DEBUG(2, "TX: NS reply");
if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) {
warn("forward query error");
}
}
static void
send_version_response(int fd, version_ack_t ack, uint32_t payload, int userid, struct query *q)
{
char out[9];
switch (ack) {
case VERSION_ACK:
strncpy(out, "VACK", sizeof(out));
break;
case VERSION_NACK:
strncpy(out, "VNAK", sizeof(out));
break;
case VERSION_FULL:
strncpy(out, "VFUL", sizeof(out));
break;
}
*(uint32_t *) (out + 4) = htonl(payload);
out[8] = userid & 0xff;
write_dns(fd, q, out, sizeof(out), users[userid].downenc);
}
void
send_data_or_ping(int userid, struct query *q, int ping, int immediate, char *tcperror)
/* Sends current fragment to user, or a ping if no data available.
ping: 1=force send ping (even if data available), 0=only send if no data.
immediate: 1=not from qmem (ie. fresh query), 0=query is from qmem
tcperror: whether to tell user that TCP socket is closed (NULL if OK or pointer to error message) */
{
uint8_t pkt[MAX_FRAGSIZE + DOWNSTREAM_PING_HDR];
size_t datalen, headerlen;
fragment *f = NULL;
struct frag_buffer *out, *in;
in = users[userid].incoming;
out = users[userid].outgoing;
window_tick(out);
if (!tcperror) {
f = window_get_next_sending_fragment(out, &users[userid].next_upstream_ack);
} else {
/* construct fake fragment containing error message. */
fragment fr;
f = &fr;
memset(f, 0, sizeof(fragment));
f->ack_other = -1;
f->len = strlen(tcperror);
memcpy(f->data, tcperror, f->len);
f->data[f->len] = 0;
f->start = 1;
f->end = 1;
DEBUG(2, "Sending ping with TCP forward disconnect; error: %s", f->data);
}
/* Build downstream data/ping header (see doc/proto_xxxxxxxx.txt) for details */
if (!f) {
/* No data, send data/ping header (with extra info) */
ping = 1;
datalen = 0;
pkt[0] = 0; /* Pings don't need seq IDs unless they have data */
pkt[1] = users[userid].next_upstream_ack & 0xFF;
pkt[2] = (users[userid].next_upstream_ack < 0 ? 0 : 1) << 3;
users[userid].next_upstream_ack = -1;
} else {
datalen = f->len;
pkt[0] = f->seqID & 0xFF;
pkt[1] = f->ack_other & 0xFF;
pkt[2] = ((f->ack_other < 0 ? 0 : 1) << 3) | ((f->compressed & 1) << 2) | (f->start << 1) | f->end;
headerlen = DOWNSTREAM_HDR;
}
/* If this is being responded to immediately (ie. not from qmem)
* This flag is used by client to calculate stats */
pkt[2] |= (immediate & 1) << 5;
if (tcperror) {
pkt[2] |= (1 << 6);
}
if (ping) {
/* set ping flag and build extra header */
pkt[2] |= 1 << 4;
pkt[3] = out->windowsize & 0xFF;
pkt[4] = in->windowsize & 0xFF;
pkt[5] = out->start_seq_id & 0xFF;
pkt[6] = in->start_seq_id & 0xFF;
headerlen = DOWNSTREAM_PING_HDR;
}
if (datalen + headerlen > sizeof(pkt)) {
/* Should never happen, or at least user should be warned about
* fragsize > MAX_FRAGLEN earlier on */
warnx("send_data_or_ping: fragment too large to send! (%" L "u)", datalen);
return;
}
if (f) {
memcpy(pkt + headerlen, f->data, datalen);
}
write_dns(get_dns_fd(&server.dns_fds, &q->from), q, (char *)pkt,
datalen + headerlen, users[userid].downenc);
/* mark query as answered */
qmem_answered(userid, pkt, datalen + headerlen);
window_tick(out);
}
void
user_process_incoming_data(int userid, int ack)
{
uint8_t pkt[65536];
size_t datalen;
int compressed = 0;
window_ack(users[userid].outgoing, ack);
window_tick(users[userid].outgoing);
datalen = window_reassemble_data(users[userid].incoming, pkt, sizeof(pkt), &compressed);
window_tick(users[userid].incoming);
/* Update time info */
users[userid].last_pkt = time(NULL);
if (datalen > 0) {
/* Data reassembled successfully + cleared out of buffer */
handle_full_packet(userid, pkt, datalen, compressed);
}
}
static int
user_send_data(int userid, uint8_t *indata, size_t len, int compressed)
/* Appends data to a user's outgoing queue and sends it (in raw mode only) */
{
size_t datalen;
int ret = 0;
uint8_t out[65536], *data;
data = indata;
datalen = len;
/* use compressed or uncompressed packet to match user settings */
if (users[userid].down_compression && !compressed) {
datalen = sizeof(out);
compress2(out, &datalen, indata, len, 9);
data = out;
} else if (!users[userid].down_compression && compressed) {
datalen = sizeof(out);
ret = uncompress(out, &datalen, indata, len);
if (ret != Z_OK) {
DEBUG(1, "FAIL: Uncompress == %d: %" L "u bytes to user %d!", ret, len, userid);
return 0;
}
}
compressed = users[userid].down_compression;
if (users[userid].conn == CONN_DNS_NULL && data && datalen) {
/* append new data to user's outgoing queue; sent later in qmem_max_wait */
ret = window_add_outgoing_data(users[userid].outgoing, data, datalen, compressed);
} else if (data && datalen) { /* CONN_RAW_UDP */
if (!compressed)
DEBUG(1, "Sending in RAW mode uncompressed to user %d!", userid);
int dns_fd = get_dns_fd(&server.dns_fds, &users[userid].host);
send_raw(dns_fd, data, datalen, userid, RAW_HDR_CMD_DATA,
&users[userid].host, users[userid].hostlen);
ret = 1;
}
return ret;
}
static int
user_send_tcp_disconnect(int userid, struct query *q, char *errormsg)
/* tell user that TCP socket has been disconnected */
{
users[userid].remote_forward_connected = -1;
close_socket(users[userid].remote_tcp_fd);
if (q == NULL)
q = qmem_get_next_response(userid);
if (q != NULL) {
send_data_or_ping(userid, q, 1, 0, errormsg);
users[userid].active = 0;
return 1;
}
users[userid].active = 0;
return 0;
}
static int
tunnel_bind()
{
char packet[64*1024];
struct sockaddr_storage from;
socklen_t fromlen;
struct fw_query *query;
unsigned short id;
int dns_fd;
int r;
fromlen = sizeof(struct sockaddr);
r = recvfrom(server.bind_fd, packet, sizeof(packet), 0,
(struct sockaddr*)&from, &fromlen);
if (r <= 0)
return 0;
id = dns_get_id(packet, r);
DEBUG(3, "RX: Got response on query %u from DNS", (id & 0xFFFF));
/* Get sockaddr from id */
fw_query_get(id, &query);
if (!query) {
DEBUG(2, "Lost sender of id %u, dropping reply", (id & 0xFFFF));
return 0;
}
DEBUG(3, "TX: client %s id %u, %d bytes",
format_addr(&query->addr, query->addrlen), (id & 0xffff), r);
dns_fd = get_dns_fd(&server.dns_fds, &query->addr);
if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr),
query->addrlen) <= 0) {
warn("forward reply error");
}
return 0;
}
static ssize_t
tunnel_tcp(int userid)
{
ssize_t len;
uint8_t buf[64*1024];
char *errormsg = NULL;
if (users[userid].remote_forward_connected != 1) {
DEBUG(2, "tunnel_tcp: user %d TCP socket not connected!", userid);
return 0;
}
len = read(users[userid].remote_tcp_fd, buf, sizeof(buf));
DEBUG(5, "read %ld bytes on TCP", len);
if (len == 0) {
DEBUG(1, "EOF on TCP forward for user %d; closing connection.", userid);
errormsg = "Connection closed by remote host.";
user_send_tcp_disconnect(userid, NULL, errormsg);
return -1;
} else if (len < 0) {
errormsg = strerror(errno);
DEBUG(1, "Error %d on TCP forward for user %d: %s", errno, userid, errormsg);
user_send_tcp_disconnect(userid, NULL, errormsg);
return -1;
}
user_send_data(userid, buf, (size_t) len, 0);
return len;
}
static int
tunnel_tun()
{
struct ip *header;
static uint8_t in[64*1024];
int userid;
int read;
if ((read = read_tun(server.tun_fd, in, sizeof(in))) <= 0)
return 0;
/* find target ip in packet, in is padded with 4 bytes TUN header */
header = (struct ip*) (in + 4);
userid = find_user_by_ip(header->ip_dst.s_addr);
if (userid < 0)
return 0;
DEBUG(3, "IN: %d byte pkt from tun to user %d; compression %d",
read, userid, users[userid].down_compression);
return user_send_data(userid, in, read, 0);
}
static int
tunnel_dns(int dns_fd)
{
struct query q;
int read;
int domain_len;
int inside_topdomain = 0;
if ((read = read_dns(dns_fd, &q)) <= 0)
return 0;
DEBUG(3, "RX: client %s ID %5d, type %d, name %s",
format_addr(&q.from, q.fromlen), q.id, q.type, q.name);
domain_len = strlen(q.name) - strlen(server.topdomain);
if (domain_len >= 0 && !strcasecmp(q.name + domain_len, server.topdomain))
inside_topdomain = 1;
/* require dot before topdomain */
if (domain_len >= 1 && q.name[domain_len - 1] != '.')
inside_topdomain = 0;
if (inside_topdomain) {
/* This is a query we can handle */
/* Handle A-type query for ns.topdomain, possibly caused
by our proper response to any NS request */
if (domain_len == 3 && q.type == T_A &&
(q.name[0] == 'n' || q.name[0] == 'N') &&
(q.name[1] == 's' || q.name[1] == 'S') &&
q.name[2] == '.') {
handle_a_request(dns_fd, &q, 0);
return 0;
}
/* Handle A-type query for www.topdomain, for anyone that's
poking around */
if (domain_len == 4 && q.type == T_A &&
(q.name[0] == 'w' || q.name[0] == 'W') &&
(q.name[1] == 'w' || q.name[1] == 'W') &&
(q.name[2] == 'w' || q.name[2] == 'W') &&
q.name[3] == '.') {
handle_a_request(dns_fd, &q, 1);
return 0;
}
switch (q.type) {
case T_NULL:
case T_PRIVATE:
case T_CNAME:
case T_A:
case T_MX:
case T_SRV:
case T_TXT:
case T_PTR:
case T_AAAA:
case T_A6:
case T_DNAME:
/* encoding is "transparent" here */
handle_null_request(dns_fd, &q, domain_len);
break;
case T_NS:
handle_ns_request(dns_fd, &q);
break;
default:
break;
}
} else {
/* Forward query to other port ? */
DEBUG(2, "Requested domain outside our topdomain.");
if (server.bind_fd) {
forward_query(server.bind_fd, &q);
}
}
return 0;
}
int
server_tunnel()
{
struct timeval tv;
fd_set read_fds, write_fds;
int i;
int userid;
struct query *answer_now = NULL;
time_t last_action = time(NULL);
if (server.debug >= 5)
window_debug = server.debug - 4;
while (server.running) {
int maxfd;
/* max wait time based on pending queries */
tv = qmem_max_wait(&userid, &answer_now);
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
maxfd = 0;
if (server.dns_fds.v4fd >= 0) {
FD_SET(server.dns_fds.v4fd, &read_fds);
maxfd = MAX(server.dns_fds.v4fd, maxfd);
}
if (server.dns_fds.v6fd >= 0) {
FD_SET(server.dns_fds.v6fd, &read_fds);
maxfd = MAX(server.dns_fds.v6fd, maxfd);
}
if (server.bind_fd) {
/* wait for replies from real DNS */
FD_SET(server.bind_fd, &read_fds);
maxfd = MAX(server.bind_fd, maxfd);
}
/* Don't read from tun if all users have filled outpacket queues */
if(!all_users_waiting_to_send()) {
FD_SET(server.tun_fd, &read_fds);
maxfd = MAX(server.tun_fd, maxfd);
}
/* add connected user TCP forward FDs to read set */
maxfd = MAX(set_user_tcp_fds(&read_fds, 1), maxfd);
/* add connectING user TCP FDs to write set */
maxfd = MAX(set_user_tcp_fds(&write_fds, 2), maxfd);
i = select(maxfd + 1, &read_fds, &write_fds, NULL, &tv);
if(i < 0) {
if (server.running)
warn("select");
return 1;
}
if (i == 0) {
if (server.max_idle_time) {
/* only trigger the check if that's worth ( ie, no need to loop over if there
is something to send */
if (difftime(time(NULL), last_action) > server.max_idle_time) {
for (userid = 0; userid < created_users; userid++) {
last_action = (users[userid].last_pkt > last_action) ? users[userid].last_pkt : last_action;
}
if (difftime(time(NULL), last_action) > server.max_idle_time) {
fprintf(stderr, "Server idle for too long, shutting down...\n");
server.running = 0;
}
}
}
} else {
if (FD_ISSET(server.tun_fd, &read_fds)) {
tunnel_tun();
}
for (userid = 0; userid < created_users; userid++) {
if (FD_ISSET(users[userid].remote_tcp_fd, &read_fds) && users[userid].remoteforward_addr_len > 0) {
DEBUG(4, "tunnel_tcp called for user %d", userid);
tunnel_tcp(userid);
} else if (users[userid].remote_forward_connected == 2 &&
FD_ISSET(users[userid].remote_tcp_fd, &write_fds)) {
DEBUG(2, "User %d TCP socket now writable (connection established)", userid);
users[userid].remote_forward_connected = 1;
}
}
if (FD_ISSET(server.dns_fds.v4fd, &read_fds)) {
tunnel_dns(server.dns_fds.v4fd);
}
if (FD_ISSET(server.dns_fds.v6fd, &read_fds)) {
tunnel_dns(server.dns_fds.v6fd);
}
if (FD_ISSET(server.bind_fd, &read_fds)) {
tunnel_bind();
}
}
}
return 0;
}
void
handle_full_packet(int userid, uint8_t *data, size_t len, int compressed)
{
size_t rawlen;
uint8_t out[64*1024], *rawdata;
struct ip *hdr;
int touser = -1;
int ret;
/* Check if data needs to be uncompressed */
if (compressed) {
rawlen = sizeof(out);
ret = uncompress(out, &rawlen, data, len);
rawdata = out;
} else {
rawlen = len;
rawdata = data;
ret = Z_OK;
}
if (ret == Z_OK) {
if (users[userid].remoteforward_addr_len == 0) {
hdr = (struct ip*) (out + 4);
touser = find_user_by_ip(hdr->ip_dst.s_addr);
DEBUG(2, "FULL PKT: %" L "u bytes from user %d (touser %d)", len, userid, touser);
if (touser == -1) {
/* send the uncompressed packet to tun device */
write_tun(server.tun_fd, rawdata, rawlen);
} else {
/* don't re-compress if possible */
if (users[touser].down_compression && compressed) {
user_send_data(touser, data, len, 1);
} else {
user_send_data(touser, rawdata, rawlen, 0);
}
}
} else {
/* Write full pkt to user's remote forward TCP stream */
if ((ret = write(users[userid].remote_tcp_fd, rawdata, rawlen)) != rawlen) {
DEBUG(2, "Write error %d on TCP socket for user %d: %s", errno, userid, strerror(errno));
}
}
} else {
DEBUG(2, "Discarded upstream data from user %d, uncompress() result: %d", userid, ret);
}
}
static void
handle_raw_login(uint8_t *packet, size_t len, struct query *q, int fd, int userid)
{
char myhash[16];
if (len < 16) {
DEBUG(2, "Invalid raw login packet: length %" L "u < 16 bytes!", len);
return;
}
if (userid < 0 || userid >= created_users ||
check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) {
DEBUG(2, "User %d not authenticated, ignoring raw login!", userid);
return;
}
DEBUG(1, "RX-raw: login, len %" L "u, from user %d", len, userid);
/* User sends hash of seed + 1 */
login_calculate(myhash, 16, server.password, users[userid].seed + 1);
if (memcmp(packet, myhash, 16) == 0) {
/* Update time info for user */
users[userid].last_pkt = time(NULL);
/* Store remote IP number */
memcpy(&(users[userid].host), &(q->from), q->fromlen);
users[userid].hostlen = q->fromlen;
/* Correct hash, reply with hash of seed - 1 */
user_set_conn_type(userid, CONN_RAW_UDP);
login_calculate(myhash, 16, server.password, users[userid].seed - 1);
send_raw(fd, (uint8_t *)myhash, 16, userid, RAW_HDR_CMD_LOGIN, &q->from, q->fromlen);
users[userid].authenticated_raw = 1;
}
}
static void
handle_raw_data(uint8_t *packet, size_t len, struct query *q, int userid)
{
if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) {
return;
}
if (!users[userid].authenticated_raw) return;
/* Update time info for user */
users[userid].last_pkt = time(NULL);
/* copy to packet buffer, update length */
DEBUG(3, "RX-raw: full pkt raw, length %" L "u, from user %d", len, userid);
handle_full_packet(userid, packet, len, 1);
}
static void
handle_raw_ping(struct query *q, int dns_fd, int userid)
{
if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) {
return;
}
if (!users[userid].authenticated_raw) return;
/* Update time info for user */
users[userid].last_pkt = time(NULL);
DEBUG(3, "RX-raw: ping from user %d", userid);
/* Send ping reply */
send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, &q->from, q->fromlen);
}
static int
raw_decode(uint8_t *packet, size_t len, struct query *q, int dns_fd)
{
int raw_user;
uint8_t raw_cmd;
/* minimum length */
if (len < RAW_HDR_LEN) return 0;
/* should start with header */
if (memcmp(packet, raw_header, RAW_HDR_IDENT_LEN))
return 0;
raw_cmd = RAW_HDR_GET_CMD(packet);
raw_user = RAW_HDR_GET_USR(packet);
DEBUG(3, "RX-raw: client %s, user %d, raw command 0x%02X, length %" L "u",
format_addr(&q->from, q->fromlen), raw_user, raw_cmd, len);
packet += RAW_HDR_LEN;
len -= RAW_HDR_LEN;
switch (raw_cmd) {
case RAW_HDR_CMD_LOGIN:
/* Login challenge */
handle_raw_login(packet, len, q, dns_fd, raw_user);
break;
case RAW_HDR_CMD_DATA:
/* Data packet */
handle_raw_data(packet, len, q, raw_user);
break;
case RAW_HDR_CMD_PING:
/* Keepalive packet */
handle_raw_ping(q, dns_fd, raw_user);
break;
default:
DEBUG(1, "Unhandled raw command %02X from user %d", raw_cmd, raw_user);
break;
}
return 1;
}
int
read_dns(int fd, struct query *q)
{
struct sockaddr_storage from;
socklen_t addrlen;
uint8_t packet[64*1024];
int r;
#ifndef WINDOWS32
char control[CMSG_SPACE(sizeof (struct in6_pktinfo))];
struct msghdr msg;
struct iovec iov;
struct cmsghdr *cmsg;
addrlen = sizeof(struct sockaddr_storage);
iov.iov_base = packet;
iov.iov_len = sizeof(packet);
msg.msg_name = (caddr_t) &from;
msg.msg_namelen = (unsigned) addrlen;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = control;
msg.msg_controllen = sizeof(control);
msg.msg_flags = 0;
r = recvmsg(fd, &msg, 0);
#else
addrlen = sizeof(struct sockaddr_storage);
r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen);
#endif /* !WINDOWS32 */
if (r > 0) {
memcpy(&q->from, &from, addrlen);
q->fromlen = addrlen;
gettimeofday(&q->time_recv, NULL);
/* TODO do not handle raw packets here! */
if (raw_decode(packet, r, q, fd)) {
return 0;
}
if (dns_decode(NULL, 0, q, QR_QUERY, (char *)packet, r) < 0) {
return 0;
}
#ifndef WINDOWS32
memset(&q->destination, 0, sizeof(struct sockaddr_storage));
/* Read destination IP address */
for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msg, cmsg)) {
if (cmsg->cmsg_level == IPPROTO_IP &&
cmsg->cmsg_type == DSTADDR_SOCKOPT) {
struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination;
addr->sin_family = AF_INET;
addr->sin_addr = *dstaddr(cmsg);
q->dest_len = sizeof(*addr);
break;
}
if (cmsg->cmsg_level == IPPROTO_IPV6 &&
cmsg->cmsg_type == IPV6_PKTINFO) {
struct in6_pktinfo *pktinfo;
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination;
pktinfo = (struct in6_pktinfo *) CMSG_DATA(cmsg);
addr->sin6_family = AF_INET6;
memcpy(&addr->sin6_addr, &pktinfo->ipi6_addr, sizeof(struct in6_addr));
q->dest_len = sizeof(*addr);
break;
}
}
#endif
return strlen(q->name);
} else if (r < 0) {
/* Error */
warn("read dns");
}
return 0;
}
static size_t
write_dns_nameenc(uint8_t *buf, size_t buflen, uint8_t *data, size_t datalen, char downenc)
/* Returns #bytes of data that were encoded */
{
static int td_cmc;
char td[3];
struct encoder *enc;
/* Make a rotating topdomain to prevent filtering, ie 10-bit CMC */
td_cmc ++;
td_cmc &= 0x3FF;
td[0] = b32_5to8(td_cmc & 0x1F);
td[1] = b32_5to8((td_cmc >> 5) & 0x1F);
td[2] = 0;
/* encode data,datalen to CNAME/MX answer */
if (downenc == 'S') {
buf[0] = 'i';
enc = b64;
} else if (downenc == 'U') {
buf[0] = 'j';
enc = b64u;
} else if (downenc == 'V') {
buf[0] = 'k';
enc = b128;
} else {
buf[0] = 'h';
enc = b32;
}
return build_hostname(buf, buflen, data, datalen, td, enc, 0xFF, 1);
}
void
write_dns(int fd, struct query *q, char *data, size_t datalen, char downenc)
{
char buf[64*1024];
int len = 0;
if (q->type == T_CNAME || q->type == T_A ||
q->type == T_PTR || q->type == T_AAAA || q->type == T_A6 || q->type == T_DNAME) {
char cnamebuf[1024]; /* max 255 */
write_dns_nameenc((uint8_t *)cnamebuf, sizeof(cnamebuf), (uint8_t *)data, datalen, downenc);
len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, cnamebuf, sizeof(cnamebuf));
} else if (q->type == T_MX || q->type == T_SRV) {
char mxbuf[64*1024];
char *b = mxbuf;
int offset = 0;
int res;
while (1) {
res = write_dns_nameenc((uint8_t *)b, sizeof(mxbuf) - (b - mxbuf),
(uint8_t *)data + offset, datalen - offset, downenc);
if (res < 1) {
/* nothing encoded */
b++; /* for final \0 */
break;
}
b = b + strlen(b) + 1;
offset += res;
if (offset >= datalen)
break;
}
/* Add final \0 */
*b = '\0';
len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, mxbuf,
sizeof(mxbuf));
} else if (q->type == T_TXT) {
/* TXT with base32 */
uint8_t 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, (uint8_t *)data, datalen);
}
else if (downenc == 'U') {
txtbuf[0] = 'u'; /* Base64 with Underscore */
len = b64u->encode(txtbuf+1, &space, (uint8_t *)data, datalen);
}
else if (downenc == 'V') {
txtbuf[0] = 'v'; /* Base128 */
len = b128->encode(txtbuf+1, &space, (uint8_t *)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, (uint8_t *)data, datalen);
}
len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, (char *)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;
}
DEBUG(3, "TX: client %s ID %5d, %" L "u bytes data, type %d, name '%10s'",
format_addr(&q->from, q->fromlen), q->id, datalen, q->type, q->name);
sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen);
}
#define CHECK_LEN(l, x) \
if (l < x) { \
write_dns(dns_fd, q, "BADLEN", 6, 'T'); \
return; \
}
void
handle_dns_version(int dns_fd, struct query *q, uint8_t *domain, int domain_len)
{
uint8_t unpacked[512];
uint32_t version = !PROTOCOL_VERSION;
int userid, read;
read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)domain + 1, domain_len - 1, b32);
/* Version greeting, compare and send ack/nak */
if (read >= 4) {
/* Received V + 32bits version (network byte order) */
version = ntohl(*(uint32_t *) unpacked);
} /* if invalid pkt, just send VNAK */
if (version != PROTOCOL_VERSION) {
send_version_response(dns_fd, VERSION_NACK, PROTOCOL_VERSION, 0, q);
syslog(LOG_INFO, "dropped user from %s, sent bad version %08X",
format_addr(&q->from, q->fromlen), version);
return;
}
userid = find_available_user();
if (userid < 0) {
/* 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",
format_addr(&q->from, q->fromlen));
return;
}
/* Reset user options to safe defaults */
struct tun_user *u = &users[userid];
u->seed = rand();
/* Store remote IP number */
memcpy(&(u->host), &(q->from), q->fromlen);
u->hostlen = q->fromlen;
u->remote_forward_connected = 0;
u->remoteforward_addr_len = 0;
u->remote_tcp_fd = 0;
u->remoteforward_addr.ss_family = AF_UNSPEC;
u->fragsize = 100; /* very safe */
u->conn = CONN_DNS_NULL;
u->encoder = get_base32_encoder();
u->down_compression = 1;
u->lazy = 0;
u->next_upstream_ack = -1;
u->outgoing->maxfraglen = u->encoder->get_raw_length(u->fragsize) - DOWNSTREAM_PING_HDR;
window_buffer_clear(u->outgoing);
window_buffer_clear(u->incoming);
qmem_init(userid);
if (q->type == T_NULL || q->type == T_PRIVATE) {
u->downenc = 'R';
u->downenc_bits = 8;
} else {
u->downenc = 'T';
u->downenc_bits = 5;
}
send_version_response(dns_fd, VERSION_ACK, u->seed, userid, q);
syslog(LOG_INFO, "Accepted version for user #%d from %s",
userid, format_addr(&q->from, q->fromlen));
DEBUG(1, "User %d connected with correct version from %s.",
userid, format_addr(&q->from, q->fromlen));
DEBUG(3, "User %d has login challenge 0x%08x", userid, u->seed);
}
void
handle_dns_downstream_codec_check(int dns_fd, struct query *q, uint8_t *domain, int domain_len)
{
char *datap;
int datalen, i, codec;
i = b32_8to5(domain[2]); /* check variant: second char in b32 */
if (i == 1) {
datap = DOWNCODECCHECK1;
datalen = DOWNCODECCHECK1_LEN;
} else {
write_dns(dns_fd, q, "BADLEN", 6, 'T');
return;
}
/* codec to test: first char raw */
codec = toupper(domain[1]);
switch (codec) {
case 'T':
case 'S':
case 'U':
case 'V':
if (q->type == T_TXT ||
q->type == T_SRV || q->type == T_MX ||
q->type == T_CNAME || q->type == T_A ||
q->type == T_PTR || q->type == T_AAAA ||
q->type == T_A6 || q->type == T_DNAME) {
write_dns(dns_fd, q, datap, datalen, codec);
return;
}
break;
case 'R':
if (q->type == T_NULL || q->type == T_TXT) {
write_dns(dns_fd, q, datap, datalen, 'R');
return;
}
break;
}
/* if still here, then codec not available */
write_dns(dns_fd, q, "BADCODEC", 8, 'T');
}
void
handle_dns_login(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid)
{
uint8_t unpacked[512], flags;
char logindata[16], *tmp[2], out[512], *reason = NULL;
char *errormsg = NULL, fromaddr[100];
struct in_addr tempip;
char remote_tcp, remote_isnt_localhost, use_ipv6, poll_status; //, drop_packets;
int length = 17, read, addrlen, login_ok = 1;
uint16_t port;
struct tun_user *u = &users[userid];
struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) &u->remoteforward_addr;
struct sockaddr_in *addr = (struct sockaddr_in *) &u->remoteforward_addr;
read = unpack_data(unpacked, sizeof(unpacked), (uint8_t *) domain + 2, domain_len - 2, b32);
/* Decode flags and calculate min. length */
flags = unpacked[0];
remote_tcp = flags & 1;
remote_isnt_localhost = (flags & 2) >> 1;
use_ipv6 = (flags & 4) >> 2;
//drop_packets = (flags & 8) >> 3; /* currently unimplemented */
poll_status = (flags & 0x10) >> 4;
addrlen = (remote_tcp && remote_isnt_localhost) ? (use_ipv6 ? 16 : 4) : 0;
length += (remote_tcp ? 2 : 0) + addrlen;
/* There should be no extra data if only polling forwarding status */
if (poll_status) {
length = 17;
}
CHECK_LEN(read, length);
strncpy(fromaddr, format_addr(&q->from, q->fromlen), 100);
DEBUG(2, "Received login request for user %d from %s",
userid, fromaddr);
DEBUG(6, "Login: length=%d, flags=0x%02x, seed=0x%08x, hash=0x%016llx%016llx",
length, flags, u->seed, *(unsigned long long *) (unpacked + 1),
*(unsigned long long *) (unpacked + 9));
if (check_user_and_ip(userid, q, server.check_ip) != 0) {
write_dns(dns_fd, q, "BADIP", 5, 'T');
syslog(LOG_WARNING, "rejected login request from user #%d from %s; expected source %s",
userid, fromaddr, format_addr(&u->host, u->hostlen));
DEBUG(1, "Rejected login request from user %d: BADIP", userid);
return;
}
/* Check remote host/port options */
if ((addrlen > 0 && !server.allow_forward_remote) ||
(remote_tcp && !server.allow_forward_local_port)) {
login_ok = 0;
reason = "requested bad TCP forward options";
}
u->last_pkt = time(NULL);
login_calculate(logindata, 16, server.password, u->seed);
if (memcmp(logindata, unpacked + 1, 16) != 0) {
login_ok = 0;
reason = "bad password";
}
if (remote_tcp) {
port = ntohs(*(uint16_t *) (unpacked + 17));
if (addrlen > 0) {
if (use_ipv6) {
addr6->sin6_family = AF_INET6;
addr6->sin6_port = htons(port);
u->remoteforward_addr_len = sizeof(*addr6);
memcpy(&addr6->sin6_addr, unpacked + 19, MIN(sizeof(*addr6), addrlen));
} else {
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
u->remoteforward_addr_len = sizeof(*addr);
memcpy(&addr->sin_addr, unpacked + 19, MIN(sizeof(*addr), addrlen));
}
DEBUG(1, "User %d requested TCP connection to %s:%hu, %s.", userid,
format_addr(&u->remoteforward_addr, u->remoteforward_addr_len),
port, login_ok ? "allowed" : "rejected");
} else {
addr->sin_family = AF_INET;
addr->sin_port = htons(port);
addr->sin_addr.s_addr = htonl(INADDR_LOOPBACK);
DEBUG(1, "User %d requested TCP connection to localhost:%hu, %s.", userid,
port, login_ok ? "allowed" : "rejected");
}
}
if (poll_status && login_ok) {
if (addrlen > 0 || (flags ^ 0x10)) {
login_ok = 0;
reason = "invalid flags";
}
}
if (!login_ok) {
write_dns(dns_fd, q, "LNAK", 4, 'T');
if (--u->authenticated >= 0)
u->authenticated = -1;
int tries = abs(u->authenticated);
DEBUG(1, "rejected login from user %d (%s), tries: %d, reason: %s",
userid, fromaddr, tries, reason);
syslog(LOG_WARNING, "rejected login request from user #%d from %s, %s; incorrect attempts: %d",
userid, fromaddr, reason, tries);
return;
}
/* Store user auth OK, count number of logins */
u->authenticated++;
if (u->authenticated > 1 && !poll_status)
syslog(LOG_WARNING, "duplicate login request from user #%d from %s",
userid, fromaddr);
if (remote_tcp) {
int tcp_fd;
DEBUG(1, "User %d connected from %s, starting TCP connection to %s.", userid,
fromaddr, format_addr(&u->remoteforward_addr, sizeof(struct sockaddr_storage)));
syslog(LOG_NOTICE, "accepted password from user #%d, connecting TCP forward", userid);
/* Open socket and connect to TCP forward host:port */
tcp_fd = open_tcp_nonblocking(&u->remoteforward_addr, &errormsg);
if (tcp_fd < 0) {
if (!errormsg)
errormsg = "Error opening socket.";
goto tcp_forward_error;
}
/* connection in progress */
out[0] = 'W';
read = 1;
write_dns(dns_fd, q, out, read + 1, u->downenc);
u->remote_tcp_fd = tcp_fd;
u->remote_forward_connected = 2; /* connecting */
return;
} else if (poll_status) {
/* Check TCP forward connection status and update user data */
int retval;
/* Check for connection errors */
if ((retval = check_tcp_error(u->remote_tcp_fd, &errormsg)) != 0) {
/* if unacceptable error, tell user */
if (retval != EINPROGRESS)
goto tcp_forward_error;
}
if (retval == EINPROGRESS)
u->remote_forward_connected = 2;
read = 1;
out[1] = 0;
/* check user TCP forward status flag, which is updated in server_tunnel
* when the file descriptor becomes writable (ie, connection established */
if (u->remote_forward_connected == 1) {
out[0] = 'C';
DEBUG(2, "User %d TCP forward connection established: %s", userid, errormsg);
} else if (u->remote_forward_connected == 2) {
out[0] = 'W';
DEBUG(3, "User %d TCP connection in progress: %s", userid, errormsg);
}
write_dns(dns_fd, q, out, read + 1, u->downenc);
return;
} else {
out[0] = 'I';
/* Send ip/mtu/netmask info */
tempip.s_addr = server.my_ip;
tmp[0] = strdup(inet_ntoa(tempip));
tempip.s_addr = u->tun_ip;
tmp[1] = strdup(inet_ntoa(tempip));
read = snprintf(out + 1, sizeof(out) - 1, "-%s-%s-%d-%d",
tmp[0], tmp[1], server.mtu, server.netmask);
DEBUG(1, "User %d connected from %s, tun_ip %s.", userid,
fromaddr, tmp[1]);
syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]);
free(tmp[1]);
free(tmp[0]);
write_dns(dns_fd, q, out, read + 1, u->downenc);
return;
}
tcp_forward_error:
DEBUG(1, "Failed to connect TCP forward for user %d: %s", userid, errormsg);
out[0] = 'E';
strncpy(out + 1, errormsg, sizeof(out) - 1);
read = strlen(out);
write_dns(dns_fd, q, out, read + 1, u->downenc);
}
void
handle_dns_ip_request(int dns_fd, struct query *q, int userid)
{
char reply[17];
int length;
reply[0] = 'I';
if (q->from.ss_family == AF_INET) {
if (server.ns_ip != INADDR_ANY) {
/* If set, use assigned external ip (-n option) */
memcpy(&reply[1], &server.ns_ip, sizeof(server.ns_ip));
} else {
/* otherwise return destination ip from packet */
struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination;
memcpy(&reply[1], &addr->sin_addr, sizeof(struct in_addr));
}
length = 1 + sizeof(struct in_addr);
} else {
struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &q->destination;
memcpy(&reply[1], &addr->sin6_addr, sizeof(struct in6_addr));
length = 1 + sizeof(struct in6_addr);
}
write_dns(dns_fd, q, reply, length, 'T');
}
void
handle_dns_upstream_codec_switch(int dns_fd, struct query *q, int userid,
uint8_t *unpacked, size_t read)
{
int codec;
struct encoder *enc;
codec = unpacked[0];
switch (codec) {
case 5: /* 5 bits per byte = base32 */
enc = b32;
user_switch_codec(userid, enc);
write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc);
break;
case 6: /* 6 bits per byte = base64 */
enc = b64;
user_switch_codec(userid, enc);
write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc);
break;
case 26: /* "2nd" 6 bits per byte = base64u, with underscore */
enc = b64u;
user_switch_codec(userid, enc);
write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc);
break;
case 7: /* 7 bits per byte = base128 */
enc = b128;
user_switch_codec(userid, enc);
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;
}
}
void
handle_dns_set_options(int dns_fd, struct query *q, int userid,
uint8_t *unpacked, size_t read)
{
uint8_t bits = 0;
char *encname = "BADCODEC";
int tmp_lazy, tmp_downenc, tmp_comp;
/* Temporary variables: don't change anything until all options parsed */
tmp_lazy = users[userid].lazy;
tmp_comp = users[userid].down_compression;
tmp_downenc = users[userid].downenc;
switch (unpacked[0] & 0x7C) {
case (1 << 6): /* Base32 */
tmp_downenc = 'T';
encname = "Base32";
bits = 5;
break;
case (1 << 5): /* Base64 */
tmp_downenc = 'S';
encname = "Base64";
bits = 6;
break;
case (1 << 4): /* Base64u */
tmp_downenc = 'U';
encname = "Base64u";
bits = 26;
break;
case (1 << 3): /* Base128 */
tmp_downenc = 'V';
encname = "Base128";
bits = 7;
break;
case (1 << 2): /* Raw */
tmp_downenc = 'R';
encname = "Raw";
bits = 8;
break;
default: /* Invalid (More than 1 encoding bit set) */
write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc);
return;
}
tmp_comp = (unpacked[0] & 2) >> 1; /* compression flag */
tmp_lazy = (unpacked[0] & 1); /* lazy mode flag */
/* Automatically switch to raw encoding if PRIVATE or NULL request */
if ((q->type == T_NULL || q->type == T_PRIVATE) && !bits) {
users[userid].downenc = 'R';
bits = 8;
DEBUG(2, "Assuming raw data encoding with NULL/PRIVATE requests for user %d.", userid);
}
if (bits) {
int f = users[userid].fragsize;
users[userid].outgoing->maxfraglen = (bits * f) / 8 - DOWNSTREAM_PING_HDR;
users[userid].downenc_bits = bits;
}
DEBUG(1, "Options for user %d: down compression %d, data bits %d/maxlen %u (enc '%c'), lazy %d.",
userid, tmp_comp, bits, users[userid].outgoing->maxfraglen, tmp_downenc, tmp_lazy);
/* Store any changes */
users[userid].down_compression = tmp_comp;
users[userid].downenc = tmp_downenc;
users[userid].lazy = tmp_lazy;
write_dns(dns_fd, q, encname, strlen(encname), users[userid].downenc);
}
void
handle_dns_fragsize_probe(int dns_fd, struct query *q, int userid,
uint8_t *unpacked, size_t read)
/* Downstream fragsize probe packet */
{
int req_frag_size;
req_frag_size = ntohs(*(uint16_t *) unpacked);
DEBUG(3, "Got downstream fragsize probe from user %d, required fragsize %d", userid, req_frag_size);
if (req_frag_size < 2 || req_frag_size > MAX_FRAGSIZE) {
write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc);
} else {
char buf[MAX_FRAGSIZE];
int i;
unsigned int v = ((unsigned int) rand()) & 0xff;
memset(buf, 0, sizeof(buf));
buf[0] = (req_frag_size >> 8) & 0xff;
buf[1] = req_frag_size & 0xff;
/* make checkable pseudo-random sequence */
buf[2] = 107;
for (i = 3; i < MAX_FRAGSIZE; i++, v = (v + 107) & 0xff)
buf[i] = v;
write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc);
}
}
void
handle_dns_set_fragsize(int dns_fd, struct query *q, int userid,
uint8_t *unpacked, size_t read)
/* Downstream fragsize packet */
{
int max_frag_size;
max_frag_size = ntohs(*(uint16_t *)unpacked);
if (max_frag_size < 2 || max_frag_size > MAX_FRAGSIZE) {
write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc);
} else {
users[userid].fragsize = max_frag_size;
users[userid].outgoing->maxfraglen = (users[userid].downenc_bits * max_frag_size) /
8 - DOWNSTREAM_PING_HDR;
write_dns(dns_fd, q, (char *)unpacked, 2, users[userid].downenc);
DEBUG(1, "Setting max downstream data length to %u bytes for user %d; %d bits (%c)",
users[userid].outgoing->maxfraglen, userid, users[userid].downenc_bits, users[userid].downenc);
}
}
void
handle_dns_ping(int dns_fd, struct query *q, int userid,
uint8_t *unpacked, size_t read)
{
int dn_seq, up_seq, dn_winsize, up_winsize, dn_ack;
int respond, set_qtimeout, set_wtimeout, tcp_disconnect;
unsigned qtimeout_ms, wtimeout_ms;
CHECK_LEN(read, UPSTREAM_PING);
/* Check if query is cached */
if (qmem_is_cached(dns_fd, userid, q))
return;
/* Unpack flags/options from ping header */
dn_ack = ((unpacked[9] >> 2) & 1) ? unpacked[0] : -1;
up_winsize = unpacked[1];
dn_winsize = unpacked[2];
up_seq = unpacked[3];
dn_seq = unpacked[4];
/* Query timeout and window frag timeout */
qtimeout_ms = ntohs(*(uint16_t *) (unpacked + 5));
wtimeout_ms = ntohs(*(uint16_t *) (unpacked + 7));
respond = unpacked[9] & 1;
set_qtimeout = (unpacked[9] >> 3) & 1;
set_wtimeout = (unpacked[9] >> 4) & 1;
tcp_disconnect = (unpacked[9] >> 5) & 1;
DEBUG(3, "PING pkt user %d, down %d/%d, up %d/%d, ACK %d, %sqtime %u ms, "
"%swtime %u ms, respond %d, tcp_close %d (flags %02X)",
userid, dn_seq, dn_winsize, up_seq, up_winsize, dn_ack,
set_qtimeout ? "SET " : "", qtimeout_ms, set_wtimeout ? "SET " : "",
wtimeout_ms, respond, tcp_disconnect, unpacked[9]);
if (tcp_disconnect) {
/* close user's TCP forward connection and mark user as inactive */
if (users[userid].remoteforward_addr_len == 0) {
DEBUG(1, "User %d attempted TCP disconnect but didn't request TCP forwarding!", userid);
} else {
DEBUG(1, "User %d closed remote TCP forward", userid);
close_socket(users[userid].remote_tcp_fd);
users[userid].active = 0;
}
}
if (set_qtimeout) {
/* update user's query timeout if timeout flag set */
users[userid].dns_timeout = ms_to_timeval(qtimeout_ms);
/* if timeout is 0, we do not enable lazy mode but it is effectively the same */
int newlazy = !(qtimeout_ms == 0);
if (newlazy != users[userid].lazy)
DEBUG(2, "User %d: not changing lazymode to %d with timeout %u",
userid, newlazy, qtimeout_ms);
}
if (set_wtimeout) {
/* update sending window fragment ACK timeout */
users[userid].outgoing->timeout = ms_to_timeval(wtimeout_ms);
}
qmem_append(userid, q);
if (respond) {
/* ping handshake - set windowsizes etc, respond NOW using this query
* NOTE: still added to qmem (for cache) even though responded to immediately */
DEBUG(2, "PING HANDSHAKE set windowsizes (old/new) up: %d/%d, dn: %d/%d",
users[userid].outgoing->windowsize, dn_winsize, users[userid].incoming->windowsize, up_winsize);
users[userid].outgoing->windowsize = dn_winsize;
users[userid].incoming->windowsize = up_winsize;
send_data_or_ping(userid, q, 1, 1, NULL);
return;
}
/* if respond flag not set, query waits in qmem and is used later */
user_process_incoming_data(userid, dn_ack);
}
void
handle_dns_data(int dns_fd, struct query *q, uint8_t *domain, int domain_len, int userid)
{
uint8_t unpacked[20];
static fragment f;
size_t len;
/* Need 6 char header + >=1 char data */
CHECK_LEN(domain_len, UPSTREAM_HDR + 1);
/* Check if cached */
if (qmem_is_cached(dns_fd, userid, q)) {
/* if is cached, by this point it has already been answered */
return;
}
qmem_append(userid, q);
/* Decode upstream data header - see docs/proto_XXXXXXXX.txt */
/* First byte (after userid) = CMC (ignored); skip 2 bytes */
len = sizeof(unpacked);
b32->decode(unpacked, &len, (uint8_t *)domain + 2, 5);
f.seqID = unpacked[0];
unpacked[2] >>= 4; /* Lower 4 bits are unused */
f.ack_other = ((unpacked[2] >> 3) & 1) ? unpacked[1] : -1;
f.compressed = (unpacked[2] >> 2) & 1;
f.start = (unpacked[2] >> 1) & 1;
f.end = unpacked[2] & 1;
/* Decode remainder of data with user encoding into fragment */
f.len = unpack_data(f.data, MAX_FRAGSIZE, (uint8_t *)domain + UPSTREAM_HDR,
domain_len - UPSTREAM_HDR, users[userid].encoder);
DEBUG(3, "frag seq %3u, datalen %5lu, ACK %3d, compression %1d, s%1d e%1d",
f.seqID, f.len, f.ack_other, f.compressed, f.start, f.end);
/* if already waiting for an ACK to be sent back upstream (on incoming buffer) */
if (users[userid].next_upstream_ack >= 0) {
/* Shouldn't normally happen; will always be reset after sending a packet. */
DEBUG(1, "[WARNING] next_upstream_ack == %d for user %d.", users[userid].next_upstream_ack, userid);
}
window_process_incoming_fragment(users[userid].incoming, &f);
users[userid].next_upstream_ack = f.seqID;
user_process_incoming_data(userid, f.ack_other);
/* Nothing to do. ACK for this fragment is sent later in qmem_max_wait,
* using an old query. This is left in qmem until needed/times out */
}
void
handle_null_request(int dns_fd, struct query *q, int domain_len)
/* Handles a NULL DNS request. See doc/proto_XXXXXXXX.txt for details on iodine protocol. */
{
char cmd, userchar;
int userid = -1;
uint8_t in[QUERY_NAME_SIZE + 1];
/* Everything here needs at least 5 chars in the name:
* cmd, userid and more data or at least 3 bytes CMC */
if (domain_len < 5)
return;
/* Duplicate domain name to prevent changing original query */
memcpy(in, q->name, QUERY_NAME_SIZE + 1);
in[QUERY_NAME_SIZE] = 0; /* null terminate */
cmd = toupper(in[0]);
DEBUG(3, "NULL request length %d/%" L "u, command '%c'", domain_len, sizeof(in), cmd);
/* Commands that do not care about userid: also these need to be backwards
* compatible with older versions of iodine (at least down to 00000502) */
if (cmd == 'V') { /* Version check - before userid is assigned*/
handle_dns_version(dns_fd, q, in, domain_len);
return;
}
else if (cmd == 'Z') { /* Upstream codec check - user independent */
/* Reply with received hostname as data (encoded in base32) */
write_dns(dns_fd, q, (char *)in, domain_len, 'T');
return;
}
else if (cmd == 'Y') { /* Downstream codec check - user independent */
handle_dns_downstream_codec_check(dns_fd, q, in, domain_len);
return;
}
/* Get userid from query (always 2nd byte in hex except for data packets) */
if (isxdigit(cmd)) {
/* Upstream data packet - first byte is userid in hex */
userchar = cmd;
cmd = 'd'; /* flag for data packet - not part of protocol */
} else {
userchar = toupper(in[1]);
}
if (isxdigit(userchar)) {
userid = (userchar >= 'A' && userchar <= 'F') ?
(userchar - 'A' + 10) : (userchar - '0');
} else {
/* Invalid user ID or bad DNS query */
write_dns(dns_fd, q, "BADLEN", 5, 'T');
}
/* Login request - after version check successful, do not check auth yet */
if (cmd == 'L') {
handle_dns_login(dns_fd, q, in, domain_len, userid);
return;
}
/* Check user IP and authentication status */
if (check_authenticated_user_and_ip(userid, q, server.check_ip) != 0) {
write_dns(dns_fd, q, "BADIP", 5, 'T');
return;
}
if (cmd == 'd') { /* Upstream data packet */
handle_dns_data(dns_fd, q, in, domain_len, userid);
return;
} else if (cmd == 'I') { /* IP request packet - no base32 data */
handle_dns_ip_request(dns_fd, q, userid);
}
/* Following commands have everything after cmd and userid in base32
* All bytes that are not valid base32 are decoded to 0 */
uint8_t unpacked[512];
size_t raw_len;
raw_len = unpack_data(unpacked, sizeof(unpacked), (uint8_t *)in + 2, domain_len - 2, b32);
if (raw_len < 3) /* always at least 3 bytes after decoding at least 5 bytes */
return; /* Just in case. */
switch (cmd) {
case 'S':
handle_dns_upstream_codec_switch(dns_fd, q, userid, unpacked, raw_len);
break;
case 'O':
handle_dns_set_options(dns_fd, q, userid, unpacked, raw_len);
break;
case 'R':
handle_dns_fragsize_probe(dns_fd, q, userid, unpacked, raw_len);
break;
case 'N':
handle_dns_set_fragsize(dns_fd, q, userid, unpacked, raw_len);
break;
case 'P':
handle_dns_ping(dns_fd, q, userid, unpacked, raw_len);
break;
default:
DEBUG(2, "Invalid DNS query! cmd = %c, hostname = '%*s'",
cmd, domain_len, in);
}
}
void
handle_ns_request(int dns_fd, struct query *q)
/* Mostly identical to handle_a_request() below */
{
char buf[64*1024];
int len;
if (server.ns_ip != INADDR_ANY) {
/* If ns_ip set, overwrite destination addr with it.
* Destination addr will be sent as additional record (A, IN) */
struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination;
memcpy(&addr->sin_addr, &server.ns_ip, sizeof(server.ns_ip));
}
len = dns_encode_ns_response(buf, sizeof(buf), q, server.topdomain);
if (len < 1) {
warnx("dns_encode_ns_response doesn't fit");
return;
}
DEBUG(2, "TX: NS reply client %s ID %5d, type %d, name %s, %d bytes",
format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len);
if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) {
warn("ns reply send error");
}
}
void
handle_a_request(int dns_fd, struct query *q, int fakeip)
/* Mostly identical to handle_ns_request() above */
{
char buf[64*1024];
int len;
if (fakeip) {
in_addr_t ip = inet_addr("127.0.0.1");
struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination;
memcpy(&addr->sin_addr, &ip, sizeof(ip));
} else if (server.ns_ip != INADDR_ANY) {
/* If ns_ip set, overwrite destination addr with it.
* Destination addr will be sent as additional record (A, IN) */
struct sockaddr_in *addr = (struct sockaddr_in *) &q->destination;
memcpy(&addr->sin_addr, &server.ns_ip, sizeof(server.ns_ip));
}
len = dns_encode_a_response(buf, sizeof(buf), q);
if (len < 1) {
warnx("dns_encode_a_response doesn't fit");
return;
}
DEBUG(2, "TX: A reply client %s ID %5d, type %d, name %s, %d bytes",
format_addr(&q->from, q->fromlen), q->id, q->type, q->name, len);
if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) {
warn("a reply send error");
}
}