From 437bcb34870f7e97d2c7c9f3fb8a8813f3c89d0a Mon Sep 17 00:00:00 2001 From: Erik Ekman Date: Sat, 13 Feb 2010 13:49:53 +0000 Subject: [PATCH] Add 0.6 branch --- iodine/CHANGELOG | 142 ++ iodine/Makefile | 62 + iodine/README | 357 +++++ iodine/README-win32.txt | 62 + iodine/TODO | 12 + iodine/doc/iodine.te | 25 + iodine/doc/proto_00000402.txt | 61 + iodine/doc/proto_00000500.txt | 112 ++ iodine/doc/proto_00000502.txt | 287 ++++ iodine/man/iodine.8 | 338 +++++ iodine/src/Makefile | 46 + iodine/src/base128.c | 291 ++++ iodine/src/base128.h | 22 + iodine/src/base32.c | 270 ++++ iodine/src/base32.h | 24 + iodine/src/base64.c | 205 +++ iodine/src/base64.h | 22 + iodine/src/client.c | 2514 +++++++++++++++++++++++++++++++++ iodine/src/client.h | 39 + iodine/src/common.c | 350 +++++ iodine/src/common.h | 133 ++ iodine/src/dns.c | 610 ++++++++ iodine/src/dns.h | 35 + iodine/src/encoding.c | 130 ++ iodine/src/encoding.h | 43 + iodine/src/fw_query.c | 49 + iodine/src/fw_query.h | 41 + iodine/src/iodine.c | 379 +++++ iodine/src/iodined.c | 2488 ++++++++++++++++++++++++++++++++ iodine/src/login.c | 57 + iodine/src/login.h | 23 + iodine/src/md5.c | 384 +++++ iodine/src/md5.h | 91 ++ iodine/src/osflags | 36 + iodine/src/read.c | 275 ++++ iodine/src/read.h | 33 + iodine/src/tun.c | 541 +++++++ iodine/src/tun.h | 27 + iodine/src/user.c | 204 +++ iodine/src/user.h | 87 ++ iodine/src/util.c | 69 + iodine/src/util.h | 6 + iodine/src/version.h | 25 + iodine/src/windows.h | 100 ++ iodine/tests/Makefile | 27 + iodine/tests/base32.c | 142 ++ iodine/tests/base64.c | 155 ++ iodine/tests/dns.c | 252 ++++ iodine/tests/encoding.c | 108 ++ iodine/tests/fw_query.c | 88 ++ iodine/tests/login.c | 70 + iodine/tests/read.c | 297 ++++ iodine/tests/test.c | 66 + iodine/tests/test.h | 37 + iodine/tests/user.c | 199 +++ 55 files changed, 12548 insertions(+) create mode 100644 iodine/CHANGELOG create mode 100644 iodine/Makefile create mode 100644 iodine/README create mode 100644 iodine/README-win32.txt create mode 100644 iodine/TODO create mode 100644 iodine/doc/iodine.te create mode 100644 iodine/doc/proto_00000402.txt create mode 100644 iodine/doc/proto_00000500.txt create mode 100644 iodine/doc/proto_00000502.txt create mode 100644 iodine/man/iodine.8 create mode 100644 iodine/src/Makefile create mode 100644 iodine/src/base128.c create mode 100644 iodine/src/base128.h create mode 100644 iodine/src/base32.c create mode 100644 iodine/src/base32.h create mode 100644 iodine/src/base64.c create mode 100644 iodine/src/base64.h create mode 100644 iodine/src/client.c create mode 100644 iodine/src/client.h create mode 100644 iodine/src/common.c create mode 100644 iodine/src/common.h create mode 100644 iodine/src/dns.c create mode 100644 iodine/src/dns.h create mode 100644 iodine/src/encoding.c create mode 100644 iodine/src/encoding.h create mode 100644 iodine/src/fw_query.c create mode 100644 iodine/src/fw_query.h create mode 100644 iodine/src/iodine.c create mode 100644 iodine/src/iodined.c create mode 100644 iodine/src/login.c create mode 100644 iodine/src/login.h create mode 100644 iodine/src/md5.c create mode 100644 iodine/src/md5.h create mode 100755 iodine/src/osflags create mode 100644 iodine/src/read.c create mode 100644 iodine/src/read.h create mode 100644 iodine/src/tun.c create mode 100644 iodine/src/tun.h create mode 100644 iodine/src/user.c create mode 100644 iodine/src/user.h create mode 100644 iodine/src/util.c create mode 100644 iodine/src/util.h create mode 100644 iodine/src/version.h create mode 100644 iodine/src/windows.h create mode 100644 iodine/tests/Makefile create mode 100644 iodine/tests/base32.c create mode 100644 iodine/tests/base64.c create mode 100644 iodine/tests/dns.c create mode 100644 iodine/tests/encoding.c create mode 100644 iodine/tests/fw_query.c create mode 100644 iodine/tests/login.c create mode 100644 iodine/tests/read.c create mode 100644 iodine/tests/test.c create mode 100644 iodine/tests/test.h create mode 100644 iodine/tests/user.c diff --git a/iodine/CHANGELOG b/iodine/CHANGELOG new file mode 100644 index 0000000..271e2ad --- /dev/null +++ b/iodine/CHANGELOG @@ -0,0 +1,142 @@ + +iodine - http://code.kryo.se/iodine + +*********************************** + +CHANGES: + +20xx-xx-xx: x.y.z "Hotspotify" + - Fixed tunnel not working on Windows. + - Any device name is now supported on Windows, fixes #47. + - Multiple installed TAP32 interfaces are now supported, fixes #46. + - Return nonzero if tunnel fails to open, fixes #62. + - Support for setting a SELinux context, based on patch by + Sebastien Raveau. Sample context file in doc/iodine.te + - Allow listen port and DNS forward port to be the same if listen IP + does not include localhost. + - The client will now exit if configuring IP or MTU fails. + - The starting cache miss value is randomized at startup, fixes #65. + - Raw UDP mode added. If the iodined server is reachable directly, + packets can be sent to it without DNS encoding. Fixes #36. + - Do not overwrite users CC/CFLAGS/LDFLAGS, only add to them. + - Added -F option to write pidfile, based on patch from + misc at mandriva.org. Fixes #70. + - Allow password to be set via environment variable, fixes #77. + Based on patch by logix. + - Client now prints server tunnel IP, fixes #78. Patch by logix. + - Fix build error on Mac OS X 10.6, patch by G. Rischard. #79. + - Added support for CNAME/TXT/A/MX query types, fixes #75. + Patch by Anne Bezemer, merge help by logix. + - Merged low-latency patch from Anne Bezemer, fixes #76. + - Resolve client nameserver argument if given as hostname, fixes #82. + - Open log before chroot, fixes #86: logging on FreeBSD. + +2009-06-01: 0.5.2 "WifiFree" + - Fixed client segfault on OS X, #57 + - Added check that nameserver lookup was successful + - Fixed ENOTSOCK error on OS X and FreeBSD, #58. + +2009-03-21: 0.5.1 "Boringo" + - Added initial Windows support, fixes #43. + - Added length check of autoprobe responses + - Refactored and added unit tests + - Added syslog logging for iodined on version and login packets + - Fixed segfault when encoding just one block, fixes #51. + The normal code was never affected by this. + - Added win32 code to read DNS server from system, fixes #45. + - Disabled password echo on win32, fixes #44. + - Fix encoding error making all autoprobing > 1024 bytes fail, #52. + - Increase default interface MTU to 1200. + - Fix autoprobing error making every third probe fail, set IP flag + Dont-Fragment where supported. Fixes #54. + - Added TAP32 version 0901 as accepted (#53). + +2009-01-23: 0.5.0 "iPassed" + - Fixed segfault in server when sending version reject. + - Applied patch to make iodine build on BeOS R5-BONE and Haiku, + from Francois Revol. Still work to do to get tun device working. + - Added capability to forward DNS queries outside tunnel domain to + a nameserver on localhost. Use -b port to enable, fixes #31. + - iodined now replies to NS request on its own domain, fixes issue #33. + The destination IP address is sent as reply. Use -n to specify + a specific IP address to return (if behind NAT etc). + - Upstream data is now Base64 encoded if relay server preserves case and + supports the plus (+) character in domain names, fixes #16. + - Fixed problem in client when DNS trans. ID has highest bit set (#37) + - IP addresses are now assigned within the netmask, so iodined can + use any address for itself, fixes #28. + - Netmask size is now adjustable. Setting a small net will reduce the + number of users. Use x.x.x.x/n notation on iodined tunnel ip. + This fixes #27. + - Downstream data is now fragmented, and the fragment size is auto- + probed after login. Fixes #7. It only took a few years :) + - Enhanced the checks that validates incoming packets + - Fixed endless loop in fragment size autodetection, #39. + - Fixed broken hostname dot placing with specific lengths, #40. + +2008-08-06: 0.4.2 "Opened Zone" + - Applied a few small patches from Maxim Bourmistrov and Gregor Herrmann + - Applied a patch for not creating and configuring the tun interface, + Debian bug #477692 by Vincent Bernat, controlled by -s switch + - Applied a security patch from Andrew Griffiths, use setgroups() to + limit the groups of the user + - Applied a patch to make iodine build on (Open)Solaris, from Albert Lee + Needs TUN/TAP driver http://www.whiteboard.ne.jp/~admin2/tuntap/ + Still needs more code in tun.c for opening/closing the device + - Added option in server (-c) to disable IP/port checking on packets, + will hopefully help when server is behind NAT + - Fixed bug #21, now only IP address part of each packet is checked. + Should remove the need for the -c option and also work with + bugfixed DNS servers worldwide. + - Added -D option on server to enable debugging. Debug level 1 now + prints info about each RX/TX datagram. + +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) + - Added manpage + - Added install/uninstall make target + - Cleanup of dns code, more test cases, use check library + - Changed directory structure + +2006-11-08: 0.3.4 + - Fixed handshake() buffer overflow + (Found by poplix, Secunia: SA22674 / FrSIRT/ADV-2006-4333) + - Added more tests + - More name parsing enhancements + - Now runs on Linux/AMD64 + - Added setting to change server port + +2006-11-05: 0.3.3 + - Fixed possible buffer overflow + (Found by poplix, Bugtraq ID: 20883) + - Reworked dns hostname encoding + +2006-09-11: 0.3.2 + - Support for NetBSD + - Fixed potential security problems + - Name parsing routines rewritten, added regression tests + - New encoding, 25% more peak upstream throughput + - New -l option to set local ip to listen to on server + +2006-07-11: 0.3.1 + - Add Mac OSX support + - Add setting device name + - Use compression of domain name in reply (should allow setting MTU + approx 200 bytes higher) + +2006-06-24: 0.3.0 + - First public release + - Support for Linux, FreeBSD, OpenBSD diff --git a/iodine/Makefile b/iodine/Makefile new file mode 100644 index 0000000..01c2325 --- /dev/null +++ b/iodine/Makefile @@ -0,0 +1,62 @@ +prefix=/usr/local +sbindir=$(prefix)/sbin +datadir=$(prefix)/share +mandir=$(datadir)/man + +DESTDIR= + +INSTALL=install +INSTALL_FLAGS= + +MKDIR=mkdir +MKDIR_FLAGS=-p + +RM=rm +RM_FLAGS=-f + +TARGETOS = `uname` + +all: + @(cd src; $(MAKE) TARGETOS=$(TARGETOS) all) + +cross-mingw: + @(cd src; $(MAKE) TARGETOS=windows32 CC=i686-mingw32-gcc all) + +cross-mingw-dist: cross-mingw + @rm -rf iodine-latest-win32* + @mkdir -p iodine-latest-win32/bin + @for i in `ls bin`; do cp bin/$$i iodine-latest-win32/bin/$$i.exe; done + @cp /usr/i686-mingw32/usr/bin/zlib1.dll iodine-latest-win32/bin + @cp README* CH* TO* iodine-latest-win32 + @echo "Create date: " > iodine-latest-win32/VERSION + @date >> iodine-latest-win32/VERSION + @echo "SVN version: " >> iodine-latest-win32/VERSION + @svnversion >> iodine-latest-win32/VERSION + @zip -r iodine-latest-win32.zip iodine-latest-win32 + +install: all + $(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) $(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) TARGETOS=$(TARGETOS) all) + +clean: + @echo "Cleaning..." + @(cd src; $(MAKE) clean) + @(cd tests; $(MAKE) clean) + @rm -rf bin iodine-latest-win32* + diff --git a/iodine/README b/iodine/README new file mode 100644 index 0000000..d9e3c29 --- /dev/null +++ b/iodine/README @@ -0,0 +1,357 @@ + +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 +firewalled, but DNS queries are allowed. + + +QUICKSTART: + +Try it out within your own LAN! Follow these simple steps: +- On your server, run: ./iodined -f 10.0.0.1 test.com + (If you already use the 10.0.0.0 network, use another internal net like + 172.16.0.0) +- Enter a password +- On the client, run: ./iodine -f -r 192.168.0.1 test.com + (Replace 192.168.0.1 with your server's ip address) +- Enter the same password +- Now the client has the tunnel ip 10.0.0.2 and the server has 10.0.0.1 +- Try pinging each other through the tunnel +- Done! :) +To actually use it through a relaying nameserver, see below. + + +HOW TO USE: + +Note: server and client are required to speak the exact same protocol. In most +cases, this means running the same iodine version. Unfortunately, implementing +backward and forward protocol compatibility is usually not feasible. + +Server side: +To use this tunnel, you need control over a real domain (like mydomain.com), +and a server with a public IP address to run iodined on. If this server +already runs a DNS program, change its listening port and then use iodined's +-b option to let iodined forward the DNS requests. (Note that this procedure +is not advised in production environments, because iodined's DNS forwarding +is not completely transparent.) + +Then, delegate a subdomain (say, t1.mydomain.com) to the iodined server. +If you use BIND for your domain, add two lines like these to the zone file: + +t1 IN NS t1ns.mydomain.com. ; note the dot! +t1ns IN A 10.15.213.99 + +The "NS" line is all that's needed to route queries for the "t1" subdomain +to the "t1ns" server. We use a short name for the subdomain, to keep as much +space as possible available for the data traffic. At the end of the "NS" line +is the name of your iodined server. This can be any name, pointing anywhere, +but in this case it's easily kept in the same zone file. It must be a name +(not an IP address), and that name itself must have an A record (not a CNAME). + +If your iodined server has a dynamic IP, use a dynamic dns provider. Simply +point the "NS" line to it, and leave the "A" line out: + +t1 IN NS myname.mydyndnsprovider.com. ; note the dot! + +Then reload or restart your nameserver program. Now any DNS queries for +domains ending in t1.mydomain.com will be sent to your iodined server. + +Finally start iodined on your server. The first argument is the IP address +inside the tunnel, which can be from any range that you don't use yet (for +example 192.168.99.1), and the second argument is the assigned domain (in this +case t1.mydomain.com). Using the -f option will keep iodined running in the +foreground, which helps when testing. iodined will open a virtual interface +("tun device"), and will also start listening for DNS queries on UDP port 53. +Either enter a password on the commandline (-P pass) or after the server has +started. Now everything is ready for the client. + +If there is a chance you'll be using an iodine tunnel from unexpected +environments, start iodined with a -c option. + +Resulting commandline in this example situation: +./iodined -f -c -P secretpassword 192.168.99.1 t1.mydomain.com + +Client side: +All the setup is done, just start iodine. It takes one or two arguments, the +first is the local relaying DNS server (optional) and the second is the domain +you used (t1.mydomain.com). If you don't specify the first argument, the +system's current DNS setting will be consulted. + +If DNS queries are allowed to any computer, you can directly give the iodined +server's address as first argument (in the example: t1ns.mydomain.com or +10.15.213.99). In that case, it may also happen that _any_ traffic is allowed +to the DNS port (53 UDP) of any computer. Iodine will detect this, and switch +to raw UDP tunneling if possible. To force DNS tunneling in any case, use the +-r option (especially useful when testing within your own network). + +The client's tunnel interface will get an IP close to the server's (in this +case 192.168.99.2 or .3 etc.) and a suitable MTU. Enter the same password as +on the server either as commandline option or after the client has started. +Using the -f option will keep the iodine client running in the foreground. + +Resulting commandline in this example situation: +./iodine -f -P secretpassword t1.mydomain.com +(add -r to force DNS tunneling even if raw UDP tunneling would be possible) + +From either side, you should now be able to ping the IP address on the other +end of the tunnel. In this case, ping 192.168.99.1 from the iodine client, and +192.168.99.2 or .3 etc. from the iodine server. + + +MISC. INFO: + +Routing: +It is possible to route all traffic through the DNS tunnel. To do this, first +add a host route to the nameserver used by iodine over the wired/wireless +interface with the default gateway as gateway. Then replace the default +gateway with the iodined server's IP address inside the DNS tunnel, and +configure the server to do NAT. + +However, note that the tunneled data traffic is not encrypted at all, and can +be read and changed by external parties relatively easily. For maximum +security, run a VPN through the DNS tunnel (=double tunneling), or use secure +shell (SSH) access, possibly with port forwarding. The latter can also be used +for web browsing, when you run a web proxy (for example Privoxy) on your +server. + +Testing: +The iodined server replies to NS requests sent for subdomains of the tunnel +domain. If your iodined subdomain is t1.mydomain.com, send a NS request for +foo123.t1.mydomain.com to see if the delegation works. dig is a good tool +for this: +dig -t NS foo123.t1.mydomain.com + +Also, the iodined server will answer requests starting with 'z' for any of the +supported request types, for example: +dig -t TXT z456.t1.mydomain.com +dig -t SRV z456.t1.mydomain.com +dig -t CNAME z456.t1.mydomain.com +The reply should look like garbled text in all these cases. + +Operational info: +The DNS-response fragment size is normally autoprobed to get maximum bandwidth. +To force a specific value (and speed things up), use the -m option. + +The DNS hostnames are normally used up to their maximum length, 255 characters. +Some DNS relays have been found that answer full-length queries rather +unreliably, giving widely varying (and mostly very bad) results of the +fragment size autoprobe on repeated tries. In these cases, use the -M switch +to reduce the DNS hostname length to for example 200 characters, which makes +these DNS relays much more stable. This is also useful on some "de-optimizing" +DNS relays that stuff the response with two full copies of the query, leaving +very little space for downstream data (also not capable of EDNS0). The -M +switch can trade some upstream bandwidth for downstream bandwidth. Note that +the minimum -M value is about 100, since the protocol can split packets (1200 +bytes max) in only 16 fragments, requiring at least 75 real data bytes per +fragment. + +The upstream data is sent gzipped encoded with Base32; or Base64 if the relay +server supports mixed case and '+' in domain names; or Base64u if '_' is +supported instead; or Base128 if high-byte-value characters are supported. +This upstream encoding is autodetected. The DNS protocol allows one query per +packet, and one query can be max 256 chars. Each domain name part can be max +63 chars. So your domain name and subdomain should be as short as possible to +allow maximum upstream throughput. + +Several DNS request types are supported, with the NULL type expected to provide +the largest downstream bandwidth. Other available types are TXT, SRV, MX, +CNAME and A (returning CNAME), in decreasing bandwidth order. Normally the +"best" request type is autodetected and used. However, DNS relays may impose +limits on for example NULL and TXT, making SRV or MX actually the best choice. +This is not autodetected, but can be forced using the -T option. It is +advisable to try various alternatives especially when the autodetected request +type provides a downstream fragment size of less than 200 bytes. + +Note that SRV, MX and A (returning CNAME) queries may/will cause additional +lookups by "smart" caching nameservers to get an actual IP address, which may +either slow down or fail completely. + +DNS responses for non-NULL queries can be encoded with the same set of codecs +as upstream data. This is normally also autodetected, but no fully exhaustive +tests are done, so some problems may not be noticed when selecting more +advanced codecs. In that case, you'll see failures/corruption in the fragment +size autoprobe. In particular, several DNS relays have been found that change +replies returning hostnames (SRV, MX, CNAME, A) to lowercase only when that +hostname exceeds ca. 180 characters. In these and similar cases, use the -O +option to try other downstream codecs; Base32 should always work. + +Normal operation now is for the server to _not_ answer a DNS request until +the next DNS request has come in, a.k.a. being "lazy". This way, the server +will always have a DNS request handy when new downstream data has to be sent. +This greatly improves (interactive) performance and latency, and allows to +slow down the quiescent ping requests to 4 second intervals by default, and +possibly much slower. In fact, the main purpose of the pings now is to force +a reply to the previous ping, and prevent DNS server timeouts (usually at +least 5-10 seconds per RFC1035). Some DNS servers are more impatient and will +give SERVFAIL errors (timeouts) in periods without tunneled data traffic. All +data should still get through in these cases, but iodine will reduce the ping +interval to 1 second anyway (-I1) to reduce the number of error messages. This +may not help for very impatient DNS relays like dnsadvantage.com (ultradns), +which time out in 1 second or even less. Yet data will still get trough, and +you can ignore the SERVFAIL errors. + +If you are running on a local network without any DNS server in-between, try +-I 50 (iodine and iodined close the connection after 60 seconds of silence). +The only time you'll notice a slowdown, is when DNS reply packets go missing; +the iodined server then has to wait for a new ping to re-send the data. You can +speed this up by generating some upstream traffic (keypress, ping). If this +happens often, check your network for bottlenecks and/or run with -I1. + +The delayed answering in lazy mode will cause some "carrier grade" commercial +DNS relays to repeatedly re-send the same DNS query to the iodined server. +If the DNS relay is actually implemented as a pool of parallel servers, +duplicate requests may even arrive from multiple sources. This effect will +only be visible in the network traffic at the iodined server, and will not +affect the client's connection. Iodined will notice these duplicates, and send +the same answer (when its time has come) to both the original query and the +latest duplicate. After that, the full answer is cached for a short while. +Delayed duplicates that arrive at the server even later, get a reply that the +iodine client will ignore (if it ever arrives there). + +If you have problems, try inspecting the traffic with network monitoring tools +like tcpdump or ethereal/wireshark, and make sure that the relaying DNS server +has not cached the response. A cached error message could mean that you +started the client before the server. The -D (and -DD) option on the server +can also show received and sent queries. + + +TIPS & TRICKS: + +If your port 53 is taken on a specific interface by an application that does +not use it, use -p on iodined to specify an alternate port (like -p 5353) and +use for instance iptables (on Linux) to forward the traffic: +iptables -t nat -A PREROUTING -i eth0 -p udp --dport 53 -j DNAT --to :5353 +(Sent in by Tom Schouten) + +Iodined will reject data from clients that have not been active (data/pings) +for more than 60 seconds. Similarly, iodine will exit when no downstream +data has been received for 60 seconds. In case of a long network outage or +similar, just restart iodine (re-login), possibly multiple times until you get +your old IP address back. Once that's done, just wait a while, and you'll +eventually see the tunneled TCP traffic continue to flow from where it left +off before the outage. + +With the introduction of the downstream packet queue in the server, its memory +usage has increased with several megabytes in the default configuration. +For use in low-memory environments (e.g. running on your DSL router), you can +decrease USERS and undefine OUTPACKETQ_LEN in user.h without any ill conse- +quence, assuming at most one client will be connected at any time. A small +DNSCACHE_LEN is still advised, preferably 2 or higher, however you can also +undefine it to save a few more kilobytes. + + +PERFORMANCE: + +This section tabulates some performance measurements. To view properly, use +a fixed-width font like Courier. + +Measurements were done in protocol 00000502 in lazy mode; upstream encoding +always Base128; iodine -M255; iodined -m1130. Network conditions were not +extremely favorable; results are not benchmarks but a realistic indication of +real-world performance that can be expected in similar situations. + +Upstream/downstream throughput was measured by scp'ing a file previously +read from /dev/urandom (i.e. incompressible), and measuring size with +"ls -l ; sleep 30 ; ls -l" on a separate non-tunneled connection. Given the +large scp block size of 16 kB, this gives a resolution of 4.3 kbit/s, which +explains why some values are exactly equal. +Ping round-trip times measured with "ping -c100", presented are average rtt +and mean deviation (indicating spread around the average), in milliseconds. + + +Situation 1: +Laptop -> Wifi AP -> Home server -> DSL provider -> Datacenter + iodine DNS "relay" bind9 DNS cache iodined + + downstr. upstream downstr. ping-up ping-down + fragsize kbit/s kbit/s avg +/-mdev avg +/-mdev +------------------------------------------------------------------------------ + +iodine -> Wifi AP :53 + -Tnull (= -Oraw) 982 43.6 131.0 28.0 4.6 26.8 3.4 + +iodine -> Home server :53 + -Tnull (= -Oraw) 1174 48.0 305.8 26.6 5.0 26.9 8.4 + +iodine -> DSL provider :53 + -Tnull (= -Oraw) 1174 56.7 367.0 20.6 3.1 21.2 4.4 + -Ttxt -Obase32 730 56.7 174.7* + -Ttxt -Obase64 874 56.7 174.7 + -Ttxt -Obase128 1018 56.7 174.7 + -Ttxt -Oraw 1162 56.7 358.2 + -Tsrv -Obase128 910 56.7 174.7 + -Tcname -Obase32 151 56.7 43.6 + -Tcname -Obase128 212 56.7 52.4 + +iodine -> DSL provider :53 + wired (no Wifi) -Tnull 1174 74.2 585.4 20.2 5.6 19.6 3.4 + + [174.7* : these all have 2frag/packet] + + +Situation 2: +Laptop -> Wifi+vpn / wired -> Home server + iodine iodined + + downstr. upstream downstr. ping-up ping-down + fragsize kbit/s kbit/s avg +/-mdev avg +/-mdev +------------------------------------------------------------------------------ + +wifi + openvpn -Tnull 1186 166.0 1022.3 6.3 1.3 6.6 1.6 + +wired -Tnull 1186 677.2 2464.1 1.3 0.2 1.3 0.1 + + +Performance is strongly coupled to low ping times, as iodine requires +confirmation for every data fragment before moving on to the next. Allowing +multiple fragments in-flight like TCP could possibly increase performance, +but it would likely cause serious overload for the intermediary DNS servers. +The current protocol scales performance with DNS responsivity, since the +DNS servers are on average handling at most one DNS request per client. + + +PORTABILITY: + +iodine has been tested on Linux (arm, ia64, x86, AMD64 and SPARC64), FreeBSD +(ia64, x86), OpenBSD (x86), NetBSD (x86), MacOS X (ppc and x86, with +http://tuntaposx.sourceforge.net/). and Windows (with OpenVPN TAP32 driver, see +win32 readme file). It should be easy to port to other unix-like systems that +has TUN/TAP tunneling support. Let us know if you get it to run on other +platforms. + + +THE NAME: + +The name iodine was chosen since it starts with IOD (IP Over DNS) and since +iodine has atomic number 53, which happens to be the DNS port number. + + +THANKS: + +- To kuxien for FreeBSD and OS X testing +- To poplix for code audit + + +AUTHORS & LICENSE: + +Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman +Also major contributions by Anne Bezemer. + +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. + + +MD5 implementation by L. Peter Deutsch (license and source in src/md5.[ch]) +Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. diff --git a/iodine/README-win32.txt b/iodine/README-win32.txt new file mode 100644 index 0000000..eec675e --- /dev/null +++ b/iodine/README-win32.txt @@ -0,0 +1,62 @@ + + +iodine - http://code.kryo.se/iodine + +*********************************** + +Extra README file for Win32 related stuff + + +== Running iodine on Windows: +1. Install the TAP32 driver + http://openvpn.net/index.php/open-source/downloads.html + Choose OpenVPN 2.0.9 Windows Installer, when installing you can + select to install only the TAP driver. + +2. Have at least one TAP32 interface installed. There are scripts for adding + and removing in the OpenVPN bin directory. If you have more than one + installed, use -d to specify which. Use double quotes if you have spaces, + example: iodine.exe -d "Local Area Connection 4" abc.ab + +3. Make sure the interface you want to use does not have a default gateway set. + +4. Run iodine/iodined as normal (see the main README file). + You may have to run it as administrator depending on user privileges. + +5. Enjoy! + + +== Building on Windows: +You need: + MinGW, MSYS, GCC, zlib + +Then just run make + + +== Cross-compiling for MinGW: +You need: + MinGW crosscompiler, crosscompiled zlib + +Then run "make cross-mingw" +Note that the binaries will not get a .exe suffix + + +== Zlib download +You can get zlib for MinGW here (both for native and crosscompile): +http://code.kryo.se/iodine/deps/zlib.zip +Unzip it in your MinGW directory on Windows or in $ROOT/usr for +cross-compile. + + +== Results of crappy Win32 API: +The following fixable limitations apply: +- Server cannot read packet destination address + +The following (probably) un-fixable limitations apply: +- A password entered as -P argument can be shown in process list +- Priviligies cannot be dropped +- chroot() cannot be used +- Detaching from terminal not possible +- Server on windows must be run with /30 netmask +- Client can only talk to server, not other clients + diff --git a/iodine/TODO b/iodine/TODO new file mode 100644 index 0000000..e852d32 --- /dev/null +++ b/iodine/TODO @@ -0,0 +1,12 @@ + +iodine - http://code.kryo.se/iodine + +*********************************** + +The TODO list is now located at + +http://dev.kryo.se/iodine/ + +The list is under the "View tickets" page + +Feel free to add your own wishes and bug reports diff --git a/iodine/doc/iodine.te b/iodine/doc/iodine.te new file mode 100644 index 0000000..9749f03 --- /dev/null +++ b/iodine/doc/iodine.te @@ -0,0 +1,25 @@ +# Sample post-initialization SELinux policy for Iodine +policy_module(iodine, 1.1) + +require { + type init_t; + type initrc_t; + type unconfined_t; + type unlabeled_t; + class udp_socket { read write }; + class rawip_socket { write read }; + class association recvfrom; + class unix_dgram_socket { create connect }; +} + +type iodine_t; +domain_type(iodine_t) +domain_dyntrans_type(initrc_t) +allow initrc_t iodine_t:process dyntransition; + +allow iodine_t unconfined_t:udp_socket { read write }; +allow iodine_t unconfined_t:rawip_socket { write read }; +allow iodine_t unlabeled_t:association recvfrom; +allow iodine_t self:unix_dgram_socket { create connect }; +corenet_raw_receive_generic_node(iodine_t) +corenet_rw_tun_tap_dev(iodine_t) diff --git a/iodine/doc/proto_00000402.txt b/iodine/doc/proto_00000402.txt new file mode 100644 index 0000000..da36919 --- /dev/null +++ b/iodine/doc/proto_00000402.txt @@ -0,0 +1,61 @@ +Detailed specification of protocol in version 00000402 +====================================================== + +CMC = 2 byte Cache Miss Counter, increased every time it is used + +Version: +Client sends: + First byte v or V + Rest encoded with base32: + 4 bytes big endian protocol version + CMC +Server replies: + 4 chars: + VACK (version ok), followed by login challenge + VNAK (version differs), followed by server protocol version + VFUL (server has no free slots), followed by max users + 4 byte value: means login challenge/server protocol version/max users + 1 byte userid of the new user, or any byte if not VACK + +Login: +Client sends: + First byte l or L + Rest encoded with base32: + 1 byte userid + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + CMC +Server replies: + LNAK means not accepted + x.x.x.x-y.y.y.y-mtu means accepted (server ip, client ip, mtu) + +Case check: +Client sends: + First byte z or Z + Lots of data that should not be decoded +Server replies: + The requested domain copied raw + +Data: +Data header: + 321 0 + +---+-+ + |UUU|L| + +---+-+ + +UUU = Userid +L = Last fragment in packet flag + +First byte is the header, 4 bits coded as hex in ASCII. +Followed by data encoded with Base32. + +Ping: +Client sends: + First byte p or P + Rest encoded with Base32: + 1 byte userid + CMC + +The server response to Ping and Data packets is a DNS NULL type response: +If server has nothing to send, data length is 0 bytes. +If server has a packet to send, data length is set and the data is a full raw +unencoded ip packet, prefixed with 32 bits tun data. diff --git a/iodine/doc/proto_00000500.txt b/iodine/doc/proto_00000500.txt new file mode 100644 index 0000000..05f100c --- /dev/null +++ b/iodine/doc/proto_00000500.txt @@ -0,0 +1,112 @@ +Detailed specification of protocol in version 00000500 +====================================================== + +CMC = 2 byte Cache Miss Counter, increased every time it is used + +Version: +Client sends: + First byte v or V + Rest encoded with base32: + 4 bytes big endian protocol version + CMC +Server replies: + 4 chars: + VACK (version ok), followed by login challenge + VNAK (version differs), followed by server protocol version + VFUL (server has no free slots), followed by max users + 4 byte value: means login challenge/server protocol version/max users + 1 byte userid of the new user, or any byte if not VACK + +Login: +Client sends: + First byte l or L + Rest encoded with base32: + 1 byte userid + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + CMC +Server replies: + LNAK means not accepted + x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits) + +Case check: +Client sends: + First byte z or Z + Lots of data that should not be decoded +Server replies: + The requested domain copied raw + +Switch codec: +Client sends: + First byte s or S + 5 bits coded as Base32 char, meaning userid + 5 bits coded as Base32 char, with value 5 or 6, representing number of raw + bits per encoded byte +Server sends: + Name of codec if accepted. After this all upstream data packets must + be encoded with the new codec. + BADCODEC if not accepted. Client must then revert to Base32 + +Probe downstream fragment size: +Client sends: + First byte r or R + 15 bits coded as 3 Base32 chars: UUUUF FFFFF FFFFF + meaning 4 bits userid, 11 bits fragment size + Then follows a long random query which contents does not matter +Server sends: + Requested number of bytes as a response. The first two bytes contains + the requested length. Rest of message can be any data. + BADFRAG if requested length not accepted. + +Set downstream fragment size: +Client sends: + First byte n or N + Rest encoded with base32: + 1 byte userid + 2 bytes new downstream fragment size + CMC +Server sends: + 2 bytes new downstream fragment size. After this all downstream + payloads will be max (fragsize + 2) bytes long. + BADFRAG if not accepted. + +Data: +Upstream data header: + 3210 432 10 43 210 4321 0 + +----+---+--+--+---+----+-+ + |UUUU|SSS|FF|FF|DDD|GGGG|L| + +----+---+--+--+---+----+-+ + +Downstream data header: + 7 654 3210 765 4321 0 + +-+---+----+---+----+-+ + |C|SSS|FFFF|DDD|GGGG|L| + +-+---+----+---+----+-+ + +UUUU = Userid +L = Last fragment in packet flag +SS = Upstream packet sequence number +FFFF = Upstream fragment number +DDD = Downstream packet sequence number +GGGG = Downstream fragment number +C = Compression enabled for downstream packet + +Upstream data packet starts with 1 byte ASCII hex coded user byte, then 3 bytes +Base32 encoded header, then comes the payload data, encoded with chosen codec. + +Downstream data starts with 2 byte header. Then payload data, which may be +compressed. + +Ping: +Client sends: + First byte p or P + Rest encoded with Base32: + 1 byte with 4 bits userid + 1 byte with: + 3 bits downstream seqno + 4 bits downstream fragment + CMC + +The server response to Ping and Data packets is a DNS NULL type response: +If server has nothing to send, data length is 0 bytes. +If server has something to send, it will send a downstream data packet, +prefixed with 2 bytes header as shown above. diff --git a/iodine/doc/proto_00000502.txt b/iodine/doc/proto_00000502.txt new file mode 100644 index 0000000..46cf2de --- /dev/null +++ b/iodine/doc/proto_00000502.txt @@ -0,0 +1,287 @@ +Detailed specification of protocol in version 00000502 +====================================================== + +Note: work in progress!! + +====================================================== +1. DNS protocol +====================================================== + +Quick alphabetical index / register: + 0-9 Data packet + A-F Data packet + I IP address + L Login + N Downstream fragsize (NS.topdomain A-type reply) + O Options + P Ping + R Downstream fragsize probe + S Switch upstream codec + V Version + W (WWW.topdomain A-type reply) + Y Downstream codec check + Z Upstream codec check + + +CMC = 2 byte Cache Miss Counter, increased every time it is used + +Version: +Client sends: + First byte v or V + Rest encoded with base32: + 4 bytes big endian protocol version + CMC +Server replies: + 4 chars: + VACK (version ok), followed by login challenge + VNAK (version differs), followed by server protocol version + VFUL (server has no free slots), followed by max users + 4 byte value: means login challenge/server protocol version/max users + 1 byte userid of the new user, or any byte if not VACK + +Login: +Client sends: + First byte l or L + Rest encoded with base32: + 1 byte userid + 16 bytes MD5 hash of: (first 32 bytes of password) xor (8 repetitions of login challenge) + CMC +Server replies: + LNAK means not accepted + x.x.x.x-y.y.y.y-mtu-netmask means accepted (server ip, client ip, mtu, netmask bits) + +IP Request: +Client sends: + First byte i or I + 5 bits coded as Base32 char, meaning userid + CMC as 3 Base32 chars +Server replies + BADIP if bad userid, or + I and then 4 bytes network order external IP address of iodined server + +Upstream codec check / bounce: +Client sends: + First byte z or Z + Lots of data that should not be decoded +Server replies: + The requested domain copied raw, in the lowest-grade downstream codec + available for the request type. + +Downstream codec check: +Client sends: + First byte y or Y + 1 char, meaning downstream codec to use + 5 bits coded as Base32 char, meaning check variant + CMC as 3 Base32 chars + Possibly extra data, depending on check variant +Server sends: + Data encoded with requested downstream codec; data content depending + on check variant number. + BADCODEC if requested downstream codec not available. + BADLEN if check variant is not available, or problem with extra data. + + Downstream codec chars are same as in 'O' Option request, below. + + Check variants: + 1: Send encoded DOWNCODECCHECK1 string as defined in encoding.h + + (Other variants reserved; possibly variant that sends a decoded-encoded + copy of Base32-encoded extra data in the request) + +Switch codec: +Client sends: + First byte s or S + 5 bits coded as Base32 char, meaning userid + 5 bits coded as Base32 char, representing number of raw bits per + encoded byte: + 5: Base32 (a-z0-5) + 6: Base64 (a-zA-Z0-9+-) + 26: Base64u (a-zA-Z0-9_-) + 7: Base128 (a-zA-Z0-9\274-\375) + CMC as 3 Base32 chars +Server sends: + Name of codec if accepted. After this all upstream data packets must + be encoded with the new codec. + BADCODEC if not accepted. Client must then revert to previous codec + BADLEN if length of query is too short + +Options: +Client sends: + First byte o or O + 5 bits coded as Base32 char, meaning userid + 1 char, meaning option + CMC as 3 Base32 chars +Server sends: + Full name of option if accepted. After this, option immediately takes + effect in server. + BADCODEC if not accepted. Previous situation remains. + All options affect only the requesting client. + + Option chars: + t or T: Downstream encoding Base32, for TXT/CNAME/A/MX (default) + s or S: Downstream encoding Base64, for TXT/CNAME/A/MX + u or U: Downstream encoding Base64u, for TXT/CNAME/A/MX + v or V: Downstream encoding Base128, for TXT/CNAME/A/MX + r or R: Downstream encoding Raw, for TXT/NULL (default for NULL) + If codec unsupported for request type, server will use Base32; note + that server will answer any mix of request types that a client sends. + Server may disregard this option; client must always use the downstream + encoding type indicated in every downstream DNS packet. + + l or L: Lazy mode, server will keep one request unanswered until the + next one comes in. Applies only to data transfer; handshake is always + answered immediately. + i or I: Immediate (non-lazy) mode, server will answer all requests + (nearly) immediately. + +Probe downstream fragment size: +Client sends: + First byte r or R + 15 bits coded as 3 Base32 chars: UUUUF FFFFF FFFFF + meaning 4 bits userid, 11 bits fragment size + Then follows a long random query which contents does not matter +Server sends: + Requested number of bytes as a response. The first two bytes contain + the requested length. The third byte is 107 (0x6B). The fourth byte + is a random value, and each following byte is incremented with 107. + This is checked by the client to determine corruption. + BADFRAG if requested length not accepted. + +Set downstream fragment size: +Client sends: + First byte n or N + Rest encoded with base32: + 1 byte userid + 2 bytes new downstream fragment size + CMC +Server sends: + 2 bytes new downstream fragment size. After this all downstream + payloads will be max (fragsize + 2) bytes long. + BADFRAG if not accepted. + +Data: +Upstream data header: + 3210 432 10 43 210 4321 0 43210 + +----+---+--+--+---+----+-+-----+ + |UUUU|SSS|FF|FF|DDD|GGGG|L|UDCMC| + +----+---+--+--+---+----+-+-----+ + +Downstream data header: + 7 654 3210 765 4321 0 + +-+---+----+---+----+-+ + |C|SSS|FFFF|DDD|GGGG|L| + +-+---+----+---+----+-+ + +UUUU = Userid +L = Last fragment in packet flag +SS = Upstream packet sequence number +FFFF = Upstream fragment number +DDD = Downstream packet sequence number +GGGG = Downstream fragment number +C = Compression enabled for downstream packet +UDCMC = Upstream Data CMC, 36 steps a-z0-9, case-insensitive + +Upstream data packet starts with 1 byte ASCII hex coded user byte; then 3 bytes +Base32 encoded header; then 1 char data-CMC; then comes the payload data, +encoded with the chosen upstream codec. + +Downstream data starts with 2 byte header. Then payload data, which may be +compressed. + +In NULL responses, downstream data is always raw. In all other response types, +downstream data is encoded (see Options above). +Encoding type is indicated by 1 prefix char: +TXT: + End result is always DNS-chopped (series of len-prefixed strings + <=255 bytes) + t or T: Base32 encoded before chop, decoded after un-chop + s or S: Base64 encoded before chop, decoded after un-chop + u or U: Base64u encoded before chop, decoded after un-chop + v or V: Base128 encoded before chop, decoded after un-chop + r or R: Raw no encoding, only DNS-chop +SRV/MX/CNAME/A: + h or H: Hostname encoded with Base32 + i or I: Hostname encoded with Base64 + j or J: Hostname encoded with Base64u + k or K: Hostname encoded with Base128 +SRV and MX may reply with multiple hostnames, each encoded separately. Each +has a 10-multiple priority, and encoding/decoding is done in strictly +increasing priority sequence 10, 20, 30, etc. without gaps. Note that some DNS +relays will shuffle the answer records in the response. + +Ping: +Client sends: + First byte p or P + Rest encoded with Base32: + 1 byte with 4 bits userid + 1 byte with: + 3 bits downstream seqno + 4 bits downstream fragment + CMC + +The server response to Ping and Data packets is a DNS NULL/TXT/.. type response, +always starting with the 2 bytes downstream data header as shown above. +If server has nothing to send, no data is added after the header. +If server has something to send, it will add the downstream data packet +(or some fragment of it) after the header. + + +"Lazy-mode" operation +===================== + +Client-server DNS traffic sequence has been reordered to provide increased +(interactive) performance and greatly reduced latency. + +Idea taken from Lucas Nussbaum's slides (24th IFIP International Security +Conference, 2009) at http://www.loria.fr/~lnussbau/tuns.html. Current +implementation is original to iodine, no code or documentation from any other +project was consulted during development. + +Server: +Upstream data is acked immediately*, to keep the slow upstream data flowing +as fast as possible (client waits for ack to send next frag). + +Upstream pings are answered _only_ when 1) downstream data arrives from tun, +OR 2) new upstream ping/data arrives from client. +In most cases, this means we answer the previous DNS query instead of the +current one. The current query is kept in queue and used as soon as +downstream data has to be sent. + +*: upstream data ack is usually done as reply on the previous ping packet, +and the upstream-data packet itself is kept in queue. + +Client: +Downstream data is acked immediately, to keep it flowing fast (includes a +ping after last downstream frag). + +Also, after all available upstream data is sent & acked by the server (which +in some cases uses up the last query), send an additional ping to prime the +server for the next downstream data. + + +====================================================== +2. Raw UDP protocol +====================================================== + +All Raw UDP protcol messages start with a 3 byte header: 0x10d19e +This is not the start of a valid DNS message so it is easy to identify. +The fourth byte contains the command and the user id. + + 7654 3210 + +----+----+ + |CCCC|UUUU| + +----+----+ + +Login message (command = 1): +The header is followed by a MD5 hash with the same password as in the DNS +login. The client starts the raw mode by sending this message, and uses +the login challenge +1, and the server responds using the login challenge -1. +After the login message has been exchanged, both the server and the client +switch to raw udp mode for the rest of the connection. + +Data message (command = 2): +After the header comes the payload data, which may be compressed. + +Ping message (command = 3): +Sent from client to server and back to keep session open. Has no payload. + diff --git a/iodine/man/iodine.8 b/iodine/man/iodine.8 new file mode 100644 index 0000000..6eee603 --- /dev/null +++ b/iodine/man/iodine.8 @@ -0,0 +1,338 @@ +.\" groff -man -Tascii iodine.8 +.TH IODINE 8 "DEC 2009" "User Manuals" +.SH NAME +iodine, iodined \- tunnel IPv4 over DNS +.SH SYNOPSIS +.B iodine [-v] + +.B iodine [-h] + +.B iodine [-f] [-r] [-u +.I user +.B ] [-P +.I password +.B ] [-m +.I fragsize +.B ] [-t +.I chrootdir +.B ] [-d +.I device +.B ] [-m +.I fragsize +.B ] [-M +.I namelen +.B ] [-z +.I context +.B ] [-F +.I pidfile +.B ] [-T +.I dnstype +.B ] [-O +.I downenc +.B ] [-L +.I 0|1 +.B ] [-I +.I interval +.B ] +.B [ +.I nameserver +.B ] +.I topdomain + +.B iodined [-v] + +.B iodined [-h] + +.B iodined [-c] [-s] [-f] [-D] [-u +.I user +.B ] [-t +.I chrootdir +.B ] [-d +.I device +.B ] [-m +.I mtu +.B ] [-l +.I listen_ip +.B ] [-p +.I port +.B ] [-n +.I external_ip +.B ] [-b +.I dnsport +.B ] [-P +.I password +.B ] [-z +.I context +.B ] [-F +.I pidfile +.B ] +.I tunnel_ip +.B [ +.I /netmask +.B ] +.I topdomain +.SH DESCRIPTION +.B iodine +lets you tunnel IPv4 data through a DNS +server. This can be useful in situations where Internet access is firewalled, +but DNS queries are allowed. It needs a TUN/TAP device to operate. The +bandwidth is asymmetrical, +with a measured maximum of 680 kbit/s upstream and 2.3 Mbit/s +downstream in a wired LAN test network. +Realistic sustained throughput on a Wifi network using a carrier-grade +DNS cache has been measured at some 50 kbit/s upstream and over 200 kbit/s +downstream. +.B iodine +is the client application, +.B iodined +is the server. + +Note: server and client are required to speak the exact same protocol. In most +cases, this means running the same iodine version. Unfortunately, implementing +backward and forward protocol compatibility is usually not feasible. +.SH OPTIONS +.SS Common Options: +.TP +.B -v +Print version info and exit. +.TP +.B -h +Print usage info and exit. +.TP +.B -f +Keep running in foreground. +.TP +.B -u user +Drop privileges and run as user 'user' after setting up tunnel. +.TP +.B -t chrootdir +Chroot to 'chrootdir' after setting up tunnel. +.TP +.B -d device +Use the TUN device 'device' instead of the normal one, which is dnsX on Linux +and otherwise tunX. +.TP +.B -P password +Use 'password' to authenticate. If not used, +.B stdin +will be used as input. Only the first 32 characters will be used. +.TP +.B -z context +Apply SELinux 'context' after initialization. +.TP +.B -F pidfile +Create 'pidfile' and write process id in it. +.SS Client Options: +.TP +.B -r +Skip raw UDP mode. If not used, iodine will try getting the public IP address +of the iodined host and test if it is reachable directly. If it is, traffic +will be sent to the server instead of the DNS relay. +.TP +.B -m fragsize +Force maximum downstream fragment size. Not setting this will cause the +client to automatically probe the maximum accepted downstream fragment size. +.TP +.B -M namelen +Maximum length of upstream hostnames, default 255. +Usable range ca. 100 to 255. +Use this option to scale back upstream bandwidth in favor of downstream +bandwidth. +Also useful for DNS servers that perform unreliably when using full-length +hostnames, noticable when fragment size autoprobe returns very +different results each time. +.TP +.B -T dnstype +DNS request type override. +By default, autodetection will probe for working DNS request types, and +will select the request type that is expected to provide the most bandwidth. +However, it may turn out that a DNS relay imposes limits that skew the +picture, which may lead to an "unexpected" DNS request type providing +more bandwidth. +In that case, use this option to override the autodetection. +In (expected) decreasing bandwidth order, the supported DNS request types are: +.IR NULL , +.IR TXT , +.IR SRV , +.IR MX , +.I CNAME +and +.I A +(returning CNAME). +Note that +.IR SRV , +.I MX +and +.I A +may/will cause additional lookups by "smart" caching +nameservers to get an actual IP address, which may either slow down or fail +completely. +.TP +.B -O downenc +Force downstream encoding type for all query type responses except NULL. +Default is autodetected, but may not spot all problems for the more advanced +codecs. +Use this option to override the autodetection. +.I Base32 +is the lowest-grade codec and should always work; this is used when +autodetection fails. +.I Base64 +provides more bandwidth, but may not work on all nameservers. +.I Base64u +is equal to Base64 except in using underscore ('_') +instead of plus sign ('+'), possibly working where +.I Base64 +does not. +.I Base128 +uses high byte values (mostly accented letters in iso8859-1), +which might work with some nameservers. +For TXT queries, +.I Raw +will provide maximum performance, but this will only work if the nameserver +path is fully 8-bit-clean for responses that are assumed to be "legible text". +.TP +.B -L 0|1 +Lazy-mode switch. +\-L1 (default): Use lazy mode for improved performance and decreased latency. +A very small minority of DNS relays appears to be unable to handle the +lazy mode traffic pattern, resulting in no or very little data coming through. +The iodine client will detect this and try to switch back to legacy mode, +but this may not always work. +In these situations use \-L0 to force running in legacy mode +(implies \-I1). +.TP +.B -I interval +Maximum interval between requests (pings) so that intermediate DNS +servers will not time out. Default is 4 in lazy mode, which will work +fine in most cases. When too many SERVFAIL errors occur, iodine +will automatically reduce this to 1. +To get absolute minimum DNS traffic, +increase well above 4, but not so high that SERVFAIL errors start to occur. +There are some DNS relays with very small timeouts, +notably dnsadvantage.com (ultradns), that will give +SERVFAIL errors even with \-I1; data will still get trough, +and these errors can be ignored. +Maximum useful value is 59, since iodined will close a client's +connection after 60 seconds of inactivity. +.SS Server Options: +.TP +.B -c +Disable checking the client IP address on all incoming requests. +By default, requests originating from non-matching IP adresses will be +rejected, however this will cause problems when requests are routed +via a cluster of DNS servers. +.TP +.B -s +Don't try to configure IP address or MTU. +This should only be used if you have already configured the device that will be +used. +.TP +.B -D +Increase debug level. Level 1 prints info about each RX/TX packet. +Implies the +.B -f +option. +On level 2 (-DD) or higher, DNS queries will be printed literally. +When using Base128 upstream encoding, this is best viewed as +ISO Latin-1 text instead of (illegal) UTF-8. +This is easily done with : "LC_ALL=C luit iodined -DD ..." +(see luit(1)). +.TP +.B -m mtu +Set 'mtu' as mtu size for the tun device. +This will be sent to the client on login, and the client will use the same mtu +for its tun device. Default 1130. Note that the DNS traffic will be +automatically fragmented when needed. +.TP +.B -l listen_ip +Make the server listen only on 'listen_ip' for incoming requests. +By default, incoming requests are accepted from all interfaces. +.TP +.B -p port +Make the server listen on 'port' instead of 53 for traffic. +.B Note: +You must make sure the dns requests are forwarded to this port yourself. +.TP +.B -n external_ip +The IP address to return in NS responses. Default is to return the address used +as destination in the query. +.TP +.B -b dnsport +If this port is specified, all incoming requests not inside the tunnel domain +will be forwarded to this port on localhost, to be handled by a real dns. +.B Note: +The forwarding is not fully transparent, and not advised for use +in production environments. +.SS Client Arguments: +.TP +.B nameserver +The nameserver to use to relay the dns traffic. This can be any relaying +nameserver or the server running iodined if reachable. This field can be +given as an IP address, or as a hostname. This argument is optional, and +if not specified a nameserver will be read from the +.I /etc/resolv.conf +file. +.TP +.B topdomain +The dns traffic will be sent as queries for subdomains under +\'topdomain'. This is normally a subdomain to a domain you own. Use a short +domain name to get better throughput. If +.B nameserver +is the iodined server, then the topdomain can be chosen freely. This argument +must be the same on both the client and the server. +.SS Server Arguments: +.TP +.B tunnel_ip[/netmask] +This is the server's ip address on the tun interface. The client will be +given the next ip number in the range. It is recommended to use the +10.0.0.0 or 172.16.0.0 ranges. The default netmask is /27, can be overriden +by specifying it here. Using a smaller network will limit the number of +concurrent users. +.TP +.B topdomain +The dns traffic is expected to arrive as queries for +subdomains under 'topdomain'. This is normally a subdomain to a domain you +own. Use a short domain name to get better throughput. This argument must be +the same on both the client and the server. Queries for domains other +than 'topdomain' will be forwarded when the \-b option is given, otherwise +they will be dropped. +.SH EXAMPLES +See the README file for both a quick test scenario, and a detailed description +of real-world deployment. +.SH SECURITY +Login is a relatively secure challenge-response MD5 hash, with the +password never passing the wire. +However, all other data is +.B NOT +encrypted in any way. The DNS traffic is also vulnerable to replay, +injection and man-in-the-middle attacks, especially when iodined is used +with the \-c option. Use of ssh or vpn tunneling is strongly recommended. +On both server and client, use +.IR iptables , +.I pf +or other firewalls to block all traffic coming in from the tun interfaces, +except to the used ssh or vpn ports. +.SH ENVIRONMENT +.SS IODINE_PASS +If the environment variable +.B IODINE_PASS +is set, iodine will use the value it is set to as password instead of asking +for one. The +.B -P +option still has precedence. +.SS IODINED_PASS +If the environment variable +.B IODINED_PASS +is set, iodined will use the value it is set to as password instead of asking +for one. The +.B -P +option still has precedence. +.El +.SH SEE ALSO +The README file in the source distribution contains some more elaborate +information. +.SH BUGS +File bugs at http://dev.kryo.se/iodine/ +.SH AUTHORS +Erik Ekman and Bjorn Andersson . Major +contributions by Anne Bezemer. diff --git a/iodine/src/Makefile b/iodine/src/Makefile new file mode 100644 index 0000000..a5b9838 --- /dev/null +++ b/iodine/src/Makefile @@ -0,0 +1,46 @@ +COMMONOBJS = tun.o dns.o read.o encoding.o login.o base32.o base64.o base64u.o base128.o md5.o common.o +CLIENTOBJS = iodine.o client.o util.o +CLIENT = ../bin/iodine +SERVEROBJS = iodined.o user.o fw_query.o +SERVER = ../bin/iodined + +OS = `echo $(TARGETOS) | tr "a-z" "A-Z"` +ARCH = `uname -m` + +LIBPATH = -L. +LDFLAGS += -lz `sh osflags $(TARGETOS) link` $(LIBPATH) +CFLAGS += -c -g -Wall -D$(OS) -pedantic `sh osflags $(TARGETOS) cflags` + +all: stateos $(CLIENT) $(SERVER) + +stateos: + @echo OS is $(OS), arch is $(ARCH) + +$(CLIENT): $(COMMONOBJS) $(CLIENTOBJS) + @echo LD $@ + @mkdir -p ../bin + @$(CC) $(COMMONOBJS) $(CLIENTOBJS) -o $(CLIENT) $(LDFLAGS) + +$(SERVER): $(COMMONOBJS) $(SERVEROBJS) + @echo LD $@ + @mkdir -p ../bin + @$(CC) $(COMMONOBJS) $(SERVEROBJS) -o $(SERVER) $(LDFLAGS) + +.c.o: + @echo CC $< + @$(CC) $(CFLAGS) $< -o $@ + +base64u.o client.o iodined.o: base64u.h +base64u.c: base64.c + @echo Making $@ + @echo '/* No use in editing, produced by Makefile! */' > $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.c >> $@ +base64u.h: base64.h + @echo Making $@ + @echo '/* No use in editing, produced by Makefile! */' > $@ + @sed -e 's/\([Bb][Aa][Ss][Ee]64\)/\1u/g ; s/0123456789+/0123456789_/' < base64.h >> $@ + +clean: + @echo "Cleaning src/" + @rm -f $(CLIENT){,.exe} $(SERVER){,.exe} *~ *.o *.core base64u.* + diff --git a/iodine/src/base128.c b/iodine/src/base128.c new file mode 100644 index 0000000..32a29f8 --- /dev/null +++ b/iodine/src/base128.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) 2009 J.A.Bezemer@opensourcepartners.nl + * + * 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. + */ + +/* + * raw 76543210 76543210 76543210 76543210 76543210 76543210 76543210 + * enc 65432106 54321065 43210654 32106543 21065432 10654321 06543210 + * ^ ^ ^ ^ ^ ^ ^ ^ + * + * 0001 1 0001 1 + * 0011 3 0011 3 + * 0111 7 0111 7 + * 1111 f 0110 6 + * 1110 e 0100 4 + * 1100 c + * 1000 8 + */ + +#include +#include +#include + +#include "encoding.h" +#include "base128.h" + +#define BLKSIZE_RAW 7 +#define BLKSIZE_ENC 8 + +/* Don't use '-' (restricted to middle of labels), prefer iso_8859-1 + * accent chars since they might readily be entered in normal use, + * don't use 254-255 because of possible function overloading in DNS systems. + */ +static const unsigned char cb128[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + "\274\275\276\277" + "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317" + "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" + "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357" + "\360\361\362\363\364\365\366\367\370\371\372\373\374\375"; +static unsigned char rev128[256]; +static int reverse_init = 0; + +static int base128_encode(char *, size_t *, const void *, size_t); +static int base128_decode(void *, size_t *, const char *, size_t); +static int base128_handles_dots(); +static int base128_blksize_raw(); +static int base128_blksize_enc(); + +static struct encoder base128_encoder = +{ + "Base128", + base128_encode, + base128_decode, + base128_handles_dots, + base128_handles_dots, + base128_blksize_raw, + base128_blksize_enc +}; + +struct encoder +*get_base128_encoder() +{ + return &base128_encoder; +} + +static int +base128_handles_dots() +{ + return 0; +} + +static int +base128_blksize_raw() +{ + return BLKSIZE_RAW; +} + +static int +base128_blksize_enc() +{ + return BLKSIZE_ENC; +} + +inline static void +base128_reverse_init() +{ + int i; + unsigned char c; + + if (!reverse_init) { + memset (rev128, 0, 256); + for (i = 0; i < 128; i++) { + c = cb128[i]; + rev128[(int) c] = i; + } + reverse_init = 1; + } +} + +static int +base128_encode(char *buf, size_t *buflen, const void *data, size_t size) +/* + * Fills *buf with max. *buflen characters, encoding size bytes of *data. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold the trailing '\0'. + * + * return value : #bytes filled in buf (excluding \0) + * sets *buflen to : #bytes encoded from data + */ +{ + unsigned char *ubuf = (unsigned char *) buf; + unsigned char *udata = (unsigned char *) data; + int iout = 0; /* to-be-filled output char */ + int iin = 0; /* one more than last input byte that can be + successfully decoded */ + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0xfe) >> 1)]; + iout++; + + if (iout >= *buflen || iin >= size) { + iout--; /* previous char is useless */ + break; + } + ubuf[iout] = cb128[((udata[iin] & 0x01) << 6) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xfc) >> 2) : 0)]; + iin++; /* 0 complete, iin=1 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0x03) << 5) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xf8) >> 3) : 0)]; + iin++; /* 1 complete, iin=2 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0x07) << 4) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xf0) >> 4) : 0)]; + iin++; /* 2 complete, iin=3 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0x0f) << 3) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xe0) >> 5) : 0)]; + iin++; /* 3 complete, iin=4 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0x1f) << 2) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xc0) >> 6) : 0)]; + iin++; /* 4 complete, iin=5 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[((udata[iin] & 0x3f) << 1) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0x80) >> 7) : 0)]; + iin++; /* 5 complete, iin=6 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + ubuf[iout] = cb128[(udata[iin] & 0x7f)]; + iin++; /* 6 complete, iin=7 */ + iout++; + } + + ubuf[iout] = '\0'; + + /* store number of bytes from data that was used */ + *buflen = iin; + + return iout; +} + +#define REV128(x) rev128[(int) (x)] + +static int +base128_decode(void *buf, size_t *buflen, const char *str, size_t slen) +/* + * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. + * Decoding stops early when *str contains \0. + * Illegal encoded chars are assumed to decode to zero. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold a trailing '\0' that is added (though *buf will usually + * contain full-binary data). + * + * return value : #bytes filled in buf (excluding \0) + */ +{ + unsigned char *ustr = (unsigned char *) str; + unsigned char *ubuf = (unsigned char *) buf; + int iout = 0; /* to-be-filled output byte */ + int iin = 0; /* next input char to use in decoding */ + + base128_reverse_init (); + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x7f) << 1) | + ((REV128(ustr[iin + 1]) & 0x40) >> 6); + iin++; /* 0 used up, iin=1 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x3f) << 2) | + ((REV128(ustr[iin + 1]) & 0x60) >> 5); + iin++; /* 1 used up, iin=2 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x1f) << 3) | + ((REV128(ustr[iin + 1]) & 0x70) >> 4); + iin++; /* 2 used up, iin=3 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x0f) << 4) | + ((REV128(ustr[iin + 1]) & 0x78) >> 3); + iin++; /* 3 used up, iin=4 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x07) << 5) | + ((REV128(ustr[iin + 1]) & 0x7c) >> 2); + iin++; /* 4 used up, iin=5 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x03) << 6) | + ((REV128(ustr[iin + 1]) & 0x7e) >> 1); + iin++; /* 5 used up, iin=6 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV128(ustr[iin]) & 0x01) << 7) | + ((REV128(ustr[iin + 1]) & 0x7f)); + iin += 2; /* 6,7 used up, iin=8 */ + iout++; + } + + ubuf[iout] = '\0'; + + return iout; +} diff --git a/iodine/src/base128.h b/iodine/src/base128.h new file mode 100644 index 0000000..235b2f9 --- /dev/null +++ b/iodine/src/base128.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2009 J.A.Bezemer@opensourcepartners.nl + * + * 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 __BASE128_H__ +#define __BASE128_H__ + +struct encoder *get_base128_encoder(void); + +#endif diff --git a/iodine/src/base32.c b/iodine/src/base32.c new file mode 100644 index 0000000..8731a92 --- /dev/null +++ b/iodine/src/base32.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * Mostly rewritten 2009 J.A.Bezemer@opensourcepartners.nl + * + * 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 +#include +#include + +#include "encoding.h" +#include "base32.h" + +#define BLKSIZE_RAW 5 +#define BLKSIZE_ENC 8 + +static const char cb32[] = + "abcdefghijklmnopqrstuvwxyz012345"; +static const char cb32_ucase[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345"; +static unsigned char rev32[256]; +static int reverse_init = 0; + +static int base32_encode(char *, size_t *, const void *, size_t); +static int base32_decode(void *, size_t *, const char *, size_t); +static int base32_handles_dots(); +static int base32_blksize_raw(); +static int base32_blksize_enc(); + +static struct encoder base32_encoder = +{ + "Base32", + base32_encode, + base32_decode, + base32_handles_dots, + base32_handles_dots, + base32_blksize_raw, + base32_blksize_enc +}; + +struct encoder +*get_base32_encoder() +{ + return &base32_encoder; +} + +static int +base32_handles_dots() +{ + return 0; +} + +static int +base32_blksize_raw() +{ + return BLKSIZE_RAW; +} + +static int +base32_blksize_enc() +{ + return BLKSIZE_ENC; +} + +inline static void +base32_reverse_init() +{ + int i; + unsigned char c; + + if (!reverse_init) { + memset (rev32, 0, 256); + for (i = 0; i < 32; i++) { + c = cb32[i]; + rev32[(int) c] = i; + c = cb32_ucase[i]; + rev32[(int) c] = i; + } + reverse_init = 1; + } +} + +int +b32_5to8(int in) +{ + return cb32[in & 31]; +} + +int +b32_8to5(int in) +{ + base32_reverse_init(); + return rev32[in]; +} + +static int +base32_encode(char *buf, size_t *buflen, const void *data, size_t size) +/* + * Fills *buf with max. *buflen characters, encoding size bytes of *data. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold the trailing '\0'. + * + * return value : #bytes filled in buf (excluding \0) + * sets *buflen to : #bytes encoded from data + */ +{ + unsigned char *udata = (unsigned char *) data; + int iout = 0; /* to-be-filled output char */ + int iin = 0; /* one more than last input byte that can be + successfully decoded */ + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb32[((udata[iin] & 0xf8) >> 3)]; + iout++; + + if (iout >= *buflen || iin >= size) { + iout--; /* previous char is useless */ + break; + } + buf[iout] = cb32[((udata[iin] & 0x07) << 2) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xc0) >> 6) : 0)]; + iin++; /* 0 complete, iin=1 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb32[((udata[iin] & 0x3e) >> 1)]; + iout++; + + if (iout >= *buflen || iin >= size) { + iout--; /* previous char is useless */ + break; + } + buf[iout] = cb32[((udata[iin] & 0x01) << 4) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xf0) >> 4) : 0)]; + iin++; /* 1 complete, iin=2 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb32[((udata[iin] & 0x0f) << 1) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0x80) >> 7) : 0)]; + iin++; /* 2 complete, iin=3 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb32[((udata[iin] & 0x7c) >> 2)]; + iout++; + + if (iout >= *buflen || iin >= size) { + iout--; /* previous char is useless */ + break; + } + buf[iout] = cb32[((udata[iin] & 0x03) << 3) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xe0) >> 5) : 0)]; + iin++; /* 3 complete, iin=4 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb32[((udata[iin] & 0x1f))]; + iin++; /* 4 complete, iin=5 */ + iout++; + } + + buf[iout] = '\0'; + + /* store number of bytes from data that was used */ + *buflen = iin; + + return iout; +} + +#define REV32(x) rev32[(int) (x)] + +static int +base32_decode(void *buf, size_t *buflen, const char *str, size_t slen) +/* + * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. + * Decoding stops early when *str contains \0. + * Illegal encoded chars are assumed to decode to zero. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold a trailing '\0' that is added (though *buf will usually + * contain full-binary data). + * + * return value : #bytes filled in buf (excluding \0) + */ +{ + unsigned char *ubuf = (unsigned char *) buf; + int iout = 0; /* to-be-filled output byte */ + int iin = 0; /* next input char to use in decoding */ + + base32_reverse_init (); + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV32(str[iin]) & 0x1f) << 3) | + ((REV32(str[iin + 1]) & 0x1c) >> 2); + iin++; /* 0 used up, iin=1 */ + iout++; + + if (iout >= *buflen || iin + 2 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0' || + str[iin + 2] == '\0') + break; + ubuf[iout] = ((REV32(str[iin]) & 0x03) << 6) | + ((REV32(str[iin + 1]) & 0x1f) << 1) | + ((REV32(str[iin + 2]) & 0x10) >> 4); + iin += 2; /* 1,2 used up, iin=3 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV32(str[iin]) & 0x0f) << 4) | + ((REV32(str[iin + 1]) & 0x1e) >> 1); + iin++; /* 3 used up, iin=4 */ + iout++; + + if (iout >= *buflen || iin + 2 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0' || + str[iin + 2] == '\0') + break; + ubuf[iout] = ((REV32(str[iin]) & 0x01) << 7) | + ((REV32(str[iin + 1]) & 0x1f) << 2) | + ((REV32(str[iin + 2]) & 0x18) >> 3); + iin += 2; /* 4,5 used up, iin=6 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV32(str[iin]) & 0x07) << 5) | + ((REV32(str[iin + 1]) & 0x1f)); + iin += 2; /* 6,7 used up, iin=8 */ + iout++; + } + + ubuf[iout] = '\0'; + + return iout; +} diff --git a/iodine/src/base32.h b/iodine/src/base32.h new file mode 100644 index 0000000..497ca33 --- /dev/null +++ b/iodine/src/base32.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __BASE32_H__ +#define __BASE32_H__ + +struct encoder *get_base32_encoder(void); + +int b32_5to8(int); +int b32_8to5(int); +#endif diff --git a/iodine/src/base64.c b/iodine/src/base64.c new file mode 100644 index 0000000..5218c09 --- /dev/null +++ b/iodine/src/base64.c @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * Mostly rewritten 2009 J.A.Bezemer@opensourcepartners.nl + * + * 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 +#include +#include + +#include "encoding.h" +#include "base64.h" + +#define BLKSIZE_RAW 3 +#define BLKSIZE_ENC 4 + +/* Note: the "unofficial" char is last here, which means that the \377 pattern + in DOWNCODECCHECK1 ('Y' request) will properly test it. */ +static const char cb64[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789+"; +static unsigned char rev64[256]; +static int reverse_init = 0; + +static int base64_encode(char *, size_t *, const void *, size_t); +static int base64_decode(void *, size_t *, const char *, size_t); +static int base64_handles_dots(); +static int base64_blksize_raw(); +static int base64_blksize_enc(); + +static struct encoder base64_encoder = +{ + "Base64", + base64_encode, + base64_decode, + base64_handles_dots, + base64_handles_dots, + base64_blksize_raw, + base64_blksize_enc +}; + +struct encoder +*get_base64_encoder() +{ + return &base64_encoder; +} + +static int +base64_handles_dots() +{ + return 0; +} + +static int +base64_blksize_raw() +{ + return BLKSIZE_RAW; +} + +static int +base64_blksize_enc() +{ + return BLKSIZE_ENC; +} + +inline static void +base64_reverse_init() +{ + int i; + unsigned char c; + + if (!reverse_init) { + memset (rev64, 0, 256); + for (i = 0; i < 64; i++) { + c = cb64[i]; + rev64[(int) c] = i; + } + reverse_init = 1; + } +} + +static int +base64_encode(char *buf, size_t *buflen, const void *data, size_t size) +/* + * Fills *buf with max. *buflen characters, encoding size bytes of *data. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold the trailing '\0'. + * + * return value : #bytes filled in buf (excluding \0) + * sets *buflen to : #bytes encoded from data + */ +{ + unsigned char *udata = (unsigned char *) data; + int iout = 0; /* to-be-filled output char */ + int iin = 0; /* one more than last input byte that can be + successfully decoded */ + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb64[((udata[iin] & 0xfc) >> 2)]; + iout++; + + if (iout >= *buflen || iin >= size) { + iout--; /* previous char is useless */ + break; + } + buf[iout] = cb64[((udata[iin] & 0x03) << 4) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xf0) >> 4) : 0)]; + iin++; /* 0 complete, iin=1 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb64[((udata[iin] & 0x0f) << 2 ) | + ((iin + 1 < size) ? + ((udata[iin + 1] & 0xc0) >> 6) : 0)]; + iin++; /* 1 complete, iin=2 */ + iout++; + + if (iout >= *buflen || iin >= size) + break; + buf[iout] = cb64[(udata[iin] & 0x3f)]; + iin++; /* 2 complete, iin=3 */ + iout++; + } + + buf[iout] = '\0'; + + /* store number of bytes from data that was used */ + *buflen = iin; + + return iout; +} + +#define REV64(x) rev64[(int) (x)] + +static int +base64_decode(void *buf, size_t *buflen, const char *str, size_t slen) +/* + * Fills *buf with max. *buflen bytes, decoded from slen chars in *str. + * Decoding stops early when *str contains \0. + * Illegal encoded chars are assumed to decode to zero. + * + * NOTE: *buf space should be at least 1 byte _more_ than *buflen + * to hold a trailing '\0' that is added (though *buf will usually + * contain full-binary data). + * + * return value : #bytes filled in buf (excluding \0) + */ +{ + unsigned char *ubuf = (unsigned char *) buf; + int iout = 0; /* to-be-filled output byte */ + int iin = 0; /* next input char to use in decoding */ + + base64_reverse_init (); + + /* Note: Don't bother to optimize manually. GCC optimizes + better(!) when using simplistic array indexing. */ + + while (1) { + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV64(str[iin]) & 0x3f) << 2) | + ((REV64(str[iin + 1]) & 0x30) >> 4); + iin++; /* 0 used up, iin=1 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV64(str[iin]) & 0x0f) << 4) | + ((REV64(str[iin + 1]) & 0x3c) >> 2); + iin++; /* 1 used up, iin=2 */ + iout++; + + if (iout >= *buflen || iin + 1 >= slen || + str[iin] == '\0' || str[iin + 1] == '\0') + break; + ubuf[iout] = ((REV64(str[iin]) & 0x03) << 6) | + (REV64(str[iin + 1]) & 0x3f); + iin += 2; /* 2,3 used up, iin=4 */ + iout++; + } + + ubuf[iout] = '\0'; + + return iout; +} diff --git a/iodine/src/base64.h b/iodine/src/base64.h new file mode 100644 index 0000000..d550cf3 --- /dev/null +++ b/iodine/src/base64.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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); + +#endif diff --git a/iodine/src/client.c b/iodine/src/client.c new file mode 100644 index 0000000..0036b0e --- /dev/null +++ b/iodine/src/client.c @@ -0,0 +1,2514 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#include +#include +#include +#endif + +#include "common.h" +#include "encoding.h" +#include "base32.h" +#include "base64.h" +#include "base64u.h" +#include "base128.h" +#include "dns.h" +#include "login.h" +#include "tun.h" +#include "version.h" +#include "client.h" + +static void handshake_lazyoff(int dns_fd); + +static int running; +static const char *password; + +static struct sockaddr_in nameserv; +static struct sockaddr_in raw_serv; +static const char *topdomain; + +static uint16_t rand_seed; + +/* Current up/downstream IP packet */ +static struct packet outpkt; +static struct packet inpkt; +int outchunkresent = 0; + +/* My userid at the server */ +static char userid; +static char userid_char; /* used when sending (lowercase) */ +static char userid_char2; /* also accepted when receiving (uppercase) */ + +/* DNS id for next packet */ +static uint16_t chunkid; +static uint16_t chunkid_prev; +static uint16_t chunkid_prev2; + +/* Base32 encoder used for non-data packets and replies */ +static struct encoder *b32; +/* Base64 etc encoders for replies */ +static struct encoder *b64; +static struct encoder *b64u; +static struct encoder *b128; + +/* The encoder used for data packets + * Defaults to Base32, can be changed after handshake */ +static struct encoder *dataenc; + +/* The encoder to use for downstream data */ +static char downenc = ' '; + +/* set query type to send */ +static unsigned short do_qtype = T_UNSET; + +/* My connection mode */ +static enum connection conn; + +static int selecttimeout; /* RFC says timeout minimum 5sec */ +static int lazymode; +static long send_ping_soon; +static time_t lastdownstreamtime; +static long send_query_sendcnt = -1; +static long send_query_recvcnt = 0; +static int hostname_maxlen = 0xFF; + +void +client_init() +{ + running = 1; + b32 = get_base32_encoder(); + b64 = get_base64_encoder(); + b64u = get_base64u_encoder(); + b128 = get_base128_encoder(); + dataenc = get_base32_encoder(); + rand_seed = ((unsigned int) rand()) & 0xFFFF; + send_ping_soon = 1; /* send ping immediately after startup */ + conn = CONN_DNS_NULL; + + chunkid = ((unsigned int) rand()) & 0xFFFF; + chunkid_prev = 0; + chunkid_prev2 = 0; + + outpkt.len = 0; + outpkt.seqno = 0; + outpkt.fragment = 0; + outchunkresent = 0; + inpkt.len = 0; + inpkt.seqno = 0; + inpkt.fragment = 0; +} + +void +client_stop() +{ + running = 0; +} + +enum connection +client_get_conn() +{ + return conn; +} + +void +client_set_nameserver(const char *cp, int port) +{ + struct in_addr addr; + + if (inet_aton(cp, &addr) != 1) { + /* try resolving if a domain is given */ + struct hostent *host; + const char *err; + host = gethostbyname(cp); + if (host != NULL && h_errno > 0) { + int i = 0; + while (host->h_addr_list[i] != 0) { + addr = *(struct in_addr *) host->h_addr_list[i++]; + fprintf(stderr, "Resolved %s to %s\n", cp, inet_ntoa(addr)); + goto setaddr; + } + } +#ifndef WINDOWS32 + err = hstrerror(h_errno); +#else + { + DWORD wserr = WSAGetLastError(); + switch (wserr) { + case WSAHOST_NOT_FOUND: + err = "Host not found"; + break; + case WSANO_DATA: + err = "No data record found"; + break; + default: + err = "Unknown error"; + break; + } + } +#endif /* !WINDOWS32 */ + errx(1, "error resolving nameserver '%s': %s", cp, err); + } + +setaddr: + memset(&nameserv, 0, sizeof(nameserv)); + nameserv.sin_family = AF_INET; + nameserv.sin_port = htons(port); + nameserv.sin_addr = addr; +} + +void +client_set_topdomain(const char *cp) +{ + topdomain = cp; +} + +void +client_set_password(const char *cp) +{ + password = cp; +} + +void +set_qtype(char *qtype) +{ + if (!strcasecmp(qtype, "NULL")) + do_qtype = T_NULL; + else if (!strcasecmp(qtype, "CNAME")) + do_qtype = T_CNAME; + else if (!strcasecmp(qtype, "A")) + do_qtype = T_A; + else if (!strcasecmp(qtype, "MX")) + do_qtype = T_MX; + else if (!strcasecmp(qtype, "SRV")) + do_qtype = T_SRV; + else if (!strcasecmp(qtype, "TXT")) + do_qtype = T_TXT; +} + +char * +get_qtype() +{ + char *c = "UNDEFINED"; + + if (do_qtype == T_NULL) c = "NULL"; + else if (do_qtype == T_CNAME) c = "CNAME"; + else if (do_qtype == T_A) c = "A"; + else if (do_qtype == T_MX) c = "MX"; + else if (do_qtype == T_SRV) c = "SRV"; + else if (do_qtype == T_TXT) c = "TXT"; + + return c; +} + +void +set_downenc(char *encoding) +{ + if (!strcasecmp(encoding, "base32")) + downenc = 'T'; + else if (!strcasecmp(encoding, "base64")) + downenc = 'S'; + else if (!strcasecmp(encoding, "base64u")) + downenc = 'U'; + else if (!strcasecmp(encoding, "base128")) + downenc = 'V'; + else if (!strcasecmp(encoding, "raw")) + downenc = 'R'; +} + +void +client_set_selecttimeout(int select_timeout) +{ + selecttimeout = select_timeout; +} + +void +client_set_lazymode(int lazy_mode) +{ + lazymode = lazy_mode; +} + +void +client_set_hostname_maxlen(int i) +{ + if (i <= 0xFF) + hostname_maxlen = i; +} + +const char * +client_get_raw_addr() +{ + return inet_ntoa(raw_serv.sin_addr); +} + +static void +send_query(int fd, char *hostname) +{ + char packet[4096]; + struct query q; + size_t len; + + chunkid_prev2 = chunkid_prev; + chunkid_prev = chunkid; + chunkid += 7727; + if (chunkid == 0) + /* 0 is used as "no-query" in iodined.c */ + chunkid = 7727; + + q.id = chunkid; + q.type = do_qtype; + + len = dns_encode(packet, sizeof(packet), &q, QR_QUERY, hostname, strlen(hostname)); + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + +#if 0 + fprintf(stderr, " Sendquery: id %5d name[0] '%c'\n", q.id, hostname[0]); +#endif + + sendto(fd, packet, len, 0, (struct sockaddr*)&nameserv, sizeof(nameserv)); + + /* There are DNS relays that time out quickly but don't send anything + back on timeout. + And there are relays where, in lazy mode, our new query apparently + _replaces_ our previous query, and we get no answers at all in + lazy mode while legacy immediate-ping-pong works just fine. + Here we detect and fix these situations. + (Can't very well do this anywhere else; this is the only place + we'll reliably get to in such situations.) + */ + + if (send_query_sendcnt >= 0 && send_query_sendcnt < 100 && lazymode) { + send_query_sendcnt++; + + if ((send_query_sendcnt > 6 && send_query_recvcnt <= 0) || + (send_query_sendcnt > 10 && + 4 * send_query_recvcnt < send_query_sendcnt)) { + if (selecttimeout > 1) { + warnx("Receiving too few answers. Setting interval to 1 (-I1)"); + selecttimeout = 1; + /* restart counting */ + send_query_sendcnt = 0; + send_query_recvcnt = 0; + } else if (lazymode) { + warnx("Receiving too few answers. Will try to switch lazy mode off, but that may not always work any more. Start with -L0 next time on this network."); + lazymode = 0; + selecttimeout = 1; + handshake_lazyoff(fd); + } + } + } +} + +static void +send_raw(int fd, char *buf, int buflen, int user, int cmd) +{ + char packet[4096]; + int len; + + len = MIN(sizeof(packet) - RAW_HDR_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); + + sendto(fd, packet, len, 0, (struct sockaddr*)&raw_serv, sizeof(raw_serv)); +} + +static void +send_raw_data(int dns_fd) +{ + send_raw(dns_fd, outpkt.data, outpkt.len, userid, RAW_HDR_CMD_DATA); + outpkt.len = 0; +} + + +static void +send_packet(int fd, char cmd, const char *data, const size_t datalen) +{ + char buf[4096]; + + buf[0] = cmd; + + build_hostname(buf + 1, sizeof(buf) - 1, data, datalen, topdomain, + b32, hostname_maxlen); + send_query(fd, buf); +} + +static inline int +is_sending() +{ + return (outpkt.len != 0); +} + +static void +send_chunk(int fd) +{ + char buf[4096]; + int avail; + int code; + char *p; + static int datacmc = 0; + char *datacmcchars = "abcdefghijklmnopqrstuvwxyz0123456789"; + + p = outpkt.data; + p += outpkt.offset; + avail = outpkt.len - outpkt.offset; + + /* Note: must be same, or smaller than send_fragsize_probe() */ + outpkt.sentlen = build_hostname(buf + 5, sizeof(buf) - 5, p, avail, + topdomain, dataenc, hostname_maxlen); + + /* Build upstream data header (see doc/proto_xxxxxxxx.txt) */ + + buf[0] = userid_char; /* First byte is hex userid */ + + code = ((outpkt.seqno & 7) << 2) | ((outpkt.fragment & 15) >> 2); + buf[1] = b32_5to8(code); /* Second byte is 3 bits seqno, 2 upper bits fragment count */ + + code = ((outpkt.fragment & 3) << 3) | (inpkt.seqno & 7); + buf[2] = b32_5to8(code); /* Third byte is 2 bits lower fragment count, 3 bits downstream packet seqno */ + + code = ((inpkt.fragment & 15) << 1) | (outpkt.sentlen == avail); + buf[3] = b32_5to8(code); /* Fourth byte is 4 bits downstream fragment count, 1 bit last frag flag */ + + buf[4] = datacmcchars[datacmc]; /* Fifth byte is data-CMC */ + datacmc++; + if (datacmc >= 36) + datacmc = 0; + +#if 0 + fprintf(stderr, " Send: down %d/%d up %d/%d, %d bytes\n", + inpkt.seqno, inpkt.fragment, outpkt.seqno, outpkt.fragment, + outpkt.sentlen); +#endif + + send_query(fd, buf); +} + +static void +send_ping(int fd) +{ + if (conn == CONN_DNS_NULL) { + char data[4]; + + data[0] = userid; + data[1] = ((inpkt.seqno & 7) << 4) | (inpkt.fragment & 15); + data[2] = (rand_seed >> 8) & 0xff; + data[3] = (rand_seed >> 0) & 0xff; + + rand_seed++; + +#if 0 + fprintf(stderr, " Send: down %d/%d (ping)\n", + inpkt.seqno, inpkt.fragment); +#endif + + send_packet(fd, 'p', data, sizeof(data)); + } else { + send_raw(fd, NULL, 0, userid, RAW_HDR_CMD_PING); + } +} + +static void +write_dns_error(struct query *q, int ignore_some_errors) +/* This is called from: + 1. handshake_waitdns() when already checked that reply fits to our + latest query. + 2. tunnel_dns() when already checked that reply is for our ping or data + packet, but not necessarily the most recent (SERVFAIL mostly comes + after long delay). + So ignorable errors are never printed. +*/ +{ + if (!q) return; + + switch (q->rcode) { + case NOERROR: /* 0 */ + if (!ignore_some_errors) + warnx("Got reply without error, but also without question and/or answer"); + break; + case FORMERR: /* 1 */ + warnx("Got FORMERR as reply: server does not understand our request"); + break; + case SERVFAIL: /* 2 */ + if (!ignore_some_errors) + warnx("Got SERVFAIL as reply: server failed or recursion timeout"); + break; + case NXDOMAIN: /* 3 */ + warnx("Got NXDOMAIN as reply: domain does not exist"); + break; + case NOTIMP: /* 4 */ + warnx("Got NOTIMP as reply: server does not support our request"); + break; + case REFUSED: /* 5 */ + warnx("Got REFUSED as reply"); + break; + default: + warnx("Got RCODE %u as reply", q->rcode); + break; + } +} + +static int +dns_namedec(char *outdata, int outdatalen, char *buf, int buflen) +/* Decodes *buf to *outdata. + * *buf WILL be changed by undotify. + * Note: buflen must be _exactly_ strlen(buf) before undotifying. + * (undotify of reduced-len won't copy \0, base-X decode will decode too much.) + * Returns #bytes usefully filled in outdata. + */ +{ + size_t outdatalenu = outdatalen; + + switch (buf[0]) { + case 'h': /* Hostname with base32 */ + case 'H': + /* Need 1 byte H, 3 bytes ".xy", >=1 byte data */ + if (buflen < 5) + return 0; + + /* this also does undotify */ + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, + b32); + + case 'i': /* Hostname++ with base64 */ + case 'I': + /* Need 1 byte I, 3 bytes ".xy", >=1 byte data */ + if (buflen < 5) + return 0; + + /* this also does undotify */ + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, + b64); + + case 'j': /* Hostname++ with base64u */ + case 'J': + /* Need 1 byte J, 3 bytes ".xy", >=1 byte data */ + if (buflen < 5) + return 0; + + /* this also does undotify */ + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, + b64u); + + case 'k': /* Hostname++ with base128 */ + case 'K': + /* Need 1 byte J, 3 bytes ".xy", >=1 byte data */ + if (buflen < 5) + return 0; + + /* this also does undotify */ + return unpack_data(outdata, outdatalen, buf + 1, buflen - 4, + b128); + + case 't': /* plain base32(Thirty-two) from TXT */ + case 'T': + if (buflen < 2) + return 0; + + return b32->decode(outdata, &outdatalenu, buf + 1, buflen - 1); + + case 's': /* plain base64(Sixty-four) from TXT */ + case 'S': + if (buflen < 2) + return 0; + + return b64->decode(outdata, &outdatalenu, buf + 1, buflen - 1); + + case 'u': /* plain base64u (Underscore) from TXT */ + case 'U': + if (buflen < 2) + return 0; + + return b64u->decode(outdata, &outdatalenu, buf + 1, buflen - 1); + + case 'v': /* plain base128 from TXT */ + case 'V': + if (buflen < 2) + return 0; + + return b128->decode(outdata, &outdatalenu, buf + 1, buflen - 1); + + case 'r': /* Raw binary from TXT */ + case 'R': + /* buflen>=1 already checked */ + buflen--; + buflen = MIN(buflen, outdatalen); + memcpy(outdata, buf + 1, buflen); + return buflen; + + default: + warnx("Received unsupported encoding"); + return 0; + } + + /* notreached */ + return 0; +} + +static int +read_dns_withq(int dns_fd, int tun_fd, char *buf, int buflen, struct query *q) +/* FIXME: tun_fd needed for raw handling */ +/* Returns -1 on receive error or decode error, including DNS error replies. + Returns 0 on replies that could be correct but are useless, and are not + DNS error replies. + Returns >0 on correct replies; value is #valid bytes in *buf. +*/ +{ + struct sockaddr_in from; + char data[64*1024]; + socklen_t addrlen; + int r; + + addrlen = sizeof(struct sockaddr); + if ((r = recvfrom(dns_fd, data, sizeof(data), 0, + (struct sockaddr*)&from, &addrlen)) < 0) { + warn("recvfrom"); + return -1; + } + + if (conn == CONN_DNS_NULL) { + int rv; + if (r <= 0) + /* useless packet */ + return 0; + + rv = dns_decode(buf, buflen, q, QR_ANSWER, data, r); + if (rv <= 0) + return rv; + + if (q->type == T_CNAME || q->type == T_TXT) + /* CNAME can also be returned from an A question */ + { + /* + * buf is a hostname or txt stream that we still need to + * decode to binary + * + * also update rv with the number of valid bytes + * + * data is unused here, and will certainly hold the smaller binary + */ + + rv = dns_namedec(data, sizeof(data), buf, rv); + + rv = MIN(rv, buflen); + if (rv > 0) + memcpy(buf, data, rv); + + } else if (q->type == T_MX || q->type == T_SRV) { + /* buf is like "Hname.com\0Hanother.com\0\0" */ + int buftotal = rv; /* idx of last \0 */ + int bufoffset = 0; + int dataoffset = 0; + int thispartlen, dataspace, datanew; + + while (1) { + thispartlen = strlen(buf); + thispartlen = MIN(thispartlen, buftotal-bufoffset); + dataspace = sizeof(data) - dataoffset; + if (thispartlen <= 0 || dataspace <= 0) + break; + + datanew = dns_namedec(data + dataoffset, dataspace, + buf + bufoffset, thispartlen); + if (datanew <= 0) + break; + + bufoffset += thispartlen + 1; + dataoffset += datanew; + } + rv = dataoffset; + rv = MIN(rv, buflen); + if (rv > 0) + memcpy(buf, data, rv); + } + + return rv; + } else { /* CONN_RAW_UDP */ + unsigned long datalen; + char buf[64*1024]; + + /* minimum length */ + if (r < RAW_HDR_LEN) return 0; + /* should start with header */ + if (memcmp(data, raw_header, RAW_HDR_IDENT_LEN)) return 0; + /* should be my user id */ + if (RAW_HDR_GET_USR(data) != userid) return 0; + + if (RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_DATA || + RAW_HDR_GET_CMD(data) == RAW_HDR_CMD_PING) + lastdownstreamtime = time(NULL); + + /* should be data packet */ + if (RAW_HDR_GET_CMD(data) != RAW_HDR_CMD_DATA) return 0; + + r -= RAW_HDR_LEN; + datalen = sizeof(buf); + if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) &data[RAW_HDR_LEN], r) == Z_OK) { + write_tun(tun_fd, buf, datalen); + } + + /* don't process any further */ + return 0; + } +} + +static int +handshake_waitdns(int dns_fd, char *buf, int buflen, char c1, char c2, int timeout) +/* Wait for DNS reply fitting to our latest query and returns it. + Returns length of reply = #bytes used in buf. + Returns 0 if fitting reply happens to be useless. + Returns -2 on (at least) DNS error that fits to our latest query, + error message already printed. + Returns -3 on timeout (given in seconds). + Returns -1 on other errors. + + Timeout is restarted when "wrong" (previous/delayed) replies are received, + so effective timeout may be longer than specified. +*/ +{ + struct query q; + int r, rv; + fd_set fds; + struct timeval tv; + + while (1) { + tv.tv_sec = timeout; + tv.tv_usec = 0; + FD_ZERO(&fds); + FD_SET(dns_fd, &fds); + r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + + if (r < 0) + return -1; /* select error */ + if (r == 0) + return -3; /* select timeout */ + + q.id = 0; + q.name[0] = '\0'; + rv = read_dns_withq(dns_fd, 0, buf, buflen, &q); + + if (q.id != chunkid || (q.name[0] != c1 && q.name[0] != c2)) { +#if 0 + fprintf(stderr, "Ignoring unfitting reply id %d starting with '%c'\n", q.id, q.name[0]); +#endif + continue; + } + + /* if still here: reply matches our latest query */ + + /* Non-recursive DNS servers (such as [a-m].root-servers.net) + return no answer, but only additional and authority records. + Can't explicitly test for that here, just assume that + NOERROR is such situation. Only trigger on the very first + requests (Y or V, depending if -T given). + */ + if (rv < 0 && q.rcode == NOERROR && + (q.name[0] == 'Y' || q.name[0] == 'y' || + q.name[0] == 'V' || q.name[0] == 'v')) { + fprintf(stderr, "Got empty reply. This nameserver may not be resolving recursively, use another.\n"); + fprintf(stderr, "Try \"iodine [options] ns.%s %s\" first, it might just work.\n", + topdomain, topdomain); + return -2; + } + + /* If we get an immediate SERVFAIL on the handshake query + we're waiting for, wait a while before sending the next. + SERVFAIL reliably happens during fragsize autoprobe, but + mostly long after we've moved along to some other queries. + However, some DNS relays, once they throw a SERVFAIL, will + for several seconds apply it immediately to _any_ new query + for the same topdomain. When this happens, waiting a while + is the only option that works. + */ + if (rv < 0 && q.rcode == SERVFAIL) + sleep(1); + + if (rv < 0) { + write_dns_error(&q, 1); + return -2; + } + /* rv either 0 or >0, return it as is. */ + return rv; + } + + /* not reached */ + return -1; +} + +static int +tunnel_tun(int tun_fd, int dns_fd) +{ + unsigned long outlen; + unsigned long inlen; + char out[64*1024]; + char in[64*1024]; + ssize_t read; + + if ((read = read_tun(tun_fd, in, sizeof(in))) <= 0) + return -1; + + /* We may be here only to empty the tun device; then return -1 + to force continue in select loop. */ + if (is_sending()) + return -1; + + outlen = sizeof(out); + inlen = read; + compress2((uint8_t*)out, &outlen, (uint8_t*)in, inlen, 9); + + memcpy(outpkt.data, out, MIN(outlen, sizeof(outpkt.data))); + outpkt.sentlen = 0; + outpkt.offset = 0; + outpkt.seqno = (outpkt.seqno + 1) & 7; + outpkt.len = outlen; + outpkt.fragment = 0; + outchunkresent = 0; + + if (conn == CONN_DNS_NULL) { + send_chunk(dns_fd); + + send_ping_soon = 0; + } else { + send_raw_data(dns_fd); + } + + return read; +} + +static int +tunnel_dns(int tun_fd, int dns_fd) +{ + static long packrecv = 0; + static long packrecv_oos = 0; + static long packrecv_servfail = 0; + int up_ack_seqno; + int up_ack_fragment; + int new_down_seqno; + int new_down_fragment; + struct query q; + unsigned long datalen; + char buf[64*1024]; + int read; + int send_something_now = 0; + + memset(q.name, 0, sizeof(q.name)); + read = read_dns_withq(dns_fd, tun_fd, buf, sizeof(buf), &q); + + if (conn != CONN_DNS_NULL) + return 1; /* everything already done */ + +#if 0 + fprintf(stderr, " Recv: id %5d name[0]='%c'\n", + q.id, q.name[0]); +#endif + + /* Don't process anything that isn't data for us; usually error + replies from fragsize probes etc. However a sequence of those, + mostly 1 sec apart, will continuously break the >=2-second select + timeout, which means we won't send a proper ping for a while. + So make select a bit faster, <1sec. */ + if (q.name[0] != 'P' && q.name[0] != 'p' && + q.name[0] != userid_char && q.name[0] != userid_char2) { + send_ping_soon = 700; + return -1; /* nothing done */ + } + + if (read < 2) { + /* Maybe SERVFAIL etc. Send ping to get things back in order, + but wait a bit to prevent fast ping-pong loops. */ + + if (read < 0) + write_dns_error(&q, 0); + + if (read < 0 && q.rcode == SERVFAIL && lazymode && + selecttimeout > 1) { + if (packrecv < 500 && packrecv_servfail < 4) { + packrecv_servfail++; + warnx("Hmm, that's %ld. Your data should still go through...", packrecv_servfail); + } else if (packrecv < 500 && packrecv_servfail == 4) { + packrecv_servfail++; + warnx("I think %ld is too many. Setting interval to 1 to hopefully reduce SERVFAILs. But just ignore them if data still comes through. (Use -I1 next time on this network.)", packrecv_servfail); + selecttimeout = 1; + send_query_sendcnt = 0; + send_query_recvcnt = 0; + } else if (packrecv >= 500 && packrecv_servfail > 0) { + warnx("(Sorry, stopped counting; try -I1 if you experience hiccups.)"); + packrecv_servfail = 0; + } + } + + /* read==1 happens with "QMEM" illegal replies, caused by + heavy reordering, or after short disconnections when + data-CMC has looped around into the "duplicate" values. + All these cases are helped by faster pinging. */ +#if 0 + if (read == 1) + fprintf(stderr, " q=%c id %5d 1-byte illegal \"QMEM\" reply\n", q.name[0], q.id); +#endif + + send_ping_soon = 900; + return -1; /* nothing done */ + } + + if (read == 5 && !strncmp("BADIP", buf, 5)) { + warnx("BADIP: Server rejected sender IP address (maybe iodined -c will help), or server kicked us due to timeout. Will exit if no downstream data is received in 60 seconds."); + return -1; /* nothing done */ + } + + if (send_ping_soon) { + send_something_now = 1; + send_ping_soon = 0; + } + + /* Decode the data header, update seqno and frag; + already checked read>=2 + Note that buf[] gets overwritten when down-pkt complete */ + new_down_seqno = (buf[1] >> 5) & 7; + new_down_fragment = (buf[1] >> 1) & 15; + up_ack_seqno = (buf[0] >> 4) & 7; + up_ack_fragment = buf[0] & 15; + +#if 0 + fprintf(stderr, " Recv: id %5d down %d/%d up %d/%d, %d bytes\n", + q.id, new_down_seqno, new_down_fragment, up_ack_seqno, + up_ack_fragment, read); +#endif + + /* Downstream data traffic */ + + if (read > 2 && new_down_seqno != inpkt.seqno && + recent_seqno(inpkt.seqno, new_down_seqno)) { + /* This is the previous seqno, or a bit earlier. + Probably out-of-sequence dupe due to unreliable + intermediary DNS. Don't get distracted, but send + ping quickly to get things back in order. + Ping will send our current seqno idea. + If it's really a new packet that skipped multiple seqnos + (why??), server will re-send and drop a few times and + eventually everything will work again. */ + read = 2; + send_ping_soon = 500; + /* Still process upstream ack, if any */ + } + + if (!(packrecv & 0x1000000)) + packrecv++; + send_query_recvcnt++; /* overflow doesn't matter */ + + /* Don't process any non-recent stuff any further. + No need to remember more than 3 ids: in practice any older replies + arrive after new/current replies, and whatever data the old replies + have, it has become useless in the mean time. + Actually, ever since iodined is replying to both the original query + and the last dupe, this hardly triggers any more. + */ + if (q.id != chunkid && q.id != chunkid_prev && q.id != chunkid_prev2) { + packrecv_oos++; +#if 0 + fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); +#endif + if (lazymode && packrecv < 1000 && packrecv_oos == 5) { + if (selecttimeout > 1) + warnx("Hmm, getting some out-of-sequence DNS replies. Setting interval to 1 (use -I1 next time on this network). If data traffic still has large hiccups, try if -L0 works better."); + else + warnx("Hmm, getting some out-of-sequence DNS replies. If data traffic often has large hiccups, try running with -L0 ."); + selecttimeout = 1; + send_query_sendcnt = 0; + send_query_recvcnt = 0; + } + + if (send_something_now) { + send_ping(dns_fd); + send_ping_soon = 0; + } + return -1; /* nothing done */ + } +#if 0 + fprintf(stderr, " q=%c Packs received = %8ld Out-of-sequence = %8ld\n", q.name[0], packrecv, packrecv_oos); +#endif + + /* Okay, we have a recent downstream packet */ + lastdownstreamtime = time(NULL); + + /* In lazy mode, we shouldn't get much replies to our most-recent + query, only during heavy data transfer. Since this means the server + doesn't have any packets left, send one relatively fast (but not + too fast, to avoid runaway ping-pong loops..) */ + if (q.id == chunkid && lazymode) { + if (!send_ping_soon || send_ping_soon > 900) + send_ping_soon = 900; + } + + if (read == 2 && new_down_seqno != inpkt.seqno && + !recent_seqno(inpkt.seqno, new_down_seqno)) { + /* This is a seqno that we didn't see yet, but it has + no data any more. Possible since iodined will send + fitting packs just once and not wait for ack. + Real data got lost, or will arrive shortly. + Update our idea of the seqno, and drop any waiting + old pack. Send ping to get things back on track. */ + inpkt.seqno = new_down_seqno; + inpkt.fragment = new_down_fragment; + inpkt.len = 0; + send_ping_soon = 500; + } + + while (read > 2) { + /* "if" with easy exit */ + + if (new_down_seqno != inpkt.seqno) { + /* New packet (and not dupe of recent; checked above) */ + /* Forget any old packet, even if incomplete */ + inpkt.seqno = new_down_seqno; + inpkt.fragment = new_down_fragment; /* hopefully 0 */ + inpkt.len = 0; + } else if (inpkt.fragment == 0 && new_down_fragment == 0 && + inpkt.len == 0) { + /* Weird situation: we probably got a no-data reply + for this seqno (see above), and the actual data + is following now. */ + /* okay, nothing to do here, just so that next else-if + doesn't trigger */ + } else if (new_down_fragment <= inpkt.fragment) { + /* Same packet but duplicate fragment, ignore. + If the server didn't get our ack for it, the next + ping or chunk will do that. */ + send_ping_soon = 500; + break; + } else if (new_down_fragment > inpkt.fragment + 1) { + /* Quite impossible. We missed a fragment, but the + server got our ack for it and is sending the next + fragment already. Don't handle it but let server + re-send and drop. */ + send_ping_soon = 500; + break; + } + inpkt.fragment = new_down_fragment; + + datalen = MIN(read - 2, sizeof(inpkt.data) - inpkt.len); + + /* we are here only when read > 2, so datalen "always" >=1 */ + + /* Skip 2 byte data header and append to packet */ + memcpy(&inpkt.data[inpkt.len], &buf[2], datalen); + inpkt.len += datalen; + + if (buf[1] & 1) { /* If last fragment flag is set */ + /* Uncompress packet and send to tun */ + /* RE-USES buf[] */ + datalen = sizeof(buf); + if (uncompress((uint8_t*)buf, &datalen, (uint8_t*) inpkt.data, inpkt.len) == Z_OK) { + write_tun(tun_fd, buf, datalen); + } + inpkt.len = 0; + /* Keep .seqno and .fragment as is, so that we won't + reassemble from duplicate fragments */ + } + + /* Send anything to ack the received seqno/frag, and get more */ + if (inpkt.len == 0) { + /* was last frag; wait just a trifle because our + tun will probably return TCP-ack immediately. + 5msec = 200 DNSreq/sec */ + send_ping_soon = 5; + } else { + /* server certainly has more data */ + send_something_now = 1; + } + + break; + } + + /* NOTE: buf[] was overwritten when down-packet complete */ + + + /* Upstream data traffic */ + + if (is_sending()) { + /* already checked read>=2 */ +#if 0 + fprintf(stderr, "Got ack for %d,%d - expecting %d,%d - id=%d cur=%d prev=%d prev2=%d\n", + up_ack_seqno, up_ack_fragment, outpkt.seqno, outpkt.fragment, + q.id, chunkid, chunkid_prev, chunkid_prev2); +#endif + + if (up_ack_seqno == outpkt.seqno && + up_ack_fragment == outpkt.fragment) { + /* Okay, previously sent fragment has arrived */ + + outpkt.offset += outpkt.sentlen; + if (outpkt.offset >= outpkt.len) { + /* Packet completed */ + outpkt.offset = 0; + outpkt.len = 0; + outpkt.sentlen = 0; + outchunkresent = 0; + + /* Normally, server still has a query in queue, + but sometimes not. So send a ping. + (Comment this out and you'll see occasional + hiccups.) + But since the server often still has a + query and we can expect a TCP-ack returned + from our tun device quickly in many cases, + don't be too fast. + 20msec still is 50 DNSreq/second... */ + if (!send_ping_soon || send_ping_soon > 20) + send_ping_soon = 20; + } else { + /* More to send */ + outpkt.fragment++; + outchunkresent = 0; + send_chunk(dns_fd); + send_ping_soon = 0; + send_something_now = 0; + } + } + /* else: Some wrong fragment has arrived, or old fragment is + acked again, mostly by ping responses. + Don't resend chunk, usually not needed; select loop will + re-send on timeout (1sec if is_sending()). */ + } + + + /* Send ping if we didn't send anything yet */ + if (send_something_now) { + send_ping(dns_fd); + send_ping_soon = 0; + } + + return read; +} + +int +client_tunnel(int tun_fd, int dns_fd) +{ + struct timeval tv; + fd_set fds; + int rv; + int i; + + rv = 0; + lastdownstreamtime = time(NULL); + send_query_sendcnt = 0; /* start counting now */ + + while (running) { + tv.tv_sec = selecttimeout; + tv.tv_usec = 0; + + if (is_sending()) { + /* fast timeout for retransmits */ + tv.tv_sec = 1; + tv.tv_usec = 0; + } + + if (send_ping_soon) { + tv.tv_sec = 0; + tv.tv_usec = send_ping_soon * 1000; + } + + FD_ZERO(&fds); + if (!is_sending() || outchunkresent >= 2) { + /* If re-sending upstream data, chances are that + we're several seconds behind already and TCP + will start filling tun buffer with (useless) + retransmits. + Get up-to-date fast by simply dropping stuff, + that's what TCP is designed to handle. */ + FD_SET(tun_fd, &fds); + } + FD_SET(dns_fd, &fds); + + i = select(MAX(tun_fd, dns_fd) + 1, &fds, NULL, NULL, &tv); + + if (lastdownstreamtime + 60 < time(NULL)) { + warnx("No downstream data received in 60 seconds, shutting down."); + running = 0; + } + + if (running == 0) + break; + + if (i < 0) + err(1, "select"); + + if (i == 0) { + /* timeout */ + if (is_sending()) { + /* Re-send current fragment; either frag + or ack probably dropped somewhere. + But problem: no cache-miss-counter, + so hostname will be identical. + Just drop whole packet after 3 retries, + and TCP retransmit will solve it. + NOTE: tun dropping above should be + >=(value_here - 1) */ + if (outchunkresent < 3) { + outchunkresent++; + send_chunk(dns_fd); + } else { + outpkt.offset = 0; + outpkt.len = 0; + outpkt.sentlen = 0; + outchunkresent = 0; + + send_ping(dns_fd); + } + } else { + send_ping(dns_fd); + } + send_ping_soon = 0; + + } else { + + if (FD_ISSET(tun_fd, &fds)) { + if (tunnel_tun(tun_fd, dns_fd) <= 0) + continue; + /* Returns -1 on error OR when quickly + dropping data in case of DNS congestion; + we need to _not_ do tunnel_dns() then. + If chunk sent, sets send_ping_soon=0. */ + } + if (FD_ISSET(dns_fd, &fds)) { + if (tunnel_dns(tun_fd, dns_fd) <= 0) + continue; + } + } + } + + return rv; +} + +static void +send_login(int fd, char *login, int len) +{ + char data[19]; + + memset(data, 0, sizeof(data)); + data[0] = userid; + memcpy(&data[1], login, MIN(len, 16)); + + data[17] = (rand_seed >> 8) & 0xff; + data[18] = (rand_seed >> 0) & 0xff; + + rand_seed++; + + send_packet(fd, 'l', data, sizeof(data)); +} + +static void +send_fragsize_probe(int fd, int fragsize) +{ + char probedata[256]; + char buf[4096]; + + /* + * build a large query domain which is random and maximum size, + * will also take up maximal space in the return packet + */ + memset(probedata, MAX(1, rand_seed & 0xff), sizeof(probedata)); + probedata[1] = MAX(1, (rand_seed >> 8) & 0xff); + rand_seed++; + + /* Note: must either be same, or larger, than send_chunk() */ + build_hostname(buf + 5, sizeof(buf) - 5, probedata, sizeof(probedata), + topdomain, dataenc, hostname_maxlen); + + fragsize &= 2047; + + buf[0] = 'r'; /* Probe downstream fragsize packet */ + buf[1] = b32_5to8((userid << 1) | ((fragsize >> 10) & 1)); + buf[2] = b32_5to8((fragsize >> 5) & 31); + buf[3] = b32_5to8(fragsize & 31); + buf[4] = 'd'; /* dummy to match send_chunk() */ + + send_query(fd, buf); +} + +static void +send_set_downstream_fragsize(int fd, int fragsize) +{ + char data[5]; + + data[0] = userid; + data[1] = (fragsize & 0xff00) >> 8; + data[2] = (fragsize & 0x00ff); + data[3] = (rand_seed >> 8) & 0xff; + data[4] = (rand_seed >> 0) & 0xff; + + rand_seed++; + + send_packet(fd, 'n', data, sizeof(data)); +} + +static void +send_version(int fd, uint32_t version) +{ + char data[6]; + + data[0] = (version >> 24) & 0xff; + data[1] = (version >> 16) & 0xff; + data[2] = (version >> 8) & 0xff; + data[3] = (version >> 0) & 0xff; + + data[4] = (rand_seed >> 8) & 0xff; + data[5] = (rand_seed >> 0) & 0xff; + + rand_seed++; + + send_packet(fd, 'v', data, sizeof(data)); +} + +static void +send_ip_request(int fd, int userid) +{ + char buf[512] = "i____."; + buf[1] = b32_5to8(userid); + + buf[2] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[3] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[4] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + +static void +send_raw_udp_login(int dns_fd, int userid, int seed) +{ + char buf[16]; + login_calculate(buf, 16, password, seed + 1); + + send_raw(dns_fd, buf, sizeof(buf), userid, RAW_HDR_CMD_LOGIN); +} + +static void +send_upenctest(int fd, char *s) +/* NOTE: String may be at most 63-4=59 chars to fit in 1 dns chunk. */ +{ + char buf[512] = "z___"; + + buf[1] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[2] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[3] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, s, 512); + strncat(buf, ".", 512); + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + +static void +send_downenctest(int fd, char downenc, int variant, char *s, int slen) +/* Note: content/handling of s is not defined yet. */ +{ + char buf[512] = "y_____."; + + buf[1] = tolower(downenc); + buf[2] = b32_5to8(variant); + + buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + +static void +send_codec_switch(int fd, int userid, int bits) +{ + char buf[512] = "s_____."; + buf[1] = b32_5to8(userid); + buf[2] = b32_5to8(bits); + + buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + + +static void +send_downenc_switch(int fd, int userid) +{ + char buf[512] = "o_____."; + buf[1] = b32_5to8(userid); + buf[2] = tolower(downenc); + + buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + +static void +send_lazy_switch(int fd, int userid) +{ + char buf[512] = "o_____."; + buf[1] = b32_5to8(userid); + + if (lazymode) + buf[2] = 'l'; + else + buf[2] = 'i'; + + buf[3] = b32_5to8((rand_seed >> 10) & 0x1f); + buf[4] = b32_5to8((rand_seed >> 5) & 0x1f); + buf[5] = b32_5to8((rand_seed ) & 0x1f); + rand_seed++; + + strncat(buf, topdomain, 512 - strlen(buf)); + send_query(fd, buf); +} + +static int +handshake_version(int dns_fd, int *seed) +{ + char hex[] = "0123456789abcdef"; + char hex2[] = "0123456789ABCDEF"; + char in[4096]; + uint32_t payload; + int i; + int read; + + for (i = 0; running && i < 5; i++) { + + send_version(dns_fd, VERSION); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'v', 'V', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read >= 9) { + payload = (((in[4] & 0xff) << 24) | + ((in[5] & 0xff) << 16) | + ((in[6] & 0xff) << 8) | + ((in[7] & 0xff))); + + if (strncmp("VACK", in, 4) == 0) { + *seed = payload; + userid = in[8]; + userid_char = hex[userid & 15]; + userid_char2 = hex2[userid & 15]; + + fprintf(stderr, "Version ok, both using protocol v 0x%08x. You are user #%d\n", VERSION, userid); + return 0; + } else if (strncmp("VNAK", in, 4) == 0) { + warnx("You use protocol v 0x%08x, server uses v 0x%08x. Giving up", + VERSION, payload); + return 1; + } else if (strncmp("VFUL", in, 4) == 0) { + warnx("Server full, all %d slots are taken. Try again later", payload); + return 1; + } + } else if (read > 0) + warnx("did not receive proper login challenge"); + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying version check...\n"); + } + warnx("couldn't connect to server (maybe other -T options will work)"); + return 1; +} + +static int +handshake_login(int dns_fd, int seed) +{ + char in[4096]; + char login[16]; + char server[65]; + char client[65]; + int mtu; + int i; + int read; + + login_calculate(login, 16, password, seed); + + for (i=0; running && i<5 ;i++) { + + send_login(dns_fd, login, 16); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'l', 'L', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + int netmask; + if (strncmp("LNAK", in, 4) == 0) { + fprintf(stderr, "Bad password\n"); + return 1; + } else if (sscanf(in, "%64[^-]-%64[^-]-%d-%d", + server, client, &mtu, &netmask) == 4) { + + server[64] = 0; + client[64] = 0; + if (tun_setip(client, server, netmask) == 0 && + tun_setmtu(mtu) == 0) { + + fprintf(stderr, "Server tunnel IP is %s\n", server); + return 0; + } else { + errx(4, "Failed to set IP and MTU"); + } + } else { + fprintf(stderr, "Received bad handshake\n"); + } + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying login...\n"); + } + warnx("couldn't login to server"); + return 1; +} + +static int +handshake_raw_udp(int dns_fd, int seed) +{ + struct timeval tv; + char in[4096]; + fd_set fds; + int i; + int r; + int len; + unsigned remoteaddr = 0; + struct in_addr server; + + fprintf(stderr, "Testing raw UDP data to the server (skip with -r)"); + for (i=0; running && i<3 ;i++) { + + send_ip_request(dns_fd, userid); + + len = handshake_waitdns(dns_fd, in, sizeof(in), 'i', 'I', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (len == 5 && in[0] == 'I') { + /* Received IP address */ + remoteaddr = (in[1] & 0xff); + remoteaddr <<= 8; + remoteaddr |= (in[2] & 0xff); + remoteaddr <<= 8; + remoteaddr |= (in[3] & 0xff); + remoteaddr <<= 8; + remoteaddr |= (in[4] & 0xff); + server.s_addr = ntohl(remoteaddr); + break; + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "."); + fflush(stderr); + } + fprintf(stderr, "\n"); + if (!running) + return 0; + + if (!remoteaddr) { + fprintf(stderr, "Failed to get raw server IP, will use DNS mode.\n"); + return 0; + } + fprintf(stderr, "Server is at %s, trying raw login: ", inet_ntoa(server)); + fflush(stderr); + + /* Store address to iodined server */ + memset(&raw_serv, 0, sizeof(raw_serv)); + raw_serv.sin_family = AF_INET; + raw_serv.sin_port = htons(53); + raw_serv.sin_addr = server; + + /* do login against port 53 on remote server + * based on the old seed. If reply received, + * switch to raw udp mode */ + for (i=0; running && i<4 ;i++) { + tv.tv_sec = i + 1; + tv.tv_usec = 0; + + send_raw_udp_login(dns_fd, userid, seed); + + FD_ZERO(&fds); + FD_SET(dns_fd, &fds); + + r = select(dns_fd + 1, &fds, NULL, NULL, &tv); + + if(r > 0) { + /* recv() needed for windows, dont change to read() */ + len = recv(dns_fd, in, sizeof(in), 0); + if (len >= (16 + RAW_HDR_LEN)) { + char hash[16]; + login_calculate(hash, 16, password, seed - 1); + if (memcmp(in, raw_header, RAW_HDR_IDENT_LEN) == 0 + && RAW_HDR_GET_CMD(in) == RAW_HDR_CMD_LOGIN + && memcmp(&in[RAW_HDR_LEN], hash, sizeof(hash)) == 0) { + + fprintf(stderr, "OK\n"); + return 1; + } + } + } + fprintf(stderr, "."); + fflush(stderr); + } + + fprintf(stderr, "failed\n"); + return 0; +} + +static int +handshake_upenctest(int dns_fd, char *s) +/* NOTE: *s may be max 59 chars; must start with "aA" for case-swap check + Returns: + -1: case swap, no need for any further test: error printed; or Ctrl-C + 0: not identical or error or timeout + 1: identical string returned +*/ +{ + char in[4096]; + unsigned char *uin = (unsigned char *) in; + unsigned char *us = (unsigned char *) s; + int i; + int read; + int slen; + + slen = strlen(s); + for (i=0; running && i<3 ;i++) { + + send_upenctest(dns_fd, s); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'z', 'Z', i+1); + + if (read == -2) + return 0; /* hard error */ + + if (read > 0 && read < slen + 4) + return 0; /* reply too short (chars dropped) */ + + if (read > 0) { + int k; +#if 0 + /* in[56] = '@'; */ + /* in[56] = '_'; */ + /* if (in[29] == '\344') in[29] = 'a'; */ + in[read] = '\0'; + fprintf(stderr, "BounceReply: >%s<\n", in); +#endif + /* quick check if case swapped, to give informative error msg */ + if (in[4] == 'A') { + fprintf(stderr, "DNS queries get changed to uppercase, keeping upstream codec Base32\n"); + return -1; + } + if (in[5] == 'a') { + fprintf(stderr, "DNS queries get changed to lowercase, keeping upstream codec Base32\n"); + return -1; + } + + for (k = 0; k < slen; k++) { + if (in[k+4] != s[k]) { + /* Definitely not reliable */ + if (in[k+4] >= ' ' && in[k+4] <= '~' && + s[k] >= ' ' && s[k] <= '~') { + fprintf(stderr, "DNS query char '%c' gets changed into '%c'\n", + s[k], in[k+4]); + } else { + fprintf(stderr, "DNS query char 0x%02X gets changed into 0x%02X\n", + (unsigned int) us[k], + (unsigned int) uin[k+4]); + } + return 0; + } + } + /* if still here, then all okay */ + return 1; + } + + fprintf(stderr, "Retrying upstream codec test...\n"); + } + + if (!running) + return -1; + + /* timeout */ + return 0; +} + +static int +handshake_upenc_autodetect(int dns_fd) +/* Returns: + 0: keep Base32 + 1: Base64 is okay + 2: Base64u is okay + 3: Base128 is okay +*/ +{ + /* Note: max 59 chars, must start with "aA". + pat64: If 0129 work, assume 3-8 are okay too. + + RFC1035 par 2.3.1 states that [A-Z0-9-] allowed, but only + [A-Z] as first, and [A-Z0-9] as last char _per label_. + Test by having '-' as last char. + */ + char *pat64="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ+0129-"; + char *pat64u="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ_0129-"; + char *pat128a="aA-Aaahhh-Drink-mal-ein-J\344germeister-"; + char *pat128b="aA-La-fl\373te-na\357ve-fran\347aise-est-retir\351-\340-Cr\350te"; + char *pat128c="aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ"; + char *pat128d="aA0123456789\274\275\276\277" + "\300\301\302\303\304\305\306\307\310\311\312\313\314\315\316\317"; + char *pat128e="aA" + "\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337" + "\340\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357" + "\360\361\362\363\364\365\366\367\370\371\372\373\374\375"; + int res; + + /* Try Base128, starting very gently to not draw attention */ + while (1) { + res = handshake_upenctest(dns_fd, pat128a); + if (res < 0) { + /* DNS swaps case, msg already printed; or Ctrl-C */ + return 0; + } else if (res == 0) { + /* Probably not okay, skip Base128 entirely */ + break; + } + + res = handshake_upenctest(dns_fd, pat128b); + if (res < 0) + return 0; + else if (res == 0) + break; + + /* if this works, we can test the real stuff */ + + res = handshake_upenctest(dns_fd, pat128c); + if (res < 0) + return 0; + else if (res == 0) + break; + + res = handshake_upenctest(dns_fd, pat128d); + if (res < 0) + return 0; + else if (res == 0) + break; + + res = handshake_upenctest(dns_fd, pat128e); + if (res < 0) + return 0; + else if (res == 0) + break; + + /* if still here, then base128 works completely */ + return 3; + } + + /* Try Base64 (with plus sign) */ + res = handshake_upenctest(dns_fd, pat64); + if (res < 0) { + /* DNS swaps case, msg already printed; or Ctrl-C */ + return 0; + } else if (res > 0) { + /* All okay, Base64 msg will be printed later */ + return 1; + } + + /* Try Base64u (with _u_nderscore) */ + res = handshake_upenctest(dns_fd, pat64u); + if (res < 0) { + /* DNS swaps case, msg already printed; or Ctrl-C */ + return 0; + } else if (res > 0) { + /* All okay, Base64u msg will be printed later */ + return 2; + } + + /* if here, then nonthing worked */ + fprintf(stderr, "Keeping upstream codec Base32\n"); + return 0; +} + +static int +handshake_downenctest(int dns_fd, char trycodec) +/* Returns: + 0: not identical or error or timeout + 1: identical string returned +*/ +{ + char in[4096]; + int i; + int read; + char *s = DOWNCODECCHECK1; + int slen = DOWNCODECCHECK1_LEN; + + for (i=0; running && i<3 ;i++) { + + send_downenctest(dns_fd, trycodec, 1, NULL, 0); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + + if (read == -2) + return 0; /* hard error */ + + if (read > 0 && read != slen) + return 0; /* reply incorrect = unreliable */ + + if (read > 0) { + int k; + for (k = 0; k < slen; k++) { + if (in[k] != s[k]) { + /* Definitely not reliable */ + return 0; + } + } + /* if still here, then all okay */ + return 1; + } + + fprintf(stderr, "Retrying downstream codec test...\n"); + } + + /* timeout */ + return 0; +} + +static char +handshake_downenc_autodetect(int dns_fd) +/* Returns codec char (or ' ' if no advanced codec works) */ +{ + int base64ok = 0; + int base64uok = 0; + int base128ok = 0; + + if (do_qtype == T_NULL) { + /* no other choice than raw */ + fprintf(stderr, "No alternative downstream codec available, using default (Raw)\n"); + return ' '; + } + + fprintf(stderr, "Autodetecting downstream codec (use -O to override)\n"); + + /* Try Base64 */ + if (handshake_downenctest(dns_fd, 'S')) + base64ok = 1; + else if (running && handshake_downenctest(dns_fd, 'U')) + base64uok = 1; + + /* Try Base128 only if 64 gives us some perspective */ + if (running && (base64ok || base64uok)) { + if (handshake_downenctest(dns_fd, 'V')) + base128ok = 1; + } + + /* If 128 works, then TXT may give us Raw as well */ + if (running && (base128ok && do_qtype == T_TXT)) { + if (handshake_downenctest(dns_fd, 'R')) + return 'R'; + } + + if (!running) + return ' '; + + if (base128ok) + return 'V'; + if (base64ok) + return 'S'; + if (base64uok) + return 'U'; + + fprintf(stderr, "No advanced downstream codecs seem to work, using default (Base32)\n"); + return ' '; +} + +static int +handshake_qtypetest(int dns_fd, int timeout) +/* Returns: + 0: doesn't work with this timeout + 1: works properly +*/ +{ + char in[4096]; + int read; + char *s = DOWNCODECCHECK1; + int slen = DOWNCODECCHECK1_LEN; + int trycodec; + int k; + + if (do_qtype == T_NULL) + trycodec = 'R'; + else + trycodec = 'T'; + + /* We could use 'Z' bouncing here, but 'Y' also tests that 0-255 + byte values can be returned, which is needed for NULL to work. */ + + send_downenctest(dns_fd, trycodec, 1, NULL, 0); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', timeout); + + if (read != slen) + return 0; /* incorrect */ + + for (k = 0; k < slen; k++) { + if (in[k] != s[k]) { + /* corrupted */ + return 0; + } + } + + /* if still here, then all okay */ + return 1; +} + +static int +handshake_qtype_numcvt(int num) +{ + switch (num) { + case 0: return T_NULL; + case 1: return T_TXT; + case 2: return T_SRV; + case 3: return T_MX; + case 4: return T_CNAME; + case 5: return T_A; + } + return T_UNSET; +} + +static int +handshake_qtype_autodetect(int dns_fd) +/* Returns: + 0: okay, do_qtype set + 1: problem, program exit +*/ +{ + int highestworking = 100; + int timeout; + int qtypenum; + + fprintf(stderr, "Autodetecting DNS query type (use -T to override)"); + fflush(stderr); + + /* Method: try all "interesting" qtypes with a 1-sec timeout, then try + all "still-interesting" qtypes with a 2-sec timeout, etc. + "Interesting" means: qtypes that (are expected to) have higher + bandwidth than what we know is working already (highestworking). + + Note that DNS relays may not immediately resolve the first (NULL) + query in 1 sec, due to long recursive lookups, so we keep trying + to see if things will start working after a while. + */ + + for (timeout = 1; running && timeout <= 3; timeout++) { + for (qtypenum = 0; running && qtypenum < highestworking; qtypenum++) { + do_qtype = handshake_qtype_numcvt(qtypenum); + if (do_qtype == T_UNSET) + break; /* this round finished */ + + fprintf(stderr, "."); + fflush(stderr); + + if (handshake_qtypetest(dns_fd, timeout)) { + /* okay */ + highestworking = qtypenum; +#if 0 + fprintf(stderr, " Type %s timeout %d works\n", + get_qtype(), timeout); +#endif + break; + /* try others with longer timeout */ + } + /* else: try next qtype with same timeout */ + } + if (highestworking == 0) + /* good, we have NULL; abort immediately */ + break; + } + + fprintf(stderr, "\n"); + + if (!running) { + warnx("Stopped while autodetecting DNS query type (try setting manually with -T)"); + return 1; /* problem */ + } + + /* finished */ + do_qtype = handshake_qtype_numcvt(highestworking); + + if (do_qtype == T_UNSET) { + /* also catches highestworking still 100 */ + warnx("No suitable DNS query type found. Are you connected to a network?"); + warnx("If you expect very long roundtrip delays, use -T explicitly."); + warnx("(Also, connecting to an \"ancient\" version of iodined won't work.)"); + return 1; /* problem */ + } + + /* "using qtype" message printed in handshake function */ + return 0; /* okay */ +} + +static int +handshake_edns0_check(int dns_fd) +/* Returns: + 0: EDNS0 not supported; or Ctrl-C + 1: EDNS0 works +*/ +{ + char in[4096]; + int i; + int read; + char *s = DOWNCODECCHECK1; + int slen = DOWNCODECCHECK1_LEN; + char trycodec; + + if (do_qtype == T_NULL) + trycodec = 'R'; + else + trycodec = 'T'; + + for (i=0; running && i<3 ;i++) { + + send_downenctest(dns_fd, trycodec, 1, NULL, 0); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'y', 'Y', i+1); + + if (read == -2) + return 0; /* hard error */ + + if (read > 0 && read != slen) + return 0; /* reply incorrect = unreliable */ + + if (read > 0) { + int k; + for (k = 0; k < slen; k++) { + if (in[k] != s[k]) { + /* Definitely not reliable */ + return 0; + } + } + /* if still here, then all okay */ + return 1; + } + + fprintf(stderr, "Retrying EDNS0 support test...\n"); + } + + /* timeout or Ctrl-C */ + return 0; +} + +static void +handshake_switch_codec(int dns_fd, int bits) +{ + char in[4096]; + int i; + int read; + struct encoder *tempenc; + + if (bits == 5) + tempenc = get_base32_encoder(); + else if (bits == 6) + tempenc = get_base64_encoder(); + else if (bits == 26) /* "2nd" 6 bits per byte, with underscore */ + tempenc = get_base64u_encoder(); + else if (bits == 7) + tempenc = get_base128_encoder(); + else return; + + fprintf(stderr, "Switching upstream to codec %s\n", tempenc->name); + + for (i=0; running && i<5 ;i++) { + + send_codec_switch(dns_fd, userid, bits); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 's', 'S', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + if (strncmp("BADLEN", in, 6) == 0) { + fprintf(stderr, "Server got bad message length. "); + goto codec_revert; + } else if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address. "); + goto codec_revert; + } else if (strncmp("BADCODEC", in, 8) == 0) { + fprintf(stderr, "Server rejected the selected codec. "); + goto codec_revert; + } + in[read] = 0; /* zero terminate */ + fprintf(stderr, "Server switched upstream to codec %s\n", in); + dataenc = tempenc; + return; + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying codec switch...\n"); + } + if (!running) + return; + + fprintf(stderr, "No reply from server on codec switch. "); + +codec_revert: + fprintf(stderr, "Falling back to upstream codec %s\n", dataenc->name); +} + +static void +handshake_switch_downenc(int dns_fd) +{ + char in[4096]; + int i; + int read; + char *dname; + + dname = "Base32"; + if (downenc == 'S') + dname = "Base64"; + else if (downenc == 'U') + dname = "Base64u"; + else if (downenc == 'V') + dname = "Base128"; + else if (downenc == 'R') + dname = "Raw"; + + fprintf(stderr, "Switching downstream to codec %s\n", dname); + for (i=0; running && i<5 ;i++) { + + send_downenc_switch(dns_fd, userid); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + if (strncmp("BADLEN", in, 6) == 0) { + fprintf(stderr, "Server got bad message length. "); + goto codec_revert; + } else if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address. "); + goto codec_revert; + } else if (strncmp("BADCODEC", in, 8) == 0) { + fprintf(stderr, "Server rejected the selected codec. "); + goto codec_revert; + } + in[read] = 0; /* zero terminate */ + fprintf(stderr, "Server switched downstream to codec %s\n", in); + return; + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying codec switch...\n"); + } + if (!running) + return; + + fprintf(stderr, "No reply from server on codec switch. "); + +codec_revert: + fprintf(stderr, "Falling back to downstream codec Base32\n"); +} + +static void +handshake_try_lazy(int dns_fd) +{ + char in[4096]; + int i; + int read; + + fprintf(stderr, "Switching to lazy mode for low-latency\n"); + for (i=0; running && i<5; i++) { + + send_lazy_switch(dns_fd, userid); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + if (strncmp("BADLEN", in, 6) == 0) { + fprintf(stderr, "Server got bad message length. "); + goto codec_revert; + } else if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address. "); + goto codec_revert; + } else if (strncmp("BADCODEC", in, 8) == 0) { + fprintf(stderr, "Server rejected lazy mode. "); + goto codec_revert; + } else if (strncmp("Lazy", in, 4) == 0) { + fprintf(stderr, "Server switched to lazy mode\n"); + lazymode = 1; + return; + } + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying lazy mode switch...\n"); + } + if (!running) + return; + + fprintf(stderr, "No reply from server on lazy switch. "); + +codec_revert: + fprintf(stderr, "Falling back to legacy mode\n"); + lazymode = 0; + selecttimeout = 1; +} + +static void +handshake_lazyoff(int dns_fd) +/* Used in the middle of data transfer, timing is different and no error msgs */ +{ + char in[4096]; + int i; + int read; + + for (i=0; running && i<5; i++) { + + send_lazy_switch(dns_fd, userid); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'o', 'O', 1); + + /*XXX START adjust indent 2 tabs back*/ + if (read == 9 && strncmp("Immediate", in, 9) == 0) { + warnx("Server switched back to legacy mode.\n"); + lazymode = 0; + selecttimeout = 1; + return; + } + /*XXX END adjust indent 2 tabs back*/ + } + if (!running) + return; + + warnx("No reply from server on legacy mode switch.\n"); +} + +static int +fragsize_check(char *in, int read, int proposed_fragsize, int *max_fragsize) +/* Returns: 0: keep checking, 1: break loop (either okay or definitely wrong) */ +{ + int acked_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff); + int okay; + int i; + unsigned int v; + + if (read >= 5 && strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "got BADIP (Try iodined -c)..\n"); + fflush(stderr); + return 0; /* maybe temporary error */ + } + + if (acked_fragsize != proposed_fragsize) { + /* + * got ack for wrong fragsize, maybe late response for + * earlier query, or ack corrupted + */ + return 0; + } + + if (read != proposed_fragsize) { + /* + * correctly acked fragsize but read too little (or too + * much): this fragsize is definitely not reliable + */ + return 1; + } + + /* here: read == proposed_fragsize == acked_fragsize */ + + /* test: */ + /* in[123] = 123; */ + + if ((in[2] & 0xff) != 107) { + fprintf(stderr, "\n"); + warnx("corruption at byte 2, this won't work. Try -O Base32, or other -T options."); + *max_fragsize = -1; + return 1; + } + + /* Check for corruption */ + okay = 1; + v = in[3] & 0xff; + + /*XXX START adjust indent 1 tab back*/ + for (i = 3; i < read; i++, v = (v + 107) & 0xff) + if ((in[i] & 0xff) != v) { + okay = 0; + break; + } + + if (okay) { + fprintf(stderr, "%d ok.. ", acked_fragsize); + fflush(stderr); + *max_fragsize = acked_fragsize; + return 1; + } else { + if (downenc != ' ' && downenc != 'T') { + fprintf(stderr, "%d corrupted at %d.. (Try -O Base32)\n", acked_fragsize, i); + } else { + fprintf(stderr, "%d corrupted at %d.. ", acked_fragsize, i); + } + fflush(stderr); + return 1; + } + /*XXX END adjust indent 1 tab back*/ + + /* notreached */ + return 1; +} + + +static int +handshake_autoprobe_fragsize(int dns_fd) +{ + char in[4096]; + int i; + int read; + int proposed_fragsize = 768; + int range = 768; + int max_fragsize; + + max_fragsize = 0; + fprintf(stderr, "Autoprobing max downstream fragment size... (skip with -m fragsize)\n"); + while (running && range > 0 && (range >= 8 || max_fragsize < 300)) { + /* stop the slow probing early when we have enough bytes anyway */ + for (i=0; running && i<3 ;i++) { + + send_fragsize_probe(dns_fd, proposed_fragsize); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'r', 'R', 1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + /* We got a reply */ + if (fragsize_check(in, read, proposed_fragsize, &max_fragsize) == 1) + break; + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "."); + fflush(stderr); + } + if (max_fragsize < 0) + break; + + range >>= 1; + if (max_fragsize == proposed_fragsize) { + /* Try bigger */ + proposed_fragsize += range; + } else { + /* Try smaller */ + fprintf(stderr, "%d not ok.. ", proposed_fragsize); + fflush(stderr); + proposed_fragsize -= range; + } + } + if (!running) { + fprintf(stderr, "\n"); + warnx("stopped while autodetecting fragment size (Try setting manually with -m)"); + return 0; + } + if (max_fragsize <= 2) { + /* Tried all the way down to 2 and found no good size. + But we _did_ do all handshake before this, so there must + be some workable connection. */ + fprintf(stderr, "\n"); + warnx("found no accepted fragment size."); + warnx("try setting -M to 200 or lower, or try other -T or -O options."); + return 0; + } + /* data header adds 2 bytes */ + fprintf(stderr, "will use %d-2=%d\n", max_fragsize, max_fragsize - 2); + + /* need 1200 / 16frags = 75 bytes fragsize */ + if (max_fragsize < 82) { + fprintf(stderr, "Note: this probably won't work well.\n"); + fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); + } else if (max_fragsize < 202 && + (do_qtype == T_NULL || do_qtype == T_TXT || + do_qtype == T_SRV || do_qtype == T_MX)) { + fprintf(stderr, "Note: this isn't very much.\n"); + fprintf(stderr, "Try setting -M to 200 or lower, or try other DNS types (-T option).\n"); + } + + return max_fragsize - 2; +} + +static void +handshake_set_fragsize(int dns_fd, int fragsize) +{ + char in[4096]; + int i; + int read; + + fprintf(stderr, "Setting downstream fragment size to max %d...\n", fragsize); + for (i=0; running && i<5 ;i++) { + + send_set_downstream_fragsize(dns_fd, fragsize); + + read = handshake_waitdns(dns_fd, in, sizeof(in), 'n', 'N', i+1); + + /*XXX START adjust indent 1 tab back*/ + if (read > 0) { + int accepted_fragsize; + + if (strncmp("BADFRAG", in, 7) == 0) { + fprintf(stderr, "Server rejected fragsize. Keeping default."); + return; + } else if (strncmp("BADIP", in, 5) == 0) { + fprintf(stderr, "Server rejected sender IP address.\n"); + return; + } + + accepted_fragsize = ((in[0] & 0xff) << 8) | (in[1] & 0xff); + return; + } + /*XXX END adjust indent 1 tab back*/ + + fprintf(stderr, "Retrying set fragsize...\n"); + } + if (!running) + return; + + fprintf(stderr, "No reply from server when setting fragsize. Keeping default.\n"); +} + +int +client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize) +{ + int seed; + int upcodec; + int r; + + dnsc_use_edns0 = 0; + + /* qtype message printed in handshake function */ + if (do_qtype == T_UNSET) { + r = handshake_qtype_autodetect(dns_fd); + if (r) { + return r; + } + } + + fprintf(stderr, "Using DNS type %s queries\n", get_qtype()); + + r = handshake_version(dns_fd, &seed); + if (r) { + return r; + } + + r = handshake_login(dns_fd, seed); + if (r) { + return r; + } + + if (raw_mode && handshake_raw_udp(dns_fd, seed)) { + conn = CONN_RAW_UDP; + selecttimeout = 20; + } else { + if (raw_mode == 0) { + fprintf(stderr, "Skipping raw mode\n"); + } + + dnsc_use_edns0 = 1; + if (handshake_edns0_check(dns_fd) && running) { + fprintf(stderr, "Using EDNS0 extension\n"); + } else if (!running) { + return -1; + } else { + fprintf(stderr, "DNS relay does not support EDNS0 extension\n"); + dnsc_use_edns0 = 0; + } + + upcodec = handshake_upenc_autodetect(dns_fd); + if (!running) + return -1; + + if (upcodec == 1) { + handshake_switch_codec(dns_fd, 6); + } else if (upcodec == 2) { + handshake_switch_codec(dns_fd, 26); + } else if (upcodec == 3) { + handshake_switch_codec(dns_fd, 7); + } + if (!running) + return -1; + + if (downenc == ' ') { + downenc = handshake_downenc_autodetect(dns_fd); + } + if (!running) + return -1; + + if (downenc != ' ') { + handshake_switch_downenc(dns_fd); + } + if (!running) + return -1; + + if (lazymode) { + handshake_try_lazy(dns_fd); + } + if (!running) + return -1; + + if (autodetect_frag_size) { + fragsize = handshake_autoprobe_fragsize(dns_fd); + if (!fragsize) { + return 1; + } + } + + handshake_set_fragsize(dns_fd, fragsize); + if (!running) + return -1; + } + + return 0; +} + diff --git a/iodine/src/client.h b/iodine/src/client.h new file mode 100644 index 0000000..16ab0e4 --- /dev/null +++ b/iodine/src/client.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __CLIENT_H__ +#define __CLIENT_H__ + +void client_init(); +void client_stop(); + +enum connection client_get_conn(); +const char *client_get_raw_addr(); + +void client_set_nameserver(const char *cp, int port); +void client_set_topdomain(const char *cp); +void client_set_password(const char *cp); +void set_qtype(char *qtype); +char *get_qtype(); +void set_downenc(char *encoding); +void client_set_selecttimeout(int select_timeout); +void client_set_lazymode(int lazy_mode); +void client_set_hostname_maxlen(int i); + +int client_handshake(int dns_fd, int raw_mode, int autodetect_frag_size, int fragsize); +int client_tunnel(int tun_fd, int dns_fd); + +#endif diff --git a/iodine/src/common.c b/iodine/src/common.c new file mode 100644 index 0000000..dd2bf2d --- /dev/null +++ b/iodine/src/common.c @@ -0,0 +1,350 @@ +/* Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * Copyright (c) 2007 Albert Lee . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include +#include +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#include +#include +#include +#include +#include +#endif + +#ifdef HAVE_SETCON +# include +#endif + +#include "common.h" + +/* The raw header used when not using DNS protocol */ +const unsigned char raw_header[RAW_HDR_LEN] = { 0x10, 0xd1, 0x9e, 0x00 }; + +/* daemon(3) exists only in 4.4BSD or later, and in GNU libc */ +#if !defined(WINDOWS32) && !(defined(BSD) && (BSD >= 199306)) && !defined(__GLIBC__) +static int daemon(int nochdir, int noclose) +{ + int fd, i; + + switch (fork()) { + case 0: + break; + case -1: + return -1; + default: + _exit(0); + } + + if (!nochdir) { + chdir("/"); + } + + if (setsid() < 0) { + return -1; + } + + if (!noclose) { + if ((fd = open("/dev/null", O_RDWR)) >= 0) { + for (i = 0; i < 3; i++) { + dup2(fd, i); + } + if (fd > 2) { + close(fd); + } + } + } + return 0; +} +#endif + +#if defined(__BEOS__) && !defined(__HAIKU__) +int setgroups(int count, int *groups) +{ + /* errno = ENOSYS; */ + return -1; +} +#endif + + +void +check_superuser(void (*usage_fn)(void)) +{ +#ifndef WINDOWS32 + if (geteuid() != 0) { + warnx("Run as root and you'll be happy.\n"); + usage_fn(); + /* NOTREACHED */ + } +#endif +} + +int +open_dns(int localport, in_addr_t listen_ip) +{ + struct sockaddr_in addr; + int flag = 1; + int fd; + + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(localport); + /* listen_ip already in network byte order from inet_addr, or 0 */ + addr.sin_addr.s_addr = listen_ip; + + if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { + fprintf(stderr, "got fd %d\n", fd); + err(1, "socket"); + } + + flag = 1; +#ifdef SO_REUSEPORT + setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (const void*) &flag, sizeof(flag)); +#endif + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*) &flag, sizeof(flag)); + +#ifndef WINDOWS32 + /* To get destination address from each UDP datagram, see iodined.c:read_dns() */ + setsockopt(fd, IPPROTO_IP, DSTADDR_SOCKOPT, (const void*) &flag, sizeof(flag)); +#endif + +#ifdef IP_OPT_DONT_FRAG + /* Set dont-fragment ip header flag */ + flag = DONT_FRAG_VALUE; + setsockopt(fd, IPPROTO_IP, IP_OPT_DONT_FRAG, (const void*) &flag, sizeof(flag)); +#endif + + if(bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) + err(1, "bind"); + + fprintf(stderr, "Opened UDP socket\n"); + + return fd; +} + +void +close_dns(int fd) +{ + close(fd); +} + +void +do_chroot(char *newroot) +{ +#if !(defined(WINDOWS32) || defined(__BEOS__) || defined(__HAIKU__)) + if (chroot(newroot) != 0 || chdir("/") != 0) + err(1, "%s", newroot); + + seteuid(geteuid()); + setuid(getuid()); +#else + warnx("chroot not available"); +#endif +} + +void +do_setcon(char *context) +{ +#ifdef HAVE_SETCON + if (-1 == setcon(context)) + err(1, "%s", context); +#else + warnx("No SELinux support built in"); +#endif +} + +void +do_pidfile(char *pidfile) +{ +#ifndef WINDOWS32 + FILE *file; + + if ((file = fopen(pidfile, "w")) == NULL) { + syslog(LOG_ERR, "Cannot write pidfile to %s, exiting", pidfile); + err(1, "do_pidfile: Can not write pidfile to %s", pidfile); + } else { + fprintf(file, "%d\n", (int)getpid()); + fclose(file); + } +#else + fprintf(stderr, "Windows version does not support pid file\n"); +#endif +} + +void +do_detach() +{ +#ifndef WINDOWS32 + fprintf(stderr, "Detaching from terminal...\n"); + daemon(0, 0); + umask(0); + alarm(0); +#else + fprintf(stderr, "Windows version does not support detaching\n"); +#endif +} + +void +read_password(char *buf, size_t len) +{ + char pwd[80]; +#ifndef WINDOWS32 + struct termios old; + struct termios tp; + + tcgetattr(0, &tp); + old = tp; + + tp.c_lflag &= (~ECHO); + tcsetattr(0, TCSANOW, &tp); +#else + int i; +#endif + + fprintf(stderr, "Enter password: "); + fflush(stderr); +#ifndef WINDOWS32 + scanf("%79s", pwd); +#else + for (i = 0; i < sizeof(pwd); i++) { + pwd[i] = getch(); + if (pwd[i] == '\r' || pwd[i] == '\n') { + pwd[i] = 0; + break; + } else if (pwd[i] == '\b') { + i--; /* Remove the \b char */ + if (i >=0) i--; /* If not first char, remove one more */ + } + } +#endif + fprintf(stderr, "\n"); + +#ifndef WINDOWS32 + tcsetattr(0, TCSANOW, &old); +#endif + + strncpy(buf, pwd, len); + buf[len-1] = '\0'; +} + +int +check_topdomain(char *str) +{ + int i; + + if(str[0] == '.') /* special case */ + return 1; + + for( i = 0; i < strlen(str); i++) { + if( isalpha(str[i]) || isdigit(str[i]) || str[i] == '-' || str[i] == '.' ) + continue; + else + return 1; + } + return 0; +} + +#ifdef WINDOWS32 +int +inet_aton(const char *cp, struct in_addr *inp) +{ + inp->s_addr = inet_addr(cp); + return inp->s_addr != INADDR_ANY; +} + +void +warn(const char *fmt, ...) +{ + va_list list; + + va_start(list, fmt); + if (fmt) fprintf(stderr, fmt, list); + if (errno == 0) { + fprintf(stderr, ": WSA error %d\n", WSAGetLastError()); + } else { + fprintf(stderr, ": %s\n", strerror(errno)); + } + va_end(list); +} + +void +warnx(const char *fmt, ...) +{ + va_list list; + + va_start(list, fmt); + if (fmt) fprintf(stderr, fmt, list); + fprintf(stderr, "\n"); + va_end(list); +} + +void +err(int eval, const char *fmt, ...) +{ + va_list list; + + va_start(list, fmt); + warn(fmt, list); + va_end(list); + exit(eval); +} + +void +errx(int eval, const char *fmt, ...) +{ + va_list list; + + va_start(list, fmt); + warnx(fmt, list); + va_end(list); + exit(eval); +} +#endif + + +int recent_seqno(int ourseqno, int gotseqno) +/* Return 1 if we've seen gotseqno recently (current or up to 3 back). + Return 0 if gotseqno is new (or very old). +*/ +{ + int i; + for (i = 0; i < 4; i++, ourseqno--) { + if (ourseqno < 0) + ourseqno = 7; + if (gotseqno == ourseqno) + return 1; + } + return 0; +} diff --git a/iodine/src/common.h b/iodine/src/common.h new file mode 100644 index 0000000..8dbbfbd --- /dev/null +++ b/iodine/src/common.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __COMMON_H__ +#define __COMMON_H__ + +/* Last byte of raw header is the command */ +#define RAW_HDR_LEN 4 +#define RAW_HDR_IDENT_LEN 3 +#define RAW_HDR_CMD 3 +#define RAW_HDR_CMD_LOGIN 0x10 +#define RAW_HDR_CMD_DATA 0x20 +#define RAW_HDR_CMD_PING 0x30 + +#define RAW_HDR_CMD_MASK 0xF0 +#define RAW_HDR_USR_MASK 0x0F +#define RAW_HDR_GET_CMD(x) ((x)[RAW_HDR_CMD] & RAW_HDR_CMD_MASK) +#define RAW_HDR_GET_USR(x) ((x)[RAW_HDR_CMD] & RAW_HDR_USR_MASK) +extern const unsigned char raw_header[RAW_HDR_LEN]; + +#ifdef WINDOWS32 +#include "windows.h" +#else +#include +#include +#include +#include +#include +#endif + +#define DNS_PORT 53 + +#ifndef MIN +#define MIN(a,b) ((a)<(b)?(a):(b)) +#endif +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif + +#define QUERY_NAME_SIZE 256 + +#if defined IP_RECVDSTADDR +# define DSTADDR_SOCKOPT IP_RECVDSTADDR +# define dstaddr(x) ((struct in_addr *) CMSG_DATA(x)) +#elif defined IP_PKTINFO +# define DSTADDR_SOCKOPT IP_PKTINFO +# define dstaddr(x) (&(((struct in_pktinfo *)(CMSG_DATA(x)))->ipi_addr)) +#endif + +#if defined IP_MTU_DISCOVER + /* Linux */ +# define IP_OPT_DONT_FRAG IP_MTU_DISCOVER +# define DONT_FRAG_VALUE IP_PMTUDISC_DO +#elif defined IP_DONTFRAG + /* FreeBSD */ +# define IP_OPT_DONT_FRAG IP_DONTFRAG +# define DONT_FRAG_VALUE 1 +#elif defined IP_DONTFRAGMENT + /* Winsock2 */ +# define IP_OPT_DONT_FRAG IP_DONTFRAGMENT +# define DONT_FRAG_VALUE 1 +#endif + +#define T_UNSET 65432 +/* Unused RR type; "private use" range, see http://www.bind9.net/dns-parameters */ + +struct packet +{ + int len; /* Total packet length */ + int sentlen; /* Length of chunk currently transmitted */ + int offset; /* Current offset */ + char data[64*1024]; /* The data */ + char seqno; /* The packet sequence number */ + char fragment; /* Fragment index */ +}; + +struct query { + char name[QUERY_NAME_SIZE]; + unsigned short type; + unsigned short rcode; + unsigned short id; + struct in_addr destination; + struct sockaddr from; + int fromlen; + unsigned short id2; + struct sockaddr from2; + int fromlen2; +}; + +enum connection { + CONN_RAW_UDP, + CONN_DNS_NULL, + CONN_MAX +}; + +void check_superuser(void (*usage_fn)(void)); +int open_dns(int, in_addr_t); +void close_dns(int); + +void do_chroot(char *); +void do_setcon(char *); +void do_detach(); +void do_pidfile(char *); + +void read_password(char*, size_t); + +int check_topdomain(char *); + +#ifdef WINDOWS32 +int inet_aton(const char *cp, struct in_addr *inp); + +void err(int eval, const char *fmt, ...); +void warn(const char *fmt, ...); +void errx(int eval, const char *fmt, ...); +void warnx(const char *fmt, ...); +#endif + +int recent_seqno(int , int); + +#endif diff --git a/iodine/src/dns.c b/iodine/src/dns.c new file mode 100644 index 0000000..fb2bcaf --- /dev/null +++ b/iodine/src/dns.c @@ -0,0 +1,610 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include "windows.h" +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#include +#include +#endif + + +#include "dns.h" +#include "encoding.h" +#include "read.h" + +int dnsc_use_edns0 = 1; + +#define CHECKLEN(x) if (buflen - (p-buf) < (x)) return 0 + +int +dns_encode(char *buf, size_t buflen, struct query *q, qr_t qr, char *data, size_t datalen) +{ + HEADER *header; + short name; + char *p; + int len; + int ancnt; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = (qr == QR_ANSWER); + header->opcode = 0; + header->aa = (qr == QR_ANSWER); + header->tc = 0; + header->rd = (qr == QR_QUERY); + header->ra = 0; + + p = buf + sizeof(HEADER); + + switch (qr) { + case QR_ANSWER: + header->qdcount = htons(1); + + name = 0xc000 | ((p - buf) & 0x3fff); + + /* Question section */ + putname(&p, buflen - (p - buf), q->name); + + CHECKLEN(4); + putshort(&p, q->type); + putshort(&p, C_IN); + + /* Answer section */ + + if (q->type == T_CNAME || q->type == T_A) { + /* data is expected to be like "Hblabla.host.name.com\0" */ + + char *startp; + int namelen; + + CHECKLEN(10); + putshort(&p, name); + if (q->type == T_A) + /* answer CNAME to A question */ + putshort(&p, T_CNAME); + else + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + putname(&p, buflen - (p - buf), data); + CHECKLEN(0); + namelen = p - startp; + namelen -= 2; + putshort(&startp, namelen); + ancnt = 1; + } else if (q->type == T_MX || q->type == T_SRV) { + /* Data is expected to be like + "Hblabla.host.name.com\0Hanother.com\0\0" + For SRV, see RFC2782. + */ + + char *mxdata = data; + char *startp; + int namelen; + + ancnt = 1; + while (1) { + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + CHECKLEN(2); + putshort(&p, 10 * ancnt); /* preference */ + + if (q->type == T_SRV) { + /* weight, port (5060 = SIP) */ + CHECKLEN(4); + putshort(&p, 10); + putshort(&p, 5060); + } + + putname(&p, buflen - (p - buf), mxdata); + CHECKLEN(0); + namelen = p - startp; + namelen -= 2; + putshort(&startp, namelen); + + mxdata = mxdata + strlen(mxdata) + 1; + if (*mxdata == '\0') + break; + + ancnt++; + } + } else if (q->type == T_TXT) { + /* TXT has binary or base-X data */ + char *startp; + int txtlen; + + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + startp = p; + p += 2; /* skip 2 bytes length */ + puttxtbin(&p, buflen - (p - buf), data, datalen); + CHECKLEN(0); + txtlen = p - startp; + txtlen -= 2; + putshort(&startp, txtlen); + ancnt = 1; + } else { + /* NULL has raw binary data */ + + CHECKLEN(10); + putshort(&p, name); + putshort(&p, q->type); + putshort(&p, C_IN); + putlong(&p, 0); /* TTL */ + + datalen = MIN(datalen, buflen - (p - buf)); + CHECKLEN(2); + putshort(&p, datalen); + CHECKLEN(datalen); + putdata(&p, data, datalen); + CHECKLEN(0); + ancnt = 1; + } + header->ancount = htons(ancnt); + break; + case QR_QUERY: + /* Note that iodined also uses this for forward queries */ + + header->qdcount = htons(1); + + datalen = MIN(datalen, buflen - (p - buf)); + putname(&p, datalen, data); + + CHECKLEN(4); + putshort(&p, q->type); + putshort(&p, C_IN); + + /* EDNS0 to advertise maximum response length + (even CNAME/A/MX, 255+255+header would be >512) */ + if (dnsc_use_edns0) { + header->arcount = htons(1); + /*XXX START adjust indent 1 tab forward*/ + CHECKLEN(11); + putbyte(&p, 0x00); /* Root */ + putshort(&p, 0x0029); /* OPT */ + putshort(&p, 0x1000); /* Payload size: 4096 */ + putshort(&p, 0x0000); /* Higher bits/edns version */ + putshort(&p, 0x8000); /* Z */ + putshort(&p, 0x0000); /* Data length */ + /*XXX END adjust indent 1 tab forward*/ + } + + break; + } + + len = p - buf; + + return len; +} + +int +dns_encode_ns_response(char *buf, size_t buflen, struct query *q, char *topdomain) +/* Only used when iodined gets an NS type query */ +/* Mostly same as dns_encode_a_response() below */ +{ + HEADER *header; + int len; + short name; + short topname; + short nsname; + char *ipp; + int domain_len; + char *p; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = 1; + header->opcode = 0; + header->aa = 1; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + p = buf + sizeof(HEADER); + + header->qdcount = htons(1); + header->ancount = htons(1); + header->arcount = htons(1); + + /* pointer to start of name */ + name = 0xc000 | ((p - buf) & 0x3fff); + + domain_len = strlen(q->name) - strlen(topdomain); + if (domain_len < 0 || domain_len == 1) + return -1; + if (strcasecmp(q->name + domain_len, topdomain)) + return -1; + if (domain_len >= 1 && q->name[domain_len - 1] != '.') + return -1; + + /* pointer to start of topdomain; instead of dots at the end + we have length-bytes in front, so total length is the same */ + topname = 0xc000 | ((p - buf + domain_len) & 0x3fff); + + /* Query section */ + putname(&p, buflen - (p - buf), q->name); /* Name */ + CHECKLEN(4); + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + + /* Answer section */ + CHECKLEN(12); + putshort(&p, name); /* Name */ + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 5); /* Data length */ + + /* pointer to ns.topdomain */ + nsname = 0xc000 | ((p - buf) & 0x3fff); + CHECKLEN(5); + putbyte(&p, 2); + putbyte(&p, 'n'); + putbyte(&p, 's'); + putshort(&p, topname); /* Name Server */ + + /* Additional data (A-record of NS server) */ + CHECKLEN(12); + putshort(&p, nsname); /* Name Server */ + putshort(&p, T_A); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 4); /* Data length */ + + /* ugly hack to output IP address */ + ipp = (char *) &q->destination; + CHECKLEN(4); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *ipp); + + len = p - buf; + return len; +} + +int +dns_encode_a_response(char *buf, size_t buflen, struct query *q) +/* Only used when iodined gets an A type query for ns.topdomain or www.topdomain */ +/* Mostly same as dns_encode_ns_response() above */ +{ + HEADER *header; + int len; + short name; + char *ipp; + char *p; + + if (buflen < sizeof(HEADER)) + return 0; + + memset(buf, 0, buflen); + + header = (HEADER*)buf; + + header->id = htons(q->id); + header->qr = 1; + header->opcode = 0; + header->aa = 1; + header->tc = 0; + header->rd = 0; + header->ra = 0; + + p = buf + sizeof(HEADER); + + header->qdcount = htons(1); + header->ancount = htons(1); + + /* pointer to start of name */ + name = 0xc000 | ((p - buf) & 0x3fff); + + /* Query section */ + putname(&p, buflen - (p - buf), q->name); /* Name */ + CHECKLEN(4); + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + + /* Answer section */ + CHECKLEN(12); + putshort(&p, name); /* Name */ + putshort(&p, q->type); /* Type */ + putshort(&p, C_IN); /* Class */ + putlong(&p, 3600); /* TTL */ + putshort(&p, 4); /* Data length */ + + /* ugly hack to output IP address */ + ipp = (char *) &q->destination; + CHECKLEN(4); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *(ipp++)); + putbyte(&p, *ipp); + + len = p - buf; + return len; +} + +#undef CHECKLEN + +unsigned short +dns_get_id(char *packet, size_t packetlen) +{ + HEADER *header; + header = (HEADER*)packet; + + if (packetlen < sizeof(HEADER)) + return 0; + + return ntohs(header->id); +} + +#define CHECKLEN(x) if (packetlen - (data-packet) < (x)) return 0 + +int +dns_decode(char *buf, size_t buflen, struct query *q, qr_t qr, char *packet, size_t packetlen) +{ + char name[QUERY_NAME_SIZE]; + char rdata[4*1024]; + HEADER *header; + short qdcount; + short ancount; + uint32_t ttl; + short class; + short type; + char *data; + short rlen; + int id; + int rv; + + q->id2 = 0; + rv = 0; + header = (HEADER*)packet; + + /* Reject short packets */ + if (packetlen < sizeof(HEADER)) + return 0; + + if (header->qr != qr) { + warnx("header->qr does not match the requested qr"); + return -1; + } + + data = packet + sizeof(HEADER); + qdcount = ntohs(header->qdcount); + ancount = ntohs(header->ancount); + + id = ntohs(header->id); + id = id & 0xFFFF; /* Kill any sign extension */ + + rlen = 0; + + if (q != NULL) + q->rcode = header->rcode; + + switch (qr) { + case QR_ANSWER: + if(qdcount < 1) { + /* We need a question */ + return -1; + } + + if (q != NULL) + q->id = id; + + /* Read name even if no answer, to give better error message */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(4); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + + /* if CHECKLEN okay, then we're sure to have a proper name */ + if (q != NULL) { + /* We only need the first char to check it */ + q->name[0] = name[0]; + q->name[1] = '\0'; + } + + if (ancount < 1) { + /* DNS errors like NXDOMAIN have ancount=0 and + stop here. CNAME may also have A; MX/SRV may have + multiple results. */ + return -1; + } + + /* Here type is still the question type */ + if (type == T_NULL) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + rv = MIN(rlen, sizeof(rdata)); + rv = readdata(packet, &data, rdata, rv); + if (rv >= 2 && buf) { + rv = MIN(rv, buflen); + memcpy(buf, rdata, rv); + } else { + rv = 0; + } + } + else if ((type == T_A || type == T_CNAME) && buf) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + memset(name, 0, sizeof(name)); + readname(packet, packetlen, &data, name, sizeof(name) - 1); + name[sizeof(name)-1] = '\0'; + strncpy(buf, name, buflen); + buf[buflen - 1] = '\0'; + rv = strlen(buf); + } + else if ((type == T_MX || type == T_SRV) && buf) { + /* We support 250 records, 250*(255+header) ~= 64kB. + Only exact 10-multiples are accepted, and gaps in + numbering are not jumped over (->truncated). + Hopefully DNS servers won't mess around too much. + */ + char names[250][QUERY_NAME_SIZE]; + char *rdatastart; + short pref; + int i; + int offset; + + memset(names, 0, sizeof(names)); + + for (i=0; i < ancount; i++) { + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(12); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + rdatastart = data; + readshort(packet, &data, &pref); + + if (type == T_SRV) { + /* skip weight, port */ + data += 4; + CHECKLEN(0); + } + + if (pref % 10 == 0 && pref >= 10 && + pref < 2500) { + readname(packet, packetlen, &data, + names[pref / 10 - 1], + QUERY_NAME_SIZE - 1); + names[pref / 10 - 1][QUERY_NAME_SIZE-1] = '\0'; + } + + /* always trust rlen, not name encoding */ + data = rdatastart + rlen; + CHECKLEN(0); + } + + /* output is like Hname10.com\0Hname20.com\0\0 */ + offset = 0; + i = 0; + while (names[i][0] != '\0') { + int l = MIN(strlen(names[i]), buflen-offset-2); + if (l <= 0) + break; + memcpy(buf + offset, names[i], l); + offset += l; + *(buf + offset) = '\0'; + offset++; + i++; + } + *(buf + offset) = '\0'; + rv = offset; + } + else if (type == T_TXT && buf) { + /* Assume that first answer is what we wanted */ + readname(packet, packetlen, &data, name, sizeof(name)); + CHECKLEN(10); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + readlong(packet, &data, &ttl); + readshort(packet, &data, &rlen); + + rv = readtxtbin(packet, &data, rlen, rdata, sizeof(rdata)); + if (rv >= 1) { + rv = MIN(rv, buflen); + memcpy(buf, rdata, rv); + } else { + rv = 0; + } + } + + /* Here type is the answer type (note A->CNAME) */ + if (q != NULL) + q->type = type; + break; + case QR_QUERY: + if (qdcount < 1) { + warnx("no question section in name query"); + return -1; + } + + memset(name, 0, sizeof(name)); + readname(packet, packetlen, &data, name, sizeof(name) - 1); + name[sizeof(name)-1] = '\0'; + CHECKLEN(4); + readshort(packet, &data, &type); + readshort(packet, &data, &class); + + if (q == NULL) { + rv = 0; + break; + } + + strncpy(q->name, name, sizeof(q->name)); + q->name[sizeof(q->name) - 1] = '\0'; + q->type = type; + q->id = id; + + rv = strlen(q->name); + break; + } + + return rv; +} + diff --git a/iodine/src/dns.h b/iodine/src/dns.h new file mode 100644 index 0000000..72d4fe9 --- /dev/null +++ b/iodine/src/dns.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __DNS_H__ +#define __DNS_H__ + +#include "common.h" + +typedef enum { + QR_QUERY = 0, + QR_ANSWER = 1 +} qr_t; + +extern int dnsc_use_edns0; + +int dns_encode(char *, size_t, struct query *, qr_t, char *, size_t); +int dns_encode_ns_response(char *buf, size_t buflen, struct query *q, char *topdomain); +int dns_encode_a_response(char *buf, size_t buflen, struct query *q); +unsigned short dns_get_id(char *packet, size_t packetlen); +int dns_decode(char *, size_t, struct query *, qr_t, char *, size_t); + +#endif /* _DNS_H_ */ diff --git a/iodine/src/encoding.c b/iodine/src/encoding.c new file mode 100644 index 0000000..896d67d --- /dev/null +++ b/iodine/src/encoding.c @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include "common.h" +#include "encoding.h" + +int +build_hostname(char *buf, size_t buflen, + const char *data, const size_t datalen, + const char *topdomain, struct encoder *encoder, int maxlen) +{ + int encsize; + size_t space; + char *b; + + space = MIN(maxlen, buflen) - strlen(topdomain) - 8; + /* 8 = 5 max header length + 1 dot before topdomain + 2 safety */ + + if (!encoder->places_dots()) + space -= (space / 57); /* 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); + + /* move b back one step to see if the dot is there */ + b--; + if (*b != '.') + *++b = '.'; + b++; + /* move b ahead of the string so we can copy to it */ + + strncpy(b, topdomain, strlen(topdomain)+1); + + return space; +} + +int +unpack_data(char *buf, size_t buflen, char *data, size_t datalen, struct encoder *enc) +{ + if (!enc->eats_dots()) + datalen = inline_undotify(data, datalen); + return enc->decode(buf, &buflen, data, datalen); +} + +int +inline_dotify(char *buf, size_t buflen) +{ + unsigned dots; + unsigned pos; + unsigned total; + char *reader, *writer; + + total = strlen(buf); + dots = total / 57; + + writer = buf; + writer += total; + writer += dots; + + total += dots; + if (strlen(buf) + dots > buflen) { + writer = buf; + writer += buflen; + total = buflen; + } + + reader = writer - dots; + pos = (unsigned) (reader - buf) + 1; + + while (dots) { + *writer-- = *reader--; + pos--; + if (pos % 57 == 0) { + *writer-- = '.'; + dots--; + } + } + + /* 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; + } + *writer++ = *reader++; + pos++; + } + + /* return new length of string */ + return len - dots; +} diff --git a/iodine/src/encoding.h b/iodine/src/encoding.h new file mode 100644 index 0000000..7ddf6e0 --- /dev/null +++ b/iodine/src/encoding.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 _ENCODING_H_ +#define _ENCODING_H_ + +/* All-0, all-1, 01010101, 10101010: each 4 times to make sure the pattern + spreads across multiple encoded chars -> 16 bytes total. + Followed by 32 bytes from my /dev/random; should be enough. + */ +#define DOWNCODECCHECK1 "\000\000\000\000\377\377\377\377\125\125\125\125\252\252\252\252\201\143\310\322\307\174\262\027\137\117\316\311\111\055\122\041\141\251\161\040\045\263\006\163\346\330\104\060\171\120\127\277" +#define DOWNCODECCHECK1_LEN 48 + +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 (*blocksize_raw)(void); + int (*blocksize_encoded)(void); +}; + +int build_hostname(char *, size_t, const char *, const size_t, const char *, struct encoder *, int); +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_ */ diff --git a/iodine/src/fw_query.c b/iodine/src/fw_query.c new file mode 100644 index 0000000..3727f08 --- /dev/null +++ b/iodine/src/fw_query.c @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2008 Erik Ekman + * + * 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 +#include "fw_query.h" + +static struct fw_query fwq[FW_QUERY_CACHE_SIZE]; +static int fwq_ix; + +void fw_query_init() +{ + memset(fwq, 0, sizeof(struct fw_query) * FW_QUERY_CACHE_SIZE); + fwq_ix = 0; +} + +void fw_query_put(struct fw_query *fw_query) +{ + memcpy(&(fwq[fwq_ix]), fw_query, sizeof(struct fw_query)); + + ++fwq_ix; + if (fwq_ix >= FW_QUERY_CACHE_SIZE) + fwq_ix = 0; +} + +void fw_query_get(unsigned short query_id, struct fw_query **fw_query) +{ + int i; + + *fw_query = NULL; + for (i = 0; i < FW_QUERY_CACHE_SIZE; i++) { + if (fwq[i].id == query_id) { + *fw_query = &(fwq[i]); + return; + } + } +} diff --git a/iodine/src/fw_query.h b/iodine/src/fw_query.h new file mode 100644 index 0000000..7568a5f --- /dev/null +++ b/iodine/src/fw_query.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2008 Erik Ekman + * + * 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 __FW_QUERY_H__ +#define __FW_QUERY_H__ + +#include +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#endif + +#define FW_QUERY_CACHE_SIZE 16 + +struct fw_query { + struct sockaddr addr; + int addrlen; + unsigned short id; +}; + +void fw_query_init(); +void fw_query_put(struct fw_query *fw_query); +void fw_query_get(unsigned short query_id, struct fw_query **fw_query); + +#endif /*__FW_QUERY_H__*/ + diff --git a/iodine/src/iodine.c b/iodine/src/iodine.c new file mode 100644 index 0000000..0d34c43 --- /dev/null +++ b/iodine/src/iodine.c @@ -0,0 +1,379 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#include +#endif + +#include "common.h" +#include "tun.h" +#include "client.h" +#include "util.h" + +#ifdef WINDOWS32 +WORD req_version = MAKEWORD(2, 2); +WSADATA wsa_data; +#endif + +#if !defined(BSD) && !defined(__GLIBC__) +static char *__progname; +#endif + +#define PASSWORD_ENV_VAR "IODINE_PASS" + +static void +sighandler(int sig) +{ + client_stop(); +} + +static void +usage() { + extern char *__progname; + + fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " + "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); + exit(2); +} + +static void +help() { + extern char *__progname; + + fprintf(stderr, "iodine IP over DNS tunneling client\n"); + fprintf(stderr, "Usage: %s [-v] [-h] [-f] [-r] [-u user] [-t chrootdir] [-d device] " + "[-P password] [-m maxfragsize] [-M maxlen] [-T type] [-O enc] [-L 0|1] [-I sec] " + "[-z context] [-F pidfile] [nameserver] topdomain\n", __progname); + fprintf(stderr, "Options to try if connection doesn't work:\n"); + fprintf(stderr, " -T force dns type: NULL, TXT, SRV, MX, CNAME, A (default: autodetect)\n"); + fprintf(stderr, " -O force downstream encoding for -T other than NULL: Base32, Base64, Base64u,\n"); + fprintf(stderr, " Base128, or (only for TXT:) Raw (default: autodetect)\n"); + fprintf(stderr, " -I max interval between requests (default 4 sec) to prevent DNS timeouts\n"); + fprintf(stderr, " -L 1: use lazy mode for low-latency (default). 0: don't (implies -I1)\n"); + fprintf(stderr, " -m max size of downstream fragments (default: autodetect)\n"); + fprintf(stderr, " -M max size of upstream hostnames (~100-255, default: 255)\n"); + fprintf(stderr, " -r to skip raw UDP mode attempt\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, "Other options:\n"); + fprintf(stderr, " -v to print version info and exit\n"); + fprintf(stderr, " -h to print this help and exit\n"); + fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); + fprintf(stderr, " -t dir to chroot to directory dir\n"); + fprintf(stderr, " -d device to set tunnel device name\n"); + fprintf(stderr, " -z context, to apply specified SELinux context after initialization\n"); + fprintf(stderr, " -F pidfile to write pid to a file\n"); + fprintf(stderr, "nameserver is the IP number/hostname of the relaying nameserver. if absent, /etc/resolv.conf is used\n"); + fprintf(stderr, "topdomain is the FQDN that is delegated to the tunnel endpoint.\n"); + + exit(0); +} + +static void +version() { + char *svnver; + + svnver = "$Rev$ from $Date$"; + + fprintf(stderr, "iodine IP over DNS tunneling client\n"); + fprintf(stderr, "SVN version: %s\n", svnver); + + exit(0); +} + +int +main(int argc, char **argv) +{ + char *nameserv_addr; + char *topdomain; +#ifndef WINDOWS32 + struct passwd *pw; +#endif + char *username; + char password[33]; + int foreground; + char *newroot; + char *context; + char *device; + char *pidfile; + int choice; + int tun_fd; + int dns_fd; + int max_downstream_frag_size; + int autodetect_frag_size; + int retval; + int raw_mode; + int lazymode; + int selecttimeout; + int hostname_maxlen; + + nameserv_addr = NULL; + topdomain = NULL; +#ifndef WINDOWS32 + pw = NULL; +#endif + username = NULL; + memset(password, 0, 33); + srand(time(NULL)); + foreground = 0; + newroot = NULL; + context = NULL; + device = NULL; + pidfile = NULL; + + autodetect_frag_size = 1; + max_downstream_frag_size = 3072; + retval = 0; + raw_mode = 1; + lazymode = 1; + selecttimeout = 4; + hostname_maxlen = 0xFF; + +#ifdef WINDOWS32 + WSAStartup(req_version, &wsa_data); +#endif + + srand((unsigned) time(NULL)); + client_init(); + +#if !defined(BSD) && !defined(__GLIBC__) + __progname = strrchr(argv[0], '/'); + if (__progname == NULL) + __progname = argv[0]; + else + __progname++; +#endif + + while ((choice = getopt(argc, argv, "vfhru:t:d:P:m:M:F:T:O:L:I:")) != -1) { + switch(choice) { + case 'v': + version(); + /* NOTREACHED */ + break; + case 'f': + foreground = 1; + break; + case 'h': + help(); + /* NOTREACHED */ + break; + case 'r': + raw_mode = 0; + case 'u': + username = optarg; + break; + case 't': + newroot = optarg; + break; + case 'd': + device = optarg; + break; + case 'P': + 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; + case 'm': + autodetect_frag_size = 0; + max_downstream_frag_size = atoi(optarg); + break; + case 'M': + hostname_maxlen = atoi(optarg); + if (hostname_maxlen > 255) + hostname_maxlen = 255; + if (hostname_maxlen < 10) + hostname_maxlen = 10; + break; + case 'z': + context = optarg; + break; + case 'F': + pidfile = optarg; + break; + case 'T': + set_qtype(optarg); + break; + case 'O': /* not -D, is Debug in server */ + set_downenc(optarg); + break; + case 'L': + lazymode = atoi(optarg); + if (lazymode > 1) + lazymode = 1; + if (lazymode < 0) + lazymode = 0; + if (!lazymode) + selecttimeout = 1; + break; + case 'I': + selecttimeout = atoi(optarg); + if (selecttimeout < 1) + selecttimeout = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + check_superuser(usage); + + argc -= optind; + argv += optind; + + 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 */ + } + + if (max_downstream_frag_size < 1 || max_downstream_frag_size > 0xffff) { + warnx("Use a max frag size between 1 and 65535 bytes.\n"); + usage(); + /* NOTREACHED */ + } + + if (nameserv_addr) { + client_set_nameserver(nameserv_addr, DNS_PORT); + } else { + warnx("No nameserver found - not connected to any network?\n"); + usage(); + /* NOTREACHED */ + } + + if (strlen(topdomain) <= 128) { + if(check_topdomain(topdomain)) { + warnx("Topdomain contains invalid characters.\n"); + usage(); + /* NOTREACHED */ + } + } else { + warnx("Use a topdomain max 128 chars long.\n"); + usage(); + /* NOTREACHED */ + } + + client_set_selecttimeout(selecttimeout); + client_set_lazymode(lazymode); + client_set_topdomain(topdomain); + client_set_hostname_maxlen(hostname_maxlen); + + if (username != NULL) { +#ifndef WINDOWS32 + if ((pw = getpwnam(username)) == NULL) { + warnx("User %s does not exist!\n", username); + usage(); + /* NOTREACHED */ + } +#endif + } + + if (strlen(password) == 0) { + if (NULL != getenv(PASSWORD_ENV_VAR)) + snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + else + read_password(password, sizeof(password)); + } + + client_set_password(password); + + if ((tun_fd = open_tun(device)) == -1) { + retval = 1; + goto cleanup1; + } + if ((dns_fd = open_dns(0, INADDR_ANY)) == -1) { + retval = 1; + goto cleanup2; + } + + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); + + fprintf(stderr, "Sending DNS queries for %s to %s\n", + topdomain, nameserv_addr); + + if (client_handshake(dns_fd, raw_mode, autodetect_frag_size, max_downstream_frag_size)) { + retval = 1; + goto cleanup2; + } + + if (client_get_conn() == CONN_RAW_UDP) { + fprintf(stderr, "Sending raw traffic directly to %s\n", client_get_raw_addr()); + } + + fprintf(stderr, "Connection setup complete, transmitting data.\n"); + + if (foreground == 0) + do_detach(); + + if (pidfile != NULL) + do_pidfile(pidfile); + + if (newroot != NULL) + do_chroot(newroot); + + if (username != NULL) { +#ifndef WINDOWS32 + gid_t gids[1]; + gids[0] = pw->pw_gid; + if (setgroups(1, gids) < 0 || setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { + warnx("Could not switch to user %s!\n", username); + usage(); + /* NOTREACHED */ + } +#endif + } + + if (context != NULL) + do_setcon(context); + + client_tunnel(tun_fd, dns_fd); + +cleanup2: + close_dns(dns_fd); + close_tun(tun_fd); +cleanup1: + + return retval; +} + diff --git a/iodine/src/iodined.c b/iodine/src/iodined.c new file mode 100644 index 0000000..84b2281 --- /dev/null +++ b/iodine/src/iodined.c @@ -0,0 +1,2488 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" + +#ifdef WINDOWS32 +#include "windows.h" +#include +#else +#include +#ifdef DARWIN +#define BIND_8_COMPAT +#include +#endif +#define _XPG4_2 +#include +#include +#include +#include +#include +#include +#include +#endif + +#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 "version.h" + +#ifdef WINDOWS32 +WORD req_version = MAKEWORD(2, 2); +WSADATA wsa_data; +#endif + +#define PASSWORD_ENV_VAR "IODINED_PASS" + +static int running = 1; +static char *topdomain; +static char password[33]; +static struct encoder *b32; +static struct encoder *b64; +static struct encoder *b64u; +static struct encoder *b128; +static int created_users; + +static int check_ip; +static int my_mtu; +static in_addr_t my_ip; +static int netmask; + +static in_addr_t ns_ip; + +static int bind_port; +static int debug; + +#if !defined(BSD) && !defined(__GLIBC__) +static char *__progname; +#endif + +static int read_dns(int, int, struct query *); +static void write_dns(int, struct query *, char *, int, char); +static void handle_full_packet(int, int, int); + +static void +sigint(int sig) +{ + running = 0; +} + +#ifdef WINDOWS32 +#define LOG_EMERG 0 +#define LOG_ALERT 1 +#define LOG_CRIT 2 +#define LOG_ERR 3 +#define LOG_WARNING 4 +#define LOG_NOTICE 5 +#define LOG_INFO 6 +#define LOG_DEBUG 7 +static void +syslog(int a, const char *str, ...) +{ + /* TODO: implement (add to event log), move to common.c */ + ; +} +#endif + +static int +check_user_and_ip(int userid, struct query *q) +{ + struct sockaddr_in *tempin; + + /* Note: duplicate in handle_raw_login() except IP-address check */ + + if (userid < 0 || userid >= created_users ) { + return 1; + } + if (!users[userid].active || users[userid].disabled) { + return 1; + } + if (users[userid].last_pkt + 60 < time(NULL)) { + return 1; + } + + /* return early if IP checking is disabled */ + if (!check_ip) { + return 0; + } + + tempin = (struct sockaddr_in *) &(q->from); + return memcmp(&(users[userid].host), &(tempin->sin_addr), sizeof(struct in_addr)); +} + +static void +send_raw(int fd, char *buf, int buflen, int user, int cmd, struct query *q) +{ + char packet[4096]; + int len; + + len = MIN(sizeof(packet) - RAW_HDR_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); + + if (debug >= 2) { + struct sockaddr_in *tempin; + tempin = (struct sockaddr_in *) &(q->from); + fprintf(stderr, "TX-raw: client %s, cmd %d, %d bytes\n", + inet_ntoa(tempin->sin_addr), cmd, len); + } + + sendto(fd, packet, len, 0, &q->from, q->fromlen); +} + + +static void +start_new_outpacket(int userid, char *data, int datalen) +/* Copies data to .outpacket and resets all counters. + data is expected to be compressed already. */ +{ + datalen = MIN(datalen, sizeof(users[userid].outpacket.data)); + memcpy(users[userid].outpacket.data, data, datalen); + users[userid].outpacket.len = datalen; + users[userid].outpacket.offset = 0; + users[userid].outpacket.sentlen = 0; + users[userid].outpacket.seqno = (users[userid].outpacket.seqno + 1) & 7; + users[userid].outpacket.fragment = 0; + users[userid].outfragresent = 0; +} + +#ifdef OUTPACKETQ_LEN + +static int +save_to_outpacketq(int userid, char *data, int datalen) +/* Find space in outpacket-queue and store data (expected compressed already). + Returns: 1 = okay, 0 = no space. */ +{ + int fill; + + if (users[userid].outpacketq_filled >= OUTPACKETQ_LEN) + /* no space */ + return 0; + + fill = users[userid].outpacketq_nexttouse + + users[userid].outpacketq_filled; + if (fill >= OUTPACKETQ_LEN) + fill -= OUTPACKETQ_LEN; + + datalen = MIN(datalen, sizeof(users[userid].outpacketq[fill].data)); + memcpy(users[userid].outpacketq[fill].data, data, datalen); + users[userid].outpacketq[fill].len = datalen; + + users[userid].outpacketq_filled++; + + if (debug >= 3) + fprintf(stderr, " Qstore, now %d\n", + users[userid].outpacketq_filled); + + return 1; +} + +static int +get_from_outpacketq(int userid) +/* Starts new outpacket from queue, if any. + Returns: 1 = okay, 0 = no packets were waiting. */ +{ + int use; + + if (users[userid].outpacketq_filled <= 0) + /* no packets */ + return 0; + + use = users[userid].outpacketq_nexttouse; + + start_new_outpacket(userid, users[userid].outpacketq[use].data, + users[userid].outpacketq[use].len); + + use++; + if (use >= OUTPACKETQ_LEN) + use = 0; + users[userid].outpacketq_nexttouse = use; + users[userid].outpacketq_filled--; + + if (debug >= 3) + fprintf(stderr, " Qget, now %d\n", + users[userid].outpacketq_filled); + + return 1; +} + +#endif /* OUTPACKETQ_LEN */ + +#ifdef DNSCACHE_LEN + +/* 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. Data-CMC is only 36 counts, so our cache length should + not exceed 36/2=18 packets. (This quick rule assumes all packets are + otherwise equal, which they arent: up/downstream seq/frag, tcp sequence + number, and of course data.) +*/ + +static void +save_to_dnscache(int userid, struct query *q, char *answer, int answerlen) +/* Store answer in our little DNS cache. */ +{ + int fill; + + if (answerlen > sizeof(users[userid].dnscache_answer[fill])) + return; /* can't store this */ + + fill = users[userid].dnscache_lastfilled + 1; + if (fill >= DNSCACHE_LEN) + fill = 0; + + memcpy(&(users[userid].dnscache_q[fill]), q, sizeof(struct query)); + memcpy(users[userid].dnscache_answer[fill], answer, answerlen); + users[userid].dnscache_answerlen[fill] = answerlen; + + users[userid].dnscache_lastfilled = fill; +} + +static int +answer_from_dnscache(int dns_fd, int userid, struct query *q) +/* Checks cache and sends repeated answer if we alreay saw this query recently. + Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is + a new query. */ +{ + int i; + int use; + + for (i = 0; i < DNSCACHE_LEN ; i++) { + /* Try cache most-recent-first */ + use = users[userid].dnscache_lastfilled - i; + if (use < 0) + use += DNSCACHE_LEN; + + if (users[userid].dnscache_q[use].id == 0) + continue; + if (users[userid].dnscache_answerlen[use] <= 0) + continue; + + if (users[userid].dnscache_q[use].type != q->type || + strcmp(users[userid].dnscache_q[use].name, q->name)) + continue; + + /* okay, match */ + if (debug >= 1) + fprintf(stderr, "OUT user %d %s from dnscache\n", userid, q->name); + + write_dns(dns_fd, q, users[userid].dnscache_answer[use], + users[userid].dnscache_answerlen[use], + users[userid].downenc); + + q->id = 0; /* this query was used */ + return 1; + } + + /* here only when no match found */ + return 0; +} + +#endif /* DNSCACHE_LEN */ + +static inline void +save_to_qmem(unsigned char *qmem_cmc, unsigned short *qmem_type, int qmem_len, + int *qmem_lastfilled, unsigned char *cmc_to_add, + unsigned short type_to_add) +/* Remember query to check for duplicates */ +{ + int fill; + + fill = *qmem_lastfilled + 1; + if (fill >= qmem_len) + fill = 0; + + memcpy(qmem_cmc + fill * 4, cmc_to_add, 4); + qmem_type[fill] = type_to_add; + *qmem_lastfilled = fill; +} + +static inline void +save_to_qmem_pingordata(int userid, struct query *q) +{ + /* Our CMC is a bit more than the "official" CMC; we store 4 bytes + just because we can, and because it may prevent some false matches. + For ping, we save the 4 decoded bytes: userid + seq/frag + CMC. + For data, we save the 4 _un_decoded chars in lowercase: seq/frag's + + 1 char CMC; that last char is non-Base32. + */ + + char cmc[8]; + int i; + + if (q->name[0] == 'P' || q->name[0] == 'p') { + /* Ping packet */ + + size_t cmcsize = sizeof(cmc); + char *cp = strchr(q->name, '.'); + + if (cp == NULL) + return; /* illegal hostname; shouldn't happen */ + + /* We already unpacked in handle_null_request(), but that's + lost now... Note: b32 directly, we want no undotify here! */ + i = b32->decode(cmc, &cmcsize, q->name + 1, (cp - q->name) - 1); + + if (i < 4) + return; /* illegal ping; shouldn't happen */ + + save_to_qmem(users[userid].qmemping_cmc, + users[userid].qmemping_type, QMEMPING_LEN, + &users[userid].qmemping_lastfilled, + (void *) cmc, q->type); + } else { + /* Data packet, hopefully not illegal */ + if (strlen(q->name) < 5) + return; + + /* We store CMC in lowercase; if routing via multiple parallel + DNS servers, one may do case-switch and another may not, + and we still want to detect duplicates. + Data-header is always base32, so case-swap won't hurt. + */ + for (i = 0; i < 4; i++) + if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') + cmc[i] = q->name[i+1] + ('a' - 'A'); + else + cmc[i] = q->name[i+1]; + + save_to_qmem(users[userid].qmemdata_cmc, + users[userid].qmemdata_type, QMEMDATA_LEN, + &users[userid].qmemdata_lastfilled, + (void *) cmc, q->type); + } +} + +static int +answer_from_qmem(int dns_fd, struct query *q, unsigned char *qmem_cmc, + unsigned short *qmem_type, int qmem_len, + unsigned char *cmc_to_check) +/* Checks query memory and sends an (illegal) answer if this is a duplicate. + Returns: 1 = answer sent, drop this query, 0 = no answer sent, this is + not a duplicate. */ +{ + int i; + + for (i = 0; i < qmem_len ; i++) { + + if (qmem_type[i] == T_UNSET) + continue; + if (qmem_type[i] != q->type) + continue; + if (memcmp(qmem_cmc + i * 4, cmc_to_check, 4)) + continue; + + /* okay, match */ + if (debug >= 1) + fprintf(stderr, "OUT from qmem for %s == duplicate, sending illegal reply\n", q->name); + + write_dns(dns_fd, q, "x", 1, 'T'); + + q->id = 0; /* this query was used */ + return 1; + } + + /* here only when no match found */ + return 0; +} + +static inline int +answer_from_qmem_data(int dns_fd, int userid, struct query *q) +/* Quick helper function to keep handle_null_request() clean */ +{ + char cmc[4]; + int i; + + for (i = 0; i < 4; i++) + if (q->name[i+1] >= 'A' && q->name[i+1] <= 'Z') + cmc[i] = q->name[i+1] + ('a' - 'A'); + else + cmc[i] = q->name[i+1]; + + return answer_from_qmem(dns_fd, q, users[userid].qmemdata_cmc, + users[userid].qmemdata_type, QMEMDATA_LEN, + (void *) cmc); +} + +static int +send_chunk_or_dataless(int dns_fd, int userid, struct query *q) +/* Sends current fragment to user, or dataless packet if there is no + current fragment available (-> normal "quiet" ping reply). + Does not update anything, except: + - discards q always (query is used) + - forgets entire users[userid].outpacket if it was sent in one go, + and then tries to get new packet from outpacket-queue + Returns: 1 = can call us again immediately, new packet from queue; + 0 = don't call us again for now. +*/ +{ + char pkt[4096]; + int datalen = 0; + int last = 0; + + /* If re-sent too many times, drop entire packet */ + if (users[userid].outpacket.len > 0 && + users[userid].outfragresent > 5) { + users[userid].outpacket.len = 0; + users[userid].outpacket.offset = 0; + users[userid].outpacket.sentlen = 0; + users[userid].outfragresent = 0; + +#ifdef OUTPACKETQ_LEN + /* Maybe more in queue, use immediately */ + get_from_outpacketq(userid); +#endif + } + + if (users[userid].outpacket.len > 0) { + datalen = MIN(users[userid].fragsize, users[userid].outpacket.len - users[userid].outpacket.offset); + datalen = MIN(datalen, sizeof(pkt)-2); + + memcpy(&pkt[2], users[userid].outpacket.data + users[userid].outpacket.offset, datalen); + users[userid].outpacket.sentlen = datalen; + last = (users[userid].outpacket.len == users[userid].outpacket.offset + datalen); + + users[userid].outfragresent++; + } + + /* Build downstream data header (see doc/proto_xxxxxxxx.txt) */ + + /* First byte is 1 bit compression flag, 3 bits upstream seqno, 4 bits upstream fragment */ + pkt[0] = (1<<7) | ((users[userid].inpacket.seqno & 7) << 4) | + (users[userid].inpacket.fragment & 15); + /* Second byte is 3 bits downstream seqno, 4 bits downstream fragment, 1 bit last flag */ + pkt[1] = ((users[userid].outpacket.seqno & 7) << 5) | + ((users[userid].outpacket.fragment & 15) << 1) | (last & 1); + + if (debug >= 1) { + fprintf(stderr, "OUT pkt seq# %d, frag %d (last=%d), offset %d, fragsize %d, total %d, to user %d\n", + users[userid].outpacket.seqno & 7, users[userid].outpacket.fragment & 15, + last, users[userid].outpacket.offset, datalen, users[userid].outpacket.len, userid); + } + write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + + if (q->id2 != 0) { + q->id = q->id2; + q->fromlen = q->fromlen2; + memcpy(&(q->from), &(q->from2), q->fromlen2); + if (debug >= 1) + fprintf(stderr, "OUT again to last duplicate\n"); + write_dns(dns_fd, q, pkt, datalen + 2, users[userid].downenc); + } + + save_to_qmem_pingordata(userid, q); + +#ifdef DNSCACHE_LEN + save_to_dnscache(userid, q, pkt, datalen + 2); +#endif + + q->id = 0; /* this query is used */ + + if (datalen > 0 && datalen == users[userid].outpacket.len) { + /* Whole packet was sent in one chunk, dont wait for ack */ + users[userid].outpacket.len = 0; + users[userid].outpacket.offset = 0; + users[userid].outpacket.sentlen = 0; + users[userid].outfragresent = 0; + +#ifdef OUTPACKETQ_LEN + /* Maybe more in queue, prepare for next time */ + if (get_from_outpacketq(userid) == 1) { + if (debug >= 3) + fprintf(stderr, " Chunk & fromqueue: callagain\n"); + return 1; /* call us again */ + } +#endif + } + + return 0; /* don't call us again */ +} + +static int +tunnel_tun(int tun_fd, int dns_fd) +{ + unsigned long outlen; + struct ip *header; + char out[64*1024]; + char in[64*1024]; + int userid; + int read; + + if ((read = read_tun(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; + + outlen = sizeof(out); + compress2((uint8_t*)out, &outlen, (uint8_t*)in, read, 9); + + if (users[userid].conn == CONN_DNS_NULL) { +#ifdef OUTPACKETQ_LEN + /* If a packet is being sent, try storing the new one in the queue. + If the queue is full, drop the packet. TCP will hopefully notice + and reduce the packet rate. */ + if (users[userid].outpacket.len > 0) { + save_to_outpacketq(userid, out, outlen); + return 0; + } +#endif + + start_new_outpacket(userid, out, outlen); + + /* Start sending immediately if query is waiting */ + if (users[userid].q_sendrealsoon.id != 0) + send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + else if (users[userid].q.id != 0) + send_chunk_or_dataless(dns_fd, userid, &users[userid].q); + + return outlen; + } else { /* CONN_RAW_UDP */ + send_raw(dns_fd, out, outlen, userid, RAW_HDR_CMD_DATA, &users[userid].q); + return outlen; + } +} + +typedef enum { + VERSION_ACK, + VERSION_NACK, + VERSION_FULL +} version_ack_t; + +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; + } + + out[4] = ((payload >> 24) & 0xff); + out[5] = ((payload >> 16) & 0xff); + out[6] = ((payload >> 8) & 0xff); + out[7] = ((payload) & 0xff); + out[8] = userid & 0xff; + + write_dns(fd, q, out, sizeof(out), users[userid].downenc); +} + +static void +process_downstream_ack(int userid, int down_seq, int down_frag) +/* Process acks from downstream fragments. + After this, .offset and .fragment are updated (if ack correct), + or .len is set to zero when all is done. +*/ +{ + if (users[userid].outpacket.len <= 0) + /* No packet to apply acks to */ + return; + + if (users[userid].outpacket.seqno != down_seq || + users[userid].outpacket.fragment != down_frag) + /* Not the ack we're waiting for; probably duplicate of old + ack, happens a lot with ping packets */ + return; + + /* Received proper ack */ + users[userid].outpacket.offset += users[userid].outpacket.sentlen; + users[userid].outpacket.sentlen = 0; + users[userid].outpacket.fragment++; + users[userid].outfragresent = 0; + + /* Is packet done? */ + if (users[userid].outpacket.offset >= users[userid].outpacket.len) { + users[userid].outpacket.len = 0; + users[userid].outpacket.offset = 0; + users[userid].outpacket.fragment--; /* unneeded ++ above */ + /* ^keep last seqno/frag, are always returned on pings */ + /* users[userid].outfragresent = 0; already above */ + +#ifdef OUTPACKETQ_LEN + /* Possibly get new packet from queue */ + get_from_outpacketq(userid); +#endif + } +} + +static void +handle_null_request(int tun_fd, int dns_fd, struct query *q, int domain_len) +{ + struct in_addr tempip; + char in[512]; + char logindata[16]; + char out[64*1024]; + char unpacked[64*1024]; + char *tmp[2]; + int userid; + int read; + + userid = -1; + + /* Everything here needs at least two chars in the name */ + if (domain_len < 2) + return; + + memcpy(in, q->name, MIN(domain_len, sizeof(in))); + + if(in[0] == 'V' || in[0] == 'v') { + int version = 0; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 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))); + } + + if (version == VERSION) { + userid = find_available_user(); + if (userid >= 0) { + int i; + struct sockaddr_in *tempin; + + users[userid].seed = rand(); + /* Store remote IP number */ + tempin = (struct sockaddr_in *) &(q->from); + memcpy(&(users[userid].host), &(tempin->sin_addr), sizeof(struct in_addr)); + + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].encoder = get_base32_encoder(); + users[userid].downenc = 'T'; + send_version_response(dns_fd, VERSION_ACK, users[userid].seed, userid, q); + syslog(LOG_INFO, "accepted version for user #%d from %s", + userid, inet_ntoa(tempin->sin_addr)); + users[userid].q.id = 0; + users[userid].q.id2 = 0; + users[userid].q_sendrealsoon.id = 0; + users[userid].q_sendrealsoon.id2 = 0; + users[userid].q_sendrealsoon_new = 0; + users[userid].outpacket.len = 0; + users[userid].outpacket.offset = 0; + users[userid].outpacket.sentlen = 0; + users[userid].outpacket.seqno = 0; + users[userid].outpacket.fragment = 0; + users[userid].outfragresent = 0; + users[userid].inpacket.len = 0; + users[userid].inpacket.offset = 0; + users[userid].inpacket.seqno = 0; + users[userid].inpacket.fragment = 0; + users[userid].fragsize = 100; /* very safe */ + users[userid].conn = CONN_DNS_NULL; + users[userid].lazy = 0; +#ifdef OUTPACKETQ_LEN + users[userid].outpacketq_nexttouse = 0; + users[userid].outpacketq_filled = 0; +#endif +#ifdef DNSCACHE_LEN + { + for (i = 0; i < DNSCACHE_LEN; i++) { + users[userid].dnscache_q[i].id = 0; + users[userid].dnscache_answerlen[i] = 0; + } + } + users[userid].dnscache_lastfilled = 0; +#endif + for (i = 0; i < QMEMPING_LEN; i++) + users[userid].qmemping_type[i] = T_UNSET; + users[userid].qmemping_lastfilled = 0; + for (i = 0; i < QMEMDATA_LEN; i++) + users[userid].qmemdata_type[i] = T_UNSET; + users[userid].qmemdata_lastfilled = 0; + } else { + /* No space for another user */ + send_version_response(dns_fd, VERSION_FULL, created_users, 0, q); + syslog(LOG_INFO, "dropped user from %s, server full", + inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr)); + } + } else { + send_version_response(dns_fd, VERSION_NACK, VERSION, 0, q); + syslog(LOG_INFO, "dropped user from %s, sent bad version %08X", + inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr), version); + } + return; + } else if(in[0] == 'L' || in[0] == 'l') { + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + if (read < 17) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Login phase, handle auth */ + userid = unpacked[0]; + + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + syslog(LOG_WARNING, "dropped login request from user #%d from unexpected source %s", + userid, inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr)); + return; + } else { + users[userid].last_pkt = time(NULL); + login_calculate(logindata, 16, password, users[userid].seed); + + if (read >= 18 && (memcmp(logindata, unpacked+1, 16) == 0)) { + /* Login ok, send ip/mtu/netmask info */ + + tempip.s_addr = my_ip; + tmp[0] = strdup(inet_ntoa(tempip)); + tempip.s_addr = users[userid].tun_ip; + tmp[1] = strdup(inet_ntoa(tempip)); + + read = snprintf(out, sizeof(out), "%s-%s-%d-%d", + tmp[0], tmp[1], my_mtu, netmask); + + write_dns(dns_fd, q, out, read, users[userid].downenc); + q->id = 0; + syslog(LOG_NOTICE, "accepted password from user #%d, given IP %s", userid, tmp[1]); + + free(tmp[1]); + free(tmp[0]); + } else { + write_dns(dns_fd, q, "LNAK", 4, 'T'); + syslog(LOG_WARNING, "rejected login request from user #%d from %s, bad password", + userid, inet_ntoa(((struct sockaddr_in *) &q->from)->sin_addr)); + } + } + return; + } else if(in[0] == 'I' || in[0] == 'i') { + /* Request for IP number */ + in_addr_t replyaddr; + unsigned addr; + char reply[5]; + + userid = b32_8to5(in[1]); + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + if (ns_ip != INADDR_ANY) { + /* If set, use assigned external ip (-n option) */ + replyaddr = ns_ip; + } else { + /* otherwise return destination ip from packet */ + memcpy(&replyaddr, &q->destination.s_addr, sizeof(in_addr_t)); + } + + addr = htonl(replyaddr); + reply[0] = 'I'; + reply[1] = (addr >> 24) & 0xFF; + reply[2] = (addr >> 16) & 0xFF; + reply[3] = (addr >> 8) & 0xFF; + reply[4] = (addr >> 0) & 0xFF; + write_dns(dns_fd, q, reply, sizeof(reply), 'T'); + } else if(in[0] == 'Z' || in[0] == 'z') { + /* Check for case conservation and chars not allowed according to RFC */ + + /* Reply with received hostname as data */ + /* No userid here, reply with lowest-grade downenc */ + write_dns(dns_fd, q, in, domain_len, 'T'); + return; + } else if(in[0] == 'S' || in[0] == 's') { + int codec; + struct encoder *enc; + if (domain_len < 3) { /* len at least 3, example: "S15" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + codec = b32_8to5(in[2]); + + switch (codec) { + case 5: /* 5 bits per byte = base32 */ + enc = get_base32_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 6: /* 6 bits per byte = base64 */ + enc = get_base64_encoder(); + user_switch_codec(userid, enc); + write_dns(dns_fd, q, enc->name, strlen(enc->name), users[userid].downenc); + break; + case 26: /* "2nd" 6 bits per byte = base64u, with underscore */ + enc = get_base64u_encoder(); + 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 = get_base128_encoder(); + 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; + } + return; + } else if(in[0] == 'O' || in[0] == 'o') { + if (domain_len < 3) { /* len at least 3, example: "O1T" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + userid = b32_8to5(in[1]); + + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + switch (in[2]) { + case 'T': + case 't': + users[userid].downenc = 'T'; + write_dns(dns_fd, q, "Base32", 6, users[userid].downenc); + break; + case 'S': + case 's': + users[userid].downenc = 'S'; + write_dns(dns_fd, q, "Base64", 6, users[userid].downenc); + break; + case 'U': + case 'u': + users[userid].downenc = 'U'; + write_dns(dns_fd, q, "Base64u", 7, users[userid].downenc); + break; + case 'V': + case 'v': + users[userid].downenc = 'V'; + write_dns(dns_fd, q, "Base128", 7, users[userid].downenc); + break; + case 'R': + case 'r': + users[userid].downenc = 'R'; + write_dns(dns_fd, q, "Raw", 3, users[userid].downenc); + break; + case 'L': + case 'l': + users[userid].lazy = 1; + write_dns(dns_fd, q, "Lazy", 4, users[userid].downenc); + break; + case 'I': + case 'i': + users[userid].lazy = 0; + write_dns(dns_fd, q, "Immediate", 9, users[userid].downenc); + break; + default: + write_dns(dns_fd, q, "BADCODEC", 8, users[userid].downenc); + break; + } + return; + } else if(in[0] == 'Y' || in[0] == 'y') { + int i; + char *datap; + int datalen; + + if (domain_len < 6) { /* len at least 6, example: "YTxCMC" */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + i = b32_8to5(in[2]); /* check variant */ + + switch (i) { + case 1: + datap = DOWNCODECCHECK1; + datalen = DOWNCODECCHECK1_LEN; + break; + default: + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + switch (in[1]) { + case 'T': + case 't': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'T'); + return; + } + break; + case 'S': + case 's': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'S'); + return; + } + break; + case 'U': + case 'u': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'U'); + return; + } + break; + case 'V': + case 'v': + if (q->type == T_TXT || + q->type == T_SRV || q->type == T_MX || + q->type == T_CNAME || q->type == T_A) { + write_dns(dns_fd, q, datap, datalen, 'V'); + return; + } + break; + case 'R': + 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'); + return; + + } else if(in[0] == 'R' || in[0] == 'r') { + int req_frag_size; + + if (domain_len < 16) { /* we'd better have some chars for data... */ + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize probe packet */ + userid = (b32_8to5(in[1]) >> 1) & 15; + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + req_frag_size = ((b32_8to5(in[1]) & 1) << 10) | ((b32_8to5(in[2]) & 31) << 5) | (b32_8to5(in[3]) & 31); + if (req_frag_size < 2 || req_frag_size > 2047) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + char buf[2048]; + 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 < 2048; i++, v = (v + 107) & 0xff) + buf[i] = v; + write_dns(dns_fd, q, buf, req_frag_size, users[userid].downenc); + } + return; + } else if(in[0] == 'N' || in[0] == 'n') { + int max_frag_size; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + + if (read < 3) { + write_dns(dns_fd, q, "BADLEN", 6, 'T'); + return; + } + + /* Downstream fragsize packet */ + userid = unpacked[0]; + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + + max_frag_size = ((unpacked[1] & 0xff) << 8) | (unpacked[2] & 0xff); + if (max_frag_size < 2) { + write_dns(dns_fd, q, "BADFRAG", 7, users[userid].downenc); + } else { + users[userid].fragsize = max_frag_size; + write_dns(dns_fd, q, &unpacked[1], 2, users[userid].downenc); + } + return; + } else if(in[0] == 'P' || in[0] == 'p') { + int dn_seq; + int dn_frag; + int didsend = 0; + + /* We can't handle id=0, that's "no packet" to us. So drop + request completely. Note that DNS servers rewrite the id. + We'll drop 1 in 64k times. If DNS server retransmits with + different id, then all okay. + Else client won't retransmit, and we'll just keep the + previous ping in cache, no problem either. */ + if (q->id == 0) + return; + + read = unpack_data(unpacked, sizeof(unpacked), &(in[1]), domain_len - 1, b32); + if (read < 4) + return; + + /* Ping packet, store userid */ + userid = unpacked[0]; + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + +#ifdef DNSCACHE_LEN + /* Check if cached */ + if (answer_from_dnscache(dns_fd, userid, q)) + return; +#endif + + /* Check if duplicate (and not in full dnscache any more) */ + if (answer_from_qmem(dns_fd, q, users[userid].qmemping_cmc, + users[userid].qmemping_type, QMEMPING_LEN, + (void *) unpacked)) + return; + + /* Check if duplicate of waiting queries; impatient DNS relays + like to re-try early and often (with _different_ .id!) */ + if (users[userid].q.id != 0 && + q->type == users[userid].q.type && + !strcmp(q->name, users[userid].q.name) && + users[userid].lazy) { + /* We have this ping already, and it's waiting to be + answered. Always keep the last duplicate, since the + relay may have forgotten its first version already. + Our answer will go to both. + (If we already sent an answer, qmem/cache will + have triggered.) */ + if (debug >= 2) { + fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q.id2 = q->id; + users[userid].q.fromlen2 = q->fromlen; + memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); + return; + } + + if (users[userid].q_sendrealsoon.id != 0 && + q->type == users[userid].q_sendrealsoon.type && + !strcmp(q->name, users[userid].q_sendrealsoon.name)) { + /* Outer select loop will send answer immediately, + to both queries. */ + if (debug >= 2) { + fprintf(stderr, "PING pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q_sendrealsoon.id2 = q->id; + users[userid].q_sendrealsoon.fromlen2 = q->fromlen; + memcpy(&(users[userid].q_sendrealsoon.from2), + &(q->from), q->fromlen); + return; + } + + dn_seq = unpacked[1] >> 4; + dn_frag = unpacked[1] & 15; + + if (debug >= 1) { + fprintf(stderr, "PING pkt from user %d, ack for downstream %d/%d\n", + userid, dn_seq, dn_frag); + } + + process_downstream_ack(userid, dn_seq, dn_frag); + + if (debug >= 3) { + fprintf(stderr, "PINGret (if any) will ack upstream %d/%d\n", + users[userid].inpacket.seqno, users[userid].inpacket.fragment); + } + + /* If there is a query that must be returned real soon, do it. + May contain new downstream data if the ping had a new ack. + Otherwise, may also be re-sending old data. */ + if (users[userid].q_sendrealsoon.id != 0) { + send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + } + + /* We need to store a new query, so if there still is an + earlier query waiting, always send a reply to finish it. + May contain new downstream data if the ping had a new ack. + Otherwise, may also be re-sending old data. + (This is duplicate data if we had q_sendrealsoon above.) */ + if (users[userid].q.id != 0) { + didsend = 1; + if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) + /* new packet from queue, send immediately */ + didsend = 0; + } + + /* Save new query and time info */ + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].last_pkt = time(NULL); + + /* If anything waiting and we didn't already send above, send + it now. And always send immediately if we're not lazy + (then above won't have sent at all). */ + if ((!didsend && users[userid].outpacket.len > 0) || + !users[userid].lazy) + send_chunk_or_dataless(dns_fd, userid, &users[userid].q); + + } else if((in[0] >= '0' && in[0] <= '9') + || (in[0] >= 'a' && in[0] <= 'f') + || (in[0] >= 'A' && in[0] <= 'F')) { + int up_seq, up_frag, dn_seq, dn_frag, lastfrag; + int upstream_ok = 1; + int didsend = 0; + int code = -1; + + /* Need 5char header + >=1 char data */ + if (domain_len < 6) + return; + + /* We can't handle id=0, that's "no packet" to us. So drop + request completely. Note that DNS servers rewrite the id. + We'll drop 1 in 64k times. If DNS server retransmits with + different id, then all okay. + Else client doesn't get our ack, and will retransmit in + 1 second. */ + if (q->id == 0) + return; + + if ((in[0] >= '0' && in[0] <= '9')) + code = in[0] - '0'; + if ((in[0] >= 'a' && in[0] <= 'f')) + code = in[0] - 'a' + 10; + if ((in[0] >= 'A' && in[0] <= 'F')) + code = in[0] - 'A' + 10; + + userid = code; + /* Check user and sending ip number */ + if (check_user_and_ip(userid, q) != 0) { + write_dns(dns_fd, q, "BADIP", 5, 'T'); + return; /* illegal id */ + } + +#ifdef DNSCACHE_LEN + /* Check if cached */ + if (answer_from_dnscache(dns_fd, userid, q)) + return; +#endif + + /* Check if duplicate (and not in full dnscache any more) */ + if (answer_from_qmem_data(dns_fd, userid, q)) + return; + + /* Check if duplicate of waiting queries; impatient DNS relays + like to re-try early and often (with _different_ .id!) */ + if (users[userid].q.id != 0 && + q->type == users[userid].q.type && + !strcmp(q->name, users[userid].q.name) && + users[userid].lazy) { + /* We have this packet already, and it's waiting to be + answered. Always keep the last duplicate, since the + relay may have forgotten its first version already. + Our answer will go to both. + (If we already sent an answer, qmem/cache will + have triggered.) */ + if (debug >= 2) { + fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q.id2 = q->id; + users[userid].q.fromlen2 = q->fromlen; + memcpy(&(users[userid].q.from2), &(q->from), q->fromlen); + return; + } + + if (users[userid].q_sendrealsoon.id != 0 && + q->type == users[userid].q_sendrealsoon.type && + !strcmp(q->name, users[userid].q_sendrealsoon.name)) { + /* Outer select loop will send answer immediately, + to both queries. */ + if (debug >= 2) { + fprintf(stderr, "IN pkt from user %d = dupe from impatient DNS server, remembering\n", + userid); + } + users[userid].q_sendrealsoon.id2 = q->id; + users[userid].q_sendrealsoon.fromlen2 = q->fromlen; + memcpy(&(users[userid].q_sendrealsoon.from2), + &(q->from), q->fromlen); + return; + } + + + /* Decode data header */ + up_seq = (b32_8to5(in[1]) >> 2) & 7; + up_frag = ((b32_8to5(in[1]) & 3) << 2) | ((b32_8to5(in[2]) >> 3) & 3); + dn_seq = (b32_8to5(in[2]) & 7); + dn_frag = b32_8to5(in[3]) >> 1; + lastfrag = b32_8to5(in[3]) & 1; + + process_downstream_ack(userid, dn_seq, dn_frag); + + if (up_seq == users[userid].inpacket.seqno && + up_frag <= users[userid].inpacket.fragment) { + /* Got repeated old packet _with data_, probably + because client didn't receive our ack. So re-send + our ack(+data) immediately to keep things flowing + fast. + If it's a _really_ old frag, it's a nameserver + that tries again, and sending our current (non- + matching) fragno won't be a problem. */ + if (debug >= 1) { + fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate frag\n", + up_seq, up_frag); + } + upstream_ok = 0; + } + else if (up_seq != users[userid].inpacket.seqno && + recent_seqno(users[userid].inpacket.seqno, up_seq)) { + /* Duplicate of recent upstream data packet; probably + need to answer this to keep DNS server happy */ + if (debug >= 1) { + fprintf(stderr, "IN pkt seq# %d, frag %d, dropped duplicate recent seqno\n", + up_seq, up_frag); + } + upstream_ok = 0; + } + else if (up_seq != users[userid].inpacket.seqno) { + /* Really new packet has arrived, no recent duplicate */ + /* Forget any old packet, even if incomplete */ + users[userid].inpacket.seqno = up_seq; + users[userid].inpacket.fragment = up_frag; + users[userid].inpacket.len = 0; + users[userid].inpacket.offset = 0; + } else { + /* seq is same, frag is higher; don't care about + missing fragments, TCP checksum will fail */ + users[userid].inpacket.fragment = up_frag; + } + + if (debug >= 3) { + fprintf(stderr, "INpack with upstream %d/%d, we are going to ack upstream %d/%d\n", + up_seq, up_frag, + users[userid].inpacket.seqno, users[userid].inpacket.fragment); + } + + if (upstream_ok) { + /* decode with this user's encoding */ + read = unpack_data(unpacked, sizeof(unpacked), &(in[5]), domain_len - 5, + users[userid].encoder); + + /* copy to packet buffer, update length */ + read = MIN(read, sizeof(users[userid].inpacket.data) - users[userid].inpacket.offset); + memcpy(users[userid].inpacket.data + users[userid].inpacket.offset, unpacked, read); + users[userid].inpacket.len += read; + users[userid].inpacket.offset += read; + + if (debug >= 1) { + fprintf(stderr, "IN pkt seq# %d, frag %d (last=%d), fragsize %d, total %d, from user %d\n", + up_seq, up_frag, lastfrag, read, users[userid].inpacket.len, userid); + } + } + + if (upstream_ok && lastfrag) { /* packet is complete */ + handle_full_packet(tun_fd, dns_fd, userid); + } + + /* If there is a query that must be returned real soon, do it. + Includes an ack of the just received upstream fragment, + may contain new data. */ + if (users[userid].q_sendrealsoon.id != 0) { + didsend = 1; + if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon) == 1) + /* new packet from queue, send immediately */ + didsend = 0; + } + + /* If we already have an earlier query waiting, we need to + get rid of it to store the new query. + - If we have new data waiting and not yet sent above, + send immediately. + - If this wasn't the last upstream fragment, then we expect + more, so ack immediately if we didn't already. + - If we are in non-lazy mode, there should be no query + waiting, but if there is, send immediately. + - In all other cases (mostly the last-fragment cases), + we can afford to wait just a tiny little while for the + TCP ack to arrive from our tun. Note that this works best + when there is only one client. + */ + if (users[userid].q.id != 0) { + if ((users[userid].outpacket.len > 0 && !didsend) || + (upstream_ok && !lastfrag && !didsend) || + (!upstream_ok && !didsend) || + !users[userid].lazy) { + didsend = 1; + if (send_chunk_or_dataless(dns_fd, userid, &users[userid].q) == 1) + /* new packet from queue, send immediately */ + didsend = 0; + } else { + memcpy(&(users[userid].q_sendrealsoon), + &(users[userid].q), + sizeof(struct query)); + users[userid].q_sendrealsoon_new = 1; + users[userid].q.id = 0; /* used */ + didsend = 1; + } + } + + /* Save new query and time info */ + memcpy(&(users[userid].q), q, sizeof(struct query)); + users[userid].last_pkt = time(NULL); + + /* If we still need to ack this upstream frag, do it to keep + upstream flowing. + - If we have new data waiting and not yet sent above, + send immediately. + - If this wasn't the last upstream fragment, then we expect + more, so ack immediately if we didn't already or are + in non-lazy mode. + - If this was the last fragment, and we didn't ack already + or are in non-lazy mode, send the ack after just a tiny + little while so that the TCP ack may have arrived from + our tun device. + - In all other cases, don't send anything now. + */ + if (users[userid].outpacket.len > 0 && !didsend) + send_chunk_or_dataless(dns_fd, userid, &users[userid].q); + else if (!didsend || !users[userid].lazy) { + if (upstream_ok && lastfrag) { + memcpy(&(users[userid].q_sendrealsoon), + &(users[userid].q), + sizeof(struct query)); + users[userid].q_sendrealsoon_new = 1; + users[userid].q.id = 0; /* used */ + } else { + send_chunk_or_dataless(dns_fd, userid, &users[userid].q); + } + } + } +} + +static void +handle_ns_request(int dns_fd, struct query *q) +/* Mostly identical to handle_a_request() below */ +{ + char buf[64*1024]; + int len; + + if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + memcpy(&q->destination.s_addr, &ns_ip, sizeof(in_addr_t)); + } + + len = dns_encode_ns_response(buf, sizeof(buf), q, topdomain); + if (len < 1) { + warnx("dns_encode_ns_response doesn't fit"); + return; + } + + if (debug >= 2) { + struct sockaddr_in *tempin; + tempin = (struct sockaddr_in *) &(q->from); + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes NS reply\n", + inet_ntoa(tempin->sin_addr), q->type, q->name, len); + } + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("ns reply send error"); + } +} + +static 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"); + memcpy(&q->destination.s_addr, &ip, sizeof(in_addr_t)); + + } else if (ns_ip != INADDR_ANY) { + /* If ns_ip set, overwrite destination addr with it. + * Destination addr will be sent as additional record (A, IN) */ + memcpy(&q->destination.s_addr, &ns_ip, sizeof(in_addr_t)); + } + + len = dns_encode_a_response(buf, sizeof(buf), q); + if (len < 1) { + warnx("dns_encode_a_response doesn't fit"); + return; + } + + if (debug >= 2) { + struct sockaddr_in *tempin; + tempin = (struct sockaddr_in *) &(q->from); + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes A reply\n", + inet_ntoa(tempin->sin_addr), q->type, q->name, len); + } + if (sendto(dns_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("a reply send error"); + } +} + +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(bind_port); + + if (debug >= 2) { + fprintf(stderr, "TX: NS reply \n"); + } + + if (sendto(bind_fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen) <= 0) { + warn("forward query error"); + } +} + +static int +tunnel_bind(int bind_fd, int dns_fd) +{ + char packet[64*1024]; + struct sockaddr_in from; + socklen_t fromlen; + struct fw_query *query; + unsigned short id; + int r; + + fromlen = sizeof(struct sockaddr); + r = recvfrom(bind_fd, packet, sizeof(packet), 0, + (struct sockaddr*)&from, &fromlen); + + if (r <= 0) + return 0; + + id = dns_get_id(packet, r); + + if (debug >= 2) { + fprintf(stderr, "RX: Got response on query %u from DNS\n", (id & 0xFFFF)); + } + + /* Get sockaddr from id */ + fw_query_get(id, &query); + if (!query && debug >= 2) { + fprintf(stderr, "Lost sender of id %u, dropping reply\n", (id & 0xFFFF)); + return 0; + } + + if (debug >= 2) { + struct sockaddr_in *in; + in = (struct sockaddr_in *) &(query->addr); + fprintf(stderr, "TX: client %s id %u, %d bytes\n", + inet_ntoa(in->sin_addr), (id & 0xffff), r); + } + + if (sendto(dns_fd, packet, r, 0, (const struct sockaddr *) &(query->addr), + query->addrlen) <= 0) { + warn("forward reply error"); + } + + return 0; +} + +static int +tunnel_dns(int tun_fd, int dns_fd, int bind_fd) +{ + struct query q; + int read; + int domain_len; + int inside_topdomain; + + if ((read = read_dns(dns_fd, tun_fd, &q)) <= 0) + return 0; + + if (debug >= 2) { + struct sockaddr_in *tempin; + tempin = (struct sockaddr_in *) &(q.from); + fprintf(stderr, "RX: client %s, type %d, name %s\n", + inet_ntoa(tempin->sin_addr), q.type, q.name); + } + + domain_len = strlen(q.name) - strlen(topdomain); + if (domain_len >= 0 && !strcasecmp(q.name + domain_len, 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_CNAME: + case T_A: + case T_MX: + case T_SRV: + case T_TXT: + /* encoding is "transparent" here */ + handle_null_request(tun_fd, dns_fd, &q, domain_len); + break; + case T_NS: + handle_ns_request(dns_fd, &q); + break; + default: + break; + } + } else { + /* Forward query to other port ? */ + if (bind_fd) { + forward_query(bind_fd, &q); + } + } + return 0; +} + +static int +tunnel(int tun_fd, int dns_fd, int bind_fd) +{ + struct timeval tv; + fd_set fds; + int i; + int userid; + + while (running) { + int maxfd; + tv.tv_sec = 10; /* doesn't really matter */ + tv.tv_usec = 0; + + /* Adjust timeout if there is anything to send realsoon. + Clients won't be sending new data until we send our ack, + so don't keep them waiting long. This only triggers at + final upstream fragments, which is about once per eight + requests during heavy upstream traffic. + 20msec: ~8 packs every 1/50sec = ~400 DNSreq/sec, + or ~1200bytes every 1/50sec = ~0.5 Mbit/sec upstream */ + for (userid = 0; userid < USERS; userid++) { + if (users[userid].active && !users[userid].disabled && + users[userid].last_pkt + 60 > time(NULL)) { + users[userid].q_sendrealsoon_new = 0; + if (users[userid].q_sendrealsoon.id != 0) { + tv.tv_sec = 0; + tv.tv_usec = 20000; + } + } + } + + FD_ZERO(&fds); + + FD_SET(dns_fd, &fds); + maxfd = dns_fd; + + if (bind_fd) { + /* wait for replies from real DNS */ + FD_SET(bind_fd, &fds); + maxfd = MAX(bind_fd, maxfd); + } + + /* Don't read from tun if no users can accept data anyway; + tun queue/TCP buffers are larger than our outpacket-queues */ + if(!all_users_waiting_to_send()) { + FD_SET(tun_fd, &fds); + maxfd = MAX(tun_fd, maxfd); + } + + i = select(maxfd + 1, &fds, NULL, NULL, &tv); + + if(i < 0) { + if (running) + warn("select"); + return 1; + } + + if (i==0) { + /* timeout; whatever; doesn't matter anymore */ + } else { + if (FD_ISSET(tun_fd, &fds)) { + tunnel_tun(tun_fd, dns_fd); + } + if (FD_ISSET(dns_fd, &fds)) { + tunnel_dns(tun_fd, dns_fd, bind_fd); + } + if (FD_ISSET(bind_fd, &fds)) { + tunnel_bind(bind_fd, dns_fd); + } + } + + /* Send realsoon's if tun or dns didn't already */ + for (userid = 0; userid < USERS; userid++) + if (users[userid].active && !users[userid].disabled && + users[userid].last_pkt + 60 > time(NULL) && + users[userid].q_sendrealsoon.id != 0 && + users[userid].conn == CONN_DNS_NULL && + !users[userid].q_sendrealsoon_new) + send_chunk_or_dataless(dns_fd, userid, &users[userid].q_sendrealsoon); + } + + return 0; +} + +static void +handle_full_packet(int tun_fd, int dns_fd, int userid) +{ + unsigned long outlen; + char out[64*1024]; + int touser; + int ret; + + outlen = sizeof(out); + ret = uncompress((uint8_t*)out, &outlen, + (uint8_t*)users[userid].inpacket.data, users[userid].inpacket.len); + + if (ret == Z_OK) { + struct ip *hdr; + + hdr = (struct ip*) (out + 4); + touser = find_user_by_ip(hdr->ip_dst.s_addr); + + if (touser == -1) { + /* send the uncompressed packet to tun device */ + write_tun(tun_fd, out, outlen); + } else { + /* send the compressed(!) packet to other client */ + /*XXX START adjust indent 1 tab forward*/ + if (users[touser].conn == CONN_DNS_NULL) { + if (users[touser].outpacket.len == 0) { + start_new_outpacket(touser, + users[userid].inpacket.data, + users[userid].inpacket.len); + + /* Start sending immediately if query is waiting */ + if (users[touser].q_sendrealsoon.id != 0) + send_chunk_or_dataless(dns_fd, touser, &users[touser].q_sendrealsoon); + else if (users[touser].q.id != 0) + send_chunk_or_dataless(dns_fd, touser, &users[touser].q); +#ifdef OUTPACKETQ_LEN + } else { + save_to_outpacketq(touser, + users[userid].inpacket.data, + users[userid].inpacket.len); +#endif + } + } else{ /* CONN_RAW_UDP */ + send_raw(dns_fd, users[userid].inpacket.data, + users[userid].inpacket.len, touser, + RAW_HDR_CMD_DATA, &users[touser].q); + } + /*XXX END adjust indent 1 tab forward*/ + } + } else { + if (debug >= 1) + fprintf(stderr, "Discarded data, uncompress() result: %d\n", ret); + } + + /* This packet is done */ + users[userid].inpacket.len = 0; + users[userid].inpacket.offset = 0; +} + +static void +handle_raw_login(char *packet, int len, struct query *q, int fd, int userid) +{ + char myhash[16]; + + if (len < 16) return; + + /* can't use check_user_and_ip() since IP address will be different, + so duplicate here except IP address */ + if (userid < 0 || userid >= created_users) return; + if (!users[userid].active || users[userid].disabled) return; + if (users[userid].last_pkt + 60 < time(NULL)) return; + + if (debug >= 1) { + fprintf(stderr, "IN login raw, len %d, from user %d\n", + len, userid); + } + + /* User sends hash of seed + 1 */ + login_calculate(myhash, 16, password, users[userid].seed + 1); + if (memcmp(packet, myhash, 16) == 0) { + struct sockaddr_in *tempin; + + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + /* Store remote IP number */ + tempin = (struct sockaddr_in *) &(q->from); + memcpy(&(users[userid].host), &(tempin->sin_addr), sizeof(struct in_addr)); + + /* Correct hash, reply with hash of seed - 1 */ + user_set_conn_type(userid, CONN_RAW_UDP); + login_calculate(myhash, 16, password, users[userid].seed - 1); + send_raw(fd, myhash, 16, userid, RAW_HDR_CMD_LOGIN, q); + } +} + +static void +handle_raw_data(char *packet, int len, struct query *q, int dns_fd, int tun_fd, int userid) +{ + if (check_user_and_ip(userid, q) != 0) { + return; + } + + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + /* copy to packet buffer, update length */ + users[userid].inpacket.offset = 0; + memcpy(users[userid].inpacket.data, packet, len); + users[userid].inpacket.len = len; + + if (debug >= 1) { + fprintf(stderr, "IN pkt raw, total %d, from user %d\n", + users[userid].inpacket.len, userid); + } + + handle_full_packet(tun_fd, dns_fd, userid); +} + +static void +handle_raw_ping(struct query *q, int dns_fd, int userid) +{ + if (check_user_and_ip(userid, q) != 0) { + return; + } + + /* Update query and time info for user */ + users[userid].last_pkt = time(NULL); + memcpy(&(users[userid].q), q, sizeof(struct query)); + + if (debug >= 1) { + fprintf(stderr, "IN ping raw, from user %d\n", userid); + } + + /* Send ping reply */ + send_raw(dns_fd, NULL, 0, userid, RAW_HDR_CMD_PING, q); +} + +static int +raw_decode(char *packet, int len, struct query *q, int dns_fd, int tun_fd) +{ + int raw_user; + + /* 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_user = RAW_HDR_GET_USR(packet); + switch (RAW_HDR_GET_CMD(packet)) { + case RAW_HDR_CMD_LOGIN: + /* Login challenge */ + handle_raw_login(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, raw_user); + break; + case RAW_HDR_CMD_DATA: + /* Data packet */ + handle_raw_data(&packet[RAW_HDR_LEN], len - RAW_HDR_LEN, q, dns_fd, tun_fd, raw_user); + break; + case RAW_HDR_CMD_PING: + /* Keepalive packet */ + handle_raw_ping(q, dns_fd, raw_user); + break; + default: + warnx("Unhandled raw command %02X from user %d", RAW_HDR_GET_CMD(packet), raw_user); + break; + } + return 1; +} + +static int +read_dns(int fd, int tun_fd, struct query *q) /* FIXME: tun_fd is because of raw_decode() below */ +{ + struct sockaddr_in from; + socklen_t addrlen; + char packet[64*1024]; + int r; +#ifndef WINDOWS32 + char address[96]; + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + + addrlen = sizeof(struct sockaddr); + 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 = address; + msg.msg_controllen = sizeof(address); + msg.msg_flags = 0; + + r = recvmsg(fd, &msg, 0); +#else + addrlen = sizeof(struct sockaddr); + r = recvfrom(fd, packet, sizeof(packet), 0, (struct sockaddr*)&from, &addrlen); +#endif /* !WINDOWS32 */ + + if (r > 0) { + memcpy((struct sockaddr*)&q->from, (struct sockaddr*)&from, addrlen); + q->fromlen = addrlen; + + /* TODO do not handle raw packets here! */ + if (raw_decode(packet, r, q, fd, tun_fd)) { + return 0; + } + if (dns_decode(NULL, 0, q, QR_QUERY, packet, r) < 0) { + return 0; + } + +#ifndef WINDOWS32 + for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == DSTADDR_SOCKOPT) { + + q->destination = *dstaddr(cmsg); + break; + } + } +#endif + + return strlen(q->name); + } else if (r < 0) { + /* Error */ + warn("read dns"); + } + + return 0; +} + +static size_t +write_dns_nameenc(char *buf, size_t buflen, char *data, int datalen, char downenc) +/* Returns #bytes of data that were encoded */ +{ + static int td1 = 0; + static int td2 = 0; + size_t space; + char *b; + + /* Make a rotating topdomain to prevent filtering */ + td1+=3; + td2+=7; + if (td1>=26) td1-=26; + if (td2>=25) td2-=25; + + /* encode data,datalen to CNAME/MX answer + (adapted from build_hostname() in encoding.c) + */ + + space = MIN(0xFF, buflen) - 4 - 2; + /* -1 encoding type, -3 ".xy", -2 for safety */ + + memset(buf, 0, sizeof(buf)); + + if (downenc == 'S') { + buf[0] = 'i'; + if (!b64->places_dots()) + space -= (space / 57); /* space for dots */ + b64->encode(buf+1, &space, data, datalen); + if (!b64->places_dots()) + inline_dotify(buf, buflen); + } else if (downenc == 'U') { + buf[0] = 'j'; + if (!b64u->places_dots()) + space -= (space / 57); /* space for dots */ + b64u->encode(buf+1, &space, data, datalen); + if (!b64u->places_dots()) + inline_dotify(buf, buflen); + } else if (downenc == 'V') { + buf[0] = 'k'; + if (!b128->places_dots()) + space -= (space / 57); /* space for dots */ + b128->encode(buf+1, &space, data, datalen); + if (!b128->places_dots()) + inline_dotify(buf, buflen); + } else { + buf[0] = 'h'; + if (!b32->places_dots()) + space -= (space / 57); /* space for dots */ + b32->encode(buf+1, &space, data, datalen); + if (!b32->places_dots()) + inline_dotify(buf, buflen); + } + + /* Add dot (if it wasn't there already) and topdomain */ + b = buf; + b += strlen(buf) - 1; + if (*b != '.') + *++b = '.'; + b++; + + *b = 'a' + td1; + b++; + *b = 'a' + td2; + b++; + *b = '\0'; + + return space; +} + +static void +write_dns(int fd, struct query *q, char *data, int datalen, char downenc) +{ + char buf[64*1024]; + int len = 0; + + if (q->type == T_CNAME || q->type == T_A) { + char cnamebuf[1024]; /* max 255 */ + + write_dns_nameenc(cnamebuf, sizeof(cnamebuf), + 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(b, sizeof(mxbuf) - (b - mxbuf), + 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 */ + char txtbuf[64*1024]; + size_t space = sizeof(txtbuf) - 1;; + + memset(txtbuf, 0, sizeof(txtbuf)); + + if (downenc == 'S') { + txtbuf[0] = 's'; /* plain base64(Sixty-four) */ + len = b64->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'U') { + txtbuf[0] = 'u'; /* Base64 with Underscore */ + len = b64u->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'V') { + txtbuf[0] = 'v'; /* Base128 */ + len = b128->encode(txtbuf+1, &space, data, datalen); + } + else if (downenc == 'R') { + txtbuf[0] = 'r'; /* Raw binary data */ + len = MIN(datalen, sizeof(txtbuf) - 1); + memcpy(txtbuf + 1, data, len); + } else { + txtbuf[0] = 't'; /* plain base32(Thirty-two) */ + len = b32->encode(txtbuf+1, &space, data, datalen); + } + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, txtbuf, len+1); + } else { + /* Normal NULL-record encode */ + len = dns_encode(buf, sizeof(buf), q, QR_ANSWER, data, datalen); + } + + if (len < 1) { + warnx("dns_encode doesn't fit"); + return; + } + + if (debug >= 2) { + struct sockaddr_in *tempin; + tempin = (struct sockaddr_in *) &(q->from); + fprintf(stderr, "TX: client %s, type %d, name %s, %d bytes data\n", + inet_ntoa(tempin->sin_addr), q->type, q->name, datalen); + } + + sendto(fd, buf, len, 0, (struct sockaddr*)&q->from, q->fromlen); +} + +static void +usage() { + extern char *__progname; + + fprintf(stderr, "Usage: %s [-v] [-h] [-c] [-s] [-f] [-D] [-u user] " + "[-t chrootdir] [-d device] [-m mtu] [-z context] " + "[-l ip address to listen on] [-p port] [-n external ip] " + "[-b dnsport] [-P password] [-F pidfile] " + "tunnel_ip[/netmask] topdomain\n", __progname); + exit(2); +} + +static void +help() { + extern char *__progname; + + fprintf(stderr, "iodine IP over DNS tunneling server\n"); + fprintf(stderr, "Usage: %s [-v] [-h] [-c] [-s] [-f] [-D] [-u user] " + "[-t chrootdir] [-d device] [-m mtu] [-z context] " + "[-l ip address to listen on] [-p port] [-n external ip] [-b dnsport] [-P password] " + "[-F pidfile] tunnel_ip[/netmask] topdomain\n", __progname); + fprintf(stderr, " -v to print version info and exit\n"); + fprintf(stderr, " -h to print this help and exit\n"); + fprintf(stderr, " -c to disable check of client IP/port on each request\n"); + fprintf(stderr, " -s to skip creating and configuring the tun device, " + "which then has to be created manually\n"); + fprintf(stderr, " -f to keep running in foreground\n"); + fprintf(stderr, " -D to increase debug level\n"); + fprintf(stderr, " (using -DD in UTF-8 terminal: \"LC_ALL=C luit iodined -DD ...\")\n"); + fprintf(stderr, " -u name to drop privileges and run as user 'name'\n"); + fprintf(stderr, " -t dir to chroot to directory dir\n"); + fprintf(stderr, " -d device to set tunnel device name\n"); + fprintf(stderr, " -m mtu to set tunnel device mtu\n"); + fprintf(stderr, " -z context to apply SELinux context after initialization\n"); + fprintf(stderr, " -l ip address to listen on for incoming dns traffic " + "(default 0.0.0.0)\n"); + fprintf(stderr, " -p port to listen on for incoming dns traffic (default 53)\n"); + fprintf(stderr, " -n ip to respond with to NS queries\n"); + fprintf(stderr, " -b port to forward normal DNS queries to (on localhost)\n"); + fprintf(stderr, " -P password used for authentication (max 32 chars will be used)\n"); + fprintf(stderr, " -F pidfile to write pid to a file\n"); + fprintf(stderr, "tunnel_ip is the IP number of the local tunnel interface.\n"); + fprintf(stderr, " /netmask sets the size of the tunnel network.\n"); + fprintf(stderr, "topdomain is the FQDN that is delegated to this server.\n"); + exit(0); +} + +static void +version() { + char *svnver; + svnver = "$Rev$ from $Date$"; + fprintf(stderr, "iodine IP over DNS tunneling server\n"); + fprintf(stderr, "SVN version: %s\n", svnver); + exit(0); +} + +int +main(int argc, char **argv) +{ + extern char *__progname; + in_addr_t listen_ip; +#ifndef WINDOWS32 + struct passwd *pw; +#endif + int foreground; + char *username; + char *newroot; + char *context; + char *device; + char *pidfile; + int dnsd_fd; + int tun_fd; + + /* settings for forwarding normal DNS to + * local real DNS server */ + int bind_fd; + int bind_enable; + + int choice; + int port; + int mtu; + int skipipconfig; + char *netsize; + int retval; + +#ifndef WINDOWS32 + pw = NULL; +#endif + username = NULL; + newroot = NULL; + context = NULL; + device = NULL; + foreground = 0; + bind_enable = 0; + bind_fd = 0; + mtu = 1130; /* Very many relays give fragsize 1150 or slightly + higher for NULL; tun/zlib adds ~17 bytes. */ + listen_ip = INADDR_ANY; + port = 53; + ns_ip = INADDR_ANY; + check_ip = 1; + skipipconfig = 0; + debug = 0; + netmask = 27; + pidfile = NULL; + + b32 = get_base32_encoder(); + b64 = get_base64_encoder(); + b64u = get_base64u_encoder(); + b128 = get_base128_encoder(); + + retval = 0; + +#ifdef WINDOWS32 + WSAStartup(req_version, &wsa_data); +#endif + +#if !defined(BSD) && !defined(__GLIBC__) + __progname = strrchr(argv[0], '/'); + if (__progname == NULL) + __progname = argv[0]; + else + __progname++; +#endif + + memset(password, 0, sizeof(password)); + srand(time(NULL)); + fw_query_init(); + + while ((choice = getopt(argc, argv, "vcsfhDu:t:d:m:l:p:n:b:P:z:F:")) != -1) { + switch(choice) { + case 'v': + version(); + break; + case 'c': + check_ip = 0; + break; + case 's': + skipipconfig = 1; + break; + case 'f': + foreground = 1; + break; + case 'h': + help(); + break; + case 'D': + debug++; + break; + case 'u': + username = optarg; + break; + case 't': + newroot = optarg; + break; + case 'd': + device = optarg; + break; + case 'm': + mtu = atoi(optarg); + break; + case 'l': + listen_ip = inet_addr(optarg); + break; + case 'p': + port = atoi(optarg); + break; + case 'n': + ns_ip = inet_addr(optarg); + break; + case 'b': + bind_enable = 1; + bind_port = atoi(optarg); + break; + case 'F': + pidfile = optarg; + break; + case 'P': + 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; + case 'z': + context = optarg; + break; + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + check_superuser(usage); + + if (argc != 2) + usage(); + + netsize = strchr(argv[0], '/'); + if (netsize) { + *netsize = 0; + netsize++; + netmask = atoi(netsize); + } + + my_ip = inet_addr(argv[0]); + + if (my_ip == INADDR_NONE) { + warnx("Bad IP address to use inside tunnel."); + usage(); + } + + topdomain = strdup(argv[1]); + if (strlen(topdomain) <= 128) { + if(check_topdomain(topdomain)) { + warnx("Topdomain contains invalid characters."); + usage(); + } + } else { + warnx("Use a topdomain max 128 chars long."); + usage(); + } + + if (username != NULL) { +#ifndef WINDOWS32 + if ((pw = getpwnam(username)) == NULL) { + warnx("User %s does not exist!", username); + usage(); + } +#endif + } + + if (mtu <= 0) { + warnx("Bad MTU given."); + usage(); + } + + if(port < 1 || port > 65535) { + warnx("Bad port number given."); + usage(); + } + + if(bind_enable) { + if (bind_port < 1 || bind_port > 65535) { + warnx("Bad DNS server port number given."); + usage(); + /* NOTREACHED */ + } + /* Avoid forwarding loops */ + if (bind_port == port && (listen_ip == INADDR_ANY || listen_ip == htonl(0x7f000001L))) { + warnx("Forward port is same as listen port (%d), will create a loop!", bind_port); + fprintf(stderr, "Use -l to set listen ip to avoid this.\n"); + usage(); + /* NOTREACHED */ + } + fprintf(stderr, "Requests for domains outside of %s will be forwarded to port %d\n", + topdomain, bind_port); + } + + if (port != 53) { + fprintf(stderr, "ALERT! Other dns servers expect you to run on port 53.\n"); + fprintf(stderr, "You must manually forward port 53 to port %d for things to work.\n", port); + } + + if (debug) { + fprintf(stderr, "Debug level %d enabled, will stay in foreground.\n", debug); + fprintf(stderr, "Add more -D switches to set higher debug level.\n"); + foreground = 1; + } + + if (listen_ip == INADDR_NONE) { + warnx("Bad IP address to listen on."); + usage(); + } + + if (ns_ip == INADDR_NONE) { + warnx("Bad IP address to return as nameserver."); + usage(); + } + if (netmask > 30 || netmask < 8) { + warnx("Bad netmask (%d bits). Use 8-30 bits.", netmask); + usage(); + } + + if (strlen(password) == 0) { + if (NULL != getenv(PASSWORD_ENV_VAR)) + snprintf(password, sizeof(password), "%s", getenv(PASSWORD_ENV_VAR)); + else + read_password(password, sizeof(password)); + } + + created_users = init_users(my_ip, netmask); + + if ((tun_fd = open_tun(device)) == -1) { + retval = 1; + goto cleanup0; + } + if (!skipipconfig) { + if (tun_setip(argv[0], users_get_first_ip(), netmask) != 0 || tun_setmtu(mtu) != 0) { + retval = 1; + goto cleanup1; + } + } + if ((dnsd_fd = open_dns(port, listen_ip)) == -1) { + retval = 1; + goto cleanup2; + } + if (bind_enable) { + if ((bind_fd = open_dns(0, INADDR_ANY)) == -1) { + retval = 1; + goto cleanup3; + } + } + + my_mtu = mtu; + + if (created_users < USERS) { + fprintf(stderr, "Limiting to %d simultaneous users because of netmask /%d\n", + created_users, netmask); + } + fprintf(stderr, "Listening to dns for domain %s\n", topdomain); + + if (foreground == 0) + do_detach(); + + if (pidfile != NULL) + do_pidfile(pidfile); + +#ifdef FREEBSD + tzsetwall(); +#endif +#ifndef WINDOWS32 + openlog( __progname, LOG_NDELAY, LOG_DAEMON ); +#endif + + if (newroot != NULL) + do_chroot(newroot); + + signal(SIGINT, sigint); + if (username != NULL) { +#ifndef WINDOWS32 + gid_t gids[1]; + gids[0] = pw->pw_gid; + if (setgroups(1, gids) < 0 || setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) { + warnx("Could not switch to user %s!\n", username); + usage(); + } +#endif + } + + if (context != NULL) + do_setcon(context); + + syslog(LOG_INFO, "started, listening on port %d", port); + + tunnel(tun_fd, dnsd_fd, bind_fd); + + syslog(LOG_INFO, "stopping"); +cleanup3: + close_dns(bind_fd); +cleanup2: + close_dns(dnsd_fd); +cleanup1: + close_tun(tun_fd); +cleanup0: + + return retval; +} diff --git a/iodine/src/login.c b/iodine/src/login.c new file mode 100644 index 0000000..c8827c3 --- /dev/null +++ b/iodine/src/login.c @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include + +#ifdef WINDOWS32 +#include "windows.h" +#else +#include +#endif + +#include "md5.h" + +/* + * Needs a 16byte array for output, and 32 bytes password + */ +void +login_calculate(char *buf, int buflen, const char *pass, int seed) +{ + unsigned char temp[32]; + md5_state_t ctx; + int *ix; + int i; + int k; + + if (buflen < 16) + return; + + memcpy(temp, pass, 32); + ix = (int*) temp; + + for (i = 0; i < 8; i++) { + k = ntohl(*ix); + k ^= seed; + *ix++ = htonl(k); + } + + md5_init(&ctx); + md5_append(&ctx, temp, 32); + md5_finish(&ctx, (unsigned char *) buf); + +} + diff --git a/iodine/src/login.h b/iodine/src/login.h new file mode 100644 index 0000000..840c920 --- /dev/null +++ b/iodine/src/login.h @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __LOGIN_H__ +#define __LOGIN_H__ + +void login_calculate(char *, int, const char *, int); + +#endif + diff --git a/iodine/src/md5.c b/iodine/src/md5.c new file mode 100644 index 0000000..89d3d5e --- /dev/null +++ b/iodine/src/md5.c @@ -0,0 +1,384 @@ +/* + Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.c is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order + either statically or dynamically; added missing #include + in library. + 2002-03-11 lpd Corrected argument list for main(), and added int return + type, in test program and T value program. + 2002-02-21 lpd Added missing #include in test program. + 2000-07-03 lpd Patched to eliminate warnings about "constant is + unsigned in ANSI C, signed in traditional"; made test program + self-checking. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). + 1999-05-03 lpd Original version. + */ + +#include "md5.h" +#include + +#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ +#ifdef ARCH_IS_BIG_ENDIAN +# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) +#else +# define BYTE_ORDER 0 +#endif + +#define T_MASK ((md5_word_t)~0) +#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) +#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) +#define T3 0x242070db +#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) +#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) +#define T6 0x4787c62a +#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) +#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) +#define T9 0x698098d8 +#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) +#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) +#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) +#define T13 0x6b901122 +#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) +#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) +#define T16 0x49b40821 +#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) +#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) +#define T19 0x265e5a51 +#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) +#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) +#define T22 0x02441453 +#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) +#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) +#define T25 0x21e1cde6 +#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) +#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) +#define T28 0x455a14ed +#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) +#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) +#define T31 0x676f02d9 +#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) +#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) +#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) +#define T35 0x6d9d6122 +#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) +#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) +#define T38 0x4bdecfa9 +#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) +#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) +#define T41 0x289b7ec6 +#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) +#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) +#define T44 0x04881d05 +#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) +#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) +#define T47 0x1fa27cf8 +#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) +#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) +#define T50 0x432aff97 +#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) +#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) +#define T53 0x655b59c3 +#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) +#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) +#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) +#define T57 0x6fa87e4f +#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) +#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) +#define T60 0x4e0811a1 +#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) +#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) +#define T63 0x2ad7d2bb +#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) + + +static void +md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) +{ + md5_word_t + a = pms->abcd[0], b = pms->abcd[1], + c = pms->abcd[2], d = pms->abcd[3]; + md5_word_t t; +#if BYTE_ORDER > 0 + /* Define storage only for big-endian CPUs. */ + md5_word_t X[16]; +#else + /* Define storage for little-endian or both types of CPUs. */ + md5_word_t xbuf[16]; + const md5_word_t *X; +#endif + + { +#if BYTE_ORDER == 0 + /* + * Determine dynamically whether this is a big-endian or + * little-endian machine, since we can use a more efficient + * algorithm on the latter. + */ + static const int w = 1; + + if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ +#endif +#if BYTE_ORDER <= 0 /* little-endian */ + { + /* + * On little-endian machines, we can process properly aligned + * data without copying it. + */ + if (!((data - (const md5_byte_t *)0) & 3)) { + /* data are properly aligned */ + X = (const md5_word_t *)data; + } else { + /* not aligned */ + memcpy(xbuf, data, 64); + X = xbuf; + } + } +#endif +#if BYTE_ORDER == 0 + else /* dynamic big-endian */ +#endif +#if BYTE_ORDER >= 0 /* big-endian */ + { + /* + * On big-endian machines, we must arrange the bytes in the + * right order. + */ + const md5_byte_t *xp = data; + int i; + +# if BYTE_ORDER == 0 + X = xbuf; /* (dynamic only) */ +# else +# define xbuf X /* (static only) */ +# endif + for (i = 0; i < 16; ++i, xp += 4) + xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); + } +#endif + } + +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) + + /* Round 1. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ +#define F(x, y, z) (((x) & (y)) | (~(x) & (z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + F(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 7, T1); + SET(d, a, b, c, 1, 12, T2); + SET(c, d, a, b, 2, 17, T3); + SET(b, c, d, a, 3, 22, T4); + SET(a, b, c, d, 4, 7, T5); + SET(d, a, b, c, 5, 12, T6); + SET(c, d, a, b, 6, 17, T7); + SET(b, c, d, a, 7, 22, T8); + SET(a, b, c, d, 8, 7, T9); + SET(d, a, b, c, 9, 12, T10); + SET(c, d, a, b, 10, 17, T11); + SET(b, c, d, a, 11, 22, T12); + SET(a, b, c, d, 12, 7, T13); + SET(d, a, b, c, 13, 12, T14); + SET(c, d, a, b, 14, 17, T15); + SET(b, c, d, a, 15, 22, T16); +#undef SET + + /* Round 2. */ + /* Let [abcd k s i] denote the operation + a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ +#define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + G(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 1, 5, T17); + SET(d, a, b, c, 6, 9, T18); + SET(c, d, a, b, 11, 14, T19); + SET(b, c, d, a, 0, 20, T20); + SET(a, b, c, d, 5, 5, T21); + SET(d, a, b, c, 10, 9, T22); + SET(c, d, a, b, 15, 14, T23); + SET(b, c, d, a, 4, 20, T24); + SET(a, b, c, d, 9, 5, T25); + SET(d, a, b, c, 14, 9, T26); + SET(c, d, a, b, 3, 14, T27); + SET(b, c, d, a, 8, 20, T28); + SET(a, b, c, d, 13, 5, T29); + SET(d, a, b, c, 2, 9, T30); + SET(c, d, a, b, 7, 14, T31); + SET(b, c, d, a, 12, 20, T32); +#undef SET + + /* Round 3. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + H(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 5, 4, T33); + SET(d, a, b, c, 8, 11, T34); + SET(c, d, a, b, 11, 16, T35); + SET(b, c, d, a, 14, 23, T36); + SET(a, b, c, d, 1, 4, T37); + SET(d, a, b, c, 4, 11, T38); + SET(c, d, a, b, 7, 16, T39); + SET(b, c, d, a, 10, 23, T40); + SET(a, b, c, d, 13, 4, T41); + SET(d, a, b, c, 0, 11, T42); + SET(c, d, a, b, 3, 16, T43); + SET(b, c, d, a, 6, 23, T44); + SET(a, b, c, d, 9, 4, T45); + SET(d, a, b, c, 12, 11, T46); + SET(c, d, a, b, 15, 16, T47); + SET(b, c, d, a, 2, 23, T48); +#undef SET + + /* Round 4. */ + /* Let [abcd k s t] denote the operation + a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ +#define I(x, y, z) ((y) ^ ((x) | ~(z))) +#define SET(a, b, c, d, k, s, Ti)\ + t = a + I(b,c,d) + X[k] + Ti;\ + a = ROTATE_LEFT(t, s) + b + /* Do the following 16 operations. */ + SET(a, b, c, d, 0, 6, T49); + SET(d, a, b, c, 7, 10, T50); + SET(c, d, a, b, 14, 15, T51); + SET(b, c, d, a, 5, 21, T52); + SET(a, b, c, d, 12, 6, T53); + SET(d, a, b, c, 3, 10, T54); + SET(c, d, a, b, 10, 15, T55); + SET(b, c, d, a, 1, 21, T56); + SET(a, b, c, d, 8, 6, T57); + SET(d, a, b, c, 15, 10, T58); + SET(c, d, a, b, 6, 15, T59); + SET(b, c, d, a, 13, 21, T60); + SET(a, b, c, d, 4, 6, T61); + SET(d, a, b, c, 11, 10, T62); + SET(c, d, a, b, 2, 15, T63); + SET(b, c, d, a, 9, 21, T64); +#undef SET + + /* Then perform the following additions. (That is increment each + of the four registers by the value it had before this block + was started.) */ + pms->abcd[0] += a; + pms->abcd[1] += b; + pms->abcd[2] += c; + pms->abcd[3] += d; +} + +void +md5_init(md5_state_t *pms) +{ + pms->count[0] = pms->count[1] = 0; + pms->abcd[0] = 0x67452301; + pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; + pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; + pms->abcd[3] = 0x10325476; +} + +void +md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) +{ + const md5_byte_t *p = data; + int left = nbytes; + int offset = (pms->count[0] >> 3) & 63; + md5_word_t nbits = (md5_word_t)(nbytes << 3); + + if (nbytes <= 0) + return; + + /* Update the message length. */ + pms->count[1] += nbytes >> 29; + pms->count[0] += nbits; + if (pms->count[0] < nbits) + pms->count[1]++; + + /* Process an initial partial block. */ + if (offset) { + int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); + + memcpy(pms->buf + offset, p, copy); + if (offset + copy < 64) + return; + p += copy; + left -= copy; + md5_process(pms, pms->buf); + } + + /* Process full blocks. */ + for (; left >= 64; p += 64, left -= 64) + md5_process(pms, p); + + /* Process a final partial block. */ + if (left) + memcpy(pms->buf, p, left); +} + +void +md5_finish(md5_state_t *pms, md5_byte_t digest[16]) +{ + static const md5_byte_t pad[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + md5_byte_t data[8]; + int i; + + /* Save the length before padding. */ + for (i = 0; i < 8; ++i) + data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); + /* Pad to 56 bytes mod 64. */ + md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); + /* Append the length. */ + md5_append(pms, data, 8); + for (i = 0; i < 16; ++i) + digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); +} + + + diff --git a/iodine/src/md5.h b/iodine/src/md5.h new file mode 100644 index 0000000..698c995 --- /dev/null +++ b/iodine/src/md5.h @@ -0,0 +1,91 @@ +/* + Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + L. Peter Deutsch + ghost@aladdin.com + + */ +/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */ +/* + Independent implementation of MD5 (RFC 1321). + + This code implements the MD5 Algorithm defined in RFC 1321, whose + text is available at + http://www.ietf.org/rfc/rfc1321.txt + The code is derived from the text of the RFC, including the test suite + (section A.5) but excluding the rest of Appendix A. It does not include + any code or documentation that is identified in the RFC as being + copyrighted. + + The original and principal author of md5.h is L. Peter Deutsch + . Other authors are noted in the change history + that follows (in reverse chronological order): + + 2002-04-13 lpd Removed support for non-ANSI compilers; removed + references to Ghostscript; clarified derivation from RFC 1321; + now handles byte order either statically or dynamically. + 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. + 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); + added conditionalization for C++ compilation from Martin + Purschke . + 1999-05-03 lpd Original version. + */ + +#ifndef md5_INCLUDED +# define md5_INCLUDED + +/* + * This package supports both compile-time and run-time determination of CPU + * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be + * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is + * defined as non-zero, the code will be compiled to run only on big-endian + * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to + * run on either big- or little-endian CPUs, but will run slightly less + * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. + */ + +typedef unsigned char md5_byte_t; /* 8-bit byte */ +typedef unsigned int md5_word_t; /* 32-bit word */ + +/* Define the state of the MD5 Algorithm. */ +typedef struct md5_state_s { + md5_word_t count[2]; /* message length in bits, lsw first */ + md5_word_t abcd[4]; /* digest buffer */ + md5_byte_t buf[64]; /* accumulate block */ +} md5_state_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Initialize the algorithm. */ +void md5_init(md5_state_t *pms); + +/* Append a string to the message. */ +void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); + +/* Finish the message and return the digest. */ +void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); + +#ifdef __cplusplus +} /* end extern "C" */ +#endif + +#endif /* md5_INCLUDED */ diff --git a/iodine/src/osflags b/iodine/src/osflags new file mode 100755 index 0000000..787ffaa --- /dev/null +++ b/iodine/src/osflags @@ -0,0 +1,36 @@ +#!/bin/sh + +case $2 in +link) + + case $1 in + SunOS | solaris) + echo '-lsocket -lnsl'; + ;; + BeOS) + echo '-lsocket -lbind -lbsd'; + ;; + Haiku) + echo '-lnetwork'; + ;; + windows32) + echo '-lws2_32 -liphlpapi'; + ;; + Linux) + [ -e /usr/include/selinux/selinux.h ] && echo '-lselinux'; + ;; + esac + ;; +cflags) + case $1 in + BeOS) + echo '-Dsocklen_t=int'; + ;; + Linux) + [ -e /usr/include/selinux/selinux.h ] && echo '-DHAVE_SETCON'; + ;; + esac +;; +*) +;; +esac diff --git a/iodine/src/read.c b/iodine/src/read.c new file mode 100644 index 0000000..ff40382 --- /dev/null +++ b/iodine/src/read.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include + +static int +readname_loop(char *packet, int packetlen, char **src, char *dst, size_t length, size_t loop) +{ + char *dummy; + char *s; + char *d; + int len; + int offset; + char c; + + if (loop <= 0) + return 0; + + len = 0; + s = *src; + d = dst; + while(*s && len < length - 2) { + c = *s++; + + /* is this a compressed label? */ + if((c & 0xc0) == 0xc0) { + offset = (((s[-1] & 0x3f) << 8) | (s[0] & 0xff)); + if (offset > packetlen) { + if (len == 0) { + /* Bad jump first in packet */ + return 0; + } else { + /* Bad jump after some data */ + break; + } + } + dummy = packet + offset; + len += readname_loop(packet, packetlen, &dummy, d, length - len, loop - 1); + goto end; + } + + while(c && len < length - 1) { + *d++ = *s++; + len++; + + c--; + } + + if (len >= length - 1) { + break; /* We used up all space */ + } + + if (*s != 0) { + *d++ = '.'; + len++; + } + } + dst[len++] = '\0'; + +end: + (*src) = s+1; + return len; +} + +int +readname(char *packet, int packetlen, char **src, char *dst, size_t length) +{ + return readname_loop(packet, packetlen, src, dst, length, 10); +} + +int +readshort(char *packet, char **src, short *dst) +{ + unsigned char *p; + + p = (unsigned char *) *src; + *dst = (p[0] << 8) | p[1]; + + (*src) += sizeof(short); + return sizeof(short); +} + +int +readlong(char *packet, char **src, uint32_t *dst) +{ + /* A long as described in dns protocol is always 32 bits */ + unsigned char *p; + + p = (unsigned char *) *src; + + *dst = ((uint32_t)p[0] << 24) + | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) + | ((uint32_t)p[3]); + + (*src) += sizeof(uint32_t); + return sizeof(uint32_t); +} + +int +readdata(char *packet, char **src, char *dst, size_t len) +{ + if (len < 0) + return 0; + + memcpy(dst, *src, len); + + (*src) += len; + + return len; +} + +int +readtxtbin(char *packet, char **src, size_t srcremain, char *dst, size_t dstremain) +{ + unsigned char *uc; + int tocopy; + int dstused = 0; + + while (srcremain > 0) + { + uc = (unsigned char*) (*src); + tocopy = *uc; + (*src)++; + srcremain--; + + if (tocopy > srcremain) + return 0; /* illegal, better have nothing */ + if (tocopy > dstremain) + return 0; /* doesn't fit, better have nothing */ + + memcpy(dst, *src, tocopy); + dst += tocopy; + (*src) += tocopy; + srcremain -= tocopy; + dstremain -= tocopy; + dstused += tocopy; + } + return dstused; +} + +int +putname(char **buf, size_t buflen, const char *host) +{ + char *word; + int left; + char *h; + char *p; + + h = strdup(host); + left = buflen; + p = *buf; + + word = strtok(h, "."); + while(word) { + if (strlen(word) > 63 || strlen(word) > left) { + free(h); + return -1; + } + + left -= (strlen(word) + 1); + *p++ = (char)strlen(word); + memcpy(p, word, strlen(word)); + p += strlen(word); + + word = strtok(NULL, "."); + } + + *p++ = 0; + + free(h); + + *buf = p; + return buflen - left; +} + +int +putbyte(char **dst, unsigned char value) +{ + **dst = value; + (*dst)++; + + return sizeof(char); +} + +int +putshort(char **dst, unsigned short value) +{ + unsigned char *p; + + p = (unsigned char *) *dst; + + *p++ = (value >> 8); + *p++ = value; + + (*dst) = (char *) p; + return sizeof(short); +} + +int +putlong(char **dst, uint32_t value) +{ + /* A long as described in dns protocol is always 32 bits */ + unsigned char *p; + + p = (unsigned char *) *dst; + + *p++ = (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = (value); + + (*dst) = (char *) p; + return sizeof(uint32_t); +} + +int +putdata(char **dst, char *data, size_t len) +{ + if (len < 0) + return 0; + + memcpy(*dst, data, len); + + (*dst) += len; + return len; +} + +int +puttxtbin(char **buf, size_t bufremain, char *from, size_t fromremain) +{ + unsigned char uc; + unsigned char *ucp = &uc; + char *cp = (char *) ucp; + int tocopy; + int bufused = 0; + + while (fromremain > 0) + { + tocopy = fromremain; + if (tocopy > 252) + tocopy = 252; /* allow off-by-1s in caches etc */ + if (tocopy + 1 > bufremain) + return -1; /* doesn't fit, better have nothing */ + + uc = tocopy; + **buf = *cp; + (*buf)++; + bufremain--; + bufused++; + + memcpy(*buf, from, tocopy); + (*buf) += tocopy; + from += tocopy; + bufremain -= tocopy; + fromremain -= tocopy; + bufused += tocopy; + } + return bufused; +} diff --git a/iodine/src/read.h b/iodine/src/read.h new file mode 100644 index 0000000..b33f3bb --- /dev/null +++ b/iodine/src/read.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 _READ_H_ +#define _READ_H_ + +int readname(char *, int, char **, char *, size_t); +int readshort(char *, char **, short *); +int readlong(char *, char **, uint32_t *); +int readdata(char *, char **, char *, size_t); +int readtxtbin(char *, char **, size_t, char *, size_t); + +int putname(char **, size_t, const char *); +int putbyte(char **, unsigned char); +int putshort(char **, unsigned short); +int putlong(char **, uint32_t); +int putdata(char **, char *, size_t); +int puttxtbin(char **, size_t, char *, size_t); + +#endif diff --git a/iodine/src/tun.c b/iodine/src/tun.c new file mode 100644 index 0000000..6802850 --- /dev/null +++ b/iodine/src/tun.c @@ -0,0 +1,541 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include +#include +#include "windows.h" + +HANDLE dev_handle; +struct tun_data data; + +static void get_name(char *ifname, int namelen, char *dev_name); + +#define TAP_CONTROL_CODE(request,method) CTL_CODE(FILE_DEVICE_UNKNOWN, request, method, FILE_ANY_ACCESS) +#define TAP_IOCTL_CONFIG_TUN TAP_CONTROL_CODE(10, METHOD_BUFFERED) +#define TAP_IOCTL_SET_MEDIA_STATUS TAP_CONTROL_CODE(6, METHOD_BUFFERED) + +#define TAP_ADAPTER_KEY "SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}" +#define NETWORK_KEY "SYSTEM\\CurrentControlSet\\Control\\Network\\{4D36E972-E325-11CE-BFC1-08002BE10318}" +#define TAP_DEVICE_SPACE "\\\\.\\Global\\" +#define TAP_VERSION_ID_0801 "tap0801" +#define TAP_VERSION_ID_0901 "tap0901" +#define KEY_COMPONENT_ID "ComponentId" +#define NET_CFG_INST_ID "NetCfgInstanceId" +#else +#include +#include +#include + +#define TUN_MAX_TRY 50 +#endif + +#include "tun.h" +#include "common.h" + +char if_name[250]; + +#ifndef WINDOWS32 +#ifdef LINUX + +#include +#include +#include + +int +open_tun(const char *tun_device) +{ + int i; + int tun_fd; + struct ifreq ifreq; + char *tunnel = "/dev/net/tun"; + + if ((tun_fd = open(tunnel, O_RDWR)) < 0) { + warn("open_tun: %s: %s", tunnel, strerror(errno)); + return -1; + } + + memset(&ifreq, 0, sizeof(ifreq)); + + ifreq.ifr_flags = IFF_TUN; + + 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) { + fprintf(stderr, "Opened %s\n", ifreq.ifr_name); + return tun_fd; + } + + if (errno != EBUSY) { + warn("open_tun: ioctl[TUNSETIFF]: %s", strerror(errno)); + return -1; + } + } else { + for (i = 0; i < TUN_MAX_TRY; i++) { + snprintf(ifreq.ifr_name, IFNAMSIZ, "dns%d", i); + + if (ioctl(tun_fd, TUNSETIFF, (void *) &ifreq) != -1) { + fprintf(stderr, "Opened %s\n", ifreq.ifr_name); + snprintf(if_name, sizeof(if_name), "dns%d", i); + return tun_fd; + } + + if (errno != EBUSY) { + warn("open_tun: ioctl[TUNSETIFF]: %s", strerror(errno)); + return -1; + } + } + + warn("open_tun: Couldn't set interface name"); + } + warn("error when opening tun"); + return -1; +} + +#else /* BSD */ + +int +open_tun(const char *tun_device) +{ + int i; + int tun_fd; + char tun_name[50]; + + 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)); + return -1; + } + + fprintf(stderr, "Opened %s\n", tun_name); + return tun_fd; + } else { + for (i = 0; i < TUN_MAX_TRY; i++) { + snprintf(tun_name, sizeof(tun_name), "/dev/tun%d", i); + + if ((tun_fd = open(tun_name, O_RDWR)) >= 0) { + fprintf(stderr, "Opened %s\n", tun_name); + snprintf(if_name, sizeof(if_name), "tun%d", i); + return tun_fd; + } + + if (errno == ENOENT) + break; + } + + warn("open_tun: Failed to open tunneling device"); + } + + return -1; +} + +#endif /* !LINUX */ +#else /* WINDOWS32 */ +static void +get_device(char *device, int device_len, const char *wanted_dev) +{ + LONG status; + HKEY adapter_key; + int index; + + index = 0; + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TAP_ADAPTER_KEY, 0, KEY_READ, &adapter_key); + + if (status != ERROR_SUCCESS) { + warnx("Error opening registry key " TAP_ADAPTER_KEY ); + return; + } + + while (TRUE) { + char name[256]; + char unit[256]; + char component[256]; + + char cid_string[256] = KEY_COMPONENT_ID; + HKEY device_key; + DWORD datatype; + DWORD len; + + /* Iterate through all adapter of this kind */ + len = sizeof(name); + status = RegEnumKeyEx(adapter_key, index, name, &len, NULL, NULL, NULL, NULL); + if (status == ERROR_NO_MORE_ITEMS) { + break; + } else if (status != ERROR_SUCCESS) { + warnx("Error enumerating subkeys of registry key " TAP_ADAPTER_KEY ); + break; + } + + snprintf(unit, sizeof(unit), TAP_ADAPTER_KEY "\\%s", name); + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, unit, 0, KEY_READ, &device_key); + if (status != ERROR_SUCCESS) { + warnx("Error opening registry key %s", unit); + goto next; + } + + /* Check component id */ + len = sizeof(component); + status = RegQueryValueEx(device_key, cid_string, NULL, &datatype, (LPBYTE)component, &len); + if (status != ERROR_SUCCESS || datatype != REG_SZ) { + goto next; + } + if (strncmp(TAP_VERSION_ID_0801, component, strlen(TAP_VERSION_ID_0801)) == 0 || + strncmp(TAP_VERSION_ID_0901, component, strlen(TAP_VERSION_ID_0901)) == 0) { + /* We found a TAP32 device, get its NetCfgInstanceId */ + char iid_string[256] = NET_CFG_INST_ID; + + status = RegQueryValueEx(device_key, iid_string, NULL, &datatype, (LPBYTE) device, (DWORD *) &device_len); + if (status != ERROR_SUCCESS || datatype != REG_SZ) { + warnx("Error reading registry key %s\\%s on TAP device", unit, iid_string); + } else { + /* Done getting GUID of TAP device, + * now check if the name is the requested one */ + if (wanted_dev) { + char name[250]; + get_name(name, sizeof(name), device); + if (strncmp(name, wanted_dev, strlen(wanted_dev))) { + /* Skip if name mismatch */ + goto next; + } + } + /* Get the if name */ + get_name(if_name, sizeof(if_name), device); + RegCloseKey(device_key); + return; + } + } +next: + RegCloseKey(device_key); + index++; + } + RegCloseKey(adapter_key); +} + +static void +get_name(char *ifname, int namelen, char *dev_name) +{ + char path[256]; + char name_str[256] = "Name"; + LONG status; + HKEY conn_key; + DWORD len; + DWORD datatype; + + memset(ifname, 0, namelen); + + snprintf(path, sizeof(path), NETWORK_KEY "\\%s\\Connection", dev_name); + status = RegOpenKeyEx(HKEY_LOCAL_MACHINE, path, 0, KEY_READ, &conn_key); + if (status != ERROR_SUCCESS) { + fprintf(stderr, "Could not look up name of interface %s: error opening key\n", dev_name); + RegCloseKey(conn_key); + return; + } + len = namelen; + status = RegQueryValueEx(conn_key, name_str, NULL, &datatype, (LPBYTE)ifname, &len); + if (status != ERROR_SUCCESS || datatype != REG_SZ) { + fprintf(stderr, "Could not look up name of interface %s: error reading value\n", dev_name); + RegCloseKey(conn_key); + return; + } + RegCloseKey(conn_key); +} + +DWORD WINAPI tun_reader(LPVOID arg) +{ + struct tun_data *tun = arg; + char buf[64*1024]; + int len; + int res; + OVERLAPPED olpd; + int sock; + + sock = open_dns(0, INADDR_ANY); + + olpd.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + while(TRUE) { + olpd.Offset = 0; + olpd.OffsetHigh = 0; + res = ReadFile(tun->tun, buf, sizeof(buf), (LPDWORD) &len, &olpd); + if (!res) { + WaitForSingleObject(olpd.hEvent, INFINITE); + res = GetOverlappedResult(dev_handle, &olpd, (LPDWORD) &len, FALSE); + res = sendto(sock, buf, len, 0, (struct sockaddr*) &(tun->addr), + sizeof(struct sockaddr_in)); + } + } + + return 0; +} + +int +open_tun(const char *tun_device) +{ + char adapter[256]; + char tapfile[512]; + int tunfd; + in_addr_t local; + + memset(adapter, 0, sizeof(adapter)); + memset(if_name, 0, sizeof(if_name)); + get_device(adapter, sizeof(adapter), tun_device); + + if (strlen(adapter) == 0 || strlen(if_name) == 0) { + if (tun_device) { + warnx("No TAP adapters found. Try without -d."); + } else { + warnx("No TAP adapters found. Version 0801 and 0901 are supported."); + } + return -1; + } + + fprintf(stderr, "Opening device %s\n", if_name); + snprintf(tapfile, sizeof(tapfile), "%s%s.tap", TAP_DEVICE_SPACE, adapter); + dev_handle = CreateFile(tapfile, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, NULL); + if (dev_handle == INVALID_HANDLE_VALUE) { + warnx("Could not open device!"); + return -1; + } + + /* Use a UDP connection to forward packets from tun, + * so we can still use select() in main code. + * A thread does blocking reads on tun device and + * sends data as udp to this socket */ + + local = htonl(0x7f000001); /* 127.0.0.1 */ + tunfd = open_dns(55353, local); + + data.tun = dev_handle; + memset(&(data.addr), 0, sizeof(data.addr)); + data.addr.sin_family = AF_INET; + data.addr.sin_port = htons(55353); + data.addr.sin_addr.s_addr = local; + CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)tun_reader, &data, 0, NULL); + + return tunfd; +} +#endif + +void +close_tun(int tun_fd) +{ + if (tun_fd >= 0) + close(tun_fd); +} + +int +write_tun(int tun_fd, char *data, size_t len) +{ +#if defined (FREEBSD) || defined (DARWIN) || defined(NETBSD) || defined(WINDOWS32) + data += 4; + len -= 4; +#else /* !FREEBSD/DARWIN */ +#ifdef LINUX + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x08; + data[3] = 0x00; +#else /* OPENBSD */ + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x02; +#endif /* !LINUX */ +#endif /* FREEBSD */ + +#ifndef WINDOWS32 + if (write(tun_fd, data, len) != len) { + warn("write_tun"); + return 1; + } +#else /* WINDOWS32 */ + { + DWORD written; + DWORD res; + OVERLAPPED olpd; + + olpd.Offset = 0; + olpd.OffsetHigh = 0; + olpd.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + res = WriteFile(dev_handle, data, len, &written, &olpd); + if (!res && GetLastError() == ERROR_IO_PENDING) { + WaitForSingleObject(olpd.hEvent, INFINITE); + res = GetOverlappedResult(dev_handle, &olpd, &written, FALSE); + if (written != len) { + return -1; + } + } + } +#endif + return 0; +} + +ssize_t +read_tun(int tun_fd, char *buf, size_t len) +{ +#if defined (FREEBSD) || defined (DARWIN) || defined(NETBSD) || defined(WINDOWS32) + /* FreeBSD/Darwin/NetBSD has no header */ + int bytes; + memset(buf, 0, 4); +#ifdef WINDOWS32 + /* Windows needs recv() since it is local UDP socket */ + bytes = recv(tun_fd, buf + 4, len - 4, 0); +#else + /* The other need read() because fd is not a socket */ + bytes = read(tun_fd, buf + 4, len - 4); +#endif /*WINDOWS32*/ + if (bytes < 0) { + return bytes; + } else { + return bytes + 4; + } +#else /* !FREEBSD */ + return read(tun_fd, buf, len); +#endif /* !FREEBSD */ +} + +int +tun_setip(const char *ip, const char *remoteip, int netbits) +{ + char cmdline[512]; + int netmask; + struct in_addr net; + int i; +#ifndef LINUX + int r; +#endif +#ifdef WINDOWS32 + DWORD status; + DWORD ipdata[3]; + struct in_addr addr; + DWORD len; +#endif + + netmask = 0; + for (i = 0; i < netbits; i++) { + netmask = (netmask << 1) | 1; + } + netmask <<= (32 - netbits); + net.s_addr = htonl(netmask); + + if (inet_addr(ip) == INADDR_NONE) { + fprintf(stderr, "Invalid IP: %s!\n", ip); + return 1; + } +#ifndef WINDOWS32 + snprintf(cmdline, sizeof(cmdline), + "/sbin/ifconfig %s %s %s netmask %s", + if_name, + ip, +#ifdef FREEBSD + remoteip, /* FreeBSD wants other IP as second IP */ +#else + ip, +#endif + inet_ntoa(net)); + + fprintf(stderr, "Setting IP of %s to %s\n", if_name, ip); +#ifndef LINUX + r = system(cmdline); + if(r != 0) { + return r; + } else { + snprintf(cmdline, sizeof(cmdline), + "/sbin/route add %s/%d %s", + ip, netbits, ip); + } + fprintf(stderr, "Adding route %s/%d to %s\n", ip, netbits, ip); +#endif + return system(cmdline); +#else /* WINDOWS32 */ + + /* Set device as connected */ + fprintf(stderr, "Enabling interface '%s'\n", if_name); + status = 1; + r = DeviceIoControl(dev_handle, TAP_IOCTL_SET_MEDIA_STATUS, &status, + sizeof(status), &status, sizeof(status), &len, NULL); + if (!r) { + fprintf(stderr, "Failed to enable interface\n"); + return -1; + } + + if (inet_aton(ip, &addr)) { + ipdata[0] = (DWORD) addr.s_addr; /* local ip addr */ + ipdata[1] = net.s_addr & ipdata[0]; /* network addr */ + ipdata[2] = (DWORD) net.s_addr; /* netmask */ + } else { + return -1; + } + + /* Tell ip/networkaddr/netmask to device for arp use */ + r = DeviceIoControl(dev_handle, TAP_IOCTL_CONFIG_TUN, &ipdata, + sizeof(ipdata), &ipdata, sizeof(ipdata), &len, NULL); + if (!r) { + fprintf(stderr, "Failed to set interface in TUN mode\n"); + return -1; + } + + /* use netsh to set ip address */ + fprintf(stderr, "Setting IP of interface '%s' to %s (can take a few seconds)...\n", if_name, ip); + snprintf(cmdline, sizeof(cmdline), "netsh interface ip set address \"%s\" static %s %s", + if_name, ip, inet_ntoa(net)); + return system(cmdline); +#endif +} + +int +tun_setmtu(const unsigned mtu) +{ +#ifndef WINDOWS32 + char cmdline[512]; + + if (mtu > 200 && mtu <= 1500) { + snprintf(cmdline, sizeof(cmdline), + "/sbin/ifconfig %s mtu %u", + if_name, + mtu); + + fprintf(stderr, "Setting MTU of %s to %u\n", if_name, mtu); + return system(cmdline); + } else { + warn("MTU out of range: %u\n", mtu); + } + + return 1; +#else /* WINDOWS32 */ + + return 0; +#endif +} + diff --git a/iodine/src/tun.h b/iodine/src/tun.h new file mode 100644 index 0000000..89ffcfa --- /dev/null +++ b/iodine/src/tun.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 _TUN_H_ +#define _TUN_H_ + +int open_tun(const char *); +void close_tun(int); +int write_tun(int, char *, size_t); +ssize_t read_tun(int, char *, size_t); +int tun_setip(const char *, const char *, int); +int tun_setmtu(const unsigned); + +#endif /* _TUN_H_ */ diff --git a/iodine/src/user.c b/iodine/src/user.c new file mode 100644 index 0000000..dfe9c36 --- /dev/null +++ b/iodine/src/user.c @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#ifdef WINDOWS32 +#include +#else +#include +#endif + +#include "common.h" +#include "encoding.h" +#include "user.h" + +struct user users[USERS]; + +int +init_users(in_addr_t my_ip, int netbits) +{ + int i; + int skip = 0; + char newip[16]; + int created_users = 0; + + int maxusers; + + in_addr_t netmask = 0; + struct in_addr net; + struct in_addr ipstart; + + for (i = 0; i < netbits; i++) { + netmask = (netmask << 1) | 1; + } + netmask <<= (32 - netbits); + net.s_addr = htonl(netmask); + ipstart.s_addr = my_ip & net.s_addr; + + maxusers = (1 << (32-netbits)) - 3; /* 3: Net addr, broadcast addr, iodined addr */ + + memset(users, 0, USERS * sizeof(struct user)); + for (i = 0; i < USERS; i++) { + in_addr_t ip; + users[i].id = i; + snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); + ip = ipstart.s_addr + inet_addr(newip); + if (ip == my_ip && skip == 0) { + /* This IP was taken by iodined */ + skip++; + snprintf(newip, sizeof(newip), "0.0.0.%d", i + skip + 1); + ip = ipstart.s_addr + inet_addr(newip); + } + users[i].tun_ip = ip; + net.s_addr = ip; + if (maxusers-- < 1) { + users[i].disabled = 1; + } else { + users[i].disabled = 0; + created_users++; + } + users[i].active = 0; + /* Rest is reset on login ('V' packet) */ + } + + return created_users; +} + +const char* +users_get_first_ip() +{ + struct in_addr ip; + ip.s_addr = users[0].tun_ip; + return inet_ntoa(ip); +} + +int +users_waiting_on_reply() +{ + int ret; + int i; + + ret = 0; + for (i = 0; i < USERS; i++) { + if (users[i].active && !users[i].disabled && + users[i].last_pkt + 60 > time(NULL) && + users[i].q.id != 0 && users[i].conn == CONN_DNS_NULL) { + ret++; + } + } + + return ret; +} + +int +find_user_by_ip(uint32_t ip) +{ + int ret; + int i; + + ret = -1; + for (i = 0; i < USERS; i++) { + if (users[i].active && !users[i].disabled && + users[i].last_pkt + 60 > time(NULL) && + ip == users[i].tun_ip) { + ret = i; + break; + } + } + return ret; +} + +int +all_users_waiting_to_send() +/* If this returns true, then reading from tun device is blocked. + So only return true when all clients have at least one packet in + the outpacket-queue, so that sending back-to-back is possible + without going through another select loop. +*/ +{ + time_t now; + int ret; + int i; + + ret = 1; + now = time(NULL); + for (i = 0; i < USERS; i++) { + if (users[i].active && !users[i].disabled && + users[i].last_pkt + 60 > now && + ((users[i].conn == CONN_RAW_UDP) || + ((users[i].conn == CONN_DNS_NULL) +#ifdef OUTPACKETQ_LEN + && users[i].outpacketq_filled < 1 +#else + && users[i].outpacket.len == 0 +#endif + ))) { + + ret = 0; + break; + } + } + return ret; +} + +int +find_available_user() +{ + int ret = -1; + int i; + for (i = 0; i < USERS; i++) { + /* Not used at all or not used in one minute */ + if ((!users[i].active || users[i].last_pkt + 60 < time(NULL)) && !users[i].disabled) { + users[i].active = 1; + users[i].last_pkt = time(NULL); + users[i].fragsize = 4096; + users[i].conn = CONN_DNS_NULL; + ret = i; + break; + } + } + return ret; +} + +void +user_switch_codec(int userid, struct encoder *enc) +{ + if (userid < 0 || userid >= USERS) + return; + + users[userid].encoder = enc; +} + +void +user_set_conn_type(int userid, enum connection c) +{ + if (userid < 0 || userid >= USERS) + return; + + if (c < 0 || c >= CONN_MAX) + return; + + users[userid].conn = c; +} + diff --git a/iodine/src/user.h b/iodine/src/user.h new file mode 100644 index 0000000..51a6092 --- /dev/null +++ b/iodine/src/user.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __USER_H__ +#define __USER_H__ + +#define USERS 16 + +#define OUTPACKETQ_LEN 4 /* Note: 16 users * 1 packet = 1MB */ +/* Undefine to have no queue for packets coming in from tun device, which may + lead to massive dropping in multi-user situations with high traffic. */ + +#define DNSCACHE_LEN 4 +/* Undefine to disable. Should be less than 18; also see comments in iodined.c */ + + +#define QMEMPING_LEN 30 +/* Max advisable: 64k/2 = 32000. Total mem usage: QMEMPING_LEN * USERS * 6 bytes */ + +#define QMEMDATA_LEN 15 +/* Max advisable: 36/2 = 18. Total mem usage: QMEMDATA_LEN * USERS * 6 bytes */ + +struct user { + char id; + int active; + int disabled; + time_t last_pkt; + int seed; + in_addr_t tun_ip; + struct in_addr host; + struct query q; + struct query q_sendrealsoon; + int q_sendrealsoon_new; + struct packet inpacket; + struct packet outpacket; + int outfragresent; + struct encoder *encoder; + char downenc; + int out_acked_seqno; + int out_acked_fragment; + int fragsize; + enum connection conn; + int lazy; + unsigned char qmemping_cmc[QMEMPING_LEN * 4]; + unsigned short qmemping_type[QMEMPING_LEN]; + int qmemping_lastfilled; + unsigned char qmemdata_cmc[QMEMDATA_LEN * 4]; + unsigned short qmemdata_type[QMEMDATA_LEN]; + int qmemdata_lastfilled; +#ifdef OUTPACKETQ_LEN + struct packet outpacketq[OUTPACKETQ_LEN]; + int outpacketq_nexttouse; + int outpacketq_filled; +#endif +#ifdef DNSCACHE_LEN + struct query dnscache_q[DNSCACHE_LEN]; + char dnscache_answer[DNSCACHE_LEN][4096]; + int dnscache_answerlen[DNSCACHE_LEN]; + int dnscache_lastfilled; +#endif +}; + +extern struct user users[USERS]; + +int init_users(in_addr_t, int); +const char* users_get_first_ip(); +int users_waiting_on_reply(); +int find_user_by_ip(uint32_t); +int all_users_waiting_to_send(); +int find_available_user(); +void user_switch_codec(int userid, struct encoder *enc); +void user_set_conn_type(int userid, enum connection c); + +#endif diff --git a/iodine/src/util.c b/iodine/src/util.c new file mode 100644 index 0000000..bc5fc8d --- /dev/null +++ b/iodine/src/util.c @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include "common.h" + +char * +get_resolvconf_addr() +{ + static char addr[16]; + char *rv; +#ifndef WINDOWS32 + char buf[80]; + FILE *fp; + + rv = NULL; + + if ((fp = fopen("/etc/resolv.conf", "r")) == NULL) + err(1, "/etc/resolv.conf"); + + while (feof(fp) == 0) { + fgets(buf, sizeof(buf), fp); + + if (sscanf(buf, "nameserver %15s", addr) == 1) { + rv = addr; + break; + } + } + + fclose(fp); +#else /* !WINDOWS32 */ + FIXED_INFO *fixed_info; + ULONG buflen; + DWORD ret; + + rv = NULL; + fixed_info = malloc(sizeof(FIXED_INFO)); + buflen = sizeof(FIXED_INFO); + + if (GetNetworkParams(fixed_info, &buflen) == ERROR_BUFFER_OVERFLOW) { + /* official ugly api workaround */ + free(fixed_info); + fixed_info = malloc(buflen); + } + + ret = GetNetworkParams(fixed_info, &buflen); + if (ret == NO_ERROR) { + strncpy(addr, fixed_info->DnsServerList.IpAddress.String, sizeof(addr)); + addr[15] = 0; + rv = addr; + } + free(fixed_info); +#endif + return rv; +} + diff --git a/iodine/src/util.h b/iodine/src/util.h new file mode 100644 index 0000000..f514139 --- /dev/null +++ b/iodine/src/util.h @@ -0,0 +1,6 @@ +#ifndef __UTIL_H__ +#define __UTIL_H__ + +char *get_resolvconf_addr(); + +#endif diff --git a/iodine/src/version.h b/iodine/src/version.h new file mode 100644 index 0000000..1561b9e --- /dev/null +++ b/iodine/src/version.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 _VERSION_H_ +#define _VERSION_H_ + +/* This is the version of the network protocol + It is usually equal to the latest iodine version number */ +#define VERSION 0x00000502 + +#endif /* _VERSION_H_ */ + diff --git a/iodine/src/windows.h b/iodine/src/windows.h new file mode 100644 index 0000000..7e0e16c --- /dev/null +++ b/iodine/src/windows.h @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __FIX_WINDOWS_H__ +#define __FIX_WINDOWS_H__ + +typedef unsigned int in_addr_t; + +#include +#include +#include +#include +#include + +/* Missing from the mingw headers */ +#ifndef DNS_TYPE_SRV +# define DNS_TYPE_SRV 33 +#endif +#ifndef DNS_TYPE_TXT +# define DNS_TYPE_TXT 16 +#endif + +#define T_A DNS_TYPE_A +#define T_NS DNS_TYPE_NS +#define T_NULL DNS_TYPE_NULL +#define T_CNAME DNS_TYPE_CNAME +#define T_MX DNS_TYPE_MX +#define T_TXT DNS_TYPE_TXT +#define T_SRV DNS_TYPE_SRV + +#define C_IN 1 + +#define FORMERR 1 +#define SERVFAIL 2 +#define NXDOMAIN 3 +#define NOTIMP 4 +#define REFUSED 5 + +#define sleep(seconds) Sleep((seconds)*1000) + +typedef struct { + unsigned id :16; /* query identification number */ + /* fields in third byte */ + unsigned rd :1; /* recursion desired */ + unsigned tc :1; /* truncated message */ + unsigned aa :1; /* authoritive answer */ + unsigned opcode :4; /* purpose of message */ + unsigned qr :1; /* response flag */ + /* fields in fourth byte */ + unsigned rcode :4; /* response code */ + unsigned cd: 1; /* checking disabled by resolver */ + unsigned ad: 1; /* authentic data from named */ + unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */ + unsigned ra :1; /* recursion available */ + /* remaining bytes */ + unsigned qdcount :16; /* number of question entries */ + unsigned ancount :16; /* number of answer entries */ + unsigned nscount :16; /* number of authority entries */ + unsigned arcount :16; /* number of resource entries */ +} HEADER; + +struct ip + { + unsigned int ip_hl:4; /* header length */ + unsigned int ip_v:4; /* version */ + u_char ip_tos; /* type of service */ + u_short ip_len; /* total length */ + u_short ip_id; /* identification */ + u_short ip_off; /* fragment offset field */ +#define IP_RF 0x8000 /* reserved fragment flag */ +#define IP_DF 0x4000 /* dont fragment flag */ +#define IP_MF 0x2000 /* more fragments flag */ +#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */ + u_char ip_ttl; /* time to live */ + u_char ip_p; /* protocol */ + u_short ip_sum; /* checksum */ + struct in_addr ip_src, ip_dst; /* source and dest address */ + }; + +DWORD WINAPI tun_reader(LPVOID arg); +struct tun_data { + HANDLE tun; + int sock; + struct sockaddr_in addr; +}; + +#endif diff --git a/iodine/tests/Makefile b/iodine/tests/Makefile new file mode 100644 index 0000000..3a7ac01 --- /dev/null +++ b/iodine/tests/Makefile @@ -0,0 +1,27 @@ +CC = gcc +TEST = test +OBJS = test.o base32.o base64.o read.o dns.o encoding.o login.o user.o fw_query.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 ../src/fw_query.o + +OS = `uname | tr "a-z" "A-Z"` + +CHECK_PATH = /usr/local +LDFLAGS = -L$(CHECK_PATH)/lib -lcheck `../src/osflags link` +CFLAGS = -g -Wall -D$(OS) -I../src -I$(CHECK_PATH)/include -pedantic `../src/osflags cflags` + +all: $(TEST) + @LD_LIBRARY_PATH=${CHECK_PATH}/lib ./$(TEST) + +$(TEST): $(OBJS) $(SRCOBJS) + @echo LD $(TEST) + @$(CC) -o $@ $(SRCOBJS) $(OBJS) $(LDFLAGS) + +.c.o: + @echo CC $< + @$(CC) $(CFLAGS) -c $< + + +clean: + @echo "Cleaning tests/" + @rm -f *~ *.core $(TEST) $(OBJS) + diff --git a/iodine/tests/base32.c b/iodine/tests/base32.c new file mode 100644 index 0000000..9ff0cf7 --- /dev/null +++ b/iodine/tests/base32.c @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include + +#include "encoding.h" +#include "base32.h" +#include "test.h" + +#define TUPLES 5 + +static struct tuple +{ + char *a; + char *b; +} testpairs[TUPLES] = { + { "iodinetestingtesting", "nfxwi0lomv0gk21unfxgo3dfon0gs1th" }, + { "abc123", "mfrggmjsgm" }, + { "test", "orsxg3a" }, + { "tst", "orzxi" }, + { "", "" }, +}; + +START_TEST(test_base32_encode) +{ + size_t len; + char buf[4096]; + struct encoder *b32; + int val; + + b32 = get_base32_encoder(); + + len = sizeof(buf); + val = b32->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + + fail_unless(strcmp(buf, testpairs[_i].b) == 0, + "'%s' != '%s'", buf, testpairs[_i].b); +} +END_TEST + +START_TEST(test_base32_decode) +{ + size_t len; + char buf[4096]; + struct encoder *b32; + int val; + + b32 = get_base32_encoder(); + + len = sizeof(buf); + val = b32->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + + fail_unless(buf != NULL, "buf == NULL"); + fail_unless(strcmp(buf, testpairs[_i].a) == 0, + "'%s' != '%s'", buf, testpairs[_i].a); +} +END_TEST + +START_TEST(test_base32_5to8_8to5) +{ + int i; + int c; + + for (i = 0; i < 32; i++) { + c = b32_5to8(i); + fail_unless(b32_8to5(c) == i); + } +} +END_TEST + +START_TEST(test_base32_blksize) +{ + size_t rawlen; + size_t enclen; + char *rawbuf; + char *encbuf; + struct encoder *b32; + int i; + int val; + + b32 = get_base32_encoder(); + + rawlen = b32->blocksize_raw(); + enclen = b32->blocksize_encoded(); + + rawbuf = malloc(rawlen + 16); + encbuf = malloc(enclen + 16); + + for (i = 0; i < rawlen; i++) { + rawbuf[i] = 'A'; + } + rawbuf[i] = 0; + + val = b32->encode(encbuf, &enclen, rawbuf, rawlen); + + fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); + fail_unless(enclen == 5, "encoded %d bytes, not 5", enclen); + fail_unless(val == 8, "encoded string %s was length %d", encbuf, val); + + memset(rawbuf, 0, rawlen + 16); + + enclen = val; + val = b32->decode(rawbuf, &rawlen, encbuf, enclen); + + fail_unless(rawlen == 5, "raw length was %d not 5", rawlen); + fail_unless(val == 5, "val was not 5 but %d", val); + for (i = 0; i < rawlen; i++) { + fail_unless(rawbuf[i] == 'A'); + } +} +END_TEST + +TCase * +test_base32_create_tests() +{ + TCase *tc; + + tc = tcase_create("Base32"); + tcase_add_loop_test(tc, test_base32_encode, 0, TUPLES); + tcase_add_loop_test(tc, test_base32_decode, 0, TUPLES); + tcase_add_test(tc, test_base32_5to8_8to5); + tcase_add_test(tc, test_base32_blksize); + + return tc; +} diff --git a/iodine/tests/base64.c b/iodine/tests/base64.c new file mode 100644 index 0000000..bd0e9ce --- /dev/null +++ b/iodine/tests/base64.c @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include + +#include "encoding.h" +#include "base64.h" +#include "test.h" + +#define TUPLES 5 + +static struct tuple +{ + char *a; + char *b; +} testpairs[TUPLES] = { + { "iodinetestingtesting", "Aw8KAw4LDgvZDgLUz2rLC2rPBMC" }, + { "abc1231", "ywjJmtiZmq" }, + { + "\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", + + "+9876543210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcbapZ" + "776543210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba+987654" + "3210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba" + }, + { + "\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", + + "+9876543210-ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcbapZ" + "776543210-ZYXWVUTSRQfHKwfHGsHGFEDCBAzyxwvutsrqponmlkjihgfedcba+987654321" + "0-ZYXWVUTSRQfHKwfHGsHGFEDCBAzyxwvutsrqponmlkjihgfedcba" + }, + { "", "" } +}; + +START_TEST(test_base64_encode) +{ + size_t len; + char buf[4096]; + struct encoder *b64; + int val; + + b64 = get_base64_encoder(); + + len = sizeof(buf); + val = b64->encode(buf, &len, testpairs[_i].a, strlen(testpairs[_i].a)); + + fail_unless(strcmp(buf, testpairs[_i].b) == 0, + "'%s' != '%s'", buf, testpairs[_i].b); +} +END_TEST + +START_TEST(test_base64_decode) +{ + size_t len; + char buf[4096]; + struct encoder *b64; + int val; + + b64 = get_base64_encoder(); + + len = sizeof(buf); + val = b64->decode(buf, &len, testpairs[_i].b, strlen(testpairs[_i].b)); + + fail_unless(buf != NULL, "buf == NULL"); + fail_unless(strcmp(buf, testpairs[_i].a) == 0, + "'%s' != '%s'", buf, testpairs[_i].a); +} +END_TEST + +START_TEST(test_base64_blksize) +{ + size_t rawlen; + size_t enclen; + char *rawbuf; + char *encbuf; + struct encoder *b64; + int i; + int val; + + b64 = get_base64_encoder(); + + rawlen = b64->blocksize_raw(); + enclen = b64->blocksize_encoded(); + + rawbuf = malloc(rawlen + 16); + encbuf = malloc(enclen + 16); + + for (i = 0; i < rawlen; i++) { + rawbuf[i] = 'A'; + } + rawbuf[i] = 0; + + val = b64->encode(encbuf, &enclen, rawbuf, rawlen); + + fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); + fail_unless(enclen == 3, "encoded %d bytes, not 3", enclen); + fail_unless(val == 4, "encoded string %s was length %d", encbuf, val); + + memset(rawbuf, 0, rawlen + 16); + + enclen = val; + val = b64->decode(rawbuf, &rawlen, encbuf, enclen); + + fail_unless(rawlen == 3, "raw length was %d not 3", rawlen); + fail_unless(val == 3); + for (i = 0; i < rawlen; i++) { + fail_unless(rawbuf[i] == 'A'); + } +} +END_TEST + +TCase * +test_base64_create_tests() +{ + TCase *tc; + + tc = tcase_create("Base64"); + tcase_add_loop_test(tc, test_base64_encode, 0, TUPLES); + tcase_add_loop_test(tc, test_base64_decode, 0, TUPLES); + tcase_add_test(tc, test_base64_blksize); + + return tc; +} diff --git a/iodine/tests/dns.c b/iodine/tests/dns.c new file mode 100644 index 0000000..3d21e4c --- /dev/null +++ b/iodine/tests/dns.c @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "dns.h" +#include "encoding.h" +#include "base32.h" +#include "test.h" + +static void dump_packet(char *, size_t); + +static char query_packet[] = + "\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 answer_packet[] = + "\x05\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05\x73\x69\x6C\x6C" + "\x79\x04\x68\x6F\x73\x74\x02\x6F\x66\x06\x69\x6F\x64\x69\x6E\x65\x04" + "\x63\x6F\x64\x65\x04\x6B\x72\x79\x6F\x02\x73\x65\x00\x00\x0A\x00\x01" + "\xC0\x0C\x00\x0A\x00\x01\x00\x00\x00\x00\x00\x23\x74\x68\x69\x73\x20" + "\x69\x73\x20\x74\x68\x65\x20\x6D\x65\x73\x73\x61\x67\x65\x20\x74\x6F" + "\x20\x62\x65\x20\x64\x65\x6C\x69\x76\x65\x72\x65\x64"; + +static char answer_packet_high_trans_id[] = + "\x85\x39\x84\x00\x00\x01\x00\x01\x00\x00\x00\x00\x05\x73\x69\x6C\x6C" + "\x79\x04\x68\x6F\x73\x74\x02\x6F\x66\x06\x69\x6F\x64\x69\x6E\x65\x04" + "\x63\x6F\x64\x65\x04\x6B\x72\x79\x6F\x02\x73\x65\x00\x00\x0A\x00\x01" + "\xC0\x0C\x00\x0A\x00\x01\x00\x00\x00\x00\x00\x23\x74\x68\x69\x73\x20" + "\x69\x73\x20\x74\x68\x65\x20\x6D\x65\x73\x73\x61\x67\x65\x20\x74\x6F" + "\x20\x62\x65\x20\x64\x65\x6C\x69\x76\x65\x72\x65\x64"; +static char *msgData = "this is the message to be delivered"; +static char *topdomain = "kryo.se"; + +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; + size_t len; + size_t enclen; + int ret; + + enclen = sizeof(resolv); + memset(&buf, 0, sizeof(buf)); + memset(&resolv, 0, sizeof(resolv)); + memset(&q, 0, sizeof(struct query)); + q.type = T_NULL; + q.id = 1337; + d = resolv; + enc = get_base32_encoder(); + + *d++ = 'A'; + enc->encode(d, &enclen, innerData, strlen(innerData)); + d = resolv + strlen(resolv); + if (*d != '.') { + *d++ = '.'; + } + strcpy(d, topdomain); + len = sizeof(buf); + ret = dns_encode(buf, len, &q, QR_QUERY, resolv, strlen(resolv)); + len = sizeof(query_packet) - 1; /* Skip extra null character */ + + if (strncmp(query_packet, buf, sizeof(query_packet)) || ret != len) { + printf("\n"); + dump_packet(query_packet, len); + dump_packet(buf, ret); + } + fail_unless(strncmp(query_packet, buf, sizeof(query_packet)) == 0, "Did not compile expected packet"); + fail_unless(ret == len, "Bad packet length: %d, expected %d", ret, len); +} +END_TEST + +START_TEST(test_decode_query) +{ + char buf[512]; + char *domain; + struct query q; + struct encoder *enc; + size_t len; + + memset(&q, 0, sizeof(struct query)); + memset(&buf, 0, sizeof(buf)); + q.id = 0; + len = sizeof(query_packet) - 1; + enc = get_base32_encoder(); + + dns_decode(buf, sizeof(buf), &q, QR_QUERY, query_packet, len); + domain = strstr(q.name, topdomain); + len = sizeof(buf); + unpack_data(buf, len, &(q.name[1]), (int) (domain - q.name) - 1, enc); + + fail_unless(strncmp(buf, innerData, strlen(innerData)) == 0, "Did not extract expected host: '%s'", buf); + fail_unless(strlen(buf) == strlen(innerData), "Bad host length: %d, expected %d: '%s'", strlen(buf), strlen(innerData), buf); +} +END_TEST + +START_TEST(test_encode_response) +{ + char buf[512]; + char *host = "silly.host.of.iodine.code.kryo.se"; + struct query q; + int len; + int ret; + + len = sizeof(buf); + memset(&buf, 0, sizeof(buf)); + memset(&q, 0, sizeof(struct query)); + strncpy(q.name, host, strlen(host)); + q.type = T_NULL; + q.id = 1337; + + ret = dns_encode(buf, len, &q, QR_ANSWER, msgData, strlen(msgData)); + len = sizeof(answer_packet) - 1; /* Skip extra null character */ + + fail_unless(strncmp(answer_packet, buf, sizeof(answer_packet)) == 0, "Did not compile expected packet"); + fail_unless(ret == len, "Bad packet length: %d, expected %d", ret, len); +} +END_TEST + +START_TEST(test_decode_response) +{ + char buf[512]; + struct query q; + int len; + int ret; + + len = sizeof(buf); + memset(&buf, 0, sizeof(buf)); + + ret = dns_decode(buf, len, &q, QR_ANSWER, answer_packet, sizeof(answer_packet)-1); + fail_unless(strncmp(msgData, buf, sizeof(msgData)) == 0, "Did not extract expected data"); + fail_unless(ret == strlen(msgData), "Bad data length: %d, expected %d", ret, strlen(msgData)); + fail_unless(q.id == 0x0539); +} +END_TEST + +START_TEST(test_decode_response_with_high_trans_id) +{ + char buf[512]; + struct query q; + int len; + int ret; + + len = sizeof(buf); + memset(&buf, 0, sizeof(buf)); + + ret = dns_decode(buf, len, &q, QR_ANSWER, answer_packet_high_trans_id, sizeof(answer_packet_high_trans_id)-1); + fail_unless(strncmp(msgData, buf, sizeof(msgData)) == 0, "Did not extract expected data"); + fail_unless(ret == strlen(msgData), "Bad data length: %d, expected %d", ret, strlen(msgData)); + fail_unless(q.id == 0x8539, "q.id was %08X instead of %08X!", q.id, 0x8539); +} +END_TEST + +START_TEST(test_get_id_short_packet) +{ + char buf[5]; + int len; + unsigned short id; + + len = sizeof(buf); + memset(&buf, 5, sizeof(buf)); + + id = dns_get_id(buf, len); + fail_unless(id == 0); +} +END_TEST + +START_TEST(test_get_id_low) +{ + unsigned short id; + + id = dns_get_id(answer_packet, sizeof(answer_packet)); + fail_unless(id == 1337); +} +END_TEST + +START_TEST(test_get_id_high) +{ + unsigned short id; + + id = dns_get_id(answer_packet_high_trans_id, sizeof(answer_packet_high_trans_id)); + fail_unless(id == 0x8539); +} +END_TEST + +static void +dump_packet(char *buf, size_t len) +{ + int pos; + + for (pos = 0; pos < len; pos++) { + printf("\\x%02X", (unsigned char) buf[pos]); + } + printf("\n"); + for (pos = 0; pos < len; pos++) { + if (isalnum((unsigned char) buf[pos])) { + printf(" %c ", (unsigned char) buf[pos]); + } else { + printf(" "); + } + } + printf("\n"); +} + +TCase * +test_dns_create_tests() +{ + TCase *tc; + + tc = tcase_create("Dns"); + tcase_add_test(tc, test_encode_query); + tcase_add_test(tc, test_decode_query); + tcase_add_test(tc, test_encode_response); + tcase_add_test(tc, test_decode_response); + tcase_add_test(tc, test_decode_response_with_high_trans_id); + tcase_add_test(tc, test_get_id_short_packet); + tcase_add_test(tc, test_get_id_low); + tcase_add_test(tc, test_get_id_high); + + return tc; +} diff --git a/iodine/tests/encoding.c b/iodine/tests/encoding.c new file mode 100644 index 0000000..ac22452 --- /dev/null +++ b/iodine/tests/encoding.c @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include + +#include "encoding.h" +#include "test.h" +#include "base32.h" +#include "base64.h" + +#define TUPLES 4 + +static struct tuple +{ + char *a; + char *b; +} dottests[] = { + { "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaabaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaa"}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."}, + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, + { "abc123", "abc123" }, + { NULL, NULL } +}; + +START_TEST(test_inline_dotify) +{ + char temp[1024]; + char *b; + + memset(temp, 0, sizeof(temp)); + strcpy(temp, dottests[_i].a); + b = temp; + inline_dotify(b, sizeof(temp)); + + fail_unless(strcmp(dottests[_i].b, temp) == 0, + "'%s' != '%s'", temp, dottests[_i].b); +} +END_TEST + +START_TEST(test_inline_undotify) +{ + char temp[1024]; + char *b; + + 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, + "'%s' != '%s'", temp, dottests[_i].a); +} +END_TEST + +START_TEST(test_build_hostname) +{ + char data[256]; + char buf[1024]; + char *topdomain = "a.c"; + int buflen; + int i; + + for (i = 0; i < sizeof(data); i++) { + data[i] = i & 0xFF; + } + + buflen = sizeof(buf); + + for (i = 1; i < sizeof(data); i++) { + int len = build_hostname(buf, buflen, data, i, topdomain, get_base32_encoder(), sizeof(buf)); + + fail_if(len > i); + fail_if(strstr(buf, ".."), "Found double dots when encoding data len %d! buf: %s", i, buf); + } +} +END_TEST + +TCase * +test_encoding_create_tests() +{ + TCase *tc; + + tc = tcase_create("Encoding"); + tcase_add_loop_test(tc, test_inline_dotify, 0, TUPLES); + tcase_add_loop_test(tc, test_inline_undotify, 0, TUPLES); + tcase_add_test(tc, test_build_hostname); + + return tc; +} diff --git a/iodine/tests/fw_query.c b/iodine/tests/fw_query.c new file mode 100644 index 0000000..6d23924 --- /dev/null +++ b/iodine/tests/fw_query.c @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2006-2009 Erik Ekman + * + * 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 + +#include "fw_query.h" +#include "test.h" + +START_TEST(test_fw_query_simple) +{ + struct fw_query q; + struct fw_query *qp; + + q.addrlen = 33; + q.id = 0x848A; + + fw_query_init(); + + /* Test empty cache */ + fw_query_get(0x848A, &qp); + fail_unless(qp == NULL); + + fw_query_put(&q); + + /* Test cache with one entry */ + fw_query_get(0x848A, &qp); + fail_unless(qp->addrlen == q.addrlen); + fail_unless(qp->id == q.id); +} +END_TEST + +START_TEST(test_fw_query_edge) +{ + struct fw_query q; + struct fw_query *qp; + int i; + + fw_query_init(); + + q.addrlen = 33; + q.id = 0x848A; + fw_query_put(&q); + + for (i = 1; i < FW_QUERY_CACHE_SIZE; i++) { + q.addrlen++; + q.id++; + fw_query_put(&q); + } + + /* The query should still be cached */ + fw_query_get(0x848A, &qp); + fail_unless(qp->addrlen == 33); + fail_unless(qp->id == 0x848A); + + q.addrlen++; + q.id++; + fw_query_put(&q); + + /* but now it is overwritten */ + fw_query_get(0x848A, &qp); + fail_unless(qp == NULL); +} +END_TEST + +TCase * +test_fw_query_create_tests() +{ + TCase *tc; + + tc = tcase_create("Forwarded query"); + tcase_add_test(tc, test_fw_query_simple); + tcase_add_test(tc, test_fw_query_edge); + + return tc; +} diff --git a/iodine/tests/login.c b/iodine/tests/login.c new file mode 100644 index 0000000..1ef23f4 --- /dev/null +++ b/iodine/tests/login.c @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include + +#include "test.h" +#include "login.h" + +START_TEST(test_login_hash) +{ + char ans[16]; + char good[] = "\x2A\x8A\x12\xB4\xE0\x42\xEE\xAB\xD0\x19\x17\x1E\x44\xA0\x88\xCD"; + char pass[32] = "iodine is the shit"; + int len; + int seed; + + len = sizeof(ans); + seed = 15; + + memset(ans, 0, sizeof(ans)); + login_calculate(ans, len, pass, seed); + fail_unless(strncmp(ans, good, len) == 0, NULL); +} +END_TEST + +START_TEST(test_login_hash_short) +{ + char ans[8]; + char check[sizeof(ans)]; + char pass[32] = "iodine is the shit"; + int len; + int seed; + + len = sizeof(ans); + seed = 15; + + memset(ans, 0, sizeof(ans)); + memset(check, 0, sizeof(check)); + + /* If len < 16, it should do nothing */ + login_calculate(ans, len, pass, seed); + fail_if(memcmp(ans, check, sizeof(ans))); +} +END_TEST + +TCase * +test_login_create_tests() +{ + TCase *tc; + + tc = tcase_create("Login"); + tcase_add_test(tc, test_login_hash); + tcase_add_test(tc, test_login_hash_short); + + return tc; +} diff --git a/iodine/tests/read.c b/iodine/tests/read.c new file mode 100644 index 0000000..18cc29c --- /dev/null +++ b/iodine/tests/read.c @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#ifdef DARWIN +#include +#endif +#include +#include +#include +#include +#include + +#include "common.h" +#include "encoding.h" +#include "dns.h" +#include "read.h" +#include "test.h" + +START_TEST(test_read_putshort) +{ + unsigned short k; + unsigned short l; + char* p; + int i; + + for (i = 0; i < 65536; i++) { + p = (char*)&k; + putshort(&p, i); + fail_unless(ntohs(k) == i, + "Bad value on putshort for %d: %d != %d", + i, ntohs(k), i); + + p = (char*)&k; + readshort(NULL, &p, (short *) &l); + fail_unless(l == i, + "Bad value on readshort for %d: %d != %d", + i, l, i); + } +} +END_TEST + +START_TEST(test_read_putlong) +{ + uint32_t k; + uint32_t l; + char* p; + int i; + int j; + + for (i = 0; i < 32; i++) { + p = (char*)&k; + j = 0xf << i; + + putlong(&p, j); + + fail_unless(ntohl(k) == j, + "Bad value on putlong for %d: %d != %d", i, ntohl(j), j); + + p = (char*)&k; + readlong(NULL, &p, &l); + + fail_unless(l == j, + "Bad value on readlong for %d: %d != %d", i, l, j); + } +} +END_TEST + +START_TEST(test_read_name_empty_loop) +{ + unsigned char emptyloop[] = { + 'A', 'A', 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01 }; + char buf[1024]; + char *data; + int rv; + + memset(buf, 0, sizeof(buf)); + data = (char*) emptyloop + sizeof(HEADER); + buf[1023] = 'A'; + rv = readname((char *) emptyloop, sizeof(emptyloop), &data, buf, 1023); + fail_unless(buf[1023] == 'A'); +} +END_TEST + +START_TEST(test_read_name_inf_loop) +{ + unsigned char infloop[] = { + 'A', 'A', 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 'A', 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01 }; + char buf[1024]; + char *data; + int rv; + + memset(buf, 0, sizeof(buf)); + data = (char*) infloop + sizeof(HEADER); + buf[4] = '\a'; + rv = readname((char*) infloop, sizeof(infloop), &data, buf, 4); + fail_unless(buf[4] == '\a'); +} +END_TEST + +START_TEST(test_read_name_longname) +{ + unsigned char longname[] = + "AA\x81\x80\x00\x01\x00\x00\x00\x00\x00\x00" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x3FzBCDEFGHIJKLMNOPQURSTUVXYZ0123456789abcdefghijklmnopqrstuvxyzAA" + "\x00\x00\x01\x00\x01"; + char buf[1024]; + char *data; + int rv; + + memset(buf, 0, sizeof(buf)); + data = (char*) longname + sizeof(HEADER); + buf[256] = '\a'; + rv = readname((char*) longname, sizeof(longname), &data, buf, 256); + fail_unless(buf[256] == '\a'); +} +END_TEST + +START_TEST(test_read_name_onejump) +{ + unsigned char onejump[] = + "AA\x81\x80\x00\x01\x00\x00\x00\x00\x00\x00" + "\x02hh\xc0\x15\x00\x01\x00\x01\x05zBCDE\x00"; + char buf[1024]; + char *data; + int rv; + + memset(buf, 0, sizeof(buf)); + data = (char*) onejump + sizeof(HEADER); + rv = readname((char*) onejump, sizeof(onejump), &data, buf, 256); + fail_unless(rv == 9); +} +END_TEST + +START_TEST(test_read_name_badjump_start) +{ + unsigned char badjump[] = { + 'A', 'A', 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xfe, 0xcc, 0x00, 0x01, 0x00, 0x01 }; + unsigned char *jumper; + char buf[1024]; + char *data; + int rv; + + /* This test uses malloc to cause segfault if jump is executed */ + memset(buf, 0, sizeof(buf)); + jumper = malloc(sizeof(badjump)); + if (jumper) { + memcpy(jumper, badjump, sizeof(badjump)); + data = (char*) jumper + sizeof(HEADER); + rv = readname((char*) jumper, sizeof(badjump), &data, buf, 256); + + fail_unless(rv == 0); + fail_unless(buf[0] == 0); + } + free(jumper); +} +END_TEST + +START_TEST(test_read_name_badjump_second) +{ + unsigned char badjump2[] = { + 'A', 'A', 0x81, 0x80, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 'B', 'A', 0xfe, 0xcc, 0x00, 0x01, 0x00, 0x01 }; + unsigned char *jumper; + char buf[1024]; + char *data; + int rv; + + /* This test uses malloc to cause segfault if jump is executed */ + memset(buf, 0, sizeof(buf)); + jumper = malloc(sizeof(badjump2)); + if (jumper) { + memcpy(jumper, badjump2, sizeof(badjump2)); + data = (char*) jumper + sizeof(HEADER); + rv = readname((char*) jumper, sizeof(badjump2), &data, buf, 256); + + fail_unless(rv == 4); + fail_unless(strcmp("BA.", buf) == 0, + "buf is not BA: %s", buf); + } + free(jumper); +} +END_TEST + +START_TEST(test_putname) +{ + char out[] = "\x06" "BADGER\x06" "BADGER\x04" "KRYO\x02" "SE\x00"; + char buf[256]; + char *domain = "BADGER.BADGER.KRYO.SE"; + char *b; + int len; + int ret; + + len = 256; + + memset(buf, 0, 256); + b = buf; + ret = putname(&b, 256, domain); + + fail_unless(ret == strlen(domain) + 1); + fail_unless(strncmp(buf, out, ret) == 0, "Happy flow failed"); +} +END_TEST + +START_TEST(test_putname_nodot) +{ + char buf[256]; + char *nodot = + "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ" + "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ"; + char *b; + int len; + int ret; + + len = 256; + + memset(buf, 0, 256); + b = buf; + ret = putname(&b, 256, nodot); + + fail_unless(ret == -1); + fail_unless(b == buf); +} +END_TEST + +START_TEST(test_putname_toolong) +{ + char buf[256]; + char *toolong = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ." + "ABCDEFGHIJKLMNOPQRSTUVWXYZ.ABCDEFGHIJKLMNOPQRSTUVWXYZ."; + char *b; + int len; + int ret; + + len = 256; + + memset(buf, 0, 256); + b = buf; + ret = putname(&b, 256, toolong); + + fail_unless(ret == -1); + fail_unless(b == buf); +} +END_TEST + + +TCase * +test_read_create_tests() +{ + TCase *tc; + + tc = tcase_create("Read"); + tcase_set_timeout(tc, 60); + tcase_add_test(tc, test_read_putshort); + tcase_add_test(tc, test_read_putlong); + tcase_add_test(tc, test_read_name_empty_loop); + tcase_add_test(tc, test_read_name_inf_loop); + tcase_add_test(tc, test_read_name_longname); + tcase_add_test(tc, test_read_name_onejump); + tcase_add_test(tc, test_read_name_badjump_start); + tcase_add_test(tc, test_read_name_badjump_second); + tcase_add_test(tc, test_putname); + tcase_add_test(tc, test_putname_nodot); + tcase_add_test(tc, test_putname_toolong); + + return tc; +} diff --git a/iodine/tests/test.c b/iodine/tests/test.c new file mode 100644 index 0000000..5bee9d2 --- /dev/null +++ b/iodine/tests/test.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include + +#include "test.h" + +int +main() +{ + SRunner *runner; + Suite *iodine; + TCase *test; + int failed; + + iodine = suite_create("iodine"); + + 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); + + test = test_encoding_create_tests(); + suite_add_tcase(iodine, test); + + test = test_read_create_tests(); + suite_add_tcase(iodine, test); + + test = test_login_create_tests(); + suite_add_tcase(iodine, test); + + test = test_user_create_tests(); + suite_add_tcase(iodine, test); + + test = test_fw_query_create_tests(); + suite_add_tcase(iodine, test); + + runner = srunner_create(iodine); + srunner_run_all(runner, CK_NORMAL); + failed = srunner_ntests_failed(runner); + + srunner_free(runner); + + return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/iodine/tests/test.h b/iodine/tests/test.h new file mode 100644 index 0000000..1022a9e --- /dev/null +++ b/iodine/tests/test.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 __TEST_H__ +#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(); +TCase *test_login_create_tests(); +TCase *test_user_create_tests(); +TCase *test_fw_query_create_tests(); + +char *va_str(const char *, ...); + +#if (CHECK_MAJOR_VERSION == 0 && \ + ((CHECK_MINOR_VERSION == 9 && CHECK_MICRO_VERSION < 2) || \ + (CHECK_MINOR_VERSION < 9))) +#define tcase_set_timeout(...) +#endif + +#endif diff --git a/iodine/tests/user.c b/iodine/tests/user.c new file mode 100644 index 0000000..afd61ca --- /dev/null +++ b/iodine/tests/user.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2006-2009 Bjorn Andersson , Erik Ekman + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "encoding.h" +#include "user.h" +#include "test.h" + +START_TEST(test_init_users) +{ + in_addr_t ip; + char givenip[16]; + int i; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 27); + for (i = 0; i < USERS; i++) { + fail_unless(users[i].id == i); + fail_unless(users[i].q.id == 0); + fail_unless(users[i].inpacket.len == 0); + fail_unless(users[i].outpacket.len == 0); + snprintf(givenip, sizeof(givenip), "127.0.0.%d", i + 2); + fail_unless(users[i].tun_ip == inet_addr(givenip)); + } +} +END_TEST + +START_TEST(test_users_waiting) +{ + in_addr_t ip; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 27); + + fail_unless(users_waiting_on_reply() == 0); + + users[3].active = 1; + + fail_unless(users_waiting_on_reply() == 0); + + users[3].last_pkt = time(NULL); + + fail_unless(users_waiting_on_reply() == 0); + + users[3].conn = CONN_DNS_NULL; + users[3].q.id = 1; + + fail_unless(users_waiting_on_reply() == 1); +} +END_TEST + +START_TEST(test_find_user_by_ip) +{ + in_addr_t ip; + unsigned int testip; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 27); + users[0].conn = CONN_DNS_NULL; + + testip = (unsigned int) inet_addr("10.0.0.1"); + fail_unless(find_user_by_ip(testip) == -1); + + testip = (unsigned int) inet_addr("127.0.0.2"); + fail_unless(find_user_by_ip(testip) == -1); + + users[0].active = 1; + + testip = (unsigned int) inet_addr("127.0.0.2"); + fail_unless(find_user_by_ip(testip) == -1); + + users[0].last_pkt = time(NULL); + + testip = (unsigned int) inet_addr("127.0.0.2"); + fail_unless(find_user_by_ip(testip) == 0); +} +END_TEST + +START_TEST(test_all_users_waiting_to_send) +{ + in_addr_t ip; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 27); + + fail_unless(all_users_waiting_to_send() == 1); + + users[0].conn = CONN_DNS_NULL; + users[0].active = 1; + + fail_unless(all_users_waiting_to_send() == 1); + + users[0].last_pkt = time(NULL); + users[0].outpacket.len = 0; + + fail_unless(all_users_waiting_to_send() == 0); + +#ifdef OUTPACKETQ_LEN + users[0].outpacketq_filled = 1; +#else + users[0].outpacket.len = 44; +#endif + + fail_unless(all_users_waiting_to_send() == 1); +} +END_TEST + +START_TEST(test_find_available_user) +{ + in_addr_t ip; + int i; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 27); + + for (i = 0; i < USERS; i++) { + fail_unless(find_available_user() == i); + } + + for (i = 0; i < USERS; i++) { + fail_unless(find_available_user() == -1); + } + + users[3].active = 0; + + fail_unless(find_available_user() == 3); + fail_unless(find_available_user() == -1); + + users[3].last_pkt = 55; + + fail_unless(find_available_user() == 3); + fail_unless(find_available_user() == -1); +} +END_TEST + +START_TEST(test_find_available_user_small_net) +{ + in_addr_t ip; + int i; + + ip = inet_addr("127.0.0.1"); + init_users(ip, 29); /* this should result in 5 enabled users */ + + for (i = 0; i < 5; i++) { + fail_unless(find_available_user() == i); + } + + for (i = 0; i < USERS; i++) { + fail_unless(find_available_user() == -1); + } + + users[3].active = 0; + + fail_unless(find_available_user() == 3); + fail_unless(find_available_user() == -1); + + users[3].last_pkt = 55; + + fail_unless(find_available_user() == 3); + fail_unless(find_available_user() == -1); +} +END_TEST + +TCase * +test_user_create_tests() +{ + TCase *tc; + + tc = tcase_create("User"); + tcase_add_test(tc, test_init_users); + tcase_add_test(tc, test_users_waiting); + tcase_add_test(tc, test_find_user_by_ip); + tcase_add_test(tc, test_all_users_waiting_to_send); + tcase_add_test(tc, test_find_available_user); + tcase_add_test(tc, test_find_available_user_small_net); + + return tc; +}