mirror of
https://github.com/yarrick/iodine.git
synced 2024-12-25 14:33:32 +02:00
Release 0.4.1 tagged
This commit is contained in:
parent
4d4da3eb87
commit
d27bd67997
34 changed files with 1231 additions and 574 deletions
18
CHANGELOG
18
CHANGELOG
|
@ -1,12 +1,22 @@
|
|||
|
||||
iodine - IP over DNS is now easy
|
||||
iodine - http://code.kryo.se/iodine
|
||||
|
||||
http://code.kryo.se/iodine
|
||||
|
||||
********************************
|
||||
***********************************
|
||||
|
||||
CHANGES:
|
||||
|
||||
2007-11-30: 0.4.1 "Tea Online"
|
||||
- Introduced encoding API
|
||||
- Switched to new Base32 implementation
|
||||
- Added Base64 implementation that only uses 63 chars (not used yet)
|
||||
- Refined 'install' make target and use $(MAKE) for recursive calls
|
||||
- All received error messages (RCODE field) are echoed
|
||||
- Top domain limited to 128 chars
|
||||
- Case preservation check sent after login to decide codec
|
||||
- Fixed crash on incoming NULL query in server with bad top domain
|
||||
- /etc/resolv.conf is consulted if no nameserver is given on commandline
|
||||
- Applied patch from Matthew W. S. Bell (Detach before chroot/dropping priv)
|
||||
|
||||
2007-03-25: 0.4.0 "Run Home"
|
||||
- Added multiuser support (up to 8 users simultaneously)
|
||||
- Added authentication (password entered as argument or on stdin)
|
||||
|
|
36
Makefile
36
Makefile
|
@ -1,6 +1,11 @@
|
|||
PREFIX=/usr/local
|
||||
prefix=/usr/local
|
||||
sbindir=$(prefix)/sbin
|
||||
datadir=$(prefix)/share
|
||||
mandir=$(datadir)/man
|
||||
|
||||
INSTALL=/usr/bin/install
|
||||
DESTDIR=
|
||||
|
||||
INSTALL=install
|
||||
INSTALL_FLAGS=
|
||||
|
||||
MKDIR=mkdir
|
||||
|
@ -10,26 +15,31 @@ RM=rm
|
|||
RM_FLAGS=-f
|
||||
|
||||
all:
|
||||
@(cd src; make all)
|
||||
@(cd src; $(MAKE) all)
|
||||
|
||||
install: all
|
||||
$(MKDIR) $(MKDIR_FLAGS) $(PREFIX)/sbin
|
||||
$(INSTALL) $(INSTALL_FLAGS) bin/iodine $(PREFIX)/sbin/iodine
|
||||
$(INSTALL) $(INSTALL_FLAGS) bin/iodined $(PREFIX)/sbin/iodined
|
||||
$(MKDIR) $(MKDIR_FLAGS) $(PREFIX)/man/man8
|
||||
$(INSTALL) $(INSTALL_FLAGS) man/iodine.8 $(PREFIX)/man/man8/iodine.8
|
||||
$(MKDIR) $(MKDIR_FLAGS) $(DESTDIR)$(sbindir)
|
||||
$(INSTALL) $(INSTALL_FLAGS) bin/iodine $(DESTDIR)$(sbindir)/iodine
|
||||
chmod 755 $(DESTDIR)$(sbindir)/iodine
|
||||
$(INSTALL) $(INSTALL_FLAGS) bin/iodined $(DESTDIR)$(sbindir)/iodined
|
||||
chmod 755 $(DESTDIR)$(sbindir)/iodined
|
||||
$(MKDIR) $(MKDIR_FLAGS) $(DESTDIR)$(mandir)/man8
|
||||
$(INSTALL) $(INSTALL_FLAGS) man/iodine.8 $(DESTDIR)$(mandir)/man8/iodine.8
|
||||
chmod 644 $(DESTDIR)$(mandir)/man8/iodine.8
|
||||
|
||||
uninstall:
|
||||
$(RM) $(RM_FLAGS) $(PREFIX)/sbin/iodine
|
||||
$(RM) $(RM_FLAGS) $(PREFIX)/sbin/iodined
|
||||
$(RM) $(RM_FLAGS) $(PREFIX)/man/man8/iodine.8
|
||||
$(RM) $(RM_FLAGS) $(DESTDIR)$(sbindir)/iodine
|
||||
$(RM) $(RM_FLAGS) $(DESTDIR)$(sbindir)/iodined
|
||||
$(RM) $(RM_FLAGS) $(DESTDIR)$(mandir)/man8/iodine.8
|
||||
|
||||
test: all
|
||||
@echo "!! The check library is required for compiling and running the tests"
|
||||
@echo "!! Get it at http://check.sf.net"
|
||||
@(cd tests; make all)
|
||||
|
||||
clean:
|
||||
@echo "Cleaning..."
|
||||
@(cd src; make clean)
|
||||
@(cd tests; make clean)
|
||||
@(cd src; $(MAKE) clean)
|
||||
@(cd tests; $(MAKE) clean)
|
||||
@rm -rf bin
|
||||
|
||||
|
|
27
README
27
README
|
@ -1,9 +1,7 @@
|
|||
|
||||
iodine - IP over DNS is now easy
|
||||
iodine - http://code.kryo.se/iodine
|
||||
|
||||
http://code.kryo.se/iodine
|
||||
|
||||
********************************
|
||||
***********************************
|
||||
|
||||
This is a piece of software that lets you tunnel IPv4 data through a DNS
|
||||
server. This can be usable in different situations where internet access is
|
||||
|
@ -30,13 +28,18 @@ HOW TO USE:
|
|||
|
||||
Server side:
|
||||
To use this tunnel, you need control over a real domain (like mytunnel.com),
|
||||
and a server with a static public IP number that does not yet run a DNS
|
||||
server. Then, delegate a subdomain (say, tunnel1.mytunnel.com) to the server.
|
||||
If you use BIND for the domain, add these lines to the zone file:
|
||||
and a server with a public IP number (not behind NAT) that does not yet run
|
||||
a DNS server. Then, delegate a subdomain (say, tunnel1.mytunnel.com) to the
|
||||
server. If you use BIND for the domain, add these lines to the zone file:
|
||||
|
||||
tunnel1host IN A 10.15.213.99
|
||||
tunnel1 IN NS tunnel1host.mytunnel.com.
|
||||
|
||||
Do not use CNAME instead of A above.
|
||||
If your server has a dynamic IP, use a dynamic dns provider:
|
||||
|
||||
tunnel1 IN NS tunnel1host.mydyndnsprovider.com
|
||||
|
||||
Now any DNS querys for domains ending with tunnel1.mytunnnel.com will be sent
|
||||
to your server. Start iodined on the server. The first argument is the tunnel
|
||||
IP address (like 192.168.99.1) and the second is the assigned domain (in this
|
||||
|
@ -60,13 +63,11 @@ either side.
|
|||
|
||||
MISC. INFO:
|
||||
|
||||
Note that you can have only one client per server at the same time. This is
|
||||
because of the fragmentation of big packets going upstream, and will be fixed
|
||||
in future versions.
|
||||
|
||||
Try experimenting with the MTU size (-m option) to get maximum bandwidth. It is
|
||||
set to 1024 by default, which seems to work with most DNS servers. If you have
|
||||
problems, try setting it to below 512.
|
||||
problems, try setting it to 220 as this ensures all packets to be < 512 bytes.
|
||||
Some DNS servers enforce a 512 byte packet limit, and this is probably the case
|
||||
if you can ping through the tunnel but not login via SSH.
|
||||
|
||||
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
|
||||
|
@ -90,7 +91,7 @@ iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to :5353
|
|||
PORTABILITY:
|
||||
|
||||
iodine has been tested on Linux (arm, ia64, x86, AMD64 and SPARC64), FreeBSD
|
||||
(ia64, x86), OpenBSD (x86), NetBSD (x86) and MacOS X (10.3, ppc, with
|
||||
(ia64, x86), OpenBSD (x86), NetBSD (x86) and MacOS X (ppc and x86, with
|
||||
http://www-user.rhrk.uni-kl.de/~nissler/tuntap/). It should work on other
|
||||
unix-like systems as well that has TUN/TAP tunneling support (after some
|
||||
patching). Let us know if you get it to run on other platforms.
|
||||
|
|
6
TODO
6
TODO
|
@ -1,9 +1,7 @@
|
|||
|
||||
iodine - IP over DNS is now easy
|
||||
iodine - http://code.kryo.se/iodine
|
||||
|
||||
http://code.kryo.se/iodine
|
||||
|
||||
********************************
|
||||
***********************************
|
||||
|
||||
The TODO list is now located at
|
||||
|
||||
|
|
13
man/iodine.8
13
man/iodine.8
|
@ -1,5 +1,5 @@
|
|||
.\" groff -man -Tascii iodine.8
|
||||
.TH IODINE 8 "FEB 2007" "User Manuals"
|
||||
.TH IODINE 8 "JUN 2007" "User Manuals"
|
||||
.SH NAME
|
||||
iodine, iodined \- tunnel IPv4 over DNS
|
||||
.SH SYNOPSIS
|
||||
|
@ -16,7 +16,9 @@ iodine, iodined \- tunnel IPv4 over DNS
|
|||
.B ] [-d
|
||||
.I device
|
||||
.B ]
|
||||
.B [
|
||||
.I nameserver
|
||||
.B ]
|
||||
.I topdomain
|
||||
|
||||
.B iodined [-v]
|
||||
|
@ -93,7 +95,8 @@ You must make sure the dns requests are forwarded to this port yourself.
|
|||
.B nameserver
|
||||
The nameserver to use to relay the dns traffic. This can be any relaying
|
||||
nameserver or the ip number of the server running iodined if reachable.
|
||||
Normally, you should specify a nameserver from your
|
||||
This argument is optional, and if not specified a nameserver will be read
|
||||
from the
|
||||
.I /etc/resolv.conf
|
||||
file.
|
||||
.TP
|
||||
|
@ -179,6 +182,12 @@ The normal case is to route all traffic through the DNS tunnel. To do this, firs
|
|||
add a route to the nameserver you use with the default gateway as gateway. Then
|
||||
replace the default gateway with the servers IP address within the DNS tunnel,
|
||||
and configure the server to do NAT.
|
||||
.TP
|
||||
.B MTU issues:
|
||||
Some relaying DNS servers enforce a 512 byte packet limit. All larger packets are
|
||||
simply dropped. If you can ping through the tunnel but not login via SSH, this is
|
||||
most likely the case. Set the MTU on the server to 220 to ensure that all packets
|
||||
are less than 512 bytes. This will however greatly affect performance.
|
||||
.SH BUGS
|
||||
File bugs at http://dev.kryo.se/iodine/
|
||||
.SH AUTHORS
|
||||
|
|
13
src/Makefile
13
src/Makefile
|
@ -1,8 +1,9 @@
|
|||
CC = gcc
|
||||
COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o md5.o common.o
|
||||
CLIENTOBJS = iodine.o
|
||||
CLIENT = ../bin/iodine
|
||||
CLIENTOBJS = iodine.o tun.o dns.o read.o encoding.o login.o base32.o md5.o common.o
|
||||
SERVEROBJS = iodined.o user.o
|
||||
SERVER = ../bin/iodined
|
||||
SERVEROBJS = iodined.o tun.o dns.o read.o encoding.o login.o base32.o md5.o common.o user.o
|
||||
|
||||
OS = `uname | tr "a-z" "A-Z"`
|
||||
ARCH = `uname -m`
|
||||
|
@ -15,15 +16,15 @@ all: stateos $(CLIENT) $(SERVER) $(TESTSUITE)
|
|||
stateos:
|
||||
@echo OS is $(OS), arch is $(ARCH)
|
||||
|
||||
$(CLIENT): $(CLIENTOBJS)
|
||||
$(CLIENT): $(COMMONOBJS) $(CLIENTOBJS)
|
||||
@echo LD $@
|
||||
@mkdir -p ../bin
|
||||
@$(CC) $(CLIENTOBJS) -o $(CLIENT) $(LDFLAGS)
|
||||
@$(CC) $(COMMONOBJS) $(CLIENTOBJS) -o $(CLIENT) $(LDFLAGS)
|
||||
|
||||
$(SERVER): $(SERVEROBJS)
|
||||
$(SERVER): $(COMMONOBJS) $(SERVEROBJS)
|
||||
@echo LD $@
|
||||
@mkdir -p ../bin
|
||||
@$(CC) $(SERVEROBJS) -o $(SERVER) $(LDFLAGS)
|
||||
@$(CC) $(COMMONOBJS) $(SERVEROBJS) -o $(SERVER) $(LDFLAGS)
|
||||
|
||||
.c.o:
|
||||
@echo CC $<
|
||||
|
|
152
src/base32.c
152
src/base32.c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -18,140 +18,160 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "encoding.h"
|
||||
#include "base32.h"
|
||||
|
||||
static const char cb32[] =
|
||||
"abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
static unsigned char rev32[128];
|
||||
static int reverse_init = 0;
|
||||
|
||||
static struct encoder base32_encoder =
|
||||
{
|
||||
"BASE32",
|
||||
base32_encode,
|
||||
base32_decode,
|
||||
base32_handles_dots,
|
||||
base32_handles_dots
|
||||
};
|
||||
|
||||
struct encoder
|
||||
*get_base32_encoder()
|
||||
{
|
||||
return &base32_encoder;
|
||||
}
|
||||
|
||||
int
|
||||
base32_encode(char **buf, size_t *buflen, const void *data, size_t size)
|
||||
base32_handles_dots()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
base32_encode(char *buf, size_t *buflen, const void *data, size_t size)
|
||||
{
|
||||
size_t newsize;
|
||||
char *newbuf;
|
||||
char *s;
|
||||
char *p;
|
||||
char *q;
|
||||
size_t maxsize;
|
||||
unsigned char *s;
|
||||
unsigned char *p;
|
||||
unsigned char *q;
|
||||
int i;
|
||||
|
||||
newsize = 8 * (size / 5 + 1) + 1;
|
||||
if (newsize > *buflen) {
|
||||
if ((newbuf = realloc(*buf, newsize)) == NULL) {
|
||||
free(*buf);
|
||||
*buf = NULL;
|
||||
*buflen = 0;
|
||||
return 0;
|
||||
}
|
||||
memset(buf, 0, *buflen);
|
||||
|
||||
*buf = newbuf;
|
||||
*buflen = newsize;
|
||||
/* how many chars can we encode within the buf */
|
||||
maxsize = 5 * (*buflen / 8 - 1) - 1;
|
||||
/* how big will the encoded data be */
|
||||
newsize = 8 * (size / 5 + 1) + 1;
|
||||
/* if the buffer is too small, eat some of the data */
|
||||
if (*buflen < newsize) {
|
||||
size = maxsize;
|
||||
}
|
||||
|
||||
p = s = *buf;
|
||||
q = (char*)data;
|
||||
p = s = (unsigned char *) buf;
|
||||
q = (unsigned char *)data;
|
||||
|
||||
for(i=0;i<size;i+=5) {
|
||||
p[0] = cb32[(q[0] >> 3)];
|
||||
p[1] = cb32[((q[0] & 0x07) << 2) | ((q[1] & 0xc0) >> 6)];
|
||||
p[0] = cb32[((q[0] & 0xf8) >> 3)];
|
||||
p[1] = cb32[(((q[0] & 0x07) << 2) | ((q[1] & 0xc0) >> 6))];
|
||||
p[2] = (i+1 < size) ? cb32[((q[1] & 0x3e) >> 1)] : '\0';
|
||||
p[3] = (i+1 < size) ? cb32[((q[1] & 0x01) << 4) | ((q[2] & 0xf0) >> 4)] : '\0';
|
||||
p[4] = (i+2 < size) ? cb32[((q[2] & 0x0f) << 1) | ((q[3] & 0x80) >> 7)] : '\0';
|
||||
p[5] = (i+3 < size) ? cb32[((q[3] & 0x3e) >> 2)] : '\0';
|
||||
p[6] = (i+3 < size) ? cb32[((q[3] & 0x03) << 3) | ((q[4] & 0xe0) > 5)] : '\0';
|
||||
p[5] = (i+3 < size) ? cb32[((q[3] & 0x7c) >> 2)] : '\0';
|
||||
p[6] = (i+3 < size) ? cb32[((q[3] & 0x03) << 3) | ((q[4] & 0xe0) >> 5)] : '\0';
|
||||
p[7] = (i+4 < size) ? cb32[((q[4] & 0x1f))] : '\0';
|
||||
|
||||
q += 5;
|
||||
p += 8;
|
||||
}
|
||||
*p = 0;
|
||||
return strlen(s);
|
||||
|
||||
/* store number of bytes from data that was used */
|
||||
*buflen = size;
|
||||
|
||||
return strlen(buf) - 1;
|
||||
}
|
||||
|
||||
#define DECODE_ERROR 0xffffffff
|
||||
#define REV32(x) rev32[(int) (x)]
|
||||
|
||||
static int
|
||||
pos(char c)
|
||||
decode_token(const unsigned char *t, unsigned char *data, size_t len)
|
||||
{
|
||||
const char *p;
|
||||
for (p = cb32; *p; p++)
|
||||
if (*p == c)
|
||||
return p - cb32;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
decode_token(const char *t, char *data)
|
||||
{
|
||||
int len;
|
||||
|
||||
len = strlen(t);
|
||||
|
||||
if (len < 2)
|
||||
return 0;
|
||||
|
||||
data[0] = ((pos(t[0]) & 0x1f) << 3) |
|
||||
((pos(t[1]) & 0x1c) >> 2);
|
||||
data[0] = ((REV32(t[0]) & 0x1f) << 3) |
|
||||
((REV32(t[1]) & 0x1c) >> 2);
|
||||
|
||||
if (len < 4)
|
||||
return 1;
|
||||
|
||||
data[1] = ((pos(t[1]) & 0x03) << 6) |
|
||||
((pos(t[2]) & 0x1f) << 1) |
|
||||
((pos(t[3]) & 0x10) >> 4);
|
||||
data[1] = ((REV32(t[1]) & 0x03) << 6) |
|
||||
((REV32(t[2]) & 0x1f) << 1) |
|
||||
((REV32(t[3]) & 0x10) >> 4);
|
||||
|
||||
if (len < 5)
|
||||
return 2;
|
||||
|
||||
data[2] = ((pos(t[3]) & 0x0f) << 4) |
|
||||
((pos(t[4]) & 0x1e) >> 1);
|
||||
data[2] = ((REV32(t[3]) & 0x0f) << 4) |
|
||||
((REV32(t[4]) & 0x1e) >> 1);
|
||||
|
||||
if (len < 7)
|
||||
return 3;
|
||||
|
||||
data[3] = ((pos(t[4]) & 0x01) << 7) |
|
||||
((pos(t[5]) & 0x1f) << 2) |
|
||||
((pos(t[6]) & 0x18) >> 3);
|
||||
data[3] = ((REV32(t[4]) & 0x01) << 7) |
|
||||
((REV32(t[5]) & 0x1f) << 2) |
|
||||
((REV32(t[6]) & 0x18) >> 3);
|
||||
|
||||
if (len < 8)
|
||||
return 4;
|
||||
|
||||
data[4] = ((pos(t[6]) & 0x07) << 5) |
|
||||
((pos(t[7]) & 0x1f));
|
||||
data[4] = ((REV32(t[6]) & 0x07) << 5) |
|
||||
((REV32(t[7]) & 0x1f));
|
||||
|
||||
return 5;
|
||||
}
|
||||
|
||||
int
|
||||
base32_decode(void **buf, size_t *buflen, const char *str)
|
||||
base32_decode(void *buf, size_t *buflen, const char *str, size_t slen)
|
||||
{
|
||||
unsigned char *q;
|
||||
size_t newsize;
|
||||
size_t maxsize;
|
||||
const char *p;
|
||||
char *newbuf;
|
||||
unsigned char c;
|
||||
int len;
|
||||
|
||||
newsize = 5 * (strlen(str) / 8 + 1) + 1;
|
||||
if (newsize > *buflen) {
|
||||
if ((newbuf = realloc(*buf, newsize)) == NULL) {
|
||||
free(*buf);
|
||||
*buf = NULL;
|
||||
*buflen = 0;
|
||||
return 0;
|
||||
}
|
||||
int i;
|
||||
|
||||
*buf = newbuf;
|
||||
*buflen = newsize;
|
||||
if (!reverse_init) {
|
||||
for (i = 0; i < 32; i++) {
|
||||
c = cb32[i];
|
||||
rev32[(int) c] = i;
|
||||
}
|
||||
reverse_init = 1;
|
||||
}
|
||||
|
||||
/* chars needed to decode slen */
|
||||
newsize = 5 * (slen / 8 + 1) + 1;
|
||||
/* encoded chars that fit in buf */
|
||||
maxsize = 8 * (*buflen / 5 + 1) + 1;
|
||||
/* if the buffer is too small, eat some of the data */
|
||||
if (*buflen < newsize) {
|
||||
slen = maxsize;
|
||||
}
|
||||
|
||||
q = *buf;
|
||||
q = buf;
|
||||
for (p = str; *p && strchr(cb32, *p); p += 8) {
|
||||
len = decode_token(p, (char *) q);
|
||||
len = decode_token((unsigned char *) p, (unsigned char *) q, slen);
|
||||
q += len;
|
||||
slen -= 8;
|
||||
|
||||
if (len < 5)
|
||||
break;
|
||||
}
|
||||
*q = '\0';
|
||||
|
||||
return q - (unsigned char *) *buf;
|
||||
return q - (unsigned char *) buf;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -17,7 +17,9 @@
|
|||
#ifndef __BASE32_H__
|
||||
#define __BASE32_H__
|
||||
|
||||
int base32_encode(char **, size_t *, const void *, size_t);
|
||||
int base32_decode(void **, size_t *, const char *);
|
||||
struct encoder *get_base32_encoder(void);
|
||||
int base32_handles_dots();
|
||||
int base32_encode(char *, size_t *, const void *, size_t);
|
||||
int base32_decode(void *, size_t *, const char *, size_t);
|
||||
|
||||
#endif
|
||||
|
|
338
src/base64.c
Normal file
338
src/base64.c
Normal file
|
@ -0,0 +1,338 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "encoding.h"
|
||||
#include "common.h"
|
||||
#include "base64.h"
|
||||
|
||||
static const char cb64[] =
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789.";
|
||||
static unsigned char rev64[128];
|
||||
static int reverse_init = 0;
|
||||
|
||||
#define REV64(x) rev64[(int) (x)]
|
||||
#define MODE (cb64[62])
|
||||
#define P62 (cb64[62])
|
||||
#define P63 (cb64[63])
|
||||
|
||||
static struct encoder base64_encoder =
|
||||
{
|
||||
"BASE64",
|
||||
base64_encode,
|
||||
base64_decode,
|
||||
base64_handles_dots,
|
||||
base64_handles_dots
|
||||
};
|
||||
|
||||
struct encoder
|
||||
*get_base64_encoder()
|
||||
{
|
||||
return &base64_encoder;
|
||||
}
|
||||
|
||||
int
|
||||
base64_handles_dots()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
findesc(int *count, unsigned char *esc, char c1, char c2, char c3, char c4)
|
||||
{
|
||||
int min1 = 0;
|
||||
int min2 = 0;
|
||||
|
||||
int num1 = 0xFF; /* a very big number */
|
||||
int num2 = 0xFE; /* a nearly as big number */
|
||||
|
||||
int i;
|
||||
|
||||
/* check if no more escapes needed */
|
||||
if (count[62] == 0 && count[63] == 0) {
|
||||
esc[0] = MODE;
|
||||
esc[1] = MODE;
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < 62; i++) {
|
||||
if (i == c1 || i == c2 || i == c3 || i == c4) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count[i] < num1) {
|
||||
min2 = min1;
|
||||
num2 = num1;
|
||||
min1 = i;
|
||||
num1 = count[i];
|
||||
} else if (count[i] < num2) {
|
||||
min2 = i;
|
||||
num2 = count[i];
|
||||
}
|
||||
}
|
||||
|
||||
esc[0] = cb64[min1];
|
||||
esc[1] = cb64[min2];
|
||||
}
|
||||
|
||||
static void
|
||||
escape_chars(char *buf, size_t buflen)
|
||||
{
|
||||
int counter[64];
|
||||
int escapes;
|
||||
int reset;
|
||||
int i;
|
||||
unsigned char temp[4096];
|
||||
unsigned char *r;
|
||||
unsigned char *w;
|
||||
unsigned char *e;
|
||||
unsigned char esc[2];
|
||||
|
||||
memset(counter, 0, sizeof(counter));
|
||||
esc[0] = P62;
|
||||
esc[1] = P63;
|
||||
|
||||
/* first, find the number of times each token is used */
|
||||
r = (unsigned char *) buf;
|
||||
w = temp;
|
||||
while (*r) {
|
||||
counter[REV64(*r)]++;
|
||||
*w++ = *r++;
|
||||
}
|
||||
|
||||
/* check if work needed */
|
||||
if (counter[62] == 0 && counter[63] == 0)
|
||||
return;
|
||||
|
||||
r = temp;
|
||||
w = (unsigned char *) buf;
|
||||
reset = 1;
|
||||
escapes = 0;
|
||||
/* check a block for esc chars */
|
||||
while (*r) {
|
||||
if (reset == 0 && escapes == 0 && (
|
||||
r[0] == esc[0] || r[1] == esc[0] ||r[2] == esc[0] ||r[2] == esc[0] ||
|
||||
r[0] == esc[1] || r[1] == esc[1] ||r[2] == esc[1] ||r[2] == esc[1])) {
|
||||
/* last set of escape chars were unused.
|
||||
* if we reset last escape switch then maybe we dont have to switch now */
|
||||
|
||||
/* change the latest escape switch to 999 (RESET) */
|
||||
e[1] = MODE;
|
||||
e[2] = MODE;
|
||||
|
||||
/* store default esc chars */
|
||||
esc[0] = P62;
|
||||
esc[1] = P63;
|
||||
|
||||
reset = 1;
|
||||
}
|
||||
/* these two if blocks can not be combined because a block can contain both
|
||||
* char 9 and/or . and the current escape chars. */
|
||||
if (r[0] == esc[0] || r[1] == esc[0] ||r[2] == esc[0] ||r[2] == esc[0] ||
|
||||
r[0] == esc[1] || r[1] == esc[1] ||r[2] == esc[1] ||r[2] == esc[1]) {
|
||||
/* switch escape chars */
|
||||
escapes = 0;
|
||||
reset = 0;
|
||||
|
||||
/* find 2 suitable escape chars */
|
||||
findesc(counter, esc, REV64(r[0]), REV64(r[1]), REV64(r[2]), REV64(r[3]));
|
||||
|
||||
/* store escape switch position */
|
||||
e = w;
|
||||
|
||||
/* write new escape chars */
|
||||
*w++ = MODE;
|
||||
*w++ = esc[0];
|
||||
*w++ = esc[1];
|
||||
}
|
||||
|
||||
/* update counter on remaining chars */
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (r[i])
|
||||
counter[REV64(r[i])]--;
|
||||
}
|
||||
|
||||
/* do the escaping */
|
||||
for (i = 0; i < 4; i++) {
|
||||
if (r[i] == P62) {
|
||||
r[i] = esc[0];
|
||||
escapes++;
|
||||
} else if (r[i] == P63) {
|
||||
r[i] = esc[1];
|
||||
escapes++;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy back to buf */
|
||||
*w++ = *r++;
|
||||
*w++ = *r++;
|
||||
*w++ = *r++;
|
||||
*w++ = *r++;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
base64_encode(char *buf, size_t *buflen, const void *data, size_t size)
|
||||
{
|
||||
size_t newsize;
|
||||
size_t maxsize;
|
||||
unsigned char c;
|
||||
unsigned char *s;
|
||||
unsigned char *p;
|
||||
unsigned char *q;
|
||||
int i;
|
||||
|
||||
memset(buf, 0, *buflen);
|
||||
|
||||
if (!reverse_init) {
|
||||
for (i = 0; i < 64; i++) {
|
||||
c = cb64[i];
|
||||
rev64[(int) c] = i;
|
||||
}
|
||||
reverse_init = 1;
|
||||
}
|
||||
|
||||
/* how many chars can we encode within the buf */
|
||||
maxsize = 3 * (*buflen / 4 - 1) - 1;
|
||||
/* how big will the encoded data be */
|
||||
newsize = 4 * (size / 3 + 1) + 1;
|
||||
/* if the buffer is too small, eat some of the data */
|
||||
if (*buflen < newsize) {
|
||||
size = maxsize;
|
||||
}
|
||||
|
||||
p = s = (unsigned char *) buf;
|
||||
q = (unsigned char *)data;
|
||||
|
||||
for(i=0;i<size;i+=3) {
|
||||
p[0] = cb64[((q[0] & 0xfc) >> 2)];
|
||||
p[1] = cb64[(((q[0] & 0x03) << 4) | ((q[1] & 0xf0) >> 4))];
|
||||
p[2] = (i+1 < size) ? cb64[((q[1] & 0x0f) << 2 ) | ((q[2] & 0xc0) >> 6)] : '\0';
|
||||
p[3] = (i+2 < size) ? cb64[(q[2] & 0x3f)] : '\0';
|
||||
|
||||
q += 3;
|
||||
p += 4;
|
||||
}
|
||||
*p = 0;
|
||||
|
||||
escape_chars(buf, *buflen);
|
||||
|
||||
/* store number of bytes from data that was used */
|
||||
*buflen = size;
|
||||
|
||||
return strlen(buf) - 1;
|
||||
}
|
||||
|
||||
#define DECODE_ERROR 0xffffffff
|
||||
|
||||
static int
|
||||
decode_token(const unsigned char *t, unsigned char *data, size_t len)
|
||||
{
|
||||
if (len < 2)
|
||||
return 0;
|
||||
|
||||
data[0] = ((REV64(t[0]) & 0x3f) << 2) |
|
||||
((REV64(t[1]) & 0x30) >> 4);
|
||||
|
||||
if (len < 3)
|
||||
return 1;
|
||||
|
||||
data[1] = ((REV64(t[1]) & 0x0f) << 4) |
|
||||
((REV64(t[2]) & 0x3c) >> 2);
|
||||
|
||||
if (len < 4)
|
||||
return 2;
|
||||
|
||||
data[2] = ((REV64(t[2]) & 0x03) << 6) |
|
||||
(REV64(t[3]) & 0x3f);
|
||||
|
||||
return 3;
|
||||
}
|
||||
|
||||
int
|
||||
base64_decode(void *buf, size_t *buflen, const char *str, size_t slen)
|
||||
{
|
||||
unsigned char *q;
|
||||
size_t newsize;
|
||||
size_t maxsize;
|
||||
const char *p;
|
||||
unsigned char c;
|
||||
unsigned char block[4];
|
||||
unsigned char prot62;
|
||||
unsigned char prot63;
|
||||
int len;
|
||||
int i;
|
||||
|
||||
if (!reverse_init) {
|
||||
for (i = 0; i < 64; i++) {
|
||||
c = cb64[i];
|
||||
rev64[(int) c] = i;
|
||||
}
|
||||
reverse_init = 1;
|
||||
}
|
||||
|
||||
/* chars needed to decode slen */
|
||||
newsize = 3 * (slen / 4 + 1) + 1;
|
||||
/* encoded chars that fit in buf */
|
||||
maxsize = 4 * (*buflen / 3 + 1) + 1;
|
||||
/* if the buffer is too small, eat some of the data */
|
||||
if (*buflen < newsize) {
|
||||
slen = maxsize;
|
||||
}
|
||||
|
||||
prot62 = P62;
|
||||
prot63 = P63;
|
||||
|
||||
q = buf;
|
||||
for (p = str; *p; p += 4) {
|
||||
/* handle escape instructions */
|
||||
if (*p == MODE) {
|
||||
p++;
|
||||
if (p[0] == MODE && p[1] == MODE) {
|
||||
/* reset escape chars */
|
||||
prot62 = P62;
|
||||
prot63 = P63;
|
||||
|
||||
p += 2;
|
||||
} else {
|
||||
prot62 = *p++;
|
||||
prot63 = *p++;
|
||||
}
|
||||
}
|
||||
/* since the str is const, we unescape in another buf */
|
||||
for (i = 0; i < 4; i++) {
|
||||
block[i] = p[i];
|
||||
if (prot62 == block[i]) {
|
||||
block[i] = P62;
|
||||
} else if (prot63 == block[i]) {
|
||||
block[i] = P63;
|
||||
}
|
||||
}
|
||||
len = decode_token(block, (unsigned char *) q, slen);
|
||||
q += len;
|
||||
slen -= 4;
|
||||
|
||||
if (len < 3)
|
||||
break;
|
||||
}
|
||||
*q = '\0';
|
||||
|
||||
return q - (unsigned char *) buf;
|
||||
}
|
||||
|
25
src/base64.h
Normal file
25
src/base64.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and 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.
|
||||
*/
|
||||
|
||||
#ifndef __BASE64_H__
|
||||
#define __BASE64_H__
|
||||
|
||||
struct encoder *get_base64_encoder(void);
|
||||
int base64_handles_dots();
|
||||
int base64_encode(char *, size_t *, const void *, size_t);
|
||||
int base64_decode(void *, size_t *, const char *, size_t);
|
||||
|
||||
#endif
|
35
src/common.c
35
src/common.c
|
@ -29,6 +29,7 @@
|
|||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include <termios.h>
|
||||
|
||||
#include "common.h"
|
||||
|
||||
|
@ -71,13 +72,11 @@ close_dns(int fd)
|
|||
void
|
||||
do_chroot(char *newroot)
|
||||
{
|
||||
if (newroot) {
|
||||
if (chroot(newroot) != 0 || chdir("/") != 0)
|
||||
err(1, "%s", newroot);
|
||||
if (chroot(newroot) != 0 || chdir("/") != 0)
|
||||
err(1, "%s", newroot);
|
||||
|
||||
seteuid(geteuid());
|
||||
setuid(getuid());
|
||||
}
|
||||
seteuid(geteuid());
|
||||
setuid(getuid());
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -88,3 +87,27 @@ do_detach()
|
|||
umask(0);
|
||||
alarm(0);
|
||||
}
|
||||
|
||||
void
|
||||
read_password(char *buf, size_t len)
|
||||
{
|
||||
struct termios old;
|
||||
struct termios tp;
|
||||
char pwd[80];
|
||||
|
||||
tcgetattr(0, &tp);
|
||||
old = tp;
|
||||
|
||||
tp.c_lflag &= (~ECHO);
|
||||
tcsetattr(0, TCSANOW, &tp);
|
||||
|
||||
printf("Enter password: ");
|
||||
fflush(stdout);
|
||||
scanf("%79s", pwd);
|
||||
printf("\n");
|
||||
|
||||
tcsetattr(0, TCSANOW, &old);
|
||||
|
||||
strncpy(buf, pwd, len);
|
||||
buf[len-1] = '\0';
|
||||
}
|
||||
|
|
14
src/common.h
14
src/common.h
|
@ -17,6 +17,7 @@
|
|||
#ifndef __COMMON_H__
|
||||
#define __COMMON_H__
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
|
@ -27,15 +28,18 @@
|
|||
#define MAX(a,b) ((a)>(b)?(a):(b))
|
||||
#endif
|
||||
|
||||
#define QUERY_NAME_SIZE 256
|
||||
|
||||
struct packet
|
||||
{
|
||||
int len;
|
||||
int offset;
|
||||
char data[64*1024];
|
||||
int len; /* Total packet length */
|
||||
int sentlen; /* Length of chunk currently transmitted */
|
||||
int offset; /* Current offset */
|
||||
char data[64*1024]; /* The data */
|
||||
};
|
||||
|
||||
struct query {
|
||||
char name[258];
|
||||
char name[QUERY_NAME_SIZE];
|
||||
short type;
|
||||
short id;
|
||||
struct sockaddr from;
|
||||
|
@ -48,4 +52,6 @@ void close_dns(int);
|
|||
void do_chroot(char *);
|
||||
void do_detach();
|
||||
|
||||
void read_password(char*, size_t);
|
||||
|
||||
#endif
|
||||
|
|
61
src/dns.c
61
src/dns.c
|
@ -61,7 +61,7 @@ dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_
|
|||
|
||||
name = 0xc000 | ((p - buf) & 0x3fff);
|
||||
|
||||
putname(&p, 256, q->name);
|
||||
putname(&p, sizeof(q->name), q->name);
|
||||
|
||||
putshort(&p, q->type);
|
||||
putshort(&p, C_IN);
|
||||
|
@ -78,7 +78,7 @@ dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_
|
|||
header->qdcount = htons(1);
|
||||
header->arcount = htons(1);
|
||||
|
||||
putname(&p, 256, data);
|
||||
putname(&p, datalen, data);
|
||||
|
||||
putshort(&p, q->type);
|
||||
putshort(&p, C_IN);
|
||||
|
@ -101,11 +101,11 @@ dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_
|
|||
int
|
||||
dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, size_t packetlen)
|
||||
{
|
||||
char name[QUERY_NAME_SIZE];
|
||||
char rdata[4*1024];
|
||||
HEADER *header;
|
||||
short qdcount;
|
||||
short ancount;
|
||||
char name[255];
|
||||
uint32_t ttl;
|
||||
short class;
|
||||
short type;
|
||||
|
@ -137,7 +137,29 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz
|
|||
switch (qr) {
|
||||
case QR_ANSWER:
|
||||
if(qdcount != 1 || ancount != 1) {
|
||||
warnx("no query or answer in answer");
|
||||
switch (header->rcode) {
|
||||
case REFUSED:
|
||||
warnx("Got REFUSED as reply");
|
||||
break;
|
||||
|
||||
case NOTIMP:
|
||||
warnx("Got NOTIMP as reply");
|
||||
break;
|
||||
|
||||
case NXDOMAIN:
|
||||
warnx("Got NXDOMAIN as reply");
|
||||
break;
|
||||
|
||||
|
||||
case SERVFAIL:
|
||||
warnx("Got SERVFAIL as reply");
|
||||
break;
|
||||
|
||||
case NOERROR:
|
||||
default:
|
||||
warnx("no query or answer in answer");
|
||||
break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -167,8 +189,8 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz
|
|||
return -1;
|
||||
}
|
||||
|
||||
readname(packet, packetlen, &data, name, sizeof(name) -1);
|
||||
name[256] = 0;
|
||||
readname(packet, packetlen, &data, name, sizeof(name) - 1);
|
||||
name[sizeof(name)-1] = '\0';
|
||||
readshort(packet, &data, &type);
|
||||
readshort(packet, &data, &class);
|
||||
|
||||
|
@ -177,7 +199,8 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz
|
|||
break;
|
||||
}
|
||||
|
||||
strncpy(q->name, name, 257);
|
||||
strncpy(q->name, name, sizeof(q->name));
|
||||
q->name[sizeof(q->name) - 1] = '\0';
|
||||
q->type = type;
|
||||
q->id = id;
|
||||
|
||||
|
@ -188,27 +211,3 @@ dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, siz
|
|||
return rv;
|
||||
}
|
||||
|
||||
int
|
||||
dns_build_hostname(char *buf, size_t buflen,
|
||||
const char *data, const size_t datalen,
|
||||
const char *topdomain)
|
||||
{
|
||||
int consumed;
|
||||
int avail;
|
||||
char *b;
|
||||
|
||||
avail = MIN(0xFF, buflen) - strlen(topdomain) - 2;
|
||||
memset(buf, 0, buflen);
|
||||
b = buf;
|
||||
|
||||
consumed = encode_data(data, datalen, avail, b);
|
||||
|
||||
b += strlen(buf);
|
||||
if (*b != '.')
|
||||
*b++ = '.';
|
||||
|
||||
strncpy(b, topdomain, strlen(topdomain)+1);
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,8 +24,6 @@ typedef enum {
|
|||
QR_ANSWER = 1
|
||||
} qr_t;
|
||||
|
||||
int dns_build_hostname(char *, size_t, const char *, const size_t, const char *);
|
||||
|
||||
int dns_encode(char *, size_t, struct query *, qr_t, char *, size_t);
|
||||
int dns_decode(char *, size_t, struct query *, qr_t, char *, size_t);
|
||||
|
||||
|
|
251
src/encoding.c
251
src/encoding.c
|
@ -14,207 +14,80 @@
|
|||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
#include <string.h>
|
||||
#include "encoding.h"
|
||||
|
||||
/* For FreeBSD */
|
||||
#ifndef MIN
|
||||
#define MIN(a,b) ((a)<(b)?(a):(b))
|
||||
#endif
|
||||
|
||||
#define SPACING 63
|
||||
#define ENC_CHUNK 8
|
||||
#define RAW_CHUNK 5
|
||||
|
||||
static const char base32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ98765-";
|
||||
static const char padder[] = " 1234";
|
||||
static char reverse32[128];
|
||||
static int reverse_init = 0;
|
||||
|
||||
/* Eat 5 bytes from src, write 8 bytes to dest */
|
||||
static void
|
||||
encode_chunk(char *dest, const char *src)
|
||||
{
|
||||
unsigned char c;
|
||||
|
||||
*dest++ = base32[(*src & 0xF8) >> 3]; /* 1111 1000 first byte */
|
||||
|
||||
c = (*src++ & 0x07) << 2; /* 0000 0111 first byte */
|
||||
c |= ((*src & 0xC0) >> 6); /* 1100 0000 second byte */
|
||||
*dest++ = base32[(int) c];
|
||||
|
||||
*dest++ = base32[(*src & 0x3E) >> 1]; /* 0011 1110 second byte */
|
||||
|
||||
c = (*src++ & 0x01) << 4; /* 0000 0001 second byte */
|
||||
c |= ((*src & 0xF0) >> 4); /* 1111 0000 third byte */
|
||||
*dest++ = base32[(int) c];
|
||||
|
||||
c = (*src++ & 0x0F) << 1; /* 0000 1111 third byte */
|
||||
c |= ((*src & 0x80) >> 7); /* 1000 0000 fourth byte */
|
||||
*dest++ = base32[(int) c];
|
||||
|
||||
*dest++ = base32[(*src & 0x7C) >> 2]; /* 0111 1100 fourth byte */
|
||||
|
||||
c = (*src++ & 0x03) << 3; /* 0000 0011 fourth byte */
|
||||
c |= ((*src & 0xE0) >> 5); /* 1110 0000 fifth byte */
|
||||
*dest++ = base32[(int) c];
|
||||
|
||||
*dest++ = base32[*src++ & 0x1F]; /* 0001 1111 fifth byte */
|
||||
}
|
||||
|
||||
/* Eat 8 bytes from src, write 5 bytes to dest */
|
||||
static void
|
||||
decode_chunk(char *dest, char *src)
|
||||
{
|
||||
unsigned char c;
|
||||
int i;
|
||||
|
||||
if (!reverse_init) {
|
||||
for (i = 0; i < 32; i++) {
|
||||
c = base32[i];
|
||||
reverse32[(int) c] = i;
|
||||
}
|
||||
reverse_init = 1;
|
||||
}
|
||||
|
||||
c = reverse32[(int) *src++] << 3; /* Take bits 11111 from byte 1 */
|
||||
c |= (reverse32[(int) *src] & 0x1C) >> 2; /* Take bits 11100 from byte 2 */
|
||||
*dest++ = c;
|
||||
|
||||
c = (reverse32[(int) *src++] & 0x3) << 6; /* Take bits 00011 from byte 2 */
|
||||
c |= reverse32[(int) *src++] << 1; /* Take bits 11111 from byte 3 */
|
||||
c |= (reverse32[(int) *src] & 0x10) >> 4; /* Take bits 10000 from byte 4 */
|
||||
*dest++ = c;
|
||||
|
||||
c = (reverse32[(int) *src++] & 0xF) << 4; /* Take bits 01111 from byte 4 */
|
||||
c |= (reverse32[(int) *src] & 0x1E) >> 1; /* Take bits 11110 from byte 5 */
|
||||
*dest++ = c;
|
||||
|
||||
c = reverse32[(int) *src++] << 7; /* Take bits 00001 from byte 5 */
|
||||
c |= reverse32[(int) *src++] << 2; /* Take bits 11111 from byte 6 */
|
||||
c |= (reverse32[(int) *src] & 0x18) >> 3; /* Take bits 11000 from byte 7 */
|
||||
*dest++ = c;
|
||||
|
||||
c = (reverse32[(int) *src++] & 0x7) << 5; /* Take bits 00111 from byte 7 */
|
||||
c |= reverse32[(int) *src++]; /* Take bits 11111 from byte 8 */
|
||||
*dest++ = c;
|
||||
}
|
||||
|
||||
int
|
||||
encode_data(const char *buf, const size_t len, int space, char *dest)
|
||||
unpack_data(char *buf, size_t buflen, char *data, size_t datalen, struct encoder *enc)
|
||||
{
|
||||
int final;
|
||||
int write;
|
||||
int realwrite;
|
||||
int chunks;
|
||||
int leftovers;
|
||||
int i;
|
||||
char encoded[255];
|
||||
char padding[5];
|
||||
const char *dp;
|
||||
char *pp;
|
||||
char *ep;
|
||||
|
||||
space -= space / SPACING;
|
||||
chunks = (space - 1) / ENC_CHUNK;
|
||||
while ((chunks + 1) * ENC_CHUNK + 1 > space) {
|
||||
chunks--;
|
||||
}
|
||||
write = RAW_CHUNK * chunks;
|
||||
write = MIN(write, len); /* do not use more bytes than is available; */
|
||||
final = (write == len); /* is this the last block? */
|
||||
chunks = write / RAW_CHUNK;
|
||||
leftovers = write % RAW_CHUNK;
|
||||
|
||||
memset(encoded, 0, sizeof(encoded));
|
||||
ep = encoded;
|
||||
dp = buf;
|
||||
for (i = 0; i < chunks; i++) {
|
||||
encode_chunk(ep, dp);
|
||||
ep += ENC_CHUNK;
|
||||
dp += RAW_CHUNK;
|
||||
}
|
||||
realwrite = ENC_CHUNK * chunks;
|
||||
memset(padding, 0, sizeof(padding));
|
||||
pp = padding;
|
||||
if (leftovers) {
|
||||
pp += RAW_CHUNK - leftovers;
|
||||
memcpy(pp, dp, leftovers);
|
||||
|
||||
pp = padding;
|
||||
*ep++ = padder[leftovers];
|
||||
encode_chunk(ep, pp);
|
||||
|
||||
realwrite += ENC_CHUNK + 1; /* plus padding character */
|
||||
}
|
||||
ep = encoded;
|
||||
if (len > 0) {
|
||||
for (i = 1; i <= realwrite; i++) {
|
||||
if (i % SPACING == 0) {
|
||||
*dest++ = '.';
|
||||
}
|
||||
*dest++ = *ep++;
|
||||
}
|
||||
}
|
||||
|
||||
return write;
|
||||
if (!enc->eats_dots())
|
||||
datalen = inline_undotify(data, datalen);
|
||||
return enc->decode(buf, &buflen, data, datalen);
|
||||
}
|
||||
|
||||
int
|
||||
decode_data(char *dest, int size, const char *src, char *srcend)
|
||||
int
|
||||
inline_dotify(char *buf, size_t buflen)
|
||||
{
|
||||
int len;
|
||||
int i;
|
||||
int chunks;
|
||||
int padded;
|
||||
char encoded[255];
|
||||
char padding[5];
|
||||
int enclen;
|
||||
char *pp;
|
||||
char *ep;
|
||||
unsigned dots;
|
||||
unsigned pos;
|
||||
unsigned total;
|
||||
char *reader, *writer;
|
||||
|
||||
memset(encoded, 0, sizeof(encoded));
|
||||
memset(dest, 0, size);
|
||||
total = strlen(buf);
|
||||
dots = total / 62;
|
||||
|
||||
/* First byte is not encoded */
|
||||
*dest++ = *src++;
|
||||
len = 1;
|
||||
writer = buf;
|
||||
writer += total;
|
||||
writer += dots;
|
||||
|
||||
ep = encoded;
|
||||
enclen = 0;
|
||||
while(enclen < sizeof(encoded) && src < srcend) {
|
||||
if(*src == '.') {
|
||||
src++;
|
||||
total += dots;
|
||||
if (strlen(buf) + dots > buflen) {
|
||||
writer = buf;
|
||||
writer += buflen;
|
||||
total = buflen;
|
||||
}
|
||||
|
||||
reader = writer - dots;
|
||||
pos = (unsigned) (reader - buf) + 1;
|
||||
|
||||
while (dots) {
|
||||
if (pos % 62 == 0) {
|
||||
*writer-- = '.';
|
||||
dots--;
|
||||
}
|
||||
*writer-- = *reader--;
|
||||
pos--;
|
||||
}
|
||||
|
||||
/* return new length of string */
|
||||
return total;
|
||||
}
|
||||
|
||||
int
|
||||
inline_undotify(char *buf, size_t len)
|
||||
{
|
||||
unsigned pos;
|
||||
unsigned dots;
|
||||
char *reader, *writer;
|
||||
|
||||
writer = buf;
|
||||
reader = writer;
|
||||
|
||||
pos = 0;
|
||||
dots = 0;
|
||||
|
||||
while (pos < len) {
|
||||
if (*reader == '.') {
|
||||
reader++;
|
||||
pos++;
|
||||
dots++;
|
||||
continue;
|
||||
}
|
||||
|
||||
*ep++ = *src++;
|
||||
enclen++;
|
||||
*writer++ = *reader++;
|
||||
pos++;
|
||||
}
|
||||
chunks = enclen / 8;
|
||||
padded = enclen % 8;
|
||||
|
||||
ep = encoded;
|
||||
for (i = 0; i < chunks-1; i++) {
|
||||
decode_chunk(dest, ep);
|
||||
dest += RAW_CHUNK;
|
||||
ep += ENC_CHUNK;
|
||||
len += RAW_CHUNK;
|
||||
}
|
||||
/* Read last chunk */
|
||||
if (padded) {
|
||||
pp = padding;
|
||||
padded = *ep++ - '0';
|
||||
decode_chunk(pp, ep);
|
||||
pp += RAW_CHUNK - padded;
|
||||
memcpy(dest, pp, padded);
|
||||
len += padded;
|
||||
} else {
|
||||
decode_chunk(dest, ep);
|
||||
len += RAW_CHUNK;
|
||||
}
|
||||
|
||||
return len;
|
||||
|
||||
/* return new length of string */
|
||||
return len - dots;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,17 @@
|
|||
#ifndef _ENCODING_H_
|
||||
#define _ENCODING_H_
|
||||
|
||||
int encode_data(const char *, const size_t, int, char *);
|
||||
int decode_data(char *, int, const char *, char *);
|
||||
struct encoder {
|
||||
char name[8];
|
||||
int (*encode) (char *, size_t *, const void *, size_t);
|
||||
int (*decode) (void *, size_t *, const char *, size_t);
|
||||
int (*places_dots) (void);
|
||||
int (*eats_dots) (void);
|
||||
};
|
||||
|
||||
int unpack_data(char *, size_t, char *, size_t, struct encoder *);
|
||||
int inline_dotify(char *, size_t);
|
||||
int inline_undotify(char *, size_t);
|
||||
|
||||
|
||||
#endif /* _ENCODING_H_ */
|
||||
|
|
325
src/iodine.c
325
src/iodine.c
|
@ -23,6 +23,7 @@
|
|||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#include <err.h>
|
||||
|
@ -35,6 +36,8 @@
|
|||
#endif
|
||||
|
||||
#include "common.h"
|
||||
#include "encoding.h"
|
||||
#include "base32.h"
|
||||
#include "dns.h"
|
||||
#include "login.h"
|
||||
#include "tun.h"
|
||||
|
@ -42,23 +45,37 @@
|
|||
|
||||
static void send_ping(int fd);
|
||||
static void send_chunk(int fd);
|
||||
static int build_hostname(char *buf, size_t buflen,
|
||||
const char *data, const size_t datalen,
|
||||
const char *topdomain, struct encoder *encoder);
|
||||
|
||||
int running = 1;
|
||||
char password[33];
|
||||
static int running = 1;
|
||||
static char password[33];
|
||||
|
||||
struct sockaddr_in peer;
|
||||
static struct sockaddr_in nameserv;
|
||||
static char *topdomain;
|
||||
|
||||
uint16_t rand_seed;
|
||||
static uint16_t rand_seed;
|
||||
|
||||
/* Current IP packet */
|
||||
static char activepacket[4096];
|
||||
static struct packet packet;
|
||||
|
||||
/* My userid at the server */
|
||||
static char userid;
|
||||
static int lastlen;
|
||||
static int packetpos;
|
||||
static int packetlen;
|
||||
|
||||
/* DNS id for next packet */
|
||||
static uint16_t chunkid;
|
||||
|
||||
/* Base32 encoder used for non-data packets */
|
||||
static struct encoder *b32;
|
||||
|
||||
/* The encoder used for data packets
|
||||
* Defaults to Base32, can be changed after handshake */
|
||||
static struct encoder *dataenc;
|
||||
|
||||
/* result of case preservation check done after login */
|
||||
static int case_preserved;
|
||||
|
||||
static void
|
||||
sighandler(int sig)
|
||||
{
|
||||
|
@ -66,57 +83,96 @@ sighandler(int sig)
|
|||
}
|
||||
|
||||
static void
|
||||
send_packet(int fd, char cmd, const char *data, const size_t datalen)
|
||||
send_query(int fd, char *hostname)
|
||||
{
|
||||
char packet[4096];
|
||||
struct query q;
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
|
||||
q.id = ++chunkid;
|
||||
q.type = T_NULL;
|
||||
|
||||
len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(hostname));
|
||||
|
||||
sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, sizeof(nameserv));
|
||||
}
|
||||
|
||||
static void
|
||||
send_packet(int fd, char cmd, const char *data, const size_t datalen)
|
||||
{
|
||||
char buf[4096];
|
||||
|
||||
buf[0] = cmd;
|
||||
|
||||
len = dns_build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain);
|
||||
len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, buf, strlen(buf));
|
||||
build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain, b32);
|
||||
send_query(fd, buf);
|
||||
}
|
||||
|
||||
sendto(fd, packet, len, 0, (struct sockaddr*)&peer, sizeof(peer));
|
||||
static int
|
||||
build_hostname(char *buf, size_t buflen,
|
||||
const char *data, const size_t datalen,
|
||||
const char *topdomain, struct encoder *encoder)
|
||||
{
|
||||
int encsize;
|
||||
size_t space;
|
||||
char *b;
|
||||
|
||||
|
||||
space = MIN(0xFF, buflen) - strlen(topdomain) - 2;
|
||||
if (!encoder->places_dots())
|
||||
space -= (space / 62); /* space for dots */
|
||||
|
||||
memset(buf, 0, buflen);
|
||||
|
||||
encsize = encoder->encode(buf, &space, data, datalen);
|
||||
|
||||
if (!encoder->places_dots())
|
||||
inline_dotify(buf, buflen);
|
||||
|
||||
b = buf;
|
||||
b += strlen(buf);
|
||||
|
||||
if (*b != '.')
|
||||
*b++ = '.';
|
||||
|
||||
strncpy(b, topdomain, strlen(topdomain)+1);
|
||||
|
||||
return space;
|
||||
}
|
||||
|
||||
int
|
||||
is_sending()
|
||||
{
|
||||
return (packetlen != 0);
|
||||
return (packet.len != 0);
|
||||
}
|
||||
|
||||
int
|
||||
read_dns(int fd, char *buf, int buflen)
|
||||
{
|
||||
struct sockaddr_in from;
|
||||
char packet[64*1024];
|
||||
char data[64*1024];
|
||||
socklen_t addrlen;
|
||||
struct query q;
|
||||
int rv;
|
||||
int r;
|
||||
|
||||
addrlen = sizeof(struct sockaddr);
|
||||
if ((r = recvfrom(fd, packet, sizeof(packet), 0,
|
||||
(struct sockaddr*)&from, &addrlen)) == -1) {
|
||||
if ((r = recvfrom(fd, data, sizeof(data), 0,
|
||||
(struct sockaddr*)&from, &addrlen)) == -1) {
|
||||
warn("recvfrom");
|
||||
return 0;
|
||||
}
|
||||
|
||||
rv = dns_decode(buf, buflen, &q, QR_ANSWER, packet, r);
|
||||
rv = dns_decode(buf, buflen, &q, QR_ANSWER, data, r);
|
||||
|
||||
if (is_sending() && chunkid == q.id) {
|
||||
/* Got ACK on sent packet */
|
||||
packetpos += lastlen;
|
||||
if (packetpos == packetlen) {
|
||||
packet.offset += packet.sentlen;
|
||||
if (packet.offset == packet.len) {
|
||||
/* Packet completed */
|
||||
packetpos = 0;
|
||||
packetlen = 0;
|
||||
lastlen = 0;
|
||||
packet.offset = 0;
|
||||
packet.len = 0;
|
||||
packet.sentlen = 0;
|
||||
} else {
|
||||
/* More to send */
|
||||
send_chunk(fd);
|
||||
|
@ -142,10 +198,10 @@ tunnel_tun(int tun_fd, int dns_fd)
|
|||
inlen = read;
|
||||
compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9);
|
||||
|
||||
memcpy(activepacket, out, MIN(outlen, sizeof(activepacket)));
|
||||
lastlen = 0;
|
||||
packetpos = 0;
|
||||
packetlen = outlen;
|
||||
memcpy(packet.data, out, MIN(outlen, sizeof(packet.data)));
|
||||
packet.sentlen = 0;
|
||||
packet.offset = 0;
|
||||
packet.len = outlen;
|
||||
|
||||
send_chunk(dns_fd);
|
||||
|
||||
|
@ -224,33 +280,26 @@ static void
|
|||
send_chunk(int fd)
|
||||
{
|
||||
char hex[] = "0123456789ABCDEF";
|
||||
char packet[4096];
|
||||
struct query q;
|
||||
char buf[4096];
|
||||
int avail;
|
||||
int code;
|
||||
char *p;
|
||||
int len;
|
||||
|
||||
q.id = ++chunkid;
|
||||
q.type = T_NULL;
|
||||
p = packet.data;
|
||||
p += packet.offset;
|
||||
avail = packet.len - packet.offset;
|
||||
|
||||
p = activepacket;
|
||||
p += packetpos;
|
||||
avail = packetlen - packetpos;
|
||||
packet.sentlen = build_hostname(buf + 1, sizeof(buf) - 1, p, avail, topdomain, dataenc);
|
||||
|
||||
lastlen = dns_build_hostname(buf + 1, sizeof(buf) - 1, p, avail, topdomain);
|
||||
|
||||
if (lastlen == avail)
|
||||
if (packet.sentlen == avail)
|
||||
code = 1;
|
||||
else
|
||||
code = 0;
|
||||
|
||||
code |= (userid << 1);
|
||||
buf[0] = hex[code];
|
||||
len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, buf, strlen(buf));
|
||||
|
||||
sendto(fd, packet, len, 0, (struct sockaddr*)&peer, sizeof(peer));
|
||||
send_query(fd, buf);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -276,9 +325,9 @@ send_ping(int fd)
|
|||
char data[3];
|
||||
|
||||
if (is_sending()) {
|
||||
lastlen = 0;
|
||||
packetpos = 0;
|
||||
packetlen = 0;
|
||||
packet.sentlen = 0;
|
||||
packet.offset = 0;
|
||||
packet.len = 0;
|
||||
}
|
||||
|
||||
data[0] = userid;
|
||||
|
@ -308,6 +357,15 @@ send_version(int fd, uint32_t version)
|
|||
send_packet(fd, 'V', data, sizeof(data));
|
||||
}
|
||||
|
||||
void
|
||||
send_case_check(int fd)
|
||||
{
|
||||
char buf[512] = "zZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyY123-4560789.";
|
||||
|
||||
strncat(buf, topdomain, 512 - strlen(buf));
|
||||
send_query(fd, buf);
|
||||
}
|
||||
|
||||
static int
|
||||
handshake(int dns_fd)
|
||||
{
|
||||
|
@ -339,15 +397,15 @@ handshake(int dns_fd)
|
|||
read = read_dns(dns_fd, in, sizeof(in));
|
||||
|
||||
if(read < 0) {
|
||||
perror("read");
|
||||
warn("handshake read");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read >= 9) {
|
||||
payload = (((in[4] & 0xff) << 24) |
|
||||
((in[5] & 0xff) << 16) |
|
||||
((in[6] & 0xff) << 8) |
|
||||
((in[7] & 0xff)));
|
||||
((in[5] & 0xff) << 16) |
|
||||
((in[6] & 0xff) << 8) |
|
||||
((in[7] & 0xff)));
|
||||
|
||||
if (strncmp("VACK", in, 4) == 0) {
|
||||
seed = payload;
|
||||
|
@ -405,7 +463,7 @@ perform_login:
|
|||
client[64] = 0;
|
||||
if (tun_setip(client) == 0 &&
|
||||
tun_setmtu(mtu) == 0) {
|
||||
return 0;
|
||||
goto perform_case_check;
|
||||
} else {
|
||||
warnx("Received handshake with bad data");
|
||||
}
|
||||
|
@ -417,23 +475,98 @@ perform_login:
|
|||
|
||||
printf("Retrying login...\n");
|
||||
}
|
||||
errx(1, "couldn't login to server");
|
||||
/* NOTREACHED */
|
||||
|
||||
return 1;
|
||||
perform_case_check:
|
||||
case_preserved = 0;
|
||||
for (i=0; running && i<5 ;i++) {
|
||||
tv.tv_sec = i + 1;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
send_case_check(dns_fd);
|
||||
|
||||
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) {
|
||||
warn("read");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (read > 0) {
|
||||
if (in[0] == 'z' || in[0] == 'Z') {
|
||||
if (read < (26 * 2)) {
|
||||
printf("Received short case reply...\n");
|
||||
} else {
|
||||
int k;
|
||||
|
||||
case_preserved = 1;
|
||||
for (k = 0; k < 26 && case_preserved; k += 2) {
|
||||
if (in[k] == in[k+1]) {
|
||||
/* test string: zZaAbBcCdD... */
|
||||
case_preserved = 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
printf("Received bad case check reply\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("Retrying case check...\n");
|
||||
}
|
||||
|
||||
printf("No reply on case check, continuing\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
get_resolvconf_addr()
|
||||
{
|
||||
static char addr[16];
|
||||
char buf[80];
|
||||
char *rv;
|
||||
FILE *fp;
|
||||
|
||||
rv = NULL;
|
||||
|
||||
if ((fp = fopen("/etc/resolv.conf", "r")) == NULL)
|
||||
err(1, "/etc/resolve.conf");
|
||||
|
||||
while (feof(fp) == 0) {
|
||||
fgets(buf, sizeof(buf), fp);
|
||||
|
||||
if (sscanf(buf, "nameserver %15s", addr) == 1) {
|
||||
rv = addr;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
static void
|
||||
set_target(const char *host)
|
||||
static void
|
||||
set_nameserver(const char *cp)
|
||||
{
|
||||
struct hostent *h;
|
||||
struct in_addr addr;
|
||||
|
||||
h = gethostbyname(host);
|
||||
if (!h)
|
||||
err(1, "couldn't resolve name %s", host);
|
||||
if (inet_aton(cp, &addr) != 1)
|
||||
errx(1, "error parsing nameserver address: '%s'", cp);
|
||||
|
||||
memset(&peer, 0, sizeof(peer));
|
||||
peer.sin_family = AF_INET;
|
||||
peer.sin_port = htons(53);
|
||||
peer.sin_addr = *((struct in_addr *) h->h_addr);
|
||||
memset(&nameserv, 0, sizeof(nameserv));
|
||||
nameserv.sin_family = AF_INET;
|
||||
nameserv.sin_port = htons(53);
|
||||
nameserv.sin_addr = addr;
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -441,7 +574,7 @@ usage() {
|
|||
extern char *__progname;
|
||||
|
||||
printf("Usage: %s [-v] [-h] [-f] [-u user] [-t chrootdir] [-d device] "
|
||||
"nameserver topdomain\n", __progname);
|
||||
"[nameserver] topdomain\n", __progname);
|
||||
exit(2);
|
||||
}
|
||||
|
||||
|
@ -451,7 +584,7 @@ help() {
|
|||
|
||||
printf("iodine IP over DNS tunneling client\n");
|
||||
printf("Usage: %s [-v] [-h] [-f] [-u user] [-t chrootdir] [-d device] "
|
||||
"[-P password] nameserver topdomain\n", __progname);
|
||||
"[-P password] [nameserver] topdomain\n", __progname);
|
||||
printf(" -v to print version info and exit\n");
|
||||
printf(" -h to print this help and exit\n");
|
||||
printf(" -f to keep running in foreground\n");
|
||||
|
@ -459,7 +592,7 @@ help() {
|
|||
printf(" -t dir to chroot to directory dir\n");
|
||||
printf(" -d device to set tunnel device name\n");
|
||||
printf(" -P password used for authentication (max 32 chars will be used)\n");
|
||||
printf("nameserver is the IP number of the relaying nameserver\n");
|
||||
printf("nameserver is the IP number of the relaying nameserver, if absent /etc/resolv.conf is used\n");
|
||||
printf("topdomain is the FQDN that is delegated to the tunnel endpoint.\n");
|
||||
|
||||
exit(0);
|
||||
|
@ -469,7 +602,7 @@ static void
|
|||
version() {
|
||||
|
||||
printf("iodine IP over DNS tunneling client\n");
|
||||
printf("version: 0.4.0 from 2007-03-25\n");
|
||||
printf("version: 0.4.1 from 2007-11-30\n");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
@ -477,6 +610,7 @@ version() {
|
|||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
char *nameserv_addr;
|
||||
struct passwd *pw;
|
||||
char *username;
|
||||
int foreground;
|
||||
|
@ -492,6 +626,9 @@ main(int argc, char **argv)
|
|||
newroot = NULL;
|
||||
device = NULL;
|
||||
chunkid = 0;
|
||||
|
||||
b32 = get_base32_encoder();
|
||||
dataenc = get_base32_encoder();
|
||||
|
||||
while ((choice = getopt(argc, argv, "vfhu:t:d:P:")) != -1) {
|
||||
switch(choice) {
|
||||
|
@ -514,8 +651,11 @@ main(int argc, char **argv)
|
|||
device = optarg;
|
||||
break;
|
||||
case 'P':
|
||||
strncpy(password, optarg, 32);
|
||||
password[32] = 0;
|
||||
strncpy(password, optarg, sizeof(password));
|
||||
password[sizeof(password)-1] = 0;
|
||||
|
||||
/* XXX: find better way of cleaning up ps(1) */
|
||||
memset(optarg, 0, strlen(optarg));
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
|
@ -524,37 +664,48 @@ main(int argc, char **argv)
|
|||
}
|
||||
|
||||
if (geteuid() != 0) {
|
||||
printf("Run as root and you'll be happy.\n");
|
||||
warnx("Run as root and you'll be happy.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
if (argc != 2)
|
||||
|
||||
switch (argc) {
|
||||
case 1:
|
||||
nameserv_addr = get_resolvconf_addr();
|
||||
topdomain = strdup(argv[0]);
|
||||
break;
|
||||
case 2:
|
||||
nameserv_addr = argv[0];
|
||||
topdomain = strdup(argv[1]);
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
/* NOTREACHED */
|
||||
}
|
||||
|
||||
topdomain = strdup(argv[1]);
|
||||
set_nameserver(nameserv_addr);
|
||||
|
||||
if(username) {
|
||||
pw = getpwnam(username);
|
||||
if (!pw) {
|
||||
printf("User %s does not exist!\n", username);
|
||||
if (strlen(topdomain) > 128 || topdomain[0] == '.') {
|
||||
warnx("Use a topdomain max 128 chars long. Do not start it with a dot.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (username != NULL) {
|
||||
if ((pw = getpwnam(username)) == NULL) {
|
||||
warnx("User %s does not exist!\n", username);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (strlen(password) == 0) {
|
||||
printf("Enter password on stdin:\n");
|
||||
scanf("%32s", password);
|
||||
password[32] = 0;
|
||||
}
|
||||
if (strlen(password) == 0)
|
||||
read_password(password, sizeof(password));
|
||||
|
||||
if ((tun_fd = open_tun(device)) == -1)
|
||||
goto cleanup1;
|
||||
if ((dns_fd = open_dns(0, INADDR_ANY)) == -1)
|
||||
goto cleanup2;
|
||||
set_target(argv[0]);
|
||||
|
||||
signal(SIGINT, sighandler);
|
||||
signal(SIGTERM, sighandler);
|
||||
|
@ -562,21 +713,21 @@ main(int argc, char **argv)
|
|||
if(handshake(dns_fd))
|
||||
goto cleanup2;
|
||||
|
||||
printf("Sending queries for %s to %s\n", argv[1], argv[0]);
|
||||
printf("Sending queries for %s to %s\n", topdomain, nameserv_addr);
|
||||
|
||||
do_chroot(newroot);
|
||||
if (foreground == 0)
|
||||
do_detach();
|
||||
|
||||
if (newroot != NULL)
|
||||
do_chroot(newroot);
|
||||
|
||||
if (username) {
|
||||
if (username != NULL) {
|
||||
if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
|
||||
printf("Could not switch to user %s!\n", username);
|
||||
warnx("Could not switch to user %s!\n", username);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!foreground) {
|
||||
do_detach();
|
||||
}
|
||||
|
||||
tunnel(tun_fd, dns_fd);
|
||||
|
||||
cleanup2:
|
||||
|
|
165
src/iodined.c
165
src/iodined.c
|
@ -21,6 +21,7 @@
|
|||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#include <err.h>
|
||||
|
@ -34,20 +35,20 @@
|
|||
|
||||
#include "common.h"
|
||||
#include "dns.h"
|
||||
#include "encoding.h"
|
||||
#include "base32.h"
|
||||
#include "user.h"
|
||||
#include "login.h"
|
||||
#include "tun.h"
|
||||
#include "encoding.h"
|
||||
#include "version.h"
|
||||
|
||||
int running = 1;
|
||||
static int running = 1;
|
||||
static char *topdomain;
|
||||
static char password[33];
|
||||
static struct encoder *b32;
|
||||
|
||||
char *topdomain;
|
||||
|
||||
char password[33];
|
||||
|
||||
int my_mtu;
|
||||
in_addr_t my_ip;
|
||||
static int my_mtu;
|
||||
static in_addr_t my_ip;
|
||||
|
||||
static int read_dns(int, struct query *, char *, int);
|
||||
static void write_dns(int, struct query *, char *, int);
|
||||
|
@ -133,6 +134,7 @@ tunnel_dns(int tun_fd, int dns_fd)
|
|||
char logindata[16];
|
||||
char out[64*1024];
|
||||
char in[64*1024];
|
||||
char unpacked[64*1024];
|
||||
char *tmp[2];
|
||||
int userid;
|
||||
int touser;
|
||||
|
@ -145,37 +147,37 @@ tunnel_dns(int tun_fd, int dns_fd)
|
|||
return 0;
|
||||
|
||||
if(in[0] == 'V' || in[0] == 'v') {
|
||||
read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), read - 1, b32);
|
||||
/* Version greeting, compare and send ack/nak */
|
||||
if (read > 4) {
|
||||
/* Received V + 32bits version */
|
||||
version = (((unpacked[0] & 0xff) << 24) |
|
||||
((unpacked[1] & 0xff) << 16) |
|
||||
((unpacked[2] & 0xff) << 8) |
|
||||
((unpacked[3] & 0xff)));
|
||||
}
|
||||
|
||||
version = (((in[1] & 0xff) << 24) |
|
||||
((in[2] & 0xff) << 16) |
|
||||
((in[3] & 0xff) << 8) |
|
||||
((in[4] & 0xff)));
|
||||
|
||||
if (version == VERSION) {
|
||||
userid = find_available_user();
|
||||
if (userid >= 0) {
|
||||
users[userid].seed = rand();
|
||||
memcpy(&(users[userid].host), &(dummy.q.from), dummy.q.fromlen);
|
||||
memcpy(&(users[userid].q), &(dummy.q), sizeof(struct query));
|
||||
users[userid].addrlen = dummy.q.fromlen;
|
||||
send_version_response(dns_fd, VERSION_ACK, users[userid].seed, &users[userid]);
|
||||
users[userid].q.id = 0;
|
||||
} else {
|
||||
/* No space for another user */
|
||||
send_version_response(dns_fd, VERSION_FULL, USERS, &dummy);
|
||||
}
|
||||
if (version == VERSION) {
|
||||
userid = find_available_user();
|
||||
if (userid >= 0) {
|
||||
users[userid].seed = rand();
|
||||
memcpy(&(users[userid].host), &(dummy.q.from), dummy.q.fromlen);
|
||||
memcpy(&(users[userid].q), &(dummy.q), sizeof(struct query));
|
||||
users[userid].addrlen = dummy.q.fromlen;
|
||||
users[userid].encoder = get_base32_encoder();
|
||||
send_version_response(dns_fd, VERSION_ACK, users[userid].seed, &users[userid]);
|
||||
users[userid].q.id = 0;
|
||||
} else {
|
||||
send_version_response(dns_fd, VERSION_NACK, VERSION, &dummy);
|
||||
/* No space for another user */
|
||||
send_version_response(dns_fd, VERSION_FULL, USERS, &dummy);
|
||||
}
|
||||
} else {
|
||||
send_version_response(dns_fd, VERSION_NACK, VERSION, &dummy);
|
||||
}
|
||||
} else if(in[0] == 'L' || in[0] == 'l') {
|
||||
read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), read - 1, b32);
|
||||
/* Login phase, handle auth */
|
||||
userid = in[1];
|
||||
userid = unpacked[0];
|
||||
if (userid < 0 || userid >= USERS) {
|
||||
write_dns(dns_fd, &(dummy.q), "BADIP", 5);
|
||||
return 0; /* illegal id */
|
||||
|
@ -187,7 +189,7 @@ tunnel_dns(int tun_fd, int dns_fd)
|
|||
memcmp(&(users[userid].host), &(dummy.q.from), dummy.q.fromlen) != 0) {
|
||||
write_dns(dns_fd, &(dummy.q), "BADIP", 5);
|
||||
} else {
|
||||
if (read >= 18 && (memcmp(logindata, in+2, 16) == 0)) {
|
||||
if (read >= 18 && (memcmp(logindata, unpacked+1, 16) == 0)) {
|
||||
/* Login ok, send ip/mtu info */
|
||||
|
||||
tempip.s_addr = my_ip;
|
||||
|
@ -208,14 +210,21 @@ tunnel_dns(int tun_fd, int dns_fd)
|
|||
}
|
||||
}
|
||||
} else if(in[0] == 'P' || in[0] == 'p') {
|
||||
read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), read - 1, b32);
|
||||
/* Ping packet, store userid */
|
||||
userid = in[1];
|
||||
userid = unpacked[0];
|
||||
if (userid < 0 || userid >= USERS) {
|
||||
write_dns(dns_fd, &(dummy.q), "BADIP", 5);
|
||||
return 0; /* illegal id */
|
||||
}
|
||||
memcpy(&(users[userid].q), &(dummy.q), sizeof(struct query));
|
||||
users[userid].last_pkt = time(NULL);
|
||||
} else if(in[0] == 'Z' || in[0] == 'z') {
|
||||
/* Case conservation check */
|
||||
|
||||
/* Reply with received hostname as data */
|
||||
write_dns(dns_fd, &(dummy.q), in, read);
|
||||
return 0;
|
||||
} else if((in[0] >= '0' && in[0] <= '9')
|
||||
|| (in[0] >= 'a' && in[0] <= 'f')
|
||||
|| (in[0] >= 'A' && in[0] <= 'F')) {
|
||||
|
@ -237,12 +246,16 @@ tunnel_dns(int tun_fd, int dns_fd)
|
|||
memcmp(&(users[userid].host), &(dummy.q.from), dummy.q.fromlen) != 0) {
|
||||
write_dns(dns_fd, &(dummy.q), "BADIP", 5);
|
||||
} else {
|
||||
/* decode with this users encoding */
|
||||
read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), read - 1,
|
||||
users[userid].encoder);
|
||||
|
||||
users[userid].last_pkt = time(NULL);
|
||||
memcpy(&(users[userid].q), &(dummy.q), sizeof(struct query));
|
||||
users[userid].addrlen = dummy.q.fromlen;
|
||||
memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, in + 1, read - 1);
|
||||
users[userid].inpacket.len += read - 1;
|
||||
users[userid].inpacket.offset += read - 1;
|
||||
memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, unpacked, read);
|
||||
users[userid].inpacket.len += read;
|
||||
users[userid].inpacket.offset += read;
|
||||
|
||||
if (code & 1) {
|
||||
outlen = sizeof(out);
|
||||
|
@ -351,12 +364,17 @@ read_dns(int fd, struct query *q, char *buf, int buflen)
|
|||
if (r > 0) {
|
||||
dns_decode(buf, buflen, q, QR_QUERY, packet, r);
|
||||
domain = strstr(q->name, topdomain);
|
||||
rv = decode_data(buf, buflen, q->name, domain);
|
||||
q->fromlen = addrlen;
|
||||
memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen);
|
||||
} else if (r < 0) {
|
||||
if (domain) {
|
||||
rv = (int) (domain - q->name);
|
||||
memcpy(buf, q->name, MIN(rv, buflen));
|
||||
q->fromlen = addrlen;
|
||||
memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen);
|
||||
} else {
|
||||
rv = 0;
|
||||
}
|
||||
} else if (r < 0) {
|
||||
/* Error */
|
||||
perror("recvfrom");
|
||||
warn("read dns");
|
||||
rv = 0;
|
||||
}
|
||||
|
||||
|
@ -410,24 +428,24 @@ help() {
|
|||
static void
|
||||
version() {
|
||||
printf("iodine IP over DNS tunneling server\n");
|
||||
printf("version: 0.4.0 from 2007-03-25\n");
|
||||
printf("version: 0.4.1 from 2007-11-30\n");
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int choice;
|
||||
int tun_fd;
|
||||
int dnsd_fd;
|
||||
char *newroot;
|
||||
char *username;
|
||||
char *device;
|
||||
int foreground;
|
||||
int mtu;
|
||||
struct passwd *pw;
|
||||
in_addr_t listen_ip;
|
||||
struct passwd *pw;
|
||||
int foreground;
|
||||
char *username;
|
||||
char *newroot;
|
||||
char *device;
|
||||
int dnsd_fd;
|
||||
int tun_fd;
|
||||
int choice;
|
||||
int port;
|
||||
int mtu;
|
||||
|
||||
username = NULL;
|
||||
newroot = NULL;
|
||||
|
@ -437,7 +455,9 @@ main(int argc, char **argv)
|
|||
listen_ip = INADDR_ANY;
|
||||
port = 53;
|
||||
|
||||
memset(password, 0, 33);
|
||||
b32 = get_base32_encoder();
|
||||
|
||||
memset(password, 0, sizeof(password));
|
||||
srand(time(NULL));
|
||||
|
||||
while ((choice = getopt(argc, argv, "vfhu:t:d:m:l:p:P:")) != -1) {
|
||||
|
@ -474,8 +494,11 @@ main(int argc, char **argv)
|
|||
}
|
||||
break;
|
||||
case 'P':
|
||||
strncpy(password, optarg, 32);
|
||||
password[32] = 0;
|
||||
strncpy(password, optarg, sizeof(password));
|
||||
password[sizeof(password)-1] = 0;
|
||||
|
||||
/* XXX: find better way of cleaning up ps(1) */
|
||||
memset(optarg, 0, strlen(optarg));
|
||||
break;
|
||||
default:
|
||||
usage();
|
||||
|
@ -487,7 +510,7 @@ main(int argc, char **argv)
|
|||
argv += optind;
|
||||
|
||||
if (geteuid() != 0) {
|
||||
printf("Run as root and you'll be happy.\n");
|
||||
warnx("Run as root and you'll be happy.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
|
@ -495,30 +518,30 @@ main(int argc, char **argv)
|
|||
usage();
|
||||
|
||||
topdomain = strdup(argv[1]);
|
||||
if (strlen(topdomain) > 128 || topdomain[0] == '.') {
|
||||
warnx("Use a topdomain max 128 chars long. Do not start it with a dot.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (username) {
|
||||
pw = getpwnam(username);
|
||||
if (!pw) {
|
||||
printf("User %s does not exist!\n", username);
|
||||
if (username != NULL) {
|
||||
if ((pw = getpwnam(username)) == NULL) {
|
||||
warnx("User %s does not exist!\n", username);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (mtu == 0) {
|
||||
printf("Bad MTU given.\n");
|
||||
warnx("Bad MTU given.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (listen_ip == INADDR_NONE) {
|
||||
printf("Bad IP address to listen on.\n");
|
||||
warnx("Bad IP address to listen on.\n");
|
||||
usage();
|
||||
}
|
||||
|
||||
if (strlen(password) == 0) {
|
||||
printf("Enter password on stdin:\n");
|
||||
scanf("%32s", password);
|
||||
password[32] = 0;
|
||||
}
|
||||
if (strlen(password) == 0)
|
||||
read_password(password, sizeof(password));
|
||||
|
||||
if ((tun_fd = open_tun(device)) == -1)
|
||||
goto cleanup0;
|
||||
|
@ -531,22 +554,22 @@ main(int argc, char **argv)
|
|||
my_mtu = mtu;
|
||||
init_users(my_ip);
|
||||
|
||||
printf("Listening to dns for domain %s\n", argv[1]);
|
||||
printf("Listening to dns for domain %s\n", topdomain);
|
||||
|
||||
do_chroot(newroot);
|
||||
if (foreground == 0)
|
||||
do_detach();
|
||||
|
||||
if (newroot != NULL)
|
||||
do_chroot(newroot);
|
||||
|
||||
signal(SIGINT, sigint);
|
||||
if (username) {
|
||||
if (username != NULL) {
|
||||
if (setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) {
|
||||
printf("Could not switch to user %s!\n", username);
|
||||
warnx("Could not switch to user %s!\n", username);
|
||||
usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (!foreground) {
|
||||
do_detach();
|
||||
}
|
||||
|
||||
tunnel(tun_fd, dnsd_fd);
|
||||
|
||||
cleanup2:
|
||||
|
|
|
@ -47,5 +47,6 @@ login_calculate(char *buf, int buflen, char *pass, int seed)
|
|||
md5_init(&ctx);
|
||||
md5_append(&ctx, temp, 32);
|
||||
md5_finish(&ctx, (unsigned char *) buf);
|
||||
|
||||
}
|
||||
|
||||
|
|
22
src/tun.c
22
src/tun.c
|
@ -58,7 +58,9 @@ open_tun(const char *tun_device)
|
|||
|
||||
if (tun_device != NULL) {
|
||||
strncpy(ifreq.ifr_name, tun_device, IFNAMSIZ);
|
||||
ifreq.ifr_name[IFNAMSIZ-1] = '\0';
|
||||
strncpy(if_name, tun_device, sizeof(if_name));
|
||||
if_name[sizeof(if_name)-1] = '\0';
|
||||
|
||||
if (ioctl(tun_fd, TUNSETIFF, (void *) &ifreq) != -1) {
|
||||
printf("Opened %s\n", ifreq.ifr_name);
|
||||
|
@ -102,6 +104,7 @@ open_tun(const char *tun_device)
|
|||
if (tun_device != NULL) {
|
||||
snprintf(tun_name, sizeof(tun_name), "/dev/%s", tun_device);
|
||||
strncpy(if_name, tun_device, sizeof(if_name));
|
||||
if_name[sizeof(if_name)-1] = '\0';
|
||||
|
||||
if ((tun_fd = open(tun_name, O_RDWR)) < 0) {
|
||||
warn("open_tun: %s: %s", tun_name, strerror(errno));
|
||||
|
@ -140,7 +143,7 @@ close_tun(int tun_fd)
|
|||
}
|
||||
|
||||
int
|
||||
write_tun(int tun_fd, char *data, int len)
|
||||
write_tun(int tun_fd, char *data, size_t len)
|
||||
{
|
||||
#if defined (FREEBSD) || defined (DARWIN) || defined(NETBSD)
|
||||
data += 4;
|
||||
|
@ -166,8 +169,8 @@ write_tun(int tun_fd, char *data, int len)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
read_tun(int tun_fd, char *buf, int len)
|
||||
ssize_t
|
||||
read_tun(int tun_fd, char *buf, size_t len)
|
||||
{
|
||||
#if defined (FREEBSD) || defined (DARWIN) || defined(NETBSD)
|
||||
/* FreeBSD/Darwin/NetBSD has no header */
|
||||
|
@ -181,6 +184,9 @@ int
|
|||
tun_setip(const char *ip)
|
||||
{
|
||||
char cmdline[512];
|
||||
#ifndef LINUX
|
||||
int r;
|
||||
#endif
|
||||
|
||||
if (inet_addr(ip) != INADDR_NONE) {
|
||||
snprintf(cmdline, sizeof(cmdline),
|
||||
|
@ -191,8 +197,6 @@ tun_setip(const char *ip)
|
|||
|
||||
printf("Setting IP of %s to %s\n", if_name, ip);
|
||||
#ifndef LINUX
|
||||
int r;
|
||||
|
||||
r = system(cmdline);
|
||||
if(r != 0) {
|
||||
return r;
|
||||
|
@ -212,20 +216,20 @@ tun_setip(const char *ip)
|
|||
}
|
||||
|
||||
int
|
||||
tun_setmtu(const int mtu)
|
||||
tun_setmtu(const size_t mtu)
|
||||
{
|
||||
char cmdline[512];
|
||||
|
||||
if (mtu > 200 && mtu < 1500) {
|
||||
snprintf(cmdline, sizeof(cmdline),
|
||||
"/sbin/ifconfig %s mtu %d",
|
||||
"/sbin/ifconfig %s mtu %u",
|
||||
if_name,
|
||||
mtu);
|
||||
|
||||
printf("Setting MTU of %s to %d\n", if_name, mtu);
|
||||
printf("Setting MTU of %s to %u\n", if_name, mtu);
|
||||
return system(cmdline);
|
||||
} else {
|
||||
warn("MTU out of range: %d\n", mtu);
|
||||
warn("MTU out of range: %u\n", mtu);
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
|
||||
int open_tun(const char *);
|
||||
void close_tun(int);
|
||||
int write_tun(int, char *, int);
|
||||
int read_tun(int, char *, int);
|
||||
int write_tun(int, char *, size_t);
|
||||
ssize_t read_tun(int, char *, size_t);
|
||||
int tun_setip(const char *);
|
||||
int tun_setmtu(const int);
|
||||
int tun_setmtu(const size_t);
|
||||
|
||||
#endif /* _TUN_H_ */
|
||||
|
|
12
src/user.c
12
src/user.c
|
@ -14,15 +14,23 @@
|
|||
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <fcntl.h>
|
||||
#include <err.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "encoding.h"
|
||||
#include "user.h"
|
||||
|
||||
struct user users[USERS];
|
||||
|
|
|
@ -30,6 +30,7 @@ struct user {
|
|||
struct query q;
|
||||
struct packet inpacket;
|
||||
struct packet outpacket;
|
||||
struct encoder *encoder;
|
||||
};
|
||||
|
||||
extern struct user users[USERS];
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
|
||||
/* This is the version of the network protocol
|
||||
It is usually equal to the latest iodine version number */
|
||||
#define VERSION 0x00000400
|
||||
#define VERSION 0x00000402
|
||||
|
||||
#endif /* _VERSION_H_ */
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
CC = gcc
|
||||
TEST = test
|
||||
OBJS = test.o base32.o read.o dns.o encoding.o login.o user.o
|
||||
SRCOBJS = ../src/base32.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o
|
||||
OBJS = test.o base32.o base64.o read.o dns.o encoding.o login.o user.o
|
||||
SRCOBJS = ../src/base32.o ../src/base64.o ../src/read.o ../src/dns.o ../src/encoding.o ../src/login.o ../src/md5.o ../src/user.o
|
||||
|
||||
OS = `uname | tr "a-z" "A-Z"`
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -20,14 +20,16 @@
|
|||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "encoding.h"
|
||||
#include "base32.h"
|
||||
#include "test.h"
|
||||
|
||||
struct touple
|
||||
static struct tuple
|
||||
{
|
||||
char *a;
|
||||
char *b;
|
||||
} testpairs[] = {
|
||||
{ "iodinetestingtesting", "nfxwi0lomv0gk21unfxgo3dfon0gs1th" },
|
||||
{ "abc123", "mfrggmjsgm" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
@ -35,46 +37,37 @@ struct touple
|
|||
START_TEST(test_base32_encode)
|
||||
{
|
||||
size_t len;
|
||||
char *buf;
|
||||
char buf[4096];
|
||||
int val;
|
||||
int i;
|
||||
|
||||
len = 0;
|
||||
buf = NULL;
|
||||
|
||||
for (i = 0; testpairs[i].a != NULL; i++) {
|
||||
val = base32_encode(&buf, &len, testpairs[i].a, strlen(testpairs[i].a));
|
||||
len = sizeof(buf);
|
||||
val = base32_encode(buf, &len, testpairs[i].a, strlen(testpairs[i].a));
|
||||
|
||||
fail_unless(val > 0, strerror(errno));
|
||||
fail_unless(buf != NULL, "buf == NULL");
|
||||
fail_unless(strcmp(buf, testpairs[i].b) == 0,
|
||||
va_str("'%s' != '%s'", buf, testpairs[i].b));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_base32_decode)
|
||||
{
|
||||
size_t len;
|
||||
void *buf;
|
||||
char buf[4096];
|
||||
int val;
|
||||
int i;
|
||||
|
||||
len = 0;
|
||||
buf = NULL;
|
||||
|
||||
for (i = 0; testpairs[i].a != NULL; i++) {
|
||||
val = base32_decode(&buf, &len, testpairs[i].b);
|
||||
len = sizeof(buf);
|
||||
val = base32_decode(buf, &len, testpairs[i].b, strlen(testpairs[i].b));
|
||||
|
||||
fail_unless(val > 0, strerror(errno));
|
||||
fail_unless(buf != NULL, "buf == NULL");
|
||||
fail_unless(strcmp(buf, testpairs[i].a) == 0,
|
||||
va_str("'%s' != '%s'", buf, testpairs[i].a));
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
|
112
tests/base64.c
Normal file
112
tests/base64.c
Normal file
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and 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 <check.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "encoding.h"
|
||||
#include "base64.h"
|
||||
#include "test.h"
|
||||
|
||||
static struct tuple
|
||||
{
|
||||
char *a;
|
||||
char *b;
|
||||
} testpairs[] = {
|
||||
{ "iodinetestingtesting", "Aw8KAw4LDgvZDgLUz2rLC2rPBMC" },
|
||||
{ "abc123", "ywjJmtiZ" },
|
||||
{
|
||||
"\xFF\xEF\x7C\xEF\xAE\x78\xDF\x6D\x74\xCF\x2C\x70\xBE\xEB\x6C\xAE\xAA\x68"
|
||||
"\x9E\x69\x64\x8E\x28\x60\x7D\xE7\x5C\x6D\xA6\x58\x5D\x65\x54\x4D\x24\x50"
|
||||
"\x3C\xE3\x4C\x2C\xA2\x48\x1C\x61\x44\x0C\x20\x40\x3F\x3F\x3C\xEF\xAE\x78"
|
||||
"\xDF\x6D\x74\xCF\x2C\x70\xBE\xEB\x6C\xAE\xAA\x68\x9E\x69\x64\x8E\x28\x60"
|
||||
"\x7D\xE7\x5C\x6D\xA6\x58\x5D\x65\x54\x4D\x24\x50\x3C\xE3\x4C\x2C\xA2\x48"
|
||||
"\x1C\x61\x44\x0C\x20\x40\xFF\xEF\x7C\xEF\xAE\x78\xDF\x6D\x74\xCF\x2C\x70"
|
||||
"\xBE\xEB\x6C\xAE\xAA\x68\x9E\x69\x64\x8E\x28\x60\x7D\xE7\x5C\x6D\xA6\x58"
|
||||
"\x5D\x65\x54\x4D\x24\x50\x3C\xE3\x4C\x2C\xA2\x48\x1C\x61\x44\x0C\x20\x40",
|
||||
|
||||
"9abba876543210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfe999dcbapZ"
|
||||
"776543210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba9abba87654"
|
||||
"3210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfe999dcba"
|
||||
},
|
||||
{
|
||||
"\xFF\xEF\x7C\xEF\xAE\x78\xDF\x6D\x74\xCF\x2C\x70\xBE\xEB\x6C\xAE\xAA\x68"
|
||||
"\x9E\x69\x64\x8E\x28\x60\x7D\xE7\x5C\x6D\xA6\x58\x5D\x65\x54\x4D\x24\x50"
|
||||
"\x3C\xE3\x4C\x2C\xA2\x48\x1C\x61\x44\x0C\x20\x40\x3F\x3F\x3C\xEF\xAE\x78"
|
||||
"\xDF\x6D\x74\xCF\x2C\x70\xBE\xEB\x6C\xAE\xA1\x61\x91\x61\x61\x81\x28\x60"
|
||||
"\x7D\xE7\x5C\x6D\xA6\x58\x5D\x65\x54\x4D\x24\x50\x3C\xE3\x4C\x2C\xA2\x48"
|
||||
"\x1C\x61\x44\x0C\x20\x40\xFF\xEF\x7C\xEF\xAE\x78\xDF\x6D\x74\xCF\x2C\x70"
|
||||
"\xBE\xEB\x6C\xAE\xA1\x61\x91\x61\x61\x81\x28\x60\x7D\xE7\x5C\x6D\xA6\x58"
|
||||
"\x5D\x65\x54\x4D\x24\x50\x3C\xE3\x4C\x2C\xA2\x48\x1C\x61\x44\x0C\x20\x40",
|
||||
|
||||
"9IJJI876543210-ZYXWVUTSRQPONMLK9LMJIHGFEDCBAzyxwvutsrqponmlkjihgfedcbapZ"
|
||||
"776543210-ZYXWVUTSRQfHKwfHGsHGFEDCBAzyxwvutsrqponmlkjihgfedcbaML87654321"
|
||||
"0-ZYXWVUTSRQfHKwfHGsHGFEDCBAzyxwvutsrqponmlkjihgfedcba"
|
||||
},
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
START_TEST(test_base64_encode)
|
||||
{
|
||||
size_t len;
|
||||
char buf[4096];
|
||||
int val;
|
||||
int i;
|
||||
|
||||
for (i = 0; testpairs[i].a != NULL; i++) {
|
||||
len = sizeof(buf);
|
||||
val = base64_encode(buf, &len, testpairs[i].a, strlen(testpairs[i].a));
|
||||
|
||||
fail_unless(val > 0, strerror(errno));
|
||||
fail_unless(strcmp(buf, testpairs[i].b) == 0,
|
||||
va_str("'%s' != '%s'", buf, testpairs[i].b));
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_base64_decode)
|
||||
{
|
||||
size_t len;
|
||||
char buf[4096];
|
||||
int val;
|
||||
int i;
|
||||
|
||||
for (i = 0; testpairs[i].a != NULL; i++) {
|
||||
len = sizeof(buf);
|
||||
val = base64_decode(buf, &len, testpairs[i].b, strlen(testpairs[i].b));
|
||||
|
||||
fail_unless(val > 0, strerror(errno));
|
||||
fail_unless(buf != NULL, "buf == NULL");
|
||||
fail_unless(strcmp(buf, testpairs[i].a) == 0,
|
||||
va_str("'%s' != '%s'", buf, testpairs[i].a));
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
TCase *
|
||||
test_base64_create_tests()
|
||||
{
|
||||
TCase *tc;
|
||||
|
||||
tc = tcase_create("Base64");
|
||||
tcase_add_test(tc, test_base64_encode);
|
||||
tcase_add_test(tc, test_base64_decode);
|
||||
|
||||
return tc;
|
||||
}
|
35
tests/dns.c
35
tests/dns.c
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -28,17 +28,17 @@
|
|||
#include "common.h"
|
||||
#include "dns.h"
|
||||
#include "encoding.h"
|
||||
#include "base32.h"
|
||||
#include "test.h"
|
||||
|
||||
static void dump_packet(char *, size_t);
|
||||
|
||||
static char queryPacket[] =
|
||||
"\x05\x39\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x32\x41\x4A\x42\x43"
|
||||
"\x55\x59\x54\x43\x50\x45\x42\x39\x47\x51\x39\x4C\x54\x45\x42\x55\x58"
|
||||
"\x47\x49\x44\x55\x4E\x42\x53\x53\x41\x36\x44\x46\x4F\x4E\x39\x43\x41"
|
||||
"\x5A\x44\x42\x32\x41\x41\x41\x41\x41\x36\x44\x42\x04\x6B\x72\x79\x6F"
|
||||
"\x02\x73\x65\x00\x00\x0A\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00"
|
||||
"\x00\x00";
|
||||
"\x05\x39\x01\x00\x00\x01\x00\x00\x00\x00\x00\x01\x2D\x41\x6A\x62\x63"
|
||||
"\x75\x79\x74\x63\x70\x65\x62\x30\x67\x71\x30\x6C\x74\x65\x62\x75\x78"
|
||||
"\x67\x69\x64\x75\x6E\x62\x73\x73\x61\x33\x64\x66\x6F\x6E\x30\x63\x61"
|
||||
"\x7A\x64\x62\x6F\x72\x71\x71\x04\x6B\x72\x79\x6F\x02\x73\x65\x00\x00"
|
||||
"\x0A\x00\x01\x00\x00\x29\x10\x00\x00\x00\x80\x00\x00\x00";
|
||||
|
||||
static char answerPacket[] =
|
||||
"\x05\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05\x73\x69\x6C\x6C"
|
||||
|
@ -51,16 +51,16 @@ static char answerPacket[] =
|
|||
static char *msgData = "this is the message to be delivered";
|
||||
static char *topdomain = "kryo.se";
|
||||
|
||||
static char *queryData = "HELLO this is the test data";
|
||||
static char *recData = "AHELLO this is the test data"; /* The A flag is added */
|
||||
static char *innerData = "HELLO this is the test data";
|
||||
|
||||
START_TEST(test_encode_query)
|
||||
{
|
||||
char buf[512];
|
||||
char resolv[512];
|
||||
struct query q;
|
||||
struct encoder *enc;
|
||||
char *d;
|
||||
int len;
|
||||
size_t len;
|
||||
int ret;
|
||||
|
||||
len = sizeof(buf);
|
||||
|
@ -70,9 +70,10 @@ START_TEST(test_encode_query)
|
|||
q.type = T_NULL;
|
||||
q.id = 1337;
|
||||
d = resolv;
|
||||
enc = get_base32_encoder();
|
||||
|
||||
*d++ = 'A';
|
||||
encode_data(queryData, strlen(queryData), 100, d);
|
||||
enc->encode(d, &len, innerData, strlen(innerData));
|
||||
d = resolv + strlen(resolv);
|
||||
if (*d != '.') {
|
||||
*d++ = '.';
|
||||
|
@ -96,20 +97,22 @@ START_TEST(test_decode_query)
|
|||
char buf[512];
|
||||
char *domain;
|
||||
struct query q;
|
||||
int len;
|
||||
int ret;
|
||||
struct encoder *enc;
|
||||
size_t len;
|
||||
|
||||
memset(&q, 0, sizeof(struct query));
|
||||
memset(&buf, 0, sizeof(buf));
|
||||
q.id = 0;
|
||||
len = sizeof(queryPacket) - 1;
|
||||
enc = get_base32_encoder();
|
||||
|
||||
dns_decode(buf, sizeof(buf), &q, QR_QUERY, queryPacket, len);
|
||||
domain = strstr(q.name, topdomain);
|
||||
ret = decode_data(buf, sizeof(buf), q.name, domain);
|
||||
len = sizeof(buf);
|
||||
unpack_data(buf, len, &(q.name[1]), (int) (domain - q.name) - 1, enc);
|
||||
|
||||
fail_unless(strncmp(buf, recData, ret) == 0, "Did not extract expected host: '%s'", buf);
|
||||
fail_unless(strlen(buf) == strlen(recData), va_str("Bad host length: %d, expected %d", strlen(q.name), strlen(recData)));
|
||||
fail_unless(strncmp(buf, innerData, strlen(innerData)) == 0, "Did not extract expected host: '%s'", buf);
|
||||
fail_unless(strlen(buf) == strlen(innerData), va_str("Bad host length: %d, expected %d: '%s'", strlen(buf), strlen(innerData), buf));
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -22,24 +22,56 @@
|
|||
#include "encoding.h"
|
||||
#include "test.h"
|
||||
|
||||
START_TEST(test_encoding_base32)
|
||||
struct tuple
|
||||
{
|
||||
char temp[256];
|
||||
char *start = "HELLOTEST";
|
||||
char *out = "1HELLOTEST";
|
||||
char end[256];
|
||||
char *tempend;
|
||||
int codedlength;
|
||||
char *a;
|
||||
char *b;
|
||||
} dottests[] = {
|
||||
{ "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a"},
|
||||
{ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."},
|
||||
{ "abc123", "abc123" },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
memset(temp, 0, sizeof(temp));
|
||||
memset(end, 0, sizeof(end));
|
||||
START_TEST(test_inline_dotify)
|
||||
{
|
||||
unsigned i;
|
||||
char temp[1024];
|
||||
char *b;
|
||||
|
||||
codedlength = encode_data(start, strlen(start), sizeof(temp) - 1, temp + 1);
|
||||
temp[0] = '1';
|
||||
tempend = temp + strlen(temp);
|
||||
decode_data(end, sizeof(end), temp, tempend);
|
||||
i = 0;
|
||||
while (dottests[i].a) {
|
||||
memset(temp, 0, sizeof(temp));
|
||||
strcpy(temp, dottests[i].a);
|
||||
b = temp;
|
||||
inline_dotify(b, sizeof(temp));
|
||||
|
||||
fail_unless(strcmp(out, end) == 0, NULL);
|
||||
fail_unless(strcmp(dottests[i].b, temp) == 0,
|
||||
va_str("'%s' != '%s'", temp, dottests[i].b));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
START_TEST(test_inline_undotify)
|
||||
{
|
||||
unsigned i;
|
||||
char temp[1024];
|
||||
char *b;
|
||||
|
||||
i = 0;
|
||||
while (dottests[i].a) {
|
||||
memset(temp, 0, sizeof(temp));
|
||||
strcpy(temp, dottests[i].b);
|
||||
b = temp;
|
||||
inline_undotify(b, sizeof(temp));
|
||||
|
||||
fail_unless(strcmp(dottests[i].a, temp) == 0,
|
||||
va_str("'%s' != '%s'", temp, dottests[i].a));
|
||||
i++;
|
||||
}
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
@ -49,7 +81,8 @@ test_encoding_create_tests()
|
|||
TCase *tc;
|
||||
|
||||
tc = tcase_create("Encoding");
|
||||
tcase_add_test(tc, test_encoding_base32);
|
||||
tcase_add_test(tc, test_inline_dotify);
|
||||
tcase_add_test(tc, test_inline_undotify);
|
||||
|
||||
return tc;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -49,6 +49,9 @@ main()
|
|||
test = test_base32_create_tests();
|
||||
suite_add_tcase(iodine, test);
|
||||
|
||||
test = test_base64_create_tests();
|
||||
suite_add_tcase(iodine, test);
|
||||
|
||||
test = test_dns_create_tests();
|
||||
suite_add_tcase(iodine, test);
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -18,6 +18,7 @@
|
|||
#define __TEST_H__
|
||||
|
||||
TCase *test_base32_create_tests();
|
||||
TCase *test_base64_create_tests();
|
||||
TCase *test_dns_create_tests();
|
||||
TCase *test_encoding_create_tests();
|
||||
TCase *test_read_create_tests();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2006 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
* Copyright (c) 2006-2007 Bjorn Andersson <flex@kryo.se>, Erik Ekman <yarrick@kryo.se>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
|
@ -23,6 +23,7 @@
|
|||
#include <netinet/in.h>
|
||||
|
||||
#include "common.h"
|
||||
#include "encoding.h"
|
||||
#include "user.h"
|
||||
#include "test.h"
|
||||
|
||||
|
|
Loading…
Reference in a new issue