diff --git a/COPYING b/COPYING index 74fe22a..85c729f 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 1998-2010 Ivo Timmermans, Guus Sliepen and others. +Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others. See the AUTHORS file for a complete list. This program is free software; you can redistribute it and/or modify it under diff --git a/ChangeLog b/ChangeLog index 859f691..831cb4e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,1953 @@ +commit 0db9e471ea53b48687ea247c855cd95ec453530c +Author: Guus Sliepen +Date: Sun Oct 14 19:22:30 2012 +0200 + + Releasing 1.1pre3. + +commit 3254e75afe0ff28fed68d8682f61c184f442161d +Author: Guus Sliepen +Date: Sun Oct 14 19:21:13 2012 +0200 + + Fix a few compiler errors/warnings. + +commit 70a1a5594af5d4e6a364186b42ba4e34c676009b +Author: Guus Sliepen +Date: Sun Oct 14 17:42:49 2012 +0200 + + Update copyright notices. + +commit 4200a378c4fedf64e89b9f8481d7cd09dac14965 +Author: Guus Sliepen +Date: Sun Oct 14 16:39:16 2012 +0200 + + Fix compile error on Windows. + +commit 368727c3dac4a1f8343e2e0eccf5bc62d9b197e2 +Author: Guus Sliepen +Date: Sun Oct 14 16:07:35 2012 +0200 + + tincctl: add node colors and edge weight to graph dump. + +commit 40ed0c07dd3d4667054b0f5952b89ee39686493b +Author: Guus Sliepen +Date: Sun Oct 14 15:37:24 2012 +0200 + + Log more messages using logger(). + +commit b234304b6628aeddce63d7f751da97c3344bbb78 +Author: Guus Sliepen +Date: Sun Oct 14 14:48:35 2012 +0200 + + Make sure the ReplayWindow option works for SPTPS as well. + +commit ee1d655f2f1ede6da66b6268974d6f9585c616b3 +Author: Guus Sliepen +Date: Sun Oct 14 14:45:27 2012 +0200 + + Only log success of initial datagram SPTPS handshake. + +commit 44a24f63acc70d19904e5540986b8301b3c9b882 +Author: Guus Sliepen +Date: Sun Oct 14 14:33:54 2012 +0200 + + Fix handling of initial datagram SPTPS packet. + + Only the very first packet of an SPTPS session should be send with REQ_KEY, + this signals the peer to abort any previous session and start a new one as + well. + +commit ec1f7e525d046bcaeb8e7040b8cec9a34a568371 +Author: Sven-Haegar Koch +Date: Fri Oct 12 17:08:01 2012 +0200 + + sptps.c: Add missing newline to log message. + +commit 94ec8d34db0ddef14b5446975663e5ff37e27b45 +Author: Guus Sliepen +Date: Thu Oct 11 22:47:13 2012 +0200 + + Strip newline from incoming SPTPS requests. + + Most of the code doesn't care whether requests are terminated with a newline or + not, except that when requests are forwarded, it is assumed they do not have + one and a newline is added. When a node using SPTPS receives a request from + another SPTPS-using node, and forwards it to a non-SPTPS-using node, this will + result in two consecutive newlines, which the latter node will see as an empty, + and thus invalid, request. + +commit 45944e4514a7765f858fa33cc1d9719a603099e0 +Author: Guus Sliepen +Date: Thu Oct 11 22:21:30 2012 +0200 + + Clear status and options fields of unreachable nodes. + +commit d917c8cb6b69475d568ccbe82389b9f2b3eb5e80 +Author: Guus Sliepen +Date: Wed Oct 10 17:17:49 2012 +0200 + + Fix whitespace. + +commit 58f4b845b9a7d83739af77337f2ce263d8df7838 +Author: Guus Sliepen +Date: Wed Oct 10 14:46:22 2012 +0200 + + Try all known addresses of node during the PMTU discovery phase. + + This helps in situations where some nodes have IPv6 and others have not. + +commit 0ed0cc6f9c30537bd74222fd99a41726d488dd37 +Author: Guus Sliepen +Date: Tue Oct 9 17:49:09 2012 +0200 + + Fix hash functions for keys whose size is not divisible by 4. + +commit d1ec010660905ae0b99d783737350ccc08b37b16 +Author: Guus Sliepen +Date: Tue Oct 9 16:27:28 2012 +0200 + + Fix memory leaks found by valgrind. + +commit 72642b40b3ad476101622da202b6f977a32b472f +Author: Guus Sliepen +Date: Tue Oct 9 15:52:58 2012 +0200 + + Clear Ethernet header when reading packets from a tun device. + + This fixes a warning from valgrind about uninitialized bytes, which were being + sent to other nodes. + +commit b346338f9c2de6f71d87cb4ad8e61b0af0052688 +Author: Guus Sliepen +Date: Tue Oct 9 13:28:09 2012 +0200 + + Remove unused variables, fix some #includes. + +commit f62b4a91344bd0de09e7fb4e4c8c1993ffc027c3 +Author: Guus Sliepen +Date: Tue Oct 9 13:23:12 2012 +0200 + + Fix deleting connections from the connection list. + +commit 0b8b23e0dd7219344543f135ca0aeba8a4a42d48 +Author: Guus Sliepen +Date: Mon Oct 8 00:35:38 2012 +0200 + + C99 extravaganza. + +commit ff306f0cdaedb50de1472e7c1fb55de922a6ca60 +Author: Guus Sliepen +Date: Sun Oct 7 21:59:53 2012 +0200 + + Replace the connection_tree with a connection_list. + + The tree functions were never used on the connection_tree, a list is more appropriate. + Also be more paranoid about connections disappearing while traversing the list. + +commit ce059e36fdb3d3049c278e8b2f36b03c93778996 +Author: Guus Sliepen +Date: Sun Oct 7 21:02:40 2012 +0200 + + Refactor outgoing connection handling. + + Struct outgoing_ts and connection_ts were depending too much on each other, + causing lots of problems, especially the reuse of a connection_t. Now, whenever + a connection is closed it is immediately removed from the list of connections + and destroyed. + +commit d93a37928b75b17ac5e1eae5c2d62fd0760a6608 +Author: Guus Sliepen +Date: Sun Oct 7 17:53:23 2012 +0200 + + Fix warnings from cppcheck. + +commit 5d0812d49275ec8bda2b5b0ac813239045463777 +Author: Guus Sliepen +Date: Sun Oct 7 14:06:47 2012 +0200 + + Remove a debug message. + +commit c2a9ed9e98e3dc4218c74fff774ddfe654adfd72 +Author: Guus Sliepen +Date: Sun Oct 7 14:03:50 2012 +0200 + + Handle packets encrypted via SPTPS that need to be forwarded via TCP. + +commit bb6b97ce3493d49b79f1bd57fdac420c312ef8d6 +Author: Guus Sliepen +Date: Sun Oct 7 13:31:19 2012 +0200 + + Make datagram SPTPS key exchange more robust. + + Similar to old style key exchange requests, keep track of whether a key + exchange is already in progress and how long it took. If no key is known yet + or if key exchange takes too long, (re)start a new key exchange. + +commit b99af2f813b897e1fd49c87a7cf44241cad3a017 +Author: Guus Sliepen +Date: Sun Oct 7 11:45:54 2012 +0200 + + Useful error messages when writing to a meta connection fails. + +commit e05371346548dee977d4ee45e12e3058e749afb6 +Author: Guus Sliepen +Date: Sat Oct 6 21:16:17 2012 +0200 + + When terminating, keep control connections open until the end. + + This ensures all device files and listening sockets have been closed before + tincctl gets notified of tincd's termination. + +commit 86116bb022f0b885638ff9ba21b359fc9f55286a +Author: Guus Sliepen +Date: Sat Oct 6 21:15:19 2012 +0200 + + Clear connection options and status fields in free_connection_partially(). + + Most fields should be zero when reusing a connection. In particular, when an + outgoing connection to a node which is reachable on more than one address is + made, the second connection to that node will have status.encryptout set but + outctx will be NULL, causing a NULL pointer dereference when + EVP_EncryptUpdate() is called in send_meta() when it shouldn't. + +commit ef9358c0d616c5ff3391c8ec3da5d357286a4457 +Author: Guus Sliepen +Date: Sat Oct 6 17:45:03 2012 +0200 + + Improve starting/stopping tincd using tincctl. + + When starting tincd, tincctl now strips non-options from the command line, and + sets argv[0] to the name of the tincd command instead of copying its own + command name. + + When stopping a running tincd, tincctl now waits for it to terminate. + +commit 47f33e07ff90b557cfa96999e921d35ea537ca80 +Author: Guus Sliepen +Date: Sat Oct 6 16:53:43 2012 +0200 + + Fix off-by-one error. + + Apart from writing 1 byte beyond an array allocated on the stack, this slipped + an unitialized byte in the seed used for key generation. + +commit 20b441a6de743b2149df59cfb94a7663e1924fa3 +Author: Guus Sliepen +Date: Mon Oct 1 10:42:13 2012 +0200 + + Libreadline might depend on libcurses. + +commit 3887e6dcb54494ee11798e721e274e06b0a5621a +Author: Guus Sliepen +Date: Mon Oct 1 10:39:15 2012 +0200 + + Remove abort() call that accidentily sneaked into commit dd1b69e. + +commit 0b0949e5bb63f9545feb4714812e2aa2112fb092 +Author: Guus Sliepen +Date: Mon Oct 1 10:36:23 2012 +0200 + + Make sure sptps_test compiles without -flto. + +commit b381acd60dbadbb4bc679d35a7d86bf425f21f86 +Author: Guus Sliepen +Date: Sun Sep 30 23:12:43 2012 +0200 + + Remove unused function declaration. + +commit dd1b69e31f83e2cc200ecc10e6d927373823332b +Author: Guus Sliepen +Date: Sun Sep 30 22:43:48 2012 +0200 + + Fix not reading Port statement from host config file. + +commit 6dfdb323612184529b4b83c1be914dda8262de47 +Merge: 9e76c46 c4940a5 +Author: Guus Sliepen +Date: Sun Sep 30 15:00:47 2012 +0200 + + Merge branch 'master' into 1.1 + + Conflicts: + lib/utils.c + src/net_setup.c + src/process.c + src/protocol_auth.c + src/protocol_key.c + src/utils.h + +commit c4940a5c888d85b4c477b6face5e9a618e64718d +Author: Guus Sliepen +Date: Sun Sep 30 13:45:47 2012 +0200 + + Add strict checks to hex to binary conversions. + + The main goal is to catch misuse of the obsolete PrivateKey and PublicKey + statements. + +commit 3bd810ea79d6933839ddac4a2cf1445c51947d38 +Author: Guus Sliepen +Date: Sun Sep 30 13:45:39 2012 +0200 + + Attribution for Martin Schürrer. + +commit 5a161e86cf35351f5274d7a8e17fef4630b40686 +Author: Martin Schürrer +Date: Sun Sep 30 02:04:55 2012 +0200 + + Output details of encryption errors + +commit 9e76c464b26b066e1eb3aa5232e573792e28020d +Author: Guus Sliepen +Date: Fri Sep 28 17:51:48 2012 +0200 + + Remove some debugging messages. + +commit e971130b601064090815c31c90b876e3d0d1d5b1 +Author: Guus Sliepen +Date: Fri Sep 28 17:36:25 2012 +0200 + + Make tincctl robust against dropped control connections. + +commit c5325ffdd1c6749beaf842c272eb28ecd5a070b6 +Author: Guus Sliepen +Date: Fri Sep 28 17:05:01 2012 +0200 + + Correctly add/remove outgoing connections when reloading configuration. + +commit f417271ea1447589ea05901f54fbb0377e7afaf9 +Author: Guus Sliepen +Date: Fri Sep 28 17:03:14 2012 +0200 + + Fix column sorting, make all lists sortable. + +commit aee86011ff2d389832fc9a23081ea23ab8484607 +Author: Guus Sliepen +Date: Thu Sep 27 22:12:15 2012 +0200 + + Let the GUI handle the new dump format. + +commit fac5593f44e47f3bd4f4b425ada38ab49fbe3b42 +Author: Guus Sliepen +Date: Thu Sep 27 17:19:02 2012 +0200 + + Fix links in documenation. + +commit 2e09986a1fd6dc5b6313f10e5d86aaaf4a531235 +Author: Guus Sliepen +Date: Thu Sep 27 17:18:49 2012 +0200 + + Fix links in documentation. + +commit f70cbc9d3ee3a88cf956592007e57f7a1dde2c17 +Author: Guus Sliepen +Date: Thu Sep 27 15:45:02 2012 +0200 + + Comment out old public/private keys when generating new ones. + +commit 38dbc63f118dbfdb955b56740b8c20a9379fb3ba +Author: Guus Sliepen +Date: Wed Sep 26 23:56:21 2012 +0200 + + Update documentation of the "dump graph" command. + +commit 1f312137d5ab12a2d996d5f7972f169aeb852040 +Author: Guus Sliepen +Date: Wed Sep 26 23:52:36 2012 +0200 + + Allow dumping either directed or undirected graphs. + + Internally, tinc maintains a directed graph of the meta connections between + nodes. However, this causes graphviz to draw two lines between nodes, which is + not always desirable. The "dump graph" command now defaults to dumping an + undirected graph, the "dump digraph" command will dump a directed graph. + +commit d6388d782ede1bbe49a5c2643362e2e0f383fa89 +Author: Guus Sliepen +Date: Wed Sep 26 23:18:32 2012 +0200 + + Let tincctl parse and format dumps. + + At the moment it just reproduces the old format. + +commit 9ade39b7d5564fb6f5a41946c9a23cfa7851a19f +Author: Guus Sliepen +Date: Wed Sep 26 22:20:43 2012 +0200 + + Keep last known address and time since reachability changed. + + This allows tincctl info to show since when a node is online or offline. + +commit 1e5deec973cd366b9d9cec6c1314a97e7051ce0f +Author: Guus Sliepen +Date: Tue Sep 25 22:28:08 2012 +0200 + + Remove remnants of Ethertap and old TUNSETIFF ioctl(). + +commit 125dd0dbcf4f46033ead3486044eb00b413fe537 +Author: Guus Sliepen +Date: Tue Sep 25 22:12:36 2012 +0200 + + Fix typo in manpage. + +commit 72f08932cf6f1ac0cfb837d377b423207e8c671a +Author: Guus Sliepen +Date: Mon Sep 24 14:56:00 2012 +0200 + + Don't ignore Makefile.am. + +commit 66e702d90d83977dc089736d7e4146330bc5df28 +Author: Guus Sliepen +Date: Mon Sep 24 14:02:07 2012 +0200 + + Attribution for Vil Brekin and some code style cleanups. + +commit f421a640777bd9484c59fa6feacadcf3e05d4b44 +Author: Vilbrekin +Date: Sat Aug 25 20:32:38 2012 +0200 + + Android cross-compilation instructions. + +commit afe4bf62eccab76c75e5a661fb2c16f1391a8417 +Author: Vilbrekin +Date: Sat Aug 25 20:01:11 2012 +0200 + + Use __ANDROID__ define rather than dirty hard-code to allow android NDK cross-compilation. + +commit c6720f1a608d19c722d8601fab1048773dbad59b +Author: Vilbrekin +Date: Sat Aug 25 19:59:26 2012 +0200 + + Add basic .gitignore file, cleaning (most) files generated by autotools. + +commit f2570c1b7f5813e087c867cf002f36f0c09b5cfa +Author: Vilbrekin +Date: Sat Aug 25 19:14:00 2012 +0200 + + Replace hard-code with new ScriptsInterpreter configuration property. + + This new setting allows choosing a custom script interpreter used for the various tinc callbacks. + If none is specified, the script itself is called as executable (as before). + This is particularly useful when storing tinc configuration and script on a mount point with no-exec attribute. + +commit 8a6f278fd2606c0a8f133f05df83b2649eacf6c3 +Author: Vilbrekin +Date: Wed Aug 22 10:46:24 2012 +0200 + + Basic patch for android cross-compilation. + + Commented non-existing functions in android NDK. + Prefix scripts execution with shell binary to allow execution on no-exec mount points. + Everyything is currently hard coded, while it should use pre-compiler variables... + +commit 2dc8deb1047a076d1c040f47bedf36ad4b41b17c +Author: Guus Sliepen +Date: Thu Sep 13 21:35:29 2012 +0200 + + Ensure sptps_test compiles with -flto. + +commit 90f1cba1fd9e748ec4b8274511d5a36ec1a24d9d +Author: Guus Sliepen +Date: Wed Sep 5 13:05:48 2012 +0200 + + Replace node_udp_tree with a hash table. + +commit 4c05afd19acada4781e1b8865cf702b197882e5d +Author: Guus Sliepen +Date: Wed Sep 5 12:45:36 2012 +0200 + + Use hash tables to lookup owners of addresses. + +commit 6b6a025488f289f749498a7e6cc1994be19f53e8 +Author: Guus Sliepen +Date: Wed Sep 5 12:44:41 2012 +0200 + + Add a simple hash table implementation. + +commit e9de08be0dab58a48f9a8ce3d250516cf05d6b8e +Author: Guus Sliepen +Date: Tue Sep 4 14:21:50 2012 +0200 + + Remove newlines at end of log messages. + +commit 05dac63dbc03dc5a64a7f4b50e24eb3766135916 +Author: Guus Sliepen +Date: Tue Sep 4 14:16:05 2012 +0200 + + Remove some debug messages. + +commit 742f7bb04e72d93f2c4a858534144a599b3fc14d +Author: Guus Sliepen +Date: Thu Aug 30 14:21:23 2012 +0200 + + Properly handle SPTPS packets with stripped Ethernet headers. + +commit d74b81b61e87c66d364a8590a48d87773ad2652c +Author: Guus Sliepen +Date: Thu Aug 30 14:00:34 2012 +0200 + + Fix node name check for "connect" and "disconnect" commands. + +commit 5567c0d4107e6ff6f4639d8664651841bd59ddad +Author: Guus Sliepen +Date: Sun Aug 5 17:25:31 2012 +0200 + + Quit when "exit" or "quit" commands are used in tincctl's shell. + +commit d18519ae21345fea68dd7f0f5525adba3a7639a9 +Author: Guus Sliepen +Date: Sun Aug 5 17:03:57 2012 +0200 + + Fix segfault when using tincctl's shell without readline. + +commit b332bd964663b7109a5fc4be596d36fbf1dbaa47 +Author: Guus Sliepen +Date: Sun Aug 5 13:50:51 2012 +0200 + + Add bash completion script. + +commit e29e0fee8812851473bcf24324a15cbf3cc854a0 +Author: Guus Sliepen +Date: Fri Aug 3 14:17:02 2012 +0200 + + Make sure the top command can be used more than once in tincctl's shell. + +commit a57db1dfe0736fd902a45ed5f695630faf3f0e1e +Author: Guus Sliepen +Date: Fri Aug 3 14:15:50 2012 +0200 + + Fork when using the "start" command in tincctl. + + This allows the command to be given in its shell without immediatly exiting tincctl. + +commit 36c6afede36b6956bd86df824f5616c1afee35ed +Author: Guus Sliepen +Date: Fri Aug 3 13:23:07 2012 +0200 + + Add readline completion for tincctl config and tincctl info. + +commit 8af2f3f5a4061a8dbfd4f7d259e0038df06a373e +Author: Guus Sliepen +Date: Thu Aug 2 17:44:59 2012 +0200 + + Optionally compress and/or strip Ethernet header from SPTPS packets. + +commit 73348be58ecb9c40cf435122a00e72ac4d1a4c9b +Author: Guus Sliepen +Date: Thu Aug 2 17:24:42 2012 +0200 + + Have tincctl act as a shell when no command is given. + + By default it uses readline to read commands. If the input and output are not a + tty, no prompt is shown. + +commit 91937812bdfe74699e4f7cdf86265d07423acbba +Author: Guus Sliepen +Date: Thu Aug 2 17:23:51 2012 +0200 + + Clear struct sptps before reusing it. + +commit 6bcd03c2027636f82ab7228566717d112df7bc6d +Author: Guus Sliepen +Date: Wed Aug 1 22:22:52 2012 +0200 + + Update the documentation to encourage using "tincctl init" and "tincctl config". + +commit 6396f42d74f22ab5f8e736dc5cb04c57917f9319 +Author: Guus Sliepen +Date: Wed Aug 1 16:51:59 2012 +0200 + + Stricter checks for netname and node names. + + - Node names should not be empty. + - Net names should not contain slashes or start with a dot, because they are + used in pathnames. + +commit 61006ced88e1bf62e8883216cabc636f2d4cb12a +Author: Guus Sliepen +Date: Wed Aug 1 16:13:23 2012 +0200 + + Add missing configuration variables. + +commit b0f3a76e9bf8ceeab75c1e6f4dce6763aecddc5e +Author: Guus Sliepen +Date: Wed Aug 1 15:50:45 2012 +0200 + + Add the ability to query configuration variables to tincctl. + +commit a9caa2a6ea3aa553c9d2140ad4f5b34b7ab7297b +Author: Guus Sliepen +Date: Wed Aug 1 15:15:37 2012 +0200 + + tincctl restart should work even if no tincd is running. + +commit 07980b056c5371f8b6fdd50172f501be07155bdf +Author: Guus Sliepen +Date: Wed Aug 1 15:14:48 2012 +0200 + + Try sending SIGTERM if we cannot connect to a tincd but we know its PID. + +commit 7a71d48009e03ff1143a6e1084803f456a27c849 +Author: Guus Sliepen +Date: Tue Jul 31 21:43:49 2012 +0200 + + Use a status bit to track which nodes use SPTPS. + +commit 6bc8df3e010509f69af95d2cc14ec893def6f644 +Author: Guus Sliepen +Date: Tue Jul 31 20:39:15 2012 +0200 + + Add Brandon Black's replay window code to SPTPS. + +commit 5ede437307cc3bbb20431f4b82f4a2ef79c9b746 +Author: Guus Sliepen +Date: Tue Jul 31 20:36:35 2012 +0200 + + Handle SPTPS datagrams in try_mac(). + +commit aaff0ed08916f936b0a7b8a3d0607b8111b7a185 +Author: Guus Sliepen +Date: Tue Jul 31 20:29:13 2012 +0200 + + Remove unused #include. + +commit 153abaa4d940bf2bc9bd7275d5efe5c01c354190 +Author: Guus Sliepen +Date: Mon Jul 30 18:36:59 2012 +0200 + + Use datagram SPTPS for packet exchange between nodes. + + When two nodes which support SPTPS want to send packets to each other, they now + always use SPTPS. The node initiating the SPTPS session send the first SPTPS + packet via an extended REQ_KEY messages. All other handshake messages are sent + using ANS_KEY messages. This ensures that intermediate nodes using an older + version of tinc can still help with NAT traversal. After the authentication + phase is over, SPTPS packets are sent via UDP, or are encapsulated in extended + REQ_KEY messages instead of PACKET messages. + +commit 248d300f1be0d5f2aae39202041699ab2b46c56b +Merge: e1355e2 3391018 +Author: Guus Sliepen +Date: Fri Jul 27 22:48:24 2012 +0200 + + Merge branch 'master' into 1.1 + +commit 3391018efbd41858d42ccae6ae919749ba94c8db +Author: Guus Sliepen +Date: Fri Jul 27 22:43:01 2012 +0200 + + Also clarify hostnames=[yes|no] in tinc.conf(5). + +commit e895b358db8863d19dfa3d77c861ae19b76bc750 +Author: Mesar Hameed +Date: Tue Jul 24 07:18:50 2012 +0100 + + Minor clarification, tinc.conf hostnames=[yes|no] variable only resolves names for logging purposes. + +commit e1355e24eb7fe36bdb5dd7c818815fa266046a51 +Author: Guus Sliepen +Date: Sun Jul 22 13:05:56 2012 +0200 + + Remove unused po/ directory. + +commit 6c9b33c8b67374d38525b88f292840034c559a45 +Author: Guus Sliepen +Date: Sun Jul 22 12:55:04 2012 +0200 + + Have tinc-gui use same way of locating pidfile as tincd and tincctl. + +commit 2b97a7d7cf6ca7f4d84d3df754062a55bdf55305 +Author: Guus Sliepen +Date: Sun Jul 22 12:52:31 2012 +0200 + + tincctl init now also creates a template tinc-up script. + +commit eb430005c74b6b5f717e7e264afa3bd35284740d +Author: Guus Sliepen +Date: Sat Jul 21 17:10:10 2012 +0200 + + Fix exit code when installing tincd as a service on Windows. + +commit e5e96882c3825cee81ff163490b2f39fad3192b8 +Author: Guus Sliepen +Date: Sat Jul 21 16:33:09 2012 +0200 + + Windows doesn't like quotes around "edit" when calling it through system(). + + Even though that works fine on the command line. + +commit 18237e1f2d9dd5eef4a4e0d746d016bf94a42ad4 +Author: Guus Sliepen +Date: Sat Jul 21 16:26:55 2012 +0200 + + Use backslashes on Windows. + + Although Windows itself supports the forward slash, some programs may not. + +commit 09a8ff649cc7aa51d291c89e1556526a6265cc81 +Author: Guus Sliepen +Date: Sat Jul 21 15:58:16 2012 +0200 + + Don't try to mkdir(CONFDIR) on Windows when there is a registry key for tinc. + +commit ed8ce60845dc0568840c64c692838136f342fa54 +Author: Guus Sliepen +Date: Sat Jul 21 15:51:15 2012 +0200 + + Fix crash when no netname is specified. + +commit 7303b512b0e4f0d9cbc3236e846b2618f527b830 +Author: Guus Sliepen +Date: Sat Jul 21 15:50:50 2012 +0200 + + Fix some compiler warnings. + +commit 33521eabd4501b4add35468618453ac4f76311f3 +Author: Guus Sliepen +Date: Sat Jul 21 15:15:04 2012 +0200 + + Have tincd and tincctl use the same method of determining netname. + +commit 1d322d2eda8223f21b0c00381af34b94054f251a +Author: Guus Sliepen +Date: Sat Jul 21 15:02:44 2012 +0200 + + Add a newline to a configuration file if it is missing. + +commit dea722c4aca9a8cfa463807d279aa10cc6a0fc64 +Author: Guus Sliepen +Date: Sat Jul 21 15:02:17 2012 +0200 + + Add some checks when changing configuration. + +commit cc0c35267f8fac4f82622ff73474ed1e2d3a1e36 +Author: Guus Sliepen +Date: Sat Jul 21 14:19:23 2012 +0200 + + Call event_init() after detaching. + + Otherwise, the call to daemon() could close filedescriptors in use by libevent + itself; for example if it uses kqueue or epoll instead of a select() or poll() + backend. + +commit 4e0fc52197546bbf8a0be7af946f4b569e13048c +Author: Guus Sliepen +Date: Sat Jul 21 13:53:22 2012 +0200 + + Fix various compiler warnings. + +commit b161088b35fad1d284855f6434a895a20e34a250 +Author: Guus Sliepen +Date: Sat Jul 21 13:38:14 2012 +0200 + + BSD make doesn't like $<. + +commit 98a72d686983178f71cd2bf336c1f3d5c647f1e7 +Author: Guus Sliepen +Date: Sat Jul 21 13:02:35 2012 +0200 + + Make sure sptps.h and info.h are in the tarball. + +commit 5eeed38b8eb15f4c0464675b7d8c7722bc8be168 +Author: Guus Sliepen +Date: Sat Jul 21 12:51:53 2012 +0200 + + Make sure tinc compiles on Windows. + +commit 1d4590ca5cae09ea3b7a7e80355639e20861d349 +Author: Guus Sliepen +Date: Fri Jul 20 20:35:07 2012 +0200 + + Prefer routes with lower weight as long as they do not increase the number of hops. + + This should improve traffic to nodes that are not directly reachable somewhat. + +commit 4c8ead98743254be97c830e942f0cc53539d780c +Author: Guus Sliepen +Date: Fri Jul 20 20:01:29 2012 +0200 + + Allow more configuration variables to be changed when reloading configuration. + + In particular, Subnets may be added or removed from the local node on the fly. + +commit c678e7c4fb52d93350eafaed0f666018ed469e10 +Author: Guus Sliepen +Date: Fri Jul 20 19:59:47 2012 +0200 + + Split setup_myself() into two functions, one for reloading configuration. + +commit 4591e96c76914795aaae317c067f16abc22fb2e0 +Author: Guus Sliepen +Date: Fri Jul 20 17:29:16 2012 +0200 + + Never remove items from cmdline_conf. + + We should treat cmdline_conf as const, so we can call read_config_options() + more than once with prefix = NULL. + +commit 68a20876d0c4a6c370064d78786dd9f2aa6273cb +Author: Guus Sliepen +Date: Fri Jul 20 01:02:51 2012 +0200 + + Use minor protocol version to determine whether to use ECDH key exchange between nodes. + +commit 76a3ada4eb4032172c3d780915a07680f9954d42 +Author: Guus Sliepen +Date: Tue Jul 17 18:05:55 2012 +0200 + + Put minor protocol version in connection options so other nodes can see it. + + This allows two nodes that do not have a meta-connection with each other see + which version they are. + +commit 68de7b481e54d6a7c573d9a2d61f76d4d3a6b2f9 +Author: Guus Sliepen +Date: Mon Jul 16 18:49:39 2012 +0200 + + When exporting configuration files, don't copy Name variables. + + These interfere with tincctl import. Besides, host configuration files should + not contain Name at all. + +commit c52c46f8717aac6904f32766d774fa3fdf9611d8 +Author: Guus Sliepen +Date: Mon Jul 16 16:48:24 2012 +0200 + + Add an easy way to export and import host configuration files. + +commit 6319dc9dde3b328ba800f25a6bb4cf303d27f664 +Author: Guus Sliepen +Date: Mon Jul 16 01:14:08 2012 +0200 + + Strip default subnet weight from output. + +commit 74646a4afa6557a0363cc85e0a95d578d4ab0ac2 +Author: Guus Sliepen +Date: Mon Jul 16 01:09:47 2012 +0200 + + Give an error message when tincctl info cannot parse the given subnet or address. + +commit 53735a9d964579829d089f4b7572aef50c4e1468 +Author: Guus Sliepen +Date: Mon Jul 16 01:05:25 2012 +0200 + + "tincctl info" gives more human readable information about nodes or subnets. + +commit 3c7003893fe2f82023d0d4f54b488bb7a16d0007 +Author: Guus Sliepen +Date: Mon Jul 16 00:52:50 2012 +0200 + + Move all functions related to subnet parsing to subnet_parse.c. + +commit e72e6febfeddbd4354560388c8e0e125a8017909 +Author: Guus Sliepen +Date: Sun Jul 15 22:53:03 2012 +0200 + + Fix tincctl dump. + +commit 9be8980a2bb6245da017270f85bd6da186fb433b +Author: Guus Sliepen +Date: Sun Jul 15 21:17:10 2012 +0200 + + Let tincctl ignore tincd options, so they will be passed on. + +commit 36dee4c539521578005eed5e58b4803b73f0c889 +Author: Guus Sliepen +Date: Sun Jul 15 21:15:35 2012 +0200 + + Fix tincctl start. + +commit 439069bda62b25baaabeb765ac0557efa57b6cfb +Author: Guus Sliepen +Date: Sun Jul 15 20:59:17 2012 +0200 + + Have tincctl notify a running tincd of configuration file changes. + +commit eb01fd96258e5f99be0e4930eac04e5487a108a0 +Author: Guus Sliepen +Date: Sun Jul 15 20:37:38 2012 +0200 + + Add an easy way to edit a configuration file. + +commit cedfeccb247abb00063316068d7d2ade880f9d09 +Author: Guus Sliepen +Date: Sun Jul 15 20:22:21 2012 +0200 + + Stricter checks for node names. + +commit 03f72c6173f27198e2e68227cb41e00f8ec4ddc9 +Author: Guus Sliepen +Date: Sun Jul 15 18:16:35 2012 +0200 + + Allow configuration variables to be added/removed using tincctl. + +commit dd102efd24d847c41890adfcc7ce6d9d2592dcdb +Author: Guus Sliepen +Date: Sun Jul 15 15:46:16 2012 +0200 + + Put every command in its own function. + +commit a444ec396456a25546a4ab3d185c7fb5e4bb7ae3 +Author: Guus Sliepen +Date: Sun Jul 15 14:49:36 2012 +0200 + + "tincctl init" creates initial directory structure, tinc.conf and keypairs. + +commit 268c8545aaf83b7433f43402f5c77e39e20006ef +Merge: bce1777 f13fd8c +Author: Guus Sliepen +Date: Sat Jul 14 15:13:21 2012 +0200 + + Merge branch 'master' into 1.1 + +commit f13fd8c35068cd1f776e33362dcac40be9499035 +Author: Guus Sliepen +Date: Thu Jul 12 11:32:08 2012 +0200 + + Update THANKS file. + +commit 2eb0043e1352944b1113c1f7e40f37dffac0021d +Author: Guus Sliepen +Date: Thu Jul 12 11:30:56 2012 +0200 + + Document how to load the tap driver on FreeBSD. + +commit ae8c0b65d8f97942d7eff5f96344f781b8dec35d +Author: Guus Sliepen +Date: Thu Jul 12 11:25:11 2012 +0200 + + Use /dev/tap0 by default on FreeBSD and NetBSD when using Mode = switch. + +commit bce177767d521b47efd458c5cd570959a98d940d +Author: Guus Sliepen +Date: Tue Jun 26 14:22:57 2012 +0200 + + Fix crash when handling the ALRM signal. + + In retry() the function do_outgoing_connection() is called, which can delete + items from the connection_tree, so when walking the tree we must first save the + pointer to the next item. + +commit 19be9cf7150858311f7898fa3fb525d692d02f64 +Merge: 62b61a1 00e71ec +Author: Guus Sliepen +Date: Tue Jun 26 13:24:20 2012 +0200 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + NEWS + README + configure.in + lib/utils.c + src/linux/device.c + src/meta.c + src/net.h + src/net_setup.c + src/net_socket.c + src/protocol.c + src/protocol_auth.c + src/tincd.c + +commit 00e71ece25070dc919f9bc0696e4ff3a387360d0 +Author: Guus Sliepen +Date: Mon Jun 25 19:45:51 2012 +0200 + + Releasing 1.0.19. + +commit 236b0ba4ebba01e22e382e79897100338a039bbb +Author: Guus Sliepen +Date: Mon Jun 25 19:03:54 2012 +0200 + + Fix crash when using Broadcast = direct. + +commit 0a84f9cb8f52f2d2b4f03a5ad5ef9dfcd3509033 +Author: Guus Sliepen +Date: Mon Jun 25 19:01:51 2012 +0200 + + Fix compiler warnings. + +commit 62ee9b776d45af41c8b040ad86e50ba8f6f8e6c4 +Author: Guus Sliepen +Date: Mon Jun 25 15:01:42 2012 +0200 + + #include on Windows. + + MinGW complained about it not being included. + +commit c0af4c37d2046ffb3e07dd62f266a4fb99ea5614 +Author: Guus Sliepen +Date: Mon Jun 25 15:00:24 2012 +0200 + + Small fixes in proxy code. + +commit 62b61a1b7c2382b1bade142b3a41a9b27c1fd40d +Author: Guus Sliepen +Date: Sun May 13 22:16:42 2012 +0200 + + Don't forget to send a newline when forwarding requests. + +commit 42a8158b1dca6ee4ec1707176199cc36c26da7af +Author: Michael Tokarev +Date: Fri May 4 16:41:47 2012 +0400 + + add (errnum) in front of windows error messages + + On localized, non-English versions of windows, it is + common to have two active charsets -- for console applications + and for GUI applications, together with localized error messages + returned by windows. But two charsets are rarely compatible, + so sending the same byte sequence to console and to windows + event log makes one or another to be unreadable. So at least + include the error number, this way it will be possible to + lookup the actual error test using external ways. + + Signed-off-by: Michael Tokarev + +commit 58007d7efa3940c863c5a398f8b257a686ce37ba +Author: Guus Sliepen +Date: Tue May 8 16:44:15 2012 +0200 + + Always pass request strings to other functions as const char *. + +commit 291a59b5b732de084e392daea1433b1fdb9fbfd5 +Author: Sven-Haegar Koch +Date: Sun Apr 22 03:44:28 2012 +0200 + + free_connection_partially(): also reset remote protocol version infos + + The used remote protocol can change between two reconnects, aka if + the remote side has enabled/disabled for example their ExperimentalProtocols + setting. + +commit 32e5c5bb7c2c9127274247cb74cffa7345b04fad +Author: Sven-Haegar Koch +Date: Sun Apr 22 03:05:29 2012 +0200 + + Silence SPTPS log messages, reduce them from DEBUG_ALWAYS to DEBUG_META. + +commit c78bb143030162f0c820f08c87808e157c014a07 +Author: Sven-Haegar Koch +Date: Sun Apr 22 02:55:06 2012 +0200 + + terminate_connection(): delete non-outgoing (aka incoming) connections. + +commit 8b9e5af0d93069a81ce2ebed9899eedf3b7b184b +Author: Sven-Haegar Koch +Date: Sat Apr 21 03:44:24 2012 +0200 + + Label control connections for log output as "", not "". + +commit d3f4cf59ca917386e7c6358a98adbe3b8e9ce87a +Author: Sven-Haegar Koch +Date: Sat Apr 21 01:59:01 2012 +0200 + + free_connection_partially(): Avoid possible use-after-free for c->hischallenge + +commit 7a6ca7a993e5907497d97fef09e375698dde182f +Author: Sven-Haegar Koch +Date: Sat Apr 21 01:51:36 2012 +0200 + + terminate_connection(): only kill c->node->connection if it is pointing + to the same connection + +commit a96c4f016c9fff2392d85f762e16f5430c0b6463 +Author: Sven-Haegar Koch +Date: Fri Apr 20 00:24:38 2012 +0200 + + terminate_connection(): Avoid use-after-free and double-free for + already freed edge structure. + +commit 5c0dd104f94519c3cb50e9ca44227656c5adc7ae +Author: Guus Sliepen +Date: Thu Apr 19 15:56:08 2012 +0200 + + Document new proxy types. + +commit 5ae19cb0bb8dd6be1e9bcd560bb051f496a373ec +Author: Guus Sliepen +Date: Thu Apr 19 15:18:31 2012 +0200 + + Add support for proxying through an external command. + + Proxy type "exec" can be used to have an external script or binary set + up an outgoing connection. Standard input and output will be used to + exchange data with the external command. The variables REMOTEADDRESS and + REMOTEPORT are set to the intended destination address and port. + +commit fb5588856fa4dd6f140c72f7360302fe85b20c75 +Author: Guus Sliepen +Date: Thu Apr 19 14:10:54 2012 +0200 + + Add support for SOCKS 5 proxies. + + This only covers outgoing TCP connections, and supports only + username/password authentication or no authentication. + +commit b58d95eb29662bce4388f95dbc5762b9e2999806 +Author: Guus Sliepen +Date: Wed Apr 18 23:19:40 2012 +0200 + + Add basic support for SOCKS 4 and HTTP CONNECT proxies. + + When the Proxy option is used, outgoing connections will be made via the + specified proxy. There is no support for authentication methods or for having + the proxy forward incoming connections, and there is no attempt to proxy UDP. + +commit 84531fb6e621959e06519fdbb7f2a8f7578f66bd +Author: Guus Sliepen +Date: Mon Apr 16 01:57:25 2012 +0200 + + Allow broadcast packets to be sent directly instead of via the MST. + + When the "Broadcast = direct" option is used, broadcast packets are not sent + and forwarded via the Minimum Spanning Tree to all nodes, but are sent directly + to all nodes that can be reached in one hop. + + One use for this is to allow running ad-hoc routing protocols, such as OLSR, on + top of tinc. + +commit 9ebb34f907e8a15cb71dd20b111270d80bad1e96 +Author: Guus Sliepen +Date: Mon Apr 16 01:16:59 2012 +0200 + + Update README to reflect that only OpenSSL is currently supported. + +commit a851d8a9f6e3b69ab75695d84471ff4d525341b7 +Author: Guus Sliepen +Date: Mon Apr 16 01:14:59 2012 +0200 + + Add autoconf checks for OpenSSL's elliptic curve functions. + +commit f8e15dfe8d155b5bdb1e39bf6b9af486606145e8 +Author: Sven-Haegar Koch +Date: Sat Apr 14 02:28:43 2012 +0200 + + ecdh & ecdsa: avoid some possible memory leaks in error conditions. + +commit 8792b9a9f343e751dc3cfd789db9528da609ba9f +Author: Sven-Haegar Koch +Date: Sat Apr 14 02:02:11 2012 +0200 + + Remove confusing error message for failed reading in ECDSA keys. + + Most likeley the error is that there just is no valid key inside the used + host file, and in this case errno just contains a random value from the + last previously failed call. + +commit a5bb6d40fb517aa175510ec179091e4f9ffaf6f6 +Author: Sven-Haegar Koch +Date: Sat Apr 14 02:29:32 2012 +0200 + + sptps_stop(): clear pointers after free to avoid double free. + + sptps_stop() may get called twice on some failed connection setups. + +commit 535a55100bb77f107c85361e9f72a194e92bc8bc +Author: Guus Sliepen +Date: Thu Mar 29 16:45:25 2012 +0100 + + Allow environment variables to be used for Name. + + When the Name starts with a $, the rest will be interpreted as the name of an + environment variable containing the real Name. When Name is $HOST, but this + environment variable does not exist, gethostname() will be used to set the + Name. In both cases, illegal characters will be converted to underscores. + +commit 1d9dacb1f26971e19463b5501c2410c57f780ecb +Merge: 86c2990 89f4574 +Author: Guus Sliepen +Date: Mon Mar 26 19:06:39 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + src/logger.c + src/net_setup.c + +commit 89f4574e0b1553c8e5dcbfc275e829a759b697f6 +Author: Guus Sliepen +Date: Mon Mar 26 14:46:09 2012 +0100 + + Add support for systemd style socket activation. + + If the LISTEN_FDS environment variable is set and tinc is run in the + foreground, tinc will use filedescriptors 3 to 3 + LISTEN_FDS for its listening + TCP sockets. For now, tinc will create matching listening UDP sockets itself. + + There is no dependency on systemd or on libsystemd-daemon. + +commit cc6aee784659bfbd21eb8d414e00a8f1a801cac4 +Author: Guus Sliepen +Date: Mon Mar 26 14:45:20 2012 +0100 + + Remove newline from log message. + +commit 16e6769feef21a5bf58f6022d990452987bb5efb +Author: Anthony G. Basile +Date: Mon Mar 26 06:29:40 2012 -0400 + + configure.in: fix AC_ARG_ENABLE and AC_ARG_WITH + + The current configure.in file does not correctly make use of these + macros. The resulting configure file will therefore enable an item + even if --disable-FEATURE is given. This patch restores the intended + behavior. + +commit 86c2990327fdf7ec1197aa73cb2b9a926a734db4 +Merge: d7bf63c b23681d +Author: Guus Sliepen +Date: Sun Mar 25 23:35:31 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + NEWS + README + configure.in + src/Makefile.am + src/conf.c + src/conf.h + src/connection.c + src/net.c + src/tincd.c + +commit b23681dddb8987571f04d46fc14f0ba012a7929c +Author: Guus Sliepen +Date: Sun Mar 25 22:54:36 2012 +0100 + + Support :: in IPv6 Subnets. + +commit 482c6119a7ae80f320e5b519ef2e785e04a77b8e +Author: Guus Sliepen +Date: Sun Mar 25 15:32:26 2012 +0100 + + Releasing 1.0.18. + +commit 64c657b32d1eb34eb669c6d5b0ec26c1a643b194 +Author: Guus Sliepen +Date: Sun Mar 25 15:30:58 2012 +0100 + + Mark DecrementTTL option experimental. + +commit f71ce341800739c7cdee01d7cf025e7492da22ac +Author: Guus Sliepen +Date: Sun Mar 25 15:17:50 2012 +0100 + + Fix return type of vde_recv() as well. + + In this case it is not really necessary as the conversion to int will already + take care of ensuring the return value is treated as signed. + +commit 6225b1884a25af4debc2d0821a4c377ddbaec696 +Author: Guus Sliepen +Date: Sun Mar 25 14:55:56 2012 +0100 + + Document OpenBSD "ifconfig link0" and Linux "ip tuntap" commands. + +commit 399835385380d485416d6d59a8f27ce71f1db644 +Author: Guus Sliepen +Date: Sun Mar 25 14:46:50 2012 +0100 + + Fix some more compiler warnings. + +commit cfe6558d4ba4f572311aeafd62737f6f2692ad86 +Author: Guus Sliepen +Date: Sun Mar 25 14:00:21 2012 +0100 + + Fix return value type of vde_send(). + + The libvdeplug_dyn.h header file incorrectly declares the return type of + vde_send() to size_t, while in reality it is ssize_t. + +commit 95968c67f9df9102ddbce5b7c8d34107989ad51a +Author: Guus Sliepen +Date: Sun Mar 25 13:58:14 2012 +0100 + + Fix compiler warnings. + +commit e2d1b0b899ef66cd7ff227549e58b96c292f784e +Author: Guus Sliepen +Date: Sun Mar 25 13:42:10 2012 +0100 + + Allow scoped addresses to be used for IPv6 multicast socket. + +commit 251204063255d95910f9a079015e2f9b428fd983 +Author: Guus Sliepen +Date: Sun Mar 25 13:40:55 2012 +0100 + + Add #ifdefs in case not all platforms support IPv4 and IPv6 multicast. + +commit b5e3bf1a85462f0c41638c11305d28f87af24395 +Author: Guus Sliepen +Date: Fri Mar 23 13:18:36 2012 +0100 + + Set default value of DecrementTTL to "no". + + Decrementing the TTL causes IPv6 to fail when Mode = switch, and there may be + other unforeseen side-effects. + +commit c373de2e9812700c0568640727ad917b6fc7d758 +Author: Guus Sliepen +Date: Wed Mar 21 17:00:53 2012 +0100 + + Add support for multicast communication with UML/QEMU/KVM. + + DeviceType = multicast allows one to specify a multicast address and port with + a Device statement. Tinc will then read/send packets to that multicast group + instead of to a tun/tap device. This allows interaction with UML, QEMU and KVM + instances that are listening on the same group. + +commit a7dbb50c23f447a23b543c92ec096ff178bc2de3 +Author: Guus Sliepen +Date: Wed Mar 21 13:20:15 2012 +0100 + + Allow a port to be specified in BindToAddress statements. + + This can be used to let tinc listen on multiple ports for incoming connections. + +commit 80e15d8b96e5313b33c91003b1f75d7f6db9924e +Author: Guus Sliepen +Date: Tue Mar 20 23:49:16 2012 +0100 + + Always try next Address when an outgoing connection fails to authenticate. + + When making outgoing connections, tinc goes through the list of Addresses and + tries all of them until one succeeds. However, before it would consider + establishing a TCP connection a success, even when the authentication failed. + This would be a problem if the first Address would point to a hostname and port + combination that belongs to the wrong tinc node, or perhaps even to a non-tinc + service, causing tinc to endlessly try this Address instead of moving to the + next one. + + Problem found by Delf Eldkraft. + +commit d7bf63c63ab397cf3e5ca4a065922364925788e7 +Author: Guus Sliepen +Date: Sun Mar 18 21:24:46 2012 +0100 + + Make sure the signature also covers the session label. + +commit 42a0b61076d5d0f6391f0dd5c2c400b8fb89c5c5 +Author: Guus Sliepen +Date: Sun Mar 18 20:38:48 2012 +0100 + + Start documenting the SPTPS protocol. + +commit d756bb92ed52d5b1ecdd42af32f11f733db64d91 +Author: Guus Sliepen +Date: Sun Mar 18 17:46:30 2012 +0100 + + Don't send an ACK message after the first key exchange in the SPTPS protocol. + +commit c970ecdd75d4e7b3203a788f28b6e40cd532759b +Author: Guus Sliepen +Date: Sun Mar 18 17:42:43 2012 +0100 + + Test SPTPS messages sent while key renegotation is in progress. + +commit 3a4fe104a06b73fd19c550546e7c65a59ff2afe3 +Author: Guus Sliepen +Date: Sun Mar 18 16:42:02 2012 +0100 + + Add datagram mode to the SPTPS protocol. + + * Everything is identical except the headers of the records. + * Instead of sending explicit message length and having an implicit sequence + number, datagram mode has an implicit message length and an explicit sequence + number. + * The sequence number is used to set the most significant bytes of the counter. + +commit 03e06fd43aff73b4a5c9d367968a1279371ae252 +Author: Guus Sliepen +Date: Sun Mar 18 16:41:13 2012 +0100 + + Allow CTR mode counter to be set to a specific value. + +commit 28a1501b9a8b4c730f7f965d6b2e8fc50feba261 +Author: Guus Sliepen +Date: Sat Mar 10 13:31:36 2012 +0100 + + Releasing 1.0.17. + +commit 4712d8f92e63e86e835ffb624d6399343ee568ea +Author: Guus Sliepen +Date: Sat Mar 10 13:23:08 2012 +0100 + + Update copyright notices. + +commit 5b0f5ad958d6db4e73aebc5ee6c608cdae81b7b5 +Author: Guus Sliepen +Date: Thu Mar 8 23:23:39 2012 +0100 + + Make sure disabling old RSA keys works on Windows. + + Seeking in files and rewriting parts of them does not seem to work properly on + Windows. Instead, when old RSA keys are found when generating new ones, the + file containing the old keys is copied to a temporary file where the changes + are made, and that file is renamed back to the original filename. On Windows, + we cannot atomically replace files with a rename(), so we need to move the + original file out of the way first. If anything fails, the new code will warn + that the user has to solve the problem by hand. + +commit 2f1c337c541fcb7e2c62aeeab245ff7a43eb51a5 +Author: Guus Sliepen +Date: Thu Mar 8 22:19:20 2012 +0100 + + Add missing ICMP6 message type definitions. + +commit 40c28589328a2aa96c2ce1419c5d90616c758b3d +Merge: 8ac096b 9dea33f +Author: Guus Sliepen +Date: Thu Mar 8 21:15:08 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + src/net_packet.c + +commit 9dea33f5301119dd4423eb962956cf2d246af3f3 +Author: Guus Sliepen +Date: Wed Mar 7 10:40:06 2012 +0100 + + Accept Subnets passed with the -o option when StrictSubnets = yes. + +commit 63f8303a5dc1758876451a580a8317dbc3d295d6 +Author: Guus Sliepen +Date: Fri Mar 2 16:09:58 2012 +0100 + + Only log errors sending UDP packets when debug level >= 5. + + Since tinc will fall back to TCP or route via another node, it is not necessary + to log such errors unconditionally. + +commit 8ac096b5bf9da1b3961a3ac4a03d083629222a63 +Author: Guus Sliepen +Date: Sun Feb 26 18:37:36 2012 +0100 + + Allow log messages to be captured by tincctl. + + This allows tincctl to receive log messages from a running tincd, + independent of what is logged to syslog or to file. Tincctl can receive + debug messages with an arbitrary level. + +commit a1bd3a291379492c8ffecd53792065dc20a28c79 +Author: Guus Sliepen +Date: Sun Feb 26 16:56:53 2012 +0100 + + Don't close control connections when handling a reload command. + + Because this would terminate the connection while the control message + handler was still running, it would lead to a segmentation fault later + on. + +commit 483c5dcfb43719e5fd50902641252e28a04fd74e +Merge: 344d6b9 ae52496 +Author: Guus Sliepen +Date: Sun Feb 26 16:27:13 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + +commit ae5249610954af17c68c547bb1b45ad286ad647e +Author: Guus Sliepen +Date: Sun Feb 26 16:23:02 2012 +0100 + + Only use broadcast at the start of the PMTU discovery phase. + + For local peer discovery, only a handful of packets are necessary for + peers to detect each other. + +commit 344d6b9ac3c795f2942e457c1ab38b1dac5f7242 +Author: Guus Sliepen +Date: Sun Feb 26 12:39:46 2012 +0100 + + Let tincctl use the NETNAME environment variable if no -n option is given. + + This allows administrators who frequently want to work with one tinc + network to omit the -n option. Since the NETNAME variable is set by + tincd when executing scripts, this makes it slightly easier to use + tincctl from within scripts. + +commit 84570275acd84628586a6ca591a283d074ca10f0 +Author: Guus Sliepen +Date: Sun Feb 26 12:33:16 2012 +0100 + + Ensure all SPTPS functions are prefixed with sptps_. + +commit 8b1ad6f76f821648079818f6ff018bbc33b9d9e9 +Author: Guus Sliepen +Date: Sat Feb 25 23:03:09 2012 +0100 + + Go back to breadth first search for path finding. + + If 1.1.x nodes using Dijkstra's algorithm are mixed with 1.0.x nodes using BFS, + then routing loops can occur. + +commit 36623e15a1c8685e5d8730345c1a7f9c93710fef +Merge: 65d6f02 5140656 +Author: Guus Sliepen +Date: Sat Feb 25 22:52:57 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + +commit 5140656de6bcfda72951a7827b05414ce306e3ca +Author: Guus Sliepen +Date: Sat Feb 25 22:11:30 2012 +0100 + + Stricter checks against routing loops. + + If a packet that had to be sent via an intermediate hop, and that intermediate + hop was the one that sent the packet, we drop it. + +commit f1d5eae643cdf537ef357f10f2da8ff83bdf32b4 +Author: Guus Sliepen +Date: Sat Feb 25 21:46:18 2012 +0100 + + Don't send ICMP Time Exceeded messages for other Time Exceeded messages. + + That would be silly. + +commit 65d6f023c46ac3a087f59b60762f87c869783f21 +Author: Guus Sliepen +Date: Sat Feb 25 18:25:21 2012 +0100 + + Use SPTPS when ExperimentalProtocol is enabled. + +commit efd21e232dced3225f119aeb7a585ebf55b7cf77 +Author: Guus Sliepen +Date: Sat Feb 25 15:18:15 2012 +0100 + + Apply HMAC after encryption. + +commit f5dc136cfd7a3a195b75f7174722734e25f30fd9 +Merge: 3fba801 5a28aa7 +Author: Guus Sliepen +Date: Thu Feb 23 13:26:01 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + src/net.c + src/net_packet.c + src/net_socket.c + +commit 5a28aa7b8b0ab6237c2eab5f8b11253ea3ec5a05 +Author: Guus Sliepen +Date: Wed Feb 22 23:17:43 2012 +0100 + + Add LocalDiscovery option which tries to detect peers on the local network. + + Currently, this is implemented by sending IPv4 broadcast packets to the + LAN during path MTU discovery. + +commit 8e717ddb602f01f656369106ec0398efbe9ca4a4 +Author: Guus Sliepen +Date: Wed Feb 22 14:37:56 2012 +0100 + + Pass index into listen_socket[] to handle_incoming_vpn_data(). + +commit 3fba80174dbe29bcfe0d121a2a1d2e61be5ee57b +Merge: fba1c85 65e8e06 +Author: Guus Sliepen +Date: Wed Feb 22 14:23:59 2012 +0100 + + Merge branch 'master' of git://tinc-vpn.org/tinc into 1.1 + + Conflicts: + NEWS + README + configure.in + doc/tincd.8.in + src/Makefile.am + src/bsd/device.c + src/connection.c + src/connection.h + src/cygwin/device.c + src/device.h + src/dropin.h + src/linux/device.c + src/mingw/device.c + src/net.c + src/net_packet.c + src/net_setup.c + src/net_socket.c + src/process.c + src/protocol.c + src/protocol_key.c + src/raw_socket_device.c + src/route.c + src/solaris/device.c + src/tincd.c + src/uml_device.c + +commit fba1c85f44edfc56c19d35332b1eb825179a8bb6 +Author: Guus Sliepen +Date: Tue Feb 21 23:19:51 2012 +0100 + + Remove useless warning about signature length being shorter than expected. + +commit cb6cbf452f6183a00746afc5bff8f63f3f55235f +Author: Guus Sliepen +Date: Tue Feb 21 23:17:12 2012 +0100 + + Use only one hash algorithm (SHA512) in the PRF. + + On some platforms, OpenSSL by default does not support the Whirlpool algorithm. + +commit 65e8e06c6dc7349b11c3c1e8f4071b51e2994c65 +Author: Nick Hibma +Date: Tue Feb 21 15:26:58 2012 +0100 + + Add missing ICMP message type definitions. + +commit ac48c4ee8c09c8144f830cb66386b9dbe7298440 +Author: Guus Sliepen +Date: Tue Feb 21 14:06:55 2012 +0100 + + Fix check for raw socket support. + + Also, move some variables so there are no compiler warnings about unused + variables when there is no support for raw sockets. + +commit d9ad3d313d96d30ef45cd53367dff9a855a396d4 +Author: Guus Sliepen +Date: Tue Feb 21 13:31:21 2012 +0100 + + Fix a bug that caused tinc to ignore all but the last listening socket. + +commit 46506b7aaf6c6a8a85561c38fdb9c95eae21aa75 +Author: Guus Sliepen +Date: Tue Feb 21 13:13:40 2012 +0100 + + Document the command line flag -o and provide --option as well. + +commit 7d76e287598c8c18cadfb5818046d9dd1b0ad881 +Author: Guus Sliepen +Date: Tue Feb 21 11:39:21 2012 +0100 + + Move initialization of char *priority up to prevent freeing an uninitialized pointer. + +commit 8420a0c8bde1781db04dd2436eb9d5dca5a1732a +Author: Guus Sliepen +Date: Mon Feb 20 17:19:00 2012 +0100 + + Allow disabling of broadcast packets. + + The Broadcast option can be used to cause tinc to drop all broadcast and + multicast packets. This option might be expanded in the future to selectively + allow only some broadcast packet types. + +commit ea415ccc1690d6e5864a7500977b181e5c8faafe +Author: Guus Sliepen +Date: Mon Feb 20 17:12:48 2012 +0100 + + Rename connection_t *broadcast to everyone. + +commit cff5a844a3e6b494f4a4f6eb5b48a84780f2d0e5 +Author: Guus Sliepen +Date: Mon Feb 20 16:52:53 2012 +0100 + + Don't bind outgoing TCP sockets anymore. + + The code introduced in commit 41a05f59ba2c3eb5caab555f096ed1b9fbe69ee3 is not + needed anymore, since tinc has been able to handle UDP packets from a different + source address than those of the TCP packets since 1.0.10. When using multiple + BindToAddress statements, this code does not make sense anymore, we do want the + kernel to choose the source address on its own. + +commit 0233b1d710222cb09be0cbd08c1297e3ece38a9f +Author: Guus Sliepen +Date: Mon Feb 20 16:34:02 2012 +0100 + + Decrement TTL of incoming packets. + + Tinc will now, by default, decrement the TTL field of incoming IPv4 and IPv6 + packets, before forwarding them to the virtual network device or to another + node. Packets with a TTL value of zero will be dropped, and an ICMP Time + Exceeded message will be sent back. + + This behaviour can be disabled using the DecrementTTL option. + +commit 6289859ab365dc1c0d420323174418b316b14502 +Author: Guus Sliepen +Date: Mon Feb 20 15:44:52 2012 +0100 + + Only compile raw socket code when it is supported on that platform. + +commit d1dcdf8eb6f800704be426b1ce6f6c1a8e65ba0d +Merge: 1b2846d 3b1fad0 +Author: Guus Sliepen +Date: Sat Feb 18 14:31:08 2012 +0100 + + Merge branch 'master' of black:tinc + +commit 3b1fad04de6bed2f284fdf3d5b27d4162aeebc8c +Author: Guus Sliepen +Date: Sat Feb 18 14:37:52 2012 +0100 + + Allow setting DeviceType to tun or tap on Linux. + +commit 6455654d26d204cea4bbc102e5bd6550b7fff7a7 +Author: Guus Sliepen +Date: Sat Feb 18 11:48:21 2012 +0100 + + Send packets back using the same socket as they were received on. + +commit 1b2846d907adfc8472fc9da0c951c3243c7ee143 +Merge: 9f6a96a 6455654 +Author: Guus Sliepen +Date: Sat Feb 18 11:43:00 2012 +0100 + + Merge branch 'master' of black:tinc + +commit 9f6a96af3939bd2de410ce346a8c8fbcf93e7c9b +Author: Guus Sliepen +Date: Fri Feb 17 16:25:00 2012 +0100 + + Allow multiple BindToAddress statements. + +commit 708314df2f61675d0f54e541c9fff62ac1f433b5 +Author: Guus Sliepen +Date: Fri Feb 17 16:13:38 2012 +0100 + + Set FD_CLOEXEC flag on all sockets. + + Scripts called by tinc would inherit its open filedescriptors. This could + be a problem if other long-running daemons are started from those scripts, + if those daemons would not close all filedescriptors before going into the + background. + + Problem found and solution suggested by Nick Hibma. + +commit 1f00111e94b2f9a4beb9608b1e03a5e73c9c5d21 +Author: Guus Sliepen +Date: Mon Dec 26 23:11:27 2011 +0100 + + Fix a few small memory leaks. + +commit b50d6a7f2ad98239018bc5ce7a5739e3bf4f50f7 +Author: Guus Sliepen +Date: Mon Dec 26 23:04:40 2011 +0100 + + Fix compiler warnings. + +commit 178e52f76ef4ba40748c13ea7e518837394d6dbc +Author: Guus Sliepen +Date: Sun Dec 4 01:20:59 2011 +0100 + + Allow linking with multiple device drivers. + + Apart from the platform specific tun/tap driver, link with the dummy and + raw_socket devices, and optionally with support for UML and VDE devices. + At runtime, the DeviceType option can be used to select which driver to + use. + +commit 5672863e59e6a114ac6b66de98254b14266c0e61 +Author: Guus Sliepen +Date: Sat Dec 3 21:59:47 2011 +0100 + + Fix a few small memory leaks. + +commit 52ded09d1713b83222b56db7d29ff061aefb95e3 +Author: Guus Sliepen +Date: Sun Nov 27 12:13:16 2011 +0100 + + Add vde/device.c to the tarball. + +commit 2c7c87ec75c94d0b3cca9f7a5aeba34384f77cc1 +Author: Guus Sliepen +Date: Sun Nov 27 12:12:34 2011 +0100 + + Fix compilation of VDE and UML interfaces. + +commit 2a9060bba62d78f73da9b09ca791fe80993520fc +Author: Guus Sliepen +Date: Thu Oct 6 15:32:12 2011 +0200 + + Exchange ACK records to indicate switch to new keys. + + This allow application records to be sent while key renegotiation is still + happening. + +commit 3b5898078af1ab86797b3e24f2381131e6e702f7 +Author: Guus Sliepen +Date: Thu Oct 6 09:34:34 2011 +0200 + + Use counter mode encryption. + +commit a0f795ff5bd671ca10a7203e4234b37a12d8d1cd +Author: Guus Sliepen +Date: Thu Oct 6 09:33:09 2011 +0200 + + Add counter mode encryption. + +commit 67ff81ec16b8ab5f15d16efbedfecfaf0be17c13 +Author: Guus Sliepen +Date: Wed Oct 5 22:05:13 2011 +0200 + + Test corner cases in the SPTPS protocol. + + * Test zero-byte messages. + * Test maximum size (65535 byte) messages. + * Test different message types. + * Test key renegotiation. + +commit 30013511504e925729ebc67772205a74c4b8aeea +Author: Guus Sliepen +Date: Wed Oct 5 22:00:51 2011 +0200 + + Update SPTPS protocol. + + * Exchange nonce and ECDH public key first, calculate the ECDSA signature + over the complete key exchange. + * Make an explicit distinction between client and server in the signatures. + * Add more comments and replace some magic numbers by #defines. + + Thanks to Erik Tews for very helpful hints and comments! + +commit 810847248ae90140ee6f3e568add80aef88c3def +Author: Guus Sliepen +Date: Wed Oct 5 21:59:33 2011 +0200 + + Fix compiler warning. + +commit ddea7a23a66b8fee4942f2ce237dcabe02e17270 +Author: Guus Sliepen +Date: Tue Aug 30 20:49:48 2011 +0200 + + Return false instead of void when there is an error. + +commit e838289683c0039fac0ae6172d40b4177c17911b +Author: Guus Sliepen +Date: Tue Aug 30 19:56:56 2011 +0200 + + Prevent read_rsa_public_key() from returning an uninitialized RSA structure. + + In case the config file could not be opened a new but unitialized RSA structure + would be returned, causing a segmentation fault later on. This would only + happen in the case that the config file could be opened before, but not when + read_rsa_public_key() was called. This situation could occur when the --user + option was used, and the config files were not readable by the specified user. + +commit 5d4336e5429b88dcc53e80c00412e76a5269b384 +Author: Guus Sliepen +Date: Wed Aug 10 17:04:17 2011 +0200 + + Handle UDP packets with unknown source addresses properly. + + Probably due to a merge, the try_harder() function had duplicated the + rate-limiting code for detecting the sender node based on the HMAC of the + packet. This prevented this detection from running at all. The function is now + identical again to that in the 1.0 branch. + +commit bbc0ba9e87f76111529d6dc9cb00c0b9435b5858 +Author: Michael Tokarev +Date: Sun Aug 7 12:18:20 2011 +0400 + + use execvp() not execve() in tincctl start + + sometimes argv[0] will have directory-less name (when the + command is started by shell searching in $PATH for example). + For tincctl start we want the same rules to run tincd as for + tincctl itself (having full path is better but if shell does + not provide one we've no other choice). Previous code tried + to run ./tincd in this case, which is obviously wrong. + + This is a fix for the previous commit. + + Signed-off-by: Michael Tokarev + +commit a7556a9d2c943a6317d2dab66d9f742997f0d47a +Author: Michael Tokarev +Date: Sun Aug 7 12:05:07 2011 +0400 + + run tincd from the same directory as tincctl and pass all options to it + + For tincctl start, run tincd from dirname($0) not SBINDIR - + this allows painless alternative directory installation and + running from build directory too. + + Also while at it, pass the rest of command line to tincd, not + only options before "start" argument. This way it's possible + to pass options to tincd like this: + tincctl -n net start -- -d 1 -R -U tincuser ... + + And also add missing newline at the end of error message there. + + Signed-Off-By: Michael Tokarev + +commit 2696ad2cca73aee13e38f740d5530dc33e4a92e6 +Author: Michael Tokarev +Date: Sun Aug 7 11:25:03 2011 +0400 + + don't mention reload twice in tincctl help + + Signed-Off-By: Michael Tokarev + +commit 3d75dbc0880484ff6d2f689a9b981def3cd75b5e +Author: Guus Sliepen +Date: Sun Jul 24 15:44:51 2011 +0200 + + Start of "Simple Peer-To-Peer Security" protocol. + + Encryption and authentication of the meta connection is spread out over + meta.c and protocol_auth.c. The new protocol was added there as well, + leading to spaghetti code. To improve things, the new protocol will now + be implemented in sptps.[ch]. + + The goal is to have a very simplified version of TLS. There is a record + layer, and there are only two record types: application data and + handshake messages. The handshake message contains a random nonce, an + ephemeral ECDH public key, and an ECDSA signature over the former. After + the ECDH public keys are exchanged, a shared secret is calculated, and a + TLS style PRF is used to generate the key material for the cipher and + HMAC algorithm, and further communication is encrypted and authenticated. + + A lot of the simplicity comes from the fact that both sides must have + each other's public keys in advance, and there are no options to choose. + There will be one fixed cipher suite, and both peers always authenticate + each other. (Inspiration taken from Ian Grigg's hypotheses[0].) + There might be some compromise in the future, to enable or disable + encryption, authentication and compression, but there will be no choice + of algorithms. This will allow SPTPS to be built with a few embedded + crypto algorithms instead of linking with huge crypto libraries. + + The API is also kept simple. There is a start and a stop function. All + data necessary to make the connection work is passed in the start + function. Instead having both send- and receive-record functions, there + is a send-record function and a receive-data function. The latter will + pass protocol data received from the peer to the SPTPS implementation, + which will in turn call a receive-record callback function when + necessary. This hides all the handshaking from the application, and is + completely independent from any event loop or socket characteristics. + + [0] http://iang.org/ssl/hn_hypotheses_in_secure_protocol_design.html + +commit 0f2aa4bd8b698608876bec141c5aef1aa619730b +Author: Guus Sliepen +Date: Sat Jul 23 14:12:23 2011 +0200 + + Releasing 1.0.16. + +commit e16ead8dd9d4600664058069f0695832dfe068b2 +Author: Guus Sliepen +Date: Sat Jul 23 14:11:44 2011 +0200 + + Use usleep() instead of sleep(), MinGW complained. + +commit ff751903aa82bd6dd66a099f9c05dcdae9fc57f2 +Author: Guus Sliepen +Date: Wed Jul 20 08:19:18 2011 +0200 + + Don't abort() on low-level crypto errors, just return false. + + The abort() calls were accidentily left in for debugging. + +commit 2f4ccfe2473948372f7c9f14d9ffce1d77f5fd8c +Author: Guus Sliepen +Date: Tue Jul 19 21:11:11 2011 +0200 + + Fix tinc 1.0.x daemons connecting when ExperimentalProtocol = yes. + commit f8d94f34fc5d7fe9ed4a076a2fd77eacbd83adca Author: Guus Sliepen Date: Sun Jul 17 20:09:08 2011 +0200 @@ -16,6 +1966,18 @@ Date: Sun Jul 17 20:01:24 2011 +0200 Write loopback address instead of "any" address in pidfile. +commit 50fcfea127c9d2fdf8894498a9fdcc6fb3bbb2ce +Author: Guus Sliepen +Date: Sun Jul 17 19:34:01 2011 +0200 + + Flush output buffer in send_tcppacket(). + + This is mainly important for Windows, where the select() call in the + main thread is not being woken up when the tapreader thread calls + route(), causing a delay of up to 1 second before the output buffer is + flushed. This would cause bad performance when UDP communication is not + possible. + commit 25091454da21941dd92375ddbee7dd6151343058 Author: Guus Sliepen Date: Sun Jul 17 19:23:52 2011 +0200 @@ -76,6 +2038,20 @@ Date: Sat Jul 16 15:15:29 2011 +0200 The flag was set incorrectly, but for most ciphers this does not have any effect. AES in any of the block modes is picky about it though. +commit be2fc8b0458b1e2ced3b5de410356d8d8639acff +Author: Guus Sliepen +Date: Sat Jul 16 10:47:35 2011 +0200 + + Make code to detect two nodes with the same Name less triggerhappy. + + First of all, if there really are two nodes with the same name, much + more than 10 contradicting ADD_EDGE and DEL_EDGE messages will be sent. + Also, we forgot to reset the counters when nothing happened. + + In case there is a ADD_EDGE/DEL_EDGE storm, we do not shut down, but + sleep an increasing amount of time, allowing tinc to recover gracefully + from temporary failures. + commit 303dd1e70219a7542921f6e63d9391ab326d434f Author: Guus Sliepen Date: Wed Jul 13 22:52:52 2011 +0200 diff --git a/INSTALL b/INSTALL index 7d1c323..a1e89e1 100644 --- a/INSTALL +++ b/INSTALL @@ -1,8 +1,8 @@ Installation Instructions ************************* -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007, 2008, 2009 Free Software Foundation, Inc. +Copyright (C) 1994-1996, 1999-2002, 2004-2011 Free Software Foundation, +Inc. Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright @@ -226,6 +226,11 @@ order to use an ANSI C compiler: and if that doesn't work, install pre-built binaries of GCC for HP-UX. + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot parse its `' header file. The option `-nodtk' can be used as a workaround. If GNU CC is not installed, it is therefore recommended diff --git a/Makefile.in b/Makefile.in index baa5005..3fe02c7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -15,6 +15,23 @@ @SET_MAKE@ VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -42,7 +59,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libevent.m4 \ $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/openssl.m4 \ - $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.in + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/zlib.m4 \ + $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) am__CONFIG_DISTCLEAN_FILES = config.status config.cache config.log \ @@ -60,6 +78,11 @@ RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \ install-pdf-recursive install-ps-recursive install-recursive \ installcheck-recursive installdirs-recursive pdf-recursive \ ps-recursive uninstall-recursive +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac RECURSIVE_CLEAN_TARGETS = mostlyclean-recursive clean-recursive \ distclean-recursive maintainer-clean-recursive AM_RECURSIVE_TARGETS = $(RECURSIVE_TARGETS:-recursive=) \ @@ -72,9 +95,11 @@ DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) top_distdir = $(distdir) am__remove_distdir = \ - { test ! -d "$(distdir)" \ - || { find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ - && rm -fr "$(distdir)"; }; } + if test -d "$(distdir)"; then \ + find "$(distdir)" -type d ! -perm -200 -exec chmod u+w {} ';' \ + && rm -rf "$(distdir)" \ + || { sleep 5 && rm -rf "$(distdir)"; }; \ + else :; fi am__relativize = \ dir0=`pwd`; \ sed_first='s,^\([^/]*\)/.*$$,\1,'; \ @@ -103,6 +128,8 @@ am__relativize = \ DIST_ARCHIVES = $(distdir).tar.gz GZIP_ENV = --best distuninstallcheck_listfiles = find . -type f -print +am__distuninstallcheck_listfiles = $(distuninstallcheck_listfiles) \ + | sed 's|^\./|$(prefix)/|' | grep -v '$(infodir)/dir$$' distcleancheck_listfiles = find . -type f -print ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -152,6 +179,7 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -214,7 +242,7 @@ all: config.h $(MAKE) $(AM_MAKEFLAGS) all-recursive .SUFFIXES: -am--refresh: +am--refresh: Makefile @: $(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) @for dep in $?; do \ @@ -250,10 +278,8 @@ $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) $(am__aclocal_m4_deps): config.h: stamp-h1 - @if test ! -f $@; then \ - rm -f stamp-h1; \ - $(MAKE) $(AM_MAKEFLAGS) stamp-h1; \ - else :; fi + @if test ! -f $@; then rm -f stamp-h1; else :; fi + @if test ! -f $@; then $(MAKE) $(AM_MAKEFLAGS) stamp-h1; else :; fi stamp-h1: $(srcdir)/config.h.in $(top_builddir)/config.status @rm -f stamp-h1 @@ -435,13 +461,10 @@ distdir: $(DISTFILES) done @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ if test "$$subdir" = .; then :; else \ - test -d "$(distdir)/$$subdir" \ - || $(MKDIR_P) "$(distdir)/$$subdir" \ - || exit 1; \ - fi; \ - done - @list='$(DIST_SUBDIRS)'; for subdir in $$list; do \ - if test "$$subdir" = .; then :; else \ + $(am__make_dryrun) \ + || test -d "$(distdir)/$$subdir" \ + || $(MKDIR_P) "$(distdir)/$$subdir" \ + || exit 1; \ dir1=$$subdir; dir2="$(distdir)/$$subdir"; \ $(am__relativize); \ new_distdir=$$reldir; \ @@ -473,7 +496,11 @@ dist-gzip: distdir $(am__remove_distdir) dist-bzip2: distdir - tardir=$(distdir) && $(am__tar) | bzip2 -9 -c >$(distdir).tar.bz2 + tardir=$(distdir) && $(am__tar) | BZIP2=$${BZIP2--9} bzip2 -c >$(distdir).tar.bz2 + $(am__remove_distdir) + +dist-lzip: distdir + tardir=$(distdir) && $(am__tar) | lzip -c $${LZIP_OPT--9} >$(distdir).tar.lz $(am__remove_distdir) dist-lzma: distdir @@ -481,7 +508,7 @@ dist-lzma: distdir $(am__remove_distdir) dist-xz: distdir - tardir=$(distdir) && $(am__tar) | xz -c >$(distdir).tar.xz + tardir=$(distdir) && $(am__tar) | XZ_OPT=$${XZ_OPT--e} xz -c >$(distdir).tar.xz $(am__remove_distdir) dist-tarZ: distdir @@ -512,6 +539,8 @@ distcheck: dist bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ *.tar.lzma*) \ lzma -dc $(distdir).tar.lzma | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ *.tar.xz*) \ xz -dc $(distdir).tar.xz | $(am__untar) ;;\ *.tar.Z*) \ @@ -521,7 +550,7 @@ distcheck: dist *.zip*) \ unzip $(distdir).zip ;;\ esac - chmod -R a-w $(distdir); chmod a+w $(distdir) + chmod -R a-w $(distdir); chmod u+w $(distdir) mkdir $(distdir)/_build mkdir $(distdir)/_inst chmod a-w $(distdir) @@ -531,6 +560,7 @@ distcheck: dist && am__cwd=`pwd` \ && $(am__cd) $(distdir)/_build \ && ../configure --srcdir=.. --prefix="$$dc_install_base" \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ $(DISTCHECK_CONFIGURE_FLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) \ && $(MAKE) $(AM_MAKEFLAGS) dvi \ @@ -559,8 +589,16 @@ distcheck: dist list='$(DIST_ARCHIVES)'; for i in $$list; do echo $$i; done) | \ sed -e 1h -e 1s/./=/g -e 1p -e 1x -e '$$p' -e '$$x' distuninstallcheck: - @$(am__cd) '$(distuninstallcheck_dir)' \ - && test `$(distuninstallcheck_listfiles) | wc -l` -le 1 \ + @test -n '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: trying to run $@ with an empty' \ + '$$(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + $(am__cd) '$(distuninstallcheck_dir)' || { \ + echo 'ERROR: cannot chdir into $(distuninstallcheck_dir)' >&2; \ + exit 1; \ + }; \ + test `$(am__distuninstallcheck_listfiles) | wc -l` -eq 0 \ || { echo "ERROR: files left after uninstall:" ; \ if test -n "$(DESTDIR)"; then \ echo " (check DESTDIR support)"; \ @@ -591,10 +629,15 @@ install-am: all-am installcheck: installcheck-recursive install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: @@ -681,17 +724,18 @@ uninstall-am: .PHONY: $(RECURSIVE_CLEAN_TARGETS) $(RECURSIVE_TARGETS) CTAGS GTAGS \ all all-am am--refresh check check-am clean clean-generic \ ctags ctags-recursive dist dist-all dist-bzip2 dist-gzip \ - dist-lzma dist-shar dist-tarZ dist-xz dist-zip distcheck \ - distclean distclean-generic distclean-hdr distclean-tags \ - distcleancheck distdir distuninstallcheck dvi dvi-am html \ - html-am info info-am install install-am install-data \ - install-data-am install-dvi install-dvi-am install-exec \ - install-exec-am install-html install-html-am install-info \ - install-info-am install-man install-pdf install-pdf-am \ - install-ps install-ps-am install-strip installcheck \ - installcheck-am installdirs installdirs-am maintainer-clean \ - maintainer-clean-generic mostlyclean mostlyclean-generic pdf \ - pdf-am ps ps-am tags tags-recursive uninstall uninstall-am + dist-lzip dist-lzma dist-shar dist-tarZ dist-xz dist-zip \ + distcheck distclean distclean-generic distclean-hdr \ + distclean-tags distcleancheck distdir distuninstallcheck dvi \ + dvi-am html html-am info info-am install install-am \ + install-data install-data-am install-dvi install-dvi-am \ + install-exec install-exec-am install-html install-html-am \ + install-info install-info-am install-man install-pdf \ + install-pdf-am install-ps install-ps-am install-strip \ + installcheck installcheck-am installdirs installdirs-am \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-generic pdf pdf-am ps ps-am tags tags-recursive \ + uninstall uninstall-am ChangeLog: diff --git a/NEWS b/NEWS index 679040b..806f2b7 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,28 @@ -Version 1.1pre2 Juli 17 2011 +Version 1.1pre3 October 14 2012 + + * New experimental protocol: + * Uses 521 bit ECDSA keys for authentication. + * Uses AES-256-CTR and HMAC-SHA256. + * Always provides perfect forward secrecy. + * Used for both meta-connections and VPN packets. + * VPN packets are encrypted end-to-end. + + * Many improvements to tincctl: + * "config" command shows/adds/changes configuration variables. + * "export" and "import" commands help exchange configuration files. + * "init" command sets up initial configuration files. + * "info" command shows details about a node, subnet or address. + * "log" command shows live log messages. + * Without a command it acts as a shell, with history and TAB completion. + * Improved starting/stopping tincd. + * Improved graph output. + + * When trying to directly send UDP packets to a node for which multiple + addresses are known, all of them are tried. + + * Many small fixes, code cleanups and documentation updates. + +Version 1.1pre2 July 17 2011 * .cookie files are renamed to .pid files, which are compatible with 1.0.x. @@ -28,6 +52,50 @@ Version 1.1pre1 June 25 2011 Thanks to Scott Lamb and Sven-Haegar Koch for their contributions to this version of tinc. +Version 1.0.19 June 25 2012 + + * Allow :: notation in IPv6 Subnets. + + * Add support for systemd style socket activation. + + * Allow environment variables to be used for the Name option. + + * Add basic support for SOCKS proxies, HTTP proxies, and proxying through an + external command. + +Version 1.0.18 March 25 2012 + + * Fixed IPv6 in switch mode by turning off DecrementTTL by default. + + * Allow a port number to be specified in BindToAddress, which also allows tinc + to listen on multiple ports. + + * Add support for multicast communication with UML/QEMU/KVM. + +Version 1.0.17 March 10 2012 + + * The DeviceType option can now be used to select dummy, raw socket, UML and + VDE devices without needing to recompile tinc. + + * Allow multiple BindToAddress statements. + + * Decrement TTL value of IPv4 and IPv6 packets. + + * Add LocalDiscovery option allowing tinc to detect peers that are behind the + same NAT. + + * Accept Subnets passed with the -o option when StrictSubnets = yes. + + * Disabling old RSA keys when generating new ones now also works properly on + Windows. + +Version 1.0.16 July 23 2011 + + * Fixed a performance issue with TCP communication under Windows. + + * Fixed code that, during network outages, would cause tinc to exit when it + thought two nodes with identical Names were on the VPN. + Version 1.0.15 June 24 2011 * Improved logging to file. diff --git a/README b/README index 09f6e6e..10cb0b5 100644 --- a/README +++ b/README @@ -1,7 +1,7 @@ -This is the README file for tinc version 1.1pre2. Installation +This is the README file for tinc version 1.1pre3. Installation instructions may be found in the INSTALL file. -tinc is Copyright (C) 1998-2011 by: +tinc is Copyright (C) 1998-2012 by: Ivo Timmermans, Guus Sliepen , @@ -29,82 +29,66 @@ protocol is not fixed yet. Security statement ------------------ -This version uses an experimental and unfinished cryptographic protocol. Use -it at your own risk. +This version uses an experimental and unfinished cryptographic protocol. Use it +at your own risk. Compatibility ------------- -Version 1.1pre2 is compatible with 1.0pre8, 1.0 and later, but not with older +Version 1.1pre3 is compatible with 1.0pre8, 1.0 and later, but not with older versions of tinc. When the ExperimentalProtocol option is used, tinc is still compatible with -1.0.X and 1.1pre2 itself, but not with any other 1.1preX version. +1.0.X and 1.1pre3 itself, but not with any other 1.1preX version. Requirements ------------ -Either OpenSSL (http://www.openssl.org/) or libgcrypt -(http://www.gnupg.org/download/#libgcrypt). - -The zlib library is used for optional compression. You can find it at -http://www.gzip.org/zlib/. - -The lzo library is also used for optional compression. You can find it at -http://www.oberhumer.com/opensource/lzo/. - -Since 1.1, the libevent library is used for the main event loop. You can find -it at http://monkey.org/~provos/libevent/. - In order to compile tinc, you will need a GNU C compiler environment. Please -ensure you have the latest stable versions of all the required libraries. +ensure you have the latest stable versions of all the required libraries: + +- OpenSSL (http://www.openssl.org/) version 1.0.0 or later. +- Libevent (http://monkey.org/~provos/libevent/) + +The following libraries are used by default, but can be disabled if necessary: + +- zlib (http://www.gzip.org/zlib/) +- lzo (http://www.oberhumer.com/opensource/lzo/) +- ncurses (http://invisible-island.net/ncurses/) +- readline (ftp://ftp.gnu.org/pub/gnu/readline/) Features -------- -This version of tinc supports multiple virtual networks at once. To -use this feature, you may supply a netname via the -n or --net -options. The standard locations for the config files will then be -/etc/tinc//. +Tinc is a peer-to-peer VPN daemon that supports VPNs with an arbitrary number +of nodes. Instead of configuring tunnels, you give tinc the location and +public key of a few nodes in the VPN. After making the initial connections to +those nodes, tinc will learn about all other nodes on the VPN, and will make +connections automatically. When direct connections are not possible, data will +be forwarded by intermediate nodes. -tincd regenerates its encryption key pairs. It does this on the first -activity after the keys have expired. This period is adjustable in the -configuration file, and the default time is 3600 seconds (one hour). +By default, nodes authenticate each other using 2048 bit RSA (or 521 bit +ECDSA*) keys. Traffic is encrypted using Blowfish in CBC mode (or AES-256 in +CTR mode*), authenticated using HMAC-SHA1 (or HMAC-SHA-256*), and is protected +against replay attacks. -This version supports multiple subnets at once. They are also sorted -on subnet mask size. This means that it is possible to have -overlapping subnets on the VPN, as long as their subnet mask sizes -differ. +*) When using the ExperimentalProtocol option. -Since pre5, tinc can operate in several routing modes. The default mode, -"router", works exactly like the older version, and uses Subnet lines to -determine the destination of packets. The other two modes, "switch" and "hub", -allow the tinc daemons to work together like a single network switch or hub. -This is useful for bridging networks. The latter modes only work properly on -Linux, FreeBSD and Windows. +Tinc fully supports IPv6. -The algorithms used for encryption and generating message authentication codes -can now be changed in the configuration files. All cipher and digest algorithms -supported by OpenSSL can be used. Useful ciphers are "blowfish" (default), -"bf-ofb", "des", "des3", etcetera. Useful digests are "sha1" (default), "md5", -etcetera. - -Support for routing IPv6 packets has been added. Just add Subnet lines with -IPv6 addresses (without using :: abbreviations) and use ifconfig or ip (from -the iproute package) to give the virtual network interface corresponding IPv6 -addresses. tinc does not provide autoconfiguration for IPv6 hosts, if you need -it use radvd or zebra. - -It is also possible to make tunnels to other tinc daemons over IPv6 networks, -if the operating system supports IPv6. tinc will automatically use both IPv6 -and IPv4 when available, but this can be changed by adding the option -"AddressFamily = ipv4" or "AddressFamily = ipv6" to the tinc.conf file. +Tinc can operate in several routing modes. In the default mode, "router", every +node is associated with one or more IPv4 and/or IPv6 Subnets. The other two +modes, "switch" and "hub", let the tinc daemons work together to form a virtual +Ethernet network switch or hub. Normally, when started tinc will detach and run in the background. In a native Windows environment this means tinc will intall itself as a service, which will restart after reboots. To prevent tinc from detaching or running as a service, use the -D option. +The status of the VPN can be queried using the "tincctl" tool, which connects +to a running tinc daemon via a control connection. The same tool also makes it +easy to start and stop tinc, and to change its configuration. diff --git a/THANKS b/THANKS index 4a6eae2..7d91271 100644 --- a/THANKS +++ b/THANKS @@ -3,12 +3,14 @@ We would like to thank the following people for their contributions to tinc: * Alexander Reil and Gemeinde Berg * Allesandro Gatti * Andreas van Cranenburgh +* Anthony G. Basile * Armijn Hemel * Brandon Black * Cris van Pelt * Delf Eldkraft * dnk * Enrique Zanardi +* Erik Tews * Flynn Marquardt * Grzegorz Dymarek * Hans Bayle @@ -26,13 +28,17 @@ We would like to thank the following people for their contributions to tinc: * Mark Glines * Markus Goetz * Martin Kihlgren +* Martin Schürrer * Matias Carrasco * Max Rijevski * Menno Smits +* Mesar Hameed * Michael Tokarev * Miles Nordin +* Nick Hibma * Nick Patavalis * Paul Littlefield +* Philipp Babel * Robert van der Meulen * Rumko * Scott Lamb @@ -40,6 +46,7 @@ We would like to thank the following people for their contributions to tinc: * Teemu Kiviniemi * Timothy Redaelli * Tonnerre Lombard +* Vil Brekin * Wessel Dankers * Wouter van Heyst diff --git a/aclocal.m4 b/aclocal.m4 index 3e5708a..4171fdd 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,7 +1,8 @@ -# generated automatically by aclocal 1.11.1 -*- Autoconf -*- +# generated automatically by aclocal 1.11.6 -*- Autoconf -*- # Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, -# 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. +# 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software Foundation, +# Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -13,8 +14,8 @@ m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl -m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.68],, -[m4_warning([this file was generated for autoconf 2.68. +m4_if(m4_defn([AC_AUTOCONF_VERSION]), [2.69],, +[m4_warning([this file was generated for autoconf 2.69. You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically `autoreconf'.])]) @@ -143,12 +144,15 @@ AC_DEFUN([AM_PATH_LIBGCRYPT], AC_SUBST(LIBGCRYPT_LIBS) ]) -# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008 Free Software Foundation, Inc. +# Copyright (C) 2002, 2003, 2005, 2006, 2007, 2008, 2011 Free Software +# Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. +# serial 1 + # AM_AUTOMAKE_VERSION(VERSION) # ---------------------------- # Automake X.Y traces this macro to ensure aclocal.m4 has been @@ -158,7 +162,7 @@ AC_DEFUN([AM_AUTOMAKE_VERSION], [am__api_version='1.11' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.11.1], [], +m4_if([$1], [1.11.6], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -174,19 +178,21 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.11.1])dnl +[AM_AUTOMAKE_VERSION([1.11.6])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- -# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# Copyright (C) 2001, 2003, 2005, 2011 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. +# serial 1 + # For projects using AC_CONFIG_AUX_DIR([foo]), Autoconf sets # $ac_aux_dir to `$srcdir/foo'. In other projects, it is set to # `$srcdir', `$srcdir/..', or `$srcdir/../..'. @@ -268,14 +274,14 @@ AC_CONFIG_COMMANDS_PRE( Usually this means the macro was only invoked conditionally.]]) fi])]) -# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009 -# Free Software Foundation, Inc. +# Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2009, +# 2010, 2011 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 10 +# serial 12 # There are a few dirty hacks below to avoid letting `AC_PROG_CC' be # written in clear, in which case automake, when reading aclocal.m4, @@ -315,6 +321,7 @@ AC_CACHE_CHECK([dependency style of $depcc], # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. + rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. @@ -379,7 +386,7 @@ AC_CACHE_CHECK([dependency style of $depcc], break fi ;; - msvisualcpp | msvcmsys) + msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok `-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. @@ -444,10 +451,13 @@ AC_DEFUN([AM_DEP_TRACK], if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' + am__nodep='_no' fi AM_CONDITIONAL([AMDEP], [test "x$enable_dependency_tracking" != xno]) AC_SUBST([AMDEPBACKSLASH])dnl _AM_SUBST_NOTMAKE([AMDEPBACKSLASH])dnl +AC_SUBST([am__nodep])dnl +_AM_SUBST_NOTMAKE([am__nodep])dnl ]) # Generate code to set up dependency tracking. -*- Autoconf -*- @@ -669,12 +679,15 @@ for _am_header in $config_headers :; do done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) -# Copyright (C) 2001, 2003, 2005, 2008 Free Software Foundation, Inc. +# Copyright (C) 2001, 2003, 2005, 2008, 2011 Free Software Foundation, +# Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. +# serial 1 + # AM_PROG_INSTALL_SH # ------------------ # Define $install_sh. @@ -714,8 +727,8 @@ AC_SUBST([am__leading_dot])]) # Add --enable-maintainer-mode option to configure. -*- Autoconf -*- # From Jim Meyering -# Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2008 -# Free Software Foundation, Inc. +# Copyright (C) 1996, 1998, 2000, 2001, 2002, 2003, 2004, 2005, 2008, +# 2011 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -735,7 +748,7 @@ AC_DEFUN([AM_MAINTAINER_MODE], [disable], [m4_define([am_maintainer_other], [enable])], [m4_define([am_maintainer_other], [enable]) m4_warn([syntax], [unexpected argument to AM@&t@_MAINTAINER_MODE: $1])]) -AC_MSG_CHECKING([whether to am_maintainer_other maintainer-specific portions of Makefiles]) +AC_MSG_CHECKING([whether to enable maintainer-specific portions of Makefiles]) dnl maintainer-mode's default is 'disable' unless 'enable' is passed AC_ARG_ENABLE([maintainer-mode], [ --][am_maintainer_other][-maintainer-mode am_maintainer_other make rules and dependencies not useful @@ -846,12 +859,15 @@ else fi ]) -# Copyright (C) 2003, 2004, 2005, 2006 Free Software Foundation, Inc. +# Copyright (C) 2003, 2004, 2005, 2006, 2011 Free Software Foundation, +# Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. +# serial 1 + # AM_PROG_MKDIR_P # --------------- # Check for `mkdir -p'. @@ -874,13 +890,14 @@ esac # Helper functions for option handling. -*- Autoconf -*- -# Copyright (C) 2001, 2002, 2003, 2005, 2008 Free Software Foundation, Inc. +# Copyright (C) 2001, 2002, 2003, 2005, 2008, 2010 Free Software +# Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 4 +# serial 5 # _AM_MANGLE_OPTION(NAME) # ----------------------- @@ -888,13 +905,13 @@ AC_DEFUN([_AM_MANGLE_OPTION], [[_AM_OPTION_]m4_bpatsubst($1, [[^a-zA-Z0-9_]], [_])]) # _AM_SET_OPTION(NAME) -# ------------------------------ +# -------------------- # Set option NAME. Presently that only means defining a flag for this option. AC_DEFUN([_AM_SET_OPTION], [m4_define(_AM_MANGLE_OPTION([$1]), 1)]) # _AM_SET_OPTIONS(OPTIONS) -# ---------------------------------- +# ------------------------ # OPTIONS is a space-separated list of Automake options. AC_DEFUN([_AM_SET_OPTIONS], [m4_foreach_w([_AM_Option], [$1], [_AM_SET_OPTION(_AM_Option)])]) @@ -970,12 +987,14 @@ Check your system clock]) fi AC_MSG_RESULT(yes)]) -# Copyright (C) 2001, 2003, 2005 Free Software Foundation, Inc. +# Copyright (C) 2001, 2003, 2005, 2011 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. +# serial 1 + # AM_PROG_INSTALL_STRIP # --------------------- # One issue with vendor `install' (even GNU) is that you can't @@ -998,13 +1017,13 @@ fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) -# Copyright (C) 2006, 2008 Free Software Foundation, Inc. +# Copyright (C) 2006, 2008, 2010 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. -# serial 2 +# serial 3 # _AM_SUBST_NOTMAKE(VARIABLE) # --------------------------- @@ -1013,13 +1032,13 @@ AC_SUBST([INSTALL_STRIP_PROGRAM])]) AC_DEFUN([_AM_SUBST_NOTMAKE]) # AM_SUBST_NOTMAKE(VARIABLE) -# --------------------------- +# -------------------------- # Public sister of _AM_SUBST_NOTMAKE. AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- -# Copyright (C) 2004, 2005 Free Software Foundation, Inc. +# Copyright (C) 2004, 2005, 2012 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1041,10 +1060,11 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # a tarball read from stdin. # $(am__untar) < result.tar AC_DEFUN([_AM_PROG_TAR], -[# Always define AMTAR for backward compatibility. -AM_MISSING_PROG([AMTAR], [tar]) +[# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AC_SUBST([AMTAR], ['$${TAR-tar}']) m4_if([$1], [v7], - [am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -'], + [am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -'], [m4_case([$1], [ustar],, [pax],, [m4_fatal([Unknown tar format])]) AC_MSG_CHECKING([how to create a $1 tar archive]) @@ -1118,4 +1138,5 @@ m4_include([m4/curses.m4]) m4_include([m4/libevent.m4]) m4_include([m4/lzo.m4]) m4_include([m4/openssl.m4]) +m4_include([m4/readline.m4]) m4_include([m4/zlib.m4]) diff --git a/config.guess b/config.guess index 40eaed4..d622a44 100755 --- a/config.guess +++ b/config.guess @@ -2,9 +2,9 @@ # Attempt to guess a canonical system name. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011 Free Software Foundation, Inc. +# 2011, 2012 Free Software Foundation, Inc. -timestamp='2011-05-11' +timestamp='2012-02-10' # This file is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by @@ -17,9 +17,7 @@ timestamp='2011-05-11' # General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA -# 02110-1301, USA. +# along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a @@ -57,8 +55,8 @@ GNU config.guess ($timestamp) Originally written by Per Bothner. Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free -Software Foundation, Inc. +2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 +Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -145,7 +143,7 @@ UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in *:NetBSD:*:*) # NetBSD (nbsd) targets should (where applicable) match one or - # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently # switched to ELF, *-*-netbsd* would select the old # object file format. This provides both forward @@ -792,13 +790,12 @@ EOF echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} exit ;; *:FreeBSD:*:*) - case ${UNAME_MACHINE} in - pc98) - echo i386-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in amd64) echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; *) - echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; esac exit ;; i*:CYGWIN*:*) @@ -807,6 +804,9 @@ EOF *:MINGW*:*) echo ${UNAME_MACHINE}-pc-mingw32 exit ;; + i*:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; i*:windows32*:*) # uname -m includes "-pc" on this system. echo ${UNAME_MACHINE}-mingw32 @@ -861,6 +861,13 @@ EOF i*86:Minix:*:*) echo ${UNAME_MACHINE}-pc-minix exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; alpha:Linux:*:*) case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in EV5) UNAME_MACHINE=alphaev5 ;; @@ -895,13 +902,16 @@ EOF echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; cris:Linux:*:*) - echo cris-axis-linux-gnu + echo ${UNAME_MACHINE}-axis-linux-gnu exit ;; crisv32:Linux:*:*) - echo crisv32-axis-linux-gnu + echo ${UNAME_MACHINE}-axis-linux-gnu exit ;; frv:Linux:*:*) - echo frv-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; i*86:Linux:*:*) LIBC=gnu @@ -943,7 +953,7 @@ EOF test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } ;; or32:Linux:*:*) - echo or32-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; padre:Linux:*:*) echo sparc-unknown-linux-gnu @@ -978,13 +988,13 @@ EOF echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; tile*:Linux:*:*) - echo ${UNAME_MACHINE}-tilera-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; vax:Linux:*:*) echo ${UNAME_MACHINE}-dec-linux-gnu exit ;; x86_64:Linux:*:*) - echo x86_64-unknown-linux-gnu + echo ${UNAME_MACHINE}-unknown-linux-gnu exit ;; xtensa*:Linux:*:*) echo ${UNAME_MACHINE}-unknown-linux-gnu @@ -1315,6 +1325,9 @@ EOF i*86:AROS:*:*) echo ${UNAME_MACHINE}-pc-aros exit ;; + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; esac #echo '(No uname command or uname output not recognized.)' 1>&2 diff --git a/config.h.in b/config.h.in index 3d4493a..ce11d29 100644 --- a/config.h.in +++ b/config.h.in @@ -6,6 +6,12 @@ /* Support for tunemu */ #undef ENABLE_TUNEMU +/* Support for UML */ +#undef ENABLE_UML + +/* Support for VDE */ +#undef ENABLE_VDE + /* Define to 1 if you have the header file. */ #undef HAVE_ARPA_INET_H @@ -52,6 +58,12 @@ /* DragonFly */ #undef HAVE_DRAGONFLY +/* Define to 1 if you have the `ECDH_compute_key' function. */ +#undef HAVE_ECDH_COMPUTE_KEY + +/* Define to 1 if you have the `ECDSA_verify' function. */ +#undef HAVE_ECDSA_VERIFY + /* Define to 1 if you have the header file. */ #undef HAVE_EVENT_H @@ -88,6 +100,9 @@ /* Define to 1 if you have the `socket' library (-lsocket). */ #undef HAVE_LIBSOCKET +/* Define to 1 if you have the header file. */ +#undef HAVE_LIBVDEPLUG_DYN_H + /* Linux */ #undef HAVE_LINUX @@ -148,6 +163,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_NETINET_TCP_H +/* Define to 1 if you have the header file. */ +#undef HAVE_NETPACKET_PACKET_H + /* Define to 1 if you have the header file. */ #undef HAVE_NET_ETHERNET_H @@ -175,6 +193,12 @@ /* OpenBSD */ #undef HAVE_OPENBSD +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_ECDH_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_OPENSSL_EC_H + /* Define to 1 if you have the header file. */ #undef HAVE_OPENSSL_ENGINE_H @@ -205,6 +229,15 @@ /* Define to 1 if you have the `RAND_pseudo_bytes' function. */ #undef HAVE_RAND_PSEUDO_BYTES +/* have readline support */ +#undef HAVE_READLINE + +/* Define to 1 if you have the header file. */ +#undef HAVE_READLINE_HISTORY_H + +/* Define to 1 if you have the header file. */ +#undef HAVE_READLINE_READLINE_H + /* Define to 1 if you have the `select' function. */ #undef HAVE_SELECT diff --git a/config.sub b/config.sub index 30fdca8..6205f84 100755 --- a/config.sub +++ b/config.sub @@ -2,9 +2,9 @@ # Configuration validation subroutine script. # Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, # 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, -# 2011 Free Software Foundation, Inc. +# 2011, 2012 Free Software Foundation, Inc. -timestamp='2011-03-23' +timestamp='2012-04-18' # This file is (in principle) common to ALL GNU software. # The presence of a machine in this file suggests that SOME GNU software @@ -21,9 +21,7 @@ timestamp='2011-03-23' # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA -# 02110-1301, USA. +# along with this program; if not, see . # # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a @@ -76,8 +74,8 @@ version="\ GNU config.sub ($timestamp) Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, -2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free -Software Foundation, Inc. +2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012 +Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." @@ -132,6 +130,10 @@ case $maybe_os in os=-$maybe_os basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` ;; + android-linux) + os=-linux-android + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; *) basic_machine=`echo $1 | sed 's/-[^-]*$//'` if [ $basic_machine != $1 ] @@ -223,6 +225,12 @@ case $os in -isc*) basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; -lynx*) os=-lynxos ;; @@ -247,17 +255,22 @@ case $basic_machine in # Some are omitted here because they have special meanings below. 1750a | 580 \ | a29k \ + | aarch64 | aarch64_be \ | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ | am33_2.0 \ | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr | avr32 \ + | be32 | be64 \ | bfin \ | c4x | clipper \ | d10v | d30v | dlx | dsp16xx \ + | epiphany \ | fido | fr30 | frv \ | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ | i370 | i860 | i960 | ia64 \ | ip2k | iq2000 \ + | le32 | le64 \ | lm32 \ | m32c | m32r | m32rle | m68000 | m68k | m88k \ | maxq | mb | microblaze | mcore | mep | metag \ @@ -291,7 +304,7 @@ case $basic_machine in | pdp10 | pdp11 | pj | pjl \ | powerpc | powerpc64 | powerpc64le | powerpcle \ | pyramid \ - | rx \ + | rl78 | rx \ | score \ | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ | sh64 | sh64le \ @@ -300,7 +313,7 @@ case $basic_machine in | spu \ | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ | ubicom32 \ - | v850 | v850e \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ | we32k \ | x86 | xc16x | xstormy16 | xtensa \ | z8k | z80) @@ -315,8 +328,7 @@ case $basic_machine in c6x) basic_machine=tic6x-unknown ;; - m6811 | m68hc11 | m6812 | m68hc12 | picochip) - # Motorola 68HC11/12. + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | picochip) basic_machine=$basic_machine-unknown os=-none ;; @@ -329,7 +341,10 @@ case $basic_machine in strongarm | thumb | xscale) basic_machine=arm-unknown ;; - + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; xscaleeb) basic_machine=armeb-unknown ;; @@ -352,11 +367,13 @@ case $basic_machine in # Recognize the basic CPU types with company name. 580-* \ | a29k-* \ + | aarch64-* | aarch64_be-* \ | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ | avr-* | avr32-* \ + | be32-* | be64-* \ | bfin-* | bs2000-* \ | c[123]* | c30-* | [cjt]90-* | c4x-* \ | clipper-* | craynv-* | cydra-* \ @@ -365,8 +382,10 @@ case $basic_machine in | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ | h8300-* | h8500-* \ | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ | i*86-* | i860-* | i960-* | ia64-* \ | ip2k-* | iq2000-* \ + | le32-* | le64-* \ | lm32-* \ | m32c-* | m32r-* | m32rle-* \ | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ @@ -400,7 +419,7 @@ case $basic_machine in | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ | pyramid-* \ - | romp-* | rs6000-* | rx-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ @@ -408,10 +427,11 @@ case $basic_machine in | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \ | tahoe-* \ | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ - | tile-* | tilegx-* \ + | tile*-* \ | tron-* \ | ubicom32-* \ - | v850-* | v850e-* | vax-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ | we32k-* \ | x86-* | x86_64-* | xc16x-* | xps100-* \ | xstormy16-* | xtensa*-* \ @@ -711,7 +731,6 @@ case $basic_machine in i370-ibm* | ibm*) basic_machine=i370-ibm ;; -# I'm not sure what "Sysv32" means. Should this be sysv3.2? i*86v32) basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` os=-sysv32 @@ -808,10 +827,18 @@ case $basic_machine in ms1-*) basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` ;; + msys) + basic_machine=i386-pc + os=-msys + ;; mvs) basic_machine=i370-ibm os=-mvs ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; ncr3000) basic_machine=i486-ncr os=-sysv4 @@ -1120,13 +1147,8 @@ case $basic_machine in basic_machine=t90-cray os=-unicos ;; - # This must be matched before tile*. - tilegx*) - basic_machine=tilegx-unknown - os=-linux-gnu - ;; tile*) - basic_machine=tile-unknown + basic_machine=$basic_machine-unknown os=-linux-gnu ;; tx39) @@ -1336,7 +1358,7 @@ case $os in | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ | -chorusos* | -chorusrdb* | -cegcc* \ - | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ | -mingw32* | -linux-gnu* | -linux-android* \ | -linux-newlib* | -linux-uclibc* \ | -uxpv* | -beos* | -mpeix* | -udk* \ @@ -1521,6 +1543,9 @@ case $basic_machine in c4x-* | tic4x-*) os=-coff ;; + hexagon-*) + os=-elf + ;; tic54x-*) os=-coff ;; @@ -1548,9 +1573,6 @@ case $basic_machine in ;; m68000-sun) os=-sunos3 - # This also exists in the configure program, but was not the - # default. - # os=-sunos4 ;; m68*-cisco) os=-aout diff --git a/configure b/configure index a87b0e9..ecbfc43 100755 --- a/configure +++ b/configure @@ -1,11 +1,9 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.68. +# Generated by GNU Autoconf 2.69. # # -# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, -# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software -# Foundation, Inc. +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. # # # This configure script is free software; the Free Software Foundation @@ -134,6 +132,31 @@ export LANGUAGE # CDPATH. (unset CDPATH) >/dev/null 2>&1 && unset CDPATH +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} if test "x$CONFIG_SHELL" = x; then as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : emulate sh @@ -167,7 +190,8 @@ if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : else exitcode=1; echo positional parameters were not saved. fi -test x\$exitcode = x0 || exit 1" +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && @@ -212,21 +236,25 @@ IFS=$as_save_IFS if test "x$CONFIG_SHELL" != x; then : - # We cannot yet assume a decent shell, so we have to provide a - # neutralization value for shells without unset; and this also - # works around shells that cannot unset nonexistent variables. - # Preserve -v and -x to the replacement shell. - BASH_ENV=/dev/null - ENV=/dev/null - (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV - export CONFIG_SHELL - case $- in # (((( - *v*x* | *x*v* ) as_opts=-vx ;; - *v* ) as_opts=-v ;; - *x* ) as_opts=-x ;; - * ) as_opts= ;; - esac - exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 fi if test x$as_have_required = xno; then : @@ -328,6 +356,14 @@ $as_echo X"$as_dir" | } # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p # as_fn_append VAR VALUE # ---------------------- # Append the text in VALUE to the end of the definition contained in VAR. Take @@ -449,6 +485,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits chmod +x "$as_me.lineno" || { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec # Don't try to exec as it changes $[0], causing all sort of problems # (the dirname of $[0] is not the place where we might find the # original and so on. Autoconf is especially sensitive to this). @@ -483,16 +523,16 @@ if (echo >conf$$.file) 2>/dev/null; then # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -p'. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -p' + as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else - as_ln_s='cp -p' + as_ln_s='cp -pR' fi else - as_ln_s='cp -p' + as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null @@ -504,28 +544,8 @@ else as_mkdir_p=false fi -if test -x / >/dev/null 2>&1; then - as_test_x='test -x' -else - if ls -dL / >/dev/null 2>&1; then - as_ls_L_option=L - else - as_ls_L_option= - fi - as_test_x=' - eval sh -c '\'' - if test -d "$1"; then - test -d "$1/."; - else - case $1 in #( - -*)set "./$1";; - esac; - case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( - ???[sx]*):;;*)false;;esac;fi - '\'' sh - ' -fi -as_executable_p=$as_test_x +as_test_x='test -x' +as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" @@ -607,9 +627,14 @@ INCLUDES LIBGCRYPT_LIBS LIBGCRYPT_CFLAGS LIBGCRYPT_CONFIG +READLINE_LIBS CURSES_LIBS TUNEMU_FALSE TUNEMU_TRUE +VDE_FALSE +VDE_TRUE +UML_FALSE +UML_TRUE host_os host_vendor host_cpu @@ -626,6 +651,7 @@ MAINTAINER_MODE_TRUE am__fastdepCC_FALSE am__fastdepCC_TRUE CCDEPMODE +am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE @@ -708,6 +734,8 @@ ac_user_opts=' enable_option_checking enable_dependency_tracking enable_maintainer_mode +enable_uml +enable_vde enable_tunemu with_windows2000 with_libgcrypt @@ -715,6 +743,10 @@ enable_curses with_curses with_curses_include with_curses_lib +enable_readline +with_readline +with_readline_include +with_readline_lib with_libevent with_libevent_include with_libevent_lib @@ -1196,8 +1228,6 @@ target=$target_alias if test "x$host_alias" != x; then if test "x$build_alias" = x; then cross_compiling=maybe - $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. - If a cross compiler is detected then cross compile mode will be used" >&2 elif test "x$build_alias" != "x$host_alias"; then cross_compiling=yes fi @@ -1363,23 +1393,30 @@ Optional Features: --enable-dependency-tracking do not reject slow dependency extractors --enable-maintainer-mode enable make rules and dependencies not useful (and sometimes confusing) to the casual installer - --enable-tunemu enable support for the tunemu driver + --disable-uml enable support for User Mode Linux + --disable-vde enable support for Virtual Distributed Ethernet + --disable-tunemu enable support for the tunemu driver --disable-curses disable curses support + --disable-readline disable readline support --disable-zlib disable zlib compression support --disable-lzo disable lzo compression support - --enable-jumbograms enable support for jumbograms (packets up to 9000 + --disable-jumbograms enable support for jumbograms (packets up to 9000 bytes) Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] --without-PACKAGE do not use PACKAGE (same as --with-PACKAGE=no) - --with-windows2000 compile with support for Windows 2000. This disables + --without-windows2000 compile with support for Windows 2000. This disables support for tunneling over existing IPv6 networks. --with-libgcrypt enable use of libgcrypt instead of OpenSSL] --with-curses=DIR curses base directory, or: --with-curses-include=DIR curses headers directory --with-curses-lib=DIR curses library directory + --with-readline=DIR readline base directory, or: + --with-readline-include=DIR + readline headers directory + --with-readline-lib=DIR readline library directory --with-libevent=DIR libevent base directory, or: --with-libevent-include=DIR libevent headers directory @@ -1475,9 +1512,9 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF configure -generated by GNU Autoconf 2.68 +generated by GNU Autoconf 2.69 -Copyright (C) 2010 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This configure script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it. _ACEOF @@ -1804,7 +1841,7 @@ $as_echo "$ac_try_echo"; } >&5 test ! -s conftest.err } && test -s conftest$ac_exeext && { test "$cross_compiling" = yes || - $as_test_x conftest$ac_exeext + test -x conftest$ac_exeext }; then : ac_retval=0 else @@ -1940,7 +1977,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. It was created by $as_me, which was -generated by GNU Autoconf 2.68. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2310,7 +2347,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -2350,7 +2387,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="gcc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -2403,7 +2440,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="${ac_tool_prefix}cc" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -2444,7 +2481,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then ac_prog_rejected=yes continue @@ -2502,7 +2539,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_CC="$ac_tool_prefix$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -2546,7 +2583,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_CC="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -2992,8 +3029,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ #include #include -#include -#include +struct stat; /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ struct buf { int x; }; FILE * (*rcsopen) (struct buf *, struct stat *, int); @@ -3233,7 +3269,7 @@ do for ac_prog in grep ggrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" - { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue + as_fn_executable_p "$ac_path_GREP" || continue # Check for GNU ac_path_GREP and select it if it is found. # Check for GNU $ac_path_GREP case `"$ac_path_GREP" --version 2>&1` in @@ -3299,7 +3335,7 @@ do for ac_prog in egrep; do for ac_exec_ext in '' $ac_executable_extensions; do ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" - { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue + as_fn_executable_p "$ac_path_EGREP" || continue # Check for GNU ac_path_EGREP and select it if it is found. # Check for GNU $ac_path_EGREP case `"$ac_path_EGREP" --version 2>&1` in @@ -3506,8 +3542,8 @@ else cat confdefs.h - <<_ACEOF >conftest.$ac_ext /* end confdefs.h. */ -# define __EXTENSIONS__ 1 - $ac_includes_default +# define __EXTENSIONS__ 1 + $ac_includes_default int main () { @@ -3606,7 +3642,7 @@ case $as_dir/ in #(( # by default. for ac_prog in ginstall scoinst install; do for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext"; then if test $ac_prog = install && grep dspmsg "$as_dir/$ac_prog$ac_exec_ext" >/dev/null 2>&1; then # AIX install. It has an incompatible calling convention. @@ -3775,7 +3811,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_STRIP="${ac_tool_prefix}strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -3815,7 +3851,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_STRIP="strip" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -3866,7 +3902,7 @@ do test -z "$as_dir" && as_dir=. for ac_prog in mkdir gmkdir; do for ac_exec_ext in '' $ac_executable_extensions; do - { test -f "$as_dir/$ac_prog$ac_exec_ext" && $as_test_x "$as_dir/$ac_prog$ac_exec_ext"; } || continue + as_fn_executable_p "$as_dir/$ac_prog$ac_exec_ext" || continue case `"$as_dir/$ac_prog$ac_exec_ext" --version 2>&1` in #( 'mkdir (GNU coreutils) '* | \ 'mkdir (coreutils) '* | \ @@ -3919,7 +3955,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_AWK="$ac_prog" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -4035,6 +4071,7 @@ fi if test "x$enable_dependency_tracking" != xno; then am_depcomp="$ac_aux_dir/depcomp" AMDEPBACKSLASH='\' + am__nodep='_no' fi if test "x$enable_dependency_tracking" != xno; then AMDEP_TRUE= @@ -4067,7 +4104,7 @@ fi # Define the identity of the package. PACKAGE=tinc - VERSION=1.1pre2 + VERSION=1.1pre3 cat >>confdefs.h <<_ACEOF @@ -4097,11 +4134,11 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # We need awk for the "check" target. The system "awk" is bad on # some platforms. -# Always define AMTAR for backward compatibility. +# Always define AMTAR for backward compatibility. Yes, it's still used +# in the wild :-( We should find a proper way to deprecate it ... +AMTAR='$${TAR-tar}' -AMTAR=${AMTAR-"${am_missing_run}tar"} - -am__tar='${AMTAR} chof - "$$tardir"'; am__untar='${AMTAR} xf -' +am__tar='$${TAR-tar} chof - "$$tardir"' am__untar='$${TAR-tar} xf -' @@ -4119,6 +4156,7 @@ else # instance it was reported that on HP-UX the gcc test will end up # making a dummy file named `D' -- because `-MD' means `put the output # in D'. + rm -rf conftest.dir mkdir conftest.dir # Copy depcomp to subdir because otherwise we won't find it if we're # using a relative directory. @@ -4178,7 +4216,7 @@ else break fi ;; - msvisualcpp | msvcmsys) + msvc7 | msvc7msys | msvisualcpp | msvcmsys) # This compiler won't grok `-c -o', but also, the minuso test has # not run yet. These depmodes are late enough in the game, and # so weak that their functioning should not be impacted. @@ -4414,7 +4452,7 @@ main () return 0; } _ACEOF -for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -xc99=all -qlanglvl=extc99 +for ac_arg in '' -std=gnu99 -std=c99 -c99 -AC99 -D_STDC_C99= -qlanglvl=extc99 do CC="$ac_save_CC $ac_arg" if ac_fn_c_try_compile "$LINENO"; then : @@ -4611,7 +4649,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -4651,7 +4689,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_prog_ac_ct_RANLIB="ranlib" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -4829,13 +4867,70 @@ $as_echo "#define HAVE_MINGW 1" >>confdefs.h ;; esac +# Check whether --enable-uml was given. +if test "${enable_uml+set}" = set; then : + enableval=$enable_uml; if test "x$enable_uml" = "xyes"; then : + +$as_echo "#define ENABLE_UML 1" >>confdefs.h + + uml=true + +else + uml=false +fi + +else + uml=false + +fi + + +# Check whether --enable-vde was given. +if test "${enable_vde+set}" = set; then : + enableval=$enable_vde; if test "x$enable_vde" = "xyes"; then : + for ac_header in libvdeplug_dyn.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "libvdeplug_dyn.h" "ac_cv_header_libvdeplug_dyn_h" "$ac_includes_default" +if test "x$ac_cv_header_libvdeplug_dyn_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBVDEPLUG_DYN_H 1 +_ACEOF + +else + as_fn_error $? "VDE plug header files not found." "$LINENO" 5; break +fi + +done + + +$as_echo "#define ENABLE_VDE 1" >>confdefs.h + + vde=true + +else + vde=false +fi + +else + vde=false + +fi + + # Check whether --enable-tunemu was given. if test "${enable_tunemu+set}" = set; then : - enableval=$enable_tunemu; + enableval=$enable_tunemu; if test "x$enable_tunemu" = "xyes"; then : + $as_echo "#define ENABLE_TUNEMU 1" >>confdefs.h - tunemu=true + tunemu=true +else + tunemu=false +fi + +else + tunemu=false fi @@ -4843,13 +4938,32 @@ fi # Check whether --with-windows2000 was given. if test "${with_windows2000+set}" = set; then : - withval=$with_windows2000; + withval=$with_windows2000; if test "x$with_windows2000" = "xyes"; then : + $as_echo "#define WITH_WINDOWS2000 1" >>confdefs.h +fi + fi + if test "$uml" = true; then + UML_TRUE= + UML_FALSE='#' +else + UML_TRUE='#' + UML_FALSE= +fi + + if test "$vde" = true; then + VDE_TRUE= + VDE_FALSE='#' +else + VDE_TRUE='#' + VDE_FALSE= +fi + if test "$tunemu" = true; then TUNEMU_TRUE= TUNEMU_FALSE='#' @@ -5078,7 +5192,7 @@ fi done -for ac_header in net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h +for ac_header in net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h netpacket/packet.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_compile "$LINENO" "$ac_header" "$as_ac_Header" "#include \"have.h\" @@ -5135,11 +5249,11 @@ else int main () { -/* FIXME: Include the comments suggested by Paul. */ + #ifndef __cplusplus - /* Ultrix mips cc rejects this. */ + /* Ultrix mips cc rejects this sort of thing. */ typedef int charset[2]; - const charset cs; + const charset cs = { 0, 0 }; /* SunOS 4.1.1 cc rejects this. */ char const *const *pcpcc; char **ppc; @@ -5156,8 +5270,9 @@ main () ++pcpcc; ppc = (char**) pcpcc; pcpcc = (char const *const *) ppc; - { /* SCO 3.2v4 cc rejects this. */ - char *t; + { /* SCO 3.2v4 cc rejects this sort of thing. */ + char tx; + char *t = &tx; char const *s = 0 ? (char *) 0 : (char const *) 0; *t++ = 0; @@ -5173,10 +5288,10 @@ main () iptr p = 0; ++p; } - { /* AIX XL C 1.02.0.0 rejects this saying + { /* AIX XL C 1.02.0.0 rejects this sort of thing, saying "k.c", line 2.27: 1506-025 (S) Operand must be a modifiable lvalue. */ - struct s { int j; const int *ap[3]; }; - struct s *b; b->j = 5; + struct s { int j; const int *ap[3]; } bx; + struct s *b = &bx; b->j = 5; } { /* ULTRIX-32 V3.1 (Rev 9) vcc rejects this */ const int foo = 10; @@ -5925,6 +6040,111 @@ fi + # Check whether --enable-readline was given. +if test "${enable_readline+set}" = set; then : + enableval=$enable_readline; +fi + + if test "x$enable_readline" != "xno"; then : + + +$as_echo "#define HAVE_READLINE 1" >>confdefs.h + + readline=true + +# Check whether --with-readline was given. +if test "${with_readline+set}" = set; then : + withval=$with_readline; readline="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib" + +fi + + + +# Check whether --with-readline-include was given. +if test "${with_readline_include+set}" = set; then : + withval=$with_readline_include; readline_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval" + +fi + + + +# Check whether --with-readline-lib was given. +if test "${with_readline_lib+set}" = set; then : + withval=$with_readline_lib; readline_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval" + +fi + + + for ac_header in readline/readline.h readline/history.h +do : + as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` +ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" +if eval test \"x\$"$as_ac_Header"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_header" | $as_tr_cpp` 1 +_ACEOF + +else + as_fn_error $? "\"readline header files not found.\"" "$LINENO" 5; break + +fi + +done + + + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for readline in -lreadline" >&5 +$as_echo_n "checking for readline in -lreadline... " >&6; } +if ${ac_cv_lib_readline_readline+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lreadline $CURSES_LIBS + $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char readline (); +int +main () +{ +return readline (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_readline_readline=yes +else + ac_cv_lib_readline_readline=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_readline_readline" >&5 +$as_echo "$ac_cv_lib_readline_readline" >&6; } +if test "x$ac_cv_lib_readline_readline" = xyes; then : + READLINE_LIBS="-lreadline" +else + as_fn_error $? "\"readline library not found.\"" "$LINENO" 5 +fi + + +fi + + + + # Check whether --with-libevent was given. if test "${with_libevent+set}" = set; then : @@ -6326,7 +6546,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_LIBGCRYPT_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -6369,7 +6589,7 @@ do IFS=$as_save_IFS test -z "$as_dir" && as_dir=. for ac_exec_ext in '' $ac_executable_extensions; do - if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ac_cv_path_ac_pt_LIBGCRYPT_CONFIG="$as_dir/$ac_word$ac_exec_ext" $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 break 2 @@ -6596,7 +6816,7 @@ if test "${with_openssl_lib+set}" = set; then : fi - for ac_header in openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h + for ac_header in openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h openssl/ecdh.h openssl/ec.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -6657,7 +6877,7 @@ else fi - for ac_func in RAND_pseudo_bytes EVP_EncryptInit_ex + for ac_func in RAND_pseudo_bytes EVP_EncryptInit_ex ECDH_compute_key ECDSA_verify do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -6688,9 +6908,12 @@ fi # Check whether --enable-jumbograms was given. if test "${enable_jumbograms+set}" = set; then : - enableval=$enable_jumbograms; + enableval=$enable_jumbograms; if test "x$enable_jumbograms" = "xyes"; then : + $as_echo "#define ENABLE_JUMBOGRAMS 1" >>confdefs.h +fi + fi @@ -6829,6 +7052,14 @@ if test -z "${MAINTAINER_MODE_TRUE}" && test -z "${MAINTAINER_MODE_FALSE}"; then as_fn_error $? "conditional \"MAINTAINER_MODE\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${UML_TRUE}" && test -z "${UML_FALSE}"; then + as_fn_error $? "conditional \"UML\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi +if test -z "${VDE_TRUE}" && test -z "${VDE_FALSE}"; then + as_fn_error $? "conditional \"VDE\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi if test -z "${TUNEMU_TRUE}" && test -z "${TUNEMU_FALSE}"; then as_fn_error $? "conditional \"TUNEMU\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 @@ -7131,16 +7362,16 @@ if (echo >conf$$.file) 2>/dev/null; then # ... but there are two gotchas: # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. - # In both cases, we have to default to `cp -p'. + # In both cases, we have to default to `cp -pR'. ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || - as_ln_s='cp -p' + as_ln_s='cp -pR' elif ln conf$$.file conf$$ 2>/dev/null; then as_ln_s=ln else - as_ln_s='cp -p' + as_ln_s='cp -pR' fi else - as_ln_s='cp -p' + as_ln_s='cp -pR' fi rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file rmdir conf$$.dir 2>/dev/null @@ -7200,28 +7431,16 @@ else as_mkdir_p=false fi -if test -x / >/dev/null 2>&1; then - as_test_x='test -x' -else - if ls -dL / >/dev/null 2>&1; then - as_ls_L_option=L - else - as_ls_L_option= - fi - as_test_x=' - eval sh -c '\'' - if test -d "$1"; then - test -d "$1/."; - else - case $1 in #( - -*)set "./$1";; - esac; - case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( - ???[sx]*):;;*)false;;esac;fi - '\'' sh - ' -fi -as_executable_p=$as_test_x + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p # Sed expression to map a string onto a valid CPP name. as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" @@ -7243,7 +7462,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # values after options handling. ac_log=" This file was extended by $as_me, which was -generated by GNU Autoconf 2.68. Invocation command line was +generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES CONFIG_HEADERS = $CONFIG_HEADERS @@ -7309,10 +7528,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ config.status -configured by $0, generated by GNU Autoconf 2.68, +configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" -Copyright (C) 2010 Free Software Foundation, Inc. +Copyright (C) 2012 Free Software Foundation, Inc. This config.status script is free software; the Free Software Foundation gives unlimited permission to copy, distribute and modify it." @@ -7403,7 +7622,7 @@ fi _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 if \$ac_cs_recheck; then - set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion shift \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 CONFIG_SHELL='$SHELL' diff --git a/configure.in b/configure.in index 2e519b0..5781537 100644 --- a/configure.in +++ b/configure.in @@ -4,7 +4,7 @@ AC_PREREQ(2.61) AC_INIT AC_CONFIG_SRCDIR([src/tincd.c]) AC_GNU_SOURCE -AM_INIT_AUTOMAKE(tinc, 1.1pre2) +AM_INIT_AUTOMAKE(tinc, 1.1pre3) AC_CONFIG_HEADERS([config.h]) AM_MAINTAINER_MODE @@ -73,18 +73,49 @@ case $host_os in ;; esac +AC_ARG_ENABLE(uml, + AS_HELP_STRING([--disable-uml], [enable support for User Mode Linux]), + [ AS_IF([test "x$enable_uml" = "xyes"], + [ AC_DEFINE(ENABLE_UML, 1, [Support for UML]) + uml=true + ], + [uml=false]) + ], + [uml=false] +) + +AC_ARG_ENABLE(vde, + AS_HELP_STRING([--disable-vde], [enable support for Virtual Distributed Ethernet]), + [ AS_IF([test "x$enable_vde" = "xyes"], + [ AC_CHECK_HEADERS(libvdeplug_dyn.h, [], [AC_MSG_ERROR([VDE plug header files not found.]); break]) + AC_DEFINE(ENABLE_VDE, 1, [Support for VDE]) + vde=true + ], + [vde=false]) + ], + [vde=false] +) + AC_ARG_ENABLE(tunemu, - AS_HELP_STRING([--enable-tunemu], [enable support for the tunemu driver]), - [ AC_DEFINE(ENABLE_TUNEMU, 1, [Support for tunemu]) - tunemu=true - ] + AS_HELP_STRING([--disable-tunemu], [enable support for the tunemu driver]), + [ AS_IF([test "x$enable_tunemu" = "xyes"], + [ AC_DEFINE(ENABLE_TUNEMU, 1, [Support for tunemu]) + tunemu=true + ], + [tunemu=false]) + ], + [tunemu=false] ) AC_ARG_WITH(windows2000, - AS_HELP_STRING([--with-windows2000], [compile with support for Windows 2000. This disables support for tunneling over existing IPv6 networks.]), - [AC_DEFINE(WITH_WINDOWS2000, 1, [Compile with support for Windows 2000])] + AS_HELP_STRING([--without-windows2000], [compile with support for Windows 2000. This disables support for tunneling over existing IPv6 networks.]), + [ AS_IF([test "x$with_windows2000" = "xyes"], + [AC_DEFINE(WITH_WINDOWS2000, 1, [Compile with support for Windows 2000])]) + ] ) +AM_CONDITIONAL(UML, test "$uml" = true) +AM_CONDITIONAL(VDE, test "$vde" = true) AM_CONDITIONAL(TUNEMU, test "$tunemu" = true) AC_CACHE_SAVE @@ -101,7 +132,7 @@ dnl We do this in multiple stages, because unlike Linux all the other operating AC_HEADER_STDC AC_CHECK_HEADERS([stdbool.h syslog.h sys/file.h sys/ioctl.h sys/mman.h sys/param.h sys/resource.h sys/socket.h sys/time.h sys/uio.h sys/un.h sys/wait.h netdb.h arpa/inet.h dirent.h]) -AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h], +AC_CHECK_HEADERS([net/if.h net/if_types.h linux/if_tun.h net/if_tun.h net/tun/if_tun.h net/if_tap.h net/tap/if_tap.h net/ethernet.h net/if_arp.h netinet/in_systm.h netinet/in.h netinet/in6.h time.h netpacket/packet.h], [], [], [#include "have.h"] ) AC_CHECK_HEADERS([netinet/if_ether.h netinet/ip.h netinet/ip6.h], @@ -151,6 +182,7 @@ dnl These are defined in files in m4/ AC_ARG_WITH(libgcrypt, AC_HELP_STRING([--with-libgcrypt], [enable use of libgcrypt instead of OpenSSL])], []) tinc_CURSES +tinc_READLINE tinc_LIBEVENT tinc_ZLIB tinc_LZO @@ -166,8 +198,10 @@ fi dnl Check if support for jumbograms is requested AC_ARG_ENABLE(jumbograms, - AS_HELP_STRING([--enable-jumbograms], [enable support for jumbograms (packets up to 9000 bytes)]), - [ AC_DEFINE(ENABLE_JUMBOGRAMS, 1, [Support for jumbograms (packets up to 9000 bytes)]) ] + AS_HELP_STRING([--disable-jumbograms], [enable support for jumbograms (packets up to 9000 bytes)]), + [ AS_IF([test "x$enable_jumbograms" = "xyes"], + [ AC_DEFINE(ENABLE_JUMBOGRAMS, 1, [Support for jumbograms (packets up to 9000 bytes)]) ]) + ] ) AC_SUBST(INCLUDES) diff --git a/depcomp b/depcomp index df8eea7..25a39e6 100755 --- a/depcomp +++ b/depcomp @@ -1,10 +1,10 @@ #! /bin/sh # depcomp - compile a program generating dependencies as side-effects -scriptversion=2009-04-28.21; # UTC +scriptversion=2012-03-27.16; # UTC -# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009 Free -# Software Foundation, Inc. +# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2006, 2007, 2009, 2010, +# 2011, 2012 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ scriptversion=2009-04-28.21; # UTC case $1 in '') - echo "$0: No command. Try \`$0 --help' for more information." 1>&2 + echo "$0: No command. Try '$0 --help' for more information." 1>&2 exit 1; ;; -h | --h*) @@ -40,11 +40,11 @@ as side-effects. Environment variables: depmode Dependency tracking mode. - source Source file read by `PROGRAMS ARGS'. - object Object file output by `PROGRAMS ARGS'. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. DEPDIR directory where to store dependencies. depfile Dependency file to output. - tmpdepfile Temporary file to use when outputing dependencies. + tmpdepfile Temporary file to use when outputting dependencies. libtool Whether libtool is used (yes/no). Report bugs to . @@ -57,6 +57,12 @@ EOF ;; esac +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' + if test -z "$depmode" || test -z "$source" || test -z "$object"; then echo "depcomp: Variables source, object and depmode must be set" 1>&2 exit 1 @@ -90,10 +96,24 @@ if test "$depmode" = msvcmsys; then # This is just like msvisualcpp but w/o cygpath translation. # Just convert the backslash-escaped backslashes to single forward # slashes to satisfy depend.m4 - cygpath_u="sed s,\\\\\\\\,/,g" + cygpath_u='sed s,\\\\,/,g' depmode=msvisualcpp fi +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency informations. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + case "$depmode" in gcc3) ## gcc 3 implements dependency tracking that does exactly what @@ -148,20 +168,21 @@ gcc) ## The second -e expression handles DOS-style file names with drive letters. sed -e 's/^[^:]*: / /' \ -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" -## This next piece of magic avoids the `deleted header file' problem. +## This next piece of magic avoids the "deleted header file" problem. ## The problem is that when a header file which appears in a .P file ## is deleted, the dependency causes make to die (because there is ## typically no way to rebuild the header). We avoid this by adding ## dummy dependencies for each header file. Too bad gcc doesn't do ## this for us directly. - tr ' ' ' -' < "$tmpdepfile" | -## Some versions of gcc put a space before the `:'. On the theory + tr ' ' "$nl" < "$tmpdepfile" | +## Some versions of gcc put a space before the ':'. On the theory ## that the space means something, we add a space to the output as -## well. +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. - sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" + sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" rm -f "$tmpdepfile" ;; @@ -193,18 +214,15 @@ sgi) # clever and replace this with sed code, as IRIX sed won't handle # lines with more than a fixed number of characters (4096 in # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; - # the IRIX cc adds comments like `#:fec' to the end of the + # the IRIX cc adds comments like '#:fec' to the end of the # dependency line. - tr ' ' ' -' < "$tmpdepfile" \ + tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' | \ - tr ' -' ' ' >> "$depfile" + tr "$nl" ' ' >> "$depfile" echo >> "$depfile" # The second pass generates a dummy entry for each header file. - tr ' ' ' -' < "$tmpdepfile" \ + tr ' ' "$nl" < "$tmpdepfile" \ | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ >> "$depfile" else @@ -216,10 +234,17 @@ sgi) rm -f "$tmpdepfile" ;; +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + aix) # The C for AIX Compiler uses -M and outputs the dependencies # in a .u file. In older versions, this file always lives in the - # current directory. Also, the AIX compiler puts `$object:' at the + # current directory. Also, the AIX compiler puts '$object:' at the # start of each line; $object doesn't have directory information. # Version 6 uses the directory in both cases. dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` @@ -249,12 +274,11 @@ aix) test -f "$tmpdepfile" && break done if test -f "$tmpdepfile"; then - # Each line is of the form `foo.o: dependent.h'. + # Each line is of the form 'foo.o: dependent.h'. # Do two passes, one to just change these to - # `$object: dependent.h' and one to simply `dependent.h:'. + # '$object: dependent.h' and one to simply 'dependent.h:'. sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" - # That's a tab and a space in the []. - sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + sed -e 's,^.*\.[a-z]*:['"$tab"' ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" else # The sourcefile does not contain any dependencies, so just # store a dummy comment line, to avoid errors with the Makefile @@ -265,23 +289,26 @@ aix) ;; icc) - # Intel's C compiler understands `-MD -MF file'. However on - # icc -MD -MF foo.d -c -o sub/foo.o sub/foo.c + # Intel's C compiler anf tcc (Tiny C Compiler) understand '-MD -MF file'. + # However on + # $CC -MD -MF foo.d -c -o sub/foo.o sub/foo.c # ICC 7.0 will fill foo.d with something like # foo.o: sub/foo.c # foo.o: sub/foo.h - # which is wrong. We want: + # which is wrong. We want # sub/foo.o: sub/foo.c # sub/foo.o: sub/foo.h # sub/foo.c: # sub/foo.h: # ICC 7.1 will output # foo.o: sub/foo.c sub/foo.h - # and will wrap long lines using \ : + # and will wrap long lines using '\': # foo.o: sub/foo.c ... \ # sub/foo.h ... \ # ... - + # tcc 0.9.26 (FIXME still under development at the moment of writing) + # will emit a similar output, but also prepend the continuation lines + # with horizontal tabulation characters. "$@" -MD -MF "$tmpdepfile" stat=$? if test $stat -eq 0; then : @@ -290,15 +317,21 @@ icc) exit $stat fi rm -f "$depfile" - # Each line is of the form `foo.o: dependent.h', - # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Each line is of the form 'foo.o: dependent.h', + # or 'foo.o: dep1.h dep2.h \', or ' dep3.h dep4.h \'. # Do two passes, one to just change these to - # `$object: dependent.h' and one to simply `dependent.h:'. - sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" | - sed -e 's/$/ :/' >> "$depfile" + # '$object: dependent.h' and one to simply 'dependent.h:'. + sed -e "s/^[ $tab][ $tab]*/ /" -e "s,^[^:]*:,$object :," \ + < "$tmpdepfile" > "$depfile" + sed ' + s/[ '"$tab"'][ '"$tab"']*/ /g + s/^ *// + s/ *\\*$// + s/^[^:]*: *// + /^$/d + /:$/d + s/$/ :/ + ' < "$tmpdepfile" >> "$depfile" rm -f "$tmpdepfile" ;; @@ -334,7 +367,7 @@ hp2) done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[a-z]*:,$object:," "$tmpdepfile" > "$depfile" - # Add `dependent.h:' lines. + # Add 'dependent.h:' lines. sed -ne '2,${ s/^ *// s/ \\*$// @@ -349,9 +382,9 @@ hp2) tru64) # The Tru64 compiler uses -MD to generate dependencies as a side - # effect. `cc -MD -o foo.o ...' puts the dependencies into `foo.o.d'. + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put - # dependencies in `foo.d' instead, so we check for that too. + # dependencies in 'foo.d' instead, so we check for that too. # Subdirectories are respected. dir=`echo "$object" | sed -e 's|/[^/]*$|/|'` test "x$dir" = "x$object" && dir= @@ -397,14 +430,59 @@ tru64) done if test -f "$tmpdepfile"; then sed -e "s,^.*\.[a-z]*:,$object:," < "$tmpdepfile" > "$depfile" - # That's a tab and a space in the []. - sed -e 's,^.*\.[a-z]*:[ ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" + sed -e 's,^.*\.[a-z]*:['"$tab"' ]*,,' -e 's,$,:,' < "$tmpdepfile" >> "$depfile" else echo "#dummy" > "$depfile" fi rm -f "$tmpdepfile" ;; +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test "$stat" = 0; then : + else + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + #nosideeffect) # This comment above is used by automake to tell side-effect # dependency tracking mechanisms from slower ones. @@ -422,7 +500,7 @@ dashmstdout) shift fi - # Remove `-o $object'. + # Remove '-o $object'. IFS=" " for arg do @@ -442,15 +520,14 @@ dashmstdout) done test -z "$dashmflag" && dashmflag=-M - # Require at least two characters before searching for `:' + # Require at least two characters before searching for ':' # in the target name. This is to cope with DOS-style filenames: - # a dependency such as `c:/foo/bar' could be seen as target `c' otherwise. + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. "$@" $dashmflag | - sed 's:^[ ]*[^: ][^:][^:]*\:[ ]*:'"$object"'\: :' > "$tmpdepfile" + sed 's:^['"$tab"' ]*[^:'"$tab"' ][^:][^:]*\:['"$tab"' ]*:'"$object"'\: :' > "$tmpdepfile" rm -f "$depfile" cat < "$tmpdepfile" > "$depfile" - tr ' ' ' -' < "$tmpdepfile" | \ + tr ' ' "$nl" < "$tmpdepfile" | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" @@ -503,9 +580,10 @@ makedepend) touch "$tmpdepfile" ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" rm -f "$depfile" - cat < "$tmpdepfile" > "$depfile" - sed '1,2d' "$tmpdepfile" | tr ' ' ' -' | \ + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + sed '1,2d' "$tmpdepfile" | tr ' ' "$nl" | \ ## Some versions of the HPUX 10.20 sed can't process this invocation ## correctly. Breaking it into two sed invocations is a workaround. sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' | sed -e 's/$/ :/' >> "$depfile" @@ -525,7 +603,7 @@ cpp) shift fi - # Remove `-o $object'. + # Remove '-o $object'. IFS=" " for arg do @@ -594,8 +672,8 @@ msvisualcpp) sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" rm -f "$depfile" echo "$object : \\" > "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s:: \1 \\:p' >> "$depfile" - echo " " >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" rm -f "$tmpdepfile" ;; diff --git a/doc/Makefile.am b/doc/Makefile.am index 8f0305e..2ada5d7 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -23,16 +23,16 @@ texi2html: tinc.texi texi2html -split=chapter tinc.texi tincd.8.html: tincd.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tincctl.8.html: tincctl.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tinc-gui.8.html: tinc-gui.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tinc.conf.5.html: tinc.conf.5 - w3mman2html $< > $@ + w3mman2html $? > $@ substitute = sed \ -e s,'@PACKAGE\@',"$(PACKAGE)",g \ @@ -41,18 +41,18 @@ substitute = sed \ -e s,'@localstatedir\@',"$(localstatedir)",g tincd.8: tincd.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tincctl.8: tincctl.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc-gui.8: tinc-gui.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc.conf.5: tinc.conf.5.in - $(substitute) $< > $@ + $(substitute) $? > $@ tincinclude.texi: tincinclude.texi.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc.texi: tincinclude.texi diff --git a/doc/Makefile.in b/doc/Makefile.in index b80f326..4610da9 100644 --- a/doc/Makefile.in +++ b/doc/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -15,6 +15,23 @@ @SET_MAKE@ VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -38,7 +55,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libevent.m4 \ $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/openssl.m4 \ - $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.in + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/zlib.m4 \ + $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d @@ -59,6 +77,11 @@ TEXI2PDF = $(TEXI2DVI) --pdf --batch MAKEINFOHTML = $(MAKEINFO) --html AM_MAKEINFOHTMLFLAGS = $(AM_MAKEINFOFLAGS) DVIPS = dvips +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac am__installdirs = "$(DESTDIR)$(infodir)" "$(DESTDIR)$(man5dir)" \ "$(DESTDIR)$(man8dir)" am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; @@ -82,6 +105,12 @@ am__nobase_list = $(am__nobase_strip_setup); \ am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } man5dir = $(mandir)/man5 man8dir = $(mandir)/man8 NROFF = nroff @@ -140,6 +169,7 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -304,9 +334,7 @@ uninstall-html-am: uninstall-info-am: @$(PRE_UNINSTALL) - @if test -d '$(DESTDIR)$(infodir)' && \ - (install-info --version && \ - install-info --version 2>&1 | sed 1q | grep -i -v debian) >/dev/null 2>&1; then \ + @if test -d '$(DESTDIR)$(infodir)' && $(am__can_run_installinfo); then \ list='$(INFO_DEPS)'; \ for file in $$list; do \ relfile=`echo "$$file" | sed 's|^.*/||'`; \ @@ -379,11 +407,18 @@ maintainer-clean-aminfo: done install-man5: $(man_MANS) @$(NORMAL_INSTALL) - test -z "$(man5dir)" || $(MKDIR_P) "$(DESTDIR)$(man5dir)" - @list=''; test -n "$(man5dir)" || exit 0; \ - { for i in $$list; do echo "$$i"; done; \ - l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ - sed -n '/\.5[a-z]*$$/p'; \ + @list1=''; \ + list2='$(man_MANS)'; \ + test -n "$(man5dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man5dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man5dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.5[a-z]*$$/p'; \ + fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ @@ -412,16 +447,21 @@ uninstall-man5: sed -n '/\.5[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^5][0-9a-z]*$$,5,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ - test -z "$$files" || { \ - echo " ( cd '$(DESTDIR)$(man5dir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(man5dir)" && rm -f $$files; } + dir='$(DESTDIR)$(man5dir)'; $(am__uninstall_files_from_dir) install-man8: $(man_MANS) @$(NORMAL_INSTALL) - test -z "$(man8dir)" || $(MKDIR_P) "$(DESTDIR)$(man8dir)" - @list=''; test -n "$(man8dir)" || exit 0; \ - { for i in $$list; do echo "$$i"; done; \ - l2='$(man_MANS)'; for i in $$l2; do echo "$$i"; done | \ - sed -n '/\.8[a-z]*$$/p'; \ + @list1=''; \ + list2='$(man_MANS)'; \ + test -n "$(man8dir)" \ + && test -n "`echo $$list1$$list2`" \ + || exit 0; \ + echo " $(MKDIR_P) '$(DESTDIR)$(man8dir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(man8dir)" || exit 1; \ + { for i in $$list1; do echo "$$i"; done; \ + if test -n "$$list2"; then \ + for i in $$list2; do echo "$$i"; done \ + | sed -n '/\.8[a-z]*$$/p'; \ + fi; \ } | while read p; do \ if test -f $$p; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; echo "$$p"; \ @@ -450,9 +490,7 @@ uninstall-man8: sed -n '/\.8[a-z]*$$/p'; \ } | sed -e 's,.*/,,;h;s,.*\.,,;s,^[^8][0-9a-z]*$$,8,;x' \ -e 's,\.[0-9a-z]*$$,,;$(transform);G;s,\n,.,'`; \ - test -z "$$files" || { \ - echo " ( cd '$(DESTDIR)$(man8dir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(man8dir)" && rm -f $$files; } + dir='$(DESTDIR)$(man8dir)'; $(am__uninstall_files_from_dir) tags: TAGS TAGS: @@ -523,10 +561,15 @@ install-am: all-am installcheck: installcheck-am install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: @@ -565,8 +608,11 @@ install-dvi: install-dvi-am install-dvi-am: $(DVIS) @$(NORMAL_INSTALL) - test -z "$(dvidir)" || $(MKDIR_P) "$(DESTDIR)$(dvidir)" @list='$(DVIS)'; test -n "$(dvidir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(dvidir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(dvidir)" || exit 1; \ + fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -581,18 +627,22 @@ install-html: install-html-am install-html-am: $(HTMLS) @$(NORMAL_INSTALL) - test -z "$(htmldir)" || $(MKDIR_P) "$(DESTDIR)$(htmldir)" @list='$(HTMLS)'; list2=; test -n "$(htmldir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(htmldir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(htmldir)" || exit 1; \ + fi; \ for p in $$list; do \ if test -f "$$p" || test -d "$$p"; then d=; else d="$(srcdir)/"; fi; \ $(am__strip_dir) \ - if test -d "$$d$$p"; then \ + d2=$$d$$p; \ + if test -d "$$d2"; then \ echo " $(MKDIR_P) '$(DESTDIR)$(htmldir)/$$f'"; \ $(MKDIR_P) "$(DESTDIR)$(htmldir)/$$f" || exit 1; \ - echo " $(INSTALL_DATA) '$$d$$p'/* '$(DESTDIR)$(htmldir)/$$f'"; \ - $(INSTALL_DATA) "$$d$$p"/* "$(DESTDIR)$(htmldir)/$$f" || exit $$?; \ + echo " $(INSTALL_DATA) '$$d2'/* '$(DESTDIR)$(htmldir)/$$f'"; \ + $(INSTALL_DATA) "$$d2"/* "$(DESTDIR)$(htmldir)/$$f" || exit $$?; \ else \ - list2="$$list2 $$d$$p"; \ + list2="$$list2 $$d2"; \ fi; \ done; \ test -z "$$list2" || { echo "$$list2" | $(am__base_list) | \ @@ -604,9 +654,12 @@ install-info: install-info-am install-info-am: $(INFO_DEPS) @$(NORMAL_INSTALL) - test -z "$(infodir)" || $(MKDIR_P) "$(DESTDIR)$(infodir)" @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ list='$(INFO_DEPS)'; test -n "$(infodir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(infodir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(infodir)" || exit 1; \ + fi; \ for file in $$list; do \ case $$file in \ $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ @@ -624,8 +677,7 @@ install-info-am: $(INFO_DEPS) echo " $(INSTALL_DATA) $$files '$(DESTDIR)$(infodir)'"; \ $(INSTALL_DATA) $$files "$(DESTDIR)$(infodir)" || exit $$?; done @$(POST_INSTALL) - @if (install-info --version && \ - install-info --version 2>&1 | sed 1q | grep -i -v debian) >/dev/null 2>&1; then \ + @if $(am__can_run_installinfo); then \ list='$(INFO_DEPS)'; test -n "$(infodir)" || list=; \ for file in $$list; do \ relfile=`echo "$$file" | sed 's|^.*/||'`; \ @@ -639,8 +691,11 @@ install-pdf: install-pdf-am install-pdf-am: $(PDFS) @$(NORMAL_INSTALL) - test -z "$(pdfdir)" || $(MKDIR_P) "$(DESTDIR)$(pdfdir)" @list='$(PDFS)'; test -n "$(pdfdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(pdfdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(pdfdir)" || exit 1; \ + fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -652,8 +707,11 @@ install-ps: install-ps-am install-ps-am: $(PSS) @$(NORMAL_INSTALL) - test -z "$(psdir)" || $(MKDIR_P) "$(DESTDIR)$(psdir)" @list='$(PSS)'; test -n "$(psdir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(psdir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(psdir)" || exit 1; \ + fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ echo "$$d$$p"; \ @@ -713,31 +771,31 @@ texi2html: tinc.texi texi2html -split=chapter tinc.texi tincd.8.html: tincd.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tincctl.8.html: tincctl.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tinc-gui.8.html: tinc-gui.8 - w3mman2html $< > $@ + w3mman2html $? > $@ tinc.conf.5.html: tinc.conf.5 - w3mman2html $< > $@ + w3mman2html $? > $@ tincd.8: tincd.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tincctl.8: tincctl.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc-gui.8: tinc-gui.8.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc.conf.5: tinc.conf.5.in - $(substitute) $< > $@ + $(substitute) $? > $@ tincinclude.texi: tincinclude.texi.in - $(substitute) $< > $@ + $(substitute) $? > $@ tinc.texi: tincinclude.texi diff --git a/doc/sample-config.tar.gz b/doc/sample-config.tar.gz index 3ad2d9a..d7edc13 100644 Binary files a/doc/sample-config.tar.gz and b/doc/sample-config.tar.gz differ diff --git a/doc/texinfo.tex b/doc/texinfo.tex index 9140826..85b68e7 100644 --- a/doc/texinfo.tex +++ b/doc/texinfo.tex @@ -3,11 +3,11 @@ % Load plain if necessary, i.e., if running under initex. \expandafter\ifx\csname fmtname\endcsname\relax\input plain\fi % -\def\texinfoversion{2009-08-14.15} +\def\texinfoversion{2012-03-11.15} % % Copyright 1985, 1986, 1988, 1990, 1991, 1992, 1993, 1994, 1995, % 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, -% 2007, 2008, 2009 Free Software Foundation, Inc. +% 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc. % % This texinfo.tex file is free software: you can redistribute it and/or % modify it under the terms of the GNU General Public License as @@ -65,7 +65,6 @@ \everyjob{\message{[Texinfo version \texinfoversion]}% \catcode`+=\active \catcode`\_=\active} - \chardef\other=12 % We never want plain's \outer definition of \+ in Texinfo. @@ -93,14 +92,13 @@ \let\ptexnewwrite\newwrite \let\ptexnoindent=\noindent \let\ptexplus=+ +\let\ptexraggedright=\raggedright \let\ptexrbrace=\} \let\ptexslash=\/ \let\ptexstar=\* \let\ptext=\t \let\ptextop=\top -{\catcode`\'=\active -\global\let\ptexquoteright'}% Math-mode def from plain.tex. -\let\ptexraggedright=\raggedright +{\catcode`\'=\active \global\let\ptexquoteright'}% active in plain's math mode % If this character appears in an error message or help string, it % starts a new line in the output. @@ -118,10 +116,11 @@ % Set up fixed words for English if not already set. \ifx\putwordAppendix\undefined \gdef\putwordAppendix{Appendix}\fi \ifx\putwordChapter\undefined \gdef\putwordChapter{Chapter}\fi +\ifx\putworderror\undefined \gdef\putworderror{error}\fi \ifx\putwordfile\undefined \gdef\putwordfile{file}\fi \ifx\putwordin\undefined \gdef\putwordin{in}\fi -\ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi -\ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi +\ifx\putwordIndexIsEmpty\undefined \gdef\putwordIndexIsEmpty{(Index is empty)}\fi +\ifx\putwordIndexNonexistent\undefined \gdef\putwordIndexNonexistent{(Index is nonexistent)}\fi \ifx\putwordInfo\undefined \gdef\putwordInfo{Info}\fi \ifx\putwordInstanceVariableof\undefined \gdef\putwordInstanceVariableof{Instance Variable of}\fi \ifx\putwordMethodon\undefined \gdef\putwordMethodon{Method on}\fi @@ -160,15 +159,18 @@ \def\spaceisspace{\catcode`\ =\spacecat} % sometimes characters are active, so we need control sequences. +\chardef\ampChar = `\& \chardef\colonChar = `\: \chardef\commaChar = `\, \chardef\dashChar = `\- \chardef\dotChar = `\. \chardef\exclamChar= `\! +\chardef\hashChar = `\# \chardef\lquoteChar= `\` \chardef\questChar = `\? \chardef\rquoteChar= `\' \chardef\semiChar = `\; +\chardef\slashChar = `\/ \chardef\underChar = `\_ % Ignore a token. @@ -199,36 +201,7 @@ % that mark overfull boxes (in case you have decided % that the text looks ok even though it passes the margin). % -\def\finalout{\overfullrule=0pt} - -% @| inserts a changebar to the left of the current line. It should -% surround any changed text. This approach does *not* work if the -% change spans more than two lines of output. To handle that, we would -% have adopt a much more difficult approach (putting marks into the main -% vertical list for the beginning and end of each change). -% -\def\|{% - % \vadjust can only be used in horizontal mode. - \leavevmode - % - % Append this vertical mode material after the current line in the output. - \vadjust{% - % We want to insert a rule with the height and depth of the current - % leading; that is exactly what \strutbox is supposed to record. - \vskip-\baselineskip - % - % \vadjust-items are inserted at the left edge of the type. So - % the \llap here moves out into the left-hand margin. - \llap{% - % - % For a thicker or thinner bar, change the `1pt'. - \vrule height\baselineskip width1pt - % - % This is the space between the bar and the text. - \hskip 12pt - }% - }% -} +\def\finalout{\overfullrule=0pt } % Sometimes it is convenient to have everything in the transcript file % and nothing on the terminal. We don't just call \tracingall here, @@ -246,7 +219,7 @@ \tracingmacros2 \tracingrestores1 \showboxbreadth\maxdimen \showboxdepth\maxdimen - \ifx\eTeXversion\undefined\else % etex gives us more logging + \ifx\eTeXversion\thisisundefined\else % etex gives us more logging \tracingscantokens1 \tracingifs1 \tracinggroups1 @@ -257,6 +230,13 @@ \errorcontextlines16 }% +% @errormsg{MSG}. Do the index-like expansions on MSG, but if things +% aren't perfect, it's not the end of the world, being an error message, +% after all. +% +\def\errormsg{\begingroup \indexnofonts \doerrormsg} +\def\doerrormsg#1{\errmessage{#1}} + % add check for \lastpenalty to plain's definitions. If the last thing % we did was a \nobreak, we don't want to insert more space. % @@ -267,7 +247,6 @@ \def\bigbreak{\ifnum\lastpenalty<10000\par\ifdim\lastskip<\bigskipamount \removelastskip\penalty-200\bigskip\fi\fi} -% For @cropmarks command. % Do @cropmarks to get crop marks. % \newif\ifcropmarks @@ -577,7 +556,7 @@ } \def\inenvironment#1{% \ifx#1\empty - out of any environment% + outside of any environment% \else in environment \expandafter\string#1% \fi @@ -589,7 +568,7 @@ \parseargdef\end{% \if 1\csname iscond.#1\endcsname \else - % The general wording of \badenverr may not be ideal, but... --kasal, 06nov03 + % The general wording of \badenverr may not be ideal. \expandafter\checkenv\csname#1\endcsname \csname E#1\endcsname \endgroup @@ -599,85 +578,6 @@ \newhelp\EMsimple{Press RETURN to continue.} -%% Simple single-character @ commands - -% @@ prints an @ -% Kludge this until the fonts are right (grr). -\def\@{{\tt\char64}} - -% This is turned off because it was never documented -% and you can use @w{...} around a quote to suppress ligatures. -%% Define @` and @' to be the same as ` and ' -%% but suppressing ligatures. -%\def\`{{`}} -%\def\'{{'}} - -% Used to generate quoted braces. -\def\mylbrace {{\tt\char123}} -\def\myrbrace {{\tt\char125}} -\let\{=\mylbrace -\let\}=\myrbrace -\begingroup - % Definitions to produce \{ and \} commands for indices, - % and @{ and @} for the aux/toc files. - \catcode`\{ = \other \catcode`\} = \other - \catcode`\[ = 1 \catcode`\] = 2 - \catcode`\! = 0 \catcode`\\ = \other - !gdef!lbracecmd[\{]% - !gdef!rbracecmd[\}]% - !gdef!lbraceatcmd[@{]% - !gdef!rbraceatcmd[@}]% -!endgroup - -% @comma{} to avoid , parsing problems. -\let\comma = , - -% Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent -% Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. -\let\, = \c -\let\dotaccent = \. -\def\ringaccent#1{{\accent23 #1}} -\let\tieaccent = \t -\let\ubaraccent = \b -\let\udotaccent = \d - -% Other special characters: @questiondown @exclamdown @ordf @ordm -% Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. -\def\questiondown{?`} -\def\exclamdown{!`} -\def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} -\def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} - -% Dotless i and dotless j, used for accents. -\def\imacro{i} -\def\jmacro{j} -\def\dotless#1{% - \def\temp{#1}% - \ifx\temp\imacro \ifmmode\imath \else\ptexi \fi - \else\ifx\temp\jmacro \ifmmode\jmath \else\j \fi - \else \errmessage{@dotless can be used only with i or j}% - \fi\fi -} - -% The \TeX{} logo, as in plain, but resetting the spacing so that a -% period following counts as ending a sentence. (Idea found in latex.) -% -\edef\TeX{\TeX \spacefactor=1000 } - -% @LaTeX{} logo. Not quite the same results as the definition in -% latex.ltx, since we use a different font for the raised A; it's most -% convenient for us to use an explicitly smaller font, rather than using -% the \scriptstyle font (since we don't reset \scriptstyle and -% \scriptscriptstyle). -% -\def\LaTeX{% - L\kern-.36em - {\setbox0=\hbox{T}% - \vbox to \ht0{\hbox{\selectfonts\lllsize A}\vss}}% - \kern-.15em - \TeX -} - % Be sure we're in horizontal mode when doing a tie, since we make space % equivalent to this in @example-like environments. Otherwise, a space % at the beginning of a line will start with \penalty -- and @@ -719,7 +619,7 @@ \else\ifx\temp\offword \plainnonfrenchspacing \else \errhelp = \EMsimple - \errmessage{Unknown @frenchspacing option `\temp', must be on/off}% + \errmessage{Unknown @frenchspacing option `\temp', must be on|off}% \fi\fi } @@ -801,15 +701,6 @@ where each line of input produces a line of output.} \newdimen\mil \mil=0.001in -% Old definition--didn't work. -%\parseargdef\need{\par % -%% This method tries to make TeX break the page naturally -%% if the depth of the box does not fit. -%{\baselineskip=0pt% -%\vtop to #1\mil{\vfil}\kern -#1\mil\nobreak -%\prevdepth=-1000pt -%}} - \parseargdef\need{% % Ensure vertical mode, so we don't make a big box in the middle of a % paragraph. @@ -873,7 +764,7 @@ where each line of input produces a line of output.} % @inmargin{WHICH}{TEXT} puts TEXT in the WHICH margin next to the current % paragraph. For more general purposes, use the \margin insertion -% class. WHICH is `l' or `r'. +% class. WHICH is `l' or `r'. Not documented, written for gawk manual. % \newskip\inmarginspacing \inmarginspacing=1cm \def\strutdepth{\dp\strutbox} @@ -920,6 +811,36 @@ where each line of input produces a line of output.} \temp } +% @| inserts a changebar to the left of the current line. It should +% surround any changed text. This approach does *not* work if the +% change spans more than two lines of output. To handle that, we would +% have adopt a much more difficult approach (putting marks into the main +% vertical list for the beginning and end of each change). This command +% is not documented, not supported, and doesn't work. +% +\def\|{% + % \vadjust can only be used in horizontal mode. + \leavevmode + % + % Append this vertical mode material after the current line in the output. + \vadjust{% + % We want to insert a rule with the height and depth of the current + % leading; that is exactly what \strutbox is supposed to record. + \vskip-\baselineskip + % + % \vadjust-items are inserted at the left edge of the type. So + % the \llap here moves out into the left-hand margin. + \llap{% + % + % For a thicker or thinner bar, change the `1pt'. + \vrule height\baselineskip width1pt + % + % This is the space between the bar and the text. + \hskip 12pt + }% + }% +} + % @include FILE -- \input text of FILE. % \def\include{\parseargusing\filenamecatcodes\includezzz} @@ -930,6 +851,7 @@ where each line of input produces a line of output.} \makevalueexpandable % we want to expand any @value in FILE. \turnoffactive % and allow special characters in the expansion \indexnofonts % Allow `@@' and other weird things in file names. + \wlog{texinfo.tex: doing @include of #1^^J}% \edef\temp{\noexpand\input #1 }% % % This trickery is to read FILE outside of a group, in case it makes @@ -965,7 +887,7 @@ where each line of input produces a line of output.} \def\popthisfilestack{\errthisfilestackempty} \def\errthisfilestackempty{\errmessage{Internal error: the stack of filenames is empty.}} - +% \def\thisfile{} % @center line @@ -973,36 +895,46 @@ where each line of input produces a line of output.} % \parseargdef\center{% \ifhmode - \let\next\centerH + \let\centersub\centerH \else - \let\next\centerV + \let\centersub\centerV \fi - \next{\hfil \ignorespaces#1\unskip \hfil}% + \centersub{\hfil \ignorespaces#1\unskip \hfil}% + \let\centersub\relax % don't let the definition persist, just in case } -\def\centerH#1{% - {% - \hfil\break - \advance\hsize by -\leftskip - \advance\hsize by -\rightskip - \line{#1}% - \break - }% +\def\centerH#1{{% + \hfil\break + \advance\hsize by -\leftskip + \advance\hsize by -\rightskip + \line{#1}% + \break +}} +% +\newcount\centerpenalty +\def\centerV#1{% + % The idea here is the same as in \startdefun, \cartouche, etc.: if + % @center is the first thing after a section heading, we need to wipe + % out the negative parskip inserted by \sectionheading, but still + % prevent a page break here. + \centerpenalty = \lastpenalty + \ifnum\centerpenalty>10000 \vskip\parskip \fi + \ifnum\centerpenalty>9999 \penalty\centerpenalty \fi + \line{\kern\leftskip #1\kern\rightskip}% } -\def\centerV#1{\line{\kern\leftskip #1\kern\rightskip}} % @sp n outputs n lines of vertical space - +% \parseargdef\sp{\vskip #1\baselineskip} % @comment ...line which is ignored... % @c is the same as @comment % @ignore ... @end ignore is another way to write a comment - +% \def\comment{\begingroup \catcode`\^^M=\other% \catcode`\@=\other \catcode`\{=\other \catcode`\}=\other% \commentxxx} {\catcode`\^^M=\other \gdef\commentxxx#1^^M{\endgroup}} - +% \let\c=\comment % @paragraphindent NCHARS @@ -1095,109 +1027,6 @@ where each line of input produces a line of output.} } -% @asis just yields its argument. Used with @table, for example. -% -\def\asis#1{#1} - -% @math outputs its argument in math mode. -% -% One complication: _ usually means subscripts, but it could also mean -% an actual _ character, as in @math{@var{some_variable} + 1}. So make -% _ active, and distinguish by seeing if the current family is \slfam, -% which is what @var uses. -{ - \catcode`\_ = \active - \gdef\mathunderscore{% - \catcode`\_=\active - \def_{\ifnum\fam=\slfam \_\else\sb\fi}% - } -} -% Another complication: we want \\ (and @\) to output a \ character. -% FYI, plain.tex uses \\ as a temporary control sequence (why?), but -% this is not advertised and we don't care. Texinfo does not -% otherwise define @\. -% -% The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. -\def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} -% -\def\math{% - \tex - \mathunderscore - \let\\ = \mathbackslash - \mathactive - % make the texinfo accent commands work in math mode - \let\"=\ddot - \let\'=\acute - \let\==\bar - \let\^=\hat - \let\`=\grave - \let\u=\breve - \let\v=\check - \let\~=\tilde - \let\dotaccent=\dot - $\finishmath -} -\def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. - -% Some active characters (such as <) are spaced differently in math. -% We have to reset their definitions in case the @math was an argument -% to a command which sets the catcodes (such as @item or @section). -% -{ - \catcode`^ = \active - \catcode`< = \active - \catcode`> = \active - \catcode`+ = \active - \catcode`' = \active - \gdef\mathactive{% - \let^ = \ptexhat - \let< = \ptexless - \let> = \ptexgtr - \let+ = \ptexplus - \let' = \ptexquoteright - } -} - -% Some math mode symbols. -\def\bullet{$\ptexbullet$} -\def\geq{\ifmmode \ge\else $\ge$\fi} -\def\leq{\ifmmode \le\else $\le$\fi} -\def\minus{\ifmmode -\else $-$\fi} - -% @dots{} outputs an ellipsis using the current font. -% We do .5em per period so that it has the same spacing in the cm -% typewriter fonts as three actual period characters; on the other hand, -% in other typewriter fonts three periods are wider than 1.5em. So do -% whichever is larger. -% -\def\dots{% - \leavevmode - \setbox0=\hbox{...}% get width of three periods - \ifdim\wd0 > 1.5em - \dimen0 = \wd0 - \else - \dimen0 = 1.5em - \fi - \hbox to \dimen0{% - \hskip 0pt plus.25fil - .\hskip 0pt plus1fil - .\hskip 0pt plus1fil - .\hskip 0pt plus.5fil - }% -} - -% @enddots{} is an end-of-sentence ellipsis. -% -\def\enddots{% - \dots - \spacefactor=\endofsentencespacefactor -} - -% @comma{} is so commas can be inserted into text without messing up -% Texinfo's parsing. -% -\let\comma = , - % @refill is a no-op. \let\refill=\relax @@ -1262,9 +1091,8 @@ where each line of input produces a line of output.} \newif\ifpdfmakepagedest % when pdftex is run in dvi mode, \pdfoutput is defined (so \pdfoutput=1 -% can be set). So we test for \relax and 0 as well as \undefined, -% borrowed from ifpdf.sty. -\ifx\pdfoutput\undefined +% can be set). So we test for \relax and 0 as well as being undefined. +\ifx\pdfoutput\thisisundefined \else \ifx\pdfoutput\relax \else @@ -1279,50 +1107,24 @@ where each line of input produces a line of output.} % for display in the outlines, and in other places. Thus, we have to % double any backslashes. Otherwise, a name like "\node" will be % interpreted as a newline (\n), followed by o, d, e. Not good. -% http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html -% (and related messages, the final outcome is that it is up to the TeX -% user to double the backslashes and otherwise make the string valid, so -% that's what we do). +% +% See http://www.ntg.nl/pipermail/ntg-pdftex/2004-July/000654.html and +% related messages. The final outcome is that it is up to the TeX user +% to double the backslashes and otherwise make the string valid, so +% that's what we do. pdftex 1.30.0 (ca.2005) introduced a primitive to +% do this reliably, so we use it. -% double active backslashes. -% -{\catcode`\@=0 \catcode`\\=\active - @gdef@activebackslashdouble{% - @catcode`@\=@active - @let\=@doublebackslash} -} - -% To handle parens, we must adopt a different approach, since parens are -% not active characters. hyperref.dtx (which has the same problem as -% us) handles it with this amazing macro to replace tokens, with minor -% changes for Texinfo. It is included here under the GPL by permission -% from the author, Heiko Oberdiek. -% -% #1 is the tokens to replace. -% #2 is the replacement. -% #3 is the control sequence with the string. -% -\def\HyPsdSubst#1#2#3{% - \def\HyPsdReplace##1#1##2\END{% - ##1% - \ifx\\##2\\% - \else - #2% - \HyReturnAfterFi{% - \HyPsdReplace##2\END - }% - \fi - }% - \xdef#3{\expandafter\HyPsdReplace#3#1\END}% -} -\long\def\HyReturnAfterFi#1\fi{\fi#1} - -% #1 is a control sequence in which to do the replacements. -\def\backslashparens#1{% - \xdef#1{#1}% redefine it as its expansion; the definition is simply - % \lastnode when called from \setref -> \pdfmkdest. - \HyPsdSubst{(}{\realbackslash(}{#1}% - \HyPsdSubst{)}{\realbackslash)}{#1}% +% #1 is a control sequence in which to do the replacements, +% which we \xdef. +\def\txiescapepdf#1{% + \ifx\pdfescapestring\relax + % No primitive available; should we give a warning or log? + % Many times it won't matter. + \else + % The expandable \pdfescapestring primitive escapes parentheses, + % backslashes, and other special chars. + \xdef#1{\pdfescapestring{#1}}% + \fi } \newhelp\nopdfimagehelp{Texinfo supports .png, .jpg, .jpeg, and .pdf images @@ -1381,32 +1183,34 @@ output) for that.)} % % #1 is image name, #2 width (might be empty/whitespace), #3 height (ditto). \def\dopdfimage#1#2#3{% - \def\imagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% - \def\imageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% + \def\pdfimagewidth{#2}\setbox0 = \hbox{\ignorespaces #2}% + \def\pdfimageheight{#3}\setbox2 = \hbox{\ignorespaces #3}% % - % pdftex (and the PDF format) support .png, .jpg, .pdf (among - % others). Let's try in that order. + % pdftex (and the PDF format) support .pdf, .png, .jpg (among + % others). Let's try in that order, PDF first since if + % someone has a scalable image, presumably better to use that than a + % bitmap. \let\pdfimgext=\empty \begingroup - \openin 1 #1.png \ifeof 1 - \openin 1 #1.jpg \ifeof 1 - \openin 1 #1.jpeg \ifeof 1 - \openin 1 #1.JPG \ifeof 1 - \openin 1 #1.pdf \ifeof 1 - \openin 1 #1.PDF \ifeof 1 + \openin 1 #1.pdf \ifeof 1 + \openin 1 #1.PDF \ifeof 1 + \openin 1 #1.png \ifeof 1 + \openin 1 #1.jpg \ifeof 1 + \openin 1 #1.jpeg \ifeof 1 + \openin 1 #1.JPG \ifeof 1 \errhelp = \nopdfimagehelp \errmessage{Could not find image file #1 for pdf}% - \else \gdef\pdfimgext{PDF}% + \else \gdef\pdfimgext{JPG}% \fi - \else \gdef\pdfimgext{pdf}% + \else \gdef\pdfimgext{jpeg}% \fi - \else \gdef\pdfimgext{JPG}% + \else \gdef\pdfimgext{jpg}% \fi - \else \gdef\pdfimgext{jpeg}% + \else \gdef\pdfimgext{png}% \fi - \else \gdef\pdfimgext{jpg}% + \else \gdef\pdfimgext{PDF}% \fi - \else \gdef\pdfimgext{png}% + \else \gdef\pdfimgext{pdf}% \fi \closein 1 \endgroup @@ -1418,8 +1222,8 @@ output) for that.)} \else \immediate\pdfximage \fi - \ifdim \wd0 >0pt width \imagewidth \fi - \ifdim \wd2 >0pt height \imageheight \fi + \ifdim \wd0 >0pt width \pdfimagewidth \fi + \ifdim \wd2 >0pt height \pdfimageheight \fi \ifnum\pdftexversion<13 #1.\pdfimgext \else @@ -1434,10 +1238,9 @@ output) for that.)} % such as \, aren't expanded when present in a section title. \indexnofonts \turnoffactive - \activebackslashdouble \makevalueexpandable \def\pdfdestname{#1}% - \backslashparens\pdfdestname + \txiescapepdf\pdfdestname \safewhatsit{\pdfdest name{\pdfdestname} xyz}% }} % @@ -1469,29 +1272,24 @@ output) for that.)} % page number. We could generate a destination for the section % text in the case where a section has no node, but it doesn't % seem worth the trouble, since most documents are normally structured. - \def\pdfoutlinedest{#3}% + \edef\pdfoutlinedest{#3}% \ifx\pdfoutlinedest\empty \def\pdfoutlinedest{#4}% \else - % Doubled backslashes in the name. - {\activebackslashdouble \xdef\pdfoutlinedest{#3}% - \backslashparens\pdfoutlinedest}% + \txiescapepdf\pdfoutlinedest \fi % - % Also double the backslashes in the display string. - {\activebackslashdouble \xdef\pdfoutlinetext{#1}% - \backslashparens\pdfoutlinetext}% + % Also escape PDF chars in the display string. + \edef\pdfoutlinetext{#1}% + \txiescapepdf\pdfoutlinetext % \pdfoutline goto name{\pdfmkpgn{\pdfoutlinedest}}#2{\pdfoutlinetext}% } % \def\pdfmakeoutlines{% \begingroup - % Thanh's hack / proper braces in bookmarks - \edef\mylbrace{\iftrue \string{\else}\fi}\let\{=\mylbrace - \edef\myrbrace{\iffalse{\else\string}\fi}\let\}=\myrbrace - % % Read toc silently, to get counts of subentries for \pdfoutline. + \def\partentry##1##2##3##4{}% ignore parts in the outlines \def\numchapentry##1##2##3##4{% \def\thischapnum{##2}% \def\thissecnum{0}% @@ -1545,15 +1343,26 @@ output) for that.)} % Latin 2 (0xea) gets translated to a | character. Info from % Staszek Wawrykiewicz, 19 Jan 2004 04:09:24 +0100. % - % xx to do this right, we have to translate 8-bit characters to - % their "best" equivalent, based on the @documentencoding. Right - % now, I guess we'll just let the pdf reader have its way. + % TODO this right, we have to translate 8-bit characters to + % their "best" equivalent, based on the @documentencoding. Too + % much work for too little return. Just use the ASCII equivalents + % we use for the index sort strings. + % \indexnofonts \setupdatafile + % We can have normal brace characters in the PDF outlines, unlike + % Texinfo index files. So set that up. + \def\{{\lbracecharliteral}% + \def\}{\rbracecharliteral}% \catcode`\\=\active \otherbackslash \input \tocreadfilename \endgroup } + {\catcode`[=1 \catcode`]=2 + \catcode`{=\other \catcode`}=\other + \gdef\lbracecharliteral[{]% + \gdef\rbracecharliteral[}]% + ] % \def\skipspaces#1{\def\PP{#1}\def\D{|}% \ifx\PP\D\let\nextsp\relax @@ -1563,7 +1372,13 @@ output) for that.)} \fi \fi \nextsp} - \def\getfilename#1{\filenamelength=0\expandafter\skipspaces#1|\relax} + \def\getfilename#1{% + \filenamelength=0 + % If we don't expand the argument now, \skipspaces will get + % snagged on things like "@value{foo}". + \edef\temp{#1}% + \expandafter\skipspaces\temp|\relax + } \ifnum\pdftexversion < 14 \let \startlink \pdfannotlink \else @@ -1695,7 +1510,7 @@ output) for that.)} % if we are producing pdf, and we have \pdffontattr, then define cmaps. % (\pdffontattr was introduced many years ago, but people still run % older pdftex's; it's easy to conditionalize, so we do.) -\ifpdf \ifx\pdffontattr\undefined \else +\ifpdf \ifx\pdffontattr\thisisundefined \else \begingroup \catcode`\^^M=\active \def^^M{^^J}% Output line endings as the ^^J char. \catcode`\%=12 \immediate\pdfobj stream {%!PS-Adobe-3.0 Resource-CMap @@ -1962,7 +1777,7 @@ end % Use cm as the default font prefix. % To specify the font prefix, you must define \fontprefix % before you read in texinfo.tex. -\ifx\fontprefix\undefined +\ifx\fontprefix\thisisundefined \def\fontprefix{cm} \fi % Support font families that don't use the same naming scheme as CM. @@ -2105,8 +1920,8 @@ end \font\reducedsy=cmsy10 \def\reducedecsize{1000} -% reset the current fonts -\textfonts +\textleading = 13.2pt % line spacing for 11pt CM +\textfonts % reset the current fonts \rm } % end of 11pt text font size definitions @@ -2236,11 +2051,9 @@ end \font\reducedsy=cmsy9 \def\reducedecsize{0900} -% reduce space between paragraphs -\divide\parskip by 2 - -% reset the current fonts -\textfonts +\divide\parskip by 2 % reduce space between paragraphs +\textleading = 12pt % line spacing for 10pt CM +\textfonts % reset the current fonts \rm } % end of 10pt text font size definitions @@ -2249,12 +2062,13 @@ end % @fonttextsize 10 % (or 11) to redefine the text font size. pt is assumed. % -\def\xword{10} \def\xiword{11} +\def\xword{10} +\def\xwordpt{10pt} % \parseargdef\fonttextsize{% \def\textsizearg{#1}% - \wlog{doing @fonttextsize \textsizearg}% + %\wlog{doing @fonttextsize \textsizearg}% % % Set \globaldefs so that documents can use this inside @tex, since % makeinfo 4.8 does not support it, but we need it nonetheless. @@ -2308,7 +2122,7 @@ end \let\tenttsl=\titlettsl \def\curfontsize{title}% \def\lsize{chap}\def\lllsize{subsec}% - \resetmathfonts \setleading{25pt}} + \resetmathfonts \setleading{27pt}} \def\titlefont#1{{\titlefonts\rmisbold #1}} \def\chapfonts{% \let\tenrm=\chaprm \let\tenit=\chapit \let\tensl=\chapsl @@ -2436,12 +2250,14 @@ end % Markup style setup for left and right quotes. \defmarkupstylesetup\markupsetuplq{% - \expandafter\let\expandafter \temp \csname markupsetuplq\currentmarkupstyle\endcsname + \expandafter\let\expandafter \temp + \csname markupsetuplq\currentmarkupstyle\endcsname \ifx\temp\relax \markupsetuplqdefault \else \temp \fi } \defmarkupstylesetup\markupsetuprq{% - \expandafter\let\expandafter \temp \csname markupsetuprq\currentmarkupstyle\endcsname + \expandafter\let\expandafter \temp + \csname markupsetuprq\currentmarkupstyle\endcsname \ifx\temp\relax \markupsetuprqdefault \else \temp \fi } @@ -2460,22 +2276,26 @@ end \let\markupsetuplqcode \markupsetcodequoteleft \let\markupsetuprqcode \markupsetcodequoteright +% \let\markupsetuplqexample \markupsetcodequoteleft \let\markupsetuprqexample \markupsetcodequoteright +% +\let\markupsetuplqsamp \markupsetcodequoteleft +\let\markupsetuprqsamp \markupsetcodequoteright +% \let\markupsetuplqverb \markupsetcodequoteleft \let\markupsetuprqverb \markupsetcodequoteright +% \let\markupsetuplqverbatim \markupsetcodequoteleft \let\markupsetuprqverbatim \markupsetcodequoteright -\let\markupsetuplqsamp \markupsetnoligaturesquoteleft \let\markupsetuplqkbd \markupsetnoligaturesquoteleft -% Allow an option to not replace quotes with a regular directed right -% quote/apostrophe (char 0x27), but instead use the undirected quote -% from cmtt (char 0x0d). The undirected quote is ugly, so don't make it -% the default, but it works for pasting with more pdf viewers (at least -% evince), the lilypond developers report. xpdf does work with the -% regular 0x27. +% Allow an option to not use regular directed right quote/apostrophe +% (char 0x27), but instead the undirected quote from cmtt (char 0x0d). +% The undirected quote is ugly, so don't make it the default, but it +% works for pasting with more pdf viewers (at least evince), the +% lilypond developers report. xpdf does work with the regular 0x27. % \def\codequoteright{% \expandafter\ifx\csname SETtxicodequoteundirected\endcsname\relax @@ -2499,33 +2319,84 @@ end \else \char'22 \fi } +% Commands to set the quote options. +% +\parseargdef\codequoteundirected{% + \def\temp{#1}% + \ifx\temp\onword + \expandafter\let\csname SETtxicodequoteundirected\endcsname + = t% + \else\ifx\temp\offword + \expandafter\let\csname SETtxicodequoteundirected\endcsname + = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @codequoteundirected value `\temp', must be on|off}% + \fi\fi +} +% +\parseargdef\codequotebacktick{% + \def\temp{#1}% + \ifx\temp\onword + \expandafter\let\csname SETtxicodequotebacktick\endcsname + = t% + \else\ifx\temp\offword + \expandafter\let\csname SETtxicodequotebacktick\endcsname + = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @codequotebacktick value `\temp', must be on|off}% + \fi\fi +} + % [Knuth] pp. 380,381,391, disable Spanish ligatures ?` and !` of \tt font. \def\noligaturesquoteleft{\relax\lq} % Count depth in font-changes, for error checks \newcount\fontdepth \fontdepth=0 -%% Add scribe-like font environments, plus @l for inline lisp (usually sans -%% serif) and @ii for TeX italic +% Font commands. -% \smartitalic{ARG} outputs arg in italics, followed by an italic correction -% unless the following character is such as not to need one. -\def\smartitalicx{\ifx\next,\else\ifx\next-\else\ifx\next.\else - \ptexslash\fi\fi\fi} -\def\smartslanted#1{{\ifusingtt\ttsl\sl #1}\futurelet\next\smartitalicx} -\def\smartitalic#1{{\ifusingtt\ttsl\it #1}\futurelet\next\smartitalicx} +% #1 is the font command (\sl or \it), #2 is the text to slant. +% If we are in a monospaced environment, however, 1) always use \ttsl, +% and 2) do not add an italic correction. +\def\dosmartslant#1#2{% + \ifusingtt + {{\ttsl #2}\let\next=\relax}% + {\def\next{{#1#2}\futurelet\next\smartitaliccorrection}}% + \next +} +\def\smartslanted{\dosmartslant\sl} +\def\smartitalic{\dosmartslant\it} -% like \smartslanted except unconditionally uses \ttsl. +% Output an italic correction unless \next (presumed to be the following +% character) is such as not to need one. +\def\smartitaliccorrection{% + \ifx\next,% + \else\ifx\next-% + \else\ifx\next.% + \else\ptexslash + \fi\fi\fi + \aftersmartic +} + +% like \smartslanted except unconditionally uses \ttsl, and no ic. % @var is set to this for defun arguments. -\def\ttslanted#1{{\ttsl #1}\futurelet\next\smartitalicx} +\def\ttslanted#1{{\ttsl #1}} % @cite is like \smartslanted except unconditionally use \sl. We never want % ttsl for book titles, do we? -\def\cite#1{{\sl #1}\futurelet\next\smartitalicx} +\def\cite#1{{\sl #1}\futurelet\next\smartitaliccorrection} + +\def\aftersmartic{} +\def\var#1{% + \let\saveaftersmartic = \aftersmartic + \def\aftersmartic{\null\let\aftersmartic=\saveaftersmartic}% + \smartslanted{#1}% +} \let\i=\smartitalic \let\slanted=\smartslanted -\def\var#1{{\setupmarkupstyle{var}\smartslanted{#1}}} \let\dfn=\smartslanted \let\emph=\smartitalic @@ -2621,7 +2492,7 @@ end \plainfrenchspacing #1% }% - \null + \null % reset spacefactor to 1000 } % We *must* turn on hyphenation at `-' and `_' in @code. @@ -2653,6 +2524,8 @@ end } } +\def\codex #1{\tclose{#1}\endgroup} + \def\realdash{-} \def\codedash{-\discretionary{}{}{}} \def\codeunder{% @@ -2666,7 +2539,6 @@ end \discretionary{}{}{}}% {\_}% } -\def\codex #1{\tclose{#1}\endgroup} % An additional complication: the above will allow breaks after, e.g., % each of the four underscores in __typeof__. This is undesirable in @@ -2686,10 +2558,156 @@ end \allowcodebreaksfalse \else \errhelp = \EMsimple - \errmessage{Unknown @allowcodebreaks option `\txiarg'}% + \errmessage{Unknown @allowcodebreaks option `\txiarg', must be true|false}% \fi\fi } +% @uref (abbreviation for `urlref') takes an optional (comma-separated) +% second argument specifying the text to display and an optional third +% arg as text to display instead of (rather than in addition to) the url +% itself. First (mandatory) arg is the url. +% (This \urefnobreak definition isn't used now, leaving it for a while +% for comparison.) +\def\urefnobreak#1{\dourefnobreak #1,,,\finish} +\def\dourefnobreak#1,#2,#3,#4\finish{\begingroup + \unsepspaces + \pdfurl{#1}% + \setbox0 = \hbox{\ignorespaces #3}% + \ifdim\wd0 > 0pt + \unhbox0 % third arg given, show only that + \else + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \ifpdf + \unhbox0 % PDF: 2nd arg given, show only it + \else + \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url + \fi + \else + \code{#1}% only url given, so show it + \fi + \fi + \endlink +\endgroup} + +% This \urefbreak definition is the active one. +\def\urefbreak{\begingroup \urefcatcodes \dourefbreak} +\let\uref=\urefbreak +\def\dourefbreak#1{\urefbreakfinish #1,,,\finish} +\def\urefbreakfinish#1,#2,#3,#4\finish{% doesn't work in @example + \unsepspaces + \pdfurl{#1}% + \setbox0 = \hbox{\ignorespaces #3}% + \ifdim\wd0 > 0pt + \unhbox0 % third arg given, show only that + \else + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0 > 0pt + \ifpdf + \unhbox0 % PDF: 2nd arg given, show only it + \else + \unhbox0\ (\urefcode{#1})% DVI: 2nd arg given, show both it and url + \fi + \else + \urefcode{#1}% only url given, so show it + \fi + \fi + \endlink +\endgroup} + +% Allow line breaks around only a few characters (only). +\def\urefcatcodes{% + \catcode\ampChar=\active \catcode\dotChar=\active + \catcode\hashChar=\active \catcode\questChar=\active + \catcode\slashChar=\active +} +{ + \urefcatcodes + % + \global\def\urefcode{\begingroup + \setupmarkupstyle{code}% + \urefcatcodes + \let&\urefcodeamp + \let.\urefcodedot + \let#\urefcodehash + \let?\urefcodequest + \let/\urefcodeslash + \codex + } + % + % By default, they are just regular characters. + \global\def&{\normalamp} + \global\def.{\normaldot} + \global\def#{\normalhash} + \global\def?{\normalquest} + \global\def/{\normalslash} +} + +% we put a little stretch before and after the breakable chars, to help +% line breaking of long url's. The unequal skips make look better in +% cmtt at least, especially for dots. +\def\urefprestretch{\urefprebreak \hskip0pt plus.13em } +\def\urefpoststretch{\urefpostbreak \hskip0pt plus.1em } +% +\def\urefcodeamp{\urefprestretch \&\urefpoststretch} +\def\urefcodedot{\urefprestretch .\urefpoststretch} +\def\urefcodehash{\urefprestretch \#\urefpoststretch} +\def\urefcodequest{\urefprestretch ?\urefpoststretch} +\def\urefcodeslash{\futurelet\next\urefcodeslashfinish} +{ + \catcode`\/=\active + \global\def\urefcodeslashfinish{% + \urefprestretch \slashChar + % Allow line break only after the final / in a sequence of + % slashes, to avoid line break between the slashes in http://. + \ifx\next/\else \urefpoststretch \fi + } +} + +% One more complication: by default we'll break after the special +% characters, but some people like to break before the special chars, so +% allow that. Also allow no breaking at all, for manual control. +% +\parseargdef\urefbreakstyle{% + \def\txiarg{#1}% + \ifx\txiarg\wordnone + \def\urefprebreak{\nobreak}\def\urefpostbreak{\nobreak} + \else\ifx\txiarg\wordbefore + \def\urefprebreak{\allowbreak}\def\urefpostbreak{\nobreak} + \else\ifx\txiarg\wordafter + \def\urefprebreak{\nobreak}\def\urefpostbreak{\allowbreak} + \else + \errhelp = \EMsimple + \errmessage{Unknown @urefbreakstyle setting `\txiarg'}% + \fi\fi\fi +} +\def\wordafter{after} +\def\wordbefore{before} +\def\wordnone{none} + +\urefbreakstyle after + +% @url synonym for @uref, since that's how everyone uses it. +% +\let\url=\uref + +% rms does not like angle brackets --karl, 17may97. +% So now @email is just like @uref, unless we are pdf. +% +%\def\email#1{\angleleft{\tt #1}\angleright} +\ifpdf + \def\email#1{\doemail#1,,\finish} + \def\doemail#1,#2,#3\finish{\begingroup + \unsepspaces + \pdfurl{mailto:#1}% + \setbox0 = \hbox{\ignorespaces #2}% + \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi + \endlink + \endgroup} +\else + \let\email=\uref +\fi + % @kbd is like @code, except that if the argument is just one @key command, % then @kbd has no effect. \def\kbd#1{{\setupmarkupstyle{kbd}\def\look{#1}\expandafter\kbdfoo\look??\par}} @@ -2707,7 +2725,7 @@ end \gdef\kbdexamplefont{\tt}\gdef\kbdfont{\tt}% \else \errhelp = \EMsimple - \errmessage{Unknown @kbdinputstyle option `\txiarg'}% + \errmessage{Unknown @kbdinputstyle setting `\txiarg'}% \fi\fi\fi } \def\worddistinct{distinct} @@ -2735,55 +2753,6 @@ end \parseargdef\clickstyle{\def\click{#1}} \def\click{\arrow} -% @uref (abbreviation for `urlref') takes an optional (comma-separated) -% second argument specifying the text to display and an optional third -% arg as text to display instead of (rather than in addition to) the url -% itself. First (mandatory) arg is the url. Perhaps eventually put in -% a hypertex \special here. -% -\def\uref#1{\douref #1,,,\finish} -\def\douref#1,#2,#3,#4\finish{\begingroup - \unsepspaces - \pdfurl{#1}% - \setbox0 = \hbox{\ignorespaces #3}% - \ifdim\wd0 > 0pt - \unhbox0 % third arg given, show only that - \else - \setbox0 = \hbox{\ignorespaces #2}% - \ifdim\wd0 > 0pt - \ifpdf - \unhbox0 % PDF: 2nd arg given, show only it - \else - \unhbox0\ (\code{#1})% DVI: 2nd arg given, show both it and url - \fi - \else - \code{#1}% only url given, so show it - \fi - \fi - \endlink -\endgroup} - -% @url synonym for @uref, since that's how everyone uses it. -% -\let\url=\uref - -% rms does not like angle brackets --karl, 17may97. -% So now @email is just like @uref, unless we are pdf. -% -%\def\email#1{\angleleft{\tt #1}\angleright} -\ifpdf - \def\email#1{\doemail#1,,\finish} - \def\doemail#1,#2,#3\finish{\begingroup - \unsepspaces - \pdfurl{mailto:#1}% - \setbox0 = \hbox{\ignorespaces #2}% - \ifdim\wd0>0pt\unhbox0\else\code{#1}\fi - \endlink - \endgroup} -\else - \let\email=\uref -\fi - % Typeset a dimension, e.g., `in' or `pt'. The only reason for the % argument is to make the input look right: @dmn{pt} instead of @dmn{}pt. % @@ -2805,6 +2774,7 @@ end \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi + \null % reset \spacefactor=1000 } % @abbr for "Comput. J." and the like. @@ -2817,10 +2787,219 @@ end \ifx\temp\empty \else \space ({\unsepspaces \ignorespaces \temp \unskip})% \fi + \null % reset \spacefactor=1000 +} + +% @asis just yields its argument. Used with @table, for example. +% +\def\asis#1{#1} + +% @math outputs its argument in math mode. +% +% One complication: _ usually means subscripts, but it could also mean +% an actual _ character, as in @math{@var{some_variable} + 1}. So make +% _ active, and distinguish by seeing if the current family is \slfam, +% which is what @var uses. +{ + \catcode`\_ = \active + \gdef\mathunderscore{% + \catcode`\_=\active + \def_{\ifnum\fam=\slfam \_\else\sb\fi}% + } +} +% Another complication: we want \\ (and @\) to output a math (or tt) \. +% FYI, plain.tex uses \\ as a temporary control sequence (for no +% particular reason), but this is not advertised and we don't care. +% +% The \mathchar is class=0=ordinary, family=7=ttfam, position=5C=\. +\def\mathbackslash{\ifnum\fam=\ttfam \mathchar"075C \else\backslash \fi} +% +\def\math{% + \tex + \mathunderscore + \let\\ = \mathbackslash + \mathactive + % make the texinfo accent commands work in math mode + \let\"=\ddot + \let\'=\acute + \let\==\bar + \let\^=\hat + \let\`=\grave + \let\u=\breve + \let\v=\check + \let\~=\tilde + \let\dotaccent=\dot + $\finishmath +} +\def\finishmath#1{#1$\endgroup} % Close the group opened by \tex. + +% Some active characters (such as <) are spaced differently in math. +% We have to reset their definitions in case the @math was an argument +% to a command which sets the catcodes (such as @item or @section). +% +{ + \catcode`^ = \active + \catcode`< = \active + \catcode`> = \active + \catcode`+ = \active + \catcode`' = \active + \gdef\mathactive{% + \let^ = \ptexhat + \let< = \ptexless + \let> = \ptexgtr + \let+ = \ptexplus + \let' = \ptexquoteright + } +} + +% @inlinefmt{FMTNAME,PROCESSED-TEXT} and @inlineraw{FMTNAME,RAW-TEXT}. +% Ignore unless FMTNAME == tex; then it is like @iftex and @tex, +% except specified as a normal braced arg, so no newlines to worry about. +% +\def\outfmtnametex{tex} +% +\long\def\inlinefmt#1{\doinlinefmt #1,\finish} +\long\def\doinlinefmt#1,#2,\finish{% + \def\inlinefmtname{#1}% + \ifx\inlinefmtname\outfmtnametex \ignorespaces #2\fi +} +% For raw, must switch into @tex before parsing the argument, to avoid +% setting catcodes prematurely. Doing it this way means that, for +% example, @inlineraw{html, foo{bar} gets a parse error instead of being +% ignored. But this isn't important because if people want a literal +% *right* brace they would have to use a command anyway, so they may as +% well use a command to get a left brace too. We could re-use the +% delimiter character idea from \verb, but it seems like overkill. +% +\long\def\inlineraw{\tex \doinlineraw} +\long\def\doinlineraw#1{\doinlinerawtwo #1,\finish} +\def\doinlinerawtwo#1,#2,\finish{% + \def\inlinerawname{#1}% + \ifx\inlinerawname\outfmtnametex \ignorespaces #2\fi + \endgroup % close group opened by \tex. } \message{glyphs,} +% and logos. + +% @@ prints an @, as does @atchar{}. +\def\@{\char64 } +\let\atchar=\@ + +% @{ @} @lbracechar{} @rbracechar{} all generate brace characters. +% Unless we're in typewriter, use \ecfont because the CM text fonts do +% not have braces, and we don't want to switch into math. +\def\mylbrace{{\ifmonospace\else\ecfont\fi \char123}} +\def\myrbrace{{\ifmonospace\else\ecfont\fi \char125}} +\let\{=\mylbrace \let\lbracechar=\{ +\let\}=\myrbrace \let\rbracechar=\} +\begingroup + % Definitions to produce \{ and \} commands for indices, + % and @{ and @} for the aux/toc files. + \catcode`\{ = \other \catcode`\} = \other + \catcode`\[ = 1 \catcode`\] = 2 + \catcode`\! = 0 \catcode`\\ = \other + !gdef!lbracecmd[\{]% + !gdef!rbracecmd[\}]% + !gdef!lbraceatcmd[@{]% + !gdef!rbraceatcmd[@}]% +!endgroup + +% @comma{} to avoid , parsing problems. +\let\comma = , + +% Accents: @, @dotaccent @ringaccent @ubaraccent @udotaccent +% Others are defined by plain TeX: @` @' @" @^ @~ @= @u @v @H. +\let\, = \ptexc +\let\dotaccent = \ptexdot +\def\ringaccent#1{{\accent23 #1}} +\let\tieaccent = \ptext +\let\ubaraccent = \ptexb +\let\udotaccent = \d + +% Other special characters: @questiondown @exclamdown @ordf @ordm +% Plain TeX defines: @AA @AE @O @OE @L (plus lowercase versions) @ss. +\def\questiondown{?`} +\def\exclamdown{!`} +\def\ordf{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{a}}} +\def\ordm{\leavevmode\raise1ex\hbox{\selectfonts\lllsize \underbar{o}}} + +% Dotless i and dotless j, used for accents. +\def\imacro{i} +\def\jmacro{j} +\def\dotless#1{% + \def\temp{#1}% + \ifx\temp\imacro \ifmmode\imath \else\ptexi \fi + \else\ifx\temp\jmacro \ifmmode\jmath \else\j \fi + \else \errmessage{@dotless can be used only with i or j}% + \fi\fi +} + +% The \TeX{} logo, as in plain, but resetting the spacing so that a +% period following counts as ending a sentence. (Idea found in latex.) +% +\edef\TeX{\TeX \spacefactor=1000 } + +% @LaTeX{} logo. Not quite the same results as the definition in +% latex.ltx, since we use a different font for the raised A; it's most +% convenient for us to use an explicitly smaller font, rather than using +% the \scriptstyle font (since we don't reset \scriptstyle and +% \scriptscriptstyle). +% +\def\LaTeX{% + L\kern-.36em + {\setbox0=\hbox{T}% + \vbox to \ht0{\hbox{% + \ifx\textnominalsize\xwordpt + % for 10pt running text, \lllsize (8pt) is too small for the A in LaTeX. + % Revert to plain's \scriptsize, which is 7pt. + \count255=\the\fam $\fam\count255 \scriptstyle A$% + \else + % For 11pt, we can use our lllsize. + \selectfonts\lllsize A% + \fi + }% + \vss + }}% + \kern-.15em + \TeX +} + +% Some math mode symbols. +\def\bullet{$\ptexbullet$} +\def\geq{\ifmmode \ge\else $\ge$\fi} +\def\leq{\ifmmode \le\else $\le$\fi} +\def\minus{\ifmmode -\else $-$\fi} + +% @dots{} outputs an ellipsis using the current font. +% We do .5em per period so that it has the same spacing in the cm +% typewriter fonts as three actual period characters; on the other hand, +% in other typewriter fonts three periods are wider than 1.5em. So do +% whichever is larger. +% +\def\dots{% + \leavevmode + \setbox0=\hbox{...}% get width of three periods + \ifdim\wd0 > 1.5em + \dimen0 = \wd0 + \else + \dimen0 = 1.5em + \fi + \hbox to \dimen0{% + \hskip 0pt plus.25fil + .\hskip 0pt plus1fil + .\hskip 0pt plus1fil + .\hskip 0pt plus.5fil + }% +} + +% @enddots{} is an end-of-sentence ellipsis. +% +\def\enddots{% + \dots + \spacefactor=\endofsentencespacefactor +} % @point{}, @result{}, @expansion{}, @print{}, @equiv{}. % @@ -2842,7 +3021,7 @@ end {\tentt \global\dimen0 = 3em}% Width of the box. \dimen2 = .55pt % Thickness of rules % The text. (`r' is open on the right, `e' somewhat less so on the left.) -\setbox0 = \hbox{\kern-.75pt \reducedsf error\kern-1.5pt} +\setbox0 = \hbox{\kern-.75pt \reducedsf \putworderror\kern-1.5pt} % \setbox\errorbox=\hbox to \dimen0{\hfil \hsize = \dimen0 \advance\hsize by -5.8pt % Space to left+right. @@ -2991,7 +3170,7 @@ end % Textures 1.7.7 (preloaded format=plain 93.10.14) (68K) 16 APR 2004 02:38 % so we'll define it if necessary. % -\ifx\Orb\undefined +\ifx\Orb\thisisundefined \def\Orb{\mathhexbox20D} \fi @@ -3019,8 +3198,9 @@ end \newif\ifsetshortcontentsaftertitlepage \let\setshortcontentsaftertitlepage = \setshortcontentsaftertitlepagetrue -\parseargdef\shorttitlepage{\begingroup\hbox{}\vskip 1.5in \chaprm \centerline{#1}% - \endgroup\page\hbox{}\page} +\parseargdef\shorttitlepage{% + \begingroup \hbox{}\vskip 1.5in \chaprm \centerline{#1}% + \endgroup\page\hbox{}\page} \envdef\titlepage{% % Open one extra group, as we want to close it in the middle of \Etitlepage. @@ -3080,7 +3260,7 @@ end \finishedtitlepagetrue } -%%% Macros to be used within @titlepage: +% Macros to be used within @titlepage: \let\subtitlerm=\tenrm \def\subtitlefont{\subtitlerm \normalbaselineskip = 13pt \normalbaselines} @@ -3113,7 +3293,7 @@ end } -%%% Set up page headings and footings. +% Set up page headings and footings. \let\thispage=\folio @@ -3207,10 +3387,14 @@ end \def\headings #1 {\csname HEADINGS#1\endcsname} -\def\HEADINGSoff{% -\global\evenheadline={\hfil} \global\evenfootline={\hfil} -\global\oddheadline={\hfil} \global\oddfootline={\hfil}} -\HEADINGSoff +\def\headingsoff{% non-global headings elimination + \evenheadline={\hfil}\evenfootline={\hfil}% + \oddheadline={\hfil}\oddfootline={\hfil}% +} + +\def\HEADINGSoff{{\globaldefs=1 \headingsoff}} % global setting +\HEADINGSoff % it's the default + % When we turn headings on, set the page number to 1. % For double-sided printing, put current file name in lower left corner, % chapter name on inside top of right hand pages, document @@ -3261,7 +3445,7 @@ end % This produces Day Month Year style of output. % Only define if not already defined, in case a txi-??.tex file has set % up a different format (e.g., txi-cs.tex does this). -\ifx\today\undefined +\ifx\today\thisisundefined \def\today{% \number\day\space \ifcase\month @@ -3322,7 +3506,7 @@ end \begingroup \advance\leftskip by-\tableindent \advance\hsize by\tableindent - \advance\rightskip by0pt plus1fil + \advance\rightskip by0pt plus1fil\relax \leavevmode\unhbox0\par \endgroup % @@ -3808,18 +3992,18 @@ end \setbox0=\vbox{X}\global\multitablelinespace=\the\baselineskip \global\advance\multitablelinespace by-\ht0 \fi -%% Test to see if parskip is larger than space between lines of -%% table. If not, do nothing. -%% If so, set to same dimension as multitablelinespace. +% Test to see if parskip is larger than space between lines of +% table. If not, do nothing. +% If so, set to same dimension as multitablelinespace. \ifdim\multitableparskip>\multitablelinespace \global\multitableparskip=\multitablelinespace -\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller - %% than skip between lines in the table. +\global\advance\multitableparskip-7pt % to keep parskip somewhat smaller + % than skip between lines in the table. \fi% \ifdim\multitableparskip=0pt \global\multitableparskip=\multitablelinespace -\global\advance\multitableparskip-7pt %% to keep parskip somewhat smaller - %% than skip between lines in the table. +\global\advance\multitableparskip-7pt % to keep parskip somewhat smaller + % than skip between lines in the table. \fi} @@ -4134,11 +4318,14 @@ end \def\@{@}% change to @@ when we switch to @ as escape char in index files. \def\ {\realbackslash\space }% % - % Need these in case \tex is in effect and \{ is a \delimiter again. - % But can't use \lbracecmd and \rbracecmd because texindex assumes - % braces and backslashes are used only as delimiters. - \let\{ = \mylbrace - \let\} = \myrbrace + % Need these unexpandable (because we define \tt as a dummy) + % definitions when @{ or @} appear in index entry text. Also, more + % complicated, when \tex is in effect and \{ is a \delimiter again. + % We can't use \lbracecmd and \rbracecmd because texindex assumes + % braces and backslashes are used only as delimiters. Perhaps we + % should define @lbrace and @rbrace commands a la @comma. + \def\{{{\tt\char123}}% + \def\}{{\tt\char125}}% % % I don't entirely understand this, but when an index entry is % generated from a macro call, the \endinput which \scanmacro inserts @@ -4191,7 +4378,7 @@ end \def\commondummies{% % % \definedummyword defines \#1 as \string\#1\space, thus effectively - % preventing its expansion. This is used only for control% words, + % preventing its expansion. This is used only for control words, % not control letters, because the \space would be incorrect for % control characters, but is needed to separate the control word % from whatever follows. @@ -4210,6 +4397,7 @@ end \commondummiesnofonts % \definedummyletter\_% + \definedummyletter\-% % % Non-English letters. \definedummyword\AA @@ -4246,20 +4434,24 @@ end \definedummyword\TeX % % Assorted special characters. + \definedummyword\arrow \definedummyword\bullet \definedummyword\comma \definedummyword\copyright \definedummyword\registeredsymbol \definedummyword\dots \definedummyword\enddots + \definedummyword\entrybreak \definedummyword\equiv \definedummyword\error \definedummyword\euro + \definedummyword\expansion + \definedummyword\geq \definedummyword\guillemetleft \definedummyword\guillemetright \definedummyword\guilsinglleft \definedummyword\guilsinglright - \definedummyword\expansion + \definedummyword\leq \definedummyword\minus \definedummyword\ogonek \definedummyword\pounds @@ -4316,19 +4508,24 @@ end \definedummyword\b \definedummyword\i \definedummyword\r + \definedummyword\sansserif \definedummyword\sc + \definedummyword\slanted \definedummyword\t % % Commands that take arguments. \definedummyword\acronym + \definedummyword\anchor \definedummyword\cite \definedummyword\code \definedummyword\command \definedummyword\dfn + \definedummyword\dmn \definedummyword\email \definedummyword\emph \definedummyword\env \definedummyword\file + \definedummyword\indicateurl \definedummyword\kbd \definedummyword\key \definedummyword\math @@ -4356,7 +4553,7 @@ end \def\definedummyaccent##1{\let##1\asis}% % We can just ignore other control letters. \def\definedummyletter##1{\let##1\empty}% - % Hopefully, all control words can become @asis. + % All control words become @asis by default; overrides below. \let\definedummyword\definedummyaccent % \commondummiesnofonts @@ -4368,8 +4565,14 @@ end % \def\ { }% \def\@{@}% - % how to handle braces? \def\_{\normalunderscore}% + \def\-{}% @- shouldn't affect sorting + % + % Unfortunately, texindex is not prepared to handle braces in the + % content at all. So for index sorting, we map @{ and @} to strings + % starting with |, since that ASCII character is between ASCII { and }. + \def\{{|a}% + \def\}{|b}% % % Non-English letters. \def\AA{AA}% @@ -4397,6 +4600,7 @@ end % % Assorted special characters. % (The following {} will end up in the sort string, but that's ok.) + \def\arrow{->}% \def\bullet{bullet}% \def\comma{,}% \def\copyright{copyright}% @@ -4406,10 +4610,12 @@ end \def\error{error}% \def\euro{euro}% \def\expansion{==>}% + \def\geq{>=}% \def\guillemetleft{<<}% \def\guillemetright{>>}% \def\guilsinglleft{<}% \def\guilsinglright{>}% + \def\leq{<=}% \def\minus{-}% \def\point{.}% \def\pounds{pounds}% @@ -4424,6 +4630,9 @@ end \def\result{=>}% \def\textdegree{o}% % + \expandafter\ifx\csname SETtxiindexlquoteignore\endcsname\relax + \else \indexlquoteignore \fi + % % We need to get rid of all macros, leaving only the arguments (if present). % Of course this is not nearly correct, but it is the best we can do for now. % makeinfo does not expand macros in the argument to @deffn, which ends up @@ -4437,6 +4646,11 @@ end \macrolist } +% Undocumented (for FSFS 2nd ed.): @set txiindexlquoteignore makes us +% ignore left quotes in the sort term. +{\catcode`\`=\active + \gdef\indexlquoteignore{\let`=\empty}} + \let\indexbackslash=0 %overridden during \printindex. \let\SETmarginindex=\relax % put index entries in margin (undocumented)? @@ -4534,10 +4748,9 @@ end % % ..., ready, GO: % -\def\safewhatsit#1{% -\ifhmode +\def\safewhatsit#1{\ifhmode #1% -\else + \else % \lastskip and \lastpenalty cannot both be nonzero simultaneously. \whatsitskip = \lastskip \edef\lastskipmacro{\the\lastskip}% @@ -4561,7 +4774,6 @@ end % to re-insert the same penalty (values >10000 are used for various % signals); since we just inserted a non-discardable item, any % following glue (such as a \parskip) would be a breakpoint. For example: - % % @deffn deffn-whatever % @vindex index-whatever % Description. @@ -4574,8 +4786,7 @@ end % (the whatsit from the \write), so we must insert a \nobreak. \nobreak\vskip\whatsitskip \fi -\fi -} +\fi} % The index entry written in the file actually looks like % \entry {sortstring}{page}{topic} @@ -4694,7 +4905,6 @@ end % But this freezes the catcodes in the argument, and can cause problems to % @code, which sets - active. This problem was fixed by a kludge--- % ``-'' was active throughout whole index, but this isn't really right. -% % The right solution is to prevent \entry from swallowing the whole text. % --kasal, 21nov03 \def\entry{% @@ -4731,10 +4941,17 @@ end % columns. \vskip 0pt plus1pt % + % When reading the text of entry, convert explicit line breaks + % from @* into spaces. The user might give these in long section + % titles, for instance. + \def\*{\unskip\space\ignorespaces}% + \def\entrybreak{\hfil\break}% + % % Swallow the left brace of the text (first parameter): \afterassignment\doentry \let\temp = } +\def\entrybreak{\unskip\space\ignorespaces}% \def\doentry{% \bgroup % Instead of the swallowed brace. \noindent @@ -4967,7 +5184,22 @@ end \message{sectioning,} % Chapters, sections, etc. -% \unnumberedno is an oxymoron, of course. But we count the unnumbered +% Let's start with @part. +\outer\parseargdef\part{\partzzz{#1}} +\def\partzzz#1{% + \chapoddpage + \null + \vskip.3\vsize % move it down on the page a bit + \begingroup + \noindent \titlefonts\rmisbold #1\par % the text + \let\lastnode=\empty % no node to associate with + \writetocentry{part}{#1}{}% but put it in the toc + \headingsoff % no headline or footline on the part page + \chapoddpage + \endgroup +} + +% \unnumberedno is an oxymoron. But we count the unnumbered % sections so that we can refer to them unambiguously in the pdf % outlines by their "section number". We avoid collisions with chapter % numbers by starting them at 10000. (If a document ever has 10000 @@ -5046,8 +5278,8 @@ end \chardef\maxseclevel = 3 % % A numbered section within an unnumbered changes to unnumbered too. -% To achive this, remember the "biggest" unnum. sec. we are currently in: -\chardef\unmlevel = \maxseclevel +% To achieve this, remember the "biggest" unnum. sec. we are currently in: +\chardef\unnlevel = \maxseclevel % % Trace whether the current chapter is an appendix or not: % \chapheadtype is "N" or "A", unnumbered chapters are ignored. @@ -5072,8 +5304,8 @@ end % The heading type: \def\headtype{#1}% \if \headtype U% - \ifnum \absseclevel < \unmlevel - \chardef\unmlevel = \absseclevel + \ifnum \absseclevel < \unnlevel + \chardef\unnlevel = \absseclevel \fi \else % Check for appendix sections: @@ -5085,10 +5317,10 @@ end \fi\fi \fi % Check for numbered within unnumbered: - \ifnum \absseclevel > \unmlevel + \ifnum \absseclevel > \unnlevel \def\headtype{U}% \else - \chardef\unmlevel = 3 + \chardef\unnlevel = 3 \fi \fi % Now print the heading: @@ -5174,7 +5406,8 @@ end \global\let\subsubsection = \appendixsubsubsec } -\outer\parseargdef\unnumbered{\unnmhead0{#1}} % normally unnmhead0 calls unnumberedzzz +% normally unnmhead0 calls unnumberedzzz: +\outer\parseargdef\unnumbered{\unnmhead0{#1}} \def\unnumberedzzz#1{% \global\secno=0 \global\subsecno=0 \global\subsubsecno=0 \global\advance\unnumberedno by 1 @@ -5218,40 +5451,47 @@ end \let\top\unnumbered % Sections. +% \outer\parseargdef\numberedsec{\numhead1{#1}} % normally calls seczzz \def\seczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynumbered}{\the\chapno.\the\secno}% } -\outer\parseargdef\appendixsection{\apphead1{#1}} % normally calls appendixsectionzzz +% normally calls appendixsectionzzz: +\outer\parseargdef\appendixsection{\apphead1{#1}} \def\appendixsectionzzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Yappendix}{\appendixletter.\the\secno}% } \let\appendixsec\appendixsection -\outer\parseargdef\unnumberedsec{\unnmhead1{#1}} % normally calls unnumberedseczzz +% normally calls unnumberedseczzz: +\outer\parseargdef\unnumberedsec{\unnmhead1{#1}} \def\unnumberedseczzz#1{% \global\subsecno=0 \global\subsubsecno=0 \global\advance\secno by 1 \sectionheading{#1}{sec}{Ynothing}{\the\unnumberedno.\the\secno}% } % Subsections. -\outer\parseargdef\numberedsubsec{\numhead2{#1}} % normally calls numberedsubseczzz +% +% normally calls numberedsubseczzz: +\outer\parseargdef\numberedsubsec{\numhead2{#1}} \def\numberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynumbered}{\the\chapno.\the\secno.\the\subsecno}% } -\outer\parseargdef\appendixsubsec{\apphead2{#1}} % normally calls appendixsubseczzz +% normally calls appendixsubseczzz: +\outer\parseargdef\appendixsubsec{\apphead2{#1}} \def\appendixsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno}% } -\outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} %normally calls unnumberedsubseczzz +% normally calls unnumberedsubseczzz: +\outer\parseargdef\unnumberedsubsec{\unnmhead2{#1}} \def\unnumberedsubseczzz#1{% \global\subsubsecno=0 \global\advance\subsecno by 1 \sectionheading{#1}{subsec}{Ynothing}% @@ -5259,21 +5499,25 @@ end } % Subsubsections. -\outer\parseargdef\numberedsubsubsec{\numhead3{#1}} % normally numberedsubsubseczzz +% +% normally numberedsubsubseczzz: +\outer\parseargdef\numberedsubsubsec{\numhead3{#1}} \def\numberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynumbered}% {\the\chapno.\the\secno.\the\subsecno.\the\subsubsecno}% } -\outer\parseargdef\appendixsubsubsec{\apphead3{#1}} % normally appendixsubsubseczzz +% normally appendixsubsubseczzz: +\outer\parseargdef\appendixsubsubsec{\apphead3{#1}} \def\appendixsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Yappendix}% {\appendixletter.\the\secno.\the\subsecno.\the\subsubsecno}% } -\outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} %normally unnumberedsubsubseczzz +% normally unnumberedsubsubseczzz: +\outer\parseargdef\unnumberedsubsubsec{\unnmhead3{#1}} \def\unnumberedsubsubseczzz#1{% \global\advance\subsubsecno by 1 \sectionheading{#1}{subsubsec}{Ynothing}% @@ -5323,14 +5567,13 @@ end % (including whitespace, linebreaking, etc. around it), % given all the information in convenient, parsed form. -%%% Args are the skip and penalty (usually negative) +% Args are the skip and penalty (usually negative) \def\dobreak#1#2{\par\ifdim\lastskip<#1\removelastskip\penalty#2\vskip#1\fi} -%%% Define plain chapter starts, and page on/off switching for it % Parameter controlling skip before chapter headings (if needed) - \newskip\chapheadingskip +% Define plain chapter starts, and page on/off switching for it. \def\chapbreak{\dobreak \chapheadingskip {-4000}} \def\chappager{\par\vfill\supereject} % Because \domark is called before \chapoddpage, the filler page will @@ -5340,9 +5583,8 @@ end \chappager \ifodd\pageno \else \begingroup - \evenheadline={\hfil}\evenfootline={\hfil}% - \oddheadline={\hfil}\oddfootline={\hfil}% - \hbox to 0pt{}% + \headingsoff + \null \chappager \endgroup \fi @@ -5534,6 +5776,8 @@ end % \def\sectionheading#1#2#3#4{% {% + \checkenv{}% should not be in an environment. + % % Switch to the right set of fonts. \csname #2fonts\endcsname \rmisbold % @@ -5645,15 +5889,15 @@ end % % We'll almost certainly start a paragraph next, so don't let that % glue accumulate. (Not a breakpoint because it's preceded by a - % discardable item.) + % discardable item.) However, when a paragraph is not started next + % (\startdefun, \cartouche, \center, etc.), this needs to be wiped out + % or the negative glue will cause weirdly wrong output, typically + % obscuring the section heading with something else. \vskip-\parskip % - % This is purely so the last item on the list is a known \penalty > - % 10000. This is so \startdefun can avoid allowing breakpoints after - % section headings. Otherwise, it would insert a valid breakpoint between: - % - % @section sec-whatever - % @deffn def-whatever + % This is so the last item on the main vertical list is a known + % \penalty > 10000, so \startdefun, etc., can recognize the situation + % and do the needful. \penalty 10001 } @@ -5785,6 +6029,7 @@ end \def\summarycontents{% \startcontents{\putwordShortTOC}% % + \let\partentry = \shortpartentry \let\numchapentry = \shortchapentry \let\appentry = \shortchapentry \let\unnchapentry = \shortunnchapentry @@ -5840,6 +6085,19 @@ end % The last argument is the page number. % The arguments in between are the chapter number, section number, ... +% Parts, in the main contents. Replace the part number, which doesn't +% exist, with an empty box. Let's hope all the numbers have the same width. +% Also ignore the page number, which is conventionally not printed. +\def\numeralbox{\setbox0=\hbox{8}\hbox to \wd0{\hfil}} +\def\partentry#1#2#3#4{\dochapentry{\numeralbox\labelspace#1}{}} +% +% Parts, in the short toc. +\def\shortpartentry#1#2#3#4{% + \penalty-300 + \vskip.5\baselineskip plus.15\baselineskip minus.1\baselineskip + \shortchapentry{{\bf #1}}{\numeralbox}{}{}% +} + % Chapters, in the main contents. \def\numchapentry#1#2#3#4{\dochapentry{#2\labelspace#1}{#4}} % @@ -5929,9 +6187,9 @@ end \message{environments,} % @foo ... @end foo. -% @tex ... @end tex escapes into raw Tex temporarily. +% @tex ... @end tex escapes into raw TeX temporarily. % One exception: @ is still an escape character, so that @end tex works. -% But \@ or @@ will get a plain tex @ character. +% But \@ or @@ will get a plain @ character. \envdef\tex{% \setupmarkupstyle{tex}% @@ -5948,6 +6206,10 @@ end \catcode`\'=\other \escapechar=`\\ % + % ' is active in math mode (mathcode"8000). So reset it, and all our + % other math active characters (just in case), to plain's definitions. + \mathactive + % \let\b=\ptexb \let\bullet=\ptexbullet \let\c=\ptexc @@ -6051,6 +6313,12 @@ end \normbskip=\baselineskip \normpskip=\parskip \normlskip=\lineskip % Flag to tell @lisp, etc., not to narrow margin. \let\nonarrowing = t% + % + % If this cartouche directly follows a sectioning command, we need the + % \parskip glue (backspaced over by default) or the cartouche can + % collide with the section heading. + \ifnum\lastpenalty>10000 \vskip\parskip \penalty\lastpenalty \fi + % \vbox\bgroup \baselineskip=0pt\parskip=0pt\lineskip=0pt \carttop @@ -6064,7 +6332,7 @@ end \lineskip=\normlskip \parskip=\normpskip \vskip -\parskip - \comment % For explanation, see the end of \def\group. + \comment % For explanation, see the end of def\group. } \def\Ecartouche{% \ifhmode\par\fi @@ -6150,41 +6418,42 @@ end } % We often define two environments, @foo and @smallfoo. -% Let's do it by one command: -\def\makedispenv #1#2{ - \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2} - \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2} +% Let's do it in one command. #1 is the env name, #2 the definition. +\def\makedispenvdef#1#2{% + \expandafter\envdef\csname#1\endcsname {\setnormaldispenv #2}% + \expandafter\envdef\csname small#1\endcsname {\setsmalldispenv #2}% \expandafter\let\csname E#1\endcsname \afterenvbreak \expandafter\let\csname Esmall#1\endcsname \afterenvbreak } -% Define two synonyms: -\def\maketwodispenvs #1#2#3{ - \makedispenv{#1}{#3} - \makedispenv{#2}{#3} +% Define two environment synonyms (#1 and #2) for an environment. +\def\maketwodispenvdef#1#2#3{% + \makedispenvdef{#1}{#3}% + \makedispenvdef{#2}{#3}% } - -% @lisp: indented, narrowed, typewriter font; @example: same as @lisp. +% +% @lisp: indented, narrowed, typewriter font; +% @example: same as @lisp. % % @smallexample and @smalllisp: use smaller fonts. % Originally contributed by Pavel@xerox. % -\maketwodispenvs {lisp}{example}{% +\maketwodispenvdef{lisp}{example}{% \nonfillstart \tt\setupmarkupstyle{example}% \let\kbdfont = \kbdexamplefont % Allow @kbd to do something special. - \gobble % eat return + \gobble % eat return } % @display/@smalldisplay: same as @lisp except keep current font. % -\makedispenv {display}{% +\makedispenvdef{display}{% \nonfillstart \gobble } % @format/@smallformat: same as @display except don't narrow margins. % -\makedispenv{format}{% +\makedispenvdef{format}{% \let\nonarrowing = t% \nonfillstart \gobble @@ -6203,7 +6472,7 @@ end \envdef\flushright{% \let\nonarrowing = t% \nonfillstart - \advance\leftskip by 0pt plus 1fill + \advance\leftskip by 0pt plus 1fill\relax \gobble } \let\Eflushright = \afterenvbreak @@ -6238,6 +6507,8 @@ end % we're doing normal filling. So, when using \aboveenvbreak and % \afterenvbreak, temporarily make \parskip 0. % +\makedispenvdef{quotation}{\quotationstart} +% \def\quotationstart{% {\parskip=0pt \aboveenvbreak}% because \aboveenvbreak inserts \parskip \parindent=0pt @@ -6253,28 +6524,18 @@ end \parsearg\quotationlabel } -\envdef\quotation{% - \setnormaldispenv - \quotationstart -} - -\envdef\smallquotation{% - \setsmalldispenv - \quotationstart -} -\let\Esmallquotation = \Equotation - % We have retained a nonzero parskip for the environment, since we're % doing normal filling. % \def\Equotation{% \par - \ifx\quotationauthor\undefined\else + \ifx\quotationauthor\thisisundefined\else % indent a bit. \leftline{\kern 2\leftskip \sl ---\quotationauthor}% \fi {\parskip=0pt \afterenvbreak}% } +\def\Esmallquotation{\Equotation} % If we're given an argument, typeset it in bold with a colon after. \def\quotationlabel#1{% @@ -6331,21 +6592,28 @@ end % Setup for the @verbatim environment % -% Real tab expansion +% Real tab expansion. \newdimen\tabw \setbox0=\hbox{\tt\space} \tabw=8\wd0 % tab amount % -\def\starttabbox{\setbox0=\hbox\bgroup} +% We typeset each line of the verbatim in an \hbox, so we can handle +% tabs. The \global is in case the verbatim line starts with an accent, +% or some other command that starts with a begin-group. Otherwise, the +% entire \verbbox would disappear at the corresponding end-group, before +% it is typeset. Meanwhile, we can't have nested verbatim commands +% (can we?), so the \global won't be overwriting itself. +\newbox\verbbox +\def\starttabbox{\global\setbox\verbbox=\hbox\bgroup} % \begingroup \catcode`\^^I=\active \gdef\tabexpand{% \catcode`\^^I=\active \def^^I{\leavevmode\egroup - \dimen0=\wd0 % the width so far, or since the previous tab - \divide\dimen0 by\tabw - \multiply\dimen0 by\tabw % compute previous multiple of \tabw - \advance\dimen0 by\tabw % advance to next multiple of \tabw - \wd0=\dimen0 \box0 \starttabbox + \dimen\verbbox=\wd\verbbox % the width so far, or since the previous tab + \divide\dimen\verbbox by\tabw + \multiply\dimen\verbbox by\tabw % compute previous multiple of \tabw + \advance\dimen\verbbox by\tabw % advance to next multiple of \tabw + \wd\verbbox=\dimen\verbbox \box\verbbox \starttabbox }% } \endgroup @@ -6354,15 +6622,16 @@ end \def\setupverbatim{% \let\nonarrowing = t% \nonfillstart - % Easiest (and conventionally used) font for verbatim - \tt - \def\par{\leavevmode\egroup\box0\endgraf}% + \tt % easiest (and conventionally used) font for verbatim + % The \leavevmode here is for blank lines. Otherwise, we would + % never \starttabox and the \egroup would end verbatim mode. + \def\par{\leavevmode\egroup\box\verbbox\endgraf}% \tabexpand \setupmarkupstyle{verbatim}% % Respect line breaks, % print special symbols as themselves, and - % make each space count - % must do in this order: + % make each space count. + % Must do in this order: \obeylines \uncatcodespecials \sepspaces \everypar{\starttabbox}% } @@ -6419,6 +6688,7 @@ end \makevalueexpandable \setupverbatim \indexnofonts % Allow `@@' and other weird things in file names. + \wlog{texinfo.tex: doing @verbatiminclude of #1^^J}% \input #1 \afterenvbreak }% @@ -6468,7 +6738,7 @@ end % commands also insert a nobreak penalty, and we don't want to allow % a break between a section heading and a defun. % - % As a minor refinement, we avoid "club" headers by signalling + % As a further refinement, we avoid "club" headers by signalling % with penalty of 10003 after the very first @deffn in the % sequence (see above), and penalty of 10002 after any following % @def command. @@ -6505,7 +6775,7 @@ end #1#2 \endheader % common ending: \interlinepenalty = 10000 - \advance\rightskip by 0pt plus 1fil + \advance\rightskip by 0pt plus 1fil\relax \endgraf \nobreak\vskip -\parskip \penalty\defunpenalty % signal to \startdefun and \dodefunx @@ -6535,13 +6805,36 @@ end \def\domakedefun#1#2#3{% \envdef#1{% \startdefun + \doingtypefnfalse % distinguish typed functions from all else \parseargusing\activeparens{\printdefunline#3}% }% \def#2{\dodefunx#1}% \def#3% } -%%% Untyped functions: +\newif\ifdoingtypefn % doing typed function? +\newif\ifrettypeownline % typeset return type on its own line? + +% @deftypefnnewline on|off says whether the return type of typed functions +% are printed on their own line. This affects @deftypefn, @deftypefun, +% @deftypeop, and @deftypemethod. +% +\parseargdef\deftypefnnewline{% + \def\temp{#1}% + \ifx\temp\onword + \expandafter\let\csname SETtxideftypefnnl\endcsname + = \empty + \else\ifx\temp\offword + \expandafter\let\csname SETtxideftypefnnl\endcsname + = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @txideftypefnnl value `\temp', + must be on|off}% + \fi\fi +} + +% Untyped functions: % @deffn category name args \makedefun{deffn}{\deffngeneral{}} @@ -6560,7 +6853,7 @@ end \defname{#2}{}{#3}\magicamp\defunargs{#4\unskip}% } -%%% Typed functions: +% Typed functions: % @deftypefn category type name args \makedefun{deftypefn}{\deftypefngeneral{}} @@ -6575,10 +6868,11 @@ end % \def\deftypefngeneral#1#2 #3 #4 #5\endheader{% \dosubind{fn}{\code{#4}}{#1}% + \doingtypefntrue \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } -%%% Typed variables: +% Typed variables: % @deftypevr category type var args \makedefun{deftypevr}{\deftypecvgeneral{}} @@ -6596,7 +6890,7 @@ end \defname{#2}{#3}{#4}\defunargs{#5\unskip}% } -%%% Untyped variables: +% Untyped variables: % @defvr category var args \makedefun{defvr}#1 {\deftypevrheader{#1} {} } @@ -6607,7 +6901,8 @@ end % \defcvof {category of}class var args \def\defcvof#1#2 {\deftypecvof{#1}#2 {} } -%%% Type: +% Types: + % @deftp category name args \makedefun{deftp}#1 #2 #3\endheader{% \doind{tp}{\code{#2}}% @@ -6635,25 +6930,49 @@ end % We are followed by (but not passed) the arguments, if any. % \def\defname#1#2#3{% + \par % Get the values of \leftskip and \rightskip as they were outside the @def... \advance\leftskip by -\defbodyindent % - % How we'll format the type name. Putting it in brackets helps + % Determine if we are typesetting the return type of a typed function + % on a line by itself. + \rettypeownlinefalse + \ifdoingtypefn % doing a typed function specifically? + % then check user option for putting return type on its own line: + \expandafter\ifx\csname SETtxideftypefnnl\endcsname\relax \else + \rettypeownlinetrue + \fi + \fi + % + % How we'll format the category name. Putting it in brackets helps % distinguish it from the body text that may end up on the next line % just below it. \def\temp{#1}% \setbox0=\hbox{\kern\deflastargmargin \ifx\temp\empty\else [\rm\temp]\fi} % - % Figure out line sizes for the paragraph shape. + % Figure out line sizes for the paragraph shape. We'll always have at + % least two. + \tempnum = 2 + % % The first line needs space for \box0; but if \rightskip is nonzero, % we need only space for the part of \box0 which exceeds it: \dimen0=\hsize \advance\dimen0 by -\wd0 \advance\dimen0 by \rightskip + % + % If doing a return type on its own line, we'll have another line. + \ifrettypeownline + \advance\tempnum by 1 + \def\maybeshapeline{0in \hsize}% + \else + \def\maybeshapeline{}% + \fi + % % The continuations: \dimen2=\hsize \advance\dimen2 by -\defargsindent - % (plain.tex says that \dimen1 should be used only as global.) - \parshape 2 0in \dimen0 \defargsindent \dimen2 % - % Put the type name to the right margin. + % The final paragraph shape: + \parshape \tempnum 0in \dimen0 \maybeshapeline \defargsindent \dimen2 + % + % Put the category name at the right margin. \noindent \hbox to 0pt{% \hfil\box0 \kern-\hsize @@ -6675,8 +6994,16 @@ end % . this still does not fix the ?` and !` ligatures, but so far no % one has made identifiers using them :). \df \tt - \def\temp{#2}% return value type - \ifx\temp\empty\else \tclose{\temp} \fi + \def\temp{#2}% text of the return type + \ifx\temp\empty\else + \tclose{\temp}% typeset the return type + \ifrettypeownline + % put return type on its own line; prohibit line break following: + \hfil\vadjust{\nobreak}\break + \else + \space % type on same line, so just followed by a space + \fi + \fi % no return type #3% output function name }% {\rm\enskip}% hskip 0.5 em of \tenrm @@ -6794,7 +7121,7 @@ end % To do this right we need a feature of e-TeX, \scantokens, % which we arrange to emulate with a temporary file in ordinary TeX. -\ifx\eTeXversion\undefined +\ifx\eTeXversion\thisisundefined \newwrite\macscribble \def\scantokens#1{% \toks0={#1}% @@ -6805,25 +7132,30 @@ end } \fi -\def\scanmacro#1{% - \begingroup - \newlinechar`\^^M - \let\xeatspaces\eatspaces - % Undo catcode changes of \startcontents and \doprintindex - % When called from @insertcopying or (short)caption, we need active - % backslash to get it printed correctly. Previously, we had - % \catcode`\\=\other instead. We'll see whether a problem appears - % with macro expansion. --kasal, 19aug04 - \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ - % ... and \example - \spaceisspace - % - % Append \endinput to make sure that TeX does not see the ending newline. - % I've verified that it is necessary both for e-TeX and for ordinary TeX - % --kasal, 29nov03 - \scantokens{#1\endinput}% - \endgroup -} +\def\scanmacro#1{\begingroup + \newlinechar`\^^M + \let\xeatspaces\eatspaces + % + % Undo catcode changes of \startcontents and \doprintindex + % When called from @insertcopying or (short)caption, we need active + % backslash to get it printed correctly. Previously, we had + % \catcode`\\=\other instead. We'll see whether a problem appears + % with macro expansion. --kasal, 19aug04 + \catcode`\@=0 \catcode`\\=\active \escapechar=`\@ + % + % ... and for \example: + \spaceisspace + % + % The \empty here causes a following catcode 5 newline to be eaten as + % part of reading whitespace after a control sequence. It does not + % eat a catcode 13 newline. There's no good way to handle the two + % cases (untried: maybe e-TeX's \everyeof could help, though plain TeX + % would then have different behavior). See the Macro Details node in + % the manual for the workaround we recommend for macros and + % line-oriented commands. + % + \scantokens{#1\empty}% +\endgroup} \def\scanexp#1{% \edef\temp{\noexpand\scanmacro{#1}}% @@ -6877,17 +7209,18 @@ end % Macro bodies are absorbed as an argument in a context where % all characters are catcode 10, 11 or 12, except \ which is active -% (as in normal texinfo). It is necessary to change the definition of \. - +% (as in normal texinfo). It is necessary to change the definition of \ +% to recognize macro arguments; this is the job of \mbodybackslash. +% % Non-ASCII encodings make 8-bit characters active, so un-activate % them to avoid their expansion. Must do this non-globally, to % confine the change to the current group. - +% % It's necessary to have hard CRs when the macro is executed. This is -% done by making ^^M (\endlinechar) catcode 12 when reading the macro +% done by making ^^M (\endlinechar) catcode 12 when reading the macro % body, and then making it the \newlinechar in \scanmacro. - -\def\scanctxt{% +% +\def\scanctxt{% used as subroutine \catcode`\"=\other \catcode`\+=\other \catcode`\<=\other @@ -6900,13 +7233,13 @@ end \ifx\declaredencoding\ascii \else \setnonasciicharscatcodenonglobal\other \fi } -\def\scanargctxt{% +\def\scanargctxt{% used for copying and captions, not macros. \scanctxt \catcode`\\=\other \catcode`\^^M=\other } -\def\macrobodyctxt{% +\def\macrobodyctxt{% used for @macro definitions \scanctxt \catcode`\{=\other \catcode`\}=\other @@ -6914,32 +7247,56 @@ end \usembodybackslash } -\def\macroargctxt{% +\def\macroargctxt{% used when scanning invocations \scanctxt - \catcode`\\=\other + \catcode`\\=0 } +% why catcode 0 for \ in the above? To recognize \\ \{ \} as "escapes" +% for the single characters \ { }. Thus, we end up with the "commands" +% that would be written @\ @{ @} in a Texinfo document. +% +% We already have @{ and @}. For @\, we define it here, and only for +% this purpose, to produce a typewriter backslash (so, the @\ that we +% define for @math can't be used with @macro calls): +% +\def\\{\normalbackslash}% +% +% We would like to do this for \, too, since that is what makeinfo does. +% But it is not possible, because Texinfo already has a command @, for a +% cedilla accent. Documents must use @comma{} instead. +% +% \anythingelse will almost certainly be an error of some kind. + % \mbodybackslash is the definition of \ in @macro bodies. % It maps \foo\ => \csname macarg.foo\endcsname => #N % where N is the macro parameter number. % We define \csname macarg.\endcsname to be \realbackslash, so % \\ in macro replacement text gets you a backslash. - +% {\catcode`@=0 @catcode`@\=@active @gdef@usembodybackslash{@let\=@mbodybackslash} @gdef@mbodybackslash#1\{@csname macarg.#1@endcsname} } \expandafter\def\csname macarg.\endcsname{\realbackslash} +\def\margbackslash#1{\char`\#1 } + \def\macro{\recursivefalse\parsearg\macroxxx} \def\rmacro{\recursivetrue\parsearg\macroxxx} \def\macroxxx#1{% - \getargs{#1}% now \macname is the macname and \argl the arglist + \getargs{#1}% now \macname is the macname and \argl the arglist \ifx\argl\empty % no arguments - \paramno=0% + \paramno=0\relax \else \expandafter\parsemargdef \argl;% + \if\paramno>256\relax + \ifx\eTeXversion\thisisundefined + \errhelp = \EMsimple + \errmessage{You need eTeX to compile a file with macros with more than 256 arguments} + \fi + \fi \fi \if1\csname ismacro.\the\macname\endcsname \message{Warning: redefining \the\macname}% @@ -6986,46 +7343,269 @@ end % an opening brace, and that opening brace is not consumed. \def\getargs#1{\getargsxxx#1{}} \def\getargsxxx#1#{\getmacname #1 \relax\getmacargs} -\def\getmacname #1 #2\relax{\macname={#1}} +\def\getmacname#1 #2\relax{\macname={#1}} \def\getmacargs#1{\def\argl{#1}} -% Parse the optional {params} list. Set up \paramno and \paramlist -% so \defmacro knows what to do. Define \macarg.blah for each blah -% in the params list, to be ##N where N is the position in that list. -% That gets used by \mbodybackslash (above). +% For macro processing make @ a letter so that we can make Texinfo private macro names. +\edef\texiatcatcode{\the\catcode`\@} +\catcode `@=11\relax +% Parse the optional {params} list. Set up \paramno and \paramlist +% so \defmacro knows what to do. Define \macarg.BLAH for each BLAH +% in the params list to some hook where the argument si to be expanded. If +% there are less than 10 arguments that hook is to be replaced by ##N where N +% is the position in that list, that is to say the macro arguments are to be +% defined `a la TeX in the macro body. +% +% That gets used by \mbodybackslash (above). +% % We need to get `macro parameter char #' into several definitions. -% The technique used is stolen from LaTeX: let \hash be something +% The technique used is stolen from LaTeX: let \hash be something % unexpandable, insert that wherever you need a #, and then redefine % it to # just before using the token list produced. % % The same technique is used to protect \eatspaces till just before % the macro is used. - -\def\parsemargdef#1;{\paramno=0\def\paramlist{}% - \let\hash\relax\let\xeatspaces\relax\parsemargdefxxx#1,;,} +% +% If there are 10 or more arguments, a different technique is used, where the +% hook remains in the body, and when macro is to be expanded the body is +% processed again to replace the arguments. +% +% In that case, the hook is \the\toks N-1, and we simply set \toks N-1 to the +% argument N value and then \edef the body (nothing else will expand because of +% the catcode regime underwhich the body was input). +% +% If you compile with TeX (not eTeX), and you have macros with 10 or more +% arguments, you need that no macro has more than 256 arguments, otherwise an +% error is produced. +\def\parsemargdef#1;{% + \paramno=0\def\paramlist{}% + \let\hash\relax + \let\xeatspaces\relax + \parsemargdefxxx#1,;,% + % In case that there are 10 or more arguments we parse again the arguments + % list to set new definitions for the \macarg.BLAH macros corresponding to + % each BLAH argument. It was anyhow needed to parse already once this list + % in order to count the arguments, and as macros with at most 9 arguments + % are by far more frequent than macro with 10 or more arguments, defining + % twice the \macarg.BLAH macros does not cost too much processing power. + \ifnum\paramno<10\relax\else + \paramno0\relax + \parsemmanyargdef@@#1,;,% 10 or more arguments + \fi +} \def\parsemargdefxxx#1,{% \if#1;\let\next=\relax \else \let\next=\parsemargdefxxx - \advance\paramno by 1% + \advance\paramno by 1 \expandafter\edef\csname macarg.\eatspaces{#1}\endcsname {\xeatspaces{\hash\the\paramno}}% \edef\paramlist{\paramlist\hash\the\paramno,}% \fi\next} +\def\parsemmanyargdef@@#1,{% + \if#1;\let\next=\relax + \else + \let\next=\parsemmanyargdef@@ + \edef\tempb{\eatspaces{#1}}% + \expandafter\def\expandafter\tempa + \expandafter{\csname macarg.\tempb\endcsname}% + % Note that we need some extra \noexpand\noexpand, this is because we + % don't want \the to be expanded in the \parsermacbody as it uses an + % \xdef . + \expandafter\edef\tempa + {\noexpand\noexpand\noexpand\the\toks\the\paramno}% + \advance\paramno by 1\relax + \fi\next} + % These two commands read recursive and nonrecursive macro bodies. % (They're different since rec and nonrec macros end differently.) +% +\catcode `\@\texiatcatcode \long\def\parsemacbody#1@end macro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% \long\def\parsermacbody#1@end rmacro% {\xdef\temp{\eatcr{#1}}\endgroup\defmacro}% +\catcode `\@=11\relax -% This defines the macro itself. There are six cases: recursive and -% nonrecursive macros of zero, one, and many arguments. +\let\endargs@\relax +\let\nil@\relax +\def\nilm@{\nil@}% +\long\def\nillm@{\nil@}% + +% This macro is expanded during the Texinfo macro expansion, not during its +% definition. It gets all the arguments values and assigns them to macros +% macarg.ARGNAME +% +% #1 is the macro name +% #2 is the list of argument names +% #3 is the list of argument values +\def\getargvals@#1#2#3{% + \def\macargdeflist@{}% + \def\saveparamlist@{#2}% Need to keep a copy for parameter expansion. + \def\paramlist{#2,\nil@}% + \def\macroname{#1}% + \begingroup + \macroargctxt + \def\argvaluelist{#3,\nil@}% + \def\@tempa{#3}% + \ifx\@tempa\empty + \setemptyargvalues@ + \else + \getargvals@@ + \fi +} + +% +\def\getargvals@@{% + \ifx\paramlist\nilm@ + % Some sanity check needed here that \argvaluelist is also empty. + \ifx\argvaluelist\nillm@ + \else + \errhelp = \EMsimple + \errmessage{Too many arguments in macro `\macroname'!}% + \fi + \let\next\macargexpandinbody@ + \else + \ifx\argvaluelist\nillm@ + % No more arguments values passed to macro. Set remaining named-arg + % macros to empty. + \let\next\setemptyargvalues@ + \else + % pop current arg name into \@tempb + \def\@tempa##1{\pop@{\@tempb}{\paramlist}##1\endargs@}% + \expandafter\@tempa\expandafter{\paramlist}% + % pop current argument value into \@tempc + \def\@tempa##1{\longpop@{\@tempc}{\argvaluelist}##1\endargs@}% + \expandafter\@tempa\expandafter{\argvaluelist}% + % Here \@tempb is the current arg name and \@tempc is the current arg value. + % First place the new argument macro definition into \@tempd + \expandafter\macname\expandafter{\@tempc}% + \expandafter\let\csname macarg.\@tempb\endcsname\relax + \expandafter\def\expandafter\@tempe\expandafter{% + \csname macarg.\@tempb\endcsname}% + \edef\@tempd{\long\def\@tempe{\the\macname}}% + \push@\@tempd\macargdeflist@ + \let\next\getargvals@@ + \fi + \fi + \next +} + +\def\push@#1#2{% + \expandafter\expandafter\expandafter\def + \expandafter\expandafter\expandafter#2% + \expandafter\expandafter\expandafter{% + \expandafter#1#2}% +} + +% Replace arguments by their values in the macro body, and place the result +% in macro \@tempa +\def\macvalstoargs@{% + % To do this we use the property that token registers that are \the'ed + % within an \edef expand only once. So we are going to place all argument + % values into respective token registers. + % + % First we save the token context, and initialize argument numbering. + \begingroup + \paramno0\relax + % Then, for each argument number #N, we place the corresponding argument + % value into a new token list register \toks#N + \expandafter\putargsintokens@\saveparamlist@,;,% + % Then, we expand the body so that argument are replaced by their + % values. The trick for values not to be expanded themselves is that they + % are within tokens and that tokens expand only once in an \edef . + \edef\@tempc{\csname mac.\macroname .body\endcsname}% + % Now we restore the token stack pointer to free the token list registers + % which we have used, but we make sure that expanded body is saved after + % group. + \expandafter + \endgroup + \expandafter\def\expandafter\@tempa\expandafter{\@tempc}% + } + +\def\macargexpandinbody@{% + %% Define the named-macro outside of this group and then close this group. + \expandafter + \endgroup + \macargdeflist@ + % First the replace in body the macro arguments by their values, the result + % is in \@tempa . + \macvalstoargs@ + % Then we point at the \norecurse or \gobble (for recursive) macro value + % with \@tempb . + \expandafter\let\expandafter\@tempb\csname mac.\macroname .recurse\endcsname + % Depending on whether it is recursive or not, we need some tailing + % \egroup . + \ifx\@tempb\gobble + \let\@tempc\relax + \else + \let\@tempc\egroup + \fi + % And now we do the real job: + \edef\@tempd{\noexpand\@tempb{\macroname}\noexpand\scanmacro{\@tempa}\@tempc}% + \@tempd +} + +\def\putargsintokens@#1,{% + \if#1;\let\next\relax + \else + \let\next\putargsintokens@ + % First we allocate the new token list register, and give it a temporary + % alias \@tempb . + \toksdef\@tempb\the\paramno + % Then we place the argument value into that token list register. + \expandafter\let\expandafter\@tempa\csname macarg.#1\endcsname + \expandafter\@tempb\expandafter{\@tempa}% + \advance\paramno by 1\relax + \fi + \next +} + +% Save the token stack pointer into macro #1 +\def\texisavetoksstackpoint#1{\edef#1{\the\@cclvi}} +% Restore the token stack pointer from number in macro #1 +\def\texirestoretoksstackpoint#1{\expandafter\mathchardef\expandafter\@cclvi#1\relax} +% newtoks that can be used non \outer . +\def\texinonouternewtoks{\alloc@ 5\toks \toksdef \@cclvi} + +% Tailing missing arguments are set to empty +\def\setemptyargvalues@{% + \ifx\paramlist\nilm@ + \let\next\macargexpandinbody@ + \else + \expandafter\setemptyargvaluesparser@\paramlist\endargs@ + \let\next\setemptyargvalues@ + \fi + \next +} + +\def\setemptyargvaluesparser@#1,#2\endargs@{% + \expandafter\def\expandafter\@tempa\expandafter{% + \expandafter\def\csname macarg.#1\endcsname{}}% + \push@\@tempa\macargdeflist@ + \def\paramlist{#2}% +} + +% #1 is the element target macro +% #2 is the list macro +% #3,#4\endargs@ is the list value +\def\pop@#1#2#3,#4\endargs@{% + \def#1{#3}% + \def#2{#4}% +} +\long\def\longpop@#1#2#3,#4\endargs@{% + \long\def#1{#3}% + \long\def#2{#4}% +} + +% This defines a Texinfo @macro. There are eight cases: recursive and +% nonrecursive macros of zero, one, up to nine, and many arguments. % Much magic with \expandafter here. % \xdef is used so that macro definitions will survive the file % they're defined in; @include reads the file inside a group. +% \def\defmacro{% \let\hash=##% convert placeholders to macro parameter chars \ifrecursive @@ -7040,17 +7620,25 @@ end \expandafter\noexpand\csname\the\macname xxx\endcsname}% \expandafter\xdef\csname\the\macname xxx\endcsname##1{% \egroup\noexpand\scanmacro{\temp}}% - \else % many - \expandafter\xdef\csname\the\macname\endcsname{% - \bgroup\noexpand\macroargctxt - \noexpand\csname\the\macname xx\endcsname}% - \expandafter\xdef\csname\the\macname xx\endcsname##1{% - \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% - \expandafter\expandafter - \expandafter\xdef - \expandafter\expandafter - \csname\the\macname xxx\endcsname - \paramlist{\egroup\noexpand\scanmacro{\temp}}% + \else + \ifnum\paramno<10\relax % at most 9 + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{\egroup\noexpand\scanmacro{\temp}}% + \else % 10 or more + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\getargvals@{\the\macname}{\argl}% + }% + \global\expandafter\let\csname mac.\the\macname .body\endcsname\temp + \global\expandafter\let\csname mac.\the\macname .recurse\endcsname\gobble + \fi \fi \else \ifcase\paramno @@ -7067,29 +7655,40 @@ end \egroup \noexpand\norecurse{\the\macname}% \noexpand\scanmacro{\temp}\egroup}% - \else % many - \expandafter\xdef\csname\the\macname\endcsname{% - \bgroup\noexpand\macroargctxt - \expandafter\noexpand\csname\the\macname xx\endcsname}% - \expandafter\xdef\csname\the\macname xx\endcsname##1{% - \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% - \expandafter\expandafter - \expandafter\xdef - \expandafter\expandafter - \csname\the\macname xxx\endcsname - \paramlist{% - \egroup - \noexpand\norecurse{\the\macname}% - \noexpand\scanmacro{\temp}\egroup}% + \else % at most 9 + \ifnum\paramno<10\relax + \expandafter\xdef\csname\the\macname\endcsname{% + \bgroup\noexpand\macroargctxt + \expandafter\noexpand\csname\the\macname xx\endcsname}% + \expandafter\xdef\csname\the\macname xx\endcsname##1{% + \expandafter\noexpand\csname\the\macname xxx\endcsname ##1,}% + \expandafter\expandafter + \expandafter\xdef + \expandafter\expandafter + \csname\the\macname xxx\endcsname + \paramlist{% + \egroup + \noexpand\norecurse{\the\macname}% + \noexpand\scanmacro{\temp}\egroup}% + \else % 10 or more: + \expandafter\xdef\csname\the\macname\endcsname{% + \noexpand\getargvals@{\the\macname}{\argl}% + }% + \global\expandafter\let\csname mac.\the\macname .body\endcsname\temp + \global\expandafter\let\csname mac.\the\macname .recurse\endcsname\norecurse + \fi \fi \fi} +\catcode `\@\texiatcatcode\relax + \def\norecurse#1{\bgroup\cslet{#1}{macsave.#1}} % \braceorline decides whether the next nonwhitespace character is a % {. If so it reads up to the closing }, if not, it reads the whole % line. Whatever was read is then fed to the next control sequence -% as an argument (by \parsebrace or \parsearg) +% as an argument (by \parsebrace or \parsearg). +% \def\braceorline#1{\let\macnamexxx=#1\futurelet\nchar\braceorlinexxx} \def\braceorlinexxx{% \ifx\nchar\bgroup\else @@ -7099,7 +7698,8 @@ end % @alias. % We need some trickery to remove the optional spaces around the equal -% sign. Just make them active and then expand them all to nothing. +% sign. Make them active and then expand them all to nothing. +% \def\alias{\parseargusing\obeyspaces\aliasxxx} \def\aliasxxx #1{\aliasyyy#1\relax} \def\aliasyyy #1=#2\relax{% @@ -7120,7 +7720,8 @@ end % @inforef is relatively simple. \def\inforef #1{\inforefzzz #1,,,,**} -\def\inforefzzz #1,#2,#3,#4**{\putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, +\def\inforefzzz #1,#2,#3,#4**{% + \putwordSee{} \putwordInfo{} \putwordfile{} \file{\ignorespaces #3{}}, node \samp{\ignorespaces#1{}}} % @node's only job in TeX is to define \lastnode, which is used in @@ -7181,11 +7782,32 @@ end \toks0 = \expandafter{\lastsection}% \immediate \writexrdef{title}{\the\toks0 }% \immediate \writexrdef{snt}{\csname #2\endcsname}% \Ynumbered etc. - \safewhatsit{\writexrdef{pg}{\folio}}% will be written later, during \shipout + \safewhatsit{\writexrdef{pg}{\folio}}% will be written later, at \shipout }% \fi } +% @xrefautosectiontitle on|off says whether @section(ing) names are used +% automatically in xrefs, if the third arg is not explicitly specified. +% This was provided as a "secret" @set xref-automatic-section-title +% variable, now it's official. +% +\parseargdef\xrefautomaticsectiontitle{% + \def\temp{#1}% + \ifx\temp\onword + \expandafter\let\csname SETxref-automatic-section-title\endcsname + = \empty + \else\ifx\temp\offword + \expandafter\let\csname SETxref-automatic-section-title\endcsname + = \relax + \else + \errhelp = \EMsimple + \errmessage{Unknown @xrefautomaticsectiontitle value `\temp', + must be on|off}% + \fi\fi +} + + % @xref, @pxref, and @ref generate cross-references. For \xrefX, #1 is % the node name, #2 the name of the Info cross-reference, #3 the printed % node name, #4 the name of the Info file, #5 the name of the printed @@ -7194,26 +7816,36 @@ end \def\pxref#1{\putwordsee{} \xrefX[#1,,,,,,,]} \def\xref#1{\putwordSee{} \xrefX[#1,,,,,,,]} \def\ref#1{\xrefX[#1,,,,,,,]} +% +\newbox\topbox +\newbox\printedrefnamebox +\newbox\printedmanualbox +% \def\xrefX[#1,#2,#3,#4,#5,#6]{\begingroup \unsepspaces - \def\printedmanual{\ignorespaces #5}% + % \def\printedrefname{\ignorespaces #3}% - \setbox1=\hbox{\printedmanual\unskip}% - \setbox0=\hbox{\printedrefname\unskip}% - \ifdim \wd0 = 0pt + \setbox\printedrefnamebox = \hbox{\printedrefname\unskip}% + % + \def\printedmanual{\ignorespaces #5}% + \setbox\printedmanualbox = \hbox{\printedmanual\unskip}% + % + % If the printed reference name (arg #3) was not explicitly given in + % the @xref, figure out what we want to use. + \ifdim \wd\printedrefnamebox = 0pt % No printed node name was explicitly given. - \expandafter\ifx\csname SETxref-automatic-section-title\endcsname\relax - % Use the node name inside the square brackets. + \expandafter\ifx\csname SETxref-automatic-section-title\endcsname \relax + % Not auto section-title: use node name inside the square brackets. \def\printedrefname{\ignorespaces #1}% \else - % Use the actual chapter/section title appear inside - % the square brackets. Use the real section title if we have it. - \ifdim \wd1 > 0pt - % It is in another manual, so we don't have it. + % Auto section-title: use chapter/section title inside + % the square brackets if we have it. + \ifdim \wd\printedmanualbox > 0pt + % It is in another manual, so we don't have it; use node name. \def\printedrefname{\ignorespaces #1}% \else \ifhavexrefs - % We know the real title if we have the xref values. + % We (should) know the real title if we have the xref values. \def\printedrefname{\refx{#1-title}{}}% \else % Otherwise just copy the Info node name. @@ -7227,13 +7859,13 @@ end \ifpdf {\indexnofonts \turnoffactive + \makevalueexpandable % This expands tokens, so do it after making catcode changes, so _ % etc. don't get their TeX definitions. \getfilename{#4}% % - % See comments at \activebackslashdouble. - {\activebackslashdouble \xdef\pdfxrefdest{#1}% - \backslashparens\pdfxrefdest}% + \edef\pdfxrefdest{#1}% + \txiescapepdf\pdfxrefdest % \leavevmode \startlink attr{/Border [0 0 0]}% @@ -7260,7 +7892,7 @@ end \iffloat\Xthisreftitle % If the user specified the print name (third arg) to the ref, % print it instead of our usual "Figure 1.2". - \ifdim\wd0 = 0pt + \ifdim\wd\printedrefnamebox = 0pt \refx{#1-snt}{}% \else \printedrefname @@ -7268,21 +7900,46 @@ end % % if the user also gave the printed manual name (fifth arg), append % "in MANUALNAME". - \ifdim \wd1 > 0pt + \ifdim \wd\printedmanualbox > 0pt \space \putwordin{} \cite{\printedmanual}% \fi \else % node/anchor (non-float) references. - % - % If we use \unhbox0 and \unhbox1 to print the node names, TeX does not - % insert empty discretionaries after hyphens, which means that it will - % not find a line break at a hyphen in a node names. Since some manuals - % are best written with fairly long node names, containing hyphens, this - % is a loss. Therefore, we give the text of the node name again, so it - % is as if TeX is seeing it for the first time. - \ifdim \wd1 > 0pt - \putwordSection{} ``\printedrefname'' \putwordin{} \cite{\printedmanual}% + % + % If we use \unhbox to print the node names, TeX does not insert + % empty discretionaries after hyphens, which means that it will not + % find a line break at a hyphen in a node names. Since some manuals + % are best written with fairly long node names, containing hyphens, + % this is a loss. Therefore, we give the text of the node name + % again, so it is as if TeX is seeing it for the first time. + % + % Cross-manual reference. Only include the "Section ``foo'' in" if + % the foo is neither missing or Top. Thus, @xref{,,,foo,The Foo Manual} + % outputs simply "see The Foo Manual". + \ifdim \wd\printedmanualbox > 0pt + % What is the 7sp about? The idea is that we also want to omit + % the Section part if we would be printing "Top", since they are + % clearly trying to refer to the whole manual. But, this being + % TeX, we can't easily compare strings while ignoring the possible + % spaces before and after in the input. By adding the arbitrary + % 7sp, we make it much less likely that a real node name would + % happen to have the same width as "Top" (e.g., in a monospaced font). + % I hope it will never happen in practice. + % + % For the same basic reason, we retypeset the "Top" at every + % reference, since the current font is indeterminate. + % + \setbox\topbox = \hbox{Top\kern7sp}% + \setbox2 = \hbox{\ignorespaces \printedrefname \unskip \kern7sp}% + \ifdim \wd2 > 7sp + \ifdim \wd2 = \wd\topbox \else + \putwordSection{} ``\printedrefname'' \putwordin{}\space + \fi + \fi + \cite{\printedmanual}% \else + % Reference in this manual. + % % _ (for example) has to be the character _ for the purposes of the % control sequence corresponding to the node, but it has to expand % into the usual \leavevmode...\vrule stuff for purposes of @@ -7294,7 +7951,7 @@ end \setbox2 = \hbox{\ignorespaces \refx{#1-snt}{}}% \ifdim \wd2 > 0pt \refx{#1-snt}\space\fi }% - % output the `[mynode]' via a macro so it can be overridden. + % output the `[mynode]' via the macro below so it can be overridden. \xrefprintnodename\printedrefname % % But we always want a comma and a space: @@ -7357,7 +8014,8 @@ end \angleleft un\-de\-fined\angleright \iflinks \ifhavexrefs - \message{\linenumber Undefined cross reference `#1'.}% + {\toks0 = {#1}% avoid expansion of possibly-complex value + \message{\linenumber Undefined cross reference `\the\toks0'.}}% \else \ifwarnedxrefs\else \global\warnedxrefstrue @@ -7521,7 +8179,7 @@ end % space to prevent strange expansion errors.) \def\supereject{\par\penalty -20000\footnoteno =0 } -% @footnotestyle is meaningful for info output only. +% @footnotestyle is meaningful for Info output only. \let\footnotestyle=\comment {\catcode `\@=11 @@ -7584,6 +8242,8 @@ end % expands into a box, it must come within the paragraph, lest it % provide a place where TeX can split the footnote. \footstrut + % + % Invoke rest of plain TeX footnote routine. \futurelet\next\fo@t } }%end \catcode `\@=11 @@ -7671,7 +8331,7 @@ end it from ftp://tug.org/tex/epsf.tex.} % \def\image#1{% - \ifx\epsfbox\undefined + \ifx\epsfbox\thisisundefined \ifwarnednoepsf \else \errhelp = \noepsfhelp \errmessage{epsf.tex not found, images will be ignored}% @@ -7687,7 +8347,7 @@ end % #2 is (optional) width, #3 is (optional) height. % #4 is (ignored optional) html alt text. % #5 is (ignored optional) extension. -% #6 is just the usual extra ignored arg for parsing this stuff. +% #6 is just the usual extra ignored arg for parsing stuff. \newif\ifimagevmode \def\imagexxx#1,#2,#3,#4,#5,#6\finish{\begingroup \catcode`\^^M = 5 % in case we're inside an example @@ -7695,6 +8355,13 @@ end % If the image is by itself, center it. \ifvmode \imagevmodetrue + \else \ifx\centersub\centerV + % for @center @image, we need a vbox so we can have our vertical space + \imagevmodetrue + \vbox\bgroup % vbox has better behavior than vtop herev + \fi\fi + % + \ifimagevmode \nobreak\medskip % Usually we'll have text after the image which will insert % \parskip glue, so insert it here too to equalize the space @@ -7704,9 +8371,13 @@ end \fi % % Leave vertical mode so that indentation from an enclosing - % environment such as @quotation is respected. On the other hand, if - % it's at the top level, we don't want the normal paragraph indentation. - \noindent + % environment such as @quotation is respected. + % However, if we're at the top level, we don't want the + % normal paragraph indentation. + % On the other hand, if we are in the case of @center @image, we don't + % want to start a paragraph, which will create a hsize-width box and + % eradicate the centering. + \ifx\centersub\centerV\else \noindent \fi % % Output the image. \ifpdf @@ -7718,7 +8389,10 @@ end \epsfbox{#1.eps}% \fi % - \ifimagevmode \medskip \fi % space after the standalone image + \ifimagevmode + \medskip % space after a standalone image + \fi + \ifx\centersub\centerV \egroup \fi \endgroup} @@ -8136,7 +8810,7 @@ directory should work if nowhere else does.} % % Latin1 (ISO-8859-1) character definitions. \def\latonechardefs{% - \gdef^^a0{~} + \gdef^^a0{\tie} \gdef^^a1{\exclamdown} \gdef^^a2{\missingcharmsg{CENT SIGN}} \gdef^^a3{{\pounds}} @@ -8166,7 +8840,7 @@ directory should work if nowhere else does.} \gdef^^b9{$^1$} \gdef^^ba{\ordm} % - \gdef^^bb{\guilletright} + \gdef^^bb{\guillemetright} \gdef^^bc{$1\over4$} \gdef^^bd{$1\over2$} \gdef^^be{$3\over4$} @@ -8258,7 +8932,7 @@ directory should work if nowhere else does.} % Latin2 (ISO-8859-2) character definitions. \def\lattwochardefs{% - \gdef^^a0{~} + \gdef^^a0{\tie} \gdef^^a1{\ogonek{A}} \gdef^^a2{\u{}} \gdef^^a3{\L} @@ -8339,8 +9013,8 @@ directory should work if nowhere else does.} \gdef^^ea{\ogonek{e}} \gdef^^eb{\"e} \gdef^^ec{\v e} - \gdef^^ed{\'\i} - \gdef^^ee{\^\i} + \gdef^^ed{\'{\dotless{i}}} + \gdef^^ee{\^{\dotless{i}}} \gdef^^ef{\v d} % \gdef^^f0{\dh} @@ -8431,7 +9105,7 @@ directory should work if nowhere else does.} \gdef\DeclareUnicodeCharacter#1#2{% \countUTFz = "#1\relax - \wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}% + %\wlog{\space\space defining Unicode char U+#1 (decimal \the\countUTFz)}% \begingroup \parseXMLCharref \def\UTFviiiTwoOctets##1##2{% @@ -8899,8 +9573,8 @@ directory should work if nowhere else does.} % Prevent underfull vbox error messages. \vbadness = 10000 -% Don't be so finicky about underfull hboxes, either. -\hbadness = 2000 +% Don't be very finicky about underfull hboxes, either. +\hbadness = 6666 % Following George Bush, get rid of widows and orphans. \widowpenalty=10000 @@ -9107,28 +9781,21 @@ directory should work if nowhere else does.} \message{and turning on texinfo input format.} +\def^^L{\par} % remove \outer, so ^L can appear in an @comment + % DEL is a comment character, in case @c does not suffice. \catcode`\^^? = 14 % Define macros to output various characters with catcode for normal text. -\catcode`\"=\other -\catcode`\~=\other -\catcode`\^=\other -\catcode`\_=\other -\catcode`\|=\other -\catcode`\<=\other -\catcode`\>=\other -\catcode`\+=\other -\catcode`\$=\other -\def\normaldoublequote{"} -\def\normaltilde{~} -\def\normalcaret{^} -\def\normalunderscore{_} -\def\normalverticalbar{|} -\def\normalless{<} -\def\normalgreater{>} -\def\normalplus{+} -\def\normaldollar{$}%$ font-lock fix +\catcode`\"=\other \def\normaldoublequote{"} +\catcode`\$=\other \def\normaldollar{$}%$ font-lock fix +\catcode`\+=\other \def\normalplus{+} +\catcode`\<=\other \def\normalless{<} +\catcode`\>=\other \def\normalgreater{>} +\catcode`\^=\other \def\normalcaret{^} +\catcode`\_=\other \def\normalunderscore{_} +\catcode`\|=\other \def\normalverticalbar{|} +\catcode`\~=\other \def\normaltilde{~} % This macro is used to make a character print one way in \tt % (where it can probably be output as-is), and another way in other fonts, @@ -9206,14 +9873,24 @@ directory should work if nowhere else does.} % In texinfo, backslash is an active character; it prints the backslash % in fixed width font. -\catcode`\\=\active -@def@normalbackslash{{@tt@backslashcurfont}} +\catcode`\\=\active % @ for escape char from now on. + +% The story here is that in math mode, the \char of \backslashcurfont +% ends up printing the roman \ from the math symbol font (because \char +% in math mode uses the \mathcode, and plain.tex sets +% \mathcode`\\="026E). It seems better for @backslashchar{} to always +% print a typewriter backslash, hence we use an explicit \mathchar, +% which is the decimal equivalent of "715c (class 7, e.g., use \fam; +% ignored family value; char position "5C). We can't use " for the +% usual hex value because it has already been made active. +@def@normalbackslash{{@tt @ifmmode @mathchar29020 @else @backslashcurfont @fi}} +@let@backslashchar = @normalbackslash % @backslashchar{} is for user documents. + % On startup, @fixbackslash assigns: % @let \ = @normalbackslash - % \rawbackslash defines an active \ to do \backslashcurfont. % \otherbackslash defines an active \ to be a literal `\' character with -% catcode other. +% catcode other. We switch back and forth between these. @gdef@rawbackslash{@let\=@backslashcurfont} @gdef@otherbackslash{@let\=@realbackslash} @@ -9221,16 +9898,16 @@ directory should work if nowhere else does.} % the literal character `\'. % @def@normalturnoffactive{% - @let\=@normalbackslash @let"=@normaldoublequote - @let~=@normaltilde + @let$=@normaldollar %$ font-lock fix + @let+=@normalplus + @let<=@normalless + @let>=@normalgreater + @let\=@normalbackslash @let^=@normalcaret @let_=@normalunderscore @let|=@normalverticalbar - @let<=@normalless - @let>=@normalgreater - @let+=@normalplus - @let$=@normaldollar %$ font-lock fix + @let~=@normaltilde @markupsetuplqdefault @markupsetuprqdefault @unsepspaces @@ -9262,10 +9939,19 @@ directory should work if nowhere else does.} % Say @foo, not \foo, in error messages. @escapechar = `@@ +% These (along with & and #) are made active for url-breaking, so need +% active definitions as the normal characters. +@def@normaldot{.} +@def@normalquest{?} +@def@normalslash{/} + % These look ok in all fonts, so just make them not special. -@catcode`@& = @other -@catcode`@# = @other -@catcode`@% = @other +% @hashchar{} gets its own user-level command, because of #line. +@catcode`@& = @other @def@normalamp{&} +@catcode`@# = @other @def@normalhash{#} +@catcode`@% = @other @def@normalpercent{%} + +@let @hashchar = @normalhash @c Finally, make ` and ' active, so that txicodequoteundirected and @c txicodequotebacktick work right in, e.g., @w{@code{`foo'}}. If we diff --git a/doc/tinc.conf.5.in b/doc/tinc.conf.5.in index a44f27c..f868c15 100644 --- a/doc/tinc.conf.5.in +++ b/doc/tinc.conf.5.in @@ -1,4 +1,4 @@ -.Dd 2010-01-16 +.Dd 2012-09-27 .Dt TINC.CONF 5 .\" Manual page created by: .\" Ivo Timmermans @@ -14,22 +14,12 @@ The files in the directory contain runtime and security information for the tinc daemon. .Sh NETWORKS -It is perfectly ok for you to run more than one tinc daemon. -However, in its default form, -you will soon notice that you can't use two different configuration files without the -.Fl c -option. - -.Pp -We have thought of another way of dealing with this: network names. -This means that you call -.Nm -with the +To distinguish multiple instances of tinc running on one computer, +you can use the .Fl n -option, which will assign a name to this daemon. - +option to assign a network name to each tinc daemon. .Pp -The effect of this is that the daemon will set its configuration root to +The effect of this option is that the daemon will set its configuration root to .Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa / , where .Ar NETNAME @@ -37,14 +27,14 @@ is your argument to the .Fl n option. You'll notice that messages appear in syslog as coming from -.Nm tincd. Ns Ar NETNAME . - +.Nm tincd. Ns Ar NETNAME , +and on Linux, unless specified otherwise, the name of the virtual network interface will be the same as the network name. .Pp -However, it is not strictly necessary that you call tinc with the +It is recommended that you use network names even if you run only one instance of tinc. +However, you can choose not to use the .Fl n option. -In this case, the network name would just be empty, -and it will be used as such. +In this case, the network name would just be empty, and .Nm tinc now looks for files in .Pa @sysconfdir@/tinc/ , @@ -55,12 +45,6 @@ the configuration file should be and the host configuration files are now expected to be in .Pa @sysconfdir@/tinc/hosts/ . -.Pp -But it is highly recommended that you use this feature of -.Nm tinc , -because it will be so much clearer whom your daemon talks to. -Hence, we will assume that you use it. - .Sh NAMES Each tinc daemon should have a name that is unique in the network which it will be part of. The name will be used by other tinc daemons for identification. @@ -72,25 +56,38 @@ file. To make things easy, choose something that will give unique and easy to remember names to your tinc daemon(s). You could try things like hostnames, owner surnames or location names. +However, you are only allowed to use alphanumerical characters (a-z, A-Z, and 0-9) and underscores (_) in the name. + +.Sh INITIAL CONFIGURATION +If you have not configured tinc yet, you can easily create a basic configuration using the following command: +.Bd -literal -offset indent +.Nm tincctl Fl n Ar NETNAME Li init Ar NAME +.Ed + +.Pp +You can further change the configuration as needed either by manually editing the configuration files, +or by using +.Xr tincctl 8 . .Sh PUBLIC/PRIVATE KEYS -You should use -.Ic tincd -K -to generate public/private keypairs. -It will generate two keys. -The private key should be stored in a separate file -.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /rsa_key.priv -\-\- where -.Ar NETNAME -stands for the network (see -.Sx NETWORKS ) -above. -The public key should be stored in the host configuration file -.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Ns Va NAME -\-\- where -.Va NAME -stands for the name of the local tinc daemon (see -.Sx NAMES ) . +The +.Nm tincctl Li init +command will have generated both RSA and ECDSA public/private keypairs. +The private keys should be stored in files named +.Pa rsa_key.priv +and +.Pa ecdsa_key.priv +in the directory +.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa / +The public keys should be stored in the host configuration file +.Pa @sysconfdir@/tinc/ Ns Ar NETNAME Ns Pa /hosts/ Ns Va NAME . + +The RSA keys are used for backwards compatibility with tinc version 1.0. +If you are upgrading from version 1.0 to 1.1, you can keep the old configuration files, +but you will need to create ECDSA keys using the following command: +.Bd -literal -offset indent +.Nm tincctl Fl n Ar NETNAME Li generate-ecdsa-keys +.Ed .Sh SERVER CONFIGURATION The server configuration of the daemon is done in the file @@ -117,6 +114,11 @@ Although all configuration options for the local host listed in this document ca it is recommended to put host specific configuration options in the host configuration file, as this makes it easy to exchange with other nodes. +.Pp +You can edit the config file manually, but it is recommended that you use +.Xr tincctl 8 +to change configuration variables for you. + .Pp Here are all valid variables, listed in alphabetical order. The default value is given between parentheses. @@ -129,14 +131,24 @@ If is selected, then depending on the operating system both IPv4 and IPv6 or just IPv6 listening sockets will be created. -.It Va BindToAddress Li = Ar address Bq experimental +.It Va BindToAddress Li = Ar address Op Ar port If your computer has more than one IPv4 or IPv6 address, .Nm tinc will by default listen on all of them for incoming connections. -It is possible to bind only to a single address with this variable. - +Multiple +.Va BindToAddress +variables may be specified, +in which case listening sockets for each specified address are made. .Pp -This option may not work on all platforms. +If no +.Ar port +is specified, the socket will be bound to the port specified by the +.Va Port +option, or to port 655 if neither is given. +To only bind to a specific port but not to a specific address, use +.Li * +for the +.Ar address . .It Va BindToInterface Li = Ar interface Bq experimental If your computer has more than one network interface, @@ -146,6 +158,28 @@ It is possible to bind only to a single interface with this variable. .Pp This option may not work on all platforms. +Also, on some platforms it will not actually bind to an interface, +but rather to the address that the interface has at the moment a socket is created. + +.It Va Broadcast Li = no | mst | direct Po mst Pc Bq experimental +This option selects the way broadcast packets are sent to other daemons. +NOTE: all nodes in a VPN must use the same +.Va Broadcast +mode, otherwise routing loops can form. + +.Bl -tag -width indent +.It no +Broadcast packets are never sent to other nodes. + +.It mst +Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree. +This ensures broadcast packets reach all nodes. + +.It direct +Broadcast packets are sent directly to all nodes that can be reached directly. +Broadcast packets received from other nodes are never forwarded. +If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. +.El .It Va ConnectTo Li = Ar name Specifies which other tinc daemon to connect to on startup. @@ -165,6 +199,16 @@ If you don't specify a host with won't try to connect to other daemons at all, and will instead just listen for incoming connections. +.It Va DecrementTTL Li = yes | no Po no Pc Bq experimental +When enabled, +.Nm tinc +will decrement the Time To Live field in IPv4 packets, or the Hop Limit field in IPv6 packets, +before forwarding a received packet to the virtual network device or to another node, +and will drop packets that have a TTL value of zero, +in which case it will send an ICMP Time Exceeded packet back. +.Pp +Do not use this option if you use switch mode and want to use IPv6. + .It Va Device Li = Ar device Po Pa /dev/tap0 , Pa /dev/net/tun No or other depending on platform Pc The virtual network device to use. .Nm tinc @@ -177,30 +221,75 @@ instead of The info pages of the tinc package contain more information about configuring the virtual network device. -.It Va DeviceType Li = tun | tunnohead | tunifhead | tap Po only supported on BSD platforms Pc +.It Va DeviceType Li = Ar type Pq platform dependent The type of the virtual network device. -Tinc will normally automatically select the right type, and this option should not be used. -However, in case tinc does not seem to correctly interpret packets received from the virtual network device, -using this option might help. +Tinc will normally automatically select the right type of tun/tap interface, and this option should not be used. +However, this option can be used to select one of the special interface types, if support for them is compiled in. .Bl -tag -width indent -.It tun +.It dummy +Use a dummy interface. +No packets are ever read or written to a virtual network device. +Useful for testing, or when setting up a node that only forwards packets for other nodes. + +.It raw_socket +Open a raw socket, and bind it to a pre-existing +.Va Interface +(eth0 by default). +All packets are read from this interface. +Packets received for the local node are written to the raw socket. +However, at least on Linux, the operating system does not process IP packets destined for the local host. + +.It multicast +Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using +.Va Device . +Packets are read from and written to this multicast socket. +This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address. +Do NOT connect multiple +.Nm tinc +daemons to the same multicast address, this will very likely cause routing loops. +Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. + +.It uml Pq not compiled in by default +Create a UNIX socket with the filename specified by +.Va Device , +or +.Pa @localstatedir@/run/ Ns Ar NETNAME Ns Pa .umlsocket +if not specified. +.Nm tinc +will wait for a User Mode Linux instance to connect to this socket. + +.It vde Pq not compiled in by default +Uses the libvdeplug library to connect to a Virtual Distributed Ethernet switch, +using the UNIX socket specified by +.Va Device , +or +.Pa @localstatedir@/run/vde.ctl +if not specified. +.El + +Also, in case tinc does not seem to correctly interpret packets received from the virtual network device, +it can be used to change the way packets are interpreted: + +.Bl -tag -width indent + +.It tun Pq BSD and Linux Set type to tun. Depending on the platform, this can either be with or without an address family header (see below). -.It tunnohead +.It tunnohead Pq BSD Set type to tun without an address family header. Tinc will expect packets read from the virtual network device to start with an IP header. On some platforms IPv6 packets cannot be read from or written to the device in this mode. -.It tunifhead +.It tunifhead Pq BSD Set type to tun with an address family header. Tinc will expect packets read from the virtual network device to start with a four byte header containing the address family, followed by an IP header. This mode should support both IPv4 and IPv6 packets. -.It tap +.It tap Pq BSD and Linux Set type to tap. Tinc will expect packets read from the virtual network device to start with an Ethernet header. @@ -247,7 +336,7 @@ This is less efficient, but allows the kernel to apply its routing and firewall and can also help debugging. .El -.It Va GraphDumpFile Li = Ar filename Bq experimental +.It Va GraphDumpFile Li = Ar filename If this option is present, .Nm tinc will dump the current network graph to the file @@ -268,7 +357,7 @@ a lookup if your DNS server is not responding. .Pp This does not affect resolving hostnames to IP addresses from the -host configuration files. +host configuration files, but whether hostnames should be resolved while logging. .It Va IffOneQueue Li = yes | no Po no Pc Bq experimental (Linux only) Set IFF_ONE_QUEUE flag on TUN/TAP devices. @@ -286,6 +375,18 @@ This option controls the period the encryption keys used to encrypt the data are It is common practice to change keys at regular intervals to make it even harder for crackers, even though it is thought to be nearly impossible to crack a single key. +.It Va LocalDiscovery Li = yes | no Pq no +When enabled, +.Nm tinc +will try to detect peers that are on the same local network. +This will allow direct communication using LAN addresses, even if both peers are behind a NAT +and they only ConnectTo a third node outside the NAT, +which normally would prevent the peers from learning each other's LAN address. + +.Pp +Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery. +This feature may not work in all possible situations. + .It Va MACExpire Li = Ar seconds Pq 600 This option controls the amount of time MAC addresses are kept before they are removed. This only has effect when @@ -327,6 +428,19 @@ while no routing table is managed. .It Va Name Li = Ar name Bq required This is the name which identifies this tinc daemon. It must be unique for the virtual private network this daemon will connect to. +The Name may only consist of alphanumeric and underscore characters. + +If +.Va Name +starts with a +.Li $ , +then the contents of the environment variable that follows will be used. +In that case, invalid characters will be converted to underscores. +If +.Va Name +is +.Li $HOST , +but no such environment variable exist, the hostname will be read using the gethostnname() system call. .It Va PingInterval Li = Ar seconds Pq 60 The number of seconds of inactivity that @@ -356,11 +470,46 @@ or specified in the configuration file. .It Va ProcessPriority Li = low | normal | high -When this option is used the priority of the tincd process will be adjusted. +When this option is used the priority of the +.Nm tincd +process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. +.It Va Proxy Li = socks4 | socks5 | http | exec Ar ... Bq experimental +Use a proxy when making outgoing connections. +The following proxy types are currently supported: +.Bl -tag -width indent +.It socks4 Ar address Ar port Op Ar username +Connects to the proxy using the SOCKS version 4 protocol. +Optionally, a +.Ar username +can be supplied which will be passed on to the proxy server. +Only IPv4 connections can be proxied using SOCKS 4. +.It socks5 Ar address Ar port Op Ar username Ar password +Connect to the proxy using the SOCKS version 5 protocol. +If a +.Ar username +and +.Ar password +are given, basic username/password authentication will be used, +otherwise no authentication will be used. +.It http Ar address Ar port +Connects to the proxy and sends a HTTP CONNECT request. +.It exec Ar command +Executes the given +.Ar command +which should set up the outgoing connection. +The environment variables +.Ev NAME , +.Ev NODE , +.Ev REMOTEADDRES +and +.Ev REMOTEPORT +are available. +.El + .It Va ReplayWindow Li = Ar bytes Pq 16 -This is the size of the replay tracking window for each remote node, in bytes. +vhis is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet per bit, so for example the default setting of 16 will track up to 128 packets in the window. In high bandwidth scenarios, setting this to a higher value can reduce packet loss from @@ -406,7 +555,7 @@ Since host configuration files only contain public keys, no secrets are revealed by sending out this information. .Bl -tag -width indent -.It Va Address Li = Ar address Oo port Oc Bq recommended +.It Va Address Li = Ar address Oo Ar port Oc Bq recommended The IP address or hostname of this tinc daemon on the real network. This will only be used when trying to make an outgoing connection to this tinc daemon. Optionally, a port can be specified to use for this address. @@ -497,12 +646,11 @@ variables can be specified. Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case a subnet consisting of only that single address is assumed, or they can be a IPv4 or IPv6 network address with a prefixlength. -Shorthand notations are not supported. For example, IPv4 subnets must be in a form like 192.168.1.0/24, where 192.168.1.0 is the network address and 24 is the number of bits set in the netmask. Note that subnets like 192.168.1.1/24 are invalid! Read a networking HOWTO/FAQ/guide if you don't understand this. -IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64. +IPv6 subnets are notated like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e. .Pp @@ -609,6 +757,10 @@ When a subnet becomes (un)reachable, this is set to the subnet. When a subnet becomes (un)reachable, this is set to the subnet weight. .El +.Pp +Do not forget that under UNIX operating systems, you have to make the scripts executable, using the command +.Nm chmod Li a+x Pa script . + .Sh FILES The most important files are: .Bl -tag -width indent @@ -636,8 +788,9 @@ its connection to the virtual network device. .Sh SEE ALSO .Xr tincd 8 , +.Xr tincctl 8 , .Pa http://www.tinc-vpn.org/ , -.Pa http://www.linuxdoc.org/LDP/nag2/ . +.Pa http://www.tldp.org/LDP/nag2/ . .Pp The full documentation for diff --git a/doc/tinc.info b/doc/tinc.info index f6c0548..8a95302 100644 --- a/doc/tinc.info +++ b/doc/tinc.info @@ -8,7 +8,7 @@ END-INFO-DIR-ENTRY This is the info manual for tinc version 1.1pre2, a Virtual Private Network daemon. - Copyright (C) 1998-2011 Ivo Timmermans, Guus Sliepen + Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and Wessel Dankers . Permission is granted to make and distribute verbatim copies of this @@ -148,7 +148,7 @@ will most likely compile and run, but it will not be able to send or receive data packets. For an up to date list of supported platforms, please check the list -on our website: `http://www.tinc-vpn.org/platforms'. +on our website: `http://www.tinc-vpn.org/platforms/'.  File: tinc.info, Node: Preparations, Next: Installation, Prev: Introduction, Up: Top @@ -210,7 +210,9 @@ File: tinc.info, Node: Configuration of FreeBSD kernels, Next: Configuration o -------------------------------------- For FreeBSD version 4.1 and higher, tun and tap drivers are included in -the default kernel configuration. Using tap devices is recommended. +the default kernel configuration. The tap driver can be loaded with +`kldload if_tap', or by adding `if_tap_load="YES"' to +`/boot/loader.conf'.  File: tinc.info, Node: Configuration of OpenBSD kernels, Next: Configuration of NetBSD kernels, Prev: Configuration of FreeBSD kernels, Up: Configuring the kernel @@ -413,10 +415,9 @@ if available. Make sure you install the development AND runtime versions of this package. If you have to install libevent manually, you can get the source code -from `http://monkey.org/~provos/libevent/'. Instructions on how to -configure, build and install this package are included within the -package. Please make sure you build development and runtime libraries -(which is the default). +from `http://libevent.org/'. Instructions on how to configure, build +and install this package are included within the package. Please make +sure you build development and runtime libraries (which is the default).  File: tinc.info, Node: Installation, Next: Configuration, Prev: Preparations, Up: Top @@ -431,9 +432,9 @@ startup scripts and sample configurations. If you cannot use one of the precompiled packages, or you want to compile tinc for yourself, you can use the source. The source is distributed under the GNU General Public License (GPL). Download the -source from the download page (http://www.tinc-vpn.org/download), which -has the checksums of these files listed; you may wish to check these -with md5sum before continuing. +source from the download page (http://www.tinc-vpn.org/download/), +which has the checksums of these files listed; you may wish to check +these with md5sum before continuing. Tinc comes in a convenient autoconf/automake package, which you can just treat the same as any other package. Which is just untar it, type @@ -474,7 +475,7 @@ File: tinc.info, Node: Darwin (MacOS/X) build environment, Next: Cygwin (Windo In order to build tinc on Darwin, you need to install the MacOS/X Developer Tools from `http://developer.apple.com/tools/macosxtools.html' and a recent -version of Fink from `http://fink.sourceforge.net/'. +version of Fink from `http://www.finkproject.org/'. After installation use fink to download and install the following packages: autoconf25, automake, dlcompat, m4, openssl, zlib and lzo. @@ -573,7 +574,6 @@ File: tinc.info, Node: Configuration, Next: Running tinc, Prev: Installation, * Multiple networks:: * How connections work:: * Configuration files:: -* Generating keypairs:: * Network interfaces:: * Example configuration:: @@ -593,13 +593,19 @@ Do you want to run tinc in router mode or switch mode? These questions can only be answered by yourself, you will not find the answers in this documentation. Make sure you have an adequate understanding of networks in general. A good resource on networking is the Linux -Network Administrators Guide (http://www.linuxdoc.org/LDP/nag2/). +Network Administrators Guide (http://www.tldp.org/LDP/nag2/). If you have everything clearly pictured in your mind, proceed in the -following order: First, generate the configuration files (`tinc.conf', -your host configuration file, `tinc-up' and perhaps `tinc-down'). Then -generate the keypairs. Finally, distribute the host configuration -files. These steps are described in the subsections below. +following order: First, create the initial configuration files and +public/private keypairs using the following command: + tincctl -n NETNAME init NAME + Second, use `tincctl -n NETNAME config ...' to further configure +tinc. Finally, export your host configuration file using `tincctl -n +NETNAME export' and send it to those people or computers you want tinc +to connect to. They should send you their host configuration file +back, which you can import using `tincctl -n NETNAME import'. + + These steps are described in the subsections below.  File: tinc.info, Node: Multiple networks, Next: How connections work, Prev: Configuration introduction, Up: Configuration @@ -610,27 +616,26 @@ File: tinc.info, Node: Multiple networks, Next: How connections work, Prev: C In order to allow you to run more than one tinc daemon on one computer, for instance if your computer is part of more than one VPN, you can assign a NETNAME to your VPN. It is not required if you only run one -tinc daemon, it doesn't even have to be the same on all the sites of +tinc daemon, it doesn't even have to be the same on all the nodes of your VPN, but it is recommended that you choose one anyway. We will asume you use a netname throughout this document. This -means that you call tincd with the -n argument, which will assign a -netname to this daemon. +means that you call tincctl with the -n argument, which will specify +the netname. - The effect of this is that the daemon will set its configuration + The effect of this option is that tinc will set its configuration root to `/etc/tinc/NETNAME/', where NETNAME is your argument to the -n -option. You'll notice that it appears in syslog as `tinc.NETNAME'. +option. You will also notice that log messages it appears in syslog as +coming from `tinc.NETNAME', and on Linux, unless specified otherwise, +the name of the virtual network interface will be the same as the +network name. However, it is not strictly necessary that you call tinc with the -n -option. In this case, the network name would just be empty, and it will -be used as such. tinc now looks for files in `/etc/tinc/', instead of -`/etc/tinc/NETNAME/'; the configuration file should be -`/etc/tinc/tinc.conf', and the host configuration files are now -expected to be in `/etc/tinc/hosts/'. - - But it is highly recommended that you use this feature of tinc, -because it will be so much clearer whom your daemon talks to. Hence, -we will assume that you use it. +option. If you don not use it, the network name will just be empty, and +tinc will look for files in `/etc/tinc/' instead of +`/etc/tinc/NETNAME/'; the configuration file will then be +`/etc/tinc/tinc.conf', and the host configuration files are expected to +be in `/etc/tinc/hosts/'.  File: tinc.info, Node: How connections work, Next: Configuration files, Prev: Multiple networks, Up: Configuration @@ -656,8 +661,31 @@ a server, and one which does specify such a value as a client. It does not matter if two tinc daemons have a `ConnectTo' value pointing to each other however. + Connections specified using `ConnectTo' are so-called +meta-connections. Tinc daemons exchange information about all other +daemon they know about via these meta-connections. After learning +about all the daemons in the VPN, tinc will create other connections as +necessary in order to communicate with them. For example, if there are +three daemons named A, B and C, and A has `ConnectTo = B' in its +tinc.conf file, and C has `ConnectTo = B' in its tinc.conf file, then A +will learn about C from B, and will be able to exchange VPN packets +with C without the need to have `ConnectTo = C' in its tinc.conf file. + + It could be that some daemons are located behind a Network Address +Translation (NAT) device, or behind a firewall. In the above scenario +with three daemons, if A and C are behind a NAT, B will automatically +help A and C punch holes through their NAT, in a way similar to the +STUN protocol, so that A and C can still communicate with each other +directly. It is not always possible to do this however, and firewalls +might also prevent direct communication. In that case, VPN packets +between A and C will be forwarded by B. + + In effect, all nodes in the VPN will be able to talk to each other, +as long as their is a path of meta-connections between them, and +whenever possible, two nodes will communicate with each other directly. +  -File: tinc.info, Node: Configuration files, Next: Generating keypairs, Prev: How connections work, Up: Configuration +File: tinc.info, Node: Configuration files, Next: Network interfaces, Prev: How connections work, Up: Configuration 4.4 Configuration files ======================= @@ -684,9 +712,12 @@ options for the local node listed in this document can also be put in configuration options in the host configuration file, as this makes it easy to exchange with other nodes. - In this section all valid variables are listed in alphabetical order. -The default value is given between parentheses, other comments are -between square brackets. + You can edit the config file manually, but it is recommended that +you use tincctl to change configuration variables for you. + + In the following two subsections all valid variables are listed in +alphabetical order. The default value is given between parentheses, +other comments are between square brackets. * Menu: @@ -707,12 +738,16 @@ AddressFamily = (any) system both IPv4 and IPv6 or just IPv6 listening sockets will be created. -BindToAddress =
[experimental] +BindToAddress =
[] If your computer has more than one IPv4 or IPv6 address, tinc will - by default listen on all of them for incoming connections. It is - possible to bind only to a single address with this variable. + by default listen on all of them for incoming connections. + Multiple BindToAddress variables may be specified, in which case + listening sockets for each specified address are made. - This option may not work on all platforms. + If no PORT is specified, the socket will be bound to the port + specified by the Port option, or to port 655 if neither is given. + To only bind to a specific port but not to a specific address, use + "*" for the ADDRESS. BindToInterface = [experimental] If you have more than one network interface in your computer, tinc @@ -720,7 +755,30 @@ BindToInterface = [experimental] It is possible to bind tinc to a single interface like eth0 or ppp0 with this variable. - This option may not work on all platforms. + This option may not work on all platforms. Also, on some + platforms it will not actually bind to an interface, but rather to + the address that the interface has at the moment a socket is + created. + +Broadcast = (mst) [experimental] + This option selects the way broadcast packets are sent to other + daemons. _NOTE: all nodes in a VPN must use the same Broadcast + mode, otherwise routing loops can form._ + + no + Broadcast packets are never sent to other nodes. + + mst + Broadcast packets are sent and forwarded via the VPN's + Minimum Spanning Tree. This ensures broadcast packets reach + all nodes. + + direct + Broadcast packets are sent directly to all nodes that can be + reached directly. Broadcast packets received from other + nodes are never forwarded. If the IndirectData option is + also set, broadcast packets will only be sent to nodes which + we have a meta connection to. ConnectTo = Specifies which other tinc daemon to connect to on startup. @@ -733,6 +791,15 @@ ConnectTo = connect to other daemons at all, and will instead just listen for incoming connections. +DecrementTTL = (no) [experimental] + When enabled, tinc will decrement the Time To Live field in IPv4 + packets, or the Hop Limit field in IPv6 packets, before forwarding + a received packet to the virtual network device or to another node, + and will drop packets that have a TTL value of zero, in which case + it will send an ICMP Time Exceeded packet back. + + Do not use this option if you use switch mode and want to use IPv6. + Device = (`/dev/tap0', `/dev/net/tun' or other depending on platform) The virtual network device to use. Tinc will automatically detect what kind of device it is. Note that you can only use one device @@ -740,31 +807,68 @@ Device = (`/dev/tap0', `/dev/net/tun' or other depending on platform) that you can only use one device per daemon. See also *note Device files::. -DeviceType = (only supported on BSD platforms) +DeviceType = (platform dependent) The type of the virtual network device. Tinc will normally - automatically select the right type, and this option should not be - used. However, in case tinc does not seem to correctly interpret - packets received from the virtual network device, using this - option might help. + automatically select the right type of tun/tap interface, and this + option should not be used. However, this option can be used to + select one of the special interface types, if support for them is + compiled in. - tun + dummy + Use a dummy interface. No packets are ever read or written + to a virtual network device. Useful for testing, or when + setting up a node that only forwards packets for other nodes. + + raw_socket + Open a raw socket, and bind it to a pre-existing INTERFACE + (eth0 by default). All packets are read from this interface. + Packets received for the local node are written to the raw + socket. However, at least on Linux, the operating system + does not process IP packets destined for the local host. + + multicast + Open a multicast UDP socket and bind it to the address and + port (separated by spaces) and optionally a TTL value + specified using DEVICE. Packets are read from and written to + this multicast socket. This can be used to connect to UML, + QEMU or KVM instances listening on the same multicast address. + Do NOT connect multiple tinc daemons to the same multicast + address, this will very likely cause routing loops. Also + note that this can cause decrypted VPN packets to be sent out + on a real network if misconfigured. + + uml (not compiled in by default) + Create a UNIX socket with the filename specified by DEVICE, + or `/var/run/NETNAME.umlsocket' if not specified. Tinc will + wait for a User Mode Linux instance to connect to this socket. + + vde (not compiled in by default) + Uses the libvdeplug library to connect to a Virtual + Distributed Ethernet switch, using the UNIX socket specified + by DEVICE, or `/var/run/vde.ctl' if not specified. + + Also, in case tinc does not seem to correctly interpret packets + received from the virtual network device, it can be used to change + the way packets are interpreted: + + tun (BSD and Linux) Set type to tun. Depending on the platform, this can either be with or without an address family header (see below). - tunnohead + tunnohead (BSD) Set type to tun without an address family header. Tinc will expect packets read from the virtual network device to start with an IP header. On some platforms IPv6 packets cannot be read from or written to the device in this mode. - tunifhead + tunifhead (BSD) Set type to tun with an address family header. Tinc will expect packets read from the virtual network device to start with a four byte header containing the address family, followed by an IP header. This mode should support both IPv4 and IPv6 packets. - tap + tap (BSD and Linux) Set type to tap. Tinc will expect packets read from the virtual network device to start with an Ethernet header. @@ -808,7 +912,7 @@ Forwarding = (internal) [experimental] efficient, but allows the kernel to apply its routing and firewall rules on them, and can also help debugging. -GraphDumpFile = [experimental] +GraphDumpFile = If this option is present, tinc will dump the current network graph to the file FILENAME every minute, unless there were no changes to the graph. The file is in a format that can be read by @@ -824,7 +928,8 @@ Hostnames = (no) responding. This does not affect resolving hostnames to IP addresses from the - configuration file. + configuration file, but whether hostnames should be resolved while + logging. Interface = Defines the name of the interface corresponding to the virtual @@ -834,6 +939,17 @@ Interface = interface will be used. If you specified a Device, this variable is almost always already correctly set. +LocalDiscovery = (no) + When enabled, tinc will try to detect peers that are on the same + local network. This will allow direct communication using LAN + addresses, even if both peers are behind a NAT and they only + ConnectTo a third node outside the NAT, which normally would + prevent the peers from learning each other's LAN address. + + Currently, local discovery is implemented by sending broadcast + packets to the LAN during path MTU discovery. This feature may + not work in all possible situations. + Mode = (router) This option selects the way packets are routed to other daemons. @@ -878,6 +994,12 @@ Name = [required] consist only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and _). + If Name starts with a $, then the contents of the environment + variable that follows will be used. In that case, invalid + characters will be converted to underscores. If Name is $HOST, + but no such environment variable exist, the hostname will be read + using the gethostnname() system call. + PingInterval = (60) The number of seconds of inactivity that tinc will wait before sending a probe to the other end. @@ -912,6 +1034,29 @@ ProcessPriority = adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. +Proxy = socks4 | socks4 | http | exec ... [experimental] + Use a proxy when making outgoing connections. The following proxy + types are currently supported: + + socks4
[] + Connects to the proxy using the SOCKS version 4 protocol. + Optionally, a USERNAME can be supplied which will be passed + on to the proxy server. + + socks4
[ ] + Connect to the proxy using the SOCKS version 5 protocol. If + a USERNAME and PASSWORD are given, basic username/password + authentication will be used, otherwise no authentication will + be used. + + http
+ Connects to the proxy and sends a HTTP CONNECT request. + + exec + Executes the given command which should set up the outgoing + connection. The environment variables `NAME', `NODE', + `REMOTEADDRES' and `REMOTEPORT' are available. + ReplayWindow = (16) This is the size of the replay tracking window for each remote node, in bytes. The window is a bitfield which tracks 1 packet @@ -1030,18 +1175,17 @@ Subnet = Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case a subnet consisting of only that single address is assumed, or they can be a IPv4 or IPv6 network address with a prefixlength. - Shorthand notations are not supported. For example, IPv4 subnets - must be in a form like 192.168.1.0/24, where 192.168.1.0 is the - network address and 24 is the number of bits set in the netmask. - Note that subnets like 192.168.1.1/24 are invalid! Read a - networking HOWTO/FAQ/guide if you don't understand this. IPv6 - subnets are notated like fec0:0:0:1:0:0:0:0/64. MAC addresses are - notated like 0:1a:2b:3c:4d:5e. + For example, IPv4 subnets must be in a form like 192.168.1.0/24, + where 192.168.1.0 is the network address and 24 is the number of + bits set in the netmask. Note that subnets like 192.168.1.1/24 + are invalid! Read a networking HOWTO/FAQ/guide if you don't + understand this. IPv6 subnets are notated like fec0:0:0:1::/64. + MAC addresses are notated like 0:1a:2b:3c:4d:5e. Prefixlength is the number of bits set to 1 in the netmask part; for example: netmask 255.255.255.0 would become /24, 255.255.252.0 becomes /22. This conforms to standard CIDR notation as described - in RFC1519 (ftp://ftp.isi.edu/in-notes/rfc1519.txt) + in RFC1519 (http://www.ietf.org/rfc/rfc1519.txt) A Subnet can be given a weight to indicate its priority over identical Subnets owned by different nodes. The default weight is @@ -1142,55 +1286,117 @@ File: tinc.info, Node: How to configure, Prev: Scripts, Up: Configuration fil 4.4.4 How to configure ---------------------- -Step 1. Creating the main configuration file -............................................. - -The main configuration file will be called -`/etc/tinc/NETNAME/tinc.conf'. Adapt the following example to create a -basic configuration file: - - Name = YOURNAME - Device = `/dev/tap0' - - Then, if you know to which other tinc daemon(s) yours is going to -connect, add `ConnectTo' values. - -Step 2. Creating your host configuration file +Step 1. Creating initial configuration files. .............................................. -If you added a line containing `Name = yourname' in the main -configuarion file, you will need to create a host configuration file -`/etc/tinc/NETNAME/hosts/yourname'. Adapt the following example to -create a host configuration file: +The initial directory structure, configuration files and public/private +keypairs are created using the following command: - Address = your.real.hostname.org - Subnet = 192.168.1.0/24 + tincctl -n NETNAME init NAME - You can also use an IP address instead of a hostname. The `Subnet' -specifies the address range that is local for _your part of the VPN -only_. If you have multiple address ranges you can specify more than -one `Subnet'. You might also need to add a `Port' if you want your -tinc daemon to run on a different port number than the default (655). + (You will need to run this as root, or use "sudo".) This will +create the configuration directory `/etc/tinc/NETNAME.', and inside it +will create another directory named `hosts/'. In the configuration +directory, it will create the file `tinc.conf' with the following +contents: + + Name = NAME + + It will also create private RSA and ECDSA keys, which will be stored +in the files `rsa_key.priv' and `ecdsa_key.priv'. It will also create +a host configuration file `hosts/NAME', which will contain the +corresponding public RSA and ECDSA keys. + + Finally, on UNIX operating systems, it will create an executable +script `tinc-up', which will initially not do anything except warning +that you should edit it. + +Step 2. Modifying the initial configuration. +............................................. + +Unless you want to use tinc in switch mode, you should now configure +which range of addresses you will use on the VPN. Let's assume you +will be part of a VPN which uses the address range 192.168.0.0/16, and +you yourself have a smaller portion of that range: 192.168.2.0/24. +Then you should run the following command: + + tincctl -n NETNAME config add subnet 192.168.2.0/24 + + This will add a Subnet statement to your host configuration file. +Try opening the file `/etc/tinc/NETNAME/hosts/NAME' in an editor. You +should now see a file containing the public RSA and ECDSA keys (which +looks like a bunch of random characters), and the following line at the +bottom: + + Subnet = 192.168.2.0/24 + + If you will use more than one address range, you can add more +Subnets. For example, if you also use the IPv6 subnet fec0:0:0:2::/64, +you can add it as well: + + tincctl -n NETNAME config add subnet fec0:0:0:2::/24 + + This will add another line to the file `hosts/NAME'. If you make a +mistake, you can undo it by simply using `config del' instead of +`config add'. + + If you want other tinc daemons to create meta-connections to your +daemon, you should add your public IP address or hostname to your host +configuration file. For example, if your hostname is foo.example.org, +run: + + tincctl -n NETNAME config add address foo.example.org + + If you already know to which daemons your daemon should make +meta-connections, you should configure that now as well. Suppose you +want to connect to a daemon named "bar", run: + + tincctl -n NETNAME config add connectto bar + + Note that you specify the Name of the other daemon here, not an IP +address or hostname! When you start tinc, and it tries to make a +connection to "bar", it will look for a host configuration file named +`hosts/bar', and will read Address statements and public keys from that +file. + +Step 2. Exchanging configuration files. +........................................ + +If your daemon has a ConnectTo = bar statement in its `tinc.conf' file, +or if bar has a ConnectTo your daemon, then you both need each other's +host configuration files. You should send `hosts/NAME' to bar, and bar +should send you his file which you should move to `hosts/bar'. If you +are on a UNIX platform, you can easily send an email containing the +necessary information using the following command (assuming the owner +of bar has the email address bar@example.org): + + tincctl -n NETNAME export | mail -s "My config file" bar@example.org + + If the owner of bar does the same to send his host configuration +file to you, you can probably pipe his email through the following +command, or you can just start this command in a terminal and +copy&paste the email: + + tincctl -n NETNAME import + + If you are the owner of bar yourself, and you have SSH access to +that computer, you can also swap the host configuration files using the +following commands: + + tincctl -n NETNAME export | ssh bar.example.org tincctl -n NETNAME import + ssh bar.example.org tincctl -n NETNAME export | tincctl -n NETNAME import + + You should repeat this for all nodes you ConnectTo, or which +ConnectTo you. However, remember that you do not need to ConnectTo all +nodes in the VPN; it is only necessary to create one or a few +meta-connections, after the connections are made tinc will learn about +all the other nodes in the VPN, and will automatically make other +connections as necessary.  -File: tinc.info, Node: Generating keypairs, Next: Network interfaces, Prev: Configuration files, Up: Configuration +File: tinc.info, Node: Network interfaces, Next: Example configuration, Prev: Configuration files, Up: Configuration -4.5 Generating keypairs -======================= - -Now that you have already created the main configuration file and your -host configuration file, you can easily create a public/private keypair -by entering the following command: - - tincctl -n NETNAME generate-keys - - Tinc will generate a public and a private key and ask you where to -put them. Just press enter to accept the defaults. - - -File: tinc.info, Node: Network interfaces, Next: Example configuration, Prev: Generating keypairs, Up: Configuration - -4.6 Network interfaces +4.5 Network interfaces ====================== Before tinc can start transmitting data over the tunnel, it must set up @@ -1211,19 +1417,28 @@ ifconfig, route, and other commands to a script named `/etc/tinc/NETNAME/tinc-up'. When tinc starts, this script will be executed. When tinc exits, it will execute the script named `/etc/tinc/NETNAME/tinc-down', but normally you don't need to create -that script. +that script. You can manually open the script in an editor, or use the +following command: - An example `tinc-up' script: + tincctl -n NETNAME edit tinc-up + + An example `tinc-up' script, that would be appropriate for the +scenario in the previous section, is: #!/bin/sh - ifconfig $INTERFACE 192.168.1.1 netmask 255.255.0.0 + ifconfig $INTERFACE 192.168.2.1 netmask 255.255.0.0 + ip addr add fec0:0:0:2::/48 dev $INTERFACE - This script gives the interface an IP address and a netmask. The -kernel will also automatically add a route to this interface, so -normally you don't need to add route commands to the `tinc-up' script. -The kernel will also bring the interface up after this command. The + The first command gives the interface an IPv4 address and a netmask. +The kernel will also automatically add an IPv4 route to this interface, +so normally you don't need to add route commands to the `tinc-up' +script. The kernel will also bring the interface up after this command. The netmask is the mask of the _entire_ VPN network, not just your own -subnet. +subnet. The second command gives the interface an IPv6 address and +netmask, which will also automatically add an IPv6 route. If you only +want to use "ip addr" commands on Linux, don't forget that it doesn't +bring the interface up, unlike ifconfig, so you need to add `ip link +set $INTERFACE up' in that case. The exact syntax of the ifconfig and route commands differs from platform to platform. You can look up the commands for setting @@ -1234,7 +1449,7 @@ platform.  File: tinc.info, Node: Example configuration, Prev: Network interfaces, Up: Configuration -4.7 Example configuration +4.6 Example configuration ========================= Imagine the following situation. Branch A of our example `company' @@ -1262,6 +1477,9 @@ configuration of the real interface is also shown as a comment, to give you an idea of how these example host is set up. All branches use the netname `company' for this particular VPN. + Each branch is set up using the `tincctl init' and `tincctl config' +commands, here we just show the end results: + For Branch A ............ @@ -1269,6 +1487,8 @@ _BranchA_ would be configured like this: In `/etc/tinc/company/tinc-up': + #!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.1.54.1 netmask 255.255.0.0 @@ -1277,7 +1497,6 @@ _BranchA_ would be configured like this: and in `/etc/tinc/company/tinc.conf': Name = BranchA - Device = /dev/tap0 On all hosts, `/etc/tinc/company/hosts/BranchA' contains: @@ -1288,17 +1507,19 @@ _BranchA_ would be configured like this: ... -----END RSA PUBLIC KEY----- - Note that the IP addresses of eth0 and tap0 are the same. This is -quite possible, if you make sure that the netmasks of the interfaces -are different. It is in fact recommended to give both real internal -network interfaces and tap interfaces the same IP address, since that -will make things a lot easier to remember and set up. + Note that the IP addresses of eth0 and the VPN interface are the +same. This is quite possible, if you make sure that the netmasks of +the interfaces are different. It is in fact recommended to give both +real internal network interfaces and VPN interfaces the same IP address, +since that will make things a lot easier to remember and set up. For Branch B ............ In `/etc/tinc/company/tinc-up': + #!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.2.43.8 netmask 255.255.0.0 @@ -1310,8 +1531,8 @@ In `/etc/tinc/company/tinc-up': ConnectTo = BranchA Note here that the internal address (on eth0) doesn't have to be the -same as on the tap0 device. Also, ConnectTo is given so that this node -will always try to connect to BranchA. +same as on the VPN interface. Also, ConnectTo is given so that this +node will always try to connect to BranchA. On all hosts, in `/etc/tinc/company/hosts/BranchB': @@ -1327,6 +1548,8 @@ For Branch C In `/etc/tinc/company/tinc-up': + #!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.3.69.254 netmask 255.255.0.0 @@ -1336,7 +1559,6 @@ In `/etc/tinc/company/tinc-up': Name = BranchC ConnectTo = BranchA - Device = /dev/tap1 C already has another daemon that runs on port 655, so they have to reserve another port for tinc. It knows the portnumber it has to listen @@ -1357,6 +1579,8 @@ For Branch D In `/etc/tinc/company/tinc-up': + #!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.4.3.32 netmask 255.255.0.0 @@ -1366,13 +1590,10 @@ In `/etc/tinc/company/tinc-up': Name = BranchD ConnectTo = BranchC - Device = /dev/net/tun D will be connecting to C, which has a tincd running for this network on port 2000. It knows the port number from the host -configuration file. Also note that since D uses the tun/tap driver, -the network interface will not be called `tun' or `tap0' or something -like that, but will have the same name as netname. +configuration file. On all hosts, in `/etc/tinc/company/hosts/BranchD': @@ -1386,16 +1607,12 @@ like that, but will have the same name as netname. Key files ......... -A, B, C and D all have generated a public/private keypair with the -following command: +A, B, C and D all have their own public/private keypairs: - tincctl -n company generate-keys - - The private key is stored in `/etc/tinc/company/rsa_key.priv', the -public key is put into the host configuration file in the -`/etc/tinc/company/hosts/' directory. During key generation, tinc -automatically guesses the right filenames based on the -n option and -the Name directive in the `tinc.conf' file (if it is available). + The private RSA key is stored in `/etc/tinc/company/rsa_key.priv', +the private ECDSA key is stored in `/etc/tinc/company/ecdsa_key.priv', +and the public RSA and ECDSA keys are put into the host configuration +file in the `/etc/tinc/company/hosts/' directory. Starting ........ @@ -1415,7 +1632,7 @@ File: tinc.info, Node: Running tinc, Next: Controlling tinc, Prev: Configurat If everything else is done, you can start tinc by typing the following command: - tincd -n NETNAME + tincctl -n NETNAME start Tinc will detach from the terminal and continue to run in the background like a good daemon. If there are any problems however you @@ -1462,6 +1679,13 @@ command line options. Store a cookie in FILENAME which allows tincctl to authenticate. If unspecified, the default is `/var/run/tinc.NETNAME.pid'. +`-o, --option=[HOST.]KEY=VALUE' + Without specifying a HOST, this will set server configuration + variable KEY to VALUE. If specified as HOST.KEY=VALUE, this will + set the host configuration variable KEY of the host named HOST to + VALUE. This option can be used more than once to specify multiple + configuration variables. + `-L, --mlock' Lock tinc into main memory. This will prevent sensitive data like shared private keys to be written to the system swap @@ -1738,12 +1962,13 @@ command. A quick example: * Menu: * tincctl runtime options:: +* tincctl environment variables:: * tincctl commands:: * tincctl examples:: * tincctl top::  -File: tinc.info, Node: tincctl runtime options, Next: tincctl commands, Up: Controlling tinc +File: tinc.info, Node: tincctl runtime options, Next: tincctl environment variables, Up: Controlling tinc 6.1 tincctl runtime options =========================== @@ -1769,13 +1994,64 @@ File: tinc.info, Node: tincctl runtime options, Next: tincctl commands, Up: C  -File: tinc.info, Node: tincctl commands, Next: tincctl examples, Prev: tincctl runtime options, Up: Controlling tinc +File: tinc.info, Node: tincctl environment variables, Next: tincctl commands, Prev: tincctl runtime options, Up: Controlling tinc -6.2 tincctl commands +6.2 tincctl environment variables +================================= + +`NETNAME' + If no netname is specified on the command line with the `-n' + option, the value of this environment variable is used. + + +File: tinc.info, Node: tincctl commands, Next: tincctl examples, Prev: tincctl environment variables, Up: Controlling tinc + +6.3 tincctl commands ==================== -`start' - Start `tincd'. +`init [NAME]' + Create initial configuration files and RSA and ECDSA keypairs with + default length. If no NAME for this node is given, it will be + asked for. + +`config [get] VARIABLE' + Print the current value of configuration variable VARIABLE. If + more than one variable with the same name exists, the value of + each of them will be printed on a separate line. + +`config [set] VARIABLE VALUE' + Set configuration variable VARIABLE to the given VALUE. All + previously existing configuration variables with the same name are + removed. To set a variable for a specific host, use the notation + HOST.VARIABLE. + +`config add VARIABLE VALUE' + As above, but without removing any previously existing + configuration variables. + +`config del VARIABLE [VALUE]' + Remove configuration variables with the same name and VALUE. If + no VALUE is given, all configuration variables with the same name + will be removed. + +`edit FILENAME' + Start an editor for the given configuration file. You do not need + to specify the full path to the file. + +`export' + Export the host configuration file of the local node to standard + output. + +`export-all' + Export all host configuration files to standard output. + +`import [--force]' + Import host configuration file(s) from standard input. Already + existing host configuration files are not overwritten unless the + option -force is used. + +`start [tincd options]' + Start `tincd', optionally with the given extra options. `stop' Stop `tincd'. @@ -1809,8 +2085,16 @@ File: tinc.info, Node: tincctl commands, Next: tincctl examples, Prev: tincct `dump connections' Dump a list of all meta connections with ourself. -`dump graph' - Dump a graph of the VPN in dotty format. +`dump graph | digraph' + Dump a graph of the VPN in dotty format. Nodes are colored + according to their reachability: red nodes are unreachable, orange + nodes are indirectly reachable, green nodes are directly reachable. + Black nodes are either directly or indirectly reachable, but + direct reachability has not been tried yet. + +`info NODE | SUBNET | ADDRESS' + Show information about a particular NODE, SUBNET or ADDRESS. If + an ADDRESS is given, any matching subnet will be shown. `purge' Purges all information remembered about unreachable nodes. @@ -1818,6 +2102,11 @@ File: tinc.info, Node: tincctl commands, Next: tincctl examples, Prev: tincct `debug LEVEL' Sets debug level to LEVEL. +`log [LEVEL]' + Capture log messages from a running tinc daemon. An optional + debug level can be given that will be applied only for log + messages sent to tincctl. + `retry' Forces tinc to try to connect to all uplinks immediately. Usually tinc attempts to do this itself, but increases the time it waits @@ -1843,7 +2132,7 @@ File: tinc.info, Node: tincctl commands, Next: tincctl examples, Prev: tincct  File: tinc.info, Node: tincctl examples, Next: tincctl top, Prev: tincctl commands, Up: Controlling tinc -6.3 tincctl examples +6.4 tincctl examples ==================== Examples of some commands: @@ -1852,10 +2141,18 @@ Examples of some commands: tincctl -n vpn pcap | tcpdump -r - tincctl -n vpn top + Example of configuring tinc using tincctl: + + tincctl -n vpn init foo + tincctl -n vpn config Subnet 192.168.1.0/24 + tincctl -n vpn config bar.Address bar.example.com + tincctl -n vpn config ConnectTo bar + tincctl -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@example.com +  File: tinc.info, Node: tincctl top, Prev: tincctl examples, Up: Controlling tinc -6.4 tincctl top +6.5 tincctl top =============== The top command connects to a running tinc daemon and repeatedly @@ -2387,6 +2684,19 @@ Solaris `ifconfig' INTERFACE `inet6 plumb up' Darwin (MacOS/X) `ifconfig' INTERFACE `inet6' ADDRESS `prefixlen' PREFIXLENGTH Windows `netsh interface ipv6 add address' INTERFACE `static' ADDRESS/PREFIXLENGTH + On some platforms, when running tinc in switch mode, the VPN +interface must be set to tap mode with an ifconfig command: + +OpenBSD `ifconfig' INTERFACE `link0' + + On Linux, it is possible to create a persistent tun/tap interface +which will continue to exist even if tinc quit, although this is +normally not required. It can be useful to set up a tun/tap interface +owned by a non-root user, so tinc can be started without needing any +root privileges at all. + +Linux `ip tuntap add dev' INTERFACE `mode' TUN|TAP `user' USERNAME +  File: tinc.info, Node: Routes, Prev: Interface configuration, Up: Platform specific information @@ -2487,14 +2797,16 @@ Concept Index * BindToAddress: Main configuration variables. (line 12) * BindToInterface: Main configuration variables. - (line 19) + (line 23) +* Broadcast: Main configuration variables. + (line 34) * Cabal: Security. (line 6) * CHAL_REPLY: Authentication protocol. (line 10) * CHALLENGE: Authentication protocol. (line 10) * CIDR notation: Host configuration variables. - (line 92) + (line 91) * Cipher: Host configuration variables. (line 12) * ClampMSS: Host configuration variables. @@ -2506,72 +2818,85 @@ Concept Index (line 24) * connection: The connection. (line 6) * ConnectTo: Main configuration variables. - (line 27) + (line 54) * daemon: Running tinc. (line 11) * data-protocol: The meta-connection. (line 18) * debug level: Runtime options. (line 17) * debug levels: Debug levels. (line 6) +* DecrementTTL: Main configuration variables. + (line 65) * DEL_EDGE: The meta-protocol. (line 47) * DEL_SUBNET: The meta-protocol. (line 47) * DEVICE: Scripts. (line 55) * Device: Main configuration variables. - (line 38) + (line 74) * device files: Device files. (line 6) * DeviceType: Main configuration variables. - (line 45) + (line 81) * Digest: Host configuration variables. (line 29) * DirectOnly: Main configuration variables. - (line 73) + (line 146) +* dummy: Main configuration variables. + (line 88) * ECDSAPrivateKeyFile: Main configuration variables. - (line 80) + (line 153) * encapsulating: The UDP tunnel. (line 30) * encryption: Encryption of network packets. (line 6) * environment variables: Scripts. (line 43) * example: Example configuration. (line 6) +* exec: Main configuration variables. + (line 326) * ExperimentalProtocol: Main configuration variables. - (line 84) + (line 157) * Forwarding: Main configuration variables. - (line 93) + (line 166) * frame type: The UDP tunnel. (line 6) * GraphDumpFile: Main configuration variables. - (line 113) + (line 186) * Hostnames: Main configuration variables. - (line 121) + (line 194) +* http: Main configuration variables. + (line 323) * hub: Main configuration variables. - (line 162) + (line 247) * ID: Authentication protocol. (line 10) * IndirectData: Host configuration variables. (line 34) * INTERFACE: Scripts. (line 58) * Interface: Main configuration variables. - (line 131) + (line 205) * IRC: Contact information. (line 9) -* key generation: Generating keypairs. (line 6) * KEY_CHANGED: The meta-protocol. (line 64) * KeyExpire: Main configuration variables. - (line 167) + (line 252) * libevent: libevent. (line 6) * libraries: Libraries. (line 6) * license: OpenSSL. (line 36) +* LocalDiscovery: Main configuration variables. + (line 213) * lzo: lzo. (line 6) * MACExpire: Main configuration variables. - (line 173) + (line 258) * MACLength: Host configuration variables. (line 42) * meta-protocol: The meta-connection. (line 18) * META_KEY: Authentication protocol. (line 10) * Mode: Main configuration variables. - (line 139) + (line 224) +* multicast: Main configuration variables. + (line 100) * multiple networks: Multiple networks. (line 6) * NAME: Scripts. (line 52) * Name: Main configuration variables. - (line 178) -* netmask: Network interfaces. (line 34) + (line 263) +* netmask: Network interfaces. (line 39) +* NETNAME <1>: tincctl environment variables. + (line 6) * NETNAME: Scripts. (line 49) * netname: Multiple networks. (line 6) * Network Administrators Guide: Configuration introduction. @@ -2583,9 +2908,9 @@ Concept Index (line 67) * PING: The meta-protocol. (line 89) * PingInterval: Main configuration variables. - (line 183) + (line 274) * PingTimeout: Main configuration variables. - (line 187) + (line 278) * platforms: Supported platforms. (line 6) * PMTU: Host configuration variables. (line 47) @@ -2596,45 +2921,53 @@ Concept Index (line 55) * port numbers: Other files. (line 17) * PriorityInheritance: Main configuration variables. - (line 193) + (line 284) * private: Virtual Private Networks. (line 10) * PrivateKey: Main configuration variables. - (line 198) + (line 289) * PrivateKeyFile: Main configuration variables. - (line 204) + (line 295) * ProcessPriority: Main configuration variables. - (line 212) + (line 303) +* Proxy: Main configuration variables. + (line 308) * PublicKey: Host configuration variables. (line 59) * PublicKeyFile: Host configuration variables. (line 62) +* raw_socket: Main configuration variables. + (line 93) * release: Supported platforms. (line 14) * REMOTEADDRESS: Scripts. (line 67) * REMOTEPORT: Scripts. (line 70) * ReplayWindow: Main configuration variables. - (line 217) + (line 331) * REQ_KEY: The meta-protocol. (line 64) * requirements: Libraries. (line 6) * router: Main configuration variables. - (line 142) + (line 227) * runtime options: Runtime options. (line 9) * scalability: tinc. (line 19) * scripts: Scripts. (line 6) * server: How connections work. (line 18) * signals: Signals. (line 6) +* socks4: Main configuration variables. + (line 312) +* socks5: Main configuration variables. + (line 317) * StrictSubnets: Main configuration variables. - (line 228) + (line 342) * SUBNET: Scripts. (line 74) * Subnet: Host configuration variables. (line 74) * SVPN: Security. (line 11) * switch: Main configuration variables. - (line 151) + (line 236) * TCP: The meta-connection. (line 10) * TCPonly: Host configuration variables. - (line 104) + (line 103) * TINC: Security. (line 6) * tinc: Introduction. (line 6) * tinc-down: Scripts. (line 18) @@ -2643,20 +2976,24 @@ Concept Index * tincd: tinc. (line 14) * traditional VPNs: tinc. (line 19) * tunifhead: Main configuration variables. - (line 62) + (line 135) * TunnelServer: Main configuration variables. - (line 233) + (line 347) * tunnohead: Main configuration variables. - (line 56) + (line 129) * UDP <1>: Encryption of network packets. (line 12) * UDP: The UDP tunnel. (line 30) * UDPRcvBuf: Main configuration variables. - (line 240) + (line 354) * UDPSndBuf: Main configuration variables. - (line 245) + (line 359) +* UML: Main configuration variables. + (line 111) * Universal tun/tap: Configuration of Linux kernels. (line 6) +* VDE: Main configuration variables. + (line 116) * virtual: Virtual Private Networks. (line 18) * virtual network device: The UDP tunnel. (line 6) @@ -2674,67 +3011,67 @@ Node: Introduction1131 Node: Virtual Private Networks1941 Node: tinc3667 Node: Supported platforms5194 -Node: Preparations5892 -Node: Configuring the kernel6148 -Node: Configuration of Linux kernels6557 -Node: Configuration of FreeBSD kernels7412 -Node: Configuration of OpenBSD kernels7802 -Node: Configuration of NetBSD kernels8410 -Node: Configuration of Solaris kernels8815 -Node: Configuration of Darwin (MacOS/X) kernels9476 -Node: Configuration of Windows10165 -Node: Libraries10679 -Node: OpenSSL11080 -Node: zlib13356 -Node: lzo14185 -Node: libevent14989 -Node: Installation15700 -Node: Building and installing tinc16715 -Node: Darwin (MacOS/X) build environment17374 -Node: Cygwin (Windows) build environment17942 -Node: MinGW (Windows) build environment18530 -Node: System files19054 -Node: Device files19319 -Node: Other files19735 -Node: Configuration20348 -Node: Configuration introduction20659 -Node: Multiple networks21932 -Node: How connections work23358 -Node: Configuration files24580 -Node: Main configuration variables25967 -Node: Host configuration variables37161 -Node: Scripts42445 -Node: How to configure45124 -Node: Generating keypairs46387 -Node: Network interfaces46899 -Node: Example configuration48747 -Node: Running tinc54083 -Node: Runtime options54668 -Node: Signals57028 -Node: Debug levels57878 -Node: Solving problems58814 -Node: Error messages60244 -Node: Sending bug reports64566 -Node: Controlling tinc65518 -Node: tincctl runtime options65881 -Node: tincctl commands66567 -Node: tincctl examples68653 -Node: tincctl top68947 -Node: Technical information70545 -Node: The connection70780 -Node: The UDP tunnel71092 -Node: The meta-connection74153 -Node: The meta-protocol75622 -Node: Security80631 -Node: Authentication protocol81761 -Node: Encryption of network packets86765 -Node: Security issues88138 -Node: Platform specific information89755 -Node: Interface configuration89983 -Node: Routes91882 -Node: About us93798 -Node: Contact information93973 -Node: Authors94377 -Node: Concept Index94782 +Node: Preparations5893 +Node: Configuring the kernel6149 +Node: Configuration of Linux kernels6558 +Node: Configuration of FreeBSD kernels7413 +Node: Configuration of OpenBSD kernels7878 +Node: Configuration of NetBSD kernels8486 +Node: Configuration of Solaris kernels8891 +Node: Configuration of Darwin (MacOS/X) kernels9552 +Node: Configuration of Windows10241 +Node: Libraries10755 +Node: OpenSSL11156 +Node: zlib13432 +Node: lzo14261 +Node: libevent15065 +Node: Installation15760 +Node: Building and installing tinc16776 +Node: Darwin (MacOS/X) build environment17435 +Node: Cygwin (Windows) build environment18002 +Node: MinGW (Windows) build environment18590 +Node: System files19114 +Node: Device files19379 +Node: Other files19795 +Node: Configuration20408 +Node: Configuration introduction20695 +Node: Multiple networks22242 +Node: How connections work23622 +Node: Configuration files26195 +Node: Main configuration variables27728 +Node: Host configuration variables44334 +Node: Scripts49564 +Node: How to configure52243 +Node: Network interfaces56861 +Node: Example configuration59262 +Node: Running tinc64414 +Node: Runtime options65007 +Node: Signals67711 +Node: Debug levels68561 +Node: Solving problems69497 +Node: Error messages70927 +Node: Sending bug reports75249 +Node: Controlling tinc76201 +Node: tincctl runtime options76598 +Node: tincctl environment variables77297 +Node: tincctl commands77641 +Node: tincctl examples81866 +Node: tincctl top82471 +Node: Technical information84069 +Node: The connection84304 +Node: The UDP tunnel84616 +Node: The meta-connection87677 +Node: The meta-protocol89146 +Node: Security94155 +Node: Authentication protocol95285 +Node: Encryption of network packets100289 +Node: Security issues101662 +Node: Platform specific information103279 +Node: Interface configuration103507 +Node: Routes105960 +Node: About us107876 +Node: Contact information108051 +Node: Authors108455 +Node: Concept Index108860  End Tag Table diff --git a/doc/tinc.texi b/doc/tinc.texi index 69e5a2b..ac3a630 100644 --- a/doc/tinc.texi +++ b/doc/tinc.texi @@ -15,7 +15,7 @@ This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon. -Copyright @copyright{} 1998-2011 Ivo Timmermans, +Copyright @copyright{} 1998-2012 Ivo Timmermans, Guus Sliepen and Wessel Dankers . @@ -39,7 +39,7 @@ permission notice identical to this one. @vskip 0pt plus 1filll This is the info manual for @value{PACKAGE} version @value{VERSION}, a Virtual Private Network daemon. -Copyright @copyright{} 1998-2011 Ivo Timmermans, +Copyright @copyright{} 1998-2012 Ivo Timmermans, Guus Sliepen and Wessel Dankers . @@ -187,7 +187,7 @@ packets. @cindex release For an up to date list of supported platforms, please check the list on our website: -@uref{http://www.tinc-vpn.org/platforms}. +@uref{http://www.tinc-vpn.org/platforms/}. @c @c @@ -262,7 +262,7 @@ alias char-major-10-200 tun @subsection Configuration of FreeBSD kernels For FreeBSD version 4.1 and higher, tun and tap drivers are included in the default kernel configuration. -Using tap devices is recommended. +The tap driver can be loaded with @code{kldload if_tap}, or by adding @code{if_tap_load="YES"} to @file{/boot/loader.conf}. @c ================================================================== @@ -276,6 +276,7 @@ which adds a tap device to OpenBSD which should work with tinc, but with recent versions of OpenBSD, a tun device can act as a tap device by setting the link0 option with ifconfig. + @c ================================================================== @node Configuration of NetBSD kernels @subsection Configuration of NetBSD kernels @@ -466,7 +467,7 @@ available. Make sure you install the development AND runtime versions of this package. If you have to install libevent manually, you can get the source code -from @url{http://monkey.org/~provos/libevent/}. Instructions on how to configure, +from @url{http://libevent.org/}. Instructions on how to configure, build and install this package are included within the package. Please make sure you build development and runtime libraries (which is the default). @@ -492,7 +493,7 @@ system startup scripts and sample configurations. If you cannot use one of the precompiled packages, or you want to compile tinc for yourself, you can use the source. The source is distributed under the GNU General Public License (GPL). Download the source from the -@uref{http://www.tinc-vpn.org/download, download page}, which has +@uref{http://www.tinc-vpn.org/download/, download page}, which has the checksums of these files listed; you may wish to check these with md5sum before continuing. @@ -533,7 +534,7 @@ The documentation that comes along with your distribution will tell you how to d In order to build tinc on Darwin, you need to install the MacOS/X Developer Tools from @uref{http://developer.apple.com/tools/macosxtools.html} and -a recent version of Fink from @uref{http://fink.sourceforge.net/}. +a recent version of Fink from @uref{http://www.finkproject.org/}. After installation use fink to download and install the following packages: autoconf25, automake, dlcompat, m4, openssl, zlib and lzo. @@ -638,7 +639,6 @@ tinc 655/udp TINC * Multiple networks:: * How connections work:: * Configuration files:: -* Generating keypairs:: * Network interfaces:: * Example configuration:: @end menu @@ -661,13 +661,19 @@ you will not find the answers in this documentation. Make sure you have an adequate understanding of networks in general. @cindex Network Administrators Guide A good resource on networking is the -@uref{http://www.linuxdoc.org/LDP/nag2/, Linux Network Administrators Guide}. +@uref{http://www.tldp.org/LDP/nag2/, Linux Network Administrators Guide}. If you have everything clearly pictured in your mind, proceed in the following order: -First, generate the configuration files (@file{tinc.conf}, your host configuration file, @file{tinc-up} and perhaps @file{tinc-down}). -Then generate the keypairs. -Finally, distribute the host configuration files. +First, create the initial configuration files and public/private keypairs using the following command: +@example +tincctl -n @var{NETNAME} init @var{NAME} +@end example +Second, use @samp{tincctl -n @var{NETNAME} config ...} to further configure tinc. +Finally, export your host configuration file using @samp{tincctl -n @var{NETNAME} export} and send it to those +people or computers you want tinc to connect to. +They should send you their host configuration file back, which you can import using @samp{tincctl -n @var{NETNAME} import}. + These steps are described in the subsections below. @@ -677,30 +683,29 @@ These steps are described in the subsections below. @cindex multiple networks @cindex netname + In order to allow you to run more than one tinc daemon on one computer, for instance if your computer is part of more than one VPN, you can assign a @var{netname} to your VPN. It is not required if you only run one tinc daemon, -it doesn't even have to be the same on all the sites of your VPN, +it doesn't even have to be the same on all the nodes of your VPN, but it is recommended that you choose one anyway. We will asume you use a netname throughout this document. -This means that you call tincd with the -n argument, -which will assign a netname to this daemon. +This means that you call tincctl with the -n argument, +which will specify the netname. -The effect of this is that the daemon will set its configuration -root to @file{@value{sysconfdir}/tinc/@var{netname}/}, where @var{netname} is your argument to the -n -option. You'll notice that it appears in syslog as @file{tinc.@var{netname}}. +The effect of this option is that tinc will set its configuration +root to @file{@value{sysconfdir}/tinc/@var{netname}/}, where @var{netname} is your argument to the -n option. +You will also notice that log messages it appears in syslog as coming from @file{tinc.@var{netname}}, +and on Linux, unless specified otherwise, the name of the virtual network interface will be the same as the network name. However, it is not strictly necessary that you call tinc with the -n -option. In this case, the network name would just be empty, and it will -be used as such. tinc now looks for files in @file{@value{sysconfdir}/tinc/}, instead of -@file{@value{sysconfdir}/tinc/@var{netname}/}; the configuration file should be @file{@value{sysconfdir}/tinc/tinc.conf}, -and the host configuration files are now expected to be in @file{@value{sysconfdir}/tinc/hosts/}. - -But it is highly recommended that you use this feature of tinc, because -it will be so much clearer whom your daemon talks to. Hence, we will -assume that you use it. +option. If you don not use it, the network name will just be empty, and +tinc will look for files in @file{@value{sysconfdir}/tinc/} instead of +@file{@value{sysconfdir}/tinc/@var{netname}/}; +the configuration file will then be @file{@value{sysconfdir}/tinc/tinc.conf}, +and the host configuration files are expected to be in @file{@value{sysconfdir}/tinc/hosts/}. @c ================================================================== @@ -727,6 +732,25 @@ If you wish, you can view a tinc daemon without a `ConnectTo' value as a server, and one which does specify such a value as a client. It does not matter if two tinc daemons have a `ConnectTo' value pointing to each other however. +Connections specified using `ConnectTo' are so-called meta-connections. +Tinc daemons exchange information about all other daemon they know about via these meta-connections. +After learning about all the daemons in the VPN, +tinc will create other connections as necessary in order to communicate with them. +For example, if there are three daemons named A, B and C, and A has @samp{ConnectTo = B} in its tinc.conf file, +and C has @samp{ConnectTo = B} in its tinc.conf file, then A will learn about C from B, +and will be able to exchange VPN packets with C without the need to have @samp{ConnectTo = C} in its tinc.conf file. + +It could be that some daemons are located behind a Network Address Translation (NAT) device, or behind a firewall. +In the above scenario with three daemons, if A and C are behind a NAT, +B will automatically help A and C punch holes through their NAT, +in a way similar to the STUN protocol, so that A and C can still communicate with each other directly. +It is not always possible to do this however, and firewalls might also prevent direct communication. +In that case, VPN packets between A and C will be forwarded by B. + +In effect, all nodes in the VPN will be able to talk to each other, as long as +their is a path of meta-connections between them, and whenever possible, two +nodes will communicate with each other directly. + @c ================================================================== @node Configuration files @@ -755,7 +779,10 @@ listed in this document can also be put in put host specific configuration options in the host configuration file, as this makes it easy to exchange with other nodes. -In this section all valid variables are listed in alphabetical order. +You can edit the config file manually, but it is recommended that you use +tincctl to change configuration variables for you. + +In the following two subsections all valid variables are listed in alphabetical order. The default value is given between parentheses, other comments are between square brackets. @@ -779,12 +806,15 @@ If any is selected, then depending on the operating system both IPv4 and IPv6 or just IPv6 listening sockets will be created. @cindex BindToAddress -@item BindToAddress = <@var{address}> [experimental] +@item BindToAddress = <@var{address}> [<@var{port}>] If your computer has more than one IPv4 or IPv6 address, tinc will by default listen on all of them for incoming connections. -It is possible to bind only to a single address with this variable. +Multiple BindToAddress variables may be specified, +in which case listening sockets for each specified address are made. -This option may not work on all platforms. +If no @var{port} is specified, the socket will be bound to the port specified by the Port option, +or to port 655 if neither is given. +To only bind to a specific port but not to a specific address, use "*" for the @var{address}. @cindex BindToInterface @item BindToInterface = <@var{interface}> [experimental] @@ -794,6 +824,27 @@ possible to bind tinc to a single interface like eth0 or ppp0 with this variable. This option may not work on all platforms. +Also, on some platforms it will not actually bind to an interface, +but rather to the address that the interface has at the moment a socket is created. + +@cindex Broadcast +@item Broadcast = (mst) [experimental] +This option selects the way broadcast packets are sent to other daemons. +@emph{NOTE: all nodes in a VPN must use the same Broadcast mode, otherwise routing loops can form.} + +@table @asis +@item no +Broadcast packets are never sent to other nodes. + +@item mst +Broadcast packets are sent and forwarded via the VPN's Minimum Spanning Tree. +This ensures broadcast packets reach all nodes. + +@item direct +Broadcast packets are sent directly to all nodes that can be reached directly. +Broadcast packets received from other nodes are never forwarded. +If the IndirectData option is also set, broadcast packets will only be sent to nodes which we have a meta connection to. +@end table @cindex ConnectTo @item ConnectTo = <@var{name}> @@ -807,6 +858,15 @@ If you don't specify a host with ConnectTo, tinc won't try to connect to other daemons at all, and will instead just listen for incoming connections. +@cindex DecrementTTL +@item DecrementTTL = (no) [experimental] +When enabled, tinc will decrement the Time To Live field in IPv4 packets, or the Hop Limit field in IPv6 packets, +before forwarding a received packet to the virtual network device or to another node, +and will drop packets that have a TTL value of zero, +in which case it will send an ICMP Time Exceeded packet back. + +Do not use this option if you use switch mode and want to use IPv6. + @cindex Device @item Device = <@var{device}> (@file{/dev/tap0}, @file{/dev/net/tun} or other depending on platform) The virtual network device to use. @@ -817,32 +877,72 @@ Note that you can only use one device per daemon. See also @ref{Device files}. @cindex DeviceType -@item DeviceType = (only supported on BSD platforms) +@item DeviceType = <@var{type}> (platform dependent) The type of the virtual network device. -Tinc will normally automatically select the right type, and this option should not be used. -However, in case tinc does not seem to correctly interpret packets received from the virtual network device, -using this option might help. +Tinc will normally automatically select the right type of tun/tap interface, and this option should not be used. +However, this option can be used to select one of the special interface types, if support for them is compiled in. @table @asis -@item tun +@cindex dummy +@item dummy +Use a dummy interface. +No packets are ever read or written to a virtual network device. +Useful for testing, or when setting up a node that only forwards packets for other nodes. + +@cindex raw_socket +@item raw_socket +Open a raw socket, and bind it to a pre-existing +@var{Interface} (eth0 by default). +All packets are read from this interface. +Packets received for the local node are written to the raw socket. +However, at least on Linux, the operating system does not process IP packets destined for the local host. + +@cindex multicast +@item multicast +Open a multicast UDP socket and bind it to the address and port (separated by spaces) and optionally a TTL value specified using @var{Device}. +Packets are read from and written to this multicast socket. +This can be used to connect to UML, QEMU or KVM instances listening on the same multicast address. +Do NOT connect multiple tinc daemons to the same multicast address, this will very likely cause routing loops. +Also note that this can cause decrypted VPN packets to be sent out on a real network if misconfigured. + +@cindex UML +@item uml (not compiled in by default) +Create a UNIX socket with the filename specified by +@var{Device}, or @file{@value{localstatedir}/run/@var{netname}.umlsocket} +if not specified. +Tinc will wait for a User Mode Linux instance to connect to this socket. + +@cindex VDE +@item vde (not compiled in by default) +Uses the libvdeplug library to connect to a Virtual Distributed Ethernet switch, +using the UNIX socket specified by +@var{Device}, or @file{@value{localstatedir}/run/vde.ctl} +if not specified. +@end table + +Also, in case tinc does not seem to correctly interpret packets received from the virtual network device, +it can be used to change the way packets are interpreted: + +@table @asis +@item tun (BSD and Linux) Set type to tun. Depending on the platform, this can either be with or without an address family header (see below). @cindex tunnohead -@item tunnohead +@item tunnohead (BSD) Set type to tun without an address family header. Tinc will expect packets read from the virtual network device to start with an IP header. On some platforms IPv6 packets cannot be read from or written to the device in this mode. @cindex tunifhead -@item tunifhead +@item tunifhead (BSD) Set type to tun with an address family header. Tinc will expect packets read from the virtual network device to start with a four byte header containing the address family, followed by an IP header. This mode should support both IPv4 and IPv6 packets. -@item tap +@item tap (BSD and Linux) Set type to tap. Tinc will expect packets read from the virtual network device to start with an Ethernet header. @@ -891,7 +991,7 @@ and can also help debugging. @end table @cindex GraphDumpFile -@item GraphDumpFile = <@var{filename}> [experimental] +@item GraphDumpFile = <@var{filename}> If this option is present, tinc will dump the current network graph to the file @var{filename} every minute, unless there were no changes to the graph. @@ -908,7 +1008,7 @@ tinc's efficiency, even stopping the daemon for a few seconds everytime it does a lookup if your DNS server is not responding. This does not affect resolving hostnames to IP addresses from the -configuration file. +configuration file, but whether hostnames should be resolved while logging. @cindex Interface @item Interface = <@var{interface}> @@ -917,6 +1017,16 @@ Depending on the operating system and the type of device this may or may not act Under Windows, this variable is used to select which network interface will be used. If you specified a Device, this variable is almost always already correctly set. +@cindex LocalDiscovery +@item LocalDiscovery = (no) +When enabled, tinc will try to detect peers that are on the same local network. +This will allow direct communication using LAN addresses, even if both peers are behind a NAT +and they only ConnectTo a third node outside the NAT, +which normally would prevent the peers from learning each other's LAN address. + +Currently, local discovery is implemented by sending broadcast packets to the LAN during path MTU discovery. +This feature may not work in all possible situations. + @cindex Mode @item Mode = (router) This option selects the way packets are routed to other daemons. @@ -963,6 +1073,11 @@ This only has effect when Mode is set to "switch". This is a symbolic name for this connection. The name should consist only of alfanumeric and underscore characters (a-z, A-Z, 0-9 and _). +If Name starts with a $, then the contents of the environment variable that follows will be used. +In that case, invalid characters will be converted to underscores. +If Name is $HOST, but no such environment variable exist, +the hostname will be read using the gethostnname() system call. + @cindex PingInterval @item PingInterval = <@var{seconds}> (60) The number of seconds of inactivity that tinc will wait before sending a @@ -1000,6 +1115,33 @@ specified in the configuration file. When this option is used the priority of the tincd process will be adjusted. Increasing the priority may help to reduce latency and packet loss on the VPN. +@cindex Proxy +@item Proxy = socks4 | socks4 | http | exec @var{...} [experimental] +Use a proxy when making outgoing connections. +The following proxy types are currently supported: + +@table @asis +@cindex socks4 +@item socks4 <@var{address}> <@var{port}> [<@var{username}>] +Connects to the proxy using the SOCKS version 4 protocol. +Optionally, a @var{username} can be supplied which will be passed on to the proxy server. + +@cindex socks5 +@item socks4 <@var{address}> <@var{port}> [<@var{username}> <@var{password}>] +Connect to the proxy using the SOCKS version 5 protocol. +If a @var{username} and @var{password} are given, basic username/password authentication will be used, +otherwise no authentication will be used. + +@cindex http +@item http <@var{address}> <@var{port}> +Connects to the proxy and sends a HTTP CONNECT request. + +@cindex exec +@item exec <@var{command}> +Executes the given command which should set up the outgoing connection. +The environment variables @env{NAME}, @env{NODE}, @env{REMOTEADDRES} and @env{REMOTEPORT} are available. +@end table + @cindex ReplayWindow @item ReplayWindow = (16) This is the size of the replay tracking window for each remote node, in bytes. @@ -1132,19 +1274,18 @@ Multiple subnet lines can be specified for each daemon. Subnets can either be single MAC, IPv4 or IPv6 addresses, in which case a subnet consisting of only that single address is assumed, or they can be a IPv4 or IPv6 network address with a prefixlength. -Shorthand notations are not supported. For example, IPv4 subnets must be in a form like 192.168.1.0/24, where 192.168.1.0 is the network address and 24 is the number of bits set in the netmask. Note that subnets like 192.168.1.1/24 are invalid! Read a networking HOWTO/FAQ/guide if you don't understand this. -IPv6 subnets are notated like fec0:0:0:1:0:0:0:0/64. +IPv6 subnets are notated like fec0:0:0:1::/64. MAC addresses are notated like 0:1a:2b:3c:4d:5e. @cindex CIDR notation Prefixlength is the number of bits set to 1 in the netmask part; for example: netmask 255.255.255.0 would become /24, 255.255.252.0 becomes /22. This conforms to standard CIDR notation as described in -@uref{ftp://ftp.isi.edu/in-notes/rfc1519.txt, RFC1519} +@uref{http://www.ietf.org/rfc/rfc1519.txt, RFC1519} A Subnet can be given a weight to indicate its priority over identical Subnets owned by different nodes. The default weight is 10. Lower values indicate @@ -1254,50 +1395,115 @@ When a subnet becomes (un)reachable, this is set to the subnet. @node How to configure @subsection How to configure -@subsubheading Step 1. Creating the main configuration file +@subsubheading Step 1. Creating initial configuration files. -The main configuration file will be called @file{@value{sysconfdir}/tinc/@var{netname}/tinc.conf}. -Adapt the following example to create a basic configuration file: +The initial directory structure, configuration files and public/private keypairs are created using the following command: @example -Name = @var{yourname} -Device = @file{/dev/tap0} +tincctl -n @var{netname} init @var{name} @end example -Then, if you know to which other tinc daemon(s) yours is going to connect, -add `ConnectTo' values. - -@subsubheading Step 2. Creating your host configuration file - -If you added a line containing `Name = yourname' in the main configuarion file, -you will need to create a host configuration file @file{@value{sysconfdir}/tinc/@var{netname}/hosts/yourname}. -Adapt the following example to create a host configuration file: +(You will need to run this as root, or use "sudo".) +This will create the configuration directory @file{@value{sysconfdir}/tinc/@var{netname}.}, +and inside it will create another directory named @file{hosts/}. +In the configuration directory, it will create the file @file{tinc.conf} with the following contents: @example -Address = your.real.hostname.org -Subnet = 192.168.1.0/24 +Name = @var{name} @end example -You can also use an IP address instead of a hostname. -The `Subnet' specifies the address range that is local for @emph{your part of the VPN only}. -If you have multiple address ranges you can specify more than one `Subnet'. -You might also need to add a `Port' if you want your tinc daemon to run on a different port number than the default (655). +It will also create private RSA and ECDSA keys, which will be stored in the files @file{rsa_key.priv} and @file{ecdsa_key.priv}. +It will also create a host configuration file @file{hosts/@var{name}}, +which will contain the corresponding public RSA and ECDSA keys. +Finally, on UNIX operating systems, it will create an executable script @file{tinc-up}, +which will initially not do anything except warning that you should edit it. -@c ================================================================== -@node Generating keypairs -@section Generating keypairs +@subsubheading Step 2. Modifying the initial configuration. -@cindex key generation -Now that you have already created the main configuration file and your host configuration file, -you can easily create a public/private keypair by entering the following command: +Unless you want to use tinc in switch mode, +you should now configure which range of addresses you will use on the VPN. +Let's assume you will be part of a VPN which uses the address range 192.168.0.0/16, +and you yourself have a smaller portion of that range: 192.168.2.0/24. +Then you should run the following command: @example -tincctl -n @var{netname} generate-keys +tincctl -n @var{netname} config add subnet 192.168.2.0/24 @end example -Tinc will generate a public and a private key and ask you where to put them. -Just press enter to accept the defaults. +This will add a Subnet statement to your host configuration file. +Try opening the file @file{@value{sysconfdir}/tinc/@var{netname}/hosts/@var{name}} in an editor. +You should now see a file containing the public RSA and ECDSA keys (which looks like a bunch of random characters), +and the following line at the bottom: + +@example +Subnet = 192.168.2.0/24 +@end example + +If you will use more than one address range, you can add more Subnets. +For example, if you also use the IPv6 subnet fec0:0:0:2::/64, you can add it as well: + +@example +tincctl -n @var{netname} config add subnet fec0:0:0:2::/24 +@end example + +This will add another line to the file @file{hosts/@var{name}}. +If you make a mistake, you can undo it by simply using @samp{config del} instead of @samp{config add}. + +If you want other tinc daemons to create meta-connections to your daemon, +you should add your public IP address or hostname to your host configuration file. +For example, if your hostname is foo.example.org, run: + +@example +tincctl -n @var{netname} config add address foo.example.org +@end example + +If you already know to which daemons your daemon should make meta-connections, +you should configure that now as well. +Suppose you want to connect to a daemon named "bar", run: + +@example +tincctl -n @var{netname} config add connectto bar +@end example + +Note that you specify the Name of the other daemon here, not an IP address or hostname! +When you start tinc, and it tries to make a connection to "bar", +it will look for a host configuration file named @file{hosts/bar}, +and will read Address statements and public keys from that file. + +@subsubheading Step 2. Exchanging configuration files. + +If your daemon has a ConnectTo = bar statement in its @file{tinc.conf} file, +or if bar has a ConnectTo your daemon, then you both need each other's host configuration files. +You should send @file{hosts/@var{name}} to bar, and bar should send you his file which you should move to @file{hosts/bar}. +If you are on a UNIX platform, you can easily send an email containing the necessary information using the following command +(assuming the owner of bar has the email address bar@@example.org): + +@example +tincctl -n @var{netname} export | mail -s "My config file" bar@@example.org +@end example + +If the owner of bar does the same to send his host configuration file to you, +you can probably pipe his email through the following command, +or you can just start this command in a terminal and copy&paste the email: + +@example +tincctl -n @var{netname} import +@end example + +If you are the owner of bar yourself, and you have SSH access to that computer, +you can also swap the host configuration files using the following commands: + +@example +tincctl -n @var{netname} export | ssh bar.example.org tincctl -n @var{netname} import +ssh bar.example.org tincctl -n @var{netname} export | tincctl -n @var{netname} import +@end example + +You should repeat this for all nodes you ConnectTo, or which ConnectTo you. +However, remember that you do not need to ConnectTo all nodes in the VPN; +it is only necessary to create one or a few meta-connections, +after the connections are made tinc will learn about all the other nodes in the VPN, +and will automatically make other connections as necessary. @c ================================================================== @@ -1320,21 +1526,31 @@ You can configure the network interface by putting ordinary ifconfig, route, and to a script named @file{@value{sysconfdir}/tinc/@var{netname}/tinc-up}. When tinc starts, this script will be executed. When tinc exits, it will execute the script named @file{@value{sysconfdir}/tinc/@var{netname}/tinc-down}, but normally you don't need to create that script. +You can manually open the script in an editor, or use the following command: -An example @file{tinc-up} script: +@example +tincctl -n @var{netname} edit tinc-up +@end example + +An example @file{tinc-up} script, that would be appropriate for the scenario in the previous section, is: @example #!/bin/sh -ifconfig $INTERFACE 192.168.1.1 netmask 255.255.0.0 +ifconfig $INTERFACE 192.168.2.1 netmask 255.255.0.0 +ip addr add fec0:0:0:2::/48 dev $INTERFACE @end example -This script gives the interface an IP address and a netmask. -The kernel will also automatically add a route to this interface, so normally you don't need +The first command gives the interface an IPv4 address and a netmask. +The kernel will also automatically add an IPv4 route to this interface, so normally you don't need to add route commands to the @file{tinc-up} script. The kernel will also bring the interface up after this command. @cindex netmask The netmask is the mask of the @emph{entire} VPN network, not just your own subnet. +The second command gives the interface an IPv6 address and netmask, +which will also automatically add an IPv6 route. +If you only want to use "ip addr" commands on Linux, don't forget that it doesn't bring the interface up, unlike ifconfig, +so you need to add @samp{ip link set $INTERFACE up} in that case. The exact syntax of the ifconfig and route commands differs from platform to platform. You can look up the commands for setting addresses and adding routes in @ref{Platform specific information}, @@ -1374,6 +1590,9 @@ the real interface is also shown as a comment, to give you an idea of how these example host is set up. All branches use the netname `company' for this particular VPN. +Each branch is set up using the @samp{tincctl init} and @samp{tincctl config} commands, +here we just show the end results: + @subsubheading For Branch A @emph{BranchA} would be configured like this: @@ -1381,6 +1600,8 @@ for this particular VPN. In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.1.54.1 netmask 255.255.0.0 @@ -1391,7 +1612,6 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchA -Device = /dev/tap0 @end example On all hosts, @file{@value{sysconfdir}/tinc/company/hosts/BranchA} contains: @@ -1405,9 +1625,9 @@ Address = 1.2.3.4 -----END RSA PUBLIC KEY----- @end example -Note that the IP addresses of eth0 and tap0 are the same. +Note that the IP addresses of eth0 and the VPN interface are the same. This is quite possible, if you make sure that the netmasks of the interfaces are different. -It is in fact recommended to give both real internal network interfaces and tap interfaces the same IP address, +It is in fact recommended to give both real internal network interfaces and VPN interfaces the same IP address, since that will make things a lot easier to remember and set up. @@ -1416,6 +1636,8 @@ since that will make things a lot easier to remember and set up. In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.2.43.8 netmask 255.255.0.0 @@ -1430,7 +1652,7 @@ ConnectTo = BranchA @end example Note here that the internal address (on eth0) doesn't have to be the -same as on the tap0 device. Also, ConnectTo is given so that this node will +same as on the VPN interface. Also, ConnectTo is given so that this node will always try to connect to BranchA. On all hosts, in @file{@value{sysconfdir}/tinc/company/hosts/BranchB}: @@ -1450,6 +1672,8 @@ Address = 2.3.4.5 In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.3.69.254 netmask 255.255.0.0 @@ -1461,7 +1685,6 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchC ConnectTo = BranchA -Device = /dev/tap1 @end example C already has another daemon that runs on port 655, so they have to @@ -1486,6 +1709,8 @@ Port = 2000 In @file{@value{sysconfdir}/tinc/company/tinc-up}: @example +#!/bin/sh + # Real interface of internal network: # ifconfig eth0 10.4.3.32 netmask 255.255.0.0 @@ -1497,14 +1722,10 @@ and in @file{@value{sysconfdir}/tinc/company/tinc.conf}: @example Name = BranchD ConnectTo = BranchC -Device = /dev/net/tun @end example D will be connecting to C, which has a tincd running for this network on port 2000. It knows the port number from the host configuration file. -Also note that since D uses the tun/tap driver, the network interface -will not be called `tun' or `tap0' or something like that, but will -have the same name as netname. On all hosts, in @file{@value{sysconfdir}/tinc/company/hosts/BranchD}: @@ -1519,16 +1740,11 @@ Address = 4.5.6.7 @subsubheading Key files -A, B, C and D all have generated a public/private keypair with the following command: +A, B, C and D all have their own public/private keypairs: -@example -tincctl -n company generate-keys -@end example - -The private key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv}, -the public key is put into the host configuration file in the @file{@value{sysconfdir}/tinc/company/hosts/} directory. -During key generation, tinc automatically guesses the right filenames based on the -n option and -the Name directive in the @file{tinc.conf} file (if it is available). +The private RSA key is stored in @file{@value{sysconfdir}/tinc/company/rsa_key.priv}, +the private ECDSA key is stored in @file{@value{sysconfdir}/tinc/company/ecdsa_key.priv}, +and the public RSA and ECDSA keys are put into the host configuration file in the @file{@value{sysconfdir}/tinc/company/hosts/} directory. @subsubheading Starting @@ -1545,7 +1761,7 @@ their daemons, tinc will try connecting until they are available. If everything else is done, you can start tinc by typing the following command: @example -tincd -n @var{netname} +tincctl -n @var{netname} start @end example @cindex daemon @@ -1600,6 +1816,12 @@ Store a cookie in @var{filename} which allows tincctl to authenticate. If unspecified, the default is @file{@value{localstatedir}/run/tinc.@var{netname}.pid}. +@item -o, --option=[@var{HOST}.]@var{KEY}=@var{VALUE} +Without specifying a @var{HOST}, this will set server configuration variable @var{KEY} to @var{VALUE}. +If specified as @var{HOST}.@var{KEY}=@var{VALUE}, +this will set the host configuration variable @var{KEY} of the host named @var{HOST} to @var{VALUE}. +This option can be used more than once to specify multiple configuration variables. + @item -L, --mlock Lock tinc into main memory. This will prevent sensitive data like shared private keys to be written to the system swap files/partitions. @@ -1868,6 +2090,7 @@ tincctl -n @var{netname} reload @menu * tincctl runtime options:: +* tincctl environment variables:: * tincctl commands:: * tincctl examples:: * tincctl top:: @@ -1900,6 +2123,16 @@ Output version information and exit. @end table +@c ================================================================== +@node tincctl environment variables +@section tincctl environment variables + +@table @env +@cindex NETNAME +@item NETNAME +If no netname is specified on the command line with the @option{-n} option, +the value of this environment variable is used. +@end table @c ================================================================== @node tincctl commands @@ -1908,8 +2141,43 @@ Output version information and exit. @c from the manpage @table @code -@item start -Start @samp{tincd}. +@item init [@var{name}] +Create initial configuration files and RSA and ECDSA keypairs with default length. +If no @var{name} for this node is given, it will be asked for. + +@item config [get] @var{variable} +Print the current value of configuration variable @var{variable}. +If more than one variable with the same name exists, +the value of each of them will be printed on a separate line. + +@item config [set] @var{variable} @var{value} +Set configuration variable @var{variable} to the given @var{value}. +All previously existing configuration variables with the same name are removed. +To set a variable for a specific host, use the notation @var{host}.@var{variable}. + +@item config add @var{variable} @var{value} +As above, but without removing any previously existing configuration variables. + +@item config del @var{variable} [@var{value}] +Remove configuration variables with the same name and @var{value}. +If no @var{value} is given, all configuration variables with the same name will be removed. + +@item edit @var{filename} +Start an editor for the given configuration file. +You do not need to specify the full path to the file. + +@item export +Export the host configuration file of the local node to standard output. + +@item export-all +Export all host configuration files to standard output. + +@item import [--force] +Import host configuration file(s) from standard input. +Already existing host configuration files are not overwritten unless the option --force is used. + +@item start [tincd options] +Start @samp{tincd}, optionally with the given extra options. @item stop Stop @samp{tincd}. @@ -1943,8 +2211,15 @@ Dump a list of all known subnets in the VPN. @item dump connections Dump a list of all meta connections with ourself. -@item dump graph +@item dump graph | digraph Dump a graph of the VPN in dotty format. +Nodes are colored according to their reachability: +red nodes are unreachable, orange nodes are indirectly reachable, green nodes are directly reachable. +Black nodes are either directly or indirectly reachable, but direct reachability has not been tried yet. + +@item info @var{node} | @var{subnet} | @var{address} +Show information about a particular @var{node}, @var{subnet} or @var{address}. +If an @var{address} is given, any matching subnet will be shown. @item purge Purges all information remembered about unreachable nodes. @@ -1952,6 +2227,10 @@ Purges all information remembered about unreachable nodes. @item debug @var{level} Sets debug level to @var{level}. +@item log [@var{level}] +Capture log messages from a running tinc daemon. +An optional debug level can be given that will be applied only for log messages sent to tincctl. + @item retry Forces tinc to try to connect to all uplinks immediately. Usually tinc attempts to do this itself, @@ -1986,6 +2265,16 @@ tincctl -n vpn pcap | tcpdump -r - tincctl -n vpn top @end example +Example of configuring tinc using tincctl: + +@example +tincctl -n vpn init foo +tincctl -n vpn config Subnet 192.168.1.0/24 +tincctl -n vpn config bar.Address bar.example.com +tincctl -n vpn config ConnectTo bar +tincctl -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@@example.com +@end example + @c ================================================================== @node tincctl top @section tincctl top @@ -2531,7 +2820,6 @@ For IPv4 addresses: @tab @code{netsh interface ip set address} @var{interface} @code{static} @var{address} @var{netmask} @end multitable - For IPv6 addresses: @multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} @@ -2553,6 +2841,22 @@ For IPv6 addresses: @tab @code{netsh interface ipv6 add address} @var{interface} @code{static} @var{address}/@var{prefixlength} @end multitable +On some platforms, when running tinc in switch mode, the VPN interface must be set to tap mode with an ifconfig command: + +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@item OpenBSD +@tab @code{ifconfig} @var{interface} @code{link0} +@end multitable + +On Linux, it is possible to create a persistent tun/tap interface which will +continue to exist even if tinc quit, although this is normally not required. +It can be useful to set up a tun/tap interface owned by a non-root user, so +tinc can be started without needing any root privileges at all. + +@multitable {Darwin (MacOS/X)} {ifconfig route add -bla network address netmask netmask prefixlength interface} +@item Linux +@tab @code{ip tuntap add dev} @var{interface} @code{mode} @var{tun|tap} @code{user} @var{username} +@end multitable @c ================================================================== @node Routes diff --git a/doc/tincctl.8.in b/doc/tincctl.8.in index bbc8dba..3834323 100644 --- a/doc/tincctl.8.in +++ b/doc/tincctl.8.in @@ -1,4 +1,4 @@ -.Dd 2011-06-25 +.Dd 2012-10-14 .Dt TINCCTL 8 .\" Manual page created by: .\" Scott Lamb @@ -37,12 +37,58 @@ Display short list of options. .It Fl -version Output version information and exit. .El +.Sh ENVIRONMENT VARIABLES +.Bl -tag -width indent +.It Ev NETNAME +If no netname is specified on the command line with the +.Fl n +option, the value of this environment variable is used. +.El .Sh COMMANDS .zZ .Bl -tag -width indent -.It start +.It init Op Ar name +Create initial configuration files and RSA and ECDSA keypairs with default length. +If no +.Ar name +for this node is given, it will be asked for. +.It config Oo get Oc Ar variable +Print the current value of configuration variable +.Ar variable . +If more than one variable with the same name exists, +the value of each of them will be printed on a separate line. +.It config Oo set Oc Ar variable Ar value +Set configuration variable +.Ar variable +to the given +.Ar value . +All previously existing configuration variables with the same name are removed. +To set a variable for a specific host, use the notation +.Ar host Ns Li . Ns Ar variable . +.It config add Ar variable Ar value +As above, but without removing any previously existing configuration variables. +.It config del Ar variable Op Ar value +Remove configuration variables with the same name and +.Ar value . +If no +.Ar value +is given, all configuration variables with the same name will be removed. +.It edit Ar filename +Start an editor for the given configuration file. +You do not need to specify the full path to the file. +.It export +Export the host configuration file of the local node to standard output. +.It export-all +Export all host configuration files to standard output. +.It import Op Fl -force +Import host configuration file(s) from standard input. +Already existing host configuration files are not overwritten unless the option +.Fl -force +is used. +.It start Op tincd options Start -.Xr tincd 8 . +.Xr tincd 8 , +optionally with the given extra options. .It stop Stop .Xr tincd 8 . @@ -69,6 +115,7 @@ If is omitted, the default length will be 2048 bits. When saving keys to existing files, tinc will not delete the old keys; you have to remove them manually. + .It dump nodes Dump a list of all known nodes in the VPN. .It dump edges @@ -77,15 +124,25 @@ Dump a list of all known connections in the VPN. Dump a list of all known subnets in the VPN. .It dump connections Dump a list of all meta connections with ourself. -.It dump graph +.It dump graph | digraph Dump a graph of the VPN in .Xr dotty 1 format. +Nodes are colored according to their reachability: +red nodes are unreachable, orange nodes are indirectly reachable, green nodes are directly reachable. +Black nodes are either directly or indirectly reachable, but direct reachability has not been tried yet. +.It info Ar node | subnet | address +Show information about a particular node, subnet or address. +If an address is given, any matching subnet will be shown. .It purge Purges all information remembered about unreachable nodes. .It debug Ar N Sets debug level to .Ar N . +.It log Op Ar N +Capture log messages from a running tinc daemon. +An optional debug level can be given that will be applied only for log messages sent to +.Nm tincctl . .It retry Forces .Xr tincd 8 @@ -123,7 +180,16 @@ Examples of some commands: tincctl -n vpn dump graph | circo -Txlib tincctl -n vpn pcap | tcpdump -r - tincctl -n vpn top +.Pp .Ed +Example of configuring tinc using +.Nm : +.Bd -literal -offset indent +tincctl -n vpn init foo +tincctl -n vpn config Subnet 192.168.1.0/24 +tincctl -n vpn config bar.Address bar.example.com +tincctl -n vpn config ConnectTo bar +tincctl -n vpn export | gpg --clearsign | mail -s "My config" vpnmaster@example.com .Sh TOP The top command connects to a running tinc daemon and repeatedly queries its per-node traffic counters. It displays a list of all the known nodes in the left-most column, @@ -172,7 +238,7 @@ If you find any bugs, report them to tinc@tinc-vpn.org. .Xr tincd 8 , .Xr tinc.conf 5 , .Xr dotty 1 , -.Xr pcap-savefile 7 , +.Xr pcap-savefile 5 , .Xr tcpdump 8 , .Xr top 1 , .Pa http://www.tinc-vpn.org/ , diff --git a/doc/tincd.8.in b/doc/tincd.8.in index bb4aa48..9468120 100644 --- a/doc/tincd.8.in +++ b/doc/tincd.8.in @@ -1,4 +1,4 @@ -.Dd 2011-06-25 +.Dd 2012-02-22 .Dt TINCD 8 .\" Manual page created by: .\" Ivo Timmermans @@ -8,11 +8,12 @@ .Nd tinc VPN daemon .Sh SYNOPSIS .Nm -.Op Fl cdDKnLRU +.Op Fl cdDKnoLRU .Op Fl -config Ns = Ns Ar DIR .Op Fl -no-detach .Op Fl -debug Ns Op = Ns Ar LEVEL .Op Fl -net Ns = Ns Ar NETNAME +.Op Fl -option Ns = Ns Ar [HOST.]KEY=VALUE .Op Fl -mlock .Op Fl -logfile Ns Op = Ns Ar FILE .Op Fl -bypass-security @@ -61,6 +62,22 @@ for .Ar NETNAME is the same as not specifying any .Ar NETNAME . +.It Fl o, -option Ns = Ns Ar [HOST.]KEY=VALUE +Without specifying a +.Ar HOST , +this will set server configuration variable +.Ar KEY +to +.Ar VALUE . +If specified as +.Ar HOST.KEY=VALUE , +this will set the host configuration variable +.Ar KEY +of the host named +.Ar HOST +to +.Ar VALUE . +This option can be used more than once to specify multiple configuration variables. .It Fl L, -mlock Lock tinc into main memory. This will prevent sensitive data like shared private keys to be written to the system swap files/partitions. diff --git a/gui/Makefile.in b/gui/Makefile.in index 632fd78..2d08ef7 100644 --- a/gui/Makefile.in +++ b/gui/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -16,6 +16,23 @@ @SET_MAKE@ VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -41,7 +58,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libevent.m4 \ $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/openssl.m4 \ - $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.in + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/zlib.m4 \ + $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d @@ -69,10 +87,21 @@ am__nobase_list = $(am__nobase_strip_setup); \ am__base_list = \ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g' +am__uninstall_files_from_dir = { \ + test -z "$$files" \ + || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \ + || { echo " ( cd '$$dir' && rm -f" $$files ")"; \ + $(am__cd) "$$dir" && rm -f $$files; }; \ + } am__installdirs = "$(DESTDIR)$(bindir)" SCRIPTS = $(dist_bin_SCRIPTS) SOURCES = DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -122,6 +151,7 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -213,8 +243,11 @@ $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) $(am__aclocal_m4_deps): install-dist_binSCRIPTS: $(dist_bin_SCRIPTS) @$(NORMAL_INSTALL) - test -z "$(bindir)" || $(MKDIR_P) "$(DESTDIR)$(bindir)" @list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ for p in $$list; do \ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \ if test -f "$$d$$p"; then echo "$$d$$p"; echo "$$p"; else :; fi; \ @@ -242,9 +275,7 @@ uninstall-dist_binSCRIPTS: @list='$(dist_bin_SCRIPTS)'; test -n "$(bindir)" || exit 0; \ files=`for p in $$list; do echo "$$p"; done | \ sed -e 's,.*/,,;$(transform)'`; \ - test -n "$$list" || exit 0; \ - echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ - cd "$(DESTDIR)$(bindir)" && rm -f $$files + dir='$(DESTDIR)$(bindir)'; $(am__uninstall_files_from_dir) tags: TAGS TAGS: @@ -299,10 +330,15 @@ install-am: all-am installcheck: installcheck-am install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: diff --git a/gui/tinc-gui b/gui/tinc-gui index c0f1cb1..9c6485f 100755 --- a/gui/tinc-gui +++ b/gui/tinc-gui @@ -1,12 +1,35 @@ #!/usr/bin/python +# tinc-gui -- GUI for controlling a running tincd +# Copyright (C) 2009-2012 Guus Sliepen +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + import string import socket import wx import sys +import os +import platform +import time from wx.lib.mixins.listctrl import ColumnSorterMixin from wx.lib.mixins.listctrl import ListCtrlAutoWidthMixin +if platform.system == 'Windows': + import _winreg + # Classes to interface with a running tinc daemon REQ_STOP = 0 @@ -30,34 +53,32 @@ CONTROL = 18 class Node: def parse(self, args): self.name = args[0] - self.address = args[2] - if args[3] != 'port': - args.insert(3, 'port') - args.insert(4, '') - self.port = args[4] - self.cipher = int(args[6]) - self.digest = int(args[8]) - self.maclength = int(args[10]) - self.compression = int(args[12]) - self.options = int(args[14], 0x10) - self.status = int(args[16], 0x10) - self.nexthop = args[18] - self.via = args[20] - self.distance = int(args[22]) - self.pmtu = int(args[24]) - self.minmtu = int(args[26]) - self.maxmtu = int(args[28][:-1]) + self.address = args[1] + self.port = args[3] + self.cipher = int(args[4]) + self.digest = int(args[5]) + self.maclength = int(args[6]) + self.compression = int(args[7]) + self.options = int(args[8], 0x10) + self.status = int(args[9], 0x10) + self.nexthop = args[10] + self.via = args[11] + self.distance = int(args[12]) + self.pmtu = int(args[13]) + self.minmtu = int(args[14]) + self.maxmtu = int(args[15]) + self.last_state_change = float(args[16]) self.subnets = {} class Edge: def parse(self, args): self.fr = args[0] - self.to = args[2] - self.address = args[4] - self.port = args[6] - self.options = int(args[8], 16) - self.weight = int(args[10]) + self.to = args[1] + self.address = args[2] + self.port = args[4] + self.options = int(args[5], 16) + self.weight = int(args[6]) class Subnet: def parse(self, args): @@ -73,19 +94,16 @@ class Subnet: self.address = address self.prefixlen = '48' - self.owner = args[2] + self.owner = args[1] class Connection: def parse(self, args): self.name = args[0] - self.address = args[2] - if args[3] != 'port': - args.insert(3, 'port') - args.insert(4, '') - self.port = args[4] - self.options = int(args[6], 0x10) - self.socket = int(args[8]) - self.status = int(args[10], 0x10) + self.address = args[1] + self.port = args[3] + self.options = int(args[4], 0x10) + self.socket = int(args[5]) + self.status = int(args[6], 0x10) self.weight = 123 class VPN: @@ -132,34 +150,34 @@ class VPN: if resp[0] != '18': break if resp[1] == '3': - if len(resp) < 3: + if len(resp) < 19: continue node = self.nodes.get(resp[2]) or Node() node.parse(resp[2:]) node.visited = True self.nodes[resp[2]] = node elif resp[1] == '4': - if len(resp) < 5: + if len(resp) < 9: continue - edge = self.nodes.get((resp[2], resp[4])) or Edge() + edge = self.nodes.get((resp[2], resp[3])) or Edge() edge.parse(resp[2:]) edge.visited = True - self.edges[(resp[2], resp[4])] = edge + self.edges[(resp[2], resp[3])] = edge elif resp[1] == '5': - if len(resp) < 5: + if len(resp) < 4: continue - subnet = self.subnets.get((resp[2], resp[4])) or Subnet() + subnet = self.subnets.get((resp[2], resp[3])) or Subnet() subnet.parse(resp[2:]) subnet.visited = True - self.subnets[(resp[2], resp[4])] = subnet + self.subnets[(resp[2], resp[3])] = subnet self.nodes[subnet.owner].subnets[resp[2]] = subnet elif resp[1] == '6': - if len(resp) < 5: + if len(resp) < 9: break - connection = self.connections.get((resp[2], resp[4])) or Connection() + connection = self.connections.get((resp[2], resp[3], resp[5])) or Connection() connection.parse(resp[2:]) connection.visited = True - self.connections[(resp[2], resp[4])] = connection + self.connections[(resp[2], resp[3], resp[5])] = connection else: break @@ -198,27 +216,38 @@ class VPN: return int(resp[2]) def __init__(self, netname = None, pidfile = None): - self.tincconf = VPN.confdir + '/' + if platform.system == 'Windows': + try: + reg = _winreg.ConnectRegistry(None, HKEY_LOCAL_MACHINE) + key = _winreg.OpenKey(reg, "SOFTWARE\\tinc") + VPN.confdir = _winreg.QueryValue(key, None) + except WindowsError: + pass if netname: self.netname = netname - self.tincconf += netname + '/' + self.confbase = os.path.join(VPN.confdir, netname) + else: + self.confbase = VPN.confdir - self.tincconf += 'tinc.conf' + self.tincconf = os.path.join(self.confbase, 'tinc.conf') - if pidfile is not None: + if pidfile != None: self.pidfile = pidfile else: - self.pidfile = VPN.piddir + 'tinc.' - if netname: - self.pidfile += netname + '.' - self.pidfile += 'pid' + if platform.system == 'Windows': + self.pidfile = os.path.join(self.confbase, 'pid') + else: + if netname: + self.pidfile = os.path.join(VPN.piddir, 'tinc.' + netname + '.pid') + else: + self.pidfile = os.path.join(VPN.piddir, 'tinc.pid') # GUI starts here argv0 = sys.argv[0] del sys.argv[0] -net = None +netname = None pidfile = None def usage(exitcode = 0): @@ -230,10 +259,10 @@ def usage(exitcode = 0): print('\nReport bugs to tinc@tinc-vpn.org.') sys.exit(exitcode) -while len(sys.argv): +while sys.argv: if sys.argv[0] in ('-n', '--net'): del sys.argv[0] - net = sys.argv[0] + netname = sys.argv[0] elif sys.argv[0] in ('--pidfile'): del sys.argv[0] pidfile = sys.argv[0] @@ -245,14 +274,20 @@ while len(sys.argv): del sys.argv[0] -vpn = VPN(net, pidfile) +if netname == None: + netname = os.getenv("NETNAME") + +if netname == ".": + netname = None + +vpn = VPN(netname, pidfile) vpn.connect() class SuperListCtrl(wx.ListCtrl, ColumnSorterMixin, ListCtrlAutoWidthMixin): def __init__(self, parent, style): wx.ListCtrl.__init__(self, parent, -1, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) ListCtrlAutoWidthMixin.__init__(self) - ColumnSorterMixin.__init__(self, 14) + ColumnSorterMixin.__init__(self, 16) def GetListCtrl(self): return self @@ -265,12 +300,12 @@ class SettingsPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) grid = wx.FlexGridSizer(cols = 2) - grid.AddGrowableCol(0, 1) + grid.AddGrowableCol(1, 1) namelabel = wx.StaticText(self, -1, 'Name:') self.name = wx.TextCtrl(self, -1, vpn.name) grid.Add(namelabel) - grid.Add(self.name) + grid.Add(self.name, 1, wx.EXPAND) portlabel = wx.StaticText(self, -1, 'Port:') self.port = wx.TextCtrl(self, -1, vpn.port) @@ -293,7 +328,7 @@ class SettingsPage(wx.Panel): class ConnectionsPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) - self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + self.list = SuperListCtrl(self, id) self.list.InsertColumn(0, 'Name') self.list.InsertColumn(1, 'Address') self.list.InsertColumn(2, 'Port') @@ -323,6 +358,7 @@ class ConnectionsPage(wx.Panel): self.PopupMenu(self.ContextMenu(self.list.itemDataMap[event.GetIndex()]), event.GetPosition()) def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 @@ -337,11 +373,13 @@ class ConnectionsPage(wx.Panel): self.list.SetStringItem(i, 4, str(connection.weight)) self.list.itemDataMap[i] = (connection.name, connection.address, connection.port, connection.options, connection.weight) self.list.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnContext) + self.list.SetItemData(i, i) i += 1 while self.list.GetItemCount() > i: self.list.DeleteItem(self.list.GetItemCount() - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) class NodesPage(wx.Panel): def __init__(self, parent, id): @@ -362,6 +400,7 @@ class NodesPage(wx.Panel): self.list.InsertColumn(12, 'PMTU') self.list.InsertColumn(13, 'Min MTU') self.list.InsertColumn(14, 'Max MTU') + self.list.InsertColumn(15, 'Since') hbox = wx.BoxSizer(wx.HORIZONTAL) hbox.Add(self.list, 1, wx.EXPAND) @@ -369,6 +408,7 @@ class NodesPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 @@ -383,25 +423,32 @@ class NodesPage(wx.Panel): self.list.SetStringItem(i, 4, str(node.digest)) self.list.SetStringItem(i, 5, str(node.maclength)) self.list.SetStringItem(i, 6, str(node.compression)) - self.list.SetStringItem(i, 7, str(node.options)) - self.list.SetStringItem(i, 8, str(node.status)) + self.list.SetStringItem(i, 7, format(node.options, "x")) + self.list.SetStringItem(i, 8, format(node.status, "04x")) self.list.SetStringItem(i, 9, node.nexthop) self.list.SetStringItem(i, 10, node.via) self.list.SetStringItem(i, 11, str(node.distance)) self.list.SetStringItem(i, 12, str(node.pmtu)) self.list.SetStringItem(i, 13, str(node.minmtu)) self.list.SetStringItem(i, 14, str(node.maxmtu)) - self.list.itemDataMap[i] = (node.name, node.address, node.port, node.cipher, node.digest, node.maclength, node.compression, node.options, node.status, node.nexthop, node.via, node.distance, node.pmtu, node.minmtu, node.maxmtu) + if node.last_state_change: + since = time.strftime("%Y-%m-%d %H:%M", time.localtime(node.last_state_change)) + else: + since = "never" + self.list.SetStringItem(i, 15, since) + self.list.itemDataMap[i] = (node.name, node.address, node.port, node.cipher, node.digest, node.maclength, node.compression, node.options, node.status, node.nexthop, node.via, node.distance, node.pmtu, node.minmtu, node.maxmtu, since) self.list.SetItemData(i, i) i += 1 while self.list.GetItemCount() > i: self.list.DeleteItem(self.list.GetItemCount() - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) + class EdgesPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) - self.list = wx.ListCtrl(self, id, style=wx.LC_REPORT | wx.LC_HRULES | wx.LC_VRULES) + self.list = SuperListCtrl(self, id) self.list.InsertColumn(0, 'From') self.list.InsertColumn(1, 'To') self.list.InsertColumn(2, 'Address') @@ -415,6 +462,7 @@ class EdgesPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 @@ -426,14 +474,17 @@ class EdgesPage(wx.Panel): self.list.SetStringItem(i, 1, edge.to) self.list.SetStringItem(i, 2, edge.address) self.list.SetStringItem(i, 3, edge.port) - self.list.SetStringItem(i, 4, str(edge.options)) + self.list.SetStringItem(i, 4, format(edge.options, "x")) self.list.SetStringItem(i, 5, str(edge.weight)) self.list.itemDataMap[i] = (edge.fr, edge.to, edge.address, edge.port, edge.options, edge.weight) + self.list.SetItemData(i, i) i += 1 while self.list.GetItemCount() > i: self.list.DeleteItem(self.list.GetItemCount() - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) + class SubnetsPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) @@ -447,6 +498,7 @@ class SubnetsPage(wx.Panel): self.refresh() def refresh(self): + sortstate = self.list.GetSortState() self.list.itemDataMap = {} i = 0 @@ -458,11 +510,14 @@ class SubnetsPage(wx.Panel): self.list.SetStringItem(i, 1, subnet.weight) self.list.SetStringItem(i, 2, subnet.owner) self.list.itemDataMap[i] = (subnet.address + '/' + subnet.prefixlen, subnet.weight, subnet.owner) - i = i + 1 + self.list.SetItemData(i, i) + i += 1 while self.list.GetItemCount() > i: self.list.DeleteItem(self.list.GetItemCount() - 1) + self.list.SortListItems(sortstate[0], sortstate[1]) + class StatusPage(wx.Panel): def __init__(self, parent, id): wx.Panel.__init__(self, parent, id) @@ -493,7 +548,7 @@ class NetPage(wx.Notebook): class MainWindow(wx.Frame): def OnQuit(self, event): - self.Close(True) + app.ExitMainLoop() def OnTimer(self, event): vpn.refresh() diff --git a/have.h b/have.h index 0ab8134..d040717 100644 --- a/have.h +++ b/have.h @@ -1,7 +1,7 @@ /* have.h -- include headers which are known to exist Copyright (C) 1998-2005 Ivo Timmermans - 2003-2011 Guus Sliepen + 2003-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -42,6 +42,7 @@ #ifdef HAVE_MINGW #include +#include #include #include #endif @@ -199,4 +200,10 @@ #include #endif +#ifdef HAVE_MINGW +#define SLASH "\\" +#else +#define SLASH "/" +#endif + #endif /* __TINC_SYSTEM_H__ */ diff --git a/install-sh b/install-sh index 6781b98..a9244eb 100755 --- a/install-sh +++ b/install-sh @@ -1,7 +1,7 @@ #!/bin/sh # install - install a program, script, or datafile -scriptversion=2009-04-28.21; # UTC +scriptversion=2011-01-19.21; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the @@ -156,6 +156,10 @@ while test $# -ne 0; do -s) stripcmd=$stripprog;; -t) dst_arg=$2 + # Protect names problematic for `test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac shift;; -T) no_target_directory=true;; @@ -186,6 +190,10 @@ if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then fi shift # arg dst_arg=$arg + # Protect names problematic for `test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac done fi @@ -200,7 +208,11 @@ if test $# -eq 0; then fi if test -z "$dir_arg"; then - trap '(exit $?); exit' 1 2 13 15 + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 # Set umask so as not to create temps with too-generous modes. # However, 'strip' requires both read and write access to temps. @@ -228,9 +240,9 @@ fi for src do - # Protect names starting with `-'. + # Protect names problematic for `test' and other utilities. case $src in - -*) src=./$src;; + -* | [=\(\)!]) src=./$src;; esac if test -n "$dir_arg"; then @@ -252,12 +264,7 @@ do echo "$0: no destination specified." >&2 exit 1 fi - dst=$dst_arg - # Protect names starting with `-'. - case $dst in - -*) dst=./$dst;; - esac # If destination is a directory, append the input filename; won't work # if double slashes aren't ignored. @@ -385,7 +392,7 @@ do case $dstdir in /*) prefix='/';; - -*) prefix='./';; + [-=\(\)!]*) prefix='./';; *) prefix='';; esac @@ -403,7 +410,7 @@ do for d do - test -z "$d" && continue + test X"$d" = X && continue prefix=$prefix$d if test -d "$prefix"; then diff --git a/m4/Makefile.in b/m4/Makefile.in index 17af65a..8f1372a 100644 --- a/m4/Makefile.in +++ b/m4/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -15,6 +15,23 @@ @SET_MAKE@ VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -39,7 +56,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libevent.m4 \ $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/openssl.m4 \ - $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.in + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/zlib.m4 \ + $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d @@ -48,6 +66,11 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = SOURCES = DIST_SOURCES = +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) ACLOCAL = @ACLOCAL@ AMTAR = @AMTAR@ @@ -97,6 +120,7 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -236,10 +260,15 @@ install-am: all-am installcheck: installcheck-am install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: diff --git a/m4/openssl.m4 b/m4/openssl.m4 index 254ea4f..922e468 100644 --- a/m4/openssl.m4 +++ b/m4/openssl.m4 @@ -35,7 +35,7 @@ AC_DEFUN([tinc_OPENSSL], LDFLAGS="$LDFLAGS -L$withval"] ) - AC_CHECK_HEADERS(openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h, + AC_CHECK_HEADERS([openssl/evp.h openssl/rsa.h openssl/rand.h openssl/err.h openssl/sha.h openssl/pem.h openssl/engine.h openssl/ecdh.h openssl/ec.h], [], [AC_MSG_ERROR([OpenSSL header files not found.]); break] ) @@ -45,7 +45,7 @@ AC_DEFUN([tinc_OPENSSL], [AC_MSG_ERROR([OpenSSL libraries not found.])] ) - AC_CHECK_FUNCS([RAND_pseudo_bytes EVP_EncryptInit_ex], , + AC_CHECK_FUNCS([RAND_pseudo_bytes EVP_EncryptInit_ex ECDH_compute_key ECDSA_verify], , [AC_MSG_ERROR([Missing OpenSSL functionality, make sure you have installed the latest version.]); break], ) diff --git a/m4/readline.m4 b/m4/readline.m4 new file mode 100644 index 0000000..f29e692 --- /dev/null +++ b/m4/readline.m4 @@ -0,0 +1,42 @@ +dnl Check to find the readline headers/libraries + +AC_DEFUN([tinc_READLINE], +[ + AC_ARG_ENABLE([readline], + AS_HELP_STRING([--disable-readline], [disable readline support])) + AS_IF([test "x$enable_readline" != "xno"], [ + AC_DEFINE(HAVE_READLINE, 1, [have readline support]) + readline=true + AC_ARG_WITH(readline, + AS_HELP_STRING([--with-readline=DIR], [readline base directory, or:]), + [readline="$withval" + CPPFLAGS="$CPPFLAGS -I$withval/include" + LDFLAGS="$LDFLAGS -L$withval/lib"] + ) + + AC_ARG_WITH(readline-include, + AS_HELP_STRING([--with-readline-include=DIR], [readline headers directory]), + [readline_include="$withval" + CPPFLAGS="$CPPFLAGS -I$withval"] + ) + + AC_ARG_WITH(readline-lib, + AS_HELP_STRING([--with-readline-lib=DIR], [readline library directory]), + [readline_lib="$withval" + LDFLAGS="$LDFLAGS -L$withval"] + ) + + AC_CHECK_HEADERS([readline/readline.h readline/history.h], + [], + [AC_MSG_ERROR("readline header files not found."); break] + ) + + AC_CHECK_LIB(readline, readline, + [READLINE_LIBS="-lreadline"], + [AC_MSG_ERROR("readline library not found.")], + [$CURSES_LIBS] + ) + ]) + + AC_SUBST(READLINE_LIBS) +]) diff --git a/missing b/missing index 28055d2..86a8fc3 100755 --- a/missing +++ b/missing @@ -1,10 +1,10 @@ #! /bin/sh # Common stub for a few missing GNU programs while installing. -scriptversion=2009-04-28.21; # UTC +scriptversion=2012-01-06.13; # UTC # Copyright (C) 1996, 1997, 1999, 2000, 2002, 2003, 2004, 2005, 2006, -# 2008, 2009 Free Software Foundation, Inc. +# 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc. # Originally by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify @@ -84,7 +84,6 @@ Supported PROGRAM values: help2man touch the output file lex create \`lex.yy.c', if possible, from existing .c makeinfo touch the output file - tar try tar, gnutar, gtar, then tar without non-portable flags yacc create \`y.tab.[ch]', if possible, from existing .[ch] Version suffixes to PROGRAM as well as the prefixes \`gnu-', \`gnu', and @@ -122,15 +121,6 @@ case $1 in # Not GNU programs, they don't have --version. ;; - tar*) - if test -n "$run"; then - echo 1>&2 "ERROR: \`tar' requires --run" - exit 1 - elif test "x$2" = "x--version" || test "x$2" = "x--help"; then - exit 1 - fi - ;; - *) if test -z "$run" && ($1 --version) > /dev/null 2>&1; then # We have it, but it failed. @@ -226,7 +216,7 @@ WARNING: \`$1' $msg. You should only need it if \`Bison' from any GNU archive site." rm -f y.tab.c y.tab.h if test $# -ne 1; then - eval LASTARG="\${$#}" + eval LASTARG=\${$#} case $LASTARG in *.y) SRCFILE=`echo "$LASTARG" | sed 's/y$/c/'` @@ -256,7 +246,7 @@ WARNING: \`$1' is $msg. You should only need it if \`Flex' from any GNU archive site." rm -f lex.yy.c if test $# -ne 1; then - eval LASTARG="\${$#}" + eval LASTARG=\${$#} case $LASTARG in *.l) SRCFILE=`echo "$LASTARG" | sed 's/l$/c/'` @@ -318,41 +308,6 @@ WARNING: \`$1' is $msg. You should only need it if touch $file ;; - tar*) - shift - - # We have already tried tar in the generic part. - # Look for gnutar/gtar before invocation to avoid ugly error - # messages. - if (gnutar --version > /dev/null 2>&1); then - gnutar "$@" && exit 0 - fi - if (gtar --version > /dev/null 2>&1); then - gtar "$@" && exit 0 - fi - firstarg="$1" - if shift; then - case $firstarg in - *o*) - firstarg=`echo "$firstarg" | sed s/o//` - tar "$firstarg" "$@" && exit 0 - ;; - esac - case $firstarg in - *h*) - firstarg=`echo "$firstarg" | sed s/h//` - tar "$firstarg" "$@" && exit 0 - ;; - esac - fi - - echo 1>&2 "\ -WARNING: I can't seem to be able to run \`tar' with the given arguments. - You may want to install GNU tar or Free paxutils, or check the - command line arguments." - exit 1 - ;; - *) echo 1>&2 "\ WARNING: \`$1' is needed, and is $msg. diff --git a/src/Makefile.am b/src/Makefile.am index 186c042..3bd8646 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,30 +1,43 @@ ## Produce this file with automake to get Makefile.in -sbin_PROGRAMS = tincd tincctl +sbin_PROGRAMS = tincd tincctl sptps_test -EXTRA_DIST = linux bsd solaris cygwin mingw raw_socket uml_socket openssl gcrypt +EXTRA_DIST = linux bsd solaris cygwin mingw openssl gcrypt tincd_SOURCES = \ - utils.c getopt.c getopt1.c list.c splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c \ + utils.c getopt.c getopt1.c list.c splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c hash.c \ buffer.c conf.c connection.c control.c edge.c graph.c logger.c meta.c net.c net_packet.c net_setup.c \ net_socket.c netutl.c node.c process.c protocol.c protocol_auth.c protocol_edge.c protocol_misc.c \ - protocol_key.c protocol_subnet.c route.c subnet.c tincd.c + protocol_key.c protocol_subnet.c route.c sptps.c subnet.c subnet_parse.c tincd.c \ + dummy_device.c raw_socket_device.c multicast_device.c + +if UML +tincd_SOURCES += uml_device.c +endif + +if VDE +tincd_SOURCES += vde_device.c +endif nodist_tincd_SOURCES = \ device.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c rsa.c tincctl_SOURCES = \ utils.c getopt.c getopt1.c dropin.c \ - list.c tincctl.c top.c + info.c list.c subnet_parse.c tincctl.c top.c nodist_tincctl_SOURCES = \ ecdsagen.c rsagen.c +sptps_test_SOURCES = \ + logger.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c \ + sptps.c sptps_test.c utils.c + if TUNEMU tincd_SOURCES += bsd/tunemu.c endif -tincctl_LDADD = $(CURSES_LIBS) +tincctl_LDADD = $(READLINE_LIBS) $(CURSES_LIBS) DEFAULT_INCLUDES = @@ -32,8 +45,8 @@ INCLUDES = @INCLUDES@ -I$(top_builddir) noinst_HEADERS = \ xalloc.h utils.h getopt.h list.h splay_tree.h dropin.h fake-getaddrinfo.h fake-getnameinfo.h fake-gai-errnos.h ipv6.h ipv4.h ethernet.h \ - buffer.h conf.h connection.h control.h control_common.h device.h edge.h graph.h logger.h meta.h net.h netutl.h node.h process.h \ - protocol.h route.h subnet.h tincctl.h top.h bsd/tunemu.h + buffer.h conf.h connection.h control.h control_common.h device.h edge.h graph.h info.h logger.h meta.h net.h netutl.h node.h process.h \ + protocol.h route.h subnet.h sptps.h tincctl.h top.h bsd/tunemu.h hash.h nodist_noinst_HEADERS = \ cipher.h crypto.h ecdh.h ecdsa.h digest.h prf.h rsa.h ecdsagen.h rsagen.h diff --git a/src/Makefile.in b/src/Makefile.in index d7e2afd..d2a972b 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -1,9 +1,9 @@ -# Makefile.in generated by automake 1.11.1 from Makefile.am. +# Makefile.in generated by automake 1.11.6 from Makefile.am. # @configure_input@ # Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, -# 2003, 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, -# Inc. +# 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011 Free Software +# Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. @@ -17,6 +17,23 @@ VPATH = @srcdir@ +am__make_dryrun = \ + { \ + am__dry=no; \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + echo 'am--echo: ; @echo "AM" OK' | $(MAKE) -f - 2>/dev/null \ + | grep '^AM OK$$' >/dev/null || am__dry=yes;; \ + *) \ + for am__flg in $$MAKEFLAGS; do \ + case $$am__flg in \ + *=*|--*) ;; \ + *n*) am__dry=yes; break;; \ + esac; \ + done;; \ + esac; \ + test $$am__dry = yes; \ + } pkgdatadir = $(datadir)/@PACKAGE@ pkgincludedir = $(includedir)/@PACKAGE@ pkglibdir = $(libdir)/@PACKAGE@ @@ -35,9 +52,11 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -sbin_PROGRAMS = tincd$(EXEEXT) tincctl$(EXEEXT) -@TUNEMU_TRUE@am__append_1 = bsd/tunemu.c -@TUNEMU_TRUE@am__append_2 = -lpcap +sbin_PROGRAMS = tincd$(EXEEXT) tincctl$(EXEEXT) sptps_test$(EXEEXT) +@UML_TRUE@am__append_1 = uml_device.c +@VDE_TRUE@am__append_2 = vde_device.c +@TUNEMU_TRUE@am__append_3 = bsd/tunemu.c +@TUNEMU_TRUE@am__append_4 = -lpcap subdir = src DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ $(srcdir)/Makefile.in @@ -45,7 +64,8 @@ ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 am__aclocal_m4_deps = $(top_srcdir)/m4/attribute.m4 \ $(top_srcdir)/m4/curses.m4 $(top_srcdir)/m4/libevent.m4 \ $(top_srcdir)/m4/lzo.m4 $(top_srcdir)/m4/openssl.m4 \ - $(top_srcdir)/m4/zlib.m4 $(top_srcdir)/configure.in + $(top_srcdir)/m4/readline.m4 $(top_srcdir)/m4/zlib.m4 \ + $(top_srcdir)/configure.in am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ $(ACLOCAL_M4) mkinstalldirs = $(install_sh) -d @@ -54,34 +74,47 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(sbindir)" PROGRAMS = $(sbin_PROGRAMS) +am_sptps_test_OBJECTS = logger.$(OBJEXT) cipher.$(OBJEXT) \ + crypto.$(OBJEXT) ecdh.$(OBJEXT) ecdsa.$(OBJEXT) \ + digest.$(OBJEXT) prf.$(OBJEXT) sptps.$(OBJEXT) \ + sptps_test.$(OBJEXT) utils.$(OBJEXT) +sptps_test_OBJECTS = $(am_sptps_test_OBJECTS) +sptps_test_LDADD = $(LDADD) am_tincctl_OBJECTS = utils.$(OBJEXT) getopt.$(OBJEXT) \ - getopt1.$(OBJEXT) dropin.$(OBJEXT) list.$(OBJEXT) \ - tincctl.$(OBJEXT) top.$(OBJEXT) + getopt1.$(OBJEXT) dropin.$(OBJEXT) info.$(OBJEXT) \ + list.$(OBJEXT) subnet_parse.$(OBJEXT) tincctl.$(OBJEXT) \ + top.$(OBJEXT) nodist_tincctl_OBJECTS = ecdsagen.$(OBJEXT) rsagen.$(OBJEXT) tincctl_OBJECTS = $(am_tincctl_OBJECTS) $(nodist_tincctl_OBJECTS) am__DEPENDENCIES_1 = -tincctl_DEPENDENCIES = $(am__DEPENDENCIES_1) +tincctl_DEPENDENCIES = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) am__tincd_SOURCES_DIST = utils.c getopt.c getopt1.c list.c \ splay_tree.c dropin.c fake-getaddrinfo.c fake-getnameinfo.c \ - buffer.c conf.c connection.c control.c edge.c graph.c logger.c \ - meta.c net.c net_packet.c net_setup.c net_socket.c netutl.c \ - node.c process.c protocol.c protocol_auth.c protocol_edge.c \ - protocol_misc.c protocol_key.c protocol_subnet.c route.c \ - subnet.c tincd.c bsd/tunemu.c -@TUNEMU_TRUE@am__objects_1 = tunemu.$(OBJEXT) + hash.c buffer.c conf.c connection.c control.c edge.c graph.c \ + logger.c meta.c net.c net_packet.c net_setup.c net_socket.c \ + netutl.c node.c process.c protocol.c protocol_auth.c \ + protocol_edge.c protocol_misc.c protocol_key.c \ + protocol_subnet.c route.c sptps.c subnet.c subnet_parse.c \ + tincd.c dummy_device.c raw_socket_device.c multicast_device.c \ + uml_device.c vde_device.c bsd/tunemu.c +@UML_TRUE@am__objects_1 = uml_device.$(OBJEXT) +@VDE_TRUE@am__objects_2 = vde_device.$(OBJEXT) +@TUNEMU_TRUE@am__objects_3 = tunemu.$(OBJEXT) am_tincd_OBJECTS = utils.$(OBJEXT) getopt.$(OBJEXT) getopt1.$(OBJEXT) \ list.$(OBJEXT) splay_tree.$(OBJEXT) dropin.$(OBJEXT) \ fake-getaddrinfo.$(OBJEXT) fake-getnameinfo.$(OBJEXT) \ - buffer.$(OBJEXT) conf.$(OBJEXT) connection.$(OBJEXT) \ - control.$(OBJEXT) edge.$(OBJEXT) graph.$(OBJEXT) \ - logger.$(OBJEXT) meta.$(OBJEXT) net.$(OBJEXT) \ + hash.$(OBJEXT) buffer.$(OBJEXT) conf.$(OBJEXT) \ + connection.$(OBJEXT) control.$(OBJEXT) edge.$(OBJEXT) \ + graph.$(OBJEXT) logger.$(OBJEXT) meta.$(OBJEXT) net.$(OBJEXT) \ net_packet.$(OBJEXT) net_setup.$(OBJEXT) net_socket.$(OBJEXT) \ netutl.$(OBJEXT) node.$(OBJEXT) process.$(OBJEXT) \ protocol.$(OBJEXT) protocol_auth.$(OBJEXT) \ protocol_edge.$(OBJEXT) protocol_misc.$(OBJEXT) \ protocol_key.$(OBJEXT) protocol_subnet.$(OBJEXT) \ - route.$(OBJEXT) subnet.$(OBJEXT) tincd.$(OBJEXT) \ - $(am__objects_1) + route.$(OBJEXT) sptps.$(OBJEXT) subnet.$(OBJEXT) \ + subnet_parse.$(OBJEXT) tincd.$(OBJEXT) dummy_device.$(OBJEXT) \ + raw_socket_device.$(OBJEXT) multicast_device.$(OBJEXT) \ + $(am__objects_1) $(am__objects_2) $(am__objects_3) nodist_tincd_OBJECTS = device.$(OBJEXT) cipher.$(OBJEXT) \ crypto.$(OBJEXT) ecdh.$(OBJEXT) ecdsa.$(OBJEXT) \ digest.$(OBJEXT) prf.$(OBJEXT) rsa.$(OBJEXT) @@ -94,9 +127,16 @@ COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) CCLD = $(CC) LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ -SOURCES = $(tincctl_SOURCES) $(nodist_tincctl_SOURCES) \ - $(tincd_SOURCES) $(nodist_tincd_SOURCES) -DIST_SOURCES = $(tincctl_SOURCES) $(am__tincd_SOURCES_DIST) +SOURCES = $(sptps_test_SOURCES) $(tincctl_SOURCES) \ + $(nodist_tincctl_SOURCES) $(tincd_SOURCES) \ + $(nodist_tincd_SOURCES) +DIST_SOURCES = $(sptps_test_SOURCES) $(tincctl_SOURCES) \ + $(am__tincd_SOURCES_DIST) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac HEADERS = $(nodist_noinst_HEADERS) $(noinst_HEADERS) ETAGS = etags CTAGS = ctags @@ -133,7 +173,7 @@ LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@ LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@ LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@ LIBOBJS = @LIBOBJS@ -LIBS = @LIBS@ @LIBGCRYPT_LIBS@ $(am__append_2) +LIBS = @LIBS@ @LIBGCRYPT_LIBS@ $(am__append_4) LN_S = @LN_S@ LTLIBOBJS = @LTLIBOBJS@ MAINT = @MAINT@ @@ -149,6 +189,7 @@ PACKAGE_URL = @PACKAGE_URL@ PACKAGE_VERSION = @PACKAGE_VERSION@ PATH_SEPARATOR = @PATH_SEPARATOR@ RANLIB = @RANLIB@ +READLINE_LIBS = @READLINE_LIBS@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ STRIP = @STRIP@ @@ -203,30 +244,36 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -EXTRA_DIST = linux bsd solaris cygwin mingw raw_socket uml_socket openssl gcrypt +EXTRA_DIST = linux bsd solaris cygwin mingw openssl gcrypt tincd_SOURCES = utils.c getopt.c getopt1.c list.c splay_tree.c \ - dropin.c fake-getaddrinfo.c fake-getnameinfo.c buffer.c conf.c \ - connection.c control.c edge.c graph.c logger.c meta.c net.c \ - net_packet.c net_setup.c net_socket.c netutl.c node.c \ + dropin.c fake-getaddrinfo.c fake-getnameinfo.c hash.c buffer.c \ + conf.c connection.c control.c edge.c graph.c logger.c meta.c \ + net.c net_packet.c net_setup.c net_socket.c netutl.c node.c \ process.c protocol.c protocol_auth.c protocol_edge.c \ protocol_misc.c protocol_key.c protocol_subnet.c route.c \ - subnet.c tincd.c $(am__append_1) + sptps.c subnet.c subnet_parse.c tincd.c dummy_device.c \ + raw_socket_device.c multicast_device.c $(am__append_1) \ + $(am__append_2) $(am__append_3) nodist_tincd_SOURCES = \ device.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c rsa.c tincctl_SOURCES = \ utils.c getopt.c getopt1.c dropin.c \ - list.c tincctl.c top.c + info.c list.c subnet_parse.c tincctl.c top.c nodist_tincctl_SOURCES = \ ecdsagen.c rsagen.c -tincctl_LDADD = $(CURSES_LIBS) +sptps_test_SOURCES = \ + logger.c cipher.c crypto.c ecdh.c ecdsa.c digest.c prf.c \ + sptps.c sptps_test.c utils.c + +tincctl_LDADD = $(READLINE_LIBS) $(CURSES_LIBS) DEFAULT_INCLUDES = noinst_HEADERS = \ xalloc.h utils.h getopt.h list.h splay_tree.h dropin.h fake-getaddrinfo.h fake-getnameinfo.h fake-gai-errnos.h ipv6.h ipv4.h ethernet.h \ - buffer.h conf.h connection.h control.h control_common.h device.h edge.h graph.h logger.h meta.h net.h netutl.h node.h process.h \ - protocol.h route.h subnet.h tincctl.h top.h bsd/tunemu.h + buffer.h conf.h connection.h control.h control_common.h device.h edge.h graph.h info.h logger.h meta.h net.h netutl.h node.h process.h \ + protocol.h route.h subnet.h sptps.h tincctl.h top.h bsd/tunemu.h hash.h nodist_noinst_HEADERS = \ cipher.h crypto.h ecdh.h ecdsa.h digest.h prf.h rsa.h ecdsagen.h rsagen.h @@ -268,8 +315,11 @@ $(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) $(am__aclocal_m4_deps): install-sbinPROGRAMS: $(sbin_PROGRAMS) @$(NORMAL_INSTALL) - test -z "$(sbindir)" || $(MKDIR_P) "$(DESTDIR)$(sbindir)" @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ for p in $$list; do echo "$$p $$p"; done | \ sed 's/$(EXEEXT)$$//' | \ while read p p1; do if test -f $$p; \ @@ -303,10 +353,13 @@ uninstall-sbinPROGRAMS: clean-sbinPROGRAMS: -test -z "$(sbin_PROGRAMS)" || rm -f $(sbin_PROGRAMS) -tincctl$(EXEEXT): $(tincctl_OBJECTS) $(tincctl_DEPENDENCIES) +sptps_test$(EXEEXT): $(sptps_test_OBJECTS) $(sptps_test_DEPENDENCIES) $(EXTRA_sptps_test_DEPENDENCIES) + @rm -f sptps_test$(EXEEXT) + $(LINK) $(sptps_test_OBJECTS) $(sptps_test_LDADD) $(LIBS) +tincctl$(EXEEXT): $(tincctl_OBJECTS) $(tincctl_DEPENDENCIES) $(EXTRA_tincctl_DEPENDENCIES) @rm -f tincctl$(EXEEXT) $(LINK) $(tincctl_OBJECTS) $(tincctl_LDADD) $(LIBS) -tincd$(EXEEXT): $(tincd_OBJECTS) $(tincd_DEPENDENCIES) +tincd$(EXEEXT): $(tincd_OBJECTS) $(tincd_DEPENDENCIES) $(EXTRA_tincd_DEPENDENCIES) @rm -f tincd$(EXEEXT) $(LINK) $(tincd_OBJECTS) $(tincd_LDADD) $(LIBS) @@ -325,6 +378,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/device.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/digest.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dropin.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dummy_device.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecdh.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecdsa.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecdsagen.Po@am__quote@ @@ -334,9 +388,12 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/getopt1.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/graph.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/hash.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/info.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/list.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/logger.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/meta.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/multicast_device.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_packet.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/net_setup.Po@am__quote@ @@ -351,16 +408,22 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_key.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_misc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/protocol_subnet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/raw_socket_device.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/route.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rsa.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rsagen.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/splay_tree.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sptps_test.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subnet.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/subnet_parse.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tincctl.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tincd.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/top.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tunemu.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/uml_device.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/utils.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/vde_device.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $(DEPDIR)/$*.Tpo -c -o $@ $< @@ -492,10 +555,15 @@ install-am: all-am installcheck: installcheck-am install-strip: - $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ - install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ - `test -z '$(STRIP)' || \ - echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi mostlyclean-generic: clean-generic: diff --git a/src/bsd/device.c b/src/bsd/device.c index 9c3009d..3e64ba9 100644 --- a/src/bsd/device.c +++ b/src/bsd/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction BSD tun/tap device Copyright (C) 2001-2005 Ivo Timmermans, - 2001-2011 Guus Sliepen + 2001-2012 Guus Sliepen 2009 Grzegorz Dymarek This program is free software; you can redistribute it and/or modify @@ -33,7 +33,12 @@ #include "bsd/tunemu.h" #endif -#define DEFAULT_DEVICE "/dev/tun0" +#define DEFAULT_TUN_DEVICE "/dev/tun0" +#if defined(HAVE_FREEBSD) || defined(HAVE_NETBSD) +#define DEFAULT_TAP_DEVICE "/dev/tap0" +#else +#define DEFAULT_TAP_DEVICE "/dev/tun0" +#endif typedef enum device_type { DEVICE_TYPE_TUN, @@ -58,18 +63,22 @@ static device_type_t device_type = DEVICE_TYPE_TUNIFHEAD; static device_type_t device_type = DEVICE_TYPE_TUN; #endif -bool setup_device(void) { +static bool setup_device(void) { char *type; - if(!get_config_string(lookup_config(config_tree, "Device"), &device)) - device = xstrdup(DEFAULT_DEVICE); + if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { + if(routing_mode == RMODE_ROUTER) + device = xstrdup(DEFAULT_TUN_DEVICE); + else + device = xstrdup(DEFAULT_TAP_DEVICE); + } if(!get_config_string(lookup_config(config_tree, "Interface"), &iface)) iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device); if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) { if(!strcasecmp(type, "tun")) - /* use default */; + /* use default */; #ifdef HAVE_TUNEMU else if(!strcasecmp(type, "tunemu")) device_type = DEVICE_TYPE_TUNEMU; @@ -81,7 +90,7 @@ bool setup_device(void) { else if(!strcasecmp(type, "tap")) device_type = DEVICE_TYPE_TAP; else { - logger(LOG_ERR, "Unknown device type %s!", type); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown device type %s!", type); return false; } } else { @@ -93,7 +102,7 @@ bool setup_device(void) { #ifdef HAVE_TUNEMU case DEVICE_TYPE_TUNEMU: { char dynamic_name[256] = ""; - device_fd = tunemu_open(dynamic_name); + device_fd = tunemu_open(dynamic_name); } break; #endif @@ -102,19 +111,23 @@ bool setup_device(void) { } if(device_fd < 0) { - logger(LOG_ERR, "Could not open %s: %s", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + switch(device_type) { default: device_type = DEVICE_TYPE_TUN; case DEVICE_TYPE_TUN: #ifdef TUNSIFHEAD - { + { const int zero = 0; if(ioctl(device_fd, TUNSIFHEAD, &zero, sizeof zero) == -1) { - logger(LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); return false; } } @@ -133,7 +146,7 @@ bool setup_device(void) { { const int one = 1; if(ioctl(device_fd, TUNSIFHEAD, &one, sizeof one) == -1) { - logger(LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "ioctl", strerror(errno)); return false; } } @@ -160,22 +173,22 @@ bool setup_device(void) { iface = xstrdup(ifr.ifr_name); } } - + #endif break; #ifdef HAVE_TUNEMU case DEVICE_TYPE_TUNEMU: - device_info = "BSD tunemu device"; + device_info = "BSD tunemu device"; break; #endif } - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } -void close_device(void) { +static void close_device(void) { switch(device_type) { #ifdef HAVE_TUNEMU case DEVICE_TYPE_TUNEMU: @@ -190,7 +203,7 @@ void close_device(void) { free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; switch(device_type) { @@ -204,7 +217,7 @@ bool read_packet(vpn_packet_t *packet) { inlen = read(device_fd, packet->data + 14, MTU - 14); if(inlen <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -219,12 +232,13 @@ bool read_packet(vpn_packet_t *packet) { packet->data[13] = 0xDD; break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown IP version %d while reading packet from %s %s", packet->data[14] >> 4, device_info, device); return false; } + memset(packet->data, 0, 12); packet->len = inlen + 14; break; @@ -233,7 +247,7 @@ bool read_packet(vpn_packet_t *packet) { struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, MTU - 14}}; if((inlen = readv(device_fd, vector, 2)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -250,19 +264,20 @@ bool read_packet(vpn_packet_t *packet) { break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown address family %x while reading packet from %s %s", ntohl(type), device_info, device); return false; } + memset(packet->data, 0, 12); packet->len = inlen + 10; break; } case DEVICE_TYPE_TAP: if((inlen = read(device_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -273,23 +288,23 @@ bool read_packet(vpn_packet_t *packet) { default: return false; } - + device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } -bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); switch(device_type) { case DEVICE_TYPE_TUN: if(write(device_fd, packet->data + 14, packet->len - 14) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -299,7 +314,7 @@ bool write_packet(vpn_packet_t *packet) { u_int32_t type; struct iovec vector[2] = {{&type, sizeof type}, {packet->data + 14, packet->len - 14}}; int af; - + af = (packet->data[12] << 8) + packet->data[13]; switch (af) { @@ -310,23 +325,23 @@ bool write_packet(vpn_packet_t *packet) { type = htonl(AF_INET6); break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown address family %x while writing packet to %s %s", af, device_info, device); return false; } if(writev(device_fd, vector, 2) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } break; } - + case DEVICE_TYPE_TAP: if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -335,7 +350,7 @@ bool write_packet(vpn_packet_t *packet) { #ifdef HAVE_TUNEMU case DEVICE_TYPE_TUNEMU: if(tunemu_write(device_fd, packet->data + 14, packet->len - 14) < 0) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -351,8 +366,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t os_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/bsd/tunemu.c b/src/bsd/tunemu.c index f532b04..1ce9007 100644 --- a/src/bsd/tunemu.c +++ b/src/bsd/tunemu.c @@ -1,20 +1,20 @@ /* * tunemu - Tun device emulation for Darwin * Copyright (C) 2009 Friedrich Schöller - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * */ #include "tunemu.h" @@ -36,18 +36,18 @@ #define PPPPROTO_CTL 1 -#define PPP_IP 0x21 -#define PPP_IPV6 0x57 +#define PPP_IP 0x21 +#define PPP_IPV6 0x57 #define SC_LOOP_TRAFFIC 0x00000200 -#define PPPIOCNEWUNIT _IOWR('t', 62, int) -#define PPPIOCSFLAGS _IOW('t', 89, int) -#define PPPIOCSNPMODE _IOW('t', 75, struct npioctl) -#define PPPIOCATTCHAN _IOW('t', 56, int) -#define PPPIOCGCHAN _IOR('t', 55, int) -#define PPPIOCCONNECT _IOW('t', 58, int) -#define PPPIOCGUNIT _IOR('t', 86, int) +#define PPPIOCNEWUNIT _IOWR('t', 62, int) +#define PPPIOCSFLAGS _IOW('t', 89, int) +#define PPPIOCSNPMODE _IOW('t', 75, struct npioctl) +#define PPPIOCATTCHAN _IOW('t', 56, int) +#define PPPIOCGCHAN _IOR('t', 55, int) +#define PPPIOCCONNECT _IOW('t', 58, int) +#define PPPIOCGUNIT _IOR('t', 86, int) struct sockaddr_ppp { diff --git a/src/bsd/tunemu.h b/src/bsd/tunemu.h index 42b1785..e0452a8 100644 --- a/src/bsd/tunemu.h +++ b/src/bsd/tunemu.h @@ -1,20 +1,20 @@ /* * tunemu - Tun device emulation for Darwin * Copyright (C) 2009 Friedrich Schöller - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with this program. If not, see . - * + * */ #ifndef TUNEMU_H diff --git a/src/buffer.c b/src/buffer.c index 3d4c329..ac57d1c 100644 --- a/src/buffer.c +++ b/src/buffer.c @@ -57,7 +57,7 @@ char *buffer_prepare(buffer_t *buffer, int size) { } // Copy data into the buffer. - + void buffer_add(buffer_t *buffer, const char *data, int size) { memcpy(buffer_prepare(buffer, size), data, size); } diff --git a/src/cipher.c b/src/cipher.c new file mode 100644 index 0000000..553b4ad --- /dev/null +++ b/src/cipher.c @@ -0,0 +1,218 @@ +/* + cipher.c -- Symmetric block cipher handling + Copyright (C) 2007-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include +#include + +#include "cipher.h" +#include "logger.h" +#include "xalloc.h" + +typedef struct cipher_counter { + unsigned char counter[EVP_MAX_IV_LENGTH]; + unsigned char block[EVP_MAX_IV_LENGTH]; + int n; +} cipher_counter_t; + +static bool cipher_open(cipher_t *cipher) { + EVP_CIPHER_CTX_init(&cipher->ctx); + + return true; +} + +bool cipher_open_by_name(cipher_t *cipher, const char *name) { + cipher->cipher = EVP_get_cipherbyname(name); + + if(cipher->cipher) + return cipher_open(cipher); + + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher name '%s'!", name); + return false; +} + +bool cipher_open_by_nid(cipher_t *cipher, int nid) { + cipher->cipher = EVP_get_cipherbynid(nid); + + if(cipher->cipher) + return cipher_open(cipher); + + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher nid %d!", nid); + return false; +} + +bool cipher_open_blowfish_ofb(cipher_t *cipher) { + cipher->cipher = EVP_bf_ofb(); + return cipher_open(cipher); +} + +void cipher_close(cipher_t *cipher) { + EVP_CIPHER_CTX_cleanup(&cipher->ctx); + free(cipher->counter); + cipher->counter = NULL; +} + +size_t cipher_keylength(const cipher_t *cipher) { + return cipher->cipher->key_len + cipher->cipher->block_size; +} + +bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) { + bool result; + + if(encrypt) + result = EVP_EncryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, (unsigned char *)key + cipher->cipher->key_len); + else + result = EVP_DecryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, (unsigned char *)key + cipher->cipher->key_len); + + if(result) + return true; + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_set_key_from_rsa(cipher_t *cipher, void *key, size_t len, bool encrypt) { + bool result; + + if(encrypt) + result = EVP_EncryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key + len - cipher->cipher->key_len, (unsigned char *)key + len - cipher->cipher->iv_len - cipher->cipher->key_len); + else + result = EVP_DecryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key + len - cipher->cipher->key_len, (unsigned char *)key + len - cipher->cipher->iv_len - cipher->cipher->key_len); + + if(result) + return true; + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_set_counter(cipher_t *cipher, const void *counter, size_t len) { + if(len > cipher->cipher->block_size - 4) { + logger(DEBUG_ALWAYS, LOG_ERR, "Counter too long"); + abort(); + } + + memcpy(cipher->counter->counter + cipher->cipher->block_size - len, counter, len); + memset(cipher->counter->counter, 0, 4); + cipher->counter->n = 0; + + return true; +} + +bool cipher_set_counter_key(cipher_t *cipher, void *key) { + int result = EVP_EncryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, NULL); + if(!result) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + if(!cipher->counter) + cipher->counter = xmalloc_and_zero(sizeof *cipher->counter); + else + cipher->counter->n = 0; + + memcpy(cipher->counter->counter, (unsigned char *)key + cipher->cipher->key_len, cipher->cipher->block_size); + + return true; +} + +bool cipher_counter_xor(cipher_t *cipher, const void *indata, size_t inlen, void *outdata) { + if(!cipher->counter) { + logger(DEBUG_ALWAYS, LOG_ERR, "Counter not initialized"); + return false; + } + + const unsigned char *in = indata; + unsigned char *out = outdata; + + while(inlen--) { + // Encrypt the new counter value if we need it + if(!cipher->counter->n) { + int len; + if(!EVP_EncryptUpdate(&cipher->ctx, cipher->counter->block, &len, cipher->counter->counter, cipher->cipher->block_size)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + // Increase the counter value + for(int i = 0; i < cipher->cipher->block_size; i++) + if(++cipher->counter->counter[i]) + break; + } + + *out++ = *in++ ^ cipher->counter->counter[cipher->counter->n++]; + + if(cipher->counter->n >= cipher->cipher->block_size) + cipher->counter->n = 0; + } + + return true; +} + + +bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + if(oneshot) { + int len, pad; + if(EVP_EncryptInit_ex(&cipher->ctx, NULL, NULL, NULL, NULL) + && EVP_EncryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) + && EVP_EncryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) { + if(outlen) *outlen = len + pad; + return true; + } + } else { + int len; + if(EVP_EncryptUpdate(&cipher->ctx, outdata, &len, indata, inlen)) { + if(outlen) *outlen = len; + return true; + } + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { + if(oneshot) { + int len, pad; + if(EVP_DecryptInit_ex(&cipher->ctx, NULL, NULL, NULL, NULL) + && EVP_DecryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) + && EVP_DecryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) { + if(outlen) *outlen = len + pad; + return true; + } + } else { + int len; + if(EVP_EncryptUpdate(&cipher->ctx, outdata, &len, indata, inlen)) { + if(outlen) *outlen = len; + return true; + } + } + + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +int cipher_get_nid(const cipher_t *cipher) { + return cipher->cipher ? cipher->cipher->nid : 0; +} + +bool cipher_active(const cipher_t *cipher) { + return cipher->cipher && cipher->cipher->nid != 0; +} diff --git a/src/conf.c b/src/conf.c index f47faef..b9bfbf6 100644 --- a/src/conf.c +++ b/src/conf.c @@ -2,9 +2,9 @@ conf.c -- configuration code Copyright (C) 1998 Robert van der Meulen 1998-2005 Ivo Timmermans - 2000-2010 Guus Sliepen + 2000-2012 Guus Sliepen 2010-2011 Julien Muchembled - 2000 Cris van Pelt + 2000 Cris van Pelt This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,18 +28,18 @@ #include "conf.h" #include "list.h" #include "logger.h" -#include "netutl.h" /* for str2address */ +#include "netutl.h" /* for str2address */ #include "protocol.h" -#include "utils.h" /* for cp */ +#include "utils.h" /* for cp */ #include "xalloc.h" splay_tree_t *config_tree; -int pinginterval = 0; /* seconds between pings */ -int pingtimeout = 0; /* seconds to wait for response */ -char *confbase = NULL; /* directory in which all config files are */ -char *netname = NULL; /* name of the vpn network */ -list_t *cmdline_conf = NULL; /* global/host configuration values given at the command line */ +int pinginterval = 0; /* seconds between pings */ +int pingtimeout = 0; /* seconds to wait for response */ +char *confbase = NULL; /* directory in which all config files are */ +char *netname = NULL; /* name of the vpn network */ +list_t *cmdline_conf = NULL; /* global/host configuration values given at the command line */ static int config_compare(const config_t *a, const config_t *b) { @@ -141,7 +141,7 @@ bool get_config_bool(const config_t *cfg, bool *result) { return true; } - logger(LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "\"yes\" or \"no\" expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -154,7 +154,7 @@ bool get_config_int(const config_t *cfg, int *result) { if(sscanf(cfg->value, "%d", result) == 1) return true; - logger(LOG_ERR, "Integer expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Integer expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -182,7 +182,7 @@ bool get_config_address(const config_t *cfg, struct addrinfo **result) { return true; } - logger(LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Hostname or IP address expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; @@ -195,7 +195,7 @@ bool get_config_subnet(const config_t *cfg, subnet_t ** result) { return false; if(!str2net(&subnet, cfg->value)) { - logger(LOG_ERR, "Subnet expected for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Subnet expected for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; } @@ -206,7 +206,7 @@ bool get_config_subnet(const config_t *cfg, subnet_t ** result) { && !maskcheck(&subnet.net.ipv4.address, subnet.net.ipv4.prefixlength, sizeof subnet.net.ipv4.address)) || ((subnet.type == SUBNET_IPV6) && !maskcheck(&subnet.net.ipv6.address, subnet.net.ipv6.prefixlength, sizeof subnet.net.ipv6.address))) { - logger(LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Network address and prefix length do not match for configuration variable %s in %s line %d", cfg->variable, cfg->file, cfg->line); return false; } @@ -236,8 +236,9 @@ static char *readline(FILE * fp, char *buf, size_t buflen) { if(!newline) return buf; - *newline = '\0'; /* kill newline */ - if(newline > p && newline[-1] == '\r') /* and carriage return if necessary */ + /* kill newline and carriage return if necessary */ + *newline = '\0'; + if(newline > p && newline[-1] == '\r') newline[-1] = '\0'; return buf; @@ -265,10 +266,10 @@ config_t *parse_config_line(char *line, const char *fname, int lineno) { if(!*value) { const char err[] = "No value for variable"; if (fname) - logger(LOG_ERR, "%s `%s' on line %d while reading config file %s", + logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' on line %d while reading config file %s", err, variable, lineno, fname); else - logger(LOG_ERR, "%s `%s' in command line option %d", + logger(DEBUG_ALWAYS, LOG_ERR, "%s `%s' in command line option %d", err, variable, lineno); return NULL; } @@ -298,7 +299,7 @@ bool read_config_file(splay_tree_t *config_tree, const char *fname) { fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); return false; } @@ -321,7 +322,7 @@ bool read_config_file(splay_tree_t *config_tree, const char *fname) { ignore = false; continue; } - + if(!strncmp(line, "-----BEGIN", 10)) { ignore = true; continue; @@ -339,33 +340,31 @@ bool read_config_file(splay_tree_t *config_tree, const char *fname) { } void read_config_options(splay_tree_t *config_tree, const char *prefix) { - list_node_t *node, *next; size_t prefix_len = prefix ? strlen(prefix) : 0; - for(node = cmdline_conf->tail; node; node = next) { - config_t *orig_cfg, *cfg = (config_t *)node->data; - next = node->prev; + for(const list_node_t *node = cmdline_conf->tail; node; node = node->prev) { + const config_t *cfg = node->data; + config_t *new; if(!prefix) { if(strchr(cfg->variable, '.')) continue; - node->data = NULL; - list_unlink_node(cmdline_conf, node); } else { if(strncmp(prefix, cfg->variable, prefix_len) || cfg->variable[prefix_len] != '.') continue; - /* Because host configuration is parsed again when - reconnecting, nodes must not be freed when a prefix - is given. */ - orig_cfg = cfg; - cfg = new_config(); - cfg->variable = xstrdup(orig_cfg->variable + prefix_len + 1); - cfg->value = xstrdup(orig_cfg->value); - cfg->file = NULL; - cfg->line = orig_cfg->line; } - config_add(config_tree, cfg); + + new = new_config(); + if(prefix) + new->variable = xstrdup(cfg->variable + prefix_len + 1); + else + new->variable = xstrdup(cfg->variable); + new->value = xstrdup(cfg->value); + new->file = NULL; + new->line = cfg->line; + + config_add(config_tree, new); } } @@ -375,26 +374,25 @@ bool read_server_config(void) { read_config_options(config_tree, NULL); - xasprintf(&fname, "%s/tinc.conf", confbase); + xasprintf(&fname, "%s" SLASH "tinc.conf", confbase); x = read_config_file(config_tree, fname); - if(!x) { /* System error: complain */ - logger(LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno)); - } + if(!x) + logger(DEBUG_ALWAYS, LOG_ERR, "Failed to read `%s': %s", fname, strerror(errno)); free(fname); return x; } -bool read_connection_config(connection_t *c) { +bool read_host_config(splay_tree_t *config_tree, const char *name) { char *fname; bool x; - read_config_options(c->config_tree, c->name); + read_config_options(config_tree, name); - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); - x = read_config_file(c->config_tree, fname); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, name); + x = read_config_file(config_tree, fname); free(fname); return x; @@ -402,12 +400,12 @@ bool read_connection_config(connection_t *c) { bool append_config_file(const char *name, const char *key, const char *value) { char *fname; - xasprintf(&fname, "%s/hosts/%s", confbase, name); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, name); FILE *fp = fopen(fname, "a"); if(!fp) { - logger(LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot open config file %s: %s", fname, strerror(errno)); } else { fprintf(fp, "\n# The following line was automatically added by tinc\n%s = %s\n", key, value); fclose(fp); @@ -415,45 +413,5 @@ bool append_config_file(const char *name, const char *key, const char *value) { free(fname); - return fp; -} - -bool disable_old_keys(FILE *f) { - char buf[100]; - long pos; - bool disabled = false; - - rewind(f); - pos = ftell(f); - - if(pos < 0) - return false; - - while(fgets(buf, sizeof buf, f)) { - if(!strncmp(buf, "-----BEGIN RSA", 14)) { - buf[11] = 'O'; - buf[12] = 'L'; - buf[13] = 'D'; - if(fseek(f, pos, SEEK_SET)) - break; - if(fputs(buf, f) <= 0) - break; - disabled = true; - } - else if(!strncmp(buf, "-----END RSA", 12)) { - buf[ 9] = 'O'; - buf[10] = 'L'; - buf[11] = 'D'; - if(fseek(f, pos, SEEK_SET)) - break; - if(fputs(buf, f) <= 0) - break; - disabled = true; - } - pos = ftell(f); - if(pos < 0) - break; - } - - return disabled; + return fp != NULL; } diff --git a/src/conf.h b/src/conf.h index bd3850b..20a78a9 100644 --- a/src/conf.h +++ b/src/conf.h @@ -1,7 +1,7 @@ /* conf.h -- header for conf.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,10 +60,7 @@ extern config_t *parse_config_line(char *, const char *, int); extern bool read_config_file(splay_tree_t *, const char *); extern void read_config_options(splay_tree_t *, const char *); extern bool read_server_config(void); -extern bool read_connection_config(struct connection_t *); +extern bool read_host_config(splay_tree_t *, const char *); extern bool append_config_file(const char *, const char *, const char *); -extern FILE *ask_and_open(const char *, const char *, const char *); -extern bool is_safe_path(const char *); -extern bool disable_old_keys(FILE *); -#endif /* __TINC_CONF_H__ */ +#endif /* __TINC_CONF_H__ */ diff --git a/src/connection.c b/src/connection.c index bae86b9..d39f43f 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1,6 +1,6 @@ /* connection.c -- connection list management - Copyright (C) 2000-2009 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans 2008 Max Rijevski @@ -21,7 +21,7 @@ #include "system.h" -#include "splay_tree.h" +#include "list.h" #include "cipher.h" #include "conf.h" #include "control_common.h" @@ -31,23 +31,19 @@ #include "utils.h" #include "xalloc.h" -splay_tree_t *connection_tree; /* Meta connections */ -connection_t *broadcast; - -static int connection_compare(const connection_t *a, const connection_t *b) { - return a < b ? -1 : a == b ? 0 : 1; -} +list_t *connection_list; +connection_t *everyone; void init_connections(void) { - connection_tree = splay_alloc_tree((splay_compare_t) connection_compare, (splay_action_t) free_connection); - broadcast = new_connection(); - broadcast->name = xstrdup("everyone"); - broadcast->hostname = xstrdup("BROADCAST"); + connection_list = list_alloc((list_action_t) free_connection); + everyone = new_connection(); + everyone->name = xstrdup("everyone"); + everyone->hostname = xstrdup("BROADCAST"); } void exit_connections(void) { - splay_delete_tree(connection_tree); - free_connection(broadcast); + list_delete_list(connection_list); + free_connection(everyone); } connection_t *new_connection(void) { @@ -58,30 +54,20 @@ void free_connection(connection_t *c) { if(!c) return; - if(c->name) - free(c->name); - - if(c->hostname) - free(c->hostname); - cipher_close(&c->incipher); digest_close(&c->indigest); cipher_close(&c->outcipher); digest_close(&c->outdigest); - ecdh_free(&c->ecdh); + sptps_stop(&c->sptps); ecdsa_free(&c->ecdsa); rsa_free(&c->rsa); - if(c->hischallenge) - free(c->hischallenge); - - if(c->config_tree) - exit_configuration(&c->config_tree); + free(c->hischallenge); buffer_clear(&c->inbuf); buffer_clear(&c->outbuf); - + if(event_initialized(&c->inevent)) event_del(&c->inevent); @@ -91,24 +77,26 @@ void free_connection(connection_t *c) { if(c->socket > 0) closesocket(c->socket); + free(c->name); + free(c->hostname); + + if(c->config_tree) + exit_configuration(&c->config_tree); + free(c); } void connection_add(connection_t *c) { - splay_insert(connection_tree, c); + list_insert_tail(connection_list, c); } void connection_del(connection_t *c) { - splay_delete(connection_tree, c); + list_delete(connection_list, c); } bool dump_connections(connection_t *cdump) { - splay_node_t *node; - connection_t *c; - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - send_request(cdump, "%d %d %s at %s options %x socket %d status %04x", + for list_each(connection_t, c, connection_list) { + send_request(cdump, "%d %d %s %s %x %d %x", CONTROL, REQ_DUMP_CONNECTIONS, c->name, c->hostname, c->options, c->socket, bitfield_to_int(&c->status, sizeof c->status)); diff --git a/src/connection.h b/src/connection.h index 26aa3f0..01c2bf4 100644 --- a/src/connection.h +++ b/src/connection.h @@ -1,6 +1,6 @@ /* connection.h -- header for connection.c - Copyright (C) 2000-2010 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -25,81 +25,83 @@ #include "cipher.h" #include "digest.h" #include "rsa.h" -#include "splay_tree.h" +#include "list.h" +#include "sptps.h" -#define OPTION_INDIRECT 0x0001 -#define OPTION_TCPONLY 0x0002 -#define OPTION_PMTU_DISCOVERY 0x0004 -#define OPTION_CLAMP_MSS 0x0008 +#define OPTION_INDIRECT 0x0001 +#define OPTION_TCPONLY 0x0002 +#define OPTION_PMTU_DISCOVERY 0x0004 +#define OPTION_CLAMP_MSS 0x0008 +#define OPTION_VERSION(x) ((x) >> 24) /* Top 8 bits are for protocol minor version */ typedef struct connection_status_t { - unsigned int pinged:1; /* sent ping */ - unsigned int active:1; /* 1 if active.. */ - unsigned int connecting:1; /* 1 if we are waiting for a non-blocking connect() to finish */ - unsigned int termreq:1; /* the termination of this connection was requested */ - unsigned int remove_unused:1; /* Set to 1 if you want this connection removed */ - unsigned int timeout_unused:1; /* 1 if gotten timeout */ - unsigned int encryptout:1; /* 1 if we can encrypt outgoing traffic */ - unsigned int decryptin:1; /* 1 if we have to decrypt incoming traffic */ - unsigned int mst:1; /* 1 if this connection is part of a minimum spanning tree */ - unsigned int control:1; - unsigned int pcap:1; - unsigned int unused:21; + unsigned int pinged:1; /* sent ping */ + unsigned int active:1; /* 1 if active.. */ + unsigned int connecting:1; /* 1 if we are waiting for a non-blocking connect() to finish */ + unsigned int unused_termreq:1; /* the termination of this connection was requested */ + unsigned int remove_unused:1; /* Set to 1 if you want this connection removed */ + unsigned int timeout_unused:1; /* 1 if gotten timeout */ + unsigned int encryptout:1; /* 1 if we can encrypt outgoing traffic */ + unsigned int decryptin:1; /* 1 if we have to decrypt incoming traffic */ + unsigned int mst:1; /* 1 if this connection is part of a minimum spanning tree */ + unsigned int control:1; /* 1 if this is a control connection */ + unsigned int pcap:1; /* 1 if this is a control connection requesting packet capture */ + unsigned int log:1; /* 1 if this is a control connection requesting log dump */ + unsigned int unused:20; } connection_status_t; -#include "ecdh.h" #include "ecdsa.h" #include "edge.h" #include "net.h" #include "node.h" typedef struct connection_t { - char *name; /* name he claims to have */ + char *name; /* name he claims to have */ - union sockaddr_t address; /* his real (internet) ip */ - char *hostname; /* the hostname of its real ip */ - int protocol_major; /* used protocol */ - int protocol_minor; /* used protocol */ + union sockaddr_t address; /* his real (internet) ip */ + char *hostname; /* the hostname of its real ip */ + int protocol_major; /* used protocol */ + int protocol_minor; /* used protocol */ - int socket; /* socket used for this connection */ - uint32_t options; /* options for this connection */ - connection_status_t status; /* status info */ - int estimated_weight; /* estimation for the weight of the edge for this connection */ - struct timeval start; /* time this connection was started, used for above estimation */ - struct outgoing_t *outgoing; /* used to keep track of outgoing connections */ + int socket; /* socket used for this connection */ + uint32_t options; /* options for this connection */ + connection_status_t status; /* status info */ + int estimated_weight; /* estimation for the weight of the edge for this connection */ + struct timeval start; /* time this connection was started, used for above estimation */ + struct outgoing_t *outgoing; /* used to keep track of outgoing connections */ - struct node_t *node; /* node associated with the other end */ - struct edge_t *edge; /* edge associated with this connection */ + struct node_t *node; /* node associated with the other end */ + struct edge_t *edge; /* edge associated with this connection */ - rsa_t rsa; /* his public RSA key */ - ecdsa_t ecdsa; /* his public ECDSA key */ - ecdsa_t ecdh; /* state for ECDH key exchange */ - cipher_t incipher; /* Cipher he will use to send data to us */ - cipher_t outcipher; /* Cipher we will use to send data to him */ + rsa_t rsa; /* his public RSA key */ + ecdsa_t ecdsa; /* his public ECDSA key */ + cipher_t incipher; /* Cipher he will use to send data to us */ + cipher_t outcipher; /* Cipher we will use to send data to him */ digest_t indigest; digest_t outdigest; + sptps_t sptps; int inmaclength; int outmaclength; int incompression; int outcompression; - char *hischallenge; /* The challenge we sent to him */ + char *hischallenge; /* The challenge we sent to him */ struct buffer_t inbuf; struct buffer_t outbuf; - struct event inevent; /* input event on this metadata connection */ - struct event outevent; /* output event on this metadata connection */ - int tcplen; /* length of incoming TCPpacket */ - int allow_request; /* defined if there's only one request possible */ + struct event inevent; /* input event on this metadata connection */ + struct event outevent; /* output event on this metadata connection */ + int tcplen; /* length of incoming TCPpacket */ + int allow_request; /* defined if there's only one request possible */ - time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */ + time_t last_ping_time; /* last time we saw some activity from the other end or pinged them */ - splay_tree_t *config_tree; /* Pointer to configuration tree belonging to him */ + splay_tree_t *config_tree; /* Pointer to configuration tree belonging to him */ } connection_t; -extern splay_tree_t *connection_tree; -extern connection_t *broadcast; +extern list_t *connection_list; +extern connection_t *everyone; extern void init_connections(void); extern void exit_connections(void); @@ -109,4 +111,4 @@ extern void connection_add(connection_t *); extern void connection_del(connection_t *); extern bool dump_connections(struct connection_t *); -#endif /* __TINC_CONNECTION_H__ */ +#endif /* __TINC_CONNECTION_H__ */ diff --git a/src/control.c b/src/control.c index 86224c2..c166943 100644 --- a/src/control.c +++ b/src/control.c @@ -1,6 +1,6 @@ /* control.c -- Control socket handling. - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,7 +29,6 @@ #include "netutl.h" #include "protocol.h" #include "route.h" -#include "splay_tree.h" #include "utils.h" #include "xalloc.h" @@ -44,16 +43,16 @@ static bool control_ok(connection_t *c, int type) { return control_return(c, type, 0); } -bool control_h(connection_t *c, char *request) { +bool control_h(connection_t *c, const char *request) { int type; if(!c->status.control || c->allow_request != CONTROL) { - logger(LOG_ERR, "Unauthorized control request from %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Unauthorized control request from %s (%s)", c->name, c->hostname); return false; } if(sscanf(request, "%*d %d", &type) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "CONTROL", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CONTROL", c->name, c->hostname); return false; } @@ -64,7 +63,7 @@ bool control_h(connection_t *c, char *request) { case REQ_DUMP_NODES: return dump_nodes(c); - + case REQ_DUMP_EDGES: return dump_edges(c); @@ -93,22 +92,18 @@ bool control_h(connection_t *c, char *request) { return control_ok(c, REQ_RETRY); case REQ_RELOAD: - logger(LOG_NOTICE, "Got '%s' command", "reload"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got '%s' command", "reload"); int result = reload_configuration(); return control_return(c, REQ_RELOAD, result); case REQ_DISCONNECT: { char name[MAX_STRING_SIZE]; - connection_t *other; - splay_node_t *node, *next; bool found = false; if(sscanf(request, "%*d %*d " MAX_STRING, name) != 1) return control_return(c, REQ_DISCONNECT, -1); - for(node = connection_tree->head; node; node = next) { - next = node->next; - other = node->data; + for list_each(connection_t, other, connection_list) { if(strcmp(other->name, name)) continue; terminate_connection(other, other->status.active); @@ -122,10 +117,17 @@ bool control_h(connection_t *c, char *request) { return dump_traffic(c); case REQ_PCAP: + sscanf(request, "%*d %*d %d", &c->outmaclength); c->status.pcap = true; pcap = true; return true; + case REQ_LOG: + sscanf(request, "%*d %*d %d", &c->outcompression); + c->status.log = true; + logcontrol = true; + return true; + default: return send_request(c, "%d %d", CONTROL, REQ_INVALID); } @@ -137,7 +139,7 @@ bool init_control(void) { FILE *f = fopen(pidfilename, "w"); if(!f) { - logger(LOG_ERR, "Cannot write control socket cookie file %s: %s", pidfilename, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot write control socket cookie file %s: %s", pidfilename, strerror(errno)); return false; } diff --git a/src/control_common.h b/src/control_common.h index 615e10a..cd54889 100644 --- a/src/control_common.h +++ b/src/control_common.h @@ -1,6 +1,7 @@ /* control_protocol.h -- control socket protocol. - Copyright (C) 2007 Scott Lamb + Copyright (C) 2007 Scott Lamb + 2009-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +40,7 @@ enum request_type { REQ_DISCONNECT, REQ_DUMP_TRAFFIC, REQ_PCAP, + REQ_LOG, }; #define TINC_CTL_VERSION_CURRENT 0 diff --git a/src/crypto.c b/src/crypto.c new file mode 100644 index 0000000..c695be8 --- /dev/null +++ b/src/crypto.c @@ -0,0 +1,43 @@ +/* + crypto.c -- Cryptographic miscellaneous functions and initialisation + Copyright (C) 2007 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include +#include +#include + +#include "crypto.h" + +void crypto_init(void) { + RAND_load_file("/dev/urandom", 1024); + + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + + OpenSSL_add_all_algorithms(); +} + +void crypto_exit(void) { + EVP_cleanup(); +} + +void randomize(void *out, size_t outlen) { + RAND_pseudo_bytes(out, outlen); +} diff --git a/src/cygwin/device.c b/src/cygwin/device.c index a4ab938..6266e87 100644 --- a/src/cygwin/device.c +++ b/src/cygwin/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction with Windows tap driver in a Cygwin environment Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2009 Guus Sliepen + 2002-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -45,7 +45,7 @@ static uint64_t device_total_out = 0; static pid_t reader_pid; static int sp[2]; -bool setup_device(void) { +static bool setup_device(void) { HKEY key, key2; int i, err; @@ -64,7 +64,7 @@ bool setup_device(void) { /* Open registry and look for network adapters */ if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) { - logger(LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); return false; } @@ -77,7 +77,7 @@ bool setup_device(void) { snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid); - if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) continue; len = sizeof adaptername; @@ -116,7 +116,7 @@ bool setup_device(void) { RegCloseKey(key); if(!found) { - logger(LOG_ERR, "No Windows tap device found!"); + logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!"); return false; } @@ -127,22 +127,22 @@ bool setup_device(void) { iface = xstrdup(adaptername); snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device); - + /* Now we are going to open this device twice: once for reading and once for writing. We do this because apparently it isn't possible to check for activity in the select() loop. Furthermore I don't really know how to do it the "Windows" way. */ if(socketpair(AF_UNIX, SOCK_DGRAM, PF_UNIX, sp)) { - logger(LOG_DEBUG, "System call `%s' failed: %s", "socketpair", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "System call `%s' failed: %s", "socketpair", strerror(errno)); return false; } /* The parent opens the tap device for writing. */ - + device_handle = CreateFile(tapname, GENERIC_WRITE, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM , 0); - + if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "Could not open Windows tap device %s (%s) for writing: %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open Windows tap device %s (%s) for writing: %s", device, iface, winerror(GetLastError())); return false; } @@ -151,7 +151,7 @@ bool setup_device(void) { /* Get MAC address from tap device */ if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) { - logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); return false; } @@ -164,14 +164,14 @@ bool setup_device(void) { reader_pid = fork(); if(reader_pid == -1) { - logger(LOG_DEBUG, "System call `%s' failed: %s", "fork", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "System call `%s' failed: %s", "fork", strerror(errno)); return false; } if(!reader_pid) { /* The child opens the tap device for reading, blocking. It passes everything it reads to the socket. */ - + char buf[MTU]; long inlen; @@ -180,13 +180,13 @@ bool setup_device(void) { device_handle = CreateFile(tapname, GENERIC_READ, FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, 0); if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "Could not open Windows tap device %s (%s) for reading: %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open Windows tap device %s (%s) for reading: %s", device, iface, winerror(GetLastError())); buf[0] = 0; write(sp[1], buf, 1); exit(1); } - logger(LOG_DEBUG, "Tap reader forked and running."); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Tap reader forked and running."); /* Notify success */ @@ -203,18 +203,18 @@ bool setup_device(void) { read(device_fd, &gelukt, 1); if(gelukt != 1) { - logger(LOG_DEBUG, "Tap reader failed!"); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Tap reader failed!"); return false; } device_info = "Windows tap device"; - logger(LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); return true; } -void close_device(void) { +static void close_device(void) { close(sp[0]); close(sp[1]); CloseHandle(device_handle); @@ -225,33 +225,33 @@ void close_device(void) { free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; if((inlen = read(sp[0], packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } - + packet->len = inlen; device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } -bool write_packet(vpn_packet_t *packet) { +static bool write_packet(vpn_packet_t *packet) { long outlen; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); if(!WriteFile (device_handle, packet->data, packet->len, &outlen, NULL)) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); return false; } @@ -260,8 +260,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t os_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/device.h b/src/device.h index 16a2486..c91f035 100644 --- a/src/device.h +++ b/src/device.h @@ -1,7 +1,7 @@ /* - net.h -- generic header for device.c + device.h -- generic header for device.c Copyright (C) 2001-2005 Ivo Timmermans - 2001-2006 Guus Sliepen + 2001-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -32,10 +32,20 @@ extern uint64_t device_in_bytes; extern uint64_t device_out_packets; extern uint64_t device_out_bytes; -extern bool setup_device(void); -extern void close_device(void); -extern bool read_packet(struct vpn_packet_t *); -extern bool write_packet(struct vpn_packet_t *); -extern void dump_device_stats(void); +typedef struct devops_t { + bool (*setup)(void); + void (*close)(void); + bool (*read)(struct vpn_packet_t *); + bool (*write)(struct vpn_packet_t *); + void (*dump_stats)(void); +} devops_t; -#endif /* __TINC_DEVICE_H__ */ +extern const devops_t os_devops; +extern const devops_t dummy_devops; +extern const devops_t raw_socket_devops; +extern const devops_t multicast_devops; +extern const devops_t uml_devops; +extern const devops_t vde_devops; +extern devops_t devops; + +#endif /* __TINC_DEVICE_H__ */ diff --git a/src/digest.c b/src/digest.c new file mode 100644 index 0000000..79db491 --- /dev/null +++ b/src/digest.c @@ -0,0 +1,127 @@ +/* + digest.c -- Digest handling + Copyright (C) 2007-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include "utils.h" +#include "xalloc.h" + +#include +#include + +#include "digest.h" +#include "logger.h" + +static void set_maclength(digest_t *digest, int maclength) { + int digestlen = EVP_MD_size(digest->digest); + + if(maclength > digestlen || maclength < 0) + digest->maclength = digestlen; + else + digest->maclength = maclength; +} + +bool digest_open_by_name(digest_t *digest, const char *name, int maclength) { + digest->digest = EVP_get_digestbyname(name); + digest->key = NULL; + + if(!digest->digest) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest name '%s'!", name); + return false; + } + + set_maclength(digest, maclength); + return true; +} + +bool digest_open_by_nid(digest_t *digest, int nid, int maclength) { + digest->digest = EVP_get_digestbynid(nid); + digest->key = NULL; + + if(!digest->digest) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest nid %d!", nid); + return false; + } + + set_maclength(digest, maclength); + return true; +} + +bool digest_open_sha1(digest_t *digest, int maclength) { + digest->digest = EVP_sha1(); + digest->key = NULL; + + set_maclength(digest, maclength); + return true; +} + +bool digest_set_key(digest_t *digest, const void *key, size_t len) { + digest->key = xrealloc(digest->key, len); + memcpy(digest->key, key, len); + digest->keylength = len; + return true; +} + +void digest_close(digest_t *digest) { + free(digest->key); + digest->key = NULL; +} + +bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *outdata) { + size_t len = EVP_MD_size(digest->digest); + unsigned char tmpdata[len]; + + if(digest->key) { + HMAC(digest->digest, digest->key, digest->keylength, indata, inlen, tmpdata, NULL); + } else { + EVP_MD_CTX ctx; + + if(!EVP_DigestInit(&ctx, digest->digest) + || !EVP_DigestUpdate(&ctx, indata, inlen) + || !EVP_DigestFinal(&ctx, tmpdata, NULL)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Error creating digest: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + } + + memcpy(outdata, tmpdata, digest->maclength); + return true; +} + +bool digest_verify(digest_t *digest, const void *indata, size_t inlen, const void *cmpdata) { + size_t len = digest->maclength; + unsigned char outdata[len]; + + return digest_create(digest, indata, inlen, outdata) && !memcmp(cmpdata, outdata, digest->maclength); +} + +int digest_get_nid(const digest_t *digest) { + return digest->digest ? digest->digest->type : 0; +} + +size_t digest_keylength(const digest_t *digest) { + return digest->digest->md_size; +} + +size_t digest_length(const digest_t *digest) { + return digest->maclength; +} + +bool digest_active(const digest_t *digest) { + return digest->digest && digest->digest->type != 0; +} diff --git a/src/dropin.c b/src/dropin.c index eb17aca..f1a51ac 100644 --- a/src/dropin.c +++ b/src/dropin.c @@ -25,7 +25,7 @@ #ifndef HAVE_DAEMON /* Replacement for the daemon() function. - + The daemon() function is for programs wishing to detach themselves from the controlling terminal and run in the background as system daemons. @@ -104,14 +104,14 @@ char *get_current_dir_name(void) { size = 100; buf = xmalloc(size); - errno = 0; /* Success */ + errno = 0; /* Success */ r = getcwd(buf, size); /* getcwd returns NULL and sets errno to ERANGE if the bufferspace is insufficient to contain the entire working directory. */ while(r == NULL && errno == ERANGE) { free(buf); - size <<= 1; /* double the size */ + size <<= 1; /* double the size */ buf = xmalloc(size); r = getcwd(buf, size); } diff --git a/src/dropin.h b/src/dropin.h index 3617b70..c6cabba 100644 --- a/src/dropin.h +++ b/src/dropin.h @@ -45,4 +45,4 @@ extern int gettimeofday(struct timeval *, void *); extern int usleep(long long usec); #endif -#endif /* __DROPIN_H__ */ +#endif /* __DROPIN_H__ */ diff --git a/src/dummy_device.c b/src/dummy_device.c new file mode 100644 index 0000000..d508180 --- /dev/null +++ b/src/dummy_device.c @@ -0,0 +1,62 @@ +/* + device.c -- Dummy device + Copyright (C) 2011-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "device.h" +#include "logger.h" +#include "net.h" + +static char *device_info = "dummy device"; + +static uint64_t device_total_in = 0; +static uint64_t device_total_out = 0; + +static bool setup_device(void) { + device = "dummy"; + iface = "dummy"; + logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + return true; +} + +static void close_device(void) { +} + +static bool read_packet(vpn_packet_t *packet) { + return false; +} + +static bool write_packet(vpn_packet_t *packet) { + device_total_out += packet->len; + return true; +} + +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +} + +const devops_t dummy_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/ecdh.c b/src/ecdh.c new file mode 100644 index 0000000..f94555d --- /dev/null +++ b/src/ecdh.c @@ -0,0 +1,96 @@ +/* + ecdh.c -- Diffie-Hellman key exchange handling + Copyright (C) 2011-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" +#include "utils.h" +#include "xalloc.h" + +#include +#include +#include + +#include "ecdh.h" +#include "logger.h" + +bool ecdh_generate_public(ecdh_t *ecdh, void *pubkey) { + *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1); + if(!*ecdh) { + logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + if(!EC_KEY_generate_key(*ecdh)) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + const EC_POINT *point = EC_KEY_get0_public_key(*ecdh); + if(!point) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Getting public key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + size_t result = EC_POINT_point2oct(EC_KEY_get0_group(*ecdh), point, POINT_CONVERSION_COMPRESSED, pubkey, ECDH_SIZE, NULL); + if(!result) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Converting EC_POINT to binary failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { + EC_POINT *point = EC_POINT_new(EC_KEY_get0_group(*ecdh)); + if(!point) { + logger(DEBUG_ALWAYS, LOG_ERR, "EC_POINT_new() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + int result = EC_POINT_oct2point(EC_KEY_get0_group(*ecdh), point, pubkey, ECDH_SIZE, NULL); + if(!result) { + EC_POINT_free(point); + logger(DEBUG_ALWAYS, LOG_ERR, "Converting binary to EC_POINT failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + result = ECDH_compute_key(shared, ECDH_SIZE, point, *ecdh, NULL); + EC_POINT_free(point); + EC_KEY_free(*ecdh); + *ecdh = NULL; + + if(!result) { + logger(DEBUG_ALWAYS, LOG_ERR, "Computing Elliptic Curve Diffie-Hellman shared key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +void ecdh_free(ecdh_t *ecdh) { + if(*ecdh) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + } +} diff --git a/src/ecdsa.c b/src/ecdsa.c new file mode 100644 index 0000000..e2af6f9 --- /dev/null +++ b/src/ecdsa.c @@ -0,0 +1,130 @@ +/* + ecdsa.c -- ECDSA key handling + Copyright (C) 2011-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include +#include + +#include "logger.h" +#include "ecdsa.h" +#include "utils.h" + +// Get and set ECDSA keys +// +bool ecdsa_set_base64_public_key(ecdsa_t *ecdsa, const char *p) { + *ecdsa = EC_KEY_new_by_curve_name(NID_secp521r1); + if(!*ecdsa) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "EC_KEY_new_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + int len = strlen(p); + unsigned char pubkey[len / 4 * 3 + 3]; + const unsigned char *ppubkey = pubkey; + len = b64decode(p, (char *)pubkey, len); + + if(!o2i_ECPublicKey(ecdsa, &ppubkey, len)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "o2i_ECPublicKey failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) { + unsigned char *pubkey = NULL; + int len = i2o_ECPublicKey(*ecdsa, &pubkey); + + char *base64 = malloc(len * 4 / 3 + 5); + b64encode((char *)pubkey, base64, len); + + free(pubkey); + + return base64; +} + +// Read PEM ECDSA keys + +bool ecdsa_read_pem_public_key(ecdsa_t *ecdsa, FILE *fp) { + *ecdsa = PEM_read_EC_PUBKEY(fp, ecdsa, NULL, NULL); + + if(*ecdsa) + return true; + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +bool ecdsa_read_pem_private_key(ecdsa_t *ecdsa, FILE *fp) { + *ecdsa = PEM_read_ECPrivateKey(fp, NULL, NULL, NULL); + + if(*ecdsa) + return true; + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; +} + +size_t ecdsa_size(ecdsa_t *ecdsa) { + return ECDSA_size(*ecdsa); +} + +// TODO: standardise output format? + +bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t len, void *sig) { + unsigned int siglen = ECDSA_size(*ecdsa); + + unsigned char hash[SHA512_DIGEST_LENGTH]; + SHA512(in, len, hash); + + memset(sig, 0, siglen); + + if(!ECDSA_sign(0, hash, sizeof hash, sig, &siglen, *ecdsa)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_sign() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t len, const void *sig) { + unsigned int siglen = ECDSA_size(*ecdsa); + + unsigned char hash[SHA512_DIGEST_LENGTH]; + SHA512(in, len, hash); + + if(!ECDSA_verify(0, hash, sizeof hash, sig, siglen, *ecdsa)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_verify() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +bool ecdsa_active(ecdsa_t *ecdsa) { + return *ecdsa; +} + +void ecdsa_free(ecdsa_t *ecdsa) { + if(*ecdsa) { + EC_KEY_free(*ecdsa); + *ecdsa = NULL; + } +} diff --git a/src/edge.c b/src/edge.c index f5aa099..fd03327 100644 --- a/src/edge.c +++ b/src/edge.c @@ -1,6 +1,6 @@ /* edge.c -- edge tree management - Copyright (C) 2000-2006 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -29,7 +29,7 @@ #include "utils.h" #include "xalloc.h" -splay_tree_t *edge_weight_tree; /* Tree with all edges, sorted on weight */ +splay_tree_t *edge_weight_tree; static int edge_compare(const edge_t *a, const edge_t *b) { return strcmp(a->to->name, b->to->name); @@ -99,7 +99,7 @@ void edge_del(edge_t *e) { edge_t *lookup_edge(node_t *from, node_t *to) { edge_t v; - + v.from = from; v.to = to; @@ -107,17 +107,10 @@ edge_t *lookup_edge(node_t *from, node_t *to) { } bool dump_edges(connection_t *c) { - splay_node_t *node, *node2; - node_t *n; - edge_t *e; - char *address; - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - for(node2 = n->edge_tree->head; node2; node2 = node2->next) { - e = node2->data; - address = sockaddr2hostname(&e->address); - send_request(c, "%d %d %s to %s at %s options %x weight %d", + for splay_each(node_t, n, node_tree) { + for splay_each(edge_t, e, n->edge_tree) { + char *address = sockaddr2hostname(&e->address); + send_request(c, "%d %d %s %s %s %x %d", CONTROL, REQ_DUMP_EDGES, e->from->name, e->to->name, address, e->options, e->weight); diff --git a/src/edge.h b/src/edge.h index ea45f49..cbd9e5a 100644 --- a/src/edge.h +++ b/src/edge.h @@ -1,6 +1,6 @@ /* edge.h -- header for edge.c - Copyright (C) 2001-2006 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -31,14 +31,14 @@ typedef struct edge_t { struct node_t *to; sockaddr_t address; - uint32_t options; /* options turned on for this edge */ - int weight; /* weight of this edge */ + uint32_t options; /* options turned on for this edge */ + int weight; /* weight of this edge */ - struct connection_t *connection; /* connection associated with this edge, if available */ - struct edge_t *reverse; /* edge in the opposite direction, if available */ + struct connection_t *connection; /* connection associated with this edge, if available */ + struct edge_t *reverse; /* edge in the opposite direction, if available */ } edge_t; -extern splay_tree_t *edge_weight_tree; /* Tree with all known edges sorted on weight */ +extern splay_tree_t *edge_weight_tree; /* Tree with all known edges sorted on weight */ extern void init_edges(void); extern void exit_edges(void); @@ -51,4 +51,4 @@ extern void edge_del(edge_t *); extern edge_t *lookup_edge(struct node_t *, struct node_t *); extern bool dump_edges(struct connection_t *); -#endif /* __TINC_EDGE_H__ */ +#endif /* __TINC_EDGE_H__ */ diff --git a/src/ethernet.h b/src/ethernet.h index eef5f42..b759ab3 100644 --- a/src/ethernet.h +++ b/src/ethernet.h @@ -54,17 +54,17 @@ struct arphdr { uint16_t ar_hrd; uint16_t ar_pro; uint8_t ar_hln; - uint8_t ar_pln; - uint16_t ar_op; + uint8_t ar_pln; + uint16_t ar_op; } __attribute__ ((__packed__)); -#define ARPOP_REQUEST 1 -#define ARPOP_REPLY 2 -#define ARPOP_RREQUEST 3 -#define ARPOP_RREPLY 4 -#define ARPOP_InREQUEST 8 -#define ARPOP_InREPLY 9 -#define ARPOP_NAK 10 +#define ARPOP_REQUEST 1 +#define ARPOP_REPLY 2 +#define ARPOP_RREQUEST 3 +#define ARPOP_RREPLY 4 +#define ARPOP_InREQUEST 8 +#define ARPOP_InREPLY 9 +#define ARPOP_NAK 10 #endif #ifndef HAVE_STRUCT_ETHER_ARP diff --git a/src/fake-gai-errnos.h b/src/fake-gai-errnos.h index 4ffabb6..33913eb 100644 --- a/src/fake-gai-errnos.h +++ b/src/fake-gai-errnos.h @@ -7,13 +7,13 @@ /* for old netdb.h */ #ifndef EAI_NODATA -#define EAI_NODATA 1 +#define EAI_NODATA 1 #endif #ifndef EAI_MEMORY -#define EAI_MEMORY 2 +#define EAI_MEMORY 2 #endif #ifndef EAI_FAMILY -#define EAI_FAMILY 3 +#define EAI_FAMILY 3 #endif diff --git a/src/fake-getaddrinfo.c b/src/fake-getaddrinfo.c index df3d347..db50f73 100644 --- a/src/fake-getaddrinfo.c +++ b/src/fake-getaddrinfo.c @@ -29,7 +29,7 @@ char *gai_strerror(int ecode) { default: return "Unknown error"; } -} +} #endif /* !HAVE_GAI_STRERROR */ #if !HAVE_DECL_FREEADDRINFO @@ -49,14 +49,14 @@ static struct addrinfo *malloc_ai(uint16_t port, uint32_t addr) { struct addrinfo *ai; ai = xmalloc_and_zero(sizeof(struct addrinfo) + sizeof(struct sockaddr_in)); - + ai->ai_addr = (struct sockaddr *)(ai + 1); ai->ai_addrlen = sizeof(struct sockaddr_in); ai->ai_addr->sa_family = ai->ai_family = AF_INET; ((struct sockaddr_in *)(ai)->ai_addr)->sin_port = port; ((struct sockaddr_in *)(ai)->ai_addr)->sin_addr.s_addr = addr; - + return ai; } @@ -77,12 +77,12 @@ int getaddrinfo(const char *hostname, const char *servname, const struct addrinf *res = malloc_ai(port, htonl(0x00000000)); return 0; } - + if (!hostname) { *res = malloc_ai(port, htonl(0x7f000001)); return 0; } - + hp = gethostbyname(hostname); if(!hp || !hp->h_addr_list || !hp->h_addr_list[0]) diff --git a/src/fake-getaddrinfo.h b/src/fake-getaddrinfo.h index 5af7491..5809985 100644 --- a/src/fake-getaddrinfo.h +++ b/src/fake-getaddrinfo.h @@ -15,25 +15,24 @@ #endif #ifndef AI_NUMERICHOST -#define AI_NUMERICHOST 4 +#define AI_NUMERICHOST 4 #endif #ifndef HAVE_STRUCT_ADDRINFO struct addrinfo { - int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ - int ai_family; /* PF_xxx */ - int ai_socktype; /* SOCK_xxx */ - int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ - size_t ai_addrlen; /* length of ai_addr */ - char *ai_canonname; /* canonical name for hostname */ - struct sockaddr *ai_addr; /* binary address */ - struct addrinfo *ai_next; /* next structure in linked list */ + int ai_flags; /* AI_PASSIVE, AI_CANONNAME */ + int ai_family; /* PF_xxx */ + int ai_socktype; /* SOCK_xxx */ + int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ + size_t ai_addrlen; /* length of ai_addr */ + char *ai_canonname; /* canonical name for hostname */ + struct sockaddr *ai_addr; /* binary address */ + struct addrinfo *ai_next; /* next structure in linked list */ }; #endif /* !HAVE_STRUCT_ADDRINFO */ #if !HAVE_DECL_GETADDRINFO -int getaddrinfo(const char *hostname, const char *servname, - const struct addrinfo *hints, struct addrinfo **res); +int getaddrinfo(const char *hostname, const char *servname, const struct addrinfo *hints, struct addrinfo **res); #endif /* !HAVE_GETADDRINFO */ #if !HAVE_DECL_GAI_STRERROR diff --git a/src/fake-getnameinfo.c b/src/fake-getnameinfo.c index 1eba492..4a4d132 100644 --- a/src/fake-getnameinfo.c +++ b/src/fake-getnameinfo.c @@ -41,10 +41,10 @@ int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, size_t host } hp = gethostbyaddr((char *)&sin->sin_addr, sizeof(struct in_addr), AF_INET); - + if(!hp || !hp->h_name || !hp->h_name[0]) return EAI_NODATA; - + len = snprintf(host, hostlen, "%s", hp->h_name); if(len < 0 || len >= hostlen) return EAI_MEMORY; diff --git a/src/fake-getnameinfo.h b/src/fake-getnameinfo.h index 4389a8f..043ed97 100644 --- a/src/fake-getnameinfo.h +++ b/src/fake-getnameinfo.h @@ -2,8 +2,7 @@ #define _FAKE_GETNAMEINFO_H #if !HAVE_DECL_GETNAMEINFO -int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, - size_t hostlen, char *serv, size_t servlen, int flags); +int getnameinfo(const struct sockaddr *sa, size_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); #endif /* !HAVE_GETNAMEINFO */ #ifndef NI_MAXSERV diff --git a/src/gcrypt/cipher.c b/src/gcrypt/cipher.c index 6a2cc5a..e9b32cf 100644 --- a/src/gcrypt/cipher.c +++ b/src/gcrypt/cipher.c @@ -1,6 +1,6 @@ /* cipher.c -- Symmetric block cipher handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -97,12 +97,12 @@ static bool cipher_open(cipher_t *cipher, int algo, int mode) { gcry_error_t err; if(!ciphertonid(algo, mode, &cipher->nid)) { - logger(LOG_DEBUG, "Cipher %d mode %d has no corresponding nid!", algo, mode); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Cipher %d mode %d has no corresponding nid!", algo, mode); return false; } if((err = gcry_cipher_open(&cipher->handle, algo, mode, 0))) { - logger(LOG_DEBUG, "Unable to intialise cipher %d mode %d: %s", algo, mode, gcry_strerror(err)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unable to intialise cipher %d mode %d: %s", algo, mode, gcry_strerror(err)); return false; } @@ -118,7 +118,7 @@ bool cipher_open_by_name(cipher_t *cipher, const char *name) { int algo, mode; if(!nametocipher(name, &algo, &mode)) { - logger(LOG_DEBUG, "Unknown cipher name '%s'!", name); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown cipher name '%s'!", name); return false; } @@ -129,7 +129,7 @@ bool cipher_open_by_nid(cipher_t *cipher, int nid) { int algo, mode; if(!nidtocipher(nid, &algo, &mode)) { - logger(LOG_DEBUG, "Unknown cipher ID %d!", nid); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown cipher ID %d!", nid); return false; } @@ -199,7 +199,7 @@ bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou size_t reqlen = ((inlen + cipher->blklen) / cipher->blklen) * cipher->blklen; if(*outlen < reqlen) { - logger(LOG_ERR, "Error while encrypting: not enough room for padding"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: not enough room for padding"); return false; } @@ -212,18 +212,18 @@ bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou else pad[i] = padbyte; } - + if(oneshot) gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); if((err = gcry_cipher_encrypt(cipher->handle, outdata, *outlen, indata, inlen))) { - logger(LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); return false; } if(cipher->padding) { if((err = gcry_cipher_encrypt(cipher->handle, outdata + inlen, cipher->blklen, pad, cipher->blklen))) { - logger(LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", gcry_strerror(err)); return false; } @@ -241,7 +241,7 @@ bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou gcry_cipher_setiv(cipher->handle, cipher->key + cipher->keylen, cipher->blklen); if((err = gcry_cipher_decrypt(cipher->handle, outdata, *outlen, indata, inlen))) { - logger(LOG_ERR, "Error while decrypting: %s", gcry_strerror(err)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", gcry_strerror(err)); return false; } @@ -252,7 +252,7 @@ bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou uint8_t padbyte = ((uint8_t *)outdata)[inlen - 1]; if(padbyte == 0 || padbyte > cipher->blklen || padbyte > inlen) { - logger(LOG_ERR, "Error while decrypting: invalid padding"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: invalid padding"); return false; } @@ -260,7 +260,7 @@ bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou for(int i = inlen - 1; i >= origlen; i--) if(((uint8_t *)outdata)[i] != padbyte) { - logger(LOG_ERR, "Error while decrypting: invalid padding"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: invalid padding"); return false; } diff --git a/src/gcrypt/cipher.h b/src/gcrypt/cipher.h index 8e4a2eb..389bb11 100644 --- a/src/gcrypt/cipher.h +++ b/src/gcrypt/cipher.h @@ -1,6 +1,6 @@ /* cipher.h -- header file cipher.c - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2009 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/gcrypt/crypto.h b/src/gcrypt/crypto.h index 8047bfb..71df50c 100644 --- a/src/gcrypt/crypto.h +++ b/src/gcrypt/crypto.h @@ -1,6 +1,6 @@ /* crypto.h -- header for crypto.c - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2009 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/gcrypt/digest.c b/src/gcrypt/digest.c index 066600f..4229b0c 100644 --- a/src/gcrypt/digest.c +++ b/src/gcrypt/digest.c @@ -1,6 +1,6 @@ /* digest.c -- Digest handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -75,7 +75,7 @@ static bool digesttonid(int algo, int *nid) { static bool digest_open(digest_t *digest, int algo, int maclength) { if(!digesttonid(algo, &digest->nid)) { - logger(LOG_DEBUG, "Digest %d has no corresponding nid!", algo); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Digest %d has no corresponding nid!", algo); return false; } @@ -85,7 +85,7 @@ static bool digest_open(digest_t *digest, int algo, int maclength) { digest->maclength = len; else digest->maclength = maclength; - + digest->algo = algo; digest->hmac = NULL; @@ -96,7 +96,7 @@ bool digest_open_by_name(digest_t *digest, const char *name, int maclength) { int algo; if(!nametodigest(name, &algo)) { - logger(LOG_DEBUG, "Unknown digest name '%s'!", name); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest name '%s'!", name); return false; } @@ -107,7 +107,7 @@ bool digest_open_by_nid(digest_t *digest, int nid, int maclength) { int algo; if(!nidtodigest(nid, &algo)) { - logger(LOG_DEBUG, "Unknown digest ID %d!", nid); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest ID %d!", nid); return false; } diff --git a/src/gcrypt/digest.h b/src/gcrypt/digest.h index 1188496..9f0d7d8 100644 --- a/src/gcrypt/digest.h +++ b/src/gcrypt/digest.h @@ -1,6 +1,6 @@ /* digest.h -- header file digest.c - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2009 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/gcrypt/rsa.c b/src/gcrypt/rsa.c index ceb1638..751f7b6 100644 --- a/src/gcrypt/rsa.c +++ b/src/gcrypt/rsa.c @@ -1,6 +1,6 @@ /* rsa.c -- RSA key handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -148,7 +148,7 @@ static size_t ber_read_len(unsigned char **p, size_t *buflen) { return *(*p)++; } } - + static bool ber_read_sequence(unsigned char **p, size_t *buflen, size_t *result) { int tag = ber_read_id(p, buflen); @@ -173,7 +173,7 @@ static bool ber_read_mpi(unsigned char **p, size_t *buflen, gcry_mpi_t *mpi) { if(mpi) err = gcry_mpi_scan(mpi, GCRYMPI_FMT_USG, *p, len, NULL); - + *p += len; *buflen -= len; @@ -187,7 +187,7 @@ bool rsa_set_hex_public_key(rsa_t *rsa, char *n, char *e) { ?: gcry_mpi_scan(&rsa->e, GCRYMPI_FMT_HEX, e, 0, NULL); if(err) { - logger(LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); return false; } @@ -202,7 +202,7 @@ bool rsa_set_hex_private_key(rsa_t *rsa, char *n, char *e, char *d) { ?: gcry_mpi_scan(&rsa->d, GCRYMPI_FMT_HEX, d, 0, NULL); if(err) { - logger(LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading RSA public key: %s", gcry_strerror(errno)); return false; } @@ -216,7 +216,7 @@ bool rsa_read_pem_public_key(rsa_t *rsa, FILE *fp) { size_t derlen; if(!pem_decode(fp, "RSA PUBLIC KEY", derbuf, sizeof derbuf, &derlen)) { - logger(LOG_ERR, "Unable to read RSA public key: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA public key: %s", strerror(errno)); return NULL; } @@ -224,7 +224,7 @@ bool rsa_read_pem_public_key(rsa_t *rsa, FILE *fp) { || !ber_read_mpi(&derp, &derlen, &rsa->n) || !ber_read_mpi(&derp, &derlen, &rsa->e) || derlen) { - logger(LOG_ERR, "Error while decoding RSA public key"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decoding RSA public key"); return NULL; } @@ -236,7 +236,7 @@ bool rsa_read_pem_private_key(rsa_t *rsa, FILE *fp) { size_t derlen; if(!pem_decode(fp, "RSA PRIVATE KEY", derbuf, sizeof derbuf, &derlen)) { - logger(LOG_ERR, "Unable to read RSA private key: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA private key: %s", strerror(errno)); return NULL; } @@ -251,7 +251,7 @@ bool rsa_read_pem_private_key(rsa_t *rsa, FILE *fp) { || !ber_read_mpi(&derp, &derlen, NULL) || !ber_read_mpi(&derp, &derlen, NULL) // u || derlen) { - logger(LOG_ERR, "Error while decoding RSA private key"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decoding RSA private key"); return NULL; } @@ -267,7 +267,7 @@ size_t rsa_size(rsa_t *rsa) { */ // TODO: get rid of this macro, properly clean up gcry_ structures after use -#define check(foo) { gcry_error_t err = (foo); if(err) {logger(LOG_ERR, "gcrypt error %s/%s at %s:%d", gcry_strsource(err), gcry_strerror(err), __FILE__, __LINE__); return false; }} +#define check(foo) { gcry_error_t err = (foo); if(err) {logger(DEBUG_ALWAYS, LOG_ERR, "gcrypt error %s/%s at %s:%d", gcry_strsource(err), gcry_strerror(err), __FILE__, __LINE__); return false; }} bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) { gcry_mpi_t inmpi; diff --git a/src/gcrypt/rsagen.c b/src/gcrypt/rsagen.c index 7d37d19..36fb104 100644 --- a/src/gcrypt/rsagen.c +++ b/src/gcrypt/rsagen.c @@ -1,6 +1,6 @@ /* rsagen.c -- RSA key generation and export - Copyright (C) 2008 Guus Sliepen + Copyright (C) 2008-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -164,12 +164,12 @@ bool rsa_write_pem_public_key(rsa_t *rsa, FILE *fp) { if(!ber_write_mpi(&derp1, &derlen1, &rsa->n) || !ber_write_mpi(&derp1, &derlen1, &rsa->e) || !ber_write_sequence(&derp2, &derlen2, derbuf1, derlen1)) { - logger(LOG_ERR, "Error while encoding RSA public key"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encoding RSA public key"); return false; } if(!pem_encode(fp, "RSA PUBLIC KEY", derbuf2, derlen2)) { - logger(LOG_ERR, "Unable to write RSA public key: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to write RSA public key: %s", strerror(errno)); return false; } @@ -193,12 +193,12 @@ bool rsa_write_pem_private_key(rsa_t *rsa, FILE *fp) { || ber_write_mpi(&derp1, &derlen1, &exp1) || ber_write_mpi(&derp1, &derlen1, &exp2) || ber_write_mpi(&derp1, &derlen1, &coeff)) - logger(LOG_ERR, "Error while encoding RSA private key"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encoding RSA private key"); return false; } if(!pem_encode(fp, "RSA PRIVATE KEY", derbuf2, derlen2)) { - logger(LOG_ERR, "Unable to write RSA private key: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to write RSA private key: %s", strerror(errno)); return false; } diff --git a/src/graph.c b/src/graph.c index bb55dfd..9036c52 100644 --- a/src/graph.c +++ b/src/graph.c @@ -1,6 +1,6 @@ /* graph.c -- graph algorithms - Copyright (C) 2001-2011 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -44,12 +44,12 @@ #include "system.h" -#include "splay_tree.h" #include "config.h" #include "connection.h" #include "device.h" #include "edge.h" #include "graph.h" +#include "list.h" #include "logger.h" #include "netutl.h" #include "node.h" @@ -65,34 +65,22 @@ Please note that sorting on weight is already done by add_edge(). */ -void mst_kruskal(void) { - splay_node_t *node, *next; - edge_t *e; - node_t *n; - connection_t *c; - +static void mst_kruskal(void) { /* Clear MST status on connections */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + for list_each(connection_t, c, connection_list) c->status.mst = false; - } - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Kruskal's algorithm:"); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Running Kruskal's algorithm:"); /* Clear visited status on nodes */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; + for splay_each(node_t, n, node_tree) n->status.visited = false; - } /* Add safe edges */ - for(node = edge_weight_tree->head; node; node = next) { - next = node->next; - e = node->data; - + for splay_each(edge_t, e, edge_weight_tree) { if(!e->reverse || (e->from->status.visited && e->to->status.visited)) continue; @@ -105,31 +93,21 @@ void mst_kruskal(void) { if(e->reverse->connection) e->reverse->connection->status.mst = true; - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name, + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Adding edge %s - %s weight %d", e->from->name, e->to->name, e->weight); } } -/* Implementation of Dijkstra's algorithm. - Running time: O(N^2) +/* Implementation of a simple breadth-first search algorithm. + Running time: O(E) */ -static void sssp_dijkstra(void) { - splay_node_t *node, *to; - edge_t *e; - node_t *n, *m; - list_t *todo_list; - list_node_t *lnode, *nnode; - bool indirect; - - todo_list = list_alloc(NULL); - - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Running Dijkstra's algorithm:"); +static void sssp_bfs(void) { + list_t *todo_list = list_alloc(NULL); /* Clear visited status on nodes */ - for(node = node_tree->head; node; node = node->next) { - n = node->data; + for splay_each(node_t, n, node_tree) { n->status.visited = false; n->status.indirect = true; n->distance = -1; @@ -137,119 +115,23 @@ static void sssp_dijkstra(void) { /* Begin with myself */ + myself->status.visited = true; myself->status.indirect = false; myself->nexthop = myself; + myself->prevedge = NULL; myself->via = myself; myself->distance = 0; list_insert_head(todo_list, myself); /* Loop while todo_list is filled */ - while(todo_list->head) { - n = NULL; - nnode = NULL; + for list_each(node_t, n, todo_list) { /* "n" is the node from which we start */ + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, " Examining edges from %s", n->name); - /* Select node from todo_list with smallest distance */ - - for(lnode = todo_list->head; lnode; lnode = lnode->next) { - m = lnode->data; - if(!n || m->status.indirect < n->status.indirect || m->distance < n->distance) { - n = m; - nnode = lnode; - } - } - - /* Mark this node as visited and remove it from the todo_list */ - - n->status.visited = true; - list_unlink_node(todo_list, nnode); - - /* Update distance of neighbours and add them to the todo_list */ - - for(to = n->edge_tree->head; to; to = to->next) { /* "to" is the edge connected to "from" */ - e = to->data; - - if(e->to->status.visited || !e->reverse) - continue; - - /* Situation: - - / - / - ----->(n)---e-->(e->to) - \ - \ - - Where e is an edge, (n) and (e->to) are nodes. - n->address is set to the e->address of the edge left of n to n. - We are currently examining the edge e right of n from n: - - - If edge e provides for better reachability of e->to, update e->to. - */ - - if(e->to->distance < 0) - list_insert_tail(todo_list, e->to); - - indirect = n->status.indirect || e->options & OPTION_INDIRECT || ((n != myself) && sockaddrcmp(&n->address, &e->reverse->address)); - - if(e->to->distance >= 0 && (!e->to->status.indirect || indirect) && e->to->distance <= n->distance + e->weight) - continue; - - e->to->distance = n->distance + e->weight; - e->to->status.indirect = indirect; - e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop; - e->to->via = indirect ? n->via : e->to; - e->to->options = e->options; - - if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN) - update_node_udp(e->to, &e->address); - - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, " Updating edge %s - %s weight %d distance %d", e->from->name, - e->to->name, e->weight, e->to->distance); - } - } - - list_free(todo_list); -} - -/* Implementation of a simple breadth-first search algorithm. - Running time: O(E) -*/ - -void sssp_bfs(void) { - splay_node_t *node, *to; - edge_t *e; - node_t *n; - list_t *todo_list; - list_node_t *from, *todonext; - bool indirect; - - todo_list = list_alloc(NULL); - - /* Clear visited status on nodes */ - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - n->status.visited = false; - n->status.indirect = true; - } - - /* Begin with myself */ - - myself->status.visited = true; - myself->status.indirect = false; - myself->nexthop = myself; - myself->via = myself; - list_insert_head(todo_list, myself); - - /* Loop while todo_list is filled */ - - for(from = todo_list->head; from; from = todonext) { /* "from" is the node from which we start */ - n = from->data; - - for(to = n->edge_tree->head; to; to = to->next) { /* "to" is the edge connected to "from" */ - e = to->data; + if(n->distance < 0) + abort(); + for splay_each(edge_t, e, n->edge_tree) { /* "e" is the edge connected to "from" */ if(!e->reverse) continue; @@ -270,61 +152,63 @@ void sssp_bfs(void) { of nodes behind it. */ - indirect = n->status.indirect || e->options & OPTION_INDIRECT; + bool indirect = n->status.indirect || e->options & OPTION_INDIRECT; if(e->to->status.visited - && (!e->to->status.indirect || indirect)) + && (!e->to->status.indirect || indirect) + && (e->to->distance != n->distance + 1 || e->weight >= e->to->prevedge->weight)) continue; e->to->status.visited = true; e->to->status.indirect = indirect; e->to->nexthop = (n->nexthop == myself) ? e->to : n->nexthop; + e->to->prevedge = e; e->to->via = indirect ? n->via : e->to; e->to->options = e->options; + e->to->distance = n->distance + 1; - if(e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN) + if(!e->to->status.reachable || (e->to->address.sa.sa_family == AF_UNSPEC && e->address.sa.sa_family != AF_UNKNOWN)) update_node_udp(e->to, &e->address); list_insert_tail(todo_list, e->to); } - todonext = from->next; - list_delete_node(todo_list, from); + next = node->next; /* Because the list_insert_tail() above could have added something extra for us! */ + list_delete_node(todo_list, node); } list_free(todo_list); } static void check_reachability(void) { - splay_node_t *node, *next; - node_t *n; - char *name; - char *address, *port; - char *envp[7]; - int i; - /* Check reachability status. */ - for(node = node_tree->head; node; node = next) { - next = node->next; - n = node->data; - + for splay_each(node_t, n, node_tree) { if(n->status.visited != n->status.reachable) { n->status.reachable = !n->status.reachable; + n->last_state_change = time(NULL); if(n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became reachable", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became reachable", n->name, n->hostname); } else { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Node %s (%s) became unreachable", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Node %s (%s) became unreachable", n->name, n->hostname); } + if(experimental && OPTION_VERSION(n->options) >= 2) + n->status.sptps = true; + /* TODO: only clear status.validkey if node is unreachable? */ n->status.validkey = false; + if(n->status.sptps) { + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + } n->last_req_key = 0; + n->status.udp_confirmed = false; n->maxmtu = MTU; n->minmtu = 0; n->mtuprobes = 0; @@ -332,6 +216,11 @@ static void check_reachability(void) { if(timeout_initialized(&n->mtuevent)) event_del(&n->mtuevent); + char *name; + char *address; + char *port; + char *envp[7]; + xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); xasprintf(&envp[1], "DEVICE=%s", device ? : ""); xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); @@ -343,31 +232,37 @@ static void check_reachability(void) { execute_script(n->status.reachable ? "host-up" : "host-down", envp); - xasprintf(&name, - n->status.reachable ? "hosts/%s-up" : "hosts/%s-down", - n->name); + xasprintf(&name, n->status.reachable ? "hosts/%s-up" : "hosts/%s-down", n->name); execute_script(name, envp); free(name); free(address); free(port); - for(i = 0; i < 6; i++) + for(int i = 0; i < 6; i++) free(envp[i]); subnet_update(n, NULL, n->status.reachable); - if(!n->status.reachable) + if(!n->status.reachable) { update_node_udp(n, NULL); - else if(n->connection) - send_ans_key(n); + memset(&n->status, 0, sizeof n->status); + n->options = 0; + } else if(n->connection) { + if(n->status.sptps) { + if(n->connection->outgoing) + send_req_key(n); + } else { + send_ans_key(n); + } + } } } } void graph(void) { subnet_cache_flush(); - sssp_dijkstra(); + sssp_bfs(); check_reachability(); mst_kruskal(); } diff --git a/src/graph.h b/src/graph.h index fb41096..fa521f5 100644 --- a/src/graph.h +++ b/src/graph.h @@ -1,6 +1,6 @@ /* graph.h -- header for graph.c - Copyright (C) 2001-2006 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify diff --git a/src/hash.c b/src/hash.c new file mode 100644 index 0000000..cf5ba90 --- /dev/null +++ b/src/hash.c @@ -0,0 +1,105 @@ +/* + hash.c -- hash table management + Copyright (C) 2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "hash.h" +#include "xalloc.h" + +/* Generic hash function */ + +static uint32_t hash_function(const void *p, size_t len) { + const uint8_t *q = p; + uint32_t hash = 0; + while(true) { + for(int i = len > 4 ? 4 : len; --i;) + hash += q[i] << (8 * i); + hash *= 0x9e370001UL; // Golden ratio prime. + if(len <= 4) + break; + len -= 4; + } + return hash; +} + +/* Map 32 bits int onto 0..n-1, without throwing away too many bits if n is 2^8 or 2^16 */ + +static uint32_t modulo(uint32_t hash, size_t n) { + if(n == 0x100) + return (hash >> 24) ^ ((hash >> 16) & 0xff) ^ ((hash >> 8) & 0xff) ^ (hash & 0xff); + else if(n == 0x10000) + return (hash >> 16) ^ (hash & 0xffff); + else + return hash % n; +} + +/* (De)allocation */ + +hash_t *hash_alloc(size_t n, size_t size) { + hash_t *hash = xmalloc_and_zero(sizeof *hash); + hash->n = n; + hash->size = size; + hash->keys = xmalloc(hash->n * hash->size); + hash->values = xmalloc_and_zero(hash->n * sizeof *hash->values); + return hash; +} + +void hash_free(hash_t *hash) { + free(hash->keys); + free(hash->values); + free(hash); +} + +/* Searching and inserting */ + +void hash_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; +} + +void *hash_search(const hash_t *hash, const void *key) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) { + return (void *)hash->values[i]; + } + return NULL; +} + +void *hash_search_or_insert(hash_t *hash, const void *key, const void *value) { + uint32_t i = modulo(hash_function(key, hash->size), hash->n); + if(hash->values[i] && !memcmp(key, hash->keys + i * hash->size, hash->size)) + return (void *)hash->values[i]; + memcpy(hash->keys + i * hash->size, key, hash->size); + hash->values[i] = value; + return NULL; +} + +/* Utility functions */ + +void hash_clear(hash_t *hash) { + memset(hash->values, 0, hash->n * sizeof *hash->values); +} + +void hash_resize(hash_t *hash, size_t n) { + hash->keys = xrealloc(hash->keys, n * hash->size); + hash->values = xrealloc(hash->values, n * sizeof *hash->values); + if(n > hash->n) + memset(hash->values + hash->n, 0, (n - hash->n) * sizeof *hash->values); +} diff --git a/src/hash.h b/src/hash.h new file mode 100644 index 0000000..83ed6af --- /dev/null +++ b/src/hash.h @@ -0,0 +1,41 @@ +/* + hash.h -- header file for hash.c + Copyright (C) 2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __TINC_HASH_H__ +#define __TINC_HASH_H__ + +typedef struct hash_t { + size_t n; + size_t size; + char *keys; + const void **values; +} hash_t; + +extern hash_t *hash_alloc(size_t n, size_t size) __attribute__ ((__malloc__)); +extern void hash_free(hash_t *); + +extern void hash_insert(hash_t *, const void *key, const void *value); + +extern void *hash_search(const hash_t *, const void *key); +extern void *hash_search_or_insert(hash_t *, const void *key, const void *value); + +extern void hash_clear(hash_t *); +extern void hash_resize(hash_t *, size_t n); + +#endif /* __TINC_HASH_H__ */ diff --git a/src/info.c b/src/info.c new file mode 100644 index 0000000..25d51bf --- /dev/null +++ b/src/info.c @@ -0,0 +1,266 @@ +/* + info.c -- Show information about a node, subnet or address + Copyright (C) 2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "control_common.h" +#include "list.h" +#include "subnet.h" +#include "tincctl.h" +#include "info.h" +#include "xalloc.h" + +void logger(int level, int priority, const char *format, ...) { + va_list ap; + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +char *strip_weight(char *netstr) { + int len = strlen(netstr); + if(len >= 3 && !strcmp(netstr + len - 3, "#10")) + netstr[len - 3] = 0; + return netstr; +} + +static int info_node(int fd, const char *item) { + // Check the list of nodes + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_NODES, item); + + bool found = false; + char line[4096]; + + char node[4096]; + char from[4096]; + char to[4096]; + char subnet[4096]; + char host[4096]; + char port[4096]; + char via[4096]; + char nexthop[4096]; + int code, req, cipher, digest, maclength, compression, distance; + short int pmtu, minmtu, maxmtu; + unsigned int options; + node_status_t status; + long int last_state_change; + + while(recvline(fd, line, sizeof line)) { + int n = sscanf(line, "%d %d %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", &code, &req, node, host, port, &cipher, &digest, &maclength, &compression, &options, (unsigned *)&status, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change); + + if(n == 2) + break; + + if(n != 18) { + fprintf(stderr, "Unable to parse node dump from tincd.\n"); + return 1; + } + + if(!strcmp(node, item)) { + found = true; + break; + } + } + + if(!found) { + fprintf(stderr, "Unknown node %s.\n", item); + return 1; + } + + while(recvline(fd, line, sizeof line)) { + if(sscanf(line, "%d %d %s", &code, &req, node) == 2) + break; + } + + printf("Node: %s\n", item); + printf("Address: %s port %s\n", host, port); + + char timestr[32] = "never"; + if(last_state_change) + strftime(timestr, sizeof timestr, "%Y-%m-%d %H:%M:%S", localtime(&last_state_change)); + + if(status.reachable) + printf("Online since: %s\n", timestr); + else + printf("Last seen: %s\n", timestr); + + printf("Status: "); + if(status.validkey) + printf(" validkey"); + if(status.visited) + printf(" visited"); + if(status.reachable) + printf(" reachable"); + if(status.indirect) + printf(" indirect"); + if(status.sptps) + printf(" sptps"); + if(status.udp_confirmed) + printf(" udp_confirmed"); + printf("\n"); + + printf("Options: "); + if(options & OPTION_INDIRECT) + printf(" indirect"); + if(options & OPTION_TCPONLY) + printf(" tcponly"); + if(options & OPTION_PMTU_DISCOVERY) + printf(" pmtu_discovery"); + if(options & OPTION_CLAMP_MSS) + printf(" clamp_mss"); + printf("\n"); + printf("Protocol: %d.%d\n", PROT_MAJOR, OPTION_VERSION(options)); + printf("Reachability: "); + if(!strcmp(host, "MYSELF")) + printf("can reach itself\n"); + else if(!status.reachable) + printf("unreachable\n"); + else if(strcmp(via, item)) + printf("indirectly via %s\n", via); + else if(!status.validkey) + printf("unknown\n"); + else if(minmtu > 0) + printf("directly with UDP\nPMTU: %d\n", pmtu); + else if(!strcmp(nexthop, item)) + printf("directly with TCP\n"); + else + printf("none, forwarded via %s\n", nexthop); + + // List edges + printf("Edges: "); + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_EDGES, item); + while(recvline(fd, line, sizeof line)) { + int n = sscanf(line, "%d %d %s %s", &code, &req, from, to); + if(n == 2) + break; + if(n != 4) { + fprintf(stderr, "Unable to parse edge dump from tincd.\n%s\n", line); + return 1; + } + if(!strcmp(from, item)) + printf(" %s", to); + } + printf("\n"); + + // List subnets + printf("Subnets: "); + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item); + while(recvline(fd, line, sizeof line)) { + int n = sscanf(line, "%d %d %s %s", &code, &req, subnet, from); + if(n == 2) + break; + if(n != 4) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + if(!strcmp(from, item)) + printf(" %s", strip_weight(subnet)); + } + printf("\n"); + + return 0; +} + +static int info_subnet(int fd, const char *item) { + subnet_t subnet, find; + + if(!str2net(&find, item)) { + fprintf(stderr, "Could not parse subnet or address '%s'.\n", item); + return 1; + } + + bool address = !strchr(item, '/'); + bool weight = strchr(item, '#'); + bool found = false; + + char line[4096]; + char netstr[4096]; + char owner[4096]; + + int code, req; + + sendline(fd, "%d %d %s", CONTROL, REQ_DUMP_SUBNETS, item); + while(recvline(fd, line, sizeof line)) { + int n = sscanf(line, "%d %d %s %s", &code, &req, netstr, owner); + if(n == 2) + break; + + if(n != 4 || !str2net(&subnet, netstr)) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + + if(find.type != subnet.type) + continue; + + if(weight) { + if(find.weight != subnet.weight) + continue; + } + + if(find.type == SUBNET_IPV4) { + if(address) { + if(maskcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, subnet.net.ipv4.prefixlength)) + continue; + } else { + if(find.net.ipv4.prefixlength != subnet.net.ipv4.prefixlength) + continue; + if(memcmp(&find.net.ipv4.address, &subnet.net.ipv4.address, sizeof subnet.net.ipv4)) + continue; + } + } else if(find.type == SUBNET_IPV6) { + if(address) { + if(maskcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, subnet.net.ipv6.prefixlength)) + continue; + } else { + if(find.net.ipv6.prefixlength != subnet.net.ipv6.prefixlength) + continue; + if(memcmp(&find.net.ipv6.address, &subnet.net.ipv6.address, sizeof subnet.net.ipv6)) + continue; + } + } if(find.type == SUBNET_MAC) { + if(memcmp(&find.net.mac.address, &subnet.net.mac.address, sizeof subnet.net.mac)) + continue; + } + + found = true; + printf("Subnet: %s\n", strip_weight(netstr)); + printf("Owner: %s\n", owner); + } + + if(!found) { + if(address) + fprintf(stderr, "Unknown address %s.\n", item); + else + fprintf(stderr, "Unknown subnet %s.\n", item); + return 1; + } + + return 0; +} + +int info(int fd, const char *item) { + if(check_id(item)) + return info_node(fd, item); + if(strchr(item, '.') || strchr(item, ':')) + return info_subnet(fd, item); + + fprintf(stderr, "Argument is not a node name, subnet or address.\n"); + return 1; +} diff --git a/src/info.h b/src/info.h new file mode 100644 index 0000000..a3f387f --- /dev/null +++ b/src/info.h @@ -0,0 +1,27 @@ +/* + info.h -- header for info.c. + Copyright (C) 2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __TINC_INFO_H__ +#define __TINC_INFO_H__ + +extern int info(int fd, const char *item); +extern char *strip_weight(char *); + +#endif + diff --git a/src/ipv4.h b/src/ipv4.h index 940c239..6cb969b 100644 --- a/src/ipv4.h +++ b/src/ipv4.h @@ -1,7 +1,7 @@ /* ipv4.h -- missing IPv4 related definitions Copyright (C) 2005 Ivo Timmermans - 2006 Guus Sliepen + 2006-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,6 +41,14 @@ #define ICMP_NET_UNKNOWN 6 #endif +#ifndef ICMP_TIME_EXCEEDED +#define ICMP_TIME_EXCEEDED 11 +#endif + +#ifndef ICMP_EXC_TTL +#define ICMP_EXC_TTL 0 +#endif + #ifndef ICMP_NET_UNREACH #define ICMP_NET_UNREACH 0 #endif @@ -64,7 +72,7 @@ struct ip { #endif uint8_t ip_tos; uint16_t ip_len; - uint16_t ip_id; + uint16_t ip_id; uint16_t ip_off; #define IP_RF 0x8000 #define IP_DF 0x4000 diff --git a/src/ipv6.h b/src/ipv6.h index d98001d..37d999a 100644 --- a/src/ipv6.h +++ b/src/ipv6.h @@ -1,7 +1,7 @@ /* ipv6.h -- missing IPv6 related definitions Copyright (C) 2005 Ivo Timmermans - 2006 Guus Sliepen + 2006-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -54,9 +54,9 @@ struct sockaddr_in6 { #ifndef IN6_IS_ADDR_V4MAPPED #define IN6_IS_ADDR_V4MAPPED(a) \ - ((((__const uint32_t *) (a))[0] == 0) \ - && (((__const uint32_t *) (a))[1] == 0) \ - && (((__const uint32_t *) (a))[2] == htonl (0xffff))) + ((((__const uint32_t *) (a))[0] == 0) \ + && (((__const uint32_t *) (a))[1] == 0) \ + && (((__const uint32_t *) (a))[2] == htonl (0xffff))) #endif #ifndef HAVE_STRUCT_IP6_HDR @@ -95,8 +95,10 @@ struct icmp6_hdr { #define ICMP6_DST_UNREACH_NOROUTE 0 #define ICMP6_DST_UNREACH 1 #define ICMP6_PACKET_TOO_BIG 2 +#define ICMP6_TIME_EXCEEDED 3 #define ICMP6_DST_UNREACH_ADMIN 1 #define ICMP6_DST_UNREACH_ADDR 3 +#define ICMP6_TIME_EXCEED_TRANSIT 0 #define ND_NEIGHBOR_SOLICIT 135 #define ND_NEIGHBOR_ADVERT 136 #define icmp6_data32 icmp6_dataun.icmp6_un_data32 diff --git a/src/linux/device.c b/src/linux/device.c index d36f3f6..18f1b6e 100644 --- a/src/linux/device.c +++ b/src/linux/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction with Linux ethertap and tun/tap device Copyright (C) 2001-2005 Ivo Timmermans, - 2001-2009 Guus Sliepen + 2001-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,6 +41,7 @@ int device_fd = -1; static device_type_t device_type; char *device = NULL; char *iface = NULL; +static char *type = NULL; static char ifrname[IFNAMSIZ]; static char *device_info; @@ -49,27 +50,35 @@ uint64_t device_in_bytes = 0; uint64_t device_out_packets = 0; uint64_t device_out_bytes = 0; -bool setup_device(void) { +static bool setup_device(void) { if(!get_config_string(lookup_config(config_tree, "Device"), &device)) device = xstrdup(DEFAULT_DEVICE); if(!get_config_string(lookup_config(config_tree, "Interface"), &iface)) -#ifdef HAVE_LINUX_IF_TUN_H - if (netname != NULL) + if(netname) iface = xstrdup(netname); -#else - iface = xstrdup(strrchr(device, '/') ? strrchr(device, '/') + 1 : device); -#endif + device_fd = open(device, O_RDWR | O_NONBLOCK); if(device_fd < 0) { - logger(LOG_ERR, "Could not open %s: %s", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + struct ifreq ifr = {{{0}}}; - if(routing_mode == RMODE_ROUTER) { + get_config_string(lookup_config(config_tree, "DeviceType"), &type); + + if(type && strcasecmp(type, "tun") && strcasecmp(type, "tap")) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown device type %s!", type); + return false; + } + + if((type && !strcasecmp(type, "tun")) || (!type && routing_mode == RMODE_ROUTER)) { ifr.ifr_flags = IFF_TUN; device_type = DEVICE_TYPE_TUN; device_info = "Linux tun/tap device (tun mode)"; @@ -92,47 +101,44 @@ bool setup_device(void) { if(!ioctl(device_fd, TUNSETIFF, &ifr)) { strncpy(ifrname, ifr.ifr_name, IFNAMSIZ); - if(iface) free(iface); - iface = xstrdup(ifrname); - } else if(!ioctl(device_fd, (('T' << 8) | 202), &ifr)) { - logger(LOG_WARNING, "Old ioctl() request was needed for %s", device); - strncpy(ifrname, ifr.ifr_name, IFNAMSIZ); - if(iface) free(iface); + free(iface); iface = xstrdup(ifrname); } - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } -void close_device(void) { +static void close_device(void) { close(device_fd); + free(type); free(device); free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; - + switch(device_type) { case DEVICE_TYPE_TUN: inlen = read(device_fd, packet->data + 10, MTU - 10); if(inlen <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } + memset(packet->data, 0, 12); packet->len = inlen + 10; break; case DEVICE_TYPE_TAP: inlen = read(device_fd, packet->data, MTU); if(inlen <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -146,28 +152,28 @@ bool read_packet(vpn_packet_t *packet) { device_in_packets++; device_in_bytes += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } -bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); switch(device_type) { case DEVICE_TYPE_TUN: packet->data[10] = packet->data[11] = 0; if(write(device_fd, packet->data + 10, packet->len - 10) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } break; case DEVICE_TYPE_TAP: if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -182,8 +188,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_in_bytes); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_out_bytes); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_in_bytes); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_out_bytes); } + +const devops_t os_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/list.c b/src/list.c index 9b67791..2dd414a 100644 --- a/src/list.c +++ b/src/list.c @@ -1,7 +1,7 @@ /* list.c -- functions to deal with double linked lists Copyright (C) 2000-2005 Ivo Timmermans - 2000-2006 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,9 +26,7 @@ /* (De)constructors */ list_t *list_alloc(list_action_t delete) { - list_t *list; - - list = xmalloc_and_zero(sizeof(list_t)); + list_t *list = xmalloc_and_zero(sizeof(list_t)); list->delete = delete; return list; @@ -52,9 +50,7 @@ void list_free_node(list_t *list, list_node_t *node) { /* Insertion and deletion */ list_node_t *list_insert_head(list_t *list, void *data) { - list_node_t *node; - - node = list_alloc_node(); + list_node_t *node = list_alloc_node(); node->data = data; node->prev = NULL; @@ -72,9 +68,7 @@ list_node_t *list_insert_head(list_t *list, void *data) { } list_node_t *list_insert_tail(list_t *list, void *data) { - list_node_t *node; - - node = list_alloc_node(); + list_node_t *node = list_alloc_node(); node->data = data; node->next = NULL; @@ -92,9 +86,7 @@ list_node_t *list_insert_tail(list_t *list, void *data) { } list_node_t *list_insert_after(list_t *list, list_node_t *after, void *data) { - list_node_t *node; - - node = list_alloc_node(); + list_node_t *node = list_alloc_node(); node->data = data; node->next = after->next; @@ -158,6 +150,12 @@ void list_delete_tail(list_t *list) { list_delete_node(list, list->tail); } +void list_delete(list_t *list, const void *data) { + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) + if(node->data == data) + list_delete_node(list, node); +} + /* Head/tail lookup */ void *list_get_head(list_t *list) { @@ -177,12 +175,8 @@ void *list_get_tail(list_t *list) { /* Fast list deletion */ void list_delete_list(list_t *list) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) list_free_node(list, node); - } list_free(list); } @@ -190,20 +184,12 @@ void list_delete_list(list_t *list) { /* Traversing */ void list_foreach_node(list_t *list, list_action_node_t action) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) action(node); - } } void list_foreach(list_t *list, list_action_t action) { - list_node_t *node, *next; - - for(node = list->head; node; node = next) { - next = node->next; + for(list_node_t *node = list->head, *next; next = node ? node->next : NULL, node; node = next) if(node->data) action(node->data); - } } diff --git a/src/list.h b/src/list.h index 4fe48db..0437bd9 100644 --- a/src/list.h +++ b/src/list.h @@ -1,7 +1,7 @@ /* list.h -- header file for list.c Copyright (C) 2000-2005 Ivo Timmermans - 2000-2006 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -57,6 +57,8 @@ extern list_node_t *list_insert_tail(list_t *, void *); extern list_node_t *list_insert_after(list_t *, list_node_t *, void *); extern list_node_t *list_insert_before(list_t *, list_node_t *, void *); +extern void list_delete(list_t *, const void *); + extern void list_unlink_node(list_t *, list_node_t *); extern void list_delete_node(list_t *, list_node_t *); @@ -77,4 +79,6 @@ extern void list_delete_list(list_t *); extern void list_foreach(list_t *, list_action_t); extern void list_foreach_node(list_t *, list_action_node_t); -#endif /* __TINC_LIST_H__ */ +#define list_each(type, item, list) (type *item = (type *)1; item; item = NULL) for(list_node_t *node = (list)->head, *next; item = node ? node->data : NULL, next = node ? node->next : NULL, node; node = next) + +#endif /* __TINC_LIST_H__ */ diff --git a/src/logger.c b/src/logger.c index 08f9795..4527a37 100644 --- a/src/logger.c +++ b/src/logger.c @@ -1,6 +1,6 @@ /* logger.c -- logging code - Copyright (C) 2004-2006 Guus Sliepen + Copyright (C) 2004-2012 Guus Sliepen 2004-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,7 +21,11 @@ #include "system.h" #include "conf.h" +#include "meta.h" #include "logger.h" +#include "connection.h" +#include "control_common.h" +#include "sptps.h" debug_t debug_level = DEBUG_NOTHING; static logmode_t logmode = LOGMODE_STDERR; @@ -32,11 +36,94 @@ static FILE *logfile = NULL; static HANDLE loghandle = NULL; #endif static const char *logident = NULL; +bool logcontrol = false; + + +static void real_logger(int level, int priority, const char *message) { + char timestr[32] = ""; + time_t now; + static bool suppress = false; + + // Bail out early if there is nothing to do. + if(suppress) + return; + + if(!logcontrol && (level > debug_level || logmode == LOGMODE_NULL)) + return; + + if(level <= debug_level) { + switch(logmode) { + case LOGMODE_STDERR: + fprintf(stderr, "%s\n", message); + fflush(stderr); + break; + case LOGMODE_FILE: + now = time(NULL); + strftime(timestr, sizeof timestr, "%Y-%m-%d %H:%M:%S", localtime(&now)); + fprintf(logfile, "%s %s[%ld]: %s\n", timestr, logident, (long)logpid, message); + fflush(logfile); + break; + case LOGMODE_SYSLOG: +#ifdef HAVE_MINGW + { + const char *messages[] = {message}; + ReportEvent(loghandle, priority, 0, 0, NULL, 1, 0, messages, NULL); + } +#else +#ifdef HAVE_SYSLOG_H + syslog(priority, "%s", message); +#endif +#endif + break; + case LOGMODE_NULL: + break; + } + } + + if(logcontrol) { + suppress = true; + logcontrol = false; + for list_each(connection_t, c, connection_list) { + if(!c->status.log) + continue; + logcontrol = true; + if(level > (c->outcompression >= 0 ? c->outcompression : debug_level)) + continue; + int len = strlen(message); + if(send_request(c, "%d %d %d", CONTROL, REQ_LOG, len)) + send_meta(c, message, len); + } + suppress = false; + } +} + +void logger(int level, int priority, const char *format, ...) { + va_list ap; + char message[1024] = ""; + + va_start(ap, format); + int len = vsnprintf(message, sizeof message, format, ap); + va_end(ap); + + if(len > 0 && len < sizeof message && message[len - 1] == '\n') + message[len - 1] = 0; + + real_logger(level, priority, message); +} + +static void sptps_logger(sptps_t *s, int s_errno, const char *format, va_list ap) { + char message[1024] = ""; + int len = vsnprintf(message, sizeof message, format, ap); + if(len > 0 && len < sizeof message && message[len - 1] == '\n') + message[len - 1] = 0; + + real_logger(DEBUG_ALWAYS, LOG_ERR, message); +} void openlogger(const char *ident, logmode_t mode) { logident = ident; logmode = mode; - + switch(mode) { case LOGMODE_STDERR: logpid = getpid(); @@ -66,6 +153,11 @@ void openlogger(const char *ident, logmode_t mode) { case LOGMODE_NULL: break; } + + if(logmode != LOGMODE_NULL) + sptps_log = sptps_logger; + else + sptps_log = sptps_log_quiet; } void reopenlogger() { @@ -75,62 +167,13 @@ void reopenlogger() { fflush(logfile); FILE *newfile = fopen(logfilename, "a"); if(!newfile) { - logger(LOG_ERR, "Unable to reopen log file %s: %s\n", logfilename, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to reopen log file %s: %s", logfilename, strerror(errno)); return; } fclose(logfile); logfile = newfile; } -void logger(int priority, const char *format, ...) { - va_list ap; - char timestr[32] = ""; - time_t now; - - va_start(ap, format); - - switch(logmode) { - case LOGMODE_STDERR: - vfprintf(stderr, format, ap); - fprintf(stderr, "\n"); - fflush(stderr); - break; - case LOGMODE_FILE: - now = time(NULL); - strftime(timestr, sizeof timestr, "%Y-%m-%d %H:%M:%S", localtime(&now)); - fprintf(logfile, "%s %s[%ld]: ", timestr, logident, (long)logpid); - vfprintf(logfile, format, ap); - fprintf(logfile, "\n"); - fflush(logfile); - break; - case LOGMODE_SYSLOG: -#ifdef HAVE_MINGW - { - char message[4096]; - const char *messages[] = {message}; - vsnprintf(message, sizeof message, format, ap); - ReportEvent(loghandle, priority, 0, 0, NULL, 1, 0, messages, NULL); - } -#else -#ifdef HAVE_SYSLOG_H -#ifdef HAVE_VSYSLOG - vsyslog(priority, format, ap); -#else - { - char message[4096]; - vsnprintf(message, sizeof message, format, ap); - syslog(priority, "%s", message); - } -#endif - break; -#endif -#endif - case LOGMODE_NULL: - break; - } - - va_end(ap); -} void closelogger(void) { switch(logmode) { diff --git a/src/logger.h b/src/logger.h index ff2cb34..637eef8 100644 --- a/src/logger.h +++ b/src/logger.h @@ -1,17 +1,37 @@ +/* + logger.h -- header file for logger.c + Copyright (C) 1998-2005 Ivo Timmermans + 2000-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + #ifndef __TINC_LOGGER_H__ #define __TINC_LOGGER_H__ typedef enum debug_t { - DEBUG_NOTHING = 0, /* Quiet mode, only show starting/stopping of the daemon */ + DEBUG_NOTHING = 0, /* Quiet mode, only show starting/stopping of the daemon */ DEBUG_ALWAYS = 0, - DEBUG_CONNECTIONS = 1, /* Show (dis)connects of other tinc daemons via TCP */ - DEBUG_ERROR = 2, /* Show error messages received from other hosts */ - DEBUG_STATUS = 2, /* Show status messages received from other hosts */ - DEBUG_PROTOCOL = 3, /* Show the requests that are sent/received */ - DEBUG_META = 4, /* Show contents of every request that is sent/received */ - DEBUG_TRAFFIC = 5, /* Show network traffic information */ - DEBUG_PACKET = 6, /* Show contents of each packet that is being sent/received */ - DEBUG_SCARY_THINGS = 10 /* You have been warned */ + DEBUG_CONNECTIONS = 1, /* Show (dis)connects of other tinc daemons via TCP */ + DEBUG_ERROR = 2, /* Show error messages received from other hosts */ + DEBUG_STATUS = 2, /* Show status messages received from other hosts */ + DEBUG_PROTOCOL = 3, /* Show the requests that are sent/received */ + DEBUG_META = 4, /* Show contents of every request that is sent/received */ + DEBUG_TRAFFIC = 5, /* Show network traffic information */ + DEBUG_PACKET = 6, /* Show contents of each packet that is being sent/received */ + DEBUG_SCARY_THINGS = 10 /* You have been warned */ } debug_t; typedef enum logmode_t { @@ -46,11 +66,10 @@ enum { #endif extern debug_t debug_level; +extern bool logcontrol; extern void openlogger(const char *, logmode_t); extern void reopenlogger(void); -extern void logger(int, const char *, ...) __attribute__ ((__format__(printf, 2, 3))); +extern void logger(int, int, const char *, ...) __attribute__ ((__format__(printf, 3, 4))); extern void closelogger(void); -#define ifdebug(l) if(debug_level >= DEBUG_##l) - #endif /* __TINC_LOGGER_H__ */ diff --git a/src/meta.c b/src/meta.c index 29dd824..84fb196 100644 --- a/src/meta.c +++ b/src/meta.c @@ -1,6 +1,6 @@ /* meta.c -- handle the meta communication - Copyright (C) 2000-2009 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans 2006 Scott Lamb @@ -21,7 +21,6 @@ #include "system.h" -#include "splay_tree.h" #include "cipher.h" #include "connection.h" #include "logger.h" @@ -31,21 +30,38 @@ #include "utils.h" #include "xalloc.h" -bool send_meta(connection_t *c, const char *buffer, int length) { +bool send_meta_sptps(void *handle, uint8_t type, const char *buffer, size_t length) { + connection_t *c = handle; + if(!c) { - logger(LOG_ERR, "send_meta() called with NULL pointer!"); + logger(DEBUG_ALWAYS, LOG_ERR, "send_meta_sptps() called with NULL pointer!"); abort(); } - ifdebug(META) logger(LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length, + buffer_add(&c->outbuf, buffer, length); + event_add(&c->outevent, NULL); + + return true; +} + +bool send_meta(connection_t *c, const char *buffer, int length) { + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "send_meta() called with NULL pointer!"); + abort(); + } + + logger(DEBUG_META, LOG_DEBUG, "Sending %d bytes of metadata to %s (%s)", length, c->name, c->hostname); + if(c->protocol_minor >= 2) + return sptps_send_record(&c->sptps, 0, buffer, length); + /* Add our data to buffer */ if(c->status.encryptout) { size_t outlen = length; if(!cipher_encrypt(&c->outcipher, buffer, length, buffer_prepare(&c->outbuf, length), &outlen, false) || outlen != length) { - logger(LOG_ERR, "Error while encrypting metadata to %s (%s)", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting metadata to %s (%s)", c->name, c->hostname); return false; } @@ -60,15 +76,47 @@ bool send_meta(connection_t *c, const char *buffer, int length) { } void broadcast_meta(connection_t *from, const char *buffer, int length) { - splay_node_t *node; - connection_t *c; - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - + for list_each(connection_t, c, connection_list) if(c != from && c->status.active) send_meta(c, buffer, length); +} + +bool receive_meta_sptps(void *handle, uint8_t type, const char *data, uint16_t length) { + connection_t *c = handle; + + if(!c) { + logger(DEBUG_ALWAYS, LOG_ERR, "receive_meta_sptps() called with NULL pointer!"); + abort(); } + + if(type == SPTPS_HANDSHAKE) { + if(c->allow_request == ACK) + return send_ack(c); + else + return true; + } + + if(!data) + return true; + + /* Are we receiving a TCPpacket? */ + + if(c->tcplen) { + if(length != c->tcplen) + return false; + receive_tcppacket(c, data, length); + c->tcplen = 0; + return true; + } + + /* Change newline to null byte, just like non-SPTPS requests */ + + if(data[length - 1] == '\n') + ((char *)data)[length - 1] = 0; + + /* Otherwise we are waiting for a request */ + + return receive_request(c, data); } bool receive_meta(connection_t *c) { @@ -88,7 +136,7 @@ bool receive_meta(connection_t *c) { buffer_compact(&c->inbuf, MAXBUFSIZE); if(sizeof inbuf <= c->inbuf.len) { - logger(LOG_ERR, "Input buffer full for %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Input buffer full for %s (%s)", c->name, c->hostname); return false; } @@ -96,17 +144,20 @@ bool receive_meta(connection_t *c) { if(inlen <= 0) { if(!inlen || !errno) { - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection closed by %s (%s)", + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", c->name, c->hostname); } else if(sockwouldblock(sockerrno)) return true; else - logger(LOG_ERR, "Metadata socket read error for %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Metadata socket read error for %s (%s): %s", c->name, c->hostname, sockstrerror(sockerrno)); return false; } do { + if(c->protocol_minor >= 2) + return sptps_receive_data(&c->sptps, bufp, inlen); + if(!c->status.decryptin) { endp = memchr(bufp, '\n', inlen); if(endp) @@ -120,10 +171,9 @@ bool receive_meta(connection_t *c) { bufp = endp; } else { size_t outlen = inlen; - ifdebug(META) logger(LOG_DEBUG, "Received encrypted %d bytes", inlen); if(!cipher_decrypt(&c->incipher, bufp, inlen, buffer_prepare(&c->inbuf, inlen), &outlen, false) || inlen != outlen) { - logger(LOG_ERR, "Error while decrypting metadata from %s (%s)", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting metadata from %s (%s)", c->name, c->hostname); return false; } @@ -137,7 +187,15 @@ bool receive_meta(connection_t *c) { if(c->tcplen) { char *tcpbuffer = buffer_read(&c->inbuf, c->tcplen); if(tcpbuffer) { - receive_tcppacket(c, tcpbuffer, c->tcplen); + if(proxytype == PROXY_SOCKS4 && c->allow_request == ID) { + if(tcpbuffer[0] == 0 && tcpbuffer[1] == 0x5a) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Proxy request rejected"); + return false; + } + } else + receive_tcppacket(c, tcpbuffer, c->tcplen); c->tcplen = 0; continue; } else { diff --git a/src/meta.h b/src/meta.h index bc81a6a..2a71228 100644 --- a/src/meta.h +++ b/src/meta.h @@ -1,6 +1,6 @@ /* meta.h -- header for meta.c - Copyright (C) 2000-2006 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -24,7 +24,9 @@ #include "connection.h" extern bool send_meta(struct connection_t *, const char *, int); +extern bool send_meta_sptps(void *, uint8_t, const char *, size_t); +extern bool receive_meta_sptps(void *, uint8_t, const char *, uint16_t); extern void broadcast_meta(struct connection_t *, const char *, int); extern bool receive_meta(struct connection_t *); -#endif /* __TINC_META_H__ */ +#endif /* __TINC_META_H__ */ diff --git a/src/mingw/device.c b/src/mingw/device.c index bdca842..3b1458c 100644 --- a/src/mingw/device.c +++ b/src/mingw/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction with Windows tap driver in a MinGW environment Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2011 Guus Sliepen + 2002-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,7 +50,7 @@ static DWORD WINAPI tapreader(void *bla) { OVERLAPPED overlapped; vpn_packet_t packet; - logger(LOG_DEBUG, "Tap reader running"); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Tap reader running"); /* Read from tap device and send to parent */ @@ -69,7 +69,7 @@ static DWORD WINAPI tapreader(void *bla) { if(!GetOverlappedResult(device_handle, &overlapped, &len, FALSE)) continue; } else { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return -1; } @@ -83,7 +83,7 @@ static DWORD WINAPI tapreader(void *bla) { } } -bool setup_device(void) { +static bool setup_device(void) { HKEY key, key2; int i; @@ -105,7 +105,7 @@ bool setup_device(void) { /* Open registry and look for network adapters */ if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, NETWORK_CONNECTIONS_KEY, 0, KEY_READ, &key)) { - logger(LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read registry: %s", winerror(GetLastError())); return false; } @@ -118,7 +118,7 @@ bool setup_device(void) { snprintf(regpath, sizeof regpath, "%s\\%s\\Connection", NETWORK_CONNECTIONS_KEY, adapterid); - if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) + if(RegOpenKeyEx(HKEY_LOCAL_MACHINE, regpath, 0, KEY_READ, &key2)) continue; len = sizeof adaptername; @@ -156,7 +156,7 @@ bool setup_device(void) { RegCloseKey(key); if(!found) { - logger(LOG_ERR, "No Windows tap device found!"); + logger(DEBUG_ALWAYS, LOG_ERR, "No Windows tap device found!"); return false; } @@ -172,16 +172,16 @@ bool setup_device(void) { snprintf(tapname, sizeof tapname, USERMODEDEVICEDIR "%s" TAPSUFFIX, device); device_handle = CreateFile(tapname, GENERIC_WRITE | GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, 0); } - + if(device_handle == INVALID_HANDLE_VALUE) { - logger(LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "%s (%s) is not a usable Windows tap device: %s", device, iface, winerror(GetLastError())); return false; } /* Get MAC address from tap device */ if(!DeviceIoControl(device_handle, TAP_IOCTL_GET_MAC, mymac.x, sizeof mymac.x, mymac.x, sizeof mymac.x, &len, 0)) { - logger(LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get MAC address from Windows tap device %s (%s): %s", device, iface, winerror(GetLastError())); return false; } @@ -194,7 +194,7 @@ bool setup_device(void) { thread = CreateThread(NULL, 0, tapreader, NULL, 0, NULL); if(!thread) { - logger(LOG_ERR, "System call `%s' failed: %s", "CreateThread", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "CreateThread", winerror(GetLastError())); return false; } @@ -205,31 +205,31 @@ bool setup_device(void) { device_info = "Windows tap device"; - logger(LOG_INFO, "%s (%s) is a %s", device, iface, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s (%s) is a %s", device, iface, device_info); return true; } -void close_device(void) { +static void close_device(void) { CloseHandle(device_handle); free(device); free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { return false; } -bool write_packet(vpn_packet_t *packet) { +static bool write_packet(vpn_packet_t *packet) { long outlen; OVERLAPPED overlapped = {0}; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); if(!WriteFile(device_handle, packet->data, packet->len, &outlen, &overlapped)) { - logger(LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while writing to %s %s: %s", device_info, device, winerror(GetLastError())); return false; } @@ -238,8 +238,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t os_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/multicast_device.c b/src/multicast_device.c new file mode 100644 index 0000000..bd9ef1d --- /dev/null +++ b/src/multicast_device.c @@ -0,0 +1,235 @@ +/* + device.c -- multicast socket + Copyright (C) 2002-2005 Ivo Timmermans, + 2002-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "conf.h" +#include "device.h" +#include "net.h" +#include "logger.h" +#include "netutl.h" +#include "utils.h" +#include "route.h" +#include "xalloc.h" + +static char *device_info; + +static uint64_t device_total_in = 0; +static uint64_t device_total_out = 0; + +static struct addrinfo *ai = NULL; +static mac_t ignore_src = {{0}}; + +static bool setup_device(void) { + char *host = NULL; + char *port; + char *space; + int ttl = 1; + + device_info = "multicast socket"; + + get_config_string(lookup_config(config_tree, "Interface"), &iface); + + if(!get_config_string(lookup_config(config_tree, "Device"), &device)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Device variable required for %s", device_info); + goto error; + } + + host = xstrdup(device); + space = strchr(host, ' '); + if(!space) { + logger(DEBUG_ALWAYS, LOG_ERR, "Port number required for %s", device_info); + goto error; + } + + *space++ = 0; + port = space; + space = strchr(port, ' '); + + if(space) { + *space++ = 0; + ttl = atoi(space); + } + + ai = str2addrinfo(host, port, SOCK_DGRAM); + if(!ai) + goto error; + + device_fd = socket(ai->ai_family, SOCK_DGRAM, IPPROTO_UDP); + if(device_fd < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Creating socket failed: %s", sockstrerror(sockerrno)); + goto error; + } + +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + + static const int one = 1; + setsockopt(device_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one); + + if(bind(device_fd, ai->ai_addr, ai->ai_addrlen)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; + } + + switch(ai->ai_family) { +#ifdef IP_ADD_MEMBERSHIP + case AF_INET: { + struct ip_mreq mreq; + struct sockaddr_in in; + memcpy(&in, ai->ai_addr, sizeof in); + mreq.imr_multiaddr.s_addr = in.sin_addr.s_addr; + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + if(setsockopt(device_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&mreq, sizeof mreq)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; + } +#ifdef IP_MULTICAST_LOOP + setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_LOOP, (const void *)&one, sizeof one); +#endif +#ifdef IP_MULTICAST_TTL + setsockopt(device_fd, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl, sizeof ttl); +#endif + } break; +#endif + +#ifdef IPV6_JOIN_GROUP + case AF_INET6: { + struct ipv6_mreq mreq; + struct sockaddr_in6 in6; + memcpy(&in6, ai->ai_addr, sizeof in6); + memcpy(&mreq.ipv6mr_multiaddr, &in6.sin6_addr, sizeof mreq.ipv6mr_multiaddr); + mreq.ipv6mr_interface = in6.sin6_scope_id; + if(setsockopt(device_fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (void *)&mreq, sizeof mreq)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot join multicast group %s %s: %s", host, port, sockstrerror(sockerrno)); + goto error; + } +#ifdef IPV6_MULTICAST_LOOP + setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (const void *)&one, sizeof one); +#endif +#ifdef IPV6_MULTICAST_HOPS + setsockopt(device_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (void *)&ttl, sizeof ttl); +#endif + } break; +#endif + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Multicast for address family %hx unsupported", ai->ai_family); + goto error; + } + + freeaddrinfo(ai); + + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); + + return true; + +error: + if(device_fd >= 0) + closesocket(device_fd); + if(ai) + freeaddrinfo(ai); + free(host); + + return false; +} + +static void close_device(void) { + close(device_fd); + + free(device); + free(iface); + + if(ai) + freeaddrinfo(ai); +} + +static bool read_packet(vpn_packet_t *packet) { + int lenin; + + if((lenin = recv(device_fd, packet->data, MTU, 0)) <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, + device, strerror(errno)); + return false; + } + + if(!memcmp(&ignore_src, packet->data + 6, sizeof ignore_src)) { + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Ignoring loopback packet of %d bytes from %s", lenin, device_info); + packet->len = 0; + return true; + } + + packet->len = lenin; + + device_total_in += packet->len; + + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + device_info); + + return true; +} + +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", + packet->len, device_info); + + if(sendto(device_fd, packet->data, packet->len, 0, ai->ai_addr, ai->ai_addrlen) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, + strerror(errno)); + return false; + } + + device_total_out += packet->len; + + memcpy(&ignore_src, packet->data + 6, sizeof ignore_src); + + return true; +} + +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +} + +const devops_t multicast_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; + +#if 0 + +static bool not_supported(void) { + logger(DEBUG_ALWAYS, LOG_ERR, "Raw socket device not supported on this platform"); + return false; +} + +const devops_t multicast_devops = { + .setup = not_supported, + .close = NULL, + .read = NULL, + .write = NULL, + .dump_stats = NULL, +}; +#endif diff --git a/src/net.c b/src/net.c index f9020b3..f8ffbe3 100644 --- a/src/net.c +++ b/src/net.c @@ -1,9 +1,9 @@ /* net.c -- most of the network code Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen 2006 Scott Lamb - 2011 Loïc Grenié + 2011 Loïc Grenié This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,6 @@ #include "system.h" #include "utils.h" -#include "splay_tree.h" #include "conf.h" #include "connection.h" #include "device.h" @@ -40,40 +39,28 @@ int contradicting_add_edge = 0; int contradicting_del_edge = 0; static int sleeptime = 10; +time_t last_config_check = 0; /* Purge edges and subnets of unreachable nodes. Use carefully. */ void purge(void) { - splay_node_t *nnode, *nnext, *enode, *enext, *snode, *snext; - node_t *n; - edge_t *e; - subnet_t *s; - - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Purging unreachable nodes"); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Purging unreachable nodes"); /* Remove all edges and subnets owned by unreachable nodes. */ - for(nnode = node_tree->head; nnode; nnode = nnext) { - nnext = nnode->next; - n = nnode->data; - + for splay_each(node_t, n, node_tree) { if(!n->status.reachable) { - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Purging node %s (%s)", n->name, - n->hostname); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Purging node %s (%s)", n->name, n->hostname); - for(snode = n->subnet_tree->head; snode; snode = snext) { - snext = snode->next; - s = snode->data; - send_del_subnet(broadcast, s); + for splay_each(subnet_t, s, n->subnet_tree) { + send_del_subnet(everyone, s); if(!strictsubnets) subnet_del(n, s); } - for(enode = n->edge_tree->head; enode; enode = enext) { - enext = enode->next; - e = enode->data; + for splay_each(edge_t, e, n->edge_tree) { if(!tunnelserver) - send_del_edge(broadcast, e); + send_del_edge(everyone, e); edge_del(e); } } @@ -81,20 +68,13 @@ void purge(void) { /* Check if anyone else claims to have an edge to an unreachable node. If not, delete node. */ - for(nnode = node_tree->head; nnode; nnode = nnext) { - nnext = nnode->next; - n = nnode->data; - + for splay_each(node_t, n, node_tree) { if(!n->status.reachable) { - for(enode = edge_weight_tree->head; enode; enode = enext) { - enext = enode->next; - e = enode->data; - + for splay_each(edge_t, e, edge_weight_tree) if(e->to == n) - break; - } + return; - if(!enode && (!strictsubnets || !n->subnet_tree->head)) + if(!strictsubnets || !n->subnet_tree->head) /* in strictsubnets mode do not delete nodes with subnets */ node_del(n); } @@ -103,25 +83,25 @@ void purge(void) { /* Terminate a connection: - - Close the socket - - Remove associated edge and tell other connections about it if report = true + - Mark it as inactive + - Remove the edge representing this connection + - Kill it with fire - Check if we need to retry making an outgoing connection - - Deactivate the host */ void terminate_connection(connection_t *c, bool report) { - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Closing connection with %s (%s)", - c->name, c->hostname); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Closing connection with %s (%s)", c->name, c->hostname); c->status.active = false; - if(c->node) + if(c->node && c->node->connection == c) c->node->connection = NULL; if(c->edge) { if(report && !tunnelserver) - send_del_edge(broadcast, c->edge); + send_del_edge(everyone, c->edge); edge_del(c->edge); + c->edge = NULL; /* Run MST and SSSP algorithms */ @@ -134,18 +114,19 @@ void terminate_connection(connection_t *c, bool report) { e = lookup_edge(c->node, myself); if(e) { if(!tunnelserver) - send_del_edge(broadcast, e); + send_del_edge(everyone, e); edge_del(e); } } } + outgoing_t *outgoing = c->outgoing; + connection_del(c); + /* Check if this was our outgoing connection */ - if(c->outgoing) - retry_outgoing(c->outgoing); - - connection_del(c); + if(outgoing) + do_outgoing_connection(outgoing); } /* @@ -157,42 +138,34 @@ void terminate_connection(connection_t *c, bool report) { and close the connection. */ static void timeout_handler(int fd, short events, void *event) { - splay_node_t *node, *next; - connection_t *c; time_t now = time(NULL); - for(node = connection_tree->head; node; node = next) { - next = node->next; - c = node->data; + for list_each(connection_t, c, connection_list) { + if(c->status.control) + continue; if(c->last_ping_time + pingtimeout <= now) { if(c->status.active) { if(c->status.pinged) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds", - c->name, c->hostname, now - c->last_ping_time); - terminate_connection(c, true); - continue; + logger(DEBUG_CONNECTIONS, LOG_INFO, "%s (%s) didn't respond to PING in %ld seconds", c->name, c->hostname, (long)now - c->last_ping_time); } else if(c->last_ping_time + pinginterval <= now) { send_ping(c); - } - } else { - if(c->status.connecting) { - ifdebug(CONNECTIONS) - logger(LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname); - c->status.connecting = false; - closesocket(c->socket); - do_outgoing_connection(c); + continue; } else { - ifdebug(CONNECTIONS) logger(LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname); - terminate_connection(c, false); continue; } + } else { + if(c->status.connecting) + logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout while connecting to %s (%s)", c->name, c->hostname); + else + logger(DEBUG_CONNECTIONS, LOG_WARNING, "Timeout from %s (%s) during authentication", c->name, c->hostname); } + terminate_connection(c, c->status.active); } } if(contradicting_del_edge > 100 && contradicting_add_edge > 100) { - logger(LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime); + logger(DEBUG_ALWAYS, LOG_WARNING, "Possible node with same Name as us! Sleeping %d seconds.", sleeptime); usleep(sleeptime * 1000000LL); sleeptime *= 2; if(sleeptime < 0) @@ -222,11 +195,8 @@ void handle_meta_connection_data(int fd, short events, void *data) { if(!result) finish_connecting(c); else { - ifdebug(CONNECTIONS) logger(LOG_DEBUG, - "Error while connecting to %s (%s): %s", - c->name, c->hostname, sockstrerror(result)); - closesocket(c->socket); - do_outgoing_connection(c); + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Error while connecting to %s (%s): %s", c->name, c->hostname, sockstrerror(result)); + terminate_connection(c, false); return; } } @@ -238,27 +208,23 @@ void handle_meta_connection_data(int fd, short events, void *data) { } static void sigterm_handler(int signal, short events, void *data) { - logger(LOG_NOTICE, "Got %s signal", strsignal(signal)); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal)); event_loopexit(NULL); } static void sighup_handler(int signal, short events, void *data) { - logger(LOG_NOTICE, "Got %s signal", strsignal(signal)); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal)); reopenlogger(); reload_configuration(); } static void sigalrm_handler(int signal, short events, void *data) { - logger(LOG_NOTICE, "Got %s signal", strsignal(signal)); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s signal", strsignal(signal)); retry(); } int reload_configuration(void) { - connection_t *c; - splay_node_t *node, *next; char *fname; - struct stat s; - static time_t last_config_check = 0; /* Reread our own configuration file */ @@ -266,85 +232,112 @@ int reload_configuration(void) { init_configuration(&config_tree); if(!read_server_config()) { - logger(LOG_ERR, "Unable to reread configuration file, exitting."); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to reread configuration file, exitting."); event_loopexit(NULL); return EINVAL; } - /* Close connections to hosts that have a changed or deleted host config file */ - - for(node = connection_tree->head; node; node = next) { - c = node->data; - next = node->next; - - if(c->outgoing) { - free(c->outgoing->name); - if(c->outgoing->ai) - freeaddrinfo(c->outgoing->ai); - free(c->outgoing); - c->outgoing = NULL; - } - - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); - if(stat(fname, &s) || s.st_mtime > last_config_check) - terminate_connection(c, c->status.active); - free(fname); - } + read_config_options(config_tree, NULL); - last_config_check = time(NULL); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, myself->name); + read_config_file(config_tree, fname); + free(fname); + + /* Parse some options that are allowed to be changed while tinc is running */ + + setup_myself_reloadable(); /* If StrictSubnet is set, expire deleted Subnets and read new ones in */ if(strictsubnets) { - subnet_t *subnet; - - - for(node = subnet_tree->head; node; node = node->next) { - subnet = node->data; + for splay_each(subnet_t, subnet, subnet_tree) subnet->expires = 1; - } load_all_subnets(); - for(node = subnet_tree->head; node; node = next) { - next = node->next; - subnet = node->data; + for splay_each(subnet_t, subnet, subnet_tree) { if(subnet->expires == 1) { - send_del_subnet(broadcast, subnet); + send_del_subnet(everyone, subnet); if(subnet->owner->status.reachable) subnet_update(subnet->owner, subnet, false); subnet_del(subnet->owner, subnet); } else if(subnet->expires == -1) { subnet->expires = 0; } else { - send_add_subnet(broadcast, subnet); + send_add_subnet(everyone, subnet); if(subnet->owner->status.reachable) subnet_update(subnet->owner, subnet, true); } } + } else { /* Only read our own subnets back in */ + for splay_each(subnet_t, subnet, myself->subnet_tree) + if(!subnet->expires) + subnet->expires = 1; + + config_t *cfg = lookup_config(config_tree, "Subnet"); + + while(cfg) { + subnet_t *subnet, *s2; + + if(!get_config_subnet(cfg, &subnet)) + continue; + + if((s2 = lookup_subnet(myself, subnet))) { + if(s2->expires == 1) + s2->expires = 0; + + free_subnet(subnet); + } else { + subnet_add(myself, subnet); + send_add_subnet(everyone, subnet); + subnet_update(myself, subnet, true); + } + + cfg = lookup_config_next(config_tree, cfg); + } + + for splay_each(subnet_t, subnet, myself->subnet_tree) { + if(subnet->expires == 1) { + send_del_subnet(everyone, subnet); + subnet_update(myself, subnet, false); + subnet_del(myself, subnet); + } + } } /* Try to make outgoing connections */ - + try_outgoing_connections(); + /* Close connections to hosts that have a changed or deleted host config file */ + + for list_each(connection_t, c, connection_list) { + if(c->status.control) + continue; + + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); + struct stat s; + if(stat(fname, &s) || s.st_mtime > last_config_check) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Host config file of %s has been changed", c->name); + terminate_connection(c, c->status.active); + } + free(fname); + } + + last_config_check = time(NULL); + return 0; } void retry(void) { - connection_t *c; - splay_node_t *node; - - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - + for list_each(connection_t, c, connection_list) { if(c->outgoing && !c->node) { if(timeout_initialized(&c->outgoing->ev)) event_del(&c->outgoing->ev); if(c->status.connecting) close(c->socket); c->outgoing->timeout = 0; - do_outgoing_connection(c); + terminate_connection(c, c->status.active); } } } @@ -375,7 +368,7 @@ int main_loop(void) { #endif if(event_loop(0) < 0) { - logger(LOG_ERR, "Error while waiting for input: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while waiting for input: %s", strerror(errno)); return 1; } diff --git a/src/net.h b/src/net.h index c511a5f..23b8cae 100644 --- a/src/net.h +++ b/src/net.h @@ -1,7 +1,7 @@ /* net.h -- header for net.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,15 +26,18 @@ #include "digest.h" #ifdef ENABLE_JUMBOGRAMS -#define MTU 9018 /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ +#define MTU 9018 /* 9000 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ #else -#define MTU 1518 /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ +#define MTU 1518 /* 1500 bytes payload + 14 bytes ethernet header + 4 bytes VLAN tag */ #endif -#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20) /* MTU + seqno + padding + HMAC + compressor overhead */ -#define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128) /* Enough room for a request with a MAXSIZEd packet or a 8192 bits RSA key */ +/* MAXSIZE is the maximum size of an encapsulated packet: MTU + seqno + padding + HMAC + compressor overhead */ +#define MAXSIZE (MTU + 4 + CIPHER_MAX_BLOCK_SIZE + DIGEST_MAX_SIZE + MTU/64 + 20) -#define MAXSOCKETS 8 /* Probably overkill... */ +/* MAXBUFSIZE is the maximum size of a request: enough for a MAXSIZEd packet or a 8192 bits RSA key */ +#define MAXBUFSIZE ((MAXSIZE > 2048 ? MAXSIZE : 2048) + 128) + +#define MAXSOCKETS 8 /* Probably overkill... */ typedef struct mac_t { uint8_t x[6]; @@ -77,12 +80,24 @@ typedef union sockaddr_t { #endif typedef struct vpn_packet_t { - length_t len; /* the actual number of bytes in the `data' field */ - int priority; /* priority or TOS */ - uint32_t seqno; /* 32 bits sequence number (network byte order of course) */ + length_t len; /* the actual number of bytes in the `data' field */ + int priority; /* priority or TOS */ + uint32_t seqno; /* 32 bits sequence number (network byte order of course) */ uint8_t data[MAXSIZE]; } vpn_packet_t; +/* Packet types when using SPTPS */ + +#define PKT_COMPRESSED 1 +#define PKT_MAC 2 +#define PKT_PROBE 4 + +typedef enum packet_type_t { + PACKET_NORMAL, + PACKET_COMPRESSED, + PACKET_PROBE +} packet_type_t; + typedef struct listen_socket_t { struct event ev_tcp; struct event ev_udp; @@ -97,6 +112,7 @@ typedef struct listen_socket_t { typedef struct outgoing_t { char *name; int timeout; + splay_tree_t *config_tree; struct config_t *cfg; struct addrinfo *ai; struct addrinfo *aip; @@ -109,6 +125,7 @@ extern int maxoutbufsize; extern int seconds_till_retry; extern int addressfamily; extern unsigned replaywin; +extern bool localdiscovery; extern listen_socket_t listen_socket[MAXSOCKETS]; extern int listen_sockets; @@ -119,6 +136,24 @@ extern bool do_prune; extern char *myport; extern int contradicting_add_edge; extern int contradicting_del_edge; +extern time_t last_config_check; + +extern char *proxyhost; +extern char *proxyport; +extern char *proxyuser; +extern char *proxypass; +typedef enum proxytype_t { + PROXY_NONE = 0, + PROXY_SOCKS4, + PROXY_SOCKS4A, + PROXY_SOCKS5, + PROXY_HTTP, + PROXY_EXEC, +} proxytype_t; +extern proxytype_t proxytype; + +extern char *scriptinterpreter; +extern char *scriptextension; /* Yes, very strange placement indeed, but otherwise the typedefs get all tangled up */ #include "connection.h" @@ -127,20 +162,23 @@ extern int contradicting_del_edge; extern void retry_outgoing(outgoing_t *); extern void handle_incoming_vpn_data(int, short, void *); extern void finish_connecting(struct connection_t *); -extern bool do_outgoing_connection(struct connection_t *); +extern bool do_outgoing_connection(struct outgoing_t *); extern void handle_new_meta_connection(int, short, void *); extern int setup_listen_socket(const sockaddr_t *); extern int setup_vpn_in_socket(const sockaddr_t *); +extern bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len); +extern bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len); extern void send_packet(struct node_t *, vpn_packet_t *); extern void receive_tcppacket(struct connection_t *, const char *, int); extern void broadcast_packet(const struct node_t *, vpn_packet_t *); +extern char *get_name(void); +extern bool setup_myself_reloadable(void); extern bool setup_network(void); extern void setup_outgoing_connection(struct outgoing_t *); extern void try_outgoing_connections(void); extern void close_network_connections(void); extern int main_loop(void); extern void terminate_connection(struct connection_t *, bool); -extern void flush_queue(struct node_t *); extern bool node_read_ecdsa_public_key(struct node_t *); extern bool read_ecdsa_public_key(struct connection_t *); extern bool read_rsa_public_key(struct connection_t *); @@ -159,4 +197,4 @@ extern void load_all_subnets(void); extern CRITICAL_SECTION mutex; #endif -#endif /* __TINC_NET_H__ */ +#endif /* __TINC_NET_H__ */ diff --git a/src/net_packet.c b/src/net_packet.c index 3627f31..67ebc22 100644 --- a/src/net_packet.c +++ b/src/net_packet.c @@ -1,7 +1,7 @@ /* net_packet.c -- Handles in- and outgoing VPN packets Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen 2010 Timothy Redaelli 2010 Brandon Black @@ -36,7 +36,6 @@ #include LZO1X_H #endif -#include "splay_tree.h" #include "cipher.h" #include "conf.h" #include "connection.h" @@ -62,24 +61,30 @@ static char lzo_wrkmem[LZO1X_999_MEM_COMPRESS > LZO1X_1_MEM_COMPRESS ? LZO1X_999 static void send_udppacket(node_t *, vpn_packet_t *); unsigned replaywin = 16; +bool localdiscovery = false; #define MAX_SEQNO 1073741824 -// mtuprobes == 1..30: initial discovery, send bursts with 1 second interval -// mtuprobes == 31: sleep pinginterval seconds -// mtuprobes == 32: send 1 burst, sleep pingtimeout second -// mtuprobes == 33: no response from other side, restart PMTU discovery process +/* mtuprobes == 1..30: initial discovery, send bursts with 1 second interval + mtuprobes == 31: sleep pinginterval seconds + mtuprobes == 32: send 1 burst, sleep pingtimeout second + mtuprobes == 33: no response from other side, restart PMTU discovery process + + Probes are sent in batches of three, with random sizes between the lower and + upper boundaries for the MTU thus far discovered. + + In case local discovery is enabled, a fourth packet is added to each batch, + which will be broadcast to the local network. +*/ static void send_mtu_probe_handler(int fd, short events, void *data) { node_t *n = data; - vpn_packet_t packet; - int len, i; int timeout = 1; - + n->mtuprobes++; if(!n->status.reachable || !n->status.validkey) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send MTU probe to unreachable or rekeying node %s (%s)", n->name, n->hostname); n->mtuprobes = 0; return; } @@ -91,14 +96,15 @@ static void send_mtu_probe_handler(int fd, short events, void *data) { goto end; } - ifdebug(TRAFFIC) logger(LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "%s (%s) did not respond to UDP ping, restarting PMTU discovery", n->name, n->hostname); + n->status.udp_confirmed = false; n->mtuprobes = 1; n->minmtu = 0; n->maxmtu = MTU; } if(n->mtuprobes >= 10 && n->mtuprobes < 32 && !n->minmtu) { - ifdebug(TRAFFIC) logger(LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "No response to MTU probes from %s (%s)", n->name, n->hostname); n->mtuprobes = 31; } @@ -108,7 +114,7 @@ static void send_mtu_probe_handler(int fd, short events, void *data) { else n->maxmtu = n->minmtu; n->mtu = n->minmtu; - ifdebug(TRAFFIC) logger(LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); + logger(DEBUG_TRAFFIC, LOG_INFO, "Fixing MTU of %s (%s) to %d after %d probes", n->name, n->hostname, n->mtu, n->mtuprobes); n->mtuprobes = 31; } @@ -119,7 +125,9 @@ static void send_mtu_probe_handler(int fd, short events, void *data) { timeout = pingtimeout; } - for(i = 0; i < 3; i++) { + for(int i = 0; i < 3 + localdiscovery; i++) { + int len; + if(n->maxmtu <= n->minmtu) len = n->maxmtu; else @@ -127,13 +135,17 @@ static void send_mtu_probe_handler(int fd, short events, void *data) { if(len < 64) len = 64; - + + vpn_packet_t packet; memset(packet.data, 0, 14); randomize(packet.data + 14, len - 14); packet.len = len; - packet.priority = 0; + if(i >= 3 && n->mtuprobes <= 10) + packet.priority = -1; + else + packet.priority = 0; - ifdebug(TRAFFIC) logger(LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending MTU probe length %d to %s (%s)", len, n->name, n->hostname); send_udppacket(n, &packet); } @@ -149,12 +161,14 @@ void send_mtu_probe(node_t *n) { } static void mtu_probe_h(node_t *n, vpn_packet_t *packet, length_t len) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Got MTU probe length %d from %s (%s)", packet->len, n->name, n->hostname); if(!packet->data[0]) { packet->data[0] = 1; send_udppacket(n, packet); } else { + n->status.udp_confirmed = true; + if(n->mtuprobes > 30) { if(n->minmtu) n->mtuprobes = 30; @@ -198,7 +212,7 @@ static length_t compress_packet(uint8_t *dest, const uint8_t *source, length_t l return -1; #endif } - + return -1; } @@ -231,7 +245,7 @@ static length_t uncompress_packet(uint8_t *dest, const uint8_t *source, length_t /* VPN packet I/O */ static void receive_packet(node_t *n, vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Received packet of %d bytes from %s (%s)", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Received packet of %d bytes from %s (%s)", packet->len, n->name, n->hostname); n->in_packets++; @@ -241,6 +255,9 @@ static void receive_packet(node_t *n, vpn_packet_t *packet) { } static bool try_mac(node_t *n, const vpn_packet_t *inpkt) { + if(n->status.sptps) + return sptps_verify_datagram(&n->sptps, (char *)&inpkt->seqno, inpkt->len); + if(!digest_active(&n->indigest) || inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) return false; @@ -254,8 +271,13 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { vpn_packet_t *outpkt = pkt[0]; size_t outlen; + if(n->status.sptps) { + sptps_receive_data(&n->sptps, (char *)&inpkt->seqno, inpkt->len); + return; + } + if(!cipher_active(&n->incipher)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got packet from %s (%s) but he hasn't got our key yet", n->name, n->hostname); return; } @@ -263,7 +285,7 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { /* Check packet length */ if(inpkt->len < sizeof inpkt->seqno + digest_length(&n->indigest)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got too short packet from %s (%s)", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got too short packet from %s (%s)", n->name, n->hostname); return; } @@ -272,8 +294,8 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { if(digest_active(&n->indigest)) { inpkt->len -= n->indigest.maclength; - if(!digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, (const char *)&inpkt->seqno + inpkt->len)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Got unauthenticated packet from %s (%s)", n->name, n->hostname); + if(!digest_verify(&n->indigest, &inpkt->seqno, inpkt->len, (const char *)&inpkt->seqno + inpkt->len)) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Got unauthenticated packet from %s (%s)", n->name, n->hostname); return; } } @@ -284,10 +306,10 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { outlen = MAXSIZE; if(!cipher_decrypt(&n->incipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Error decrypting packet from %s (%s)", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Error decrypting packet from %s (%s)", n->name, n->hostname); return; } - + outpkt->len = outlen; inpkt = outpkt; } @@ -301,17 +323,17 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { if(inpkt->seqno != n->received_seqno + 1) { if(inpkt->seqno >= n->received_seqno + replaywin * 8) { if(n->farfuture++ < replaywin >> 2) { - logger(LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)", + logger(DEBUG_ALWAYS, LOG_WARNING, "Packet from %s (%s) is %d seqs in the future, dropped (%u)", n->name, n->hostname, inpkt->seqno - n->received_seqno - 1, n->farfuture); return; } - logger(LOG_WARNING, "Lost %d packets from %s (%s)", - inpkt->seqno - n->received_seqno - 1, n->name, n->hostname); + logger(DEBUG_ALWAYS, LOG_WARNING, "Lost %d packets from %s (%s)", + inpkt->seqno - n->received_seqno - 1, n->name, n->hostname); memset(n->late, 0, replaywin); } else if (inpkt->seqno <= n->received_seqno) { if((n->received_seqno >= replaywin * 8 && inpkt->seqno <= n->received_seqno - replaywin * 8) || !(n->late[(inpkt->seqno / 8) % replaywin] & (1 << inpkt->seqno % 8))) { - logger(LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d", - n->name, n->hostname, inpkt->seqno, n->received_seqno); + logger(DEBUG_ALWAYS, LOG_WARNING, "Got late or replayed packet from %s (%s), seqno %d, last received %d", + n->name, n->hostname, inpkt->seqno, n->received_seqno); return; } } else { @@ -326,7 +348,7 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { if(inpkt->seqno > n->received_seqno) n->received_seqno = inpkt->seqno; - + if(n->received_seqno > MAX_SEQNO) regenerate_key(); @@ -338,8 +360,8 @@ static void receive_udppacket(node_t *n, vpn_packet_t *inpkt) { outpkt = pkt[nextpkt++]; if((outpkt->len = uncompress_packet(outpkt->data, inpkt->data, inpkt->len, n->incompression)) < 0) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while uncompressing packet from %s (%s)", - n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while uncompressing packet from %s (%s)", + n->name, n->hostname); return; } @@ -369,6 +391,53 @@ void receive_tcppacket(connection_t *c, const char *buffer, int len) { receive_packet(c->node, &outpkt); } +static void send_sptps_packet(node_t *n, vpn_packet_t *origpkt) { + if(!n->status.validkey) { + logger(DEBUG_TRAFFIC, LOG_INFO, "No valid key known yet for %s (%s)", n->name, n->hostname); + if(!n->status.waitingforkey) + send_req_key(n); + else if(n->last_req_key + 10 < time(NULL)) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "No key from %s after 10 seconds, restarting SPTPS", n->name); + sptps_stop(&n->sptps); + n->status.waitingforkey = false; + send_req_key(n); + } + return; + } + + uint8_t type = 0; + int offset = 0; + + if(!(origpkt->data[12] | origpkt->data[13])) { + sptps_send_record(&n->sptps, PKT_PROBE, (char *)origpkt->data, origpkt->len); + return; + } + + if(routing_mode == RMODE_ROUTER) + offset = 14; + else + type = PKT_MAC; + + if(origpkt->len < offset) + return; + + vpn_packet_t outpkt; + + if(n->outcompression) { + int len = compress_packet(outpkt.data + offset, origpkt->data + offset, origpkt->len - offset, n->outcompression); + if(len < 0) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", n->name, n->hostname); + } else if(len < origpkt->len - offset) { + outpkt.len = len + offset; + origpkt = &outpkt; + type |= PKT_COMPRESSED; + } + } + + sptps_send_record(&n->sptps, type, (char *)origpkt->data + offset, origpkt->len - offset); + return; +} + static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { vpn_packet_t pkt1, pkt2; vpn_packet_t *pkt[] = { &pkt1, &pkt2, &pkt1, &pkt2 }; @@ -379,21 +448,23 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { size_t outlen; #if defined(SOL_IP) && defined(IP_TOS) static int priority = 0; - int origpriority = origpkt->priority; #endif - int sock; + int origpriority = origpkt->priority; if(!n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Trying to send UDP packet to unreachable node %s (%s)", n->name, n->hostname); return; } + if(n->status.sptps) + return send_sptps_packet(n, origpkt); + /* Make sure we have a valid key */ if(!n->status.validkey) { time_t now = time(NULL); - ifdebug(TRAFFIC) logger(LOG_INFO, + logger(DEBUG_TRAFFIC, LOG_INFO, "No valid key known yet for %s (%s), forwarding via TCP", n->name, n->hostname); @@ -408,7 +479,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { } if(n->options & OPTION_PMTU_DISCOVERY && inpkt->len > n->minmtu && (inpkt->data[12] | inpkt->data[13])) { - ifdebug(TRAFFIC) logger(LOG_INFO, + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) larger than minimum MTU, forwarding via %s", n->name, n->hostname, n != n->nexthop ? n->nexthop->name : "TCP"); @@ -426,7 +497,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { outpkt = pkt[nextpkt++]; if((outpkt->len = compress_packet(outpkt->data, inpkt->data, inpkt->len, n->outcompression)) < 0) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while compressing packet to %s (%s)", + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while compressing packet to %s (%s)", n->name, n->hostname); return; } @@ -446,7 +517,7 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { outlen = MAXSIZE; if(!cipher_encrypt(&n->outcipher, &inpkt->seqno, inpkt->len, &outpkt->seqno, &outlen, true)) { - ifdebug(TRAFFIC) logger(LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname); + logger(DEBUG_TRAFFIC, LOG_ERR, "Error while encrypting packet to %s (%s)", n->name, n->hostname); goto end; } @@ -461,41 +532,221 @@ static void send_udppacket(node_t *n, vpn_packet_t *origpkt) { inpkt->len += digest_length(&n->outdigest); } + /* Send the packet */ + + sockaddr_t *sa; + int sock; + + /* Overloaded use of priority field: -1 means local broadcast */ + + if(origpriority == -1 && n->prevedge) { + sockaddr_t broadcast; + broadcast.in.sin_family = AF_INET; + broadcast.in.sin_addr.s_addr = -1; + broadcast.in.sin_port = n->prevedge->address.in.sin_port; + sa = &broadcast; + sock = 0; + } else { + if(origpriority == -1) + origpriority = 0; + + if(n->status.udp_confirmed) { + /* Address of this node is confirmed, so use it. */ + sa = &n->address; + sock = n->sock; + } else { + /* Otherwise, go through the list of known addresses of + this node. The first address we try is always the + one in n->address; that could be set to the node's + reflexive UDP address discovered during key + exchange. The other known addresses are those found + in edges to this node. */ + + static unsigned int i; + int j = 0; + edge_t *candidate = NULL; + + if(i) { + for splay_each(edge_t, e, edge_weight_tree) { + if(e->to != n) + continue; + j++; + if(!candidate || j == i) + candidate = e; + } + } + + if(!candidate) { + sa = &n->address; + sock = n->sock; + } else { + sa = &candidate->address; + sock = rand() % listen_sockets; + } + + if(i++) + if(i > j) + i = 0; + } + } + /* Determine which socket we have to use */ - for(sock = 0; sock < listen_sockets; sock++) - if(n->address.sa.sa_family == listen_socket[sock].sa.sa.sa_family) - break; + if(sa->sa.sa_family != listen_socket[sock].sa.sa.sa_family) + for(sock = 0; sock < listen_sockets; sock++) + if(sa->sa.sa_family == listen_socket[sock].sa.sa.sa_family) + break; if(sock >= listen_sockets) - sock = 0; /* If none is available, just use the first and hope for the best. */ + sock = 0; - /* Send the packet */ + if(!n->status.udp_confirmed) + n->sock = sock; #if defined(SOL_IP) && defined(IP_TOS) if(priorityinheritance && origpriority != priority - && listen_socket[sock].sa.sa.sa_family == AF_INET) { + && listen_socket[n->sock].sa.sa.sa_family == AF_INET) { priority = origpriority; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Setting outgoing packet priority to %d", priority); - if(setsockopt(listen_socket[sock].udp, SOL_IP, IP_TOS, &priority, sizeof priority)) /* SO_PRIORITY doesn't seem to work */ - logger(LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno)); + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Setting outgoing packet priority to %d", priority); + if(setsockopt(listen_socket[n->sock].udp, SOL_IP, IP_TOS, &priority, sizeof(priority))) /* SO_PRIORITY doesn't seem to work */ + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setsockopt", strerror(errno)); } #endif - if(sendto(listen_socket[sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, &(n->address.sa), SALEN(n->address.sa)) < 0 && !sockwouldblock(sockerrno)) { + socklen_t sl = SALEN(n->address.sa); + + if(sendto(listen_socket[sock].udp, (char *) &inpkt->seqno, inpkt->len, 0, &sa->sa, sl) < 0 && !sockwouldblock(sockerrno)) { if(sockmsgsize(sockerrno)) { if(n->maxmtu >= origlen) n->maxmtu = origlen - 1; if(n->mtu >= origlen) n->mtu = origlen - 1; } else - logger(LOG_ERR, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno)); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending packet to %s (%s): %s", n->name, n->hostname, sockstrerror(sockerrno)); } end: origpkt->len = origlen; } +bool send_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { + node_t *to = handle; + + /* Send it via TCP if it is a handshake packet, TCPOnly is in use, or this packet is larger than the MTU. */ + + if(type >= SPTPS_HANDSHAKE || ((myself->options | to->options) & OPTION_TCPONLY) || (type != PKT_PROBE && len > to->minmtu)) { + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + /* If no valid key is known yet, send the packets using ANS_KEY requests, + to ensure we get to learn the reflexive UDP address. */ + if(!to->status.validkey) + return send_request(to->nexthop->connection, "%d %s %s %s -1 -1 -1 %d", ANS_KEY, myself->name, to->name, buf, myself->incompression); + else + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_SPTPS, buf); + } + + /* Otherwise, send the packet via UDP */ + + struct sockaddr *sa; + socklen_t sl; + int sock; + + sa = &(to->address.sa); + sl = SALEN(to->address.sa); + sock = to->sock; + + if(sendto(listen_socket[sock].udp, data, len, 0, sa, sl) < 0 && !sockwouldblock(sockerrno)) { + if(sockmsgsize(sockerrno)) { + if(to->maxmtu >= len) + to->maxmtu = len - 1; + if(to->mtu >= len) + to->mtu = len - 1; + } else { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Error sending UDP SPTPS packet to %s (%s): %s", to->name, to->hostname, sockstrerror(sockerrno)); + return false; + } + } + + return true; +} + +bool receive_sptps_record(void *handle, uint8_t type, const char *data, uint16_t len) { + node_t *from = handle; + + if(type == SPTPS_HANDSHAKE) { + if(!from->status.validkey) { + from->status.validkey = true; + from->status.waitingforkey = false; + logger(DEBUG_META, LOG_INFO, "SPTPS key exchange with %s (%s) succesful", from->name, from->hostname); + } + return true; + } + + if(len > MTU) { + logger(DEBUG_ALWAYS, LOG_ERR, "Packet from %s (%s) larger than maximum supported size (%d > %d)", from->name, from->hostname, len, MTU); + return false; + } + + vpn_packet_t inpkt; + + if(type == PKT_PROBE) { + inpkt.len = len; + memcpy(inpkt.data, data, len); + mtu_probe_h(from, &inpkt, len); + return true; + } + + if(type & ~(PKT_COMPRESSED | PKT_MAC)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Unexpected SPTPS record type %d len %d from %s (%s)", type, len, from->name, from->hostname); + return false; + } + + /* Check if we have the headers we need */ + if(routing_mode != RMODE_ROUTER && !(type & PKT_MAC)) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Received packet from %s (%s) without MAC header (maybe Mode is not set correctly)", from->name, from->hostname); + return false; + } else if(routing_mode == RMODE_ROUTER && (type & PKT_MAC)) { + logger(DEBUG_TRAFFIC, LOG_WARNING, "Received packet from %s (%s) with MAC header (maybe Mode is not set correctly)", from->name, from->hostname); + } + + int offset = (type & PKT_MAC) ? 0 : 14; + if(type & PKT_COMPRESSED) { + len = uncompress_packet(inpkt.data + offset, (const uint8_t *)data, len, from->incompression); + if(len < 0) { + return false; + } else { + inpkt.len = len + offset; + } + if(inpkt.len > MAXSIZE) + abort(); + } else { + memcpy(inpkt.data + offset, data, len); + inpkt.len = len + offset; + } + + /* Generate the Ethernet packet type if necessary */ + if(offset) { + switch(inpkt.data[14] >> 4) { + case 4: + inpkt.data[12] = 0x08; + inpkt.data[13] = 0x00; + break; + case 6: + inpkt.data[12] = 0x86; + inpkt.data[13] = 0xDD; + break; + default: + logger(DEBUG_TRAFFIC, LOG_ERR, + "Unknown IP version %d while reading packet from %s (%s)", + inpkt.data[14] >> 4, from->name, from->hostname); + return false; + } + } + + receive_packet(from, &inpkt); + return true; +} + /* send a packet to the given vpn ip. */ @@ -507,15 +758,15 @@ void send_packet(node_t *n, vpn_packet_t *packet) { memcpy(packet->data, mymac.x, ETH_ALEN); n->out_packets++; n->out_bytes += packet->len; - write_packet(packet); + devops.write(packet); return; } - ifdebug(TRAFFIC) logger(LOG_ERR, "Sending packet of %d bytes to %s (%s)", + logger(DEBUG_TRAFFIC, LOG_ERR, "Sending packet of %d bytes to %s (%s)", packet->len, n->name, n->hostname); if(!n->status.reachable) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Node %s (%s) is not reachable", + logger(DEBUG_TRAFFIC, LOG_INFO, "Node %s (%s) is not reachable", n->name, n->hostname); return; } @@ -523,10 +774,15 @@ void send_packet(node_t *n, vpn_packet_t *packet) { n->out_packets++; n->out_bytes += packet->len; + if(n->status.sptps) { + send_sptps_packet(n, packet); + return; + } + via = (packet->priority == -1 || n->via == myself) ? n->nexthop : n->via; if(via != n) - ifdebug(TRAFFIC) logger(LOG_INFO, "Sending packet to %s via %s (%s)", + logger(DEBUG_TRAFFIC, LOG_INFO, "Sending packet to %s via %s (%s)", n->name, via->name, n->via->hostname); if(packet->priority == -1 || ((myself->options | via->options) & OPTION_TCPONLY)) { @@ -539,46 +795,53 @@ void send_packet(node_t *n, vpn_packet_t *packet) { /* Broadcast a packet using the minimum spanning tree */ void broadcast_packet(const node_t *from, vpn_packet_t *packet) { - splay_node_t *node; - connection_t *c; - - ifdebug(TRAFFIC) logger(LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", - packet->len, from->name, from->hostname); - - if(from != myself) { + // Always give ourself a copy of the packet. + if(from != myself) send_packet(myself, packet); - // In TunnelServer mode, do not forward broadcast packets. - // The MST might not be valid and create loops. - if(tunnelserver) - return; - } + // In TunnelServer mode, do not forward broadcast packets. + // The MST might not be valid and create loops. + if(tunnelserver || broadcast_mode == BMODE_NONE) + return; - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + logger(DEBUG_TRAFFIC, LOG_INFO, "Broadcasting packet of %d bytes from %s (%s)", + packet->len, from->name, from->hostname); - if(c->status.active && c->status.mst && c != from->nexthop->connection) - send_packet(c->node, packet); + switch(broadcast_mode) { + // In MST mode, broadcast packets travel via the Minimum Spanning Tree. + // This guarantees all nodes receive the broadcast packet, and + // usually distributes the sending of broadcast packets over all nodes. + case BMODE_MST: + for list_each(connection_t, c, connection_list) + if(c->status.active && c->status.mst && c != from->nexthop->connection) + send_packet(c->node, packet); + break; + + // In direct mode, we send copies to each node we know of. + // However, this only reaches nodes that can be reached in a single hop. + // We don't have enough information to forward broadcast packets in this case. + case BMODE_DIRECT: + if(from != myself) + break; + + for splay_each(node_t, n, node_tree) + if(n->status.reachable && ((n->via == myself && n->nexthop == n) || n->via == n)) + send_packet(n, packet); + break; + + default: + break; } } static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) { - splay_node_t *node; - edge_t *e; node_t *n = NULL; bool hard = false; static time_t last_hard_try = 0; time_t now = time(NULL); - if(last_hard_try == now) - return NULL; - else - last_hard_try = now; - - for(node = edge_weight_tree->head; node; node = node->next) { - e = node->data; - - if(e->to == myself) + for splay_each(edge_t, e, edge_weight_tree) { + if(!e->to->status.reachable || e->to == myself) continue; if(sockaddrcmp_noport(from, &e->address)) { @@ -597,13 +860,14 @@ static node_t *try_harder(const sockaddr_t *from, const vpn_packet_t *pkt) { if(hard) last_hard_try = now; + last_hard_try = now; return n; } void handle_incoming_vpn_data(int sock, short events, void *data) { vpn_packet_t pkt; char *hostname; - sockaddr_t from; + sockaddr_t from = {{0}}; socklen_t fromlen = sizeof from; node_t *n; int len; @@ -612,13 +876,13 @@ void handle_incoming_vpn_data(int sock, short events, void *data) { if(len <= 0 || len > MAXSIZE) { if(!sockwouldblock(sockerrno)) - logger(LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Receiving packet failed: %s", sockstrerror(sockerrno)); return; } pkt.len = len; - sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */ + sockaddrunmap(&from); /* Some braindead IPv6 implementations do stupid things. */ n = lookup_node_udp(&from); @@ -626,9 +890,9 @@ void handle_incoming_vpn_data(int sock, short events, void *data) { n = try_harder(&from, &pkt); if(n) update_node_udp(n, &from); - else ifdebug(PROTOCOL) { + else if(debug_level >= DEBUG_PROTOCOL) { hostname = sockaddr2hostname(&from); - logger(LOG_WARNING, "Received UDP packet from unknown source %s", hostname); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Received UDP packet from unknown source %s", hostname); free(hostname); return; } @@ -636,6 +900,8 @@ void handle_incoming_vpn_data(int sock, short events, void *data) { return; } + n->sock = (intptr_t)data; + receive_udppacket(n, &pkt); } @@ -644,7 +910,7 @@ void handle_device_data(int sock, short events, void *data) { packet.priority = 0; - if(read_packet(&packet)) { + if(devops.read(&packet)) { myself->in_packets++; myself->in_bytes += packet.len; route(myself, &packet); diff --git a/src/net_setup.c b/src/net_setup.c index 91f7609..95ff5c3 100644 --- a/src/net_setup.c +++ b/src/net_setup.c @@ -1,7 +1,7 @@ /* net_setup.c -- Setup. Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2010 Guus Sliepen + 2000-2012 Guus Sliepen 2006 Scott Lamb 2010 Brandon Black @@ -22,7 +22,6 @@ #include "system.h" -#include "splay_tree.h" #include "cipher.h" #include "conf.h" #include "connection.h" @@ -44,6 +43,16 @@ char *myport; static struct event device_ev; +devops_t devops; + +char *proxyhost; +char *proxyport; +char *proxyuser; +char *proxypass; +proxytype_t proxytype; + +char *scriptinterpreter; +char *scriptextension; bool node_read_ecdsa_public_key(node_t *n) { if(ecdsa_active(&n->ecdsa)) @@ -51,14 +60,14 @@ bool node_read_ecdsa_public_key(node_t *n) { splay_tree_t *config_tree; FILE *fp; - char *fname; + char *pubname = NULL, *hcfname = NULL; char *p; bool result = false; - xasprintf(&fname, "%s/hosts/%s", confbase, n->name); + xasprintf(&hcfname, "%s" SLASH "hosts" SLASH "%s", confbase, n->name); init_configuration(&config_tree); - if(!read_config_file(config_tree, fname)) + if(!read_config_file(config_tree, hcfname)) goto exit; /* First, check for simple ECDSAPublicKey statement */ @@ -71,15 +80,13 @@ bool node_read_ecdsa_public_key(node_t *n) { /* Else, check for ECDSAPublicKeyFile statement and read it */ - free(fname); + if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &pubname)) + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, n->name); - if(!get_config_string(lookup_config(config_tree, "ECDSAPublicKeyFile"), &fname)) - xasprintf(&fname, "%s/hosts/%s", confbase, n->name); - - fp = fopen(fname, "r"); + fp = fopen(pubname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading ECDSA public key file `%s': %s", pubname, strerror(errno)); goto exit; } @@ -88,7 +95,8 @@ bool node_read_ecdsa_public_key(node_t *n) { exit: exit_configuration(&config_tree); - free(fname); + free(hcfname); + free(pubname); return result; } @@ -109,12 +117,12 @@ bool read_ecdsa_public_key(connection_t *c) { /* Else, check for ECDSAPublicKeyFile statement and read it */ if(!get_config_string(lookup_config(c->config_tree, "ECDSAPublicKeyFile"), &fname)) - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading ECDSA public key file `%s': %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading ECDSA public key file `%s': %s", fname, strerror(errno)); free(fname); return false; @@ -123,8 +131,8 @@ bool read_ecdsa_public_key(connection_t *c) { result = ecdsa_read_pem_public_key(&c->ecdsa, fp); fclose(fp); - if(!result) - logger(LOG_ERR, "Reading ECDSA public key file `%s' failed: %s", fname, strerror(errno)); + if(!result) + logger(DEBUG_ALWAYS, LOG_ERR, "Parsing ECDSA public key file `%s' failed.", fname); free(fname); return result; } @@ -146,13 +154,12 @@ bool read_rsa_public_key(connection_t *c) { /* Else, check for PublicKeyFile statement and read it */ if(!get_config_string(lookup_config(c->config_tree, "PublicKeyFile"), &fname)) - xasprintf(&fname, "%s/hosts/%s", confbase, c->name); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, c->name); fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading RSA public key file `%s': %s", - fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading RSA public key file `%s': %s", fname, strerror(errno)); free(fname); return false; } @@ -160,8 +167,8 @@ bool read_rsa_public_key(connection_t *c) { result = rsa_read_pem_public_key(&c->rsa, fp); fclose(fp); - if(!result) - logger(LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno)); + if(!result) + logger(DEBUG_ALWAYS, LOG_ERR, "Reading RSA public key file `%s' failed: %s", fname, strerror(errno)); free(fname); return result; } @@ -174,13 +181,12 @@ static bool read_ecdsa_private_key(void) { /* Check for PrivateKeyFile statement and read it */ if(!get_config_string(lookup_config(config_tree, "ECDSAPrivateKeyFile"), &fname)) - xasprintf(&fname, "%s/ecdsa_key.priv", confbase); + xasprintf(&fname, "%s" SLASH "ecdsa_key.priv", confbase); fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading ECDSA private key file `%s': %s", - fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading ECDSA private key file `%s': %s", fname, strerror(errno)); free(fname); return false; } @@ -189,20 +195,20 @@ static bool read_ecdsa_private_key(void) { struct stat s; if(fstat(fileno(fp), &s)) { - logger(LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat ECDSA private key file `%s': %s'", fname, strerror(errno)); free(fname); return false; } if(s.st_mode & ~0100700) - logger(LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname); + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: insecure file permissions for ECDSA private key file `%s'!", fname); #endif result = ecdsa_read_pem_private_key(&myself->connection->ecdsa, fp); fclose(fp); - if(!result) - logger(LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno)); + if(!result) + logger(DEBUG_ALWAYS, LOG_ERR, "Reading ECDSA private key file `%s' failed: %s", fname, strerror(errno)); free(fname); return result; } @@ -217,25 +223,25 @@ static bool read_rsa_private_key(void) { if(get_config_string(lookup_config(config_tree, "PrivateKey"), &d)) { if(!get_config_string(lookup_config(config_tree, "PublicKey"), &n)) { - logger(LOG_ERR, "PrivateKey used but no PublicKey found!"); + logger(DEBUG_ALWAYS, LOG_ERR, "PrivateKey used but no PublicKey found!"); free(d); return false; } result = rsa_set_hex_private_key(&myself->connection->rsa, n, "FFFF", d); free(n); free(d); - return true; + return result; } /* Else, check for PrivateKeyFile statement and read it */ if(!get_config_string(lookup_config(config_tree, "PrivateKeyFile"), &fname)) - xasprintf(&fname, "%s/rsa_key.priv", confbase); + xasprintf(&fname, "%s" SLASH "rsa_key.priv", confbase); fp = fopen(fname, "r"); if(!fp) { - logger(LOG_ERR, "Error reading RSA private key file `%s': %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error reading RSA private key file `%s': %s", fname, strerror(errno)); free(fname); return false; @@ -245,20 +251,20 @@ static bool read_rsa_private_key(void) { struct stat s; if(fstat(fileno(fp), &s)) { - logger(LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not stat RSA private key file `%s': %s'", fname, strerror(errno)); free(fname); return false; } if(s.st_mode & ~0100700) - logger(LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname); + logger(DEBUG_ALWAYS, LOG_WARNING, "Warning: insecure file permissions for RSA private key file `%s'!", fname); #endif result = rsa_read_pem_private_key(&myself->connection->rsa, fp); fclose(fp); - if(!result) - logger(LOG_ERR, "Reading RSA private key file `%s' failed: %s", fname, strerror(errno)); + if(!result) + logger(DEBUG_ALWAYS, LOG_ERR, "Reading RSA private key file `%s' failed: %s", fname, strerror(errno)); free(fname); return result; } @@ -271,7 +277,7 @@ static void keyexpire_handler(int fd, short events, void *data) { void regenerate_key(void) { if(timeout_initialized(&keyexpire_event)) { - ifdebug(STATUS) logger(LOG_INFO, "Expiring symmetric keys"); + logger(DEBUG_STATUS, LOG_INFO, "Expiring symmetric keys"); event_del(&keyexpire_event); send_key_changed(); } else { @@ -288,17 +294,11 @@ void load_all_subnets(void) { DIR *dir; struct dirent *ent; char *dname; - char *fname; - splay_tree_t *config_tree; - config_t *cfg; - subnet_t *s, *s2; - node_t *n; - bool result; - xasprintf(&dname, "%s/hosts", confbase); + xasprintf(&dname, "%s" SLASH "hosts", confbase); dir = opendir(dname); if(!dir) { - logger(LOG_ERR, "Could not open %s: %s", dname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", dname, strerror(errno)); free(dname); return; } @@ -307,18 +307,20 @@ void load_all_subnets(void) { if(!check_id(ent->d_name)) continue; - n = lookup_node(ent->d_name); + node_t *n = lookup_node(ent->d_name); #ifdef _DIRENT_HAVE_D_TYPE //if(ent->d_type != DT_REG) // continue; #endif - xasprintf(&fname, "%s/hosts/%s", confbase, ent->d_name); + char *fname; + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, ent->d_name); + + splay_tree_t *config_tree; init_configuration(&config_tree); - result = read_config_file(config_tree, fname); + read_config_options(config_tree, ent->d_name); + read_config_file(config_tree, fname); free(fname); - if(!result) - continue; if(!n) { n = new_node(); @@ -326,7 +328,9 @@ void load_all_subnets(void) { node_add(n); } - for(cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + for(config_t *cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + subnet_t *s, *s2; + if(!get_config_subnet(cfg, &s)) continue; @@ -343,85 +347,128 @@ void load_all_subnets(void) { closedir(dir); } -/* - Configure node_t myself and set up the local sockets (listen only) -*/ -static bool setup_myself(void) { - config_t *cfg; - subnet_t *subnet; - char *name, *hostname, *mode, *afname, *cipher, *digest; - char *fname = NULL; - char *address = NULL; - char *envp[5]; - struct addrinfo *ai, *aip, hint = {0}; - bool choice; - int i, err; - int replaywin_int; +char *get_name(void) { + char *name = NULL; - myself = new_node(); - myself->connection = new_connection(); + get_config_string(lookup_config(config_tree, "Name"), &name); - myself->hostname = xstrdup("MYSELF"); - myself->connection->hostname = xstrdup("MYSELF"); + if(!name) + return NULL; - myself->connection->options = 0; - myself->connection->protocol_major = PROT_MAJOR; - myself->connection->protocol_minor = PROT_MINOR; - - if(!get_config_string(lookup_config(config_tree, "Name"), &name)) { /* Not acceptable */ - logger(LOG_ERR, "Name for tinc daemon required!"); - return false; + if(*name == '$') { + char *envname = getenv(name + 1); + if(!envname) { + if(strcmp(name + 1, "HOST")) { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid Name: environment variable %s does not exist\n", name + 1); + return false; + } + envname = alloca(32); + if(gethostname(envname, 32)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get hostname: %s\n", strerror(errno)); + return false; + } + envname[31] = 0; + } + free(name); + name = xstrdup(envname); + for(char *c = name; *c; c++) + if(!isalnum(*c)) + *c = '_'; } if(!check_id(name)) { - logger(LOG_ERR, "Invalid name for myself!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for myself!"); free(name); return false; } - myself->name = name; - myself->connection->name = xstrdup(name); - xasprintf(&fname, "%s/hosts/%s", confbase, name); - read_config_options(config_tree, name); - read_config_file(config_tree, fname); - free(fname); + return name; +} - get_config_bool(lookup_config(config_tree, "ExperimentalProtocol"), &experimental); +bool setup_myself_reloadable(void) { + char *proxy = NULL; + char *rmode = NULL; + char *fmode = NULL; + char *bmode = NULL; + char *afname = NULL; + char *space; + bool choice; - if(experimental && !read_ecdsa_private_key()) - return false; + free(scriptinterpreter); + scriptinterpreter = NULL; + get_config_string(lookup_config(config_tree, "ScriptsInterpreter"), &scriptinterpreter); - if(!read_rsa_private_key()) - return false; - if(!get_config_string(lookup_config(config_tree, "Port"), &myport)) - myport = xstrdup("655"); + free(scriptextension); + if(!get_config_string(lookup_config(config_tree, "ScriptsExtension"), &scriptextension)) +#ifdef HAVE_MINGW + scriptextension = xstrdup(".bat"); +#else + scriptextension = xstrdup(""); +#endif - if(!atoi(myport)) { - struct addrinfo *ai = str2addrinfo("localhost", myport, SOCK_DGRAM); - sockaddr_t sa; - if(!ai || !ai->ai_addr) + get_config_string(lookup_config(config_tree, "Proxy"), &proxy); + if(proxy) { + if((space = strchr(proxy, ' '))) + *space++ = 0; + + if(!strcasecmp(proxy, "none")) { + proxytype = PROXY_NONE; + } else if(!strcasecmp(proxy, "socks4")) { + proxytype = PROXY_SOCKS4; + } else if(!strcasecmp(proxy, "socks4a")) { + proxytype = PROXY_SOCKS4A; + } else if(!strcasecmp(proxy, "socks5")) { + proxytype = PROXY_SOCKS5; + } else if(!strcasecmp(proxy, "http")) { + proxytype = PROXY_HTTP; + } else if(!strcasecmp(proxy, "exec")) { + proxytype = PROXY_EXEC; + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type %s!", proxy); return false; - free(myport); - memcpy(&sa, ai->ai_addr, ai->ai_addrlen); - sockaddr2str(&sa, NULL, &myport); + } + + switch(proxytype) { + case PROXY_NONE: + default: + break; + + case PROXY_EXEC: + if(!space || !*space) { + logger(DEBUG_ALWAYS, LOG_ERR, "Argument expected for proxy type exec!"); + return false; + } + proxyhost = xstrdup(space); + break; + + case PROXY_SOCKS4: + case PROXY_SOCKS4A: + case PROXY_SOCKS5: + case PROXY_HTTP: + proxyhost = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxyport = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxyuser = space; + if(space && (space = strchr(space, ' '))) + *space++ = 0, proxypass = space; + if(!proxyhost || !*proxyhost || !proxyport || !*proxyport) { + logger(DEBUG_ALWAYS, LOG_ERR, "Host and port argument expected for proxy!"); + return false; + } + proxyhost = xstrdup(proxyhost); + proxyport = xstrdup(proxyport); + if(proxyuser && *proxyuser) + proxyuser = xstrdup(proxyuser); + if(proxypass && *proxypass) + proxypass = xstrdup(proxypass); + break; + } + + free(proxy); } - /* Read in all the subnets specified in the host configuration file */ - - cfg = lookup_config(config_tree, "Subnet"); - - while(cfg) { - if(!get_config_subnet(cfg, &subnet)) - return false; - - subnet_add(myself, subnet); - - cfg = lookup_config_next(config_tree, cfg); - } - - /* Check some options */ - if(get_config_bool(lookup_config(config_tree, "IndirectData"), &choice) && choice) myself->options |= OPTION_INDIRECT; @@ -432,36 +479,34 @@ static bool setup_myself(void) { myself->options |= OPTION_INDIRECT; get_config_bool(lookup_config(config_tree, "DirectOnly"), &directonly); - get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets); - get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver); - strictsubnets |= tunnelserver; + get_config_bool(lookup_config(config_tree, "LocalDiscovery"), &localdiscovery); - if(get_config_string(lookup_config(config_tree, "Mode"), &mode)) { - if(!strcasecmp(mode, "router")) + if(get_config_string(lookup_config(config_tree, "Mode"), &rmode)) { + if(!strcasecmp(rmode, "router")) routing_mode = RMODE_ROUTER; - else if(!strcasecmp(mode, "switch")) + else if(!strcasecmp(rmode, "switch")) routing_mode = RMODE_SWITCH; - else if(!strcasecmp(mode, "hub")) + else if(!strcasecmp(rmode, "hub")) routing_mode = RMODE_HUB; else { - logger(LOG_ERR, "Invalid routing mode!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid routing mode!"); return false; } - free(mode); + free(rmode); } - if(get_config_string(lookup_config(config_tree, "Forwarding"), &mode)) { - if(!strcasecmp(mode, "off")) + if(get_config_string(lookup_config(config_tree, "Forwarding"), &fmode)) { + if(!strcasecmp(fmode, "off")) forwarding_mode = FMODE_OFF; - else if(!strcasecmp(mode, "internal")) + else if(!strcasecmp(fmode, "internal")) forwarding_mode = FMODE_INTERNAL; - else if(!strcasecmp(mode, "kernel")) + else if(!strcasecmp(fmode, "kernel")) forwarding_mode = FMODE_KERNEL; else { - logger(LOG_ERR, "Invalid forwarding mode!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid forwarding mode!"); return false; } - free(mode); + free(fmode); } choice = true; @@ -475,10 +520,24 @@ static bool setup_myself(void) { myself->options |= OPTION_CLAMP_MSS; get_config_bool(lookup_config(config_tree, "PriorityInheritance"), &priorityinheritance); + get_config_bool(lookup_config(config_tree, "DecrementTTL"), &decrement_ttl); + if(get_config_string(lookup_config(config_tree, "Broadcast"), &bmode)) { + if(!strcasecmp(bmode, "no")) + broadcast_mode = BMODE_NONE; + else if(!strcasecmp(bmode, "yes") || !strcasecmp(bmode, "mst")) + broadcast_mode = BMODE_MST; + else if(!strcasecmp(bmode, "direct")) + broadcast_mode = BMODE_DIRECT; + else { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid broadcast mode!"); + return false; + } + free(bmode); + } #if !defined(SOL_IP) || !defined(IP_TOS) if(priorityinheritance) - logger(LOG_WARNING, "%s not supported on this platform", "PriorityInheritance"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "PriorityInheritance"); #endif if(!get_config_int(lookup_config(config_tree, "MACExpire"), &macexpire)) @@ -486,34 +545,12 @@ static bool setup_myself(void) { if(get_config_int(lookup_config(config_tree, "MaxTimeout"), &maxtimeout)) { if(maxtimeout <= 0) { - logger(LOG_ERR, "Bogus maximum timeout!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus maximum timeout!"); return false; } } else maxtimeout = 900; - if(get_config_int(lookup_config(config_tree, "UDPRcvBuf"), &udp_rcvbuf)) { - if(udp_rcvbuf <= 0) { - logger(LOG_ERR, "UDPRcvBuf cannot be negative!"); - return false; - } - } - - if(get_config_int(lookup_config(config_tree, "UDPSndBuf"), &udp_sndbuf)) { - if(udp_sndbuf <= 0) { - logger(LOG_ERR, "UDPSndBuf cannot be negative!"); - return false; - } - } - - if(get_config_int(lookup_config(config_tree, "ReplayWindow"), &replaywin_int)) { - if(replaywin_int < 0) { - logger(LOG_ERR, "ReplayWindow cannot be negative!"); - return false; - } - replaywin = (unsigned)replaywin_int; - } - if(get_config_string(lookup_config(config_tree, "AddressFamily"), &afname)) { if(!strcasecmp(afname, "IPv4")) addressfamily = AF_INET; @@ -522,7 +559,7 @@ static bool setup_myself(void) { else if(!strcasecmp(afname, "any")) addressfamily = AF_UNSPEC; else { - logger(LOG_ERR, "Invalid address family!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid address family!"); return false; } free(afname); @@ -530,44 +567,149 @@ static bool setup_myself(void) { get_config_bool(lookup_config(config_tree, "Hostnames"), &hostnames); + if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime)) + keylifetime = 3600; + + return true; +} + +/* + Configure node_t myself and set up the local sockets (listen only) +*/ +static bool setup_myself(void) { + char *name, *hostname, *cipher, *digest, *type; + char *fname = NULL; + char *address = NULL; + + if(!(name = get_name())) { + logger(DEBUG_ALWAYS, LOG_ERR, "Name for tinc daemon required!"); + return false; + } + + myself = new_node(); + myself->connection = new_connection(); + myself->name = name; + myself->connection->name = xstrdup(name); + xasprintf(&fname, "%s" SLASH "hosts" SLASH "%s", confbase, name); + read_config_options(config_tree, name); + read_config_file(config_tree, fname); + free(fname); + + if(!get_config_string(lookup_config(config_tree, "Port"), &myport)) + myport = xstrdup("655"); + + xasprintf(&myself->hostname, "MYSELF port %s", myport); + myself->connection->hostname = xstrdup(myself->hostname); + + myself->connection->options = 0; + myself->connection->protocol_major = PROT_MAJOR; + myself->connection->protocol_minor = PROT_MINOR; + + myself->options |= PROT_MINOR << 24; + + get_config_bool(lookup_config(config_tree, "ExperimentalProtocol"), &experimental); + + if(experimental && !read_ecdsa_private_key()) + return false; + + if(!read_rsa_private_key()) + return false; + + if(!atoi(myport)) { + struct addrinfo *ai = str2addrinfo("localhost", myport, SOCK_DGRAM); + sockaddr_t sa; + if(!ai || !ai->ai_addr) + return false; + free(myport); + memcpy(&sa, ai->ai_addr, ai->ai_addrlen); + sockaddr2str(&sa, NULL, &myport); + } + + /* Read in all the subnets specified in the host configuration file */ + + for(config_t *cfg = lookup_config(config_tree, "Subnet"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + subnet_t *subnet; + + if(!get_config_subnet(cfg, &subnet)) + return false; + + subnet_add(myself, subnet); + } + + /* Check some options */ + + if(!setup_myself_reloadable()) + return false; + + get_config_bool(lookup_config(config_tree, "StrictSubnets"), &strictsubnets); + get_config_bool(lookup_config(config_tree, "TunnelServer"), &tunnelserver); + strictsubnets |= tunnelserver; + + + + if(get_config_int(lookup_config(config_tree, "UDPRcvBuf"), &udp_rcvbuf)) { + if(udp_rcvbuf <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "UDPRcvBuf cannot be negative!"); + return false; + } + } + + if(get_config_int(lookup_config(config_tree, "UDPSndBuf"), &udp_sndbuf)) { + if(udp_sndbuf <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "UDPSndBuf cannot be negative!"); + return false; + } + } + + int replaywin_int; + if(get_config_int(lookup_config(config_tree, "ReplayWindow"), &replaywin_int)) { + if(replaywin_int < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "ReplayWindow cannot be negative!"); + return false; + } + replaywin = (unsigned)replaywin_int; + sptps_replaywin = replaywin; + } + /* Generate packet encryption key */ if(!get_config_string(lookup_config(config_tree, "Cipher"), &cipher)) cipher = xstrdup("blowfish"); if(!cipher_open_by_name(&myself->incipher, cipher)) { - logger(LOG_ERR, "Unrecognized cipher type!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Unrecognized cipher type!"); return false; } - if(!get_config_int(lookup_config(config_tree, "KeyExpire"), &keylifetime)) - keylifetime = 3600; + free(cipher); regenerate_key(); /* Check if we want to use message authentication codes... */ - if(!get_config_string(lookup_config(config_tree, "Digest"), &digest)) - digest = xstrdup("sha1"); - int maclength = 4; get_config_int(lookup_config(config_tree, "MACLength"), &maclength); if(maclength < 0) { - logger(LOG_ERR, "Bogus MAC length!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus MAC length!"); return false; } + if(!get_config_string(lookup_config(config_tree, "Digest"), &digest)) + digest = xstrdup("sha1"); + if(!digest_open_by_name(&myself->indigest, digest, maclength)) { - logger(LOG_ERR, "Unrecognized digest type!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Unrecognized digest type!"); return false; } + free(digest); + /* Compression */ if(get_config_int(lookup_config(config_tree, "Compression"), &myself->incompression)) { if(myself->incompression < 0 || myself->incompression > 11) { - logger(LOG_ERR, "Bogus compression level!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus compression level!"); return false; } } else @@ -580,6 +722,8 @@ static bool setup_myself(void) { myself->nexthop = myself; myself->via = myself; myself->status.reachable = true; + myself->last_state_change = time(NULL); + myself->status.sptps = experimental; node_add(myself); graph(); @@ -589,20 +733,40 @@ static bool setup_myself(void) { /* Open device */ - if(!setup_device()) + devops = os_devops; + + if(get_config_string(lookup_config(config_tree, "DeviceType"), &type)) { + if(!strcasecmp(type, "dummy")) + devops = dummy_devops; + else if(!strcasecmp(type, "raw_socket")) + devops = raw_socket_devops; + else if(!strcasecmp(type, "multicast")) + devops = multicast_devops; +#ifdef ENABLE_UML + else if(!strcasecmp(type, "uml")) + devops = uml_devops; +#endif +#ifdef ENABLE_VDE + else if(!strcasecmp(type, "vde")) + devops = vde_devops; +#endif + } + + if(!devops.setup()) return false; if(device_fd >= 0) { event_set(&device_ev, device_fd, EV_READ|EV_PERSIST, handle_device_data, NULL); if (event_add(&device_ev, NULL) < 0) { - logger(LOG_ERR, "event_add failed: %s", strerror(errno)); - close_device(); + logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno)); + devops.close(); return false; } } /* Run tinc-up script to further initialize the tap interface */ + char *envp[5]; xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); xasprintf(&envp[1], "DEVICE=%s", device ? : ""); xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); @@ -611,7 +775,7 @@ static bool setup_myself(void) { execute_script("tinc-up", envp); - for(i = 0; i < 4; i++) + for(int i = 0; i < 4; i++) free(envp[i]); /* Run subnet-up scripts for our own subnets */ @@ -620,80 +784,155 @@ static bool setup_myself(void) { /* Open sockets */ - get_config_string(lookup_config(config_tree, "BindToAddress"), &address); + if(!do_detach && getenv("LISTEN_FDS")) { + sockaddr_t sa; + socklen_t salen; - hint.ai_family = addressfamily; - hint.ai_socktype = SOCK_STREAM; - hint.ai_protocol = IPPROTO_TCP; - hint.ai_flags = AI_PASSIVE; + listen_sockets = atoi(getenv("LISTEN_FDS")); +#ifdef HAVE_UNSETENV + unsetenv("LISTEN_FDS"); +#endif - err = getaddrinfo(address, myport, &hint, &ai); + if(listen_sockets > MAXSOCKETS) { + logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets"); + return false; + } - if(err || !ai) { - logger(LOG_ERR, "System call `%s' failed: %s", "getaddrinfo", - gai_strerror(err)); - return false; + for(int i = 0; i < listen_sockets; i++) { + salen = sizeof sa; + if(getsockname(i + 3, &sa.sa, &salen) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not get address of listen fd %d: %s", i + 3, sockstrerror(errno)); + return false; + } + + listen_socket[i].tcp = i + 3; + +#ifdef FD_CLOEXEC + fcntl(i + 3, F_SETFD, FD_CLOEXEC); +#endif + + listen_socket[i].udp = setup_vpn_in_socket(&sa); + if(listen_socket[i].udp < 0) + return false; + + event_set(&listen_socket[i].ev_tcp, listen_socket[i].tcp, EV_READ|EV_PERSIST, handle_new_meta_connection, NULL); + if(event_add(&listen_socket[i].ev_tcp, NULL) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno)); + abort(); + } + + event_set(&listen_socket[i].ev_udp, listen_socket[i].udp, EV_READ|EV_PERSIST, handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets); + if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno)); + abort(); + } + + if(debug_level >= DEBUG_CONNECTIONS) { + hostname = sockaddr2hostname(&sa); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname); + free(hostname); + } + + memcpy(&listen_socket[i].sa, &sa, salen); + } + } else { + listen_sockets = 0; + config_t *cfg = lookup_config(config_tree, "BindToAddress"); + + do { + get_config_string(cfg, &address); + if(cfg) + cfg = lookup_config_next(config_tree, cfg); + + char *port = myport; + + if(address) { + char *space = strchr(address, ' '); + if(space) { + *space++ = 0; + port = space; + } + + if(!strcmp(address, "*")) + *address = 0; + } + + struct addrinfo *ai, hint = {0}; + hint.ai_family = addressfamily; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_flags = AI_PASSIVE; + + int err = getaddrinfo(address && *address ? address : NULL, port, &hint, &ai); + free(address); + + if(err || !ai) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "getaddrinfo", + gai_strerror(err)); + return false; + } + + for(struct addrinfo *aip = ai; aip; aip = aip->ai_next) { + if(listen_sockets >= MAXSOCKETS) { + logger(DEBUG_ALWAYS, LOG_ERR, "Too many listening sockets"); + return false; + } + + listen_socket[listen_sockets].tcp = + setup_listen_socket((sockaddr_t *) aip->ai_addr); + + if(listen_socket[listen_sockets].tcp < 0) + continue; + + listen_socket[listen_sockets].udp = + setup_vpn_in_socket((sockaddr_t *) aip->ai_addr); + + if(listen_socket[listen_sockets].udp < 0) { + close(listen_socket[listen_sockets].tcp); + continue; + } + + event_set(&listen_socket[listen_sockets].ev_tcp, + listen_socket[listen_sockets].tcp, + EV_READ|EV_PERSIST, + handle_new_meta_connection, NULL); + if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno)); + abort(); + } + + event_set(&listen_socket[listen_sockets].ev_udp, + listen_socket[listen_sockets].udp, + EV_READ|EV_PERSIST, + handle_incoming_vpn_data, (void *)(intptr_t)listen_sockets); + if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "event_add failed: %s", strerror(errno)); + abort(); + } + + if(debug_level >= DEBUG_CONNECTIONS) { + hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Listening on %s", hostname); + free(hostname); + } + + memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); + listen_sockets++; + } + + freeaddrinfo(ai); + } while(cfg); } - listen_sockets = 0; - - for(aip = ai; aip; aip = aip->ai_next) { - listen_socket[listen_sockets].tcp = - setup_listen_socket((sockaddr_t *) aip->ai_addr); - - if(listen_socket[listen_sockets].tcp < 0) - continue; - - listen_socket[listen_sockets].udp = - setup_vpn_in_socket((sockaddr_t *) aip->ai_addr); - - if(listen_socket[listen_sockets].udp < 0) { - close(listen_socket[listen_sockets].tcp); - continue; - } - - event_set(&listen_socket[listen_sockets].ev_tcp, - listen_socket[listen_sockets].tcp, - EV_READ|EV_PERSIST, - handle_new_meta_connection, NULL); - if(event_add(&listen_socket[listen_sockets].ev_tcp, NULL) < 0) { - logger(LOG_ERR, "event_add failed: %s", strerror(errno)); - abort(); - } - - event_set(&listen_socket[listen_sockets].ev_udp, - listen_socket[listen_sockets].udp, - EV_READ|EV_PERSIST, - handle_incoming_vpn_data, NULL); - if(event_add(&listen_socket[listen_sockets].ev_udp, NULL) < 0) { - logger(LOG_ERR, "event_add failed: %s", strerror(errno)); - abort(); - } - - ifdebug(CONNECTIONS) { - hostname = sockaddr2hostname((sockaddr_t *) aip->ai_addr); - logger(LOG_NOTICE, "Listening on %s", hostname); - free(hostname); - } - - memcpy(&listen_socket[listen_sockets].sa, aip->ai_addr, aip->ai_addrlen); - listen_sockets++; - - if(listen_sockets >= MAXSOCKETS) { - logger(LOG_WARNING, "Maximum of %d listening sockets reached", MAXSOCKETS); - break; - } - } - - freeaddrinfo(ai); - if(listen_sockets) - logger(LOG_NOTICE, "Ready"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Ready"); else { - logger(LOG_ERR, "Unable to create any listening socket!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to create any listening socket!"); return false; } + last_config_check = time(NULL); + return true; } @@ -732,14 +971,12 @@ bool setup_network(void) { close all open network connections */ void close_network_connections(void) { - splay_node_t *node, *next; - connection_t *c; - char *envp[5]; - int i; - - for(node = connection_tree->head; node; node = next) { + for(list_node_t *node = connection_list->head, *next; node; node = next) { next = node->next; - c = node->data; + connection_t *c = node->data; + /* Keep control connections open until the end, so they know when we really terminated */ + if(c->status.control) + c->socket = -1; c->outgoing = NULL; terminate_connection(c, false); } @@ -752,13 +989,14 @@ void close_network_connections(void) { free_connection(myself->connection); } - for(i = 0; i < listen_sockets; i++) { + for(int i = 0; i < listen_sockets; i++) { event_del(&listen_socket[i].ev_tcp); event_del(&listen_socket[i].ev_udp); close(listen_socket[i].tcp); close(listen_socket[i].udp); } + char *envp[5]; xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); xasprintf(&envp[1], "DEVICE=%s", device ? : ""); xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); @@ -775,10 +1013,10 @@ void close_network_connections(void) { if(myport) free(myport); - for(i = 0; i < 4; i++) + for(int i = 0; i < 4; i++) free(envp[i]); - close_device(); + devops.close(); return; } diff --git a/src/net_socket.c b/src/net_socket.c index a1dfcd3..09c5207 100644 --- a/src/net_socket.c +++ b/src/net_socket.c @@ -1,7 +1,7 @@ /* net_socket.c -- Handle various kinds of sockets. Copyright (C) 1998-2005 Ivo Timmermans, - 2000-2010 Guus Sliepen + 2000-2012 Guus Sliepen 2006 Scott Lamb 2009 Florian Forster @@ -22,9 +22,9 @@ #include "system.h" -#include "splay_tree.h" #include "conf.h" #include "connection.h" +#include "list.h" #include "logger.h" #include "meta.h" #include "net.h" @@ -33,8 +33,6 @@ #include "utils.h" #include "xalloc.h" -#include - /* Needed on Mac OS/X */ #ifndef SOL_TCP #define SOL_TCP IPPROTO_TCP @@ -59,13 +57,13 @@ static void configure_tcp(connection_t *c) { int flags = fcntl(c->socket, F_GETFL); if(fcntl(c->socket, F_SETFL, flags | O_NONBLOCK) < 0) { - logger(LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "fcntl for %s: %s", c->hostname, strerror(errno)); } #elif defined(WIN32) unsigned long arg = 1; if(ioctlsocket(c->socket, FIONBIO, &arg) != 0) { - logger(LOG_ERR, "ioctlsocket for %s: %d", c->hostname, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "ioctlsocket for %s: %s", c->hostname, sockstrerror(sockerrno)); } #endif @@ -98,74 +96,17 @@ static bool bind_to_interface(int sd) { status = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof(ifr)); if(status) { - logger(LOG_ERR, "Can't bind to interface %s: %s", iface, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface, strerror(errno)); return false; } #else /* if !defined(SOL_SOCKET) || !defined(SO_BINDTODEVICE) */ - logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface"); #endif return true; } -static bool bind_to_address(connection_t *c) { - char *node; - struct addrinfo *ai_list; - struct addrinfo *ai_ptr; - struct addrinfo ai_hints; - int status; - - assert(c != NULL); - assert(c->socket >= 0); - - node = NULL; - if(!get_config_string(lookup_config(config_tree, "BindToAddress"), - &node)) - return true; - - assert(node != NULL); - - memset(&ai_hints, 0, sizeof(ai_hints)); - ai_hints.ai_family = c->address.sa.sa_family; - /* We're called from `do_outgoing_connection' only. */ - ai_hints.ai_socktype = SOCK_STREAM; - ai_hints.ai_protocol = IPPROTO_TCP; - - ai_list = NULL; - - status = getaddrinfo(node, /* service = */ NULL, - &ai_hints, &ai_list); - if(status) { - logger(LOG_WARNING, "Error looking up %s port %s: %s", - node, "any", gai_strerror(status)); - free(node); - return false; - } - assert(ai_list != NULL); - - status = -1; - for(ai_ptr = ai_list; ai_ptr != NULL; ai_ptr = ai_ptr->ai_next) { - status = bind(c->socket, - ai_list->ai_addr, ai_list->ai_addrlen); - if(!status) - break; - } - - - if(status) { - logger(LOG_ERR, "Can't bind to %s/tcp: %s", node, sockstrerror(sockerrno)); - } else ifdebug(CONNECTIONS) { - logger(LOG_DEBUG, "Successfully bound outgoing " - "TCP socket to %s", node); - } - - free(node); - freeaddrinfo(ai_list); - - return status ? false : true; -} - int setup_listen_socket(const sockaddr_t *sa) { int nfd; char *addrstr; @@ -175,10 +116,14 @@ int setup_listen_socket(const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_STREAM, IPPROTO_TCP); if(nfd < 0) { - ifdebug(STATUS) logger(LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_STATUS, LOG_ERR, "Creating metasocket failed: %s", sockstrerror(sockerrno)); return -1; } +#ifdef FD_CLOEXEC + fcntl(nfd, F_SETFD, FD_CLOEXEC); +#endif + /* Optimize TCP settings */ option = 1; @@ -199,26 +144,26 @@ int setup_listen_socket(const sockaddr_t *sa) { if(setsockopt(nfd, SOL_SOCKET, SO_BINDTODEVICE, (void *)&ifr, sizeof ifr)) { closesocket(nfd); - logger(LOG_ERR, "Can't bind to interface %s: %s", iface, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to interface %s: %s", iface, strerror(sockerrno)); return -1; } #else - logger(LOG_WARNING, "%s not supported on this platform", "BindToInterface"); + logger(DEBUG_ALWAYS, LOG_WARNING, "%s not supported on this platform", "BindToInterface"); #endif } if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/tcp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } if(listen(nfd, 3)) { closesocket(nfd); - logger(LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "listen", sockstrerror(sockerrno)); return -1; } @@ -233,17 +178,21 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { nfd = socket(sa->sa.sa_family, SOCK_DGRAM, IPPROTO_UDP); if(nfd < 0) { - logger(LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Creating UDP socket failed: %s", sockstrerror(sockerrno)); return -1; } +#ifdef FD_CLOEXEC + fcntl(nfd, F_SETFD, FD_CLOEXEC); +#endif + #ifdef O_NONBLOCK { int flags = fcntl(nfd, F_GETFL); if(fcntl(nfd, F_SETFL, flags | O_NONBLOCK) < 0) { closesocket(nfd); - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); return -1; } @@ -253,7 +202,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { unsigned long arg = 1; if(ioctlsocket(nfd, FIONBIO, &arg) != 0) { closesocket(nfd); - logger(LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Call to `%s' failed: %s", "ioctlsocket", sockstrerror(sockerrno)); return -1; } } @@ -261,12 +210,13 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { option = 1; setsockopt(nfd, SOL_SOCKET, SO_REUSEADDR, (void *)&option, sizeof option); + setsockopt(nfd, SOL_SOCKET, SO_BROADCAST, (void *)&option, sizeof option); if(udp_rcvbuf && setsockopt(nfd, SOL_SOCKET, SO_RCVBUF, (void *)&udp_rcvbuf, sizeof(udp_rcvbuf))) - logger(LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_RCVBUF to %i: %s", udp_rcvbuf, strerror(errno)); if(udp_sndbuf && setsockopt(nfd, SOL_SOCKET, SO_SNDBUF, (void *)&udp_sndbuf, sizeof(udp_sndbuf))) - logger(LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_WARNING, "Can't set UDP SO_SNDBUF to %i: %s", udp_sndbuf, strerror(errno)); #if defined(IPPROTO_IPV6) && defined(IPV6_V6ONLY) if(sa->sa.sa_family == AF_INET6) @@ -313,7 +263,7 @@ int setup_vpn_in_socket(const sockaddr_t *sa) { if(bind(nfd, &sa->sa, SALEN(sa->sa))) { closesocket(nfd); addrstr = sockaddr2hostname(sa); - logger(LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't bind to %s/udp: %s", addrstr, sockstrerror(sockerrno)); free(addrstr); return -1; } @@ -334,15 +284,16 @@ void retry_outgoing(outgoing_t *outgoing) { timeout_set(&outgoing->ev, retry_outgoing_handler, outgoing); event_add(&outgoing->ev, &(struct timeval){outgoing->timeout, 0}); - ifdebug(CONNECTIONS) logger(LOG_NOTICE, + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Trying to re-establish outgoing connection in %d seconds", outgoing->timeout); } void finish_connecting(connection_t *c) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); + logger(DEBUG_CONNECTIONS, LOG_INFO, "Connected to %s (%s)", c->name, c->hostname); - configure_tcp(c); + if(proxytype != PROXY_EXEC) + configure_tcp(c); c->last_ping_time = time(NULL); c->status.connecting = false; @@ -350,113 +301,68 @@ void finish_connecting(connection_t *c) { send_id(c); } -bool do_outgoing_connection(connection_t *c) { - char *address, *port, *space; - int result; +static void do_outgoing_pipe(connection_t *c, char *command) { +#ifndef HAVE_MINGW + int fd[2]; - if(!c->outgoing) { - logger(LOG_ERR, "do_outgoing_connection() for %s called without c->outgoing", c->name); - abort(); + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create socketpair: %s", strerror(errno)); + return; } -begin: - if(!c->outgoing->ai) { - if(!c->outgoing->cfg) { - ifdebug(CONNECTIONS) logger(LOG_ERR, "Could not set up a meta connection to %s", - c->name); - retry_outgoing(c->outgoing); - c->outgoing = NULL; - connection_del(c); - return false; - } - - get_config_string(c->outgoing->cfg, &address); - - space = strchr(address, ' '); - if(space) { - port = xstrdup(space + 1); - *space = 0; - } else { - if(!get_config_string(lookup_config(c->config_tree, "Port"), &port)) - port = xstrdup("655"); - } - - c->outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); - free(address); - free(port); - - c->outgoing->aip = c->outgoing->ai; - c->outgoing->cfg = lookup_config_next(c->config_tree, c->outgoing->cfg); + if(fork()) { + c->socket = fd[0]; + close(fd[1]); + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Using proxy %s", command); + return; } - if(!c->outgoing->aip) { - if(c->outgoing->ai) - freeaddrinfo(c->outgoing->ai); - c->outgoing->ai = NULL; - goto begin; - } + close(0); + close(1); + close(fd[0]); + dup2(fd[1], 0); + dup2(fd[1], 1); + close(fd[1]); - memcpy(&c->address, c->outgoing->aip->ai_addr, c->outgoing->aip->ai_addrlen); - c->outgoing->aip = c->outgoing->aip->ai_next; + // Other filedescriptors should be closed automatically by CLOEXEC - if(c->hostname) - free(c->hostname); + char *host = NULL; + char *port = NULL; - c->hostname = sockaddr2hostname(&c->address); + sockaddr2str(&c->address, &host, &port); + setenv("REMOTEADDRESS", host, true); + setenv("REMOTEPORT", port, true); + setenv("NODE", c->name, true); + setenv("NAME", myself->name, true); + if(netname) + setenv("NETNAME", netname, true); - ifdebug(CONNECTIONS) logger(LOG_INFO, "Trying to connect to %s (%s)", c->name, - c->hostname); - - c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); - - if(c->socket == -1) { - ifdebug(CONNECTIONS) logger(LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); - goto begin; - } - -#if defined(SOL_IPV6) && defined(IPV6_V6ONLY) - int option = 1; - if(c->address.sa.sa_family == AF_INET6) - setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option); + int result = system(command); + if(result < 0) + logger(DEBUG_ALWAYS, LOG_ERR, "Could not execute %s: %s", command, strerror(errno)); + else if(result) + logger(DEBUG_ALWAYS, LOG_ERR, "%s exited with non-zero status %d", command, result); + exit(result); +#else + logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type exec not supported on this platform!"); + return; #endif - - bind_to_interface(c->socket); - bind_to_address(c); - - /* Optimize TCP settings */ - - configure_tcp(c); - - /* Connect */ - - result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); - - if(result == -1) { - if(sockinprogress(sockerrno)) { - c->status.connecting = true; - return true; - } - - closesocket(c->socket); - - ifdebug(CONNECTIONS) logger(LOG_ERR, "%s: %s", c->hostname, sockstrerror(sockerrno)); - - goto begin; - } - - finish_connecting(c); - - return true; } static void handle_meta_write(int sock, short events, void *data) { - ifdebug(META) logger(LOG_DEBUG, "handle_meta_write() called"); - connection_t *c = data; ssize_t outlen = send(c->socket, c->outbuf.data + c->outbuf.offset, c->outbuf.len - c->outbuf.offset, 0); if(outlen <= 0) { - logger(LOG_ERR, "Onoes, outlen = %d (%s)", (int)outlen, strerror(errno)); + if(!errno || errno == EPIPE) { + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection closed by %s (%s)", c->name, c->hostname); + } else if(sockwouldblock(sockerrno)) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Sending %d bytes to %s (%s) would block", c->outbuf.len - c->outbuf.offset, c->name, c->hostname); + return; + } else { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not send %d bytes of data to %s (%s): %s", c->outbuf.len - c->outbuf.offset, c->name, c->hostname, strerror(errno)); + } + terminate_connection(c, c->status.active); return; } @@ -466,51 +372,151 @@ static void handle_meta_write(int sock, short events, void *data) { event_del(&c->outevent); } -void setup_outgoing_connection(outgoing_t *outgoing) { - connection_t *c; - node_t *n; - if(event_initialized(&outgoing->ev)) - event_del(&outgoing->ev); +bool do_outgoing_connection(outgoing_t *outgoing) { + char *address, *port, *space; + struct addrinfo *proxyai = NULL; + int result; - n = lookup_node(outgoing->name); - - if(n) - if(n->connection) { - ifdebug(CONNECTIONS) logger(LOG_INFO, "Already connected to %s", outgoing->name); - - n->connection->outgoing = outgoing; - return; +begin: + if(!outgoing->ai) { + if(!outgoing->cfg) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not set up a meta connection to %s", outgoing->name); + retry_outgoing(outgoing); + return false; } - c = new_connection(); + get_config_string(outgoing->cfg, &address); + + space = strchr(address, ' '); + if(space) { + port = xstrdup(space + 1); + *space = 0; + } else { + if(!get_config_string(lookup_config(outgoing->config_tree, "Port"), &port)) + port = xstrdup("655"); + } + + outgoing->ai = str2addrinfo(address, port, SOCK_STREAM); + free(address); + free(port); + + outgoing->aip = outgoing->ai; + outgoing->cfg = lookup_config_next(outgoing->config_tree, outgoing->cfg); + } + + if(!outgoing->aip) { + if(outgoing->ai) + freeaddrinfo(outgoing->ai); + outgoing->ai = NULL; + goto begin; + } + + connection_t *c = new_connection(); + c->outgoing = outgoing; + + memcpy(&c->address, outgoing->aip->ai_addr, outgoing->aip->ai_addrlen); + outgoing->aip = outgoing->aip->ai_next; + + c->hostname = sockaddr2hostname(&c->address); + + logger(DEBUG_CONNECTIONS, LOG_INFO, "Trying to connect to %s (%s)", outgoing->name, c->hostname); + + if(!proxytype) { + c->socket = socket(c->address.sa.sa_family, SOCK_STREAM, IPPROTO_TCP); + configure_tcp(c); + } else if(proxytype == PROXY_EXEC) { + do_outgoing_pipe(c, proxyhost); + } else { + proxyai = str2addrinfo(proxyhost, proxyport, SOCK_STREAM); + if(!proxyai) { + free_connection(c); + goto begin; + } + logger(DEBUG_CONNECTIONS, LOG_INFO, "Using proxy at %s port %s", proxyhost, proxyport); + c->socket = socket(proxyai->ai_family, SOCK_STREAM, IPPROTO_TCP); + } + + if(c->socket == -1) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Creating socket for %s failed: %s", c->hostname, sockstrerror(sockerrno)); + free_connection(c); + goto begin; + } + +#ifdef FD_CLOEXEC + fcntl(c->socket, F_SETFD, FD_CLOEXEC); +#endif + + if(proxytype != PROXY_EXEC) { +#if defined(SOL_IPV6) && defined(IPV6_V6ONLY) + int option = 1; + if(c->address.sa.sa_family == AF_INET6) + setsockopt(c->socket, SOL_IPV6, IPV6_V6ONLY, (void *)&option, sizeof option); +#endif + + bind_to_interface(c->socket); + } + + /* Connect */ + + if(!proxytype) { + result = connect(c->socket, &c->address.sa, SALEN(c->address.sa)); + } else if(proxytype == PROXY_EXEC) { + result = 0; + } else { + result = connect(c->socket, proxyai->ai_addr, proxyai->ai_addrlen); + freeaddrinfo(proxyai); + } + + if(result == -1 && !sockinprogress(sockerrno)) { + logger(DEBUG_CONNECTIONS, LOG_ERR, "Could not connect to %s (%s): %s", outgoing->name, c->hostname, sockstrerror(sockerrno)); + free_connection(c); + + goto begin; + } + + /* Now that there is a working socket, fill in the rest and register this connection. */ + + c->status.connecting = true; c->name = xstrdup(outgoing->name); c->outcipher = myself->connection->outcipher; c->outdigest = myself->connection->outdigest; c->outmaclength = myself->connection->outmaclength; c->outcompression = myself->connection->outcompression; - - init_configuration(&c->config_tree); - read_connection_config(c); - - outgoing->cfg = lookup_config(c->config_tree, "Address"); - - if(!outgoing->cfg) { - logger(LOG_ERR, "No address specified for %s", c->name); - free_connection(c); - return; - } - - c->outgoing = outgoing; c->last_ping_time = time(NULL); connection_add(c); - if (do_outgoing_connection(c)) { - event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c); - event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c); - event_add(&c->inevent, NULL); + event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c); + event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c); + event_add(&c->inevent, NULL); + + return true; +} + +void setup_outgoing_connection(outgoing_t *outgoing) { + if(event_initialized(&outgoing->ev)) + event_del(&outgoing->ev); + + node_t *n = lookup_node(outgoing->name); + + if(n && n->connection) { + logger(DEBUG_CONNECTIONS, LOG_INFO, "Already connected to %s", outgoing->name); + + n->connection->outgoing = outgoing; + return; } + + init_configuration(&outgoing->config_tree); + read_host_config(outgoing->config_tree, outgoing->name); + outgoing->cfg = lookup_config(outgoing->config_tree, "Address"); + + if(!outgoing->cfg) { + logger(DEBUG_ALWAYS, LOG_ERR, "No address specified for %s", outgoing->name); + return; + } + + do_outgoing_connection(outgoing); } /* @@ -526,7 +532,7 @@ void handle_new_meta_connection(int sock, short events, void *data) { fd = accept(sock, &sa.sa, &len); if(fd < 0) { - logger(LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Accepting a new connection failed: %s", sockstrerror(sockerrno)); return; } @@ -544,12 +550,12 @@ void handle_new_meta_connection(int sock, short events, void *data) { c->socket = fd; c->last_ping_time = time(NULL); - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection from %s", c->hostname); + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection from %s", c->hostname); event_set(&c->inevent, c->socket, EV_READ | EV_PERSIST, handle_meta_connection_data, c); event_set(&c->outevent, c->socket, EV_WRITE | EV_PERSIST, handle_meta_write, c); event_add(&c->inevent, NULL); - + configure_tcp(c); connection_add(c); @@ -559,9 +565,15 @@ void handle_new_meta_connection(int sock, short events, void *data) { } static void free_outgoing(outgoing_t *outgoing) { + if(event_initialized(&outgoing->ev)) + event_del(&outgoing->ev); + if(outgoing->ai) freeaddrinfo(outgoing->ai); + if(outgoing->config_tree) + exit_configuration(&outgoing->config_tree); + if(outgoing->name) free(outgoing->name); @@ -569,26 +581,60 @@ static void free_outgoing(outgoing_t *outgoing) { } void try_outgoing_connections(void) { - static config_t *cfg = NULL; - char *name; - outgoing_t *outgoing; - - outgoing_list = list_alloc((list_action_t)free_outgoing); - - for(cfg = lookup_config(config_tree, "ConnectTo"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + /* If there is no outgoing list yet, create one. Otherwise, mark all outgoings as deleted. */ + + if(!outgoing_list) { + outgoing_list = list_alloc((list_action_t)free_outgoing); + } else { + for list_each(outgoing_t, outgoing, outgoing_list) + outgoing->timeout = -1; + } + + /* Make sure there is one outgoing_t in the list for each ConnectTo. */ + + for(config_t *cfg = lookup_config(config_tree, "ConnectTo"); cfg; cfg = lookup_config_next(config_tree, cfg)) { + char *name; get_config_string(cfg, &name); if(!check_id(name)) { - logger(LOG_ERR, + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid name for outgoing connection in %s line %d", cfg->file, cfg->line); free(name); continue; } - outgoing = xmalloc_and_zero(sizeof *outgoing); - outgoing->name = name; - list_insert_tail(outgoing_list, outgoing); - setup_outgoing_connection(outgoing); + bool found = false; + + for list_each(outgoing_t, outgoing, outgoing_list) { + if(!strcmp(outgoing->name, name)) { + found = true; + outgoing->timeout = 0; + break; + } + } + + if(!found) { + outgoing_t *outgoing = xmalloc_and_zero(sizeof *outgoing); + outgoing->name = name; + list_insert_tail(outgoing_list, outgoing); + setup_outgoing_connection(outgoing); + } } + + /* Terminate any connections whose outgoing_t is to be deleted. */ + + for list_each(connection_t, c, connection_list) { + if(c->outgoing && c->outgoing->timeout == -1) { + c->outgoing = NULL; + logger(DEBUG_CONNECTIONS, LOG_INFO, "No more outgoing connection to %s", c->name); + terminate_connection(c, c->status.active); + } + } + + /* Delete outgoing_ts for which there is no ConnectTo. */ + + for list_each(outgoing_t, outgoing, outgoing_list) + if(outgoing->timeout == -1) + list_delete_node(outgoing_list, node); } diff --git a/src/netutl.c b/src/netutl.c index 8db252d..a71b370 100644 --- a/src/netutl.c +++ b/src/netutl.c @@ -1,7 +1,7 @@ /* netutl.c -- some supporting network utility code Copyright (C) 1998-2005 Ivo Timmermans - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -42,7 +42,7 @@ struct addrinfo *str2addrinfo(const char *address, const char *service, int sock err = getaddrinfo(address, service, &hint, &ai); if(err) { - logger(LOG_WARNING, "Error looking up %s port %s: %s", address, + logger(DEBUG_ALWAYS, LOG_WARNING, "Error looking up %s port %s: %s", address, service, gai_strerror(err)); return NULL; } @@ -62,8 +62,7 @@ sockaddr_t str2sockaddr(const char *address, const char *port) { err = getaddrinfo(address, port, &hint, &ai); if(err || !ai) { - ifdebug(SCARY_THINGS) - logger(LOG_DEBUG, "Unknown type address %s port %s", address, port); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Unknown type address %s port %s", address, port); result.sa.sa_family = AF_UNKNOWN; result.unknown.address = xstrdup(address); result.unknown.port = xstrdup(port); @@ -83,15 +82,17 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { int err; if(sa->sa.sa_family == AF_UNKNOWN) { - *addrstr = xstrdup(sa->unknown.address); - *portstr = xstrdup(sa->unknown.port); + if(addrstr) + *addrstr = xstrdup(sa->unknown.address); + if(portstr) + *portstr = xstrdup(sa->unknown.port); return; } err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if(err) { - logger(LOG_ERR, "Error while translating addresses: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while translating addresses: %s", gai_strerror(err)); abort(); } @@ -99,7 +100,7 @@ void sockaddr2str(const sockaddr_t *sa, char **addrstr, char **portstr) { scopeid = strchr(address, '%'); if(scopeid) - *scopeid = '\0'; /* Descope. */ + *scopeid = '\0'; /* Descope. */ if(addrstr) *addrstr = xstrdup(address); @@ -121,7 +122,7 @@ char *sockaddr2hostname(const sockaddr_t *sa) { err = getnameinfo(&sa->sa, SALEN(sa->sa), address, sizeof address, port, sizeof port, hostnames ? 0 : (NI_NUMERICHOST | NI_NUMERICSERV)); if(err) { - logger(LOG_ERR, "Error while looking up hostname: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Error while looking up hostname: %s", gai_strerror(err)); } @@ -152,7 +153,7 @@ int sockaddrcmp_noport(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)); default: - logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } @@ -195,7 +196,7 @@ int sockaddrcmp(const sockaddr_t *a, const sockaddr_t *b) { return memcmp(&a->in6.sin6_port, &b->in6.sin6_port, sizeof a->in6.sin6_port); default: - logger(LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", + logger(DEBUG_ALWAYS, LOG_ERR, "sockaddrcmp() was called with unknown address family %d, exitting!", a->sa.sa_family); abort(); } @@ -217,78 +218,10 @@ void sockaddrfree(sockaddr_t *a) { free(a->unknown.port); } } - + void sockaddrunmap(sockaddr_t *sa) { if(sa->sa.sa_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&sa->in6.sin6_addr)) { sa->in.sin_addr.s_addr = ((uint32_t *) & sa->in6.sin6_addr)[3]; sa->in.sin_family = AF_INET; } } - -/* Subnet mask handling */ - -int maskcmp(const void *va, const void *vb, int masklen) { - int i, m, result; - const char *a = va; - const char *b = vb; - - for(m = masklen, i = 0; m >= 8; m -= 8, i++) { - result = a[i] - b[i]; - if(result) - return result; - } - - if(m) - return (a[i] & (0x100 - (1 << (8 - m)))) - - (b[i] & (0x100 - (1 << (8 - m)))); - - return 0; -} - -void mask(void *va, int masklen, int len) { - int i; - char *a = va; - - i = masklen / 8; - masklen %= 8; - - if(masklen) - a[i++] &= (0x100 - (1 << (8 - masklen))); - - for(; i < len; i++) - a[i] = 0; -} - -void maskcpy(void *va, const void *vb, int masklen, int len) { - int i, m; - char *a = va; - const char *b = vb; - - for(m = masklen, i = 0; m >= 8; m -= 8, i++) - a[i] = b[i]; - - if(m) { - a[i] = b[i] & (0x100 - (1 << (8 - m))); - i++; - } - - for(; i < len; i++) - a[i] = 0; -} - -bool maskcheck(const void *va, int masklen, int len) { - int i; - const char *a = va; - - i = masklen / 8; - masklen %= 8; - - if(masklen && a[i++] & (0xff >> masklen)) - return false; - - for(; i < len; i++) - if(a[i] != 0) - return false; - - return true; -} diff --git a/src/netutl.h b/src/netutl.h index 7fc41e8..0fef2d6 100644 --- a/src/netutl.h +++ b/src/netutl.h @@ -1,7 +1,7 @@ /* netutl.h -- header file for netutl.c Copyright (C) 1998-2005 Ivo Timmermans - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,9 +34,5 @@ extern int sockaddrcmp_noport(const sockaddr_t *, const sockaddr_t *); extern void sockaddrunmap(sockaddr_t *); extern void sockaddrfree(sockaddr_t *); extern void sockaddrcpy(sockaddr_t *, const sockaddr_t *); -extern int maskcmp(const void *, const void *, int); -extern void maskcpy(void *, const void *, int, int); -extern void mask(void *, int, int); -extern bool maskcheck(const void *, int, int); -#endif /* __TINC_NETUTL_H__ */ +#endif /* __TINC_NETUTL_H__ */ diff --git a/src/node.c b/src/node.c index 38123e2..e67b9b9 100644 --- a/src/node.c +++ b/src/node.c @@ -1,6 +1,6 @@ /* node.c -- node tree management - Copyright (C) 2001-2011 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -21,16 +21,17 @@ #include "system.h" #include "control_common.h" -#include "splay_tree.h" +#include "hash.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "node.h" +#include "splay_tree.h" #include "utils.h" #include "xalloc.h" -splay_tree_t *node_tree; /* Known nodes, sorted by name */ -splay_tree_t *node_udp_tree; /* Known nodes, sorted by address and port */ +splay_tree_t *node_tree; +static hash_t *node_udp_cache; node_t *myself; @@ -38,24 +39,13 @@ static int node_compare(const node_t *a, const node_t *b) { return strcmp(a->name, b->name); } -static int node_udp_compare(const node_t *a, const node_t *b) { - int result; - - result = sockaddrcmp(&a->address, &b->address); - - if(result) - return result; - - return (a->name && b->name) ? strcmp(a->name, b->name) : 0; -} - void init_nodes(void) { node_tree = splay_alloc_tree((splay_compare_t) node_compare, (splay_action_t) free_node); - node_udp_tree = splay_alloc_tree((splay_compare_t) node_udp_compare, NULL); + node_udp_cache = hash_alloc(0x100, sizeof(sockaddr_t)); } void exit_nodes(void) { - splay_delete_tree(node_udp_tree); + hash_free(node_udp_cache); splay_delete_tree(node_tree); } @@ -85,12 +75,12 @@ void free_node(node_t *n) { cipher_close(&n->outcipher); digest_close(&n->outdigest); - ecdh_free(&n->ecdh); ecdsa_free(&n->ecdsa); + sptps_stop(&n->sptps); if(timeout_initialized(&n->mtuevent)) event_del(&n->mtuevent); - + if(n->hostname) free(n->hostname); @@ -108,23 +98,12 @@ void node_add(node_t *n) { } void node_del(node_t *n) { - splay_node_t *node, *next; - edge_t *e; - subnet_t *s; - - for(node = n->subnet_tree->head; node; node = next) { - next = node->next; - s = node->data; + for splay_each(subnet_t, s, n->subnet_tree) subnet_del(n, s); - } - for(node = n->edge_tree->head; node; node = next) { - next = node->next; - e = node->data; + for splay_each(edge_t, e, n->edge_tree) edge_del(e); - } - splay_delete(node_udp_tree, n); splay_delete(node_tree, n); } @@ -137,62 +116,41 @@ node_t *lookup_node(char *name) { } node_t *lookup_node_udp(const sockaddr_t *sa) { - node_t n = {NULL}; - - n.address = *sa; - n.name = NULL; - - return splay_search(node_udp_tree, &n); + return hash_search(node_udp_cache, sa); } void update_node_udp(node_t *n, const sockaddr_t *sa) { if(n == myself) { - logger(LOG_WARNING, "Trying to update UDP address of myself!"); + logger(DEBUG_ALWAYS, LOG_WARNING, "Trying to update UDP address of myself!"); return; } - splay_delete(node_udp_tree, n); - - if(n->hostname) - free(n->hostname); + hash_insert(node_udp_cache, &n->address, NULL); if(sa) { n->address = *sa; + hash_insert(node_udp_cache, sa, n); + free(n->hostname); n->hostname = sockaddr2hostname(&n->address); - splay_insert(node_udp_tree, n); - ifdebug(PROTOCOL) logger(LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); - } else { - memset(&n->address, 0, sizeof n->address); - n->hostname = NULL; - ifdebug(PROTOCOL) logger(LOG_DEBUG, "UDP address of %s cleared", n->name); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "UDP address of %s set to %s", n->name, n->hostname); } } bool dump_nodes(connection_t *c) { - splay_node_t *node; - node_t *n; - - for(node = node_tree->head; node; node = node->next) { - n = node->data; - send_request(c, "%d %d %s at %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %hd (min %hd max %hd)", CONTROL, REQ_DUMP_NODES, - n->name, n->hostname, cipher_get_nid(&n->outcipher), + for splay_each(node_t, n, node_tree) + send_request(c, "%d %d %s %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", CONTROL, REQ_DUMP_NODES, + n->name, n->hostname ?: "unknown port unknown", cipher_get_nid(&n->outcipher), digest_get_nid(&n->outdigest), (int)digest_length(&n->outdigest), n->outcompression, n->options, bitfield_to_int(&n->status, sizeof n->status), n->nexthop ? n->nexthop->name : "-", - n->via ? n->via->name ?: "-" : "-", n->distance, n->mtu, n->minmtu, n->maxmtu); - } + n->via ? n->via->name ?: "-" : "-", n->distance, n->mtu, n->minmtu, n->maxmtu, (long)n->last_state_change); return send_request(c, "%d %d", CONTROL, REQ_DUMP_NODES); } bool dump_traffic(connection_t *c) { - splay_node_t *node; - node_t *n; - - for(node = node_tree->head; node; node = node->next) { - n = node->data; + for splay_each(node_t, n, node_tree) send_request(c, "%d %d %s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, CONTROL, REQ_DUMP_TRAFFIC, n->name, n->in_packets, n->in_bytes, n->out_packets, n->out_bytes); - } return send_request(c, "%d %d", CONTROL, REQ_DUMP_TRAFFIC); } diff --git a/src/node.h b/src/node.h index 0ce7542..3327fca 100644 --- a/src/node.h +++ b/src/node.h @@ -1,6 +1,6 @@ /* node.h -- header for node.c - Copyright (C) 2001-2010 Guus Sliepen , + Copyright (C) 2001-2012 Guus Sliepen , 2001-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -25,62 +25,65 @@ #include "cipher.h" #include "connection.h" #include "digest.h" -#include "ecdh.h" #include "subnet.h" typedef struct node_status_t { - unsigned int unused_active:1; /* 1 if active (not used for nodes) */ - unsigned int validkey:1; /* 1 if we currently have a valid key for him */ - unsigned int unused_waitingforkey:1; /* 1 if we already sent out a request */ - unsigned int visited:1; /* 1 if this node has been visited by one of the graph algorithms */ - unsigned int reachable:1; /* 1 if this node is reachable in the graph */ - unsigned int indirect:1; /* 1 if this node is not directly reachable by us */ - unsigned int ecdh:1; /* 1 if this node supports ECDH key exchange */ - unsigned int unused:25; + unsigned int unused_active:1; /* 1 if active (not used for nodes) */ + unsigned int validkey:1; /* 1 if we currently have a valid key for him */ + unsigned int waitingforkey:1; /* 1 if we already sent out a request */ + unsigned int visited:1; /* 1 if this node has been visited by one of the graph algorithms */ + unsigned int reachable:1; /* 1 if this node is reachable in the graph */ + unsigned int indirect:1; /* 1 if this node is not directly reachable by us */ + unsigned int sptps:1; /* 1 if this node supports SPTPS */ + unsigned int udp_confirmed:1; /* 1 if the address is one that we received UDP traffic on */ + unsigned int unused:24; } node_status_t; typedef struct node_t { - char *name; /* name of this node */ - uint32_t options; /* options turned on for this node */ + char *name; /* name of this node */ + uint32_t options; /* options turned on for this node */ - sockaddr_t address; /* his real (internet) ip to send UDP packets to */ - char *hostname; /* the hostname of its real ip */ + int sock; /* Socket to use for outgoing UDP packets */ + sockaddr_t address; /* his real (internet) ip to send UDP packets to */ + char *hostname; /* the hostname of its real ip */ node_status_t status; + time_t last_state_change; time_t last_req_key; - ecdsa_t ecdsa; /* His public ECDSA key */ - ecdh_t ecdh; /* State for ECDH key exchange */ + ecdsa_t ecdsa; /* His public ECDSA key */ + sptps_t sptps; - cipher_t incipher; /* Cipher for UDP packets */ - digest_t indigest; /* Digest for UDP packets */ + cipher_t incipher; /* Cipher for UDP packets */ + digest_t indigest; /* Digest for UDP packets */ - cipher_t outcipher; /* Cipher for UDP packets */ - digest_t outdigest; /* Digest for UDP packets */ + cipher_t outcipher; /* Cipher for UDP packets */ + digest_t outdigest; /* Digest for UDP packets */ - int incompression; /* Compressionlevel, 0 = no compression */ - int outcompression; /* Compressionlevel, 0 = no compression */ + int incompression; /* Compressionlevel, 0 = no compression */ + int outcompression; /* Compressionlevel, 0 = no compression */ int distance; - struct node_t *nexthop; /* nearest node from us to him */ - struct node_t *via; /* next hop for UDP packets */ + struct node_t *nexthop; /* nearest node from us to him */ + struct edge_t *prevedge; /* nearest node from him to us */ + struct node_t *via; /* next hop for UDP packets */ - splay_tree_t *subnet_tree; /* Pointer to a tree of subnets belonging to this node */ + splay_tree_t *subnet_tree; /* Pointer to a tree of subnets belonging to this node */ - splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ + splay_tree_t *edge_tree; /* Edges with this node as one of the endpoints */ - struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */ + struct connection_t *connection; /* Connection associated with this node (if a direct connection exists) */ - uint32_t sent_seqno; /* Sequence number last sent to this node */ - uint32_t received_seqno; /* Sequence number last received from this node */ - uint32_t farfuture; /* Packets in a row that have arrived from the far future */ - unsigned char* late; /* Bitfield marking late packets */ + uint32_t sent_seqno; /* Sequence number last sent to this node */ + uint32_t received_seqno; /* Sequence number last received from this node */ + uint32_t farfuture; /* Packets in a row that have arrived from the far future */ + unsigned char* late; /* Bitfield marking late packets */ - length_t mtu; /* Maximum size of packets to send to this node */ - length_t minmtu; /* Probed minimum MTU */ - length_t maxmtu; /* Probed maximum MTU */ - int mtuprobes; /* Number of probes */ - struct event mtuevent; /* Probe event */ + length_t mtu; /* Maximum size of packets to send to this node */ + length_t minmtu; /* Probed minimum MTU */ + length_t maxmtu; /* Probed maximum MTU */ + int mtuprobes; /* Number of probes */ + struct event mtuevent; /* Probe event */ uint64_t in_packets; uint64_t in_bytes; @@ -90,7 +93,6 @@ typedef struct node_t { extern struct node_t *myself; extern splay_tree_t *node_tree; -extern splay_tree_t *node_udp_tree; extern void init_nodes(void); extern void exit_nodes(void); @@ -104,4 +106,4 @@ extern bool dump_nodes(struct connection_t *); extern bool dump_traffic(struct connection_t *); extern void update_node_udp(node_t *, const sockaddr_t *); -#endif /* __TINC_NODE_H__ */ +#endif /* __TINC_NODE_H__ */ diff --git a/src/openssl/cipher.c b/src/openssl/cipher.c index 86a1aca..553b4ad 100644 --- a/src/openssl/cipher.c +++ b/src/openssl/cipher.c @@ -1,6 +1,6 @@ /* cipher.c -- Symmetric block cipher handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -26,6 +26,12 @@ #include "logger.h" #include "xalloc.h" +typedef struct cipher_counter { + unsigned char counter[EVP_MAX_IV_LENGTH]; + unsigned char block[EVP_MAX_IV_LENGTH]; + int n; +} cipher_counter_t; + static bool cipher_open(cipher_t *cipher) { EVP_CIPHER_CTX_init(&cipher->ctx); @@ -38,7 +44,7 @@ bool cipher_open_by_name(cipher_t *cipher, const char *name) { if(cipher->cipher) return cipher_open(cipher); - logger(LOG_ERR, "Unknown cipher name '%s'!", name); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher name '%s'!", name); return false; } @@ -48,7 +54,7 @@ bool cipher_open_by_nid(cipher_t *cipher, int nid) { if(cipher->cipher) return cipher_open(cipher); - logger(LOG_ERR, "Unknown cipher nid %d!", nid); + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown cipher nid %d!", nid); return false; } @@ -59,10 +65,12 @@ bool cipher_open_blowfish_ofb(cipher_t *cipher) { void cipher_close(cipher_t *cipher) { EVP_CIPHER_CTX_cleanup(&cipher->ctx); + free(cipher->counter); + cipher->counter = NULL; } size_t cipher_keylength(const cipher_t *cipher) { - return cipher->cipher->key_len + cipher->cipher->iv_len; + return cipher->cipher->key_len + cipher->cipher->block_size; } bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) { @@ -76,7 +84,7 @@ bool cipher_set_key(cipher_t *cipher, void *key, bool encrypt) { if(result) return true; - logger(LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -91,28 +99,92 @@ bool cipher_set_key_from_rsa(cipher_t *cipher, void *key, size_t len, bool encry if(result) return true; - logger(LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } +bool cipher_set_counter(cipher_t *cipher, const void *counter, size_t len) { + if(len > cipher->cipher->block_size - 4) { + logger(DEBUG_ALWAYS, LOG_ERR, "Counter too long"); + abort(); + } + + memcpy(cipher->counter->counter + cipher->cipher->block_size - len, counter, len); + memset(cipher->counter->counter, 0, 4); + cipher->counter->n = 0; + + return true; +} + +bool cipher_set_counter_key(cipher_t *cipher, void *key) { + int result = EVP_EncryptInit_ex(&cipher->ctx, cipher->cipher, NULL, (unsigned char *)key, NULL); + if(!result) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while setting key: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + if(!cipher->counter) + cipher->counter = xmalloc_and_zero(sizeof *cipher->counter); + else + cipher->counter->n = 0; + + memcpy(cipher->counter->counter, (unsigned char *)key + cipher->cipher->key_len, cipher->cipher->block_size); + + return true; +} + +bool cipher_counter_xor(cipher_t *cipher, const void *indata, size_t inlen, void *outdata) { + if(!cipher->counter) { + logger(DEBUG_ALWAYS, LOG_ERR, "Counter not initialized"); + return false; + } + + const unsigned char *in = indata; + unsigned char *out = outdata; + + while(inlen--) { + // Encrypt the new counter value if we need it + if(!cipher->counter->n) { + int len; + if(!EVP_EncryptUpdate(&cipher->ctx, cipher->counter->block, &len, cipher->counter->counter, cipher->cipher->block_size)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + // Increase the counter value + for(int i = 0; i < cipher->cipher->block_size; i++) + if(++cipher->counter->counter[i]) + break; + } + + *out++ = *in++ ^ cipher->counter->counter[cipher->counter->n++]; + + if(cipher->counter->n >= cipher->cipher->block_size) + cipher->counter->n = 0; + } + + return true; +} + + bool cipher_encrypt(cipher_t *cipher, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool oneshot) { if(oneshot) { int len, pad; if(EVP_EncryptInit_ex(&cipher->ctx, NULL, NULL, NULL, NULL) && EVP_EncryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) && EVP_EncryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) { - *outlen = len + pad; + if(outlen) *outlen = len + pad; return true; } } else { int len; if(EVP_EncryptUpdate(&cipher->ctx, outdata, &len, indata, inlen)) { - *outlen = len; + if(outlen) *outlen = len; return true; } } - logger(LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while encrypting: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -122,18 +194,18 @@ bool cipher_decrypt(cipher_t *cipher, const void *indata, size_t inlen, void *ou if(EVP_DecryptInit_ex(&cipher->ctx, NULL, NULL, NULL, NULL) && EVP_DecryptUpdate(&cipher->ctx, (unsigned char *)outdata, &len, indata, inlen) && EVP_DecryptFinal(&cipher->ctx, (unsigned char *)outdata + len, &pad)) { - *outlen = len + pad; + if(outlen) *outlen = len + pad; return true; } } else { int len; if(EVP_EncryptUpdate(&cipher->ctx, outdata, &len, indata, inlen)) { - *outlen = len; + if(outlen) *outlen = len; return true; } } - logger(LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while decrypting: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } diff --git a/src/openssl/cipher.h b/src/openssl/cipher.h index 380384a..c9f89eb 100644 --- a/src/openssl/cipher.h +++ b/src/openssl/cipher.h @@ -1,6 +1,6 @@ /* cipher.h -- header file cipher.c - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,6 +29,7 @@ typedef struct cipher { EVP_CIPHER_CTX ctx; const EVP_CIPHER *cipher; + struct cipher_counter *counter; } cipher_t; extern bool cipher_open_by_name(cipher_t *, const char *); @@ -38,8 +39,11 @@ extern void cipher_close(cipher_t *); extern size_t cipher_keylength(const cipher_t *); extern bool cipher_set_key(cipher_t *, void *, bool); extern bool cipher_set_key_from_rsa(cipher_t *, void *, size_t, bool); +extern bool cipher_set_counter(cipher_t *, const void *, size_t); +extern bool cipher_set_counter_key(cipher_t *, void *); extern bool cipher_encrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool); extern bool cipher_decrypt(cipher_t *, const void *indata, size_t inlen, void *outdata, size_t *outlen, bool); +extern bool cipher_counter_xor(cipher_t *, const void *indata, size_t inlen, void *outdata); extern int cipher_get_nid(const cipher_t *); extern bool cipher_active(const cipher_t *); diff --git a/src/openssl/crypto.c b/src/openssl/crypto.c index db921d6..c695be8 100644 --- a/src/openssl/crypto.c +++ b/src/openssl/crypto.c @@ -26,12 +26,12 @@ #include "crypto.h" void crypto_init(void) { - RAND_load_file("/dev/urandom", 1024); + RAND_load_file("/dev/urandom", 1024); - ENGINE_load_builtin_engines(); - ENGINE_register_all_complete(); + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); - OpenSSL_add_all_algorithms(); + OpenSSL_add_all_algorithms(); } void crypto_exit(void) { diff --git a/src/openssl/digest.c b/src/openssl/digest.c index 09ed666..79db491 100644 --- a/src/openssl/digest.c +++ b/src/openssl/digest.c @@ -1,6 +1,6 @@ /* digest.c -- Digest handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -41,7 +41,7 @@ bool digest_open_by_name(digest_t *digest, const char *name, int maclength) { digest->key = NULL; if(!digest->digest) { - logger(LOG_DEBUG, "Unknown digest name '%s'!", name); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest name '%s'!", name); return false; } @@ -54,7 +54,7 @@ bool digest_open_by_nid(digest_t *digest, int nid, int maclength) { digest->key = NULL; if(!digest->digest) { - logger(LOG_DEBUG, "Unknown digest nid %d!", nid); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Unknown digest nid %d!", nid); return false; } @@ -78,8 +78,7 @@ bool digest_set_key(digest_t *digest, const void *key, size_t len) { } void digest_close(digest_t *digest) { - if(digest->key) - free(digest->key); + free(digest->key); digest->key = NULL; } @@ -95,7 +94,7 @@ bool digest_create(digest_t *digest, const void *indata, size_t inlen, void *out if(!EVP_DigestInit(&ctx, digest->digest) || !EVP_DigestUpdate(&ctx, indata, inlen) || !EVP_DigestFinal(&ctx, tmpdata, NULL)) { - logger(LOG_DEBUG, "Error creating digest: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "Error creating digest: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } } @@ -115,6 +114,10 @@ int digest_get_nid(const digest_t *digest) { return digest->digest ? digest->digest->type : 0; } +size_t digest_keylength(const digest_t *digest) { + return digest->digest->md_size; +} + size_t digest_length(const digest_t *digest) { return digest->maclength; } diff --git a/src/openssl/digest.h b/src/openssl/digest.h index f3855c9..c192249 100644 --- a/src/openssl/digest.h +++ b/src/openssl/digest.h @@ -1,6 +1,6 @@ /* digest.h -- header file digest.c - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2011 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,7 @@ extern bool digest_create(struct digest *, const void *indata, size_t inlen, voi extern bool digest_verify(struct digest *, const void *indata, size_t inlen, const void *digestdata); extern bool digest_set_key(struct digest *, const void *key, size_t len); extern int digest_get_nid(const struct digest *); +extern size_t digest_keylength(const struct digest *); extern size_t digest_length(const struct digest *); extern bool digest_active(const struct digest *); diff --git a/src/openssl/ecdh.c b/src/openssl/ecdh.c index 4dd399f..f94555d 100644 --- a/src/openssl/ecdh.c +++ b/src/openssl/ecdh.c @@ -1,6 +1,6 @@ /* ecdh.c -- Diffie-Hellman key exchange handling - Copyright (C) 2011 Guus Sliepen + Copyright (C) 2011-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,21 +30,32 @@ bool ecdh_generate_public(ecdh_t *ecdh, void *pubkey) { *ecdh = EC_KEY_new_by_curve_name(NID_secp521r1); - if(!EC_KEY_generate_key(*ecdh)) { - logger(LOG_ERR, "Generating EC key failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + if(!*ecdh) { + logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } - + + if(!EC_KEY_generate_key(*ecdh)) { + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Generating EC key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } + const EC_POINT *point = EC_KEY_get0_public_key(*ecdh); if(!point) { - logger(LOG_ERR, "Getting public key failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Getting public key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } size_t result = EC_POINT_point2oct(EC_KEY_get0_group(*ecdh), point, POINT_CONVERSION_COMPRESSED, pubkey, ECDH_SIZE, NULL); if(!result) { - logger(LOG_ERR, "Converting EC_POINT to binary failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + EC_KEY_free(*ecdh); + *ecdh = NULL; + logger(DEBUG_ALWAYS, LOG_ERR, "Converting EC_POINT to binary failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } return true; @@ -53,14 +64,15 @@ bool ecdh_generate_public(ecdh_t *ecdh, void *pubkey) { bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { EC_POINT *point = EC_POINT_new(EC_KEY_get0_group(*ecdh)); if(!point) { - logger(LOG_ERR, "EC_POINT_new() failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + logger(DEBUG_ALWAYS, LOG_ERR, "EC_POINT_new() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } int result = EC_POINT_oct2point(EC_KEY_get0_group(*ecdh), point, pubkey, ECDH_SIZE, NULL); if(!result) { - logger(LOG_ERR, "Converting binary to EC_POINT failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + EC_POINT_free(point); + logger(DEBUG_ALWAYS, LOG_ERR, "Converting binary to EC_POINT failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } result = ECDH_compute_key(shared, ECDH_SIZE, point, *ecdh, NULL); @@ -69,7 +81,7 @@ bool ecdh_compute_shared(ecdh_t *ecdh, const void *pubkey, void *shared) { *ecdh = NULL; if(!result) { - logger(LOG_ERR, "Computing Elliptic Curve Diffie-Hellman shared key failed: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Computing Elliptic Curve Diffie-Hellman shared key failed: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } diff --git a/src/openssl/ecdsa.c b/src/openssl/ecdsa.c index 43464d8..e2af6f9 100644 --- a/src/openssl/ecdsa.c +++ b/src/openssl/ecdsa.c @@ -1,6 +1,6 @@ /* ecdsa.c -- ECDSA key handling - Copyright (C) 2011 Guus Sliepen + Copyright (C) 2011-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -30,15 +30,19 @@ // bool ecdsa_set_base64_public_key(ecdsa_t *ecdsa, const char *p) { *ecdsa = EC_KEY_new_by_curve_name(NID_secp521r1); + if(!*ecdsa) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "EC_KEY_new_by_curve_name failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; + } int len = strlen(p); unsigned char pubkey[len / 4 * 3 + 3]; const unsigned char *ppubkey = pubkey; - len = b64decode(p, pubkey, len); + len = b64decode(p, (char *)pubkey, len); if(!o2i_ECPublicKey(ecdsa, &ppubkey, len)) { - logger(LOG_DEBUG, "o2i_ECPublicKey failed: %s", ERR_error_string(ERR_get_error(), NULL)); - abort(); + logger(DEBUG_ALWAYS, LOG_DEBUG, "o2i_ECPublicKey failed: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } return true; @@ -49,7 +53,7 @@ char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) { int len = i2o_ECPublicKey(*ecdsa, &pubkey); char *base64 = malloc(len * 4 / 3 + 5); - b64encode(pubkey, base64, len); + b64encode((char *)pubkey, base64, len); free(pubkey); @@ -64,7 +68,7 @@ bool ecdsa_read_pem_public_key(ecdsa_t *ecdsa, FILE *fp) { if(*ecdsa) return true; - logger(LOG_ERR, "Unable to read ECDSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -73,8 +77,8 @@ bool ecdsa_read_pem_private_key(ecdsa_t *ecdsa, FILE *fp) { if(*ecdsa) return true; - - logger(LOG_ERR, "Unable to read ECDSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read ECDSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -87,31 +91,27 @@ size_t ecdsa_size(ecdsa_t *ecdsa) { bool ecdsa_sign(ecdsa_t *ecdsa, const void *in, size_t len, void *sig) { unsigned int siglen = ECDSA_size(*ecdsa); - char hash[SHA512_DIGEST_LENGTH]; + unsigned char hash[SHA512_DIGEST_LENGTH]; SHA512(in, len, hash); memset(sig, 0, siglen); if(!ECDSA_sign(0, hash, sizeof hash, sig, &siglen, *ecdsa)) { - logger(LOG_DEBUG, "ECDSA_sign() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_sign() failed: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } - if(siglen != ECDSA_size(*ecdsa)) { - logger(LOG_ERR, "Signature length %d != %d", siglen, ECDSA_size(*ecdsa)); - } - return true; } bool ecdsa_verify(ecdsa_t *ecdsa, const void *in, size_t len, const void *sig) { unsigned int siglen = ECDSA_size(*ecdsa); - char hash[SHA512_DIGEST_LENGTH]; + unsigned char hash[SHA512_DIGEST_LENGTH]; SHA512(in, len, hash); if(!ECDSA_verify(0, hash, sizeof hash, sig, siglen, *ecdsa)) { - logger(LOG_DEBUG, "ECDSA_verify() failed: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_DEBUG, "ECDSA_verify() failed: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } diff --git a/src/openssl/ecdsagen.c b/src/openssl/ecdsagen.c index 86b79a2..883c77e 100644 --- a/src/openssl/ecdsagen.c +++ b/src/openssl/ecdsagen.c @@ -67,7 +67,7 @@ char *ecdsa_get_base64_public_key(ecdsa_t *ecdsa) { int len = i2o_ECPublicKey(*ecdsa, &pubkey); char *base64 = malloc(len * 4 / 3 + 5); - b64encode(pubkey, base64, len); + b64encode((char *)pubkey, base64, len); free(pubkey); diff --git a/src/openssl/prf.c b/src/openssl/prf.c index df7f445..b37efdf 100644 --- a/src/openssl/prf.c +++ b/src/openssl/prf.c @@ -1,6 +1,6 @@ /* prf.c -- Pseudo-Random Function for key material generation - Copyright (C) 2011 Guus Sliepen + Copyright (C) 2011-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -19,16 +19,18 @@ #include "system.h" +#include + #include "digest.h" #include "prf.h" /* Generate key material from a master secret and a seed, based on RFC 4346 section 5. - We use SHA512 and Whirlpool instead of MD5 and SHA1. + We use SHA512 instead of MD5 and SHA1. */ -static bool prf_xor(int nid, char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) { +static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) { digest_t digest; - + if(!digest_open_by_nid(&digest, nid, -1)) return false; @@ -65,12 +67,9 @@ static bool prf_xor(int nid, char *secret, size_t secretlen, char *seed, size_t return true; } -bool prf(char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { - /* Split secret in half, generate outlen bits with two different hash algorithms, - and XOR the results. */ - +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */ memset(out, 0, outlen); - return prf_xor(NID_sha512, secret, (secretlen + 1) / 2, seed, seedlen, out, outlen) - && prf_xor(NID_whirlpool, secret + secretlen / 2, (secretlen + 1) / 2, seed, seedlen, out, outlen); + return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen); } diff --git a/src/openssl/prf.h b/src/openssl/prf.h index 264d198..6525505 100644 --- a/src/openssl/prf.h +++ b/src/openssl/prf.h @@ -20,6 +20,6 @@ #ifndef __TINC_PRF_H__ #define __TINC_PRF_H__ -extern bool prf(char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen); +extern bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen); #endif diff --git a/src/openssl/rsa.c b/src/openssl/rsa.c index c3ea692..1b5ce56 100644 --- a/src/openssl/rsa.c +++ b/src/openssl/rsa.c @@ -1,6 +1,6 @@ /* rsa.c -- RSA key handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,16 +29,21 @@ bool rsa_set_hex_public_key(rsa_t *rsa, char *n, char *e) { *rsa = RSA_new(); - BN_hex2bn(&(*rsa)->n, n); - BN_hex2bn(&(*rsa)->e, e); + if(BN_hex2bn(&(*rsa)->n, n) != strlen(n)) + return false; + if(BN_hex2bn(&(*rsa)->e, e) != strlen(e)) + return false; return true; } bool rsa_set_hex_private_key(rsa_t *rsa, char *n, char *e, char *d) { *rsa = RSA_new(); - BN_hex2bn(&(*rsa)->n, n); - BN_hex2bn(&(*rsa)->e, e); - BN_hex2bn(&(*rsa)->d, d); + if(BN_hex2bn(&(*rsa)->n, n) != strlen(n)) + return false; + if(BN_hex2bn(&(*rsa)->e, e) != strlen(e)) + return false; + if(BN_hex2bn(&(*rsa)->d, d) != strlen(d)) + return false; return true; } @@ -49,13 +54,13 @@ bool rsa_read_pem_public_key(rsa_t *rsa, FILE *fp) { if(*rsa) return true; - + *rsa = PEM_read_RSA_PUBKEY(fp, rsa, NULL, NULL); if(*rsa) return true; - logger(LOG_ERR, "Unable to read RSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA public key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -64,8 +69,8 @@ bool rsa_read_pem_private_key(rsa_t *rsa, FILE *fp) { if(*rsa) return true; - - logger(LOG_ERR, "Unable to read RSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); + + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to read RSA private key: %s", ERR_error_string(ERR_get_error(), NULL)); return false; } @@ -77,16 +82,16 @@ bool rsa_public_encrypt(rsa_t *rsa, void *in, size_t len, void *out) { if(RSA_public_encrypt(len, in, out, *rsa, RSA_NO_PADDING) == len) return true; - logger(LOG_ERR, "Unable to perform RSA encryption: %s", ERR_error_string(ERR_get_error(), NULL)); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to perform RSA encryption: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } bool rsa_private_decrypt(rsa_t *rsa, void *in, size_t len, void *out) { if(RSA_private_decrypt(len, in, out, *rsa, RSA_NO_PADDING) == len) return true; - logger(LOG_ERR, "Unable to perform RSA decryption: %s", ERR_error_string(ERR_get_error(), NULL)); - return false; + logger(DEBUG_ALWAYS, LOG_ERR, "Unable to perform RSA decryption: %s", ERR_error_string(ERR_get_error(), NULL)); + return false; } bool rsa_active(rsa_t *rsa) { diff --git a/src/openssl/rsa.h b/src/openssl/rsa.h index 10fe346..9a826cb 100644 --- a/src/openssl/rsa.h +++ b/src/openssl/rsa.h @@ -1,6 +1,6 @@ /* rsa.h -- RSA key handling - Copyright (C) 2007 Guus Sliepen + Copyright (C) 2007-2011 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/src/prf.c b/src/prf.c new file mode 100644 index 0000000..b37efdf --- /dev/null +++ b/src/prf.c @@ -0,0 +1,75 @@ +/* + prf.c -- Pseudo-Random Function for key material generation + Copyright (C) 2011-2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include + +#include "digest.h" +#include "prf.h" + +/* Generate key material from a master secret and a seed, based on RFC 4346 section 5. + We use SHA512 instead of MD5 and SHA1. + */ + +static bool prf_xor(int nid, const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, ssize_t outlen) { + digest_t digest; + + if(!digest_open_by_nid(&digest, nid, -1)) + return false; + + if(!digest_set_key(&digest, secret, secretlen)) + return false; + + size_t len = digest_length(&digest); + + /* Data is what the "inner" HMAC function processes. + It consists of the previous HMAC result plus the seed. + */ + + char data[len + seedlen]; + memset(data, 0, len); + memcpy(data + len, seed, seedlen); + + char hash[len]; + + while(outlen > 0) { + /* Inner HMAC */ + digest_create(&digest, data, len + seedlen, data); + + /* Outer HMAC */ + digest_create(&digest, data, len + seedlen, hash); + + /* XOR the results of the outer HMAC into the out buffer */ + for(int i = 0; i < len && i < outlen; i++) + *out++ ^= hash[i]; + + outlen -= len; + } + + digest_close(&digest); + return true; +} + +bool prf(const char *secret, size_t secretlen, char *seed, size_t seedlen, char *out, size_t outlen) { + /* This construction allows us to easily switch back to a scheme where the PRF is calculated using two different digest algorithms. */ + memset(out, 0, outlen); + + return prf_xor(NID_sha512, secret, secretlen, seed, seedlen, out, outlen); +} diff --git a/src/process.c b/src/process.c index a7d0f26..37ba84b 100644 --- a/src/process.c +++ b/src/process.c @@ -1,7 +1,7 @@ /* process.c -- process management functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,13 +55,11 @@ static SERVICE_STATUS_HANDLE statushandle = 0; static bool install_service(void) { char command[4096] = "\""; - char **argp; - bool space; SERVICE_DESCRIPTION description = {"Virtual Private Network daemon"}; manager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(!manager) { - logger(LOG_ERR, "Could not open service manager: %s", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open service manager: %s", winerror(GetLastError())); return false; } @@ -74,13 +72,13 @@ static bool install_service(void) { strncat(command, "\"", sizeof command - strlen(command)); - for(argp = g_argv + 1; *argp; argp++) { - space = strchr(*argp, ' '); + for(char **argp = g_argv + 1; *argp; argp++) { + char *space = strchr(*argp, ' '); strncat(command, " ", sizeof command - strlen(command)); - + if(space) strncat(command, "\"", sizeof command - strlen(command)); - + strncat(command, *argp, sizeof command - strlen(command)); if(space) @@ -90,25 +88,25 @@ static bool install_service(void) { service = CreateService(manager, identname, identname, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, NULL, NULL, NULL, NULL, NULL); - + if(!service) { DWORD lasterror = GetLastError(); - logger(LOG_ERR, "Could not create %s service: %s", identname, winerror(lasterror)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not create %s service: %s", identname, winerror(lasterror)); if(lasterror != ERROR_SERVICE_EXISTS) return false; } if(service) { ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &description); - logger(LOG_INFO, "%s service installed", identname); + logger(DEBUG_ALWAYS, LOG_INFO, "%s service installed", identname); } else { service = OpenService(manager, identname, SERVICE_ALL_ACCESS); } if(!StartService(service, 0, NULL)) - logger(LOG_WARNING, "Could not start %s service: %s", identname, winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_WARNING, "Could not start %s service: %s", identname, winerror(GetLastError())); else - logger(LOG_INFO, "%s service started", identname); + logger(DEBUG_ALWAYS, LOG_INFO, "%s service started", identname); return true; } @@ -119,53 +117,49 @@ DWORD WINAPI controlhandler(DWORD request, DWORD type, LPVOID boe, LPVOID bah) { SetServiceStatus(statushandle, &status); return NO_ERROR; case SERVICE_CONTROL_STOP: - logger(LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_STOP"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_STOP"); break; case SERVICE_CONTROL_SHUTDOWN: - logger(LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_SHUTDOWN"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Got %s request", "SERVICE_CONTROL_SHUTDOWN"); break; default: - logger(LOG_WARNING, "Got unexpected request %d", request); + logger(DEBUG_ALWAYS, LOG_WARNING, "Got unexpected request %d", (int)request); return ERROR_CALL_NOT_IMPLEMENTED; } event_loopexit(NULL); - status.dwWaitHint = 30000; - status.dwCurrentState = SERVICE_STOP_PENDING; + status.dwWaitHint = 30000; + status.dwCurrentState = SERVICE_STOP_PENDING; SetServiceStatus(statushandle, &status); return NO_ERROR; } VOID WINAPI run_service(DWORD argc, LPTSTR* argv) { - int err = 1; extern int main2(int argc, char **argv); - - status.dwServiceType = SERVICE_WIN32; + status.dwServiceType = SERVICE_WIN32; status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; - status.dwWin32ExitCode = 0; - status.dwServiceSpecificExitCode = 0; - status.dwCheckPoint = 0; + status.dwWin32ExitCode = 0; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = 0; - statushandle = RegisterServiceCtrlHandlerEx(identname, controlhandler, NULL); + statushandle = RegisterServiceCtrlHandlerEx(identname, controlhandler, NULL); if (!statushandle) { - logger(LOG_ERR, "System call `%s' failed: %s", "RegisterServiceCtrlHandlerEx", winerror(GetLastError())); - err = 1; + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "RegisterServiceCtrlHandlerEx", winerror(GetLastError())); } else { - status.dwWaitHint = 30000; - status.dwCurrentState = SERVICE_START_PENDING; + status.dwWaitHint = 30000; + status.dwCurrentState = SERVICE_START_PENDING; SetServiceStatus(statushandle, &status); - status.dwWaitHint = 0; + status.dwWaitHint = 0; status.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(statushandle, &status); - err = main2(argc, argv); + main2(argc, argv); status.dwWaitHint = 0; - status.dwCurrentState = SERVICE_STOPPED; - //status.dwWin32ExitCode = err; + status.dwCurrentState = SERVICE_STOPPED; SetServiceStatus(statushandle, &status); } @@ -183,7 +177,7 @@ bool init_service(void) { return false; } else - logger(LOG_ERR, "System call `%s' failed: %s", "StartServiceCtrlDispatcher", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "StartServiceCtrlDispatcher", winerror(GetLastError())); } return true; @@ -206,19 +200,18 @@ bool detach(void) { if(do_detach) { #ifndef HAVE_MINGW if(daemon(0, 0)) { - fprintf(stderr, "Couldn't detach from terminal: %s", - strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Couldn't detach from terminal: %s", strerror(errno)); return false; } #else if(!statushandle) - exit(install_service()); + exit(!install_service()); #endif } openlogger(identname, use_logfile?LOGMODE_FILE:(do_detach?LOGMODE_SYSLOG:LOGMODE_STDERR)); - logger(LOG_NOTICE, "tincd %s (%s %s) starting, debug level %d", + logger(DEBUG_ALWAYS, LOG_NOTICE, "tincd %s (%s %s) starting, debug level %d", VERSION, __DATE__, __TIME__, debug_level); return true; @@ -226,46 +219,40 @@ bool detach(void) { bool execute_script(const char *name, char **envp) { #ifdef HAVE_SYSTEM - int status, len; char *scriptname; - int i; + char *command; -#ifndef HAVE_MINGW - len = xasprintf(&scriptname, "\"%s/%s\"", confbase, name); -#else - len = xasprintf(&scriptname, "\"%s/%s.bat\"", confbase, name); -#endif - if(len < 0) - return false; + xasprintf(&scriptname, "%s" SLASH "%s%s", confbase, name, scriptextension); - scriptname[len - 1] = '\0'; - -#ifndef HAVE_TUNEMU /* First check if there is a script */ - if(access(scriptname + 1, F_OK)) { + if(access(scriptname, F_OK)) { free(scriptname); return true; } -#endif - ifdebug(STATUS) logger(LOG_INFO, "Executing script %s", name); + logger(DEBUG_STATUS, LOG_INFO, "Executing script %s", name); #ifdef HAVE_PUTENV /* Set environment */ - - for(i = 0; envp[i]; i++) + + for(int i = 0; envp[i]; i++) putenv(envp[i]); #endif - scriptname[len - 1] = '\"'; - status = system(scriptname); + if(scriptinterpreter) + xasprintf(&command, "%s \"%s\"", scriptinterpreter, scriptname); + else + xasprintf(&command, "\"%s\"", scriptname); + int status = system(command); + + free(command); free(scriptname); /* Unset environment */ - for(i = 0; envp[i]; i++) { + for(int i = 0; envp[i]; i++) { char *e = strchr(envp[i], '='); if(e) { char p[e - envp[i] + 1]; @@ -277,22 +264,22 @@ bool execute_script(const char *name, char **envp) { #ifdef WEXITSTATUS if(status != -1) { - if(WIFEXITED(status)) { /* Child exited by itself */ + if(WIFEXITED(status)) { /* Child exited by itself */ if(WEXITSTATUS(status)) { - logger(LOG_ERR, "Script %s exited with non-zero status %d", + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s exited with non-zero status %d", name, WEXITSTATUS(status)); return false; } - } else if(WIFSIGNALED(status)) { /* Child was killed by a signal */ - logger(LOG_ERR, "Script %s was killed by signal %d (%s)", + } else if(WIFSIGNALED(status)) { /* Child was killed by a signal */ + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s was killed by signal %d (%s)", name, WTERMSIG(status), strsignal(WTERMSIG(status))); return false; - } else { /* Something strange happened */ - logger(LOG_ERR, "Script %s terminated abnormally", name); + } else { /* Something strange happened */ + logger(DEBUG_ALWAYS, LOG_ERR, "Script %s terminated abnormally", name); return false; } } else { - logger(LOG_ERR, "System call `%s' failed: %s", "system", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "system", strerror(errno)); return false; } #endif diff --git a/src/process.h b/src/process.h index ea7815e..0b296db 100644 --- a/src/process.h +++ b/src/process.h @@ -1,7 +1,7 @@ /* process.h -- header file for process.c Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2006 Guus Sliepen + 2000-2010 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -33,4 +33,4 @@ extern bool kill_other(int); extern bool init_service(void); #endif -#endif /* __TINC_PROCESS_H__ */ +#endif /* __TINC_PROCESS_H__ */ diff --git a/src/protocol.c b/src/protocol.c index 63163a0..3c08d72 100644 --- a/src/protocol.c +++ b/src/protocol.c @@ -1,7 +1,7 @@ /* protocol.c -- handle the meta-protocol, basic functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ bool experimental = false; /* Jumptable for the request handlers */ -static bool (*request_handlers[])(connection_t *, char *) = { +static bool (*request_handlers[])(connection_t *, const char *) = { id_h, metakey_h, challenge_h, chal_reply_h, ack_h, status_h, error_h, termreq_h, ping_h, pong_h, @@ -56,6 +56,9 @@ static char (*request_name[]) = { static splay_tree_t *past_request_tree; bool check_id(const char *id) { + if(!id || !*id) + return false; + for(; *id; id++) if(!isalnum(*id) && *id != '_') return false; @@ -80,85 +83,71 @@ bool send_request(connection_t *c, const char *format, ...) { va_end(args); if(len < 0 || len > MAXBUFSIZE - 1) { - logger(LOG_ERR, "Output buffer overflow while sending request to %s (%s)", + logger(DEBUG_ALWAYS, LOG_ERR, "Output buffer overflow while sending request to %s (%s)", c->name, c->hostname); return false; } - ifdebug(PROTOCOL) { - ifdebug(META) - logger(LOG_DEBUG, "Sending %s to %s (%s): %s", - request_name[atoi(request)], c->name, c->hostname, request); - else - logger(LOG_DEBUG, "Sending %s to %s (%s)", request_name[atoi(request)], - c->name, c->hostname); - } + logger(DEBUG_META, LOG_DEBUG, "Sending %s to %s (%s): %s", request_name[atoi(request)], c->name, c->hostname, request); request[len++] = '\n'; - if(c == broadcast) { + if(c == everyone) { broadcast_meta(NULL, request, len); return true; } else return send_meta(c, request, len); } -void forward_request(connection_t *from, char *request) { - /* Note: request is not zero terminated anymore after a call to this function! */ - ifdebug(PROTOCOL) { - ifdebug(META) - logger(LOG_DEBUG, "Forwarding %s from %s (%s): %s", - request_name[atoi(request)], from->name, from->hostname, request); - else - logger(LOG_DEBUG, "Forwarding %s from %s (%s)", - request_name[atoi(request)], from->name, from->hostname); - } +void forward_request(connection_t *from, const char *request) { + logger(DEBUG_META, LOG_DEBUG, "Forwarding %s from %s (%s): %s", request_name[atoi(request)], from->name, from->hostname, request); + // Create a temporary newline-terminated copy of the request int len = strlen(request); - request[len++] = '\n'; - broadcast_meta(from, request, len); + char tmp[len + 1]; + memcpy(tmp, request, len); + tmp[len] = '\n'; + broadcast_meta(from, tmp, sizeof tmp); } -bool receive_request(connection_t *c, char *request) { +bool receive_request(connection_t *c, const char *request) { + if(proxytype == PROXY_HTTP && c->allow_request == ID) { + if(!request[0] || request[0] == '\r') + return true; + if(!strncasecmp(request, "HTTP/1.1 ", 9)) { + if(!strncmp(request + 9, "200", 3)) { + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Proxy request granted"); + return true; + } else { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Proxy request rejected: %s", request + 9); + return false; + } + } + } + int reqno = atoi(request); if(reqno || *request == '0') { if((reqno < 0) || (reqno >= LAST) || !request_handlers[reqno]) { - ifdebug(META) - logger(LOG_DEBUG, "Unknown request from %s (%s): %s", - c->name, c->hostname, request); - else - logger(LOG_ERR, "Unknown request from %s (%s)", - c->name, c->hostname); - + logger(DEBUG_META, LOG_DEBUG, "Unknown request from %s (%s): %s", c->name, c->hostname, request); return false; } else { - ifdebug(PROTOCOL) { - ifdebug(META) - logger(LOG_DEBUG, "Got %s from %s (%s): %s", - request_name[reqno], c->name, c->hostname, request); - else - logger(LOG_DEBUG, "Got %s from %s (%s)", - request_name[reqno], c->name, c->hostname); - } + logger(DEBUG_META, LOG_DEBUG, "Got %s from %s (%s): %s", request_name[reqno], c->name, c->hostname, request); } if((c->allow_request != ALL) && (c->allow_request != reqno)) { - logger(LOG_ERR, "Unauthorized request from %s (%s)", c->name, - c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Unauthorized request from %s (%s)", c->name, c->hostname); return false; } if(!request_handlers[reqno](c, request)) { /* Something went wrong. Probably scriptkiddies. Terminate. */ - logger(LOG_ERR, "Error while processing %s from %s (%s)", - request_name[reqno], c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Error while processing %s from %s (%s)", request_name[reqno], c->name, c->hostname); return false; } } else { - logger(LOG_ERR, "Bogus data received from %s (%s)", - c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Bogus data received from %s (%s)", c->name, c->hostname); return false; } @@ -171,20 +160,20 @@ static int past_request_compare(const past_request_t *a, const past_request_t *b static void free_past_request(past_request_t *r) { if(r->request) - free(r->request); + free((char *)r->request); free(r); } static struct event past_request_event; -bool seen_request(char *request) { +bool seen_request(const char *request) { past_request_t *new, p = {NULL}; p.request = request; if(splay_search(past_request_tree, &p)) { - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Already seen request"); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Already seen request"); return true; } else { new = xmalloc(sizeof *new); @@ -197,15 +186,10 @@ bool seen_request(char *request) { } static void age_past_requests(int fd, short events, void *data) { - splay_node_t *node, *next; - past_request_t *p; int left = 0, deleted = 0; time_t now = time(NULL); - for(node = past_request_tree->head; node; node = next) { - next = node->next; - p = node->data; - + for splay_each(past_request_t, p, past_request_tree) { if(p->firstseen + pinginterval <= now) splay_delete_node(past_request_tree, node), deleted++; else @@ -213,7 +197,7 @@ static void age_past_requests(int fd, short events, void *data) { } if(left || deleted) - ifdebug(SCARY_THINGS) logger(LOG_DEBUG, "Aging past requests: deleted %d, left %d", + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Aging past requests: deleted %d, left %d", deleted, left); if(left) diff --git a/src/protocol.h b/src/protocol.h index 2c97641..1df54fc 100644 --- a/src/protocol.h +++ b/src/protocol.h @@ -1,7 +1,7 @@ /* protocol.h -- header for protocol.c Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ /* Protocol version. Different major versions are incompatible. */ #define PROT_MAJOR 17 -#define PROT_MINOR 2 +#define PROT_MINOR 2 /* Should not exceed 255! */ /* Silly Windows */ @@ -35,7 +35,7 @@ /* Request numbers */ typedef enum request_t { - ALL = -1, /* Guardian for allow_request */ + ALL = -1, /* Guardian for allow_request */ ID = 0, METAKEY, CHALLENGE, CHAL_REPLY, ACK, STATUS, ERROR, TERMREQ, PING, PONG, @@ -43,12 +43,15 @@ typedef enum request_t { ADD_EDGE, DEL_EDGE, KEY_CHANGED, REQ_KEY, ANS_KEY, PACKET, + /* Tinc 1.1 requests */ CONTROL, - LAST /* Guardian for the highest request number */ + REQ_PUBKEY, ANS_PUBKEY, + REQ_SPTPS, + LAST /* Guardian for the highest request number */ } request_t; typedef struct past_request_t { - char *request; + const char *request; time_t firstseen; } past_request_t; @@ -72,13 +75,13 @@ extern bool experimental; /* Basic functions */ extern bool send_request(struct connection_t *, const char *, ...) __attribute__ ((__format__(printf, 2, 3))); -extern void forward_request(struct connection_t *, char *); -extern bool receive_request(struct connection_t *, char *); +extern void forward_request(struct connection_t *, const char *); +extern bool receive_request(struct connection_t *, const char *); extern bool check_id(const char *); extern void init_requests(void); extern void exit_requests(void); -extern bool seen_request(char *); +extern bool seen_request(const char *); /* Requests */ @@ -89,7 +92,7 @@ extern bool send_challenge(struct connection_t *); extern bool send_chal_reply(struct connection_t *); extern bool send_ack(struct connection_t *); extern bool send_status(struct connection_t *, int, const char *); -extern bool send_error(struct connection_t *, int,const char *); +extern bool send_error(struct connection_t *, int, const char *); extern bool send_termreq(struct connection_t *); extern bool send_ping(struct connection_t *); extern bool send_pong(struct connection_t *); @@ -104,24 +107,24 @@ extern bool send_tcppacket(struct connection_t *, const struct vpn_packet_t *); /* Request handlers */ -extern bool id_h(struct connection_t *, char *); -extern bool metakey_h(struct connection_t *, char *); -extern bool challenge_h(struct connection_t *, char *); -extern bool chal_reply_h(struct connection_t *, char *); -extern bool ack_h(struct connection_t *, char *); -extern bool status_h(struct connection_t *, char *); -extern bool error_h(struct connection_t *, char *); -extern bool termreq_h(struct connection_t *, char *); -extern bool ping_h(struct connection_t *, char *); -extern bool pong_h(struct connection_t *, char *); -extern bool add_subnet_h(struct connection_t *, char *); -extern bool del_subnet_h(struct connection_t *, char *); -extern bool add_edge_h(struct connection_t *, char *); -extern bool del_edge_h(struct connection_t *, char *); -extern bool key_changed_h(struct connection_t *, char *); -extern bool req_key_h(struct connection_t *, char *); -extern bool ans_key_h(struct connection_t *, char *); -extern bool tcppacket_h(struct connection_t *, char *); -extern bool control_h(struct connection_t *, char *); +extern bool id_h(struct connection_t *, const char *); +extern bool metakey_h(struct connection_t *, const char *); +extern bool challenge_h(struct connection_t *, const char *); +extern bool chal_reply_h(struct connection_t *, const char *); +extern bool ack_h(struct connection_t *, const char *); +extern bool status_h(struct connection_t *, const char *); +extern bool error_h(struct connection_t *, const char *); +extern bool termreq_h(struct connection_t *, const char *); +extern bool ping_h(struct connection_t *, const char *); +extern bool pong_h(struct connection_t *, const char *); +extern bool add_subnet_h(struct connection_t *, const char *); +extern bool del_subnet_h(struct connection_t *, const char *); +extern bool add_edge_h(struct connection_t *, const char *); +extern bool del_edge_h(struct connection_t *, const char *); +extern bool key_changed_h(struct connection_t *, const char *); +extern bool req_key_h(struct connection_t *, const char *); +extern bool ans_key_h(struct connection_t *, const char *); +extern bool tcppacket_h(struct connection_t *, const char *); +extern bool control_h(struct connection_t *, const char *); -#endif /* __TINC_PROTOCOL_H__ */ +#endif /* __TINC_PROTOCOL_H__ */ diff --git a/src/protocol_auth.c b/src/protocol_auth.c index 4331e94..1b061b3 100644 --- a/src/protocol_auth.c +++ b/src/protocol_auth.c @@ -1,7 +1,7 @@ /* protocol_auth.c -- handle the meta-protocol, authentication Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2010 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,6 @@ #include "system.h" -#include "splay_tree.h" #include "conf.h" #include "connection.h" #include "control.h" @@ -31,15 +30,103 @@ #include "edge.h" #include "graph.h" #include "logger.h" +#include "meta.h" #include "net.h" #include "netutl.h" #include "node.h" #include "prf.h" #include "protocol.h" #include "rsa.h" +#include "sptps.h" #include "utils.h" #include "xalloc.h" +static bool send_proxyrequest(connection_t *c) { + switch(proxytype) { + case PROXY_HTTP: { + char *host; + char *port; + + sockaddr2str(&c->address, &host, &port); + send_request(c, "CONNECT %s:%s HTTP/1.1\r\n\r", host, port); + free(host); + free(port); + return true; + } + case PROXY_SOCKS4: { + if(c->address.sa.sa_family != AF_INET) { + logger(DEBUG_ALWAYS, LOG_ERR, "Cannot connect to an IPv6 host through a SOCKS 4 proxy!"); + return false; + } + char s4req[9 + (proxyuser ? strlen(proxyuser) : 0)]; + s4req[0] = 4; + s4req[1] = 1; + memcpy(s4req + 2, &c->address.in.sin_port, 2); + memcpy(s4req + 4, &c->address.in.sin_addr, 4); + if(proxyuser) + memcpy(s4req + 8, proxyuser, strlen(proxyuser)); + s4req[sizeof s4req - 1] = 0; + c->tcplen = 8; + return send_meta(c, s4req, sizeof s4req); + } + case PROXY_SOCKS5: { + int len = 3 + 6 + (c->address.sa.sa_family == AF_INET ? 4 : 16); + c->tcplen = 2; + if(proxypass) + len += 3 + strlen(proxyuser) + strlen(proxypass); + char s5req[len]; + int i = 0; + s5req[i++] = 5; + s5req[i++] = 1; + if(proxypass) { + s5req[i++] = 2; + s5req[i++] = 1; + s5req[i++] = strlen(proxyuser); + memcpy(s5req + i, proxyuser, strlen(proxyuser)); + i += strlen(proxyuser); + s5req[i++] = strlen(proxypass); + memcpy(s5req + i, proxypass, strlen(proxypass)); + i += strlen(proxypass); + c->tcplen += 2; + } else { + s5req[i++] = 0; + } + s5req[i++] = 5; + s5req[i++] = 1; + s5req[i++] = 0; + if(c->address.sa.sa_family == AF_INET) { + s5req[i++] = 1; + memcpy(s5req + i, &c->address.in.sin_addr, 4); + i += 4; + memcpy(s5req + i, &c->address.in.sin_port, 2); + i += 2; + c->tcplen += 10; + } else if(c->address.sa.sa_family == AF_INET6) { + s5req[i++] = 3; + memcpy(s5req + i, &c->address.in6.sin6_addr, 16); + i += 16; + memcpy(s5req + i, &c->address.in6.sin6_port, 2); + i += 2; + c->tcplen += 22; + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Address family %hx not supported for SOCKS 5 proxies!", c->address.sa.sa_family); + return false; + } + if(i > len) + abort(); + return send_meta(c, s5req, sizeof s5req); + } + case PROXY_SOCKS4A: + logger(DEBUG_ALWAYS, LOG_ERR, "Proxy type not implemented yet"); + return false; + case PROXY_EXEC: + return true; + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown proxy type"); + return false; + } +} + bool send_id(connection_t *c) { gettimeofday(&c->start, NULL); @@ -52,14 +139,18 @@ bool send_id(connection_t *c) { minor = myself->connection->protocol_minor; } + if(proxytype) + if(!send_proxyrequest(c)) + return false; + return send_request(c, "%d %s %d.%d", ID, myself->connection->name, myself->connection->protocol_major, minor); } -bool id_h(connection_t *c, char *request) { +bool id_h(connection_t *c, const char *request) { char name[MAX_STRING_SIZE]; if(sscanf(request, "%*d " MAX_STRING " %d.%d", name, &c->protocol_major, &c->protocol_minor) < 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ID", c->name, c->hostname); return false; } @@ -70,13 +161,17 @@ bool id_h(connection_t *c, char *request) { c->status.control = true; c->allow_request = CONTROL; c->last_ping_time = time(NULL) + 3600; + + free(c->name); + c->name = xstrdup(""); + return send_request(c, "%d %d %d", ACK, TINC_CTL_VERSION_CURRENT, getpid()); } /* Check if identity is a valid name */ if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ID", c->name, c->hostname, "invalid name"); return false; } @@ -85,7 +180,7 @@ bool id_h(connection_t *c, char *request) { if(c->outgoing) { if(strcmp(c->name, name)) { - logger(LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name, + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s is %s instead of %s", c->hostname, name, c->name); return false; } @@ -98,7 +193,7 @@ bool id_h(connection_t *c, char *request) { /* Check if version matches */ if(c->protocol_major != myself->connection->protocol_major) { - logger(LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d", + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s (%s) uses incompatible version %d.%d", c->name, c->hostname, c->protocol_major, c->protocol_minor); return false; } @@ -110,52 +205,41 @@ bool id_h(connection_t *c, char *request) { return send_ack(c); } - if(!c->config_tree) { - init_configuration(&c->config_tree); - - if(!read_connection_config(c)) { - logger(LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, - c->name); - return false; - } - - if(experimental && c->protocol_minor >= 2) - if(!read_ecdsa_public_key(c)) - return false; - } else { - if(!ecdsa_active(&c->ecdsa)) - c->protocol_minor = 1; - } - if(!experimental) c->protocol_minor = 0; + if(!c->config_tree) { + init_configuration(&c->config_tree); + + if(!read_host_config(c->config_tree, c->name)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Peer %s had unknown identity (%s)", c->hostname, c->name); + return false; + } + + if(experimental && c->protocol_minor >= 2) { + if(!read_ecdsa_public_key(c)) + return false; + } + } else { + if(c->protocol_minor && !ecdsa_active(&c->ecdsa)) + c->protocol_minor = 1; + } + c->allow_request = METAKEY; - if(c->protocol_minor >= 2) - return send_metakey_ec(c); - else + if(c->protocol_minor >= 2) { + c->allow_request = ACK; + char label[25 + strlen(myself->name) + strlen(c->name)]; + + if(c->outgoing) + snprintf(label, sizeof label, "tinc TCP key expansion %s %s", myself->name, c->name); + else + snprintf(label, sizeof label, "tinc TCP key expansion %s %s", c->name, myself->name); + + return sptps_start(&c->sptps, c, c->outgoing, false, myself->connection->ecdsa, c->ecdsa, label, sizeof label, send_meta_sptps, receive_meta_sptps); + } else { return send_metakey(c); -} - -bool send_metakey_ec(connection_t *c) { - logger(LOG_DEBUG, "Sending ECDH metakey to %s", c->name); - - size_t siglen = ecdsa_size(&myself->connection->ecdsa); - - char key[(ECDH_SIZE + siglen) * 2 + 1]; - - // TODO: include nonce? Use relevant parts of SSH or TLS protocol - - if(!ecdh_generate_public(&c->ecdh, key)) - return false; - - if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) - return false; - - b64encode(key, key, ECDH_SIZE + siglen); - - return send_request(c, "%d %s", METAKEY, key); + } } bool send_metakey(connection_t *c) { @@ -164,7 +248,7 @@ bool send_metakey(connection_t *c) { if(!cipher_open_blowfish_ofb(&c->outcipher)) return false; - + if(!digest_open_sha1(&c->outdigest, -1)) return false; @@ -191,9 +275,9 @@ bool send_metakey(connection_t *c) { cipher_set_key_from_rsa(&c->outcipher, key, len, true); - ifdebug(SCARY_THINGS) { + if(debug_level >= DEBUG_SCARY_THINGS) { bin2hex(key, hexkey, len); - logger(LOG_DEBUG, "Generated random meta key (unencrypted): %s", hexkey); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Generated random meta key (unencrypted): %s", hexkey); } /* Encrypt the random data @@ -204,7 +288,7 @@ bool send_metakey(connection_t *c) { */ if(!rsa_public_encrypt(&c->rsa, key, len, enckey)) { - logger(LOG_ERR, "Error during encryption of meta key for %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Error during encryption of meta key for %s (%s)", c->name, c->hostname); return false; } @@ -218,90 +302,12 @@ bool send_metakey(connection_t *c) { cipher_get_nid(&c->outcipher), digest_get_nid(&c->outdigest), c->outmaclength, c->outcompression, hexkey); - + c->status.encryptout = true; return result; } -static bool metakey_ec_h(connection_t *c, const char *request) { - size_t siglen = ecdsa_size(&c->ecdsa); - char key[MAX_STRING_SIZE]; - char sig[siglen]; - - logger(LOG_DEBUG, "Got ECDH metakey from %s", c->name); - - if(sscanf(request, "%*d " MAX_STRING, key) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname); - return false; - } - - int inlen = b64decode(key, key, sizeof key); - - if(inlen != (ECDH_SIZE + siglen)) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength"); - return false; - } - - if(!ecdsa_verify(&c->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "invalid ECDSA signature"); - return false; - } - - char shared[ECDH_SHARED_SIZE]; - - if(!ecdh_compute_shared(&c->ecdh, key, shared)) - return false; - - /* Update our crypto end */ - - if(!cipher_open_by_name(&c->incipher, "aes-256-ofb")) - return false; - if(!digest_open_by_name(&c->indigest, "sha512", -1)) - return false; - if(!cipher_open_by_name(&c->outcipher, "aes-256-ofb")) - return false; - if(!digest_open_by_name(&c->outdigest, "sha512", -1)) - return false; - - size_t mykeylen = cipher_keylength(&c->incipher); - size_t hiskeylen = cipher_keylength(&c->outcipher); - - char *mykey; - char *hiskey; - char *seed; - - if(strcmp(myself->name, c->name) < 0) { - mykey = key; - hiskey = key + mykeylen * 2; - xasprintf(&seed, "tinc TCP key expansion %s %s", myself->name, c->name); - } else { - mykey = key + hiskeylen * 2; - hiskey = key; - xasprintf(&seed, "tinc TCP key expansion %s %s", c->name, myself->name); - } - - if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2)) - return false; - - free(seed); - - cipher_set_key(&c->incipher, mykey, false); - digest_set_key(&c->indigest, mykey + mykeylen, mykeylen); - - cipher_set_key(&c->outcipher, hiskey, true); - digest_set_key(&c->outdigest, hiskey + hiskeylen, hiskeylen); - - c->status.decryptin = true; - c->status.encryptout = true; - c->allow_request = CHALLENGE; - - return send_challenge(c); -} - -bool metakey_h(connection_t *c, char *request) { - if(c->protocol_minor >= 2) - return metakey_ec_h(c, request); - +bool metakey_h(connection_t *c, const char *request) { char hexkey[MAX_STRING_SIZE]; int cipher, digest, maclength, compression; size_t len = rsa_size(&myself->connection->rsa); @@ -309,7 +315,7 @@ bool metakey_h(connection_t *c, char *request) { char key[len]; if(sscanf(request, "%*d %d %d %d %d " MAX_STRING, &cipher, &digest, &maclength, &compression, hexkey) != 5) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "METAKEY", c->name, c->hostname); return false; } @@ -320,31 +326,31 @@ bool metakey_h(connection_t *c, char *request) { /* Check if the length of the meta key is all right */ if(inlen != len) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength"); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong keylength"); return false; } /* Decrypt the meta key */ if(!rsa_private_decrypt(&myself->connection->rsa, enckey, len, key)) { - logger(LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Error during decryption of meta key for %s (%s)", c->name, c->hostname); return false; } - ifdebug(SCARY_THINGS) { + if(debug_level >= DEBUG_SCARY_THINGS) { bin2hex(key, hexkey, len); - logger(LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey); + logger(DEBUG_SCARY_THINGS, LOG_DEBUG, "Received random meta key (unencrypted): %s", hexkey); } /* Check and lookup cipher and digest algorithms */ if(!cipher_open_by_nid(&c->incipher, cipher) || !cipher_set_key_from_rsa(&c->incipher, key, len, false)) { - logger(LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of cipher from %s (%s)", c->name, c->hostname); return false; } if(!digest_open_by_nid(&c->indigest, digest, -1)) { - logger(LOG_ERR, "Error during initialisation of digest from %s (%s)", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Error during initialisation of digest from %s (%s)", c->name, c->hostname); return false; } @@ -356,7 +362,7 @@ bool metakey_h(connection_t *c, char *request) { } bool send_challenge(connection_t *c) { - size_t len = c->protocol_minor >= 2 ? ECDH_SIZE : rsa_size(&c->rsa); + size_t len = rsa_size(&c->rsa); char buffer[len * 2 + 1]; if(!c->hischallenge) @@ -375,14 +381,14 @@ bool send_challenge(connection_t *c) { return send_request(c, "%d %s", CHALLENGE, buffer); } -bool challenge_h(connection_t *c, char *request) { +bool challenge_h(connection_t *c, const char *request) { char buffer[MAX_STRING_SIZE]; - size_t len = c->protocol_minor >= 2 ? ECDH_SIZE : rsa_size(&myself->connection->rsa); + size_t len = rsa_size(&myself->connection->rsa); size_t digestlen = digest_length(&c->indigest); char digest[digestlen]; if(sscanf(request, "%*d " MAX_STRING, buffer) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHALLENGE", c->name, c->hostname); return false; } @@ -393,7 +399,7 @@ bool challenge_h(connection_t *c, char *request) { /* Check if the length of the challenge is all right */ if(inlen != len) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge length"); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge length"); return false; } @@ -412,11 +418,11 @@ bool challenge_h(connection_t *c, char *request) { return send_request(c, "%d %s", CHAL_REPLY, buffer); } -bool chal_reply_h(connection_t *c, char *request) { +bool chal_reply_h(connection_t *c, const char *request) { char hishash[MAX_STRING_SIZE]; if(sscanf(request, "%*d " MAX_STRING, hishash) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "CHAL_REPLY", c->name, c->hostname); return false; } @@ -428,15 +434,15 @@ bool chal_reply_h(connection_t *c, char *request) { /* Check if the length of the hash is all right */ if(inlen != digest_length(&c->outdigest)) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length"); + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply length"); return false; } /* Verify the hash */ - if(!digest_verify(&c->outdigest, c->hischallenge, c->protocol_minor >= 2 ? ECDH_SIZE : rsa_size(&c->rsa), hishash)) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply"); + if(!digest_verify(&c->outdigest, c->hischallenge, rsa_size(&c->rsa), hishash)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Possible intruder %s (%s): %s", c->name, c->hostname, "wrong challenge reply"); return false; } @@ -498,61 +504,48 @@ bool send_ack(connection_t *c) { get_config_int(lookup_config(c->config_tree, "Weight"), &c->estimated_weight); - return send_request(c, "%d %s %d %x", ACK, myport, c->estimated_weight, c->options); + return send_request(c, "%d %s %d %x", ACK, myport, c->estimated_weight, (c->options & 0xffffff) | (experimental ? (PROT_MINOR << 24) : 0)); } static void send_everything(connection_t *c) { - splay_node_t *node, *node2; - node_t *n; - subnet_t *s; - edge_t *e; - /* Send all known subnets and edges */ if(tunnelserver) { - for(node = myself->subnet_tree->head; node; node = node->next) { - s = node->data; + for splay_each(subnet_t, s, myself->subnet_tree) send_add_subnet(c, s); - } return; } - for(node = node_tree->head; node; node = node->next) { - n = node->data; - - for(node2 = n->subnet_tree->head; node2; node2 = node2->next) { - s = node2->data; + for splay_each(node_t, n, node_tree) { + for splay_each(subnet_t, s, n->subnet_tree) send_add_subnet(c, s); - } - for(node2 = n->edge_tree->head; node2; node2 = node2->next) { - e = node2->data; + for splay_each(edge_t, e, n->edge_tree) send_add_edge(c, e); - } } } -static bool upgrade_h(connection_t *c, char *request) { +static bool upgrade_h(connection_t *c, const char *request) { char pubkey[MAX_STRING_SIZE]; if(sscanf(request, "%*d " MAX_STRING, pubkey) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); return false; } if(ecdsa_active(&c->ecdsa) || read_ecdsa_public_key(c)) { - logger(LOG_INFO, "Already have ECDSA public key from %s (%s), not upgrading.", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_INFO, "Already have ECDSA public key from %s (%s), not upgrading.", c->name, c->hostname); return false; } - logger(LOG_INFO, "Got ECDSA public key from %s (%s), upgrading!", c->name, c->hostname); + logger(DEBUG_ALWAYS, LOG_INFO, "Got ECDSA public key from %s (%s), upgrading!", c->name, c->hostname); append_config_file(c->name, "ECDSAPublicKey", pubkey); c->allow_request = TERMREQ; return send_termreq(c); } -bool ack_h(connection_t *c, char *request) { +bool ack_h(connection_t *c, const char *request) { if(c->protocol_minor == 1) return upgrade_h(c, request); @@ -564,7 +557,7 @@ bool ack_h(connection_t *c, char *request) { bool choice; if(sscanf(request, "%*d " MAX_STRING " %d %x", hisport, &weight, &options) != 3) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ACK", c->name, c->hostname); return false; } @@ -580,11 +573,11 @@ bool ack_h(connection_t *c, char *request) { } else { if(n->connection) { /* Oh dear, we already have a connection to this node. */ - ifdebug(CONNECTIONS) logger(LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname); + logger(DEBUG_CONNECTIONS, LOG_DEBUG, "Established a second connection with %s (%s), closing old connection", n->connection->name, n->connection->hostname); if(n->connection->outgoing) { if(c->outgoing) - logger(LOG_WARNING, "Two outgoing connections to the same node!"); + logger(DEBUG_ALWAYS, LOG_WARNING, "Two outgoing connections to the same node!"); else c->outgoing = n->connection->outgoing; @@ -618,15 +611,12 @@ bool ack_h(connection_t *c, char *request) { c->options &= ~OPTION_CLAMP_MSS; } - if(c->protocol_minor > 0) - c->node->status.ecdh = true; - /* Activate this connection */ c->allow_request = ALL; c->status.active = true; - ifdebug(CONNECTIONS) logger(LOG_NOTICE, "Connection with %s (%s) activated", c->name, + logger(DEBUG_CONNECTIONS, LOG_NOTICE, "Connection with %s (%s) activated", c->name, c->hostname); /* Send him everything we know */ @@ -652,7 +642,7 @@ bool ack_h(connection_t *c, char *request) { if(tunnelserver) send_add_edge(c, c->edge); else - send_add_edge(broadcast, c->edge); + send_add_edge(everyone, c->edge); /* Run MST and SSSP algorithms */ diff --git a/src/protocol_edge.c b/src/protocol_edge.c index 1dd68d5..e285a6d 100644 --- a/src/protocol_edge.c +++ b/src/protocol_edge.c @@ -1,7 +1,7 @@ /* protocol_edge.c -- handle the meta-protocol, edges Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen 2009 Michael Tokarev This program is free software; you can redistribute it and/or modify @@ -21,7 +21,6 @@ #include "system.h" -#include "splay_tree.h" #include "conf.h" #include "connection.h" #include "edge.h" @@ -50,7 +49,7 @@ bool send_add_edge(connection_t *c, const edge_t *e) { return x; } -bool add_edge_h(connection_t *c, char *request) { +bool add_edge_h(connection_t *c, const char *request) { edge_t *e; node_t *from, *to; char from_name[MAX_STRING_SIZE]; @@ -63,7 +62,7 @@ bool add_edge_h(connection_t *c, char *request) { if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING" "MAX_STRING" "MAX_STRING" %x %d", from_name, to_name, to_address, to_port, &options, &weight) != 6) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_EDGE", c->name, c->hostname); return false; } @@ -71,7 +70,7 @@ bool add_edge_h(connection_t *c, char *request) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_EDGE", c->name, c->hostname, "invalid name"); return false; } @@ -88,7 +87,7 @@ bool add_edge_h(connection_t *c, char *request) { from != myself && from != c->node && to != myself && to != c->node) { /* ignore indirect edge registrations for tunnelserver */ - ifdebug(PROTOCOL) logger(LOG_WARNING, + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s)", "ADD_EDGE", c->name, c->hostname); return true; @@ -118,12 +117,12 @@ bool add_edge_h(connection_t *c, char *request) { if(e) { if(e->weight != weight || e->options != options || sockaddrcmp(&e->address, &address)) { if(from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not match existing entry", "ADD_EDGE", c->name, c->hostname); send_add_edge(c, e); return true; } else { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not match existing entry", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not match existing entry", "ADD_EDGE", c->name, c->hostname); edge_del(e); graph(); @@ -131,7 +130,7 @@ bool add_edge_h(connection_t *c, char *request) { } else return true; } else if(from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself which does not exist", "ADD_EDGE", c->name, c->hostname); contradicting_add_edge++; e = new_edge(); @@ -167,14 +166,14 @@ bool send_del_edge(connection_t *c, const edge_t *e) { e->from->name, e->to->name); } -bool del_edge_h(connection_t *c, char *request) { +bool del_edge_h(connection_t *c, const char *request) { edge_t *e; char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; node_t *from, *to; if(sscanf(request, "%*d %*x "MAX_STRING" "MAX_STRING, from_name, to_name) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_EDGE", c->name, c->hostname); return false; } @@ -182,7 +181,7 @@ bool del_edge_h(connection_t *c, char *request) { /* Check if names are valid */ if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_EDGE", c->name, c->hostname, "invalid name"); return false; } @@ -199,20 +198,20 @@ bool del_edge_h(connection_t *c, char *request) { from != myself && from != c->node && to != myself && to != c->node) { /* ignore indirect edge registrations for tunnelserver */ - ifdebug(PROTOCOL) logger(LOG_WARNING, + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s)", "DEL_EDGE", c->name, c->hostname); return true; } if(!from) { - ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } if(!to) { - ifdebug(PROTOCOL) logger(LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", + logger(DEBUG_PROTOCOL, LOG_ERR, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } @@ -222,16 +221,16 @@ bool del_edge_h(connection_t *c, char *request) { e = lookup_edge(from, to); if(!e) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) which does not appear in the edge tree", "DEL_EDGE", c->name, c->hostname); return true; } if(e->from == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", "DEL_EDGE", c->name, c->hostname); contradicting_del_edge++; - send_add_edge(c, e); /* Send back a correction */ + send_add_edge(c, e); /* Send back a correction */ return true; } @@ -254,7 +253,7 @@ bool del_edge_h(connection_t *c, char *request) { e = lookup_edge(to, myself); if(e) { if(!tunnelserver) - send_del_edge(broadcast, e); + send_del_edge(everyone, e); edge_del(e); } } diff --git a/src/protocol_key.c b/src/protocol_key.c index 5246e7d..b54df3c 100644 --- a/src/protocol_key.c +++ b/src/protocol_key.c @@ -1,7 +1,7 @@ /* protocol_key.c -- handle the meta-protocol, key exchange Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,43 +20,45 @@ #include "system.h" -#include "splay_tree.h" #include "cipher.h" #include "connection.h" #include "crypto.h" -#include "ecdh.h" #include "logger.h" #include "net.h" #include "netutl.h" #include "node.h" #include "prf.h" #include "protocol.h" +#include "sptps.h" #include "utils.h" #include "xalloc.h" static bool mykeyused = false; void send_key_changed(void) { - splay_node_t *node; - connection_t *c; - - send_request(broadcast, "%d %x %s", KEY_CHANGED, rand(), myself->name); + send_request(everyone, "%d %x %s", KEY_CHANGED, rand(), myself->name); /* Immediately send new keys to directly connected nodes to keep UDP mappings alive */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; - if(c->status.active && c->node && c->node->status.reachable) + for list_each(connection_t, c, connection_list) + if(c->status.active && c->node && c->node->status.reachable && !c->node->status.sptps) send_ans_key(c->node); + + /* Force key exchange for connections using SPTPS */ + + if(experimental) { + for splay_each(node_t, n, node_tree) + if(n->status.reachable && n->status.validkey && n->status.sptps) + sptps_force_kex(&n->sptps); } } -bool key_changed_h(connection_t *c, char *request) { +bool key_changed_h(connection_t *c, const char *request) { char name[MAX_STRING_SIZE]; node_t *n; if(sscanf(request, "%*d %*x " MAX_STRING, name) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED", + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "KEY_CHANGED", c->name, c->hostname); return false; } @@ -67,13 +69,15 @@ bool key_changed_h(connection_t *c, char *request) { n = lookup_node(name); if(!n) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist", "KEY_CHANGED", c->name, c->hostname, name); return true; } - n->status.validkey = false; - n->last_req_key = 0; + if(!n->status.sptps) { + n->status.validkey = false; + n->last_req_key = 0; + } /* Tell the others */ @@ -83,31 +87,137 @@ bool key_changed_h(connection_t *c, char *request) { return true; } -bool send_req_key(node_t *to) { - return send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, experimental ? 1 : 0); +static bool send_initial_sptps_data(void *handle, uint8_t type, const char *data, size_t len) { + node_t *to = handle; + to->sptps.send_data = send_sptps_data; + char buf[len * 4 / 3 + 5]; + b64encode(data, buf, len); + return send_request(to->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, to->name, REQ_KEY, buf); } -bool req_key_h(connection_t *c, char *request) { +bool send_req_key(node_t *to) { + if(to->status.sptps) { + if(!node_read_ecdsa_public_key(to)) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "No ECDSA key known for %s (%s)", to->name, to->hostname); + send_request(to->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, to->name, REQ_PUBKEY); + return true; + } + + if(to->sptps.label) + logger(DEBUG_ALWAYS, LOG_DEBUG, "send_req_key(%s) called while sptps->label != NULL!", to->name); + + char label[25 + strlen(myself->name) + strlen(to->name)]; + snprintf(label, sizeof label, "tinc UDP key expansion %s %s", myself->name, to->name); + sptps_stop(&to->sptps); + to->status.validkey = false; + to->status.waitingforkey = true; + to->last_req_key = time(NULL); + to->incompression = myself->incompression; + return sptps_start(&to->sptps, to, true, true, myself->connection->ecdsa, to->ecdsa, label, sizeof label, send_initial_sptps_data, receive_sptps_record); + } + + return send_request(to->nexthop->connection, "%d %s %s", REQ_KEY, myself->name, to->name); +} + +/* REQ_KEY is overloaded to allow arbitrary requests to be routed between two nodes. */ + +static bool req_key_ext_h(connection_t *c, const char *request, node_t *from, int reqno) { + switch(reqno) { + case REQ_PUBKEY: { + char *pubkey = ecdsa_get_base64_public_key(&myself->connection->ecdsa); + send_request(from->nexthop->connection, "%d %s %s %d %s", REQ_KEY, myself->name, from->name, ANS_PUBKEY, pubkey); + free(pubkey); + return true; + } + + case ANS_PUBKEY: { + if(node_read_ecdsa_public_key(from)) { + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got ANS_PUBKEY from %s (%s) even though we already have his pubkey", from->name, from->hostname); + return true; + } + + char pubkey[MAX_STRING_SIZE]; + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, pubkey) != 1 || !ecdsa_set_base64_public_key(&from->ecdsa, pubkey)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_PUBKEY", from->name, from->hostname, "invalid pubkey"); + return true; + } + + logger(DEBUG_PROTOCOL, LOG_INFO, "Learned ECDSA public key from %s (%s)", from->name, from->hostname); + append_config_file(from->name, "ECDSAPublicKey", pubkey); + return true; + } + + case REQ_KEY: { + if(!node_read_ecdsa_public_key(from)) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "No ECDSA key known for %s (%s)", from->name, from->hostname); + send_request(from->nexthop->connection, "%d %s %s %d", REQ_KEY, myself->name, from->name, REQ_PUBKEY); + return true; + } + + if(from->sptps.label) + logger(DEBUG_ALWAYS, LOG_DEBUG, "Got REQ_KEY from %s while we already started a SPTPS session!", from->name); + + char buf[MAX_STRING_SIZE]; + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_SPTPS_START", from->name, from->hostname, "invalid SPTPS data"); + return true; + } + int len = b64decode(buf, buf, strlen(buf)); + + char label[25 + strlen(from->name) + strlen(myself->name)]; + snprintf(label, sizeof label, "tinc UDP key expansion %s %s", from->name, myself->name); + sptps_stop(&from->sptps); + from->status.validkey = false; + from->status.waitingforkey = true; + from->last_req_key = time(NULL); + sptps_start(&from->sptps, from, false, true, myself->connection->ecdsa, from->ecdsa, label, sizeof label, send_sptps_data, receive_sptps_record); + sptps_receive_data(&from->sptps, buf, len); + return true; + } + + case REQ_SPTPS: { + if(!from->status.validkey) { + logger(DEBUG_PROTOCOL, LOG_ERR, "Got REQ_SPTPS from %s (%s) but we don't have a valid key yet", from->name, from->hostname); + return true; + } + + char buf[MAX_STRING_SIZE]; + if(sscanf(request, "%*d %*s %*s %*d " MAX_STRING, buf) != 1) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_SPTPS", from->name, from->hostname, "invalid SPTPS data"); + return true; + } + int len = b64decode(buf, buf, strlen(buf)); + sptps_receive_data(&from->sptps, buf, len); + return true; + } + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown extended REQ_KEY request from %s (%s): %s", from->name, from->hostname, request); + return true; + } +} + +bool req_key_h(connection_t *c, const char *request) { char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; node_t *from, *to; - int kx_version = 0; + int reqno = 0; - if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &kx_version) < 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, + if(sscanf(request, "%*d " MAX_STRING " " MAX_STRING " %d", from_name, to_name, &reqno) < 2) { + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "REQ_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "REQ_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(from_name); if(!from) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, from_name); return true; } @@ -115,25 +225,26 @@ bool req_key_h(connection_t *c, char *request) { to = lookup_node(to_name); if(!to) { - logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "REQ_KEY", c->name, c->hostname, to_name); return true; } /* Check if this key request is for us */ - if(to == myself) { /* Yes, send our own key back */ - if(experimental && kx_version >= 1) { - logger(LOG_DEBUG, "Got ECDH key request from %s", from->name); - from->status.ecdh = true; - } + if(to == myself) { /* Yes */ + /* Is this an extended REQ_KEY message? */ + if(experimental && reqno) + return req_key_ext_h(c, request, from, reqno); + + /* No, just send our key back */ send_ans_key(from); } else { if(tunnelserver) return true; if(!to->status.reachable) { - logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "REQ_KEY", c->name, c->hostname, to_name); return true; } @@ -144,41 +255,16 @@ bool req_key_h(connection_t *c, char *request) { return true; } -bool send_ans_key_ecdh(node_t *to) { - int siglen = ecdsa_size(&myself->connection->ecdsa); - char key[(ECDH_SIZE + siglen) * 2 + 1]; - - if(!ecdh_generate_public(&to->ecdh, key)) - return false; - - if(!ecdsa_sign(&myself->connection->ecdsa, key, ECDH_SIZE, key + ECDH_SIZE)) - return false; - - b64encode(key, key, ECDH_SIZE + siglen); - - char *pubkey = ecdsa_get_base64_public_key(&myself->connection->ecdsa); - - if(!pubkey) - return false; - - int result = send_request(to->nexthop->connection, "%d %s %s ECDH:%s:%s %d %d %zu %d", ANS_KEY, - myself->name, to->name, key, pubkey, - cipher_get_nid(&myself->incipher), - digest_get_nid(&myself->indigest), - digest_length(&myself->indigest), - myself->incompression); - - free(pubkey); - return result; -} - bool send_ans_key(node_t *to) { - if(experimental && to->status.ecdh) - return send_ans_key_ecdh(to); + if(to->status.sptps) + abort(); size_t keylen = cipher_keylength(&myself->incipher); char key[keylen * 2 + 1]; + cipher_close(&to->incipher); + digest_close(&to->indigest); + cipher_open_by_nid(&to->incipher, cipher_get_nid(&myself->incipher)); digest_open_by_nid(&to->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest)); to->incompression = myself->incompression; @@ -194,40 +280,40 @@ bool send_ans_key(node_t *to) { to->received_seqno = 0; if(replaywin) memset(to->late, 0, replaywin); - return send_request(to->nexthop->connection, "%d %s %s %s %d %d %zu %d", ANS_KEY, + return send_request(to->nexthop->connection, "%d %s %s %s %d %d %d %d", ANS_KEY, myself->name, to->name, key, cipher_get_nid(&to->incipher), digest_get_nid(&to->indigest), - digest_length(&to->indigest), + (int)digest_length(&to->indigest), to->incompression); } -bool ans_key_h(connection_t *c, char *request) { +bool ans_key_h(connection_t *c, const char *request) { char from_name[MAX_STRING_SIZE]; char to_name[MAX_STRING_SIZE]; char key[MAX_STRING_SIZE]; - char address[MAX_STRING_SIZE] = ""; - char port[MAX_STRING_SIZE] = ""; + char address[MAX_STRING_SIZE] = ""; + char port[MAX_STRING_SIZE] = ""; int cipher, digest, maclength, compression, keylen; node_t *from, *to; if(sscanf(request, "%*d "MAX_STRING" "MAX_STRING" "MAX_STRING" %d %d %d %d "MAX_STRING" "MAX_STRING, from_name, to_name, key, &cipher, &digest, &maclength, &compression, address, port) < 7) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ANS_KEY", c->name, c->hostname); return false; } if(!check_id(from_name) || !check_id(to_name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ANS_KEY", c->name, c->hostname, "invalid name"); return false; } from = lookup_node(from_name); if(!from) { - logger(LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) origin %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, from_name); return true; } @@ -235,7 +321,7 @@ bool ans_key_h(connection_t *c, char *request) { to = lookup_node(to_name); if(!to) { - logger(LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", + logger(DEBUG_ALWAYS, LOG_ERR, "Got %s from %s (%s) destination %s which does not exist in our connection list", "ANS_KEY", c->name, c->hostname, to_name); return true; } @@ -247,14 +333,14 @@ bool ans_key_h(connection_t *c, char *request) { return true; if(!to->status.reachable) { - logger(LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", + logger(DEBUG_ALWAYS, LOG_WARNING, "Got %s from %s (%s) destination %s which is not reachable", "ANS_KEY", c->name, c->hostname, to_name); return true; } if(!*address && from->address.sa.sa_family != AF_UNSPEC) { char *address, *port; - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Appending reflexive UDP address to ANS_KEY from %s to %s", from->name, to->name); sockaddr2str(&from->address, &address, &port); send_request(to->nexthop->connection, "%s %s %s", request, address, port); free(address); @@ -265,142 +351,77 @@ bool ans_key_h(connection_t *c, char *request) { return send_request(to->nexthop->connection, "%s", request); } - /* Check and lookup cipher and digest algorithms */ - - if(!cipher_open_by_nid(&from->outcipher, cipher)) { - logger(LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname); - return false; - } - - if(!digest_open_by_nid(&from->outdigest, digest, maclength)) { - logger(LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, from->hostname); - return false; - } - - if(maclength != digest_length(&from->outdigest)) { - logger(LOG_ERR, "Node %s (%s) uses bogus MAC length!", from->name, from->hostname); - return false; - } + /* Don't use key material until every check has passed. */ + cipher_close(&from->outcipher); + digest_close(&from->outdigest); + from->status.validkey = false; if(compression < 0 || compression > 11) { - logger(LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses bogus compression level!", from->name, from->hostname); return true; } from->outcompression = compression; - /* ECDH or old-style key exchange? */ - - if(experimental && !strncmp(key, "ECDH:", 5)) { - char *pubkey = strchr(key + 5, ':'); - if(pubkey) - *pubkey++ = 0; - - /* Check if we already have an ECDSA public key for this node. - * If not, use the one from the key exchange, and store it. */ + /* SPTPS or old-style key exchange? */ - if(!node_read_ecdsa_public_key(from)) { - if(!pubkey) { - logger(LOG_ERR, "No ECDSA public key known for %s (%s), cannot verify ECDH key exchange!", from->name, from->hostname); - return true; + if(from->status.sptps) { + char buf[strlen(key)]; + int len = b64decode(key, buf, strlen(key)); + + if(!sptps_receive_data(&from->sptps, buf, len)) + logger(DEBUG_ALWAYS, LOG_ERR, "Error processing SPTPS data from %s (%s)", from->name, from->hostname); + + if(from->status.validkey) { + if(*address && *port) { + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + sockaddr_t sa = str2sockaddr(address, port); + update_node_udp(from, &sa); } - if(!ecdsa_set_base64_public_key(&from->ecdsa, pubkey)) - return true; - - append_config_file(from->name, "ECDSAPublicKey", pubkey); + if(from->options & OPTION_PMTU_DISCOVERY) + send_mtu_probe(from); } - int siglen = ecdsa_size(&from->ecdsa); - int keylen = b64decode(key + 5, key + 5, sizeof key - 5); - - if(keylen != ECDH_SIZE + siglen) { - logger(LOG_ERR, "Node %s (%s) uses wrong keylength! %d != %d", from->name, from->hostname, keylen, ECDH_SIZE + siglen); - return true; - } - - if(ECDH_SHARED_SIZE < cipher_keylength(&from->outcipher)) { - logger(LOG_ERR, "ECDH key too short for cipher of %s!", from->name); - return true; - } - - if(!ecdsa_verify(&from->ecdsa, key + 5, ECDH_SIZE, key + 5 + ECDH_SIZE)) { - logger(LOG_ERR, "Possible intruder %s (%s): %s", from->name, from->hostname, "invalid ECDSA signature"); - return true; - } - - if(!from->ecdh) { - from->status.ecdh = true; - if(!send_ans_key(from)) - return true; - } - - char shared[ECDH_SHARED_SIZE * 2 + 1]; - - if(!ecdh_compute_shared(&from->ecdh, key + 5, shared)) - return true; - - /* Update our crypto end */ - - size_t mykeylen = cipher_keylength(&myself->incipher); - size_t hiskeylen = cipher_keylength(&from->outcipher); - - char *mykey; - char *hiskey; - char *seed; - - if(strcmp(myself->name, from->name) < 0) { - mykey = key; - hiskey = key + mykeylen * 2; - xasprintf(&seed, "tinc UDP key expansion %s %s", myself->name, from->name); - } else { - mykey = key + hiskeylen * 2; - hiskey = key; - xasprintf(&seed, "tinc UDP key expansion %s %s", from->name, myself->name); - } - - if(!prf(shared, ECDH_SHARED_SIZE, seed, strlen(seed), key, hiskeylen * 2 + mykeylen * 2)) - return true; - - free(seed); - - cipher_open_by_nid(&from->incipher, cipher_get_nid(&myself->incipher)); - digest_open_by_nid(&from->indigest, digest_get_nid(&myself->indigest), digest_length(&myself->indigest)); - from->incompression = myself->incompression; - - cipher_set_key(&from->incipher, mykey, false); - digest_set_key(&from->indigest, mykey + mykeylen, mykeylen); - - cipher_set_key(&from->outcipher, hiskey, true); - digest_set_key(&from->outdigest, hiskey + hiskeylen, hiskeylen); - - // Reset sequence number and late packet window - mykeyused = true; - from->received_seqno = 0; - if(replaywin) - memset(from->late, 0, replaywin); - - if(strcmp(myself->name, from->name) < 0) - memmove(key, key + mykeylen * 2, hiskeylen * 2); - } else { - keylen = hex2bin(key, key, sizeof key); - - if(keylen != cipher_keylength(&from->outcipher)) { - logger(LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); - return true; - } - - /* Update our copy of the origin's packet key */ - - cipher_set_key(&from->outcipher, key, true); - digest_set_key(&from->outdigest, key, keylen); + return true; } + /* Check and lookup cipher and digest algorithms */ + + if(!cipher_open_by_nid(&from->outcipher, cipher)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown cipher!", from->name, from->hostname); + return false; + } + + if(!digest_open_by_nid(&from->outdigest, digest, maclength)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses unknown digest!", from->name, from->hostname); + return false; + } + + if(maclength != digest_length(&from->outdigest)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses bogus MAC length!", from->name, from->hostname); + return false; + } + + /* Process key */ + + keylen = hex2bin(key, key, sizeof key); + + if(keylen != cipher_keylength(&from->outcipher)) { + logger(DEBUG_ALWAYS, LOG_ERR, "Node %s (%s) uses wrong keylength!", from->name, from->hostname); + return true; + } + + /* Update our copy of the origin's packet key */ + + cipher_set_key(&from->outcipher, key, true); + digest_set_key(&from->outdigest, key, keylen); + from->status.validkey = true; from->sent_seqno = 0; if(*address && *port) { - ifdebug(PROTOCOL) logger(LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); + logger(DEBUG_PROTOCOL, LOG_DEBUG, "Using reflexive UDP address from %s: %s port %s", from->name, address, port); sockaddr_t sa = str2sockaddr(address, port); update_node_udp(from, &sa); } diff --git a/src/protocol_misc.c b/src/protocol_misc.c index 225d7b4..7617091 100644 --- a/src/protocol_misc.c +++ b/src/protocol_misc.c @@ -1,7 +1,7 @@ /* protocol_misc.c -- handle the meta-protocol, miscellaneous functions Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -40,17 +40,17 @@ bool send_status(connection_t *c, int statusno, const char *statusstring) { return send_request(c, "%d %d %s", STATUS, statusno, statusstring); } -bool status_h(connection_t *c, char *request) { +bool status_h(connection_t *c, const char *request) { int statusno; char statusstring[MAX_STRING_SIZE]; if(sscanf(request, "%*d %d " MAX_STRING, &statusno, statusstring) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "STATUS", + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "STATUS", c->name, c->hostname); return false; } - ifdebug(STATUS) logger(LOG_NOTICE, "Status message from %s (%s): %d: %s", + logger(DEBUG_STATUS, LOG_NOTICE, "Status message from %s (%s): %d: %s", c->name, c->hostname, statusno, statusstring); return true; @@ -63,17 +63,17 @@ bool send_error(connection_t *c, int err, const char *errstring) { return send_request(c, "%d %d %s", ERROR, err, errstring); } -bool error_h(connection_t *c, char *request) { +bool error_h(connection_t *c, const char *request) { int err; char errorstring[MAX_STRING_SIZE]; if(sscanf(request, "%*d %d " MAX_STRING, &err, errorstring) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ERROR", + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ERROR", c->name, c->hostname); return false; } - ifdebug(ERROR) logger(LOG_NOTICE, "Error message from %s (%s): %d: %s", + logger(DEBUG_ERROR, LOG_NOTICE, "Error message from %s (%s): %d: %s", c->name, c->hostname, err, errorstring); return false; @@ -83,7 +83,7 @@ bool send_termreq(connection_t *c) { return send_request(c, "%d", TERMREQ); } -bool termreq_h(connection_t *c, char *request) { +bool termreq_h(connection_t *c, const char *request) { return false; } @@ -94,7 +94,7 @@ bool send_ping(connection_t *c) { return send_request(c, "%d", PING); } -bool ping_h(connection_t *c, char *request) { +bool ping_h(connection_t *c, const char *request) { return send_pong(c); } @@ -102,13 +102,19 @@ bool send_pong(connection_t *c) { return send_request(c, "%d", PONG); } -bool pong_h(connection_t *c, char *request) { +bool pong_h(connection_t *c, const char *request) { c->status.pinged = false; /* Succesful connection, reset timeout if this is an outgoing connection. */ - if(c->outgoing) + if(c->outgoing) { c->outgoing->timeout = 0; + c->outgoing->cfg = NULL; + if(c->outgoing->ai) + freeaddrinfo(c->outgoing->ai); + c->outgoing->ai = NULL; + c->outgoing->aip = NULL; + } return true; } @@ -117,7 +123,7 @@ bool pong_h(connection_t *c, char *request) { bool send_tcppacket(connection_t *c, const vpn_packet_t *packet) { /* If there already is a lot of data in the outbuf buffer, discard this packet. - We use a very simple Random Early Drop algorithm. */ + We use a very simple Random Early Drop algorithm. */ if(2.0 * c->outbuf.len / (float)maxoutbufsize - 1 > (float)rand()/(float)RAND_MAX) return true; @@ -128,11 +134,11 @@ bool send_tcppacket(connection_t *c, const vpn_packet_t *packet) { return send_meta(c, (char *)packet->data, packet->len); } -bool tcppacket_h(connection_t *c, char *request) { +bool tcppacket_h(connection_t *c, const char *request) { short int len; if(sscanf(request, "%*d %hd", &len) != 1) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "PACKET", c->name, c->hostname); return false; } diff --git a/src/protocol_subnet.c b/src/protocol_subnet.c index 6f9e626..06dafbc 100644 --- a/src/protocol_subnet.c +++ b/src/protocol_subnet.c @@ -1,7 +1,7 @@ /* protocol_subnet.c -- handle the meta-protocol, subnets Copyright (C) 1999-2005 Ivo Timmermans, - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen 2009 Michael Tokarev This program is free software; you can redistribute it and/or modify @@ -41,14 +41,14 @@ bool send_add_subnet(connection_t *c, const subnet_t *subnet) { return send_request(c, "%d %x %s %s", ADD_SUBNET, rand(), subnet->owner->name, netstr); } -bool add_subnet_h(connection_t *c, char *request) { +bool add_subnet_h(connection_t *c, const char *request) { char subnetstr[MAX_STRING_SIZE]; char name[MAX_STRING_SIZE]; node_t *owner; subnet_t s = {NULL}, *new, *old; if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "ADD_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "ADD_SUBNET", c->name, c->hostname); return false; } @@ -56,7 +56,7 @@ bool add_subnet_h(connection_t *c, char *request) { /* Check if owner name is valid */ if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, "invalid name"); return false; } @@ -64,7 +64,7 @@ bool add_subnet_h(connection_t *c, char *request) { /* Check if subnet string is valid */ if(!str2net(&s, subnetstr)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, "invalid subnet string"); return false; } @@ -78,7 +78,7 @@ bool add_subnet_h(connection_t *c, char *request) { if(tunnelserver && owner != myself && owner != c->node) { /* in case of tunnelserver, ignore indirect subnet registrations */ - ifdebug(PROTOCOL) logger(LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", "ADD_SUBNET", c->name, c->hostname, subnetstr); return true; } @@ -97,7 +97,7 @@ bool add_subnet_h(connection_t *c, char *request) { /* If we don't know this subnet, but we are the owner, retaliate with a DEL_SUBNET */ if(owner == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", "ADD_SUBNET", c->name, c->hostname); s.owner = myself; send_del_subnet(c, &s); @@ -107,7 +107,7 @@ bool add_subnet_h(connection_t *c, char *request) { /* In tunnel server mode, we should already know all allowed subnets */ if(tunnelserver) { - logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, subnetstr); return true; } @@ -115,7 +115,7 @@ bool add_subnet_h(connection_t *c, char *request) { /* Ignore if strictsubnets is true, but forward it to others */ if(strictsubnets) { - logger(LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", + logger(DEBUG_ALWAYS, LOG_WARNING, "Ignoring unauthorized %s from %s (%s): %s", "ADD_SUBNET", c->name, c->hostname, subnetstr); forward_request(c, request); return true; @@ -151,14 +151,14 @@ bool send_del_subnet(connection_t *c, const subnet_t *s) { return send_request(c, "%d %x %s %s", DEL_SUBNET, rand(), s->owner->name, netstr); } -bool del_subnet_h(connection_t *c, char *request) { +bool del_subnet_h(connection_t *c, const char *request) { char subnetstr[MAX_STRING_SIZE]; char name[MAX_STRING_SIZE]; node_t *owner; subnet_t s = {NULL}, *find; if(sscanf(request, "%*d %*x " MAX_STRING " " MAX_STRING, name, subnetstr) != 2) { - logger(LOG_ERR, "Got bad %s from %s (%s)", "DEL_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s)", "DEL_SUBNET", c->name, c->hostname); return false; } @@ -166,7 +166,7 @@ bool del_subnet_h(connection_t *c, char *request) { /* Check if owner name is valid */ if(!check_id(name)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, c->hostname, "invalid name"); return false; } @@ -174,7 +174,7 @@ bool del_subnet_h(connection_t *c, char *request) { /* Check if subnet string is valid */ if(!str2net(&s, subnetstr)) { - logger(LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, + logger(DEBUG_ALWAYS, LOG_ERR, "Got bad %s from %s (%s): %s", "DEL_SUBNET", c->name, c->hostname, "invalid subnet string"); return false; } @@ -188,13 +188,13 @@ bool del_subnet_h(connection_t *c, char *request) { if(tunnelserver && owner != myself && owner != c->node) { /* in case of tunnelserver, ignore indirect subnet deletion */ - ifdebug(PROTOCOL) logger(LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", - "DEL_SUBNET", c->name, c->hostname, subnetstr); + logger(DEBUG_PROTOCOL, LOG_WARNING, "Ignoring indirect %s from %s (%s) for %s", + "DEL_SUBNET", c->name, c->hostname, subnetstr); return true; } if(!owner) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for %s which is not in our node tree", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for %s which is not in our node tree", "DEL_SUBNET", c->name, c->hostname, name); return true; } @@ -206,7 +206,7 @@ bool del_subnet_h(connection_t *c, char *request) { find = lookup_subnet(owner, &s); if(!find) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for %s which does not appear in his subnet tree", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for %s which does not appear in his subnet tree", "DEL_SUBNET", c->name, c->hostname, name); if(strictsubnets) forward_request(c, request); @@ -216,7 +216,7 @@ bool del_subnet_h(connection_t *c, char *request) { /* If we are the owner of this subnet, retaliate with an ADD_SUBNET */ if(owner == myself) { - ifdebug(PROTOCOL) logger(LOG_WARNING, "Got %s from %s (%s) for ourself", + logger(DEBUG_PROTOCOL, LOG_WARNING, "Got %s from %s (%s) for ourself", "DEL_SUBNET", c->name, c->hostname); send_add_subnet(c, find); return true; diff --git a/src/raw_socket/device.c b/src/raw_socket_device.c similarity index 57% rename from src/raw_socket/device.c rename to src/raw_socket_device.c index 410e46e..48954d5 100644 --- a/src/raw_socket/device.c +++ b/src/raw_socket_device.c @@ -1,7 +1,7 @@ /* device.c -- raw socket Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2009 Guus Sliepen + 2002-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,9 @@ #include "system.h" +#ifdef HAVE_NETPACKET_PACKET_H #include +#endif #include "conf.h" #include "device.h" @@ -30,16 +32,13 @@ #include "route.h" #include "xalloc.h" -int device_fd = -1; -char *device = NULL; -char *iface = NULL; -static char ifrname[IFNAMSIZ]; +#if defined(PF_PACKET) && defined(ETH_P_ALL) && defined(AF_PACKET) static char *device_info; static uint64_t device_total_in = 0; static uint64_t device_total_out = 0; -bool setup_device(void) { +static bool setup_device(void) { struct ifreq ifr; struct sockaddr_ll sa; @@ -52,16 +51,21 @@ bool setup_device(void) { device_info = "raw socket"; if((device_fd = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) < 0) { - logger(LOG_ERR, "Could not open %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info, strerror(errno)); return false; } memset(&ifr, 0, sizeof ifr); + +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + strncpy(ifr.ifr_ifrn.ifrn_name, iface, IFNAMSIZ); if(ioctl(device_fd, SIOCGIFINDEX, &ifr)) { close(device_fd); - logger(LOG_ERR, "Can't find interface %s: %s", iface, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't find interface %s: %s", iface, strerror(errno)); return false; } @@ -72,27 +76,27 @@ bool setup_device(void) { sa.sll_ifindex = ifr.ifr_ifindex; if(bind(device_fd, (struct sockaddr *) &sa, (socklen_t) sizeof sa)) { - logger(LOG_ERR, "Could not bind %s to %s: %s", device, iface, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind %s to %s: %s", device, iface, strerror(errno)); return false; } - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } -void close_device(void) { +static void close_device(void) { close(device_fd); free(device); free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; if((inlen = read(device_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -101,18 +105,18 @@ bool read_packet(vpn_packet_t *packet) { device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } -bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); if(write(device_fd, packet->data, packet->len) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -122,8 +126,32 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t raw_socket_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; + +#else + +static bool not_supported(void) { + logger(DEBUG_ALWAYS, LOG_ERR, "Raw socket device not supported on this platform"); + return false; +} + +const devops_t raw_socket_devops = { + .setup = not_supported, + .close = NULL, + .read = NULL, + .write = NULL, + .dump_stats = NULL, +}; +#endif diff --git a/src/route.c b/src/route.c index 0b2d22e..e874d89 100644 --- a/src/route.c +++ b/src/route.c @@ -1,7 +1,7 @@ /* route.c -- routing Copyright (C) 2000-2005 Ivo Timmermans, - 2000-2010 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,6 @@ #include "system.h" -#include "splay_tree.h" #include "connection.h" #include "control_common.h" #include "ethernet.h" @@ -36,6 +35,8 @@ rmode_t routing_mode = RMODE_ROUTER; fmode_t forwarding_mode = FMODE_INTERNAL; +bmode_t broadcast_mode = BMODE_MST; +bool decrement_ttl = false; bool directonly = false; bool priorityinheritance = false; int macexpire = 600; @@ -70,7 +71,7 @@ static uint16_t inet_checksum(void *data, int len, uint16_t prevsum) { checksum += *p++; len -= 2; } - + if(len) checksum += *(uint8_t *)p; @@ -84,21 +85,22 @@ static bool ratelimit(int frequency) { static time_t lasttime = 0; static int count = 0; time_t now = time(NULL); - + if(lasttime == now) { - if(++count > frequency) + if(count >= frequency) return true; } else { lasttime = now; count = 0; } + count++; return false; } static bool checklength(node_t *source, vpn_packet_t *packet, length_t length) { if(packet->len < length) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got too short packet from %s (%s)", source->name, source->hostname); return false; } else return true; @@ -160,8 +162,8 @@ static void clamp_mss(const node_t *source, const node_t *via, vpn_packet_t *pac if(oldmss <= newmss) break; - - ifdebug(TRAFFIC) logger(LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss); + + logger(DEBUG_TRAFFIC, LOG_INFO, "Clamping MSS of packet from %s to %s to %d", source->name, via->name, newmss); /* Update the MSS value and the checksum */ packet->data[start + 22 + i] = newmss >> 8; @@ -182,29 +184,22 @@ static void swap_mac_addresses(vpn_packet_t *packet) { memcpy(&packet->data[0], &packet->data[6], sizeof tmp); memcpy(&packet->data[6], &tmp, sizeof tmp); } - + static void age_subnets(int fd, short events, void *data) { - subnet_t *s; - connection_t *c; - splay_node_t *node, *next, *node2; bool left = false; time_t now = time(NULL); - for(node = myself->subnet_tree->head; node; node = next) { - next = node->next; - s = node->data; + for splay_each(subnet_t, s, myself->subnet_tree) { if(s->expires && s->expires < now) { - ifdebug(TRAFFIC) { + if(debug_level >= DEBUG_TRAFFIC) { char netstr[MAXNETSTR]; if(net2str(netstr, sizeof netstr, s)) - logger(LOG_INFO, "Subnet %s expired", netstr); + logger(DEBUG_TRAFFIC, LOG_INFO, "Subnet %s expired", netstr); } - for(node2 = connection_tree->head; node2; node2 = node2->next) { - c = node2->data; + for list_each(connection_t, c, connection_list) if(c->status.active) send_del_subnet(c, s); - } subnet_del(myself, s); } else { @@ -218,16 +213,12 @@ static void age_subnets(int fd, short events, void *data) { } static void learn_mac(mac_t *address) { - subnet_t *subnet; - splay_node_t *node; - connection_t *c; - - subnet = lookup_subnet_mac(myself, address); + subnet_t *subnet = lookup_subnet_mac(myself, address); /* If we don't know this MAC address yet, store it */ if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx", + logger(DEBUG_TRAFFIC, LOG_INFO, "Learned new MAC address %hx:%hx:%hx:%hx:%hx:%hx", address->x[0], address->x[1], address->x[2], address->x[3], address->x[4], address->x[5]); @@ -241,11 +232,9 @@ static void learn_mac(mac_t *address) { /* And tell all other tinc daemons it's our MAC */ - for(node = connection_tree->head; node; node = node->next) { - c = node->data; + for list_each(connection_t, c, connection_list) if(c->status.active) send_add_subnet(c, subnet); - } if(!timeout_initialized(&age_subnets_event)) timeout_set(&age_subnets_event, age_subnets, NULL); @@ -261,14 +250,14 @@ static void learn_mac(mac_t *address) { static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, uint8_t type, uint8_t code) { struct ip ip = {0}; struct icmp icmp = {0}; - + struct in_addr ip_src; struct in_addr ip_dst; uint32_t oldlen; if(ratelimit(3)) return; - + /* Swap Ethernet source and destination addresses */ swap_mac_addresses(packet); @@ -278,7 +267,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, uint8_t memcpy(&ip, packet->data + ether_size, ip_size); /* Remember original source and destination */ - + ip_src = ip.ip_src; ip_dst = ip.ip_dst; @@ -289,13 +278,13 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, uint8_t if(oldlen >= IP_MSS - ip_size - icmp_size) oldlen = IP_MSS - ip_size - icmp_size; - + /* Copy first part of original contents to ICMP message */ - + memmove(packet->data + ether_size + ip_size + icmp_size, packet->data + ether_size, oldlen); /* Fill in IPv4 header */ - + ip.ip_v = 4; ip.ip_hl = ip_size / 4; ip.ip_tos = 0; @@ -309,13 +298,13 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, uint8_t ip.ip_dst = ip_src; ip.ip_sum = inet_checksum(&ip, ip_size, ~0); - + /* Fill in ICMP header */ - + icmp.icmp_type = type; icmp.icmp_code = code; icmp.icmp_cksum = 0; - + icmp.icmp_cksum = inet_checksum(&icmp, icmp_size, ~0); icmp.icmp_cksum = inet_checksum(packet->data + ether_size + ip_size + icmp_size, oldlen, icmp.icmp_cksum); @@ -323,7 +312,7 @@ static void route_ipv4_unreachable(node_t *source, vpn_packet_t *packet, uint8_t memcpy(packet->data + ether_size, &ip, ip_size); memcpy(packet->data + ether_size + ip_size, &icmp, icmp_size); - + packet->len = ether_size + ip_size + icmp_size + oldlen; send_packet(source, packet); @@ -337,28 +326,28 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet) { int len, maxlen, todo; uint8_t *offset; uint16_t ip_off, origf; - + memcpy(&ip, packet->data + ether_size, ip_size); fragment.priority = packet->priority; if(ip.ip_hl != ip_size / 4) return; - + todo = ntohs(ip.ip_len) - ip_size; if(ether_size + ip_size + todo != packet->len) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo)); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Length of packet (%d) doesn't match length in IPv4 header (%d)", packet->len, (int)(ether_size + ip_size + todo)); return; } - ifdebug(TRAFFIC) logger(LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname); + logger(DEBUG_TRAFFIC, LOG_INFO, "Fragmenting packet of %d bytes to %s (%s)", packet->len, dest->name, dest->hostname); offset = packet->data + ether_size + ip_size; maxlen = (dest->mtu - ether_size - ip_size) & ~0x7; ip_off = ntohs(ip.ip_off); origf = ip_off & ~IP_OFFMASK; ip_off &= IP_OFFMASK; - + while(todo) { len = todo > maxlen ? maxlen : todo; memcpy(fragment.data + ether_size + ip_size, offset, len); @@ -376,7 +365,7 @@ static void fragment_ipv4_packet(node_t *dest, vpn_packet_t *packet) { send_packet(dest, &fragment); ip_off += len / 8; - } + } } static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { @@ -388,7 +377,7 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv4(&dest); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d", + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv4 destination address %d.%d.%d.%d", source->name, source->hostname, dest.x[0], dest.x[1], @@ -398,9 +387,9 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_UNKNOWN); return; } - + if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -414,12 +403,17 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { packet->priority = packet->data[15]; via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via; - + + if(via == source) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); + return; + } + if(directonly && subnet->owner != via) return route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_NET_ANO); if(via && packet->len > MAX(via->mtu, 590) && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); if(packet->data[20] & 0x40) { packet->len = MAX(via->mtu, 590); route_ipv4_unreachable(source, packet, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED); @@ -431,7 +425,7 @@ static void route_ipv4_unicast(node_t *source, vpn_packet_t *packet) { } clamp_mss(source, via, packet); - + send_packet(subnet->owner, packet); } @@ -439,11 +433,11 @@ static void route_ipv4(node_t *source, vpn_packet_t *packet) { if(!checklength(source, packet, ether_size + ip_size)) return; - if(((packet->data[30] & 0xf0) == 0xe0) || ( + if(broadcast_mode && (((packet->data[30] & 0xf0) == 0xe0) || ( packet->data[30] == 255 && packet->data[31] == 255 && packet->data[32] == 255 && - packet->data[33] == 255)) + packet->data[33] == 255))) broadcast_packet(source, packet); else route_ipv4_unicast(source, packet); @@ -454,18 +448,18 @@ static void route_ipv4(node_t *source, vpn_packet_t *packet) { static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, uint8_t type, uint8_t code) { struct ip6_hdr ip6; struct icmp6_hdr icmp6 = {0}; - uint16_t checksum; + uint16_t checksum; struct { - struct in6_addr ip6_src; /* source address */ - struct in6_addr ip6_dst; /* destination address */ + struct in6_addr ip6_src; /* source address */ + struct in6_addr ip6_dst; /* destination address */ uint32_t length; uint32_t next; } pseudo; if(ratelimit(3)) return; - + /* Swap Ethernet source and destination addresses */ swap_mac_addresses(packet); @@ -475,7 +469,7 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, uint8_t memcpy(&ip6, packet->data + ether_size, ip6_size); /* Remember original source and destination */ - + pseudo.ip6_src = ip6.ip6_dst; pseudo.ip6_dst = ip6.ip6_src; @@ -483,16 +477,16 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, uint8_t if(type == ICMP6_PACKET_TOO_BIG) icmp6.icmp6_mtu = htonl(pseudo.length); - + if(pseudo.length >= IP_MSS - ip6_size - icmp6_size) pseudo.length = IP_MSS - ip6_size - icmp6_size; - + /* Copy first part of original contents to ICMP message */ - + memmove(packet->data + ether_size + ip6_size + icmp6_size, packet->data + ether_size, pseudo.length); /* Fill in IPv6 header */ - + ip6.ip6_flow = htonl(0x60000000UL); ip6.ip6_plen = htons(icmp6_size + pseudo.length); ip6.ip6_nxt = IPPROTO_ICMPV6; @@ -501,18 +495,18 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, uint8_t ip6.ip6_dst = pseudo.ip6_dst; /* Fill in ICMP header */ - + icmp6.icmp6_type = type; icmp6.icmp6_code = code; icmp6.icmp6_cksum = 0; /* Create pseudo header */ - + pseudo.length = htonl(icmp6_size + pseudo.length); pseudo.next = htonl(IPPROTO_ICMPV6); /* Generate checksum */ - + checksum = inet_checksum(&pseudo, sizeof pseudo, ~0); checksum = inet_checksum(&icmp6, icmp6_size, checksum); checksum = inet_checksum(packet->data + ether_size + ip6_size + icmp6_size, ntohl(pseudo.length) - icmp6_size, checksum); @@ -523,9 +517,9 @@ static void route_ipv6_unreachable(node_t *source, vpn_packet_t *packet, uint8_t memcpy(packet->data + ether_size, &ip6, ip6_size); memcpy(packet->data + ether_size + ip6_size, &icmp6, icmp6_size); - + packet->len = ether_size + ip6_size + ntohl(pseudo.length); - + send_packet(source, packet); } @@ -538,7 +532,7 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv6(&dest); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown IPv6 destination address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", source->name, source->hostname, ntohs(dest.x[0]), ntohs(dest.x[1]), @@ -554,7 +548,7 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { } if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -565,19 +559,24 @@ static void route_ipv6_unicast(node_t *source, vpn_packet_t *packet) { return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN); via = (subnet->owner->via == myself) ? subnet->owner->nexthop : subnet->owner->via; - + + if(via == source) { + logger(DEBUG_TRAFFIC, LOG_ERR, "Routing loop for packet from %s (%s)!", source->name, source->hostname); + return; + } + if(directonly && subnet->owner != via) return route_ipv6_unreachable(source, packet, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN); if(via && packet->len > MAX(via->mtu, 1294) && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); packet->len = MAX(via->mtu, 1294); route_ipv6_unreachable(source, packet, ICMP6_PACKET_TOO_BIG, 0); return; } clamp_mss(source, via, packet); - + send_packet(subnet->owner, packet); } @@ -592,19 +591,19 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { bool has_opt; struct { - struct in6_addr ip6_src; /* source address */ - struct in6_addr ip6_dst; /* destination address */ + struct in6_addr ip6_src; + struct in6_addr ip6_dst; uint32_t length; uint32_t next; } pseudo; if(!checklength(source, packet, ether_size + ip6_size + ns_size)) return; - + has_opt = packet->len >= ether_size + ip6_size + ns_size + opt_size + ETH_ALEN; - + if(source != myself) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got neighbor solicitation request from %s (%s) while in router mode!", source->name, source->hostname); return; } @@ -624,7 +623,7 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { if(ns.nd_ns_hdr.icmp6_type != ND_NEIGHBOR_SOLICIT || (has_opt && opt.nd_opt_type != ND_OPT_SOURCE_LINKADDR)) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type neighbor solicitation request"); return; } @@ -648,7 +647,7 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { } if(checksum) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: checksum error for neighbor solicitation request"); return; } @@ -657,7 +656,7 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv6((ipv6_t *) &ns.nd_ns_target); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: neighbor solicitation request for unknown address %hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx", ntohs(((uint16_t *) &ns.nd_ns_target)[0]), ntohs(((uint16_t *) &ns.nd_ns_target)[1]), ntohs(((uint16_t *) &ns.nd_ns_target)[2]), @@ -673,22 +672,22 @@ static void route_neighborsol(node_t *source, vpn_packet_t *packet) { /* Check if it is for our own subnet */ if(subnet->owner == myself) - return; /* silently ignore */ + return; /* silently ignore */ /* Create neighbor advertation reply */ - memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ - packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ + memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ + packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ - ip6.ip6_dst = ip6.ip6_src; /* swap destination and source protocoll address */ + ip6.ip6_dst = ip6.ip6_src; /* swap destination and source protocoll address */ ip6.ip6_src = ns.nd_ns_target; if(has_opt) - memcpy(packet->data + ether_size + ip6_size + ns_size + opt_size, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ + memcpy(packet->data + ether_size + ip6_size + ns_size + opt_size, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ ns.nd_ns_cksum = 0; ns.nd_ns_type = ND_NEIGHBOR_ADVERT; - ns.nd_ns_reserved = htonl(0x40000000UL); /* Set solicited flag */ + ns.nd_ns_reserved = htonl(0x40000000UL); /* Set solicited flag */ opt.nd_opt_type = ND_OPT_TARGET_LINKADDR; /* Create pseudo header */ @@ -731,7 +730,7 @@ static void route_ipv6(node_t *source, vpn_packet_t *packet) { return; } - if(packet->data[38] == 255) + if(broadcast_mode && packet->data[38] == 255) broadcast_packet(source, packet); else route_ipv6_unicast(source, packet); @@ -748,7 +747,7 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { return; if(source != myself) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Got ARP request from %s (%s) while in router mode!", source->name, source->hostname); return; } @@ -765,7 +764,7 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { if(ntohs(arp.arp_hrd) != ARPHRD_ETHER || ntohs(arp.arp_pro) != ETH_P_IP || arp.arp_hln != ETH_ALEN || arp.arp_pln != sizeof addr || ntohs(arp.arp_op) != ARPOP_REQUEST) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: received unknown type ARP request"); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: received unknown type ARP request"); return; } @@ -774,7 +773,7 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { subnet = lookup_subnet_ipv4((ipv4_t *) &arp.arp_tpa); if(!subnet) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d", + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet: ARP request for unknown address %d.%d.%d.%d", arp.arp_tpa[0], arp.arp_tpa[1], arp.arp_tpa[2], arp.arp_tpa[3]); return; @@ -783,17 +782,17 @@ static void route_arp(node_t *source, vpn_packet_t *packet) { /* Check if it is for our own subnet */ if(subnet->owner == myself) - return; /* silently ignore */ + return; /* silently ignore */ - memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ - packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ + memcpy(packet->data, packet->data + ETH_ALEN, ETH_ALEN); /* copy destination address */ + packet->data[ETH_ALEN * 2 - 1] ^= 0xFF; /* mangle source address so it looks like it's not from us */ - memcpy(&addr, arp.arp_tpa, sizeof addr); /* save protocol addr */ - memcpy(arp.arp_tpa, arp.arp_spa, sizeof addr); /* swap destination and source protocol address */ - memcpy(arp.arp_spa, &addr, sizeof addr); /* ... */ + memcpy(&addr, arp.arp_tpa, sizeof addr); /* save protocol addr */ + memcpy(arp.arp_tpa, arp.arp_spa, sizeof addr); /* swap destination and source protocol address */ + memcpy(arp.arp_spa, &addr, sizeof addr); /* ... */ - memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN); /* set target hard/proto addr */ - memcpy(arp.arp_sha, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ + memcpy(arp.arp_tha, arp.arp_sha, ETH_ALEN); /* set target hard/proto addr */ + memcpy(arp.arp_sha, packet->data + ETH_ALEN, ETH_ALEN); /* add fake source hard addr */ arp.arp_op = htons(ARPOP_REPLY); /* Copy structs on stack back to packet */ @@ -826,7 +825,7 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { } if(subnet->owner == source) { - ifdebug(TRAFFIC) logger(LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); + logger(DEBUG_TRAFFIC, LOG_WARNING, "Packet looping back to %s (%s)!", source->name, source->hostname); return; } @@ -839,9 +838,9 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { if(directonly && subnet->owner != via) return; - + if(via && packet->len > via->mtu && via != myself) { - ifdebug(TRAFFIC) logger(LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); + logger(DEBUG_TRAFFIC, LOG_INFO, "Packet for %s (%s) length %d larger than MTU %d", subnet->owner->name, subnet->owner->hostname, packet->len, via->mtu); uint16_t type = packet->data[12] << 8 | packet->data[13]; if(type == ETH_P_IP && packet->len > 590) { if(packet->data[20] & 0x40) { @@ -859,20 +858,70 @@ static void route_mac(node_t *source, vpn_packet_t *packet) { } clamp_mss(source, via, packet); - + send_packet(subnet->owner, packet); } static void send_pcap(vpn_packet_t *packet) { pcap = false; - for(splay_node_t *node = connection_tree->head; node; node = node->next) { - connection_t *c = node->data; + + for list_each(connection_t, c, connection_list) { if(!c->status.pcap) continue; - else - pcap = true; - if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, packet->len)) - send_meta(c, (char *)packet->data, packet->len); + + pcap = true; + int len = packet->len; + if(c->outmaclength && c->outmaclength < len) + len = c->outmaclength; + + if(send_request(c, "%d %d %d", CONTROL, REQ_PCAP, len)) + send_meta(c, (char *)packet->data, len); + } +} + +static bool do_decrement_ttl(node_t *source, vpn_packet_t *packet) { + uint16_t type = packet->data[12] << 8 | packet->data[13]; + + switch (type) { + case ETH_P_IP: + if(!checklength(source, packet, 14 + 32)) + return false; + + if(packet->data[22] < 1) { + if(packet->data[25] != IPPROTO_ICMP || packet->data[46] != ICMP_TIME_EXCEEDED) + route_ipv4_unreachable(source, packet, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL); + return false; + } + + uint16_t old = packet->data[22] << 8 | packet->data[23]; + packet->data[22]--; + uint16_t new = packet->data[22] << 8 | packet->data[23]; + + uint32_t checksum = packet->data[24] << 8 | packet->data[25]; + checksum += old + (~new & 0xFFFF); + while(checksum >> 16) + checksum = (checksum & 0xFFFF) + (checksum >> 16); + packet->data[24] = checksum >> 8; + packet->data[25] = checksum & 0xff; + + return true; + + case ETH_P_IPV6: + if(!checklength(source, packet, 14 + 40)) + return false; + + if(packet->data[21] < 1) { + if(packet->data[20] != IPPROTO_ICMPV6 || packet->data[54] != ICMP6_TIME_EXCEEDED) + route_ipv6_unreachable(source, packet, ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT); + return false; + } + + packet->data[21]--; + + return true; + + default: + return true; } } @@ -888,28 +937,30 @@ void route(node_t *source, vpn_packet_t *packet) { if(!checklength(source, packet, ether_size)) return; + if(decrement_ttl && source != myself) + if(!do_decrement_ttl(source, packet)) + return; + + uint16_t type = packet->data[12] << 8 | packet->data[13]; + switch (routing_mode) { case RMODE_ROUTER: - { - uint16_t type = packet->data[12] << 8 | packet->data[13]; + switch (type) { + case ETH_P_ARP: + route_arp(source, packet); + break; - switch (type) { - case ETH_P_ARP: - route_arp(source, packet); - break; + case ETH_P_IP: + route_ipv4(source, packet); + break; - case ETH_P_IP: - route_ipv4(source, packet); - break; + case ETH_P_IPV6: + route_ipv6(source, packet); + break; - case ETH_P_IPV6: - route_ipv6(source, packet); - break; - - default: - ifdebug(TRAFFIC) logger(LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type); - break; - } + default: + logger(DEBUG_TRAFFIC, LOG_WARNING, "Cannot route packet from %s (%s): unknown type %hx", source->name, source->hostname, type); + break; } break; diff --git a/src/route.h b/src/route.h index 5af2a09..a0121d7 100644 --- a/src/route.h +++ b/src/route.h @@ -1,7 +1,7 @@ /* route.h -- header file for route.c Copyright (C) 2000-2005 Ivo Timmermans - 2000-2006 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -36,8 +36,16 @@ typedef enum fmode_t { FMODE_KERNEL, } fmode_t; +typedef enum bmode_t { + BMODE_NONE = 0, + BMODE_MST, + BMODE_DIRECT, +} bmode_t; + extern rmode_t routing_mode; extern fmode_t forwarding_mode; +extern bmode_t broadcast_mode; +extern bool decrement_ttl; extern bool directonly; extern bool overwrite_mac; extern bool priorityinheritance; @@ -48,4 +56,4 @@ extern mac_t mymac; extern void route(struct node_t *, struct vpn_packet_t *); -#endif /* __TINC_ROUTE_H__ */ +#endif /* __TINC_ROUTE_H__ */ diff --git a/src/solaris/device.c b/src/solaris/device.c index eac267a..cb2ece7 100644 --- a/src/solaris/device.c +++ b/src/solaris/device.c @@ -1,7 +1,7 @@ /* device.c -- Interaction with Solaris tun device Copyright (C) 2001-2005 Ivo Timmermans, - 2001-2011 Guus Sliepen + 2001-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,7 +35,7 @@ #define DEFAULT_DEVICE "/dev/tun" int device_fd = -1; -int ip_fd = -1, if_fd = -1; +static int ip_fd = -1, if_fd = -1; char *device = NULL; char *iface = NULL; static char *device_info = NULL; @@ -43,7 +43,7 @@ static char *device_info = NULL; static uint64_t device_total_in = 0; static uint64_t device_total_out = 0; -bool setup_device(void) { +static bool setup_device(void) { int ppa; char *ptr; @@ -51,10 +51,14 @@ bool setup_device(void) { device = xstrdup(DEFAULT_DEVICE); if((device_fd = open(device, O_RDWR | O_NONBLOCK)) < 0) { - logger(LOG_ERR, "Could not open %s: %s", device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + ppa = 0; ptr = device; @@ -63,35 +67,43 @@ bool setup_device(void) { ppa = atoi(ptr); if((ip_fd = open("/dev/ip", O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open /dev/ip: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open /dev/ip: %s", strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(ip_fd, F_SETFD, FD_CLOEXEC); +#endif + /* Assign a new PPA and get its unit number. */ if((ppa = ioctl(device_fd, TUNNEWPPA, ppa)) < 0) { - logger(LOG_ERR, "Can't assign new interface: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't assign new interface: %s", strerror(errno)); return false; } if((if_fd = open(device, O_RDWR, 0)) < 0) { - logger(LOG_ERR, "Could not open %s twice: %s", device, + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s twice: %s", device, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(if_fd, F_SETFD, FD_CLOEXEC); +#endif + if(ioctl(if_fd, I_PUSH, "ip") < 0) { - logger(LOG_ERR, "Can't push IP module: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't push IP module: %s", strerror(errno)); return false; } /* Assign ppa according to the unit number returned by tun device */ if(ioctl(if_fd, IF_UNITSEL, (char *) &ppa) < 0) { - logger(LOG_ERR, "Can't set PPA %d: %s", ppa, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't set PPA %d: %s", ppa, strerror(errno)); return false; } if(ioctl(ip_fd, I_LINK, if_fd) < 0) { - logger(LOG_ERR, "Can't link TUN device to IP: %s", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't link TUN device to IP: %s", strerror(errno)); return false; } @@ -100,12 +112,12 @@ bool setup_device(void) { device_info = "Solaris tun device"; - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); return true; } -void close_device(void) { +static void close_device(void) { close(if_fd); close(ip_fd); close(device_fd); @@ -114,11 +126,11 @@ void close_device(void) { free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; if((inlen = read(device_fd, packet->data + 14, MTU - 14)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -133,28 +145,29 @@ bool read_packet(vpn_packet_t *packet) { packet->data[13] = 0xDD; break; default: - ifdebug(TRAFFIC) logger(LOG_ERR, + logger(DEBUG_TRAFFIC, LOG_ERR, "Unknown IP version %d while reading packet from %s %s", packet->data[14] >> 4, device_info, device); return false; } + memset(packet->data, 0, 12); packet->len = inlen + 14; device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } -bool write_packet(vpn_packet_t *packet) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", +static bool write_packet(vpn_packet_t *packet) { + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); if(write(device_fd, packet->data + 14, packet->len - 14) < 0) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); return false; } @@ -164,8 +177,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t os_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/splay_tree.c b/src/splay_tree.c index 135ba06..e7d6dbb 100644 --- a/src/splay_tree.c +++ b/src/splay_tree.c @@ -1,6 +1,6 @@ /* splay_tree.c -- splay tree and linked list convenience - Copyright (C) 2004-2006 Guus Sliepen + Copyright (C) 2004-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,10 +44,10 @@ static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *r rightbottom->left = child; child->parent = rightbottom; rightbottom = child; - + if((root->left = child->right)) child->right->parent = root; - + child->right = root; root->parent = child; @@ -74,7 +74,7 @@ static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *r rightbottom->left = root; root->parent = rightbottom; rightbottom = root; - + root->left = NULL; child->parent = NULL; @@ -88,10 +88,10 @@ static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *r leftbottom->right = child; child->parent = leftbottom; leftbottom = child; - + if((root->right = child->left)) child->left->parent = root; - + child->left = root; root->parent = child; @@ -158,14 +158,14 @@ static splay_node_t *splay_top_down(splay_tree_t *tree, const void *data, int *r return tree->root; } - + static void splay_bottom_up(splay_tree_t *tree, splay_node_t *node) { splay_node_t *parent, *grandparent, *greatgrandparent; while((parent = node->parent)) { if(!(grandparent = parent->parent)) { /* zig */ if(node == parent->left) { - if((parent->left = node->right)) + if((parent->left = node->right)) parent->left->parent = parent; node->right = parent; } else { @@ -326,7 +326,7 @@ splay_node_t *splay_search_closest_node_nosplay(const splay_tree_t *tree, const } else if(c > 0) { if(node->right) node = node->right; - else + else break; } else { break; @@ -384,12 +384,12 @@ splay_node_t *splay_insert(splay_tree_t *tree, void *data) { new = splay_alloc_node(); new->data = data; - + if(result < 0) splay_insert_before(tree, closest, new); else splay_insert_after(tree, closest, new); - } + } return new; } @@ -402,7 +402,7 @@ splay_node_t *splay_insert_node(splay_tree_t *tree, splay_node_t *node) { splay_insert_top(tree, node); else { closest = splay_search_closest_node(tree, node->data, &result); - + if(!result) return NULL; @@ -530,9 +530,7 @@ void splay_delete(splay_tree_t *tree, void *data) { /* Fast tree cleanup */ void splay_delete_tree(splay_tree_t *tree) { - splay_node_t *node, *next; - - for(node = tree->head; node; node = next) { + for(splay_node_t *node = tree->head, *next; node; node = next) { next = node->next; splay_free_node(tree, node); } @@ -543,18 +541,14 @@ void splay_delete_tree(splay_tree_t *tree) { /* Tree walking */ void splay_foreach(const splay_tree_t *tree, splay_action_t action) { - splay_node_t *node, *next; - - for(node = tree->head; node; node = next) { + for(splay_node_t *node = tree->head, *next; node; node = next) { next = node->next; action(node->data); } } void splay_foreach_node(const splay_tree_t *tree, splay_action_t action) { - splay_node_t *node, *next; - - for(node = tree->head; node; node = next) { + for(splay_node_t *node = tree->head, *next; node; node = next) { next = node->next; action(node); } diff --git a/src/splay_tree.h b/src/splay_tree.h index e4af0c4..88d7516 100644 --- a/src/splay_tree.h +++ b/src/splay_tree.h @@ -1,6 +1,6 @@ /* splay_tree.h -- header file for splay_tree.c - Copyright (C) 2004-2006 Guus Sliepen + Copyright (C) 2004-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -104,4 +104,6 @@ extern splay_node_t *splay_search_closest_greater_node(splay_tree_t *, const voi extern void splay_foreach(const splay_tree_t *, splay_action_t); extern void splay_foreach_node(const splay_tree_t *, splay_action_t); +#define splay_each(type, item, tree) (type *item = (type *)1; item; item = NULL) for(splay_node_t *node = (tree)->head, *next; item = node ? node->data : NULL, next = node ? node->next : NULL, node; node = next) + #endif diff --git a/src/sptps.c b/src/sptps.c new file mode 100644 index 0000000..5a99055 --- /dev/null +++ b/src/sptps.c @@ -0,0 +1,662 @@ +/* + sptps.c -- Simple Peer-to-Peer Security + Copyright (C) 2011-2012 Guus Sliepen , + 2010 Brandon L. Black + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "cipher.h" +#include "crypto.h" +#include "digest.h" +#include "ecdh.h" +#include "ecdsa.h" +#include "logger.h" +#include "prf.h" +#include "sptps.h" + +unsigned int sptps_replaywin = 16; + +/* + Nonce MUST be exchanged first (done) + Signatures MUST be done over both nonces, to guarantee the signature is fresh + Otherwise: if ECDHE key of one side is compromised, it can be reused! + + Add explicit tag to beginning of structure to distinguish the client and server when signing. (done) + + Sign all handshake messages up to ECDHE kex with long-term public keys. (done) + + HMACed KEX finished message to prevent downgrade attacks and prove you have the right key material (done by virtue of ECDSA over the whole ECDHE exchange?) + + Explicit close message needs to be added. + + Maybe do add some alert messages to give helpful error messages? Not more than TLS sends. + + Use counter mode instead of OFB. (done) + + Make sure ECC operations are fixed time (aka prevent side-channel attacks). +*/ + +void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap) { +} + +void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap) { + vfprintf(stderr, format, ap); + fputc('\n', stderr); +} + +void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap) = sptps_log_stderr; + +// Log an error message. +static bool error(sptps_t *s, int s_errno, const char *format, ...) { + if(format) { + va_list ap; + va_start(ap, format); + sptps_log(s, s_errno, format, ap); + va_end(ap); + } + + errno = s_errno; + return false; +} + +static void warning(sptps_t *s, const char *format, ...) { + va_list ap; + va_start(ap, format); + sptps_log(s, 0, format, ap); + va_end(ap); +} + +// Send a record (datagram version, accepts all record types, handles encryption and authentication). +static bool send_record_priv_datagram(sptps_t *s, uint8_t type, const char *data, uint16_t len) { + char buffer[len + 23UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = htonl(s->outseqno++); + uint16_t netlen = htons(len); + + memcpy(buffer, &netlen, 2); + memcpy(buffer + 2, &seqno, 4); + buffer[6] = type; + + // Add plaintext (TODO: avoid unnecessary copy) + memcpy(buffer + 7, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + cipher_set_counter(&s->outcipher, &seqno, sizeof seqno); + if(!cipher_counter_xor(&s->outcipher, buffer + 6, len + 1UL, buffer + 6)) + return false; + + if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) + return false; + + return s->send_data(s->handle, type, buffer + 2, len + 21UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer + 2, len + 5UL); + } +} +// Send a record (private version, accepts all record types, handles encryption and authentication). +static bool send_record_priv(sptps_t *s, uint8_t type, const char *data, uint16_t len) { + if(s->datagram) + return send_record_priv_datagram(s, type, data, len); + + char buffer[len + 23UL]; + + // Create header with sequence number, length and record type + uint32_t seqno = htonl(s->outseqno++); + uint16_t netlen = htons(len); + + memcpy(buffer, &seqno, 4); + memcpy(buffer + 4, &netlen, 2); + buffer[6] = type; + + // Add plaintext (TODO: avoid unnecessary copy) + memcpy(buffer + 7, data, len); + + if(s->outstate) { + // If first handshake has finished, encrypt and HMAC + if(!cipher_counter_xor(&s->outcipher, buffer + 4, len + 3UL, buffer + 4)) + return false; + + if(!digest_create(&s->outdigest, buffer, len + 7UL, buffer + 7UL + len)) + return false; + + return s->send_data(s->handle, type, buffer + 4, len + 19UL); + } else { + // Otherwise send as plaintext + return s->send_data(s->handle, type, buffer + 4, len + 3UL); + } +} + +// Send an application record. +bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len) { + // Sanity checks: application cannot send data before handshake is finished, + // and only record types 0..127 are allowed. + if(!s->outstate) + return error(s, EINVAL, "Handshake phase not finished yet"); + + if(type >= SPTPS_HANDSHAKE) + return error(s, EINVAL, "Invalid application record type"); + + return send_record_priv(s, type, data, len); +} + +// Send a Key EXchange record, containing a random nonce and an ECDHE public key. +static bool send_kex(sptps_t *s) { + size_t keylen = ECDH_SIZE; + + // Make room for our KEX message, which we will keep around since send_sig() needs it. + if(s->mykex) + abort(); + s->mykex = realloc(s->mykex, 1 + 32 + keylen); + if(!s->mykex) + return error(s, errno, strerror(errno)); + + // Set version byte to zero. + s->mykex[0] = SPTPS_VERSION; + + // Create a random nonce. + randomize(s->mykex + 1, 32); + + // Create a new ECDH public key. + if(!ecdh_generate_public(&s->ecdh, s->mykex + 1 + 32)) + return false; + + return send_record_priv(s, SPTPS_HANDSHAKE, s->mykex, 1 + 32 + keylen); +} + +// Send a SIGnature record, containing an ECDSA signature over both KEX records. +static bool send_sig(sptps_t *s) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(&s->mykey); + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator, plus label + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + char sig[siglen]; + + msg[0] = s->initiator; + memcpy(msg + 1, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Sign the result. + if(!ecdsa_sign(&s->mykey, msg, sizeof msg, sig)) + return false; + + // Send the SIG exchange record. + return send_record_priv(s, SPTPS_HANDSHAKE, sig, sizeof sig); +} + +// Generate key material from the shared secret created from the ECDHE key exchange. +static bool generate_key_material(sptps_t *s, const char *shared, size_t len) { + // Initialise cipher and digest structures if necessary + if(!s->outstate) { + bool result + = cipher_open_by_name(&s->incipher, "aes-256-ecb") + && cipher_open_by_name(&s->outcipher, "aes-256-ecb") + && digest_open_by_name(&s->indigest, "sha256", 16) + && digest_open_by_name(&s->outdigest, "sha256", 16); + if(!result) + return false; + } + + // Allocate memory for key material + size_t keylen = digest_keylength(&s->indigest) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher) + cipher_keylength(&s->outcipher); + + s->key = realloc(s->key, keylen); + if(!s->key) + return error(s, errno, strerror(errno)); + + // Create the HMAC seed, which is "key expansion" + session label + server nonce + client nonce + char seed[s->labellen + 64 + 13]; + strcpy(seed, "key expansion"); + if(s->initiator) { + memcpy(seed + 13, s->mykex + 1, 32); + memcpy(seed + 45, s->hiskex + 1, 32); + } else { + memcpy(seed + 13, s->hiskex + 1, 32); + memcpy(seed + 45, s->mykex + 1, 32); + } + memcpy(seed + 77, s->label, s->labellen); + + // Use PRF to generate the key material + if(!prf(shared, len, seed, s->labellen + 64 + 13, s->key, keylen)) + return false; + + return true; +} + +// Send an ACKnowledgement record. +static bool send_ack(sptps_t *s) { + return send_record_priv(s, SPTPS_HANDSHAKE, "", 0); +} + +// Receive an ACKnowledgement record. +static bool receive_ack(sptps_t *s, const char *data, uint16_t len) { + if(len) + return error(s, EIO, "Invalid ACK record length"); + + if(s->initiator) { + bool result + = cipher_set_counter_key(&s->incipher, s->key) + && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->incipher), digest_keylength(&s->indigest)); + if(!result) + return false; + } else { + bool result + = cipher_set_counter_key(&s->incipher, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest)) + && digest_set_key(&s->indigest, s->key + cipher_keylength(&s->outcipher) + digest_keylength(&s->outdigest) + cipher_keylength(&s->incipher), digest_keylength(&s->indigest)); + if(!result) + return false; + } + + free(s->key); + s->key = NULL; + s->instate = true; + + return true; +} + +// Receive a Key EXchange record, respond by sending a SIG record. +static bool receive_kex(sptps_t *s, const char *data, uint16_t len) { + // Verify length of the HELLO record + if(len != 1 + 32 + ECDH_SIZE) + return error(s, EIO, "Invalid KEX record length"); + + // Ignore version number for now. + + // Make a copy of the KEX message, send_sig() and receive_sig() need it + if(s->hiskex) + abort(); + s->hiskex = realloc(s->hiskex, len); + if(!s->hiskex) + return error(s, errno, strerror(errno)); + + memcpy(s->hiskex, data, len); + + return send_sig(s); +} + +// Receive a SIGnature record, verify it, if it passed, compute the shared secret and calculate the session keys. +static bool receive_sig(sptps_t *s, const char *data, uint16_t len) { + size_t keylen = ECDH_SIZE; + size_t siglen = ecdsa_size(&s->hiskey); + + // Verify length of KEX record. + if(len != siglen) + return error(s, EIO, "Invalid KEX record length"); + + // Concatenate both KEX messages, plus tag indicating if it is from the connection originator + char msg[(1 + 32 + keylen) * 2 + 1 + s->labellen]; + + msg[0] = !s->initiator; + memcpy(msg + 1, s->hiskex, 1 + 32 + keylen); + memcpy(msg + 1 + 33 + keylen, s->mykex, 1 + 32 + keylen); + memcpy(msg + 1 + 2 * (33 + keylen), s->label, s->labellen); + + // Verify signature. + if(!ecdsa_verify(&s->hiskey, msg, sizeof msg, data)) + return false; + + // Compute shared secret. + char shared[ECDH_SHARED_SIZE]; + if(!ecdh_compute_shared(&s->ecdh, s->hiskex + 1 + 32, shared)) + return false; + + // Generate key material from shared secret. + if(!generate_key_material(s, shared, sizeof shared)) + return false; + + free(s->mykex); + free(s->hiskex); + + s->mykex = NULL; + s->hiskex = NULL; + + // Send cipher change record + if(s->outstate && !send_ack(s)) + return false; + + // TODO: only set new keys after ACK has been set/received + if(s->initiator) { + bool result + = cipher_set_counter_key(&s->outcipher, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest)) + && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->incipher) + digest_keylength(&s->indigest) + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest)); + if(!result) + return false; + } else { + bool result + = cipher_set_counter_key(&s->outcipher, s->key) + && digest_set_key(&s->outdigest, s->key + cipher_keylength(&s->outcipher), digest_keylength(&s->outdigest)); + if(!result) + return false; + } + + return true; +} + +// Force another Key EXchange (for testing purposes). +bool sptps_force_kex(sptps_t *s) { + if(!s->outstate || s->state != SPTPS_SECONDARY_KEX) + return error(s, EINVAL, "Cannot force KEX in current state"); + + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Receive a handshake record. +static bool receive_handshake(sptps_t *s, const char *data, uint16_t len) { + // Only a few states to deal with handshaking. + switch(s->state) { + case SPTPS_SECONDARY_KEX: + // We receive a secondary KEX request, first respond by sending our own. + if(!send_kex(s)) + return false; + case SPTPS_KEX: + // We have sent our KEX request, we expect our peer to sent one as well. + if(!receive_kex(s, data, len)) + return false; + s->state = SPTPS_SIG; + return true; + case SPTPS_SIG: + // If we already sent our secondary public ECDH key, we expect the peer to send his. + if(!receive_sig(s, data, len)) + return false; + if(s->outstate) + s->state = SPTPS_ACK; + else { + s->outstate = true; + if(!receive_ack(s, NULL, 0)) + return false; + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + } + + return true; + case SPTPS_ACK: + // We expect a handshake message to indicate transition to the new keys. + if(!receive_ack(s, data, len)) + return false; + s->receive_record(s->handle, SPTPS_HANDSHAKE, NULL, 0); + s->state = SPTPS_SECONDARY_KEX; + return true; + // TODO: split ACK into a VERify and ACK? + default: + return error(s, EIO, "Invalid session state"); + } +} + +// Check datagram for valid HMAC +bool sptps_verify_datagram(sptps_t *s, const char *data, size_t len) { + if(!s->instate || len < 21) + return false; + + char buffer[len + 23]; + uint16_t netlen = htons(len - 21); + + memcpy(buffer, &netlen, 2); + memcpy(buffer + 2, data, len); + + return digest_verify(&s->indigest, buffer, len - 14, buffer + len - 14); +} + +// Receive incoming data, datagram version. +static bool sptps_receive_data_datagram(sptps_t *s, const char *data, size_t len) { + if(len < (s->instate ? 21 : 5)) + return error(s, EIO, "Received short packet"); + + uint32_t seqno; + memcpy(&seqno, data, 4); + seqno = ntohl(seqno); + + if(!s->instate) { + if(seqno != s->inseqno) + return error(s, EIO, "Invalid packet seqno: %d != %d", seqno, s->inseqno); + + s->inseqno = seqno + 1; + + uint8_t type = data[4]; + + if(type != SPTPS_HANDSHAKE) + return error(s, EIO, "Application record received before handshake finished"); + + return receive_handshake(s, data + 5, len - 5); + } + + // Replay protection using a sliding window of configurable size. + // s->inseqno is expected sequence number + // seqno is received sequence number + // s->late[] is a circular buffer, a 1 bit means a packet has not been received yet + // The circular buffer contains bits for sequence numbers from s->inseqno - s->replaywin * 8 to (but excluding) s->inseqno. + if(s->replaywin) { + if(seqno != s->inseqno) { + if(seqno >= s->inseqno + s->replaywin * 8) { + // Prevent packets that jump far ahead of the queue from causing many others to be dropped. + if(s->farfuture++ < s->replaywin >> 2) + return error(s, EIO, "Packet is %d seqs in the future, dropped (%u)\n", seqno - s->inseqno, s->farfuture); + + // Unless we have seen lots of them, in which case we consider the others lost. + warning(s, "Lost %d packets\n", seqno - s->inseqno); + memset(s->late, 0, s->replaywin); + } else if (seqno < s->inseqno) { + // If the sequence number is farther in the past than the bitmap goes, or if the packet was already received, drop it. + if((s->inseqno >= s->replaywin * 8 && seqno < s->inseqno - s->replaywin * 8) || !(s->late[(seqno / 8) % s->replaywin] & (1 << seqno % 8))) + return error(s, EIO, "Received late or replayed packet, seqno %d, last received %d\n", seqno, s->inseqno); + } else { + // We missed some packets. Mark them in the bitmap as being late. + for(int i = s->inseqno; i < seqno; i++) + s->late[(i / 8) % s->replaywin] |= 1 << i % 8; + } + } + + // Mark the current packet as not being late. + s->late[(seqno / 8) % s->replaywin] &= ~(1 << seqno % 8); + s->farfuture = 0; + } + + if(seqno > s->inseqno) + s->inseqno = seqno + 1; + + uint16_t netlen = htons(len - 21); + + char buffer[len + 23]; + + memcpy(buffer, &netlen, 2); + memcpy(buffer + 2, data, len); + + memcpy(&seqno, buffer + 2, 4); + + // Check HMAC and decrypt. + if(!digest_verify(&s->indigest, buffer, len - 14, buffer + len - 14)) + return error(s, EIO, "Invalid HMAC"); + + cipher_set_counter(&s->incipher, &seqno, sizeof seqno); + if(!cipher_counter_xor(&s->incipher, buffer + 6, len - 4, buffer + 6)) + return false; + + // Append a NULL byte for safety. + buffer[len - 14] = 0; + + uint8_t type = buffer[6]; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) + return error(s, EIO, "Application record received before handshake finished"); + if(!s->receive_record(s->handle, type, buffer + 7, len - 21)) + return false; + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, buffer + 7, len - 21)) + return false; + } else { + return error(s, EIO, "Invalid record type"); + } + + return true; +} + +// Receive incoming data. Check if it contains a complete record, if so, handle it. +bool sptps_receive_data(sptps_t *s, const char *data, size_t len) { + if(s->datagram) + return sptps_receive_data_datagram(s, data, len); + + while(len) { + // First read the 2 length bytes. + if(s->buflen < 6) { + size_t toread = 6 - s->buflen; + if(toread > len) + toread = len; + + memcpy(s->inbuf + s->buflen, data, toread); + + s->buflen += toread; + len -= toread; + data += toread; + + // Exit early if we don't have the full length. + if(s->buflen < 6) + return true; + + // Decrypt the length bytes + + if(s->instate) { + if(!cipher_counter_xor(&s->incipher, s->inbuf + 4, 2, &s->reclen)) + return false; + } else { + memcpy(&s->reclen, s->inbuf + 4, 2); + } + + s->reclen = ntohs(s->reclen); + + // If we have the length bytes, ensure our buffer can hold the whole request. + s->inbuf = realloc(s->inbuf, s->reclen + 23UL); + if(!s->inbuf) + return error(s, errno, strerror(errno)); + + // Add sequence number. + uint32_t seqno = htonl(s->inseqno++); + memcpy(s->inbuf, &seqno, 4); + + // Exit early if we have no more data to process. + if(!len) + return true; + } + + // Read up to the end of the record. + size_t toread = s->reclen + (s->instate ? 23UL : 7UL) - s->buflen; + if(toread > len) + toread = len; + + memcpy(s->inbuf + s->buflen, data, toread); + s->buflen += toread; + len -= toread; + data += toread; + + // If we don't have a whole record, exit. + if(s->buflen < s->reclen + (s->instate ? 23UL : 7UL)) + return true; + + // Check HMAC and decrypt. + if(s->instate) { + if(!digest_verify(&s->indigest, s->inbuf, s->reclen + 7UL, s->inbuf + s->reclen + 7UL)) + return error(s, EIO, "Invalid HMAC"); + + if(!cipher_counter_xor(&s->incipher, s->inbuf + 6UL, s->reclen + 1UL, s->inbuf + 6UL)) + return false; + } + + // Append a NULL byte for safety. + s->inbuf[s->reclen + 7UL] = 0; + + uint8_t type = s->inbuf[6]; + + if(type < SPTPS_HANDSHAKE) { + if(!s->instate) + return error(s, EIO, "Application record received before handshake finished"); + if(!s->receive_record(s->handle, type, s->inbuf + 7, s->reclen)) + return false; + } else if(type == SPTPS_HANDSHAKE) { + if(!receive_handshake(s, s->inbuf + 7, s->reclen)) + return false; + } else { + return error(s, EIO, "Invalid record type"); + } + + s->buflen = 4; + } + + return true; +} + +// Start a SPTPS session. +bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t mykey, ecdsa_t hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record) { + // Initialise struct sptps + memset(s, 0, sizeof *s); + + s->handle = handle; + s->initiator = initiator; + s->datagram = datagram; + s->mykey = mykey; + s->hiskey = hiskey; + s->replaywin = sptps_replaywin; + if(s->replaywin) { + s->late = malloc(s->replaywin); + if(!s->late) + return error(s, errno, strerror(errno)); + } + + s->label = malloc(labellen); + if(!s->label) + return error(s, errno, strerror(errno)); + + if(!datagram) { + s->inbuf = malloc(7); + if(!s->inbuf) + return error(s, errno, strerror(errno)); + s->buflen = 4; + memset(s->inbuf, 0, 4); + } + + memcpy(s->label, label, labellen); + s->labellen = labellen; + + s->send_data = send_data; + s->receive_record = receive_record; + + // Do first KEX immediately + s->state = SPTPS_KEX; + return send_kex(s); +} + +// Stop a SPTPS session. +bool sptps_stop(sptps_t *s) { + // Clean up any resources. + cipher_close(&s->incipher); + cipher_close(&s->outcipher); + digest_close(&s->indigest); + digest_close(&s->outdigest); + ecdh_free(&s->ecdh); + free(s->inbuf); + free(s->mykex); + free(s->hiskex); + free(s->key); + free(s->label); + free(s->late); + memset(s, 0, sizeof *s); + return true; +} diff --git a/src/sptps.h b/src/sptps.h new file mode 100644 index 0000000..a19be97 --- /dev/null +++ b/src/sptps.h @@ -0,0 +1,94 @@ +/* + sptps.h -- Simple Peer-to-Peer Security + Copyright (C) 2011-2012 Guus Sliepen , + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#ifndef __SPTPS_H__ +#define __SPTPS_H__ + +#include "system.h" + +#include "cipher.h" +#include "digest.h" +#include "ecdh.h" +#include "ecdsa.h" + +#define SPTPS_VERSION 0 + +// Record types +#define SPTPS_HANDSHAKE 128 // Key exchange and authentication +#define SPTPS_ALERT 129 // Warning or error messages +#define SPTPS_CLOSE 130 // Application closed the connection + +// Key exchange states +#define SPTPS_KEX 0 // Waiting for the first Key EXchange record +#define SPTPS_SECONDARY_KEX 1 // Ready to receive a secondary Key EXchange record +#define SPTPS_SIG 2 // Waiting for a SIGnature record +#define SPTPS_ACK 3 // Waiting for an ACKnowledgement record + +typedef bool (*send_data_t)(void *handle, uint8_t type, const char *data, size_t len); +typedef bool (*receive_record_t)(void *handle, uint8_t type, const char *data, uint16_t len); + +typedef struct sptps { + bool initiator; + bool datagram; + int state; + + char *inbuf; + size_t buflen; + uint16_t reclen; + + bool instate; + cipher_t incipher; + digest_t indigest; + uint32_t inseqno; + unsigned int replaywin; + unsigned int farfuture; + char *late; + + bool outstate; + cipher_t outcipher; + digest_t outdigest; + uint32_t outseqno; + + ecdsa_t mykey; + ecdsa_t hiskey; + ecdh_t ecdh; + + char *mykex; + char *hiskex; + char *key; + char *label; + size_t labellen; + + void *handle; + send_data_t send_data; + receive_record_t receive_record; +} sptps_t; + +extern unsigned int sptps_replaywin; +extern void sptps_log_quiet(sptps_t *s, int s_errno, const char *format, va_list ap); +extern void sptps_log_stderr(sptps_t *s, int s_errno, const char *format, va_list ap); +extern void (*sptps_log)(sptps_t *s, int s_errno, const char *format, va_list ap); +extern bool sptps_start(sptps_t *s, void *handle, bool initiator, bool datagram, ecdsa_t mykey, ecdsa_t hiskey, const char *label, size_t labellen, send_data_t send_data, receive_record_t receive_record); +extern bool sptps_stop(sptps_t *s); +extern bool sptps_send_record(sptps_t *s, uint8_t type, const char *data, uint16_t len); +extern bool sptps_receive_data(sptps_t *s, const char *data, size_t len); +extern bool sptps_force_kex(sptps_t *s); +extern bool sptps_verify_datagram(sptps_t *s, const char *data, size_t len); + +#endif diff --git a/src/sptps_test.c b/src/sptps_test.c new file mode 100644 index 0000000..7aafbbe --- /dev/null +++ b/src/sptps_test.c @@ -0,0 +1,194 @@ +/* + sptps_test.c -- Simple Peer-to-Peer Security test program + Copyright (C) 2011-2012 Guus Sliepen , + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "crypto.h" +#include "ecdsa.h" +#include "sptps.h" +#include "utils.h" + +// Symbols necessary to link with logger.o +bool send_request(void *c, const char *msg, ...) { return false; } +struct list_t *connection_list = NULL; +bool send_meta(void *c, const char *msg , int len) { return false; } +char *logfilename = NULL; + +ecdsa_t mykey, hiskey; + +static bool send_data(void *handle, uint8_t type, const char *data, size_t len) { + char hex[len * 2 + 1]; + bin2hex(data, hex, len); + fprintf(stderr, "Sending %d bytes of data:\n%s\n", (int)len, hex); + const int *sock = handle; + if(send(*sock, data, len, 0) != len) + return false; + return true; +} + +static bool receive_record(void *handle, uint8_t type, const char *data, uint16_t len) { + fprintf(stderr, "Received type %d record of %hu bytes:\n", type, len); + fwrite(data, len, 1, stdout); + return true; +} + +int main(int argc, char *argv[]) { + bool initiator = false; + bool datagram = false; + + if(argc > 1 && !strcmp(argv[1], "-d")) { + datagram = true; + argc--; + argv++; + } + + if(argc < 4) { + fprintf(stderr, "Usage: %s [-d] my_ecdsa_key_file his_ecdsa_key_file [host] port\n", argv[0]); + return 1; + } + + if(argc > 4) + initiator = true; + +#ifdef HAVE_MINGW + static struct WSAData wsa_state; + if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) + return 1; +#endif + + struct addrinfo *ai, hint; + memset(&hint, 0, sizeof hint); + + hint.ai_family = AF_UNSPEC; + hint.ai_socktype = SOCK_STREAM; + hint.ai_protocol = IPPROTO_TCP; + hint.ai_flags = initiator ? 0 : AI_PASSIVE; + + if(getaddrinfo(initiator ? argv[3] : NULL, initiator ? argv[4] : argv[3], &hint, &ai) || !ai) { + fprintf(stderr, "getaddrinfo() failed: %s\n", strerror(errno)); + return 1; + } + + int sock = socket(ai->ai_family, SOCK_STREAM, IPPROTO_TCP); + if(sock < 0) { + fprintf(stderr, "Could not create socket: %s\n", strerror(errno)); + return 1; + } + + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof one); + + if(initiator) { + if(connect(sock, ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not connect to peer: %s\n", strerror(errno)); + return 1; + } + fprintf(stderr, "Connected\n"); + } else { + if(bind(sock, ai->ai_addr, ai->ai_addrlen)) { + fprintf(stderr, "Could not bind socket: %s\n", strerror(errno)); + return 1; + } + if(listen(sock, 1)) { + fprintf(stderr, "Could not listen on socket: %s\n", strerror(errno)); + return 1; + } + fprintf(stderr, "Listening...\n"); + + sock = accept(sock, NULL, NULL); + if(sock < 0) { + fprintf(stderr, "Could not accept connection: %s\n", strerror(errno)); + return 1; + } + + fprintf(stderr, "Connected\n"); + } + + crypto_init(); + + FILE *fp = fopen(argv[1], "r"); + if(!ecdsa_read_pem_private_key(&mykey, fp)) + return 1; + fclose(fp); + + fp = fopen(argv[2], "r"); + if(!ecdsa_read_pem_public_key(&hiskey, fp)) + return 1; + fclose(fp); + + fprintf(stderr, "Keys loaded\n"); + + sptps_t s; + if(!sptps_start(&s, &sock, initiator, datagram, mykey, hiskey, "sptps_test", 10, send_data, receive_record)) + return 1; + + while(true) { + char buf[65535] = ""; + + fd_set fds; + FD_ZERO(&fds); +#ifndef HAVE_MINGW + FD_SET(0, &fds); +#endif + FD_SET(sock, &fds); + if(select(sock + 1, &fds, NULL, NULL, NULL) <= 0) + return 1; + + if(FD_ISSET(0, &fds)) { + ssize_t len = read(0, buf, sizeof buf); + if(len < 0) { + fprintf(stderr, "Could not read from stdin: %s\n", strerror(errno)); + return 1; + } + if(len == 0) + break; + if(buf[0] == '^') + sptps_send_record(&s, SPTPS_HANDSHAKE, NULL, 0); + else if(buf[0] == '$') { + sptps_force_kex(&s); + if(len > 1) + sptps_send_record(&s, 0, buf, len); + } else + if(!sptps_send_record(&s, buf[0] == '!' ? 1 : 0, buf, buf[0] == '\n' ? 0 : buf[0] == '*' ? sizeof buf : len)) + return 1; + } + + if(FD_ISSET(sock, &fds)) { + ssize_t len = recv(sock, buf, sizeof buf, 0); + if(len < 0) { + fprintf(stderr, "Could not read from socket: %s\n", strerror(errno)); + return 1; + } + if(len == 0) { + fprintf(stderr, "Connection terminated by peer.\n"); + break; + } + char hex[len * 2 + 1]; + bin2hex(buf, hex, len); + fprintf(stderr, "Received %d bytes of data:\n%s\n", (int)len, hex); + if(!sptps_receive_data(&s, buf, len)) + return 1; + } + } + + if(!sptps_stop(&s)) + return 1; + + return 0; +} diff --git a/src/subnet.c b/src/subnet.c index bf1bda2..4b6c068 100644 --- a/src/subnet.c +++ b/src/subnet.c @@ -1,6 +1,6 @@ /* subnet.c -- handle subnet lookups and lists - Copyright (C) 2000-2010 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "splay_tree.h" #include "control_common.h" #include "device.h" +#include "hash.h" #include "logger.h" #include "net.h" #include "netutl.h" @@ -38,109 +39,14 @@ splay_tree_t *subnet_tree; /* Subnet lookup cache */ -static ipv4_t cache_ipv4_address[2]; -static subnet_t *cache_ipv4_subnet[2]; -static bool cache_ipv4_valid[2]; -static int cache_ipv4_slot; - -static ipv6_t cache_ipv6_address[2]; -static subnet_t *cache_ipv6_subnet[2]; -static bool cache_ipv6_valid[2]; -static int cache_ipv6_slot; - -static mac_t cache_mac_address[2]; -static subnet_t *cache_mac_subnet[2]; -static bool cache_mac_valid[2]; -static int cache_mac_slot; +hash_t *ipv4_cache; +hash_t *ipv6_cache; +hash_t *mac_cache; void subnet_cache_flush(void) { - cache_ipv4_valid[0] = cache_ipv4_valid[1] = false; - cache_ipv6_valid[0] = cache_ipv6_valid[1] = false; - cache_mac_valid[0] = cache_mac_valid[1] = false; -} - -/* Subnet comparison */ - -static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) { - int result; - - result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof a->net.mac.address); - - if(result) - return result; - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) - return result; - - return strcmp(a->owner->name, b->owner->name); -} - -static int subnet_compare_ipv4(const subnet_t *a, const subnet_t *b) { - int result; - - result = b->net.ipv4.prefixlength - a->net.ipv4.prefixlength; - - if(result) - return result; - - result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t)); - - if(result) - return result; - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) - return result; - - return strcmp(a->owner->name, b->owner->name); -} - -static int subnet_compare_ipv6(const subnet_t *a, const subnet_t *b) { - int result; - - result = b->net.ipv6.prefixlength - a->net.ipv6.prefixlength; - - if(result) - return result; - - result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t)); - - if(result) - return result; - - result = a->weight - b->weight; - - if(result || !a->owner || !b->owner) - return result; - - return strcmp(a->owner->name, b->owner->name); -} - -int subnet_compare(const subnet_t *a, const subnet_t *b) { - int result; - - result = a->type - b->type; - - if(result) - return result; - - switch (a->type) { - case SUBNET_MAC: - return subnet_compare_mac(a, b); - case SUBNET_IPV4: - return subnet_compare_ipv4(a, b); - case SUBNET_IPV6: - return subnet_compare_ipv6(a, b); - default: - logger(LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!", - a->type); - exit(0); - } - - return 0; + hash_clear(ipv4_cache); + hash_clear(ipv6_cache); + hash_clear(mac_cache); } /* Initialising trees */ @@ -148,11 +54,17 @@ int subnet_compare(const subnet_t *a, const subnet_t *b) { void init_subnets(void) { subnet_tree = splay_alloc_tree((splay_compare_t) subnet_compare, (splay_action_t) free_subnet); - subnet_cache_flush(); + ipv4_cache = hash_alloc(0x100, sizeof(ipv4_t)); + ipv6_cache = hash_alloc(0x100, sizeof(ipv6_t)); + mac_cache = hash_alloc(0x100, sizeof(mac_t)); } void exit_subnets(void) { splay_delete_tree(subnet_tree); + + hash_free(ipv4_cache); + hash_free(ipv6_cache); + hash_free(mac_cache); } splay_tree_t *new_subnet_tree(void) { @@ -191,139 +103,6 @@ void subnet_del(node_t *n, subnet_t *subnet) { subnet_cache_flush(); } -/* Ascii representation of subnets */ - -bool str2net(subnet_t *subnet, const char *subnetstr) { - int i, l; - uint16_t x[8]; - int weight = 10; - - if(sscanf(subnetstr, "%hu.%hu.%hu.%hu/%d#%d", - &x[0], &x[1], &x[2], &x[3], &l, &weight) >= 5) { - if(l < 0 || l > 32) - return false; - - subnet->type = SUBNET_IPV4; - subnet->net.ipv4.prefixlength = l; - subnet->weight = weight; - - for(i = 0; i < 4; i++) { - if(x[i] > 255) - return false; - subnet->net.ipv4.address.x[i] = x[i]; - } - - return true; - } - - if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d", - &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], - &l, &weight) >= 9) { - if(l < 0 || l > 128) - return false; - - subnet->type = SUBNET_IPV6; - subnet->net.ipv6.prefixlength = l; - subnet->weight = weight; - - for(i = 0; i < 8; i++) - subnet->net.ipv6.address.x[i] = htons(x[i]); - - return true; - } - - if(sscanf(subnetstr, "%hu.%hu.%hu.%hu#%d", &x[0], &x[1], &x[2], &x[3], &weight) >= 4) { - subnet->type = SUBNET_IPV4; - subnet->net.ipv4.prefixlength = 32; - subnet->weight = weight; - - for(i = 0; i < 4; i++) { - if(x[i] > 255) - return false; - subnet->net.ipv4.address.x[i] = x[i]; - } - - return true; - } - - if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx#%d", - &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &weight) >= 8) { - subnet->type = SUBNET_IPV6; - subnet->net.ipv6.prefixlength = 128; - subnet->weight = weight; - - for(i = 0; i < 8; i++) - subnet->net.ipv6.address.x[i] = htons(x[i]); - - return true; - } - - if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx#%d", - &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &weight) >= 6) { - subnet->type = SUBNET_MAC; - subnet->weight = weight; - - for(i = 0; i < 6; i++) - subnet->net.mac.address.x[i] = x[i]; - - return true; - } - - return false; -} - -bool net2str(char *netstr, int len, const subnet_t *subnet) { - if(!netstr || !subnet) { - logger(LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet); - return false; - } - - switch (subnet->type) { - case SUBNET_MAC: - snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx#%d", - subnet->net.mac.address.x[0], - subnet->net.mac.address.x[1], - subnet->net.mac.address.x[2], - subnet->net.mac.address.x[3], - subnet->net.mac.address.x[4], - subnet->net.mac.address.x[5], - subnet->weight); - break; - - case SUBNET_IPV4: - snprintf(netstr, len, "%hu.%hu.%hu.%hu/%d#%d", - subnet->net.ipv4.address.x[0], - subnet->net.ipv4.address.x[1], - subnet->net.ipv4.address.x[2], - subnet->net.ipv4.address.x[3], - subnet->net.ipv4.prefixlength, - subnet->weight); - break; - - case SUBNET_IPV6: - snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d", - ntohs(subnet->net.ipv6.address.x[0]), - ntohs(subnet->net.ipv6.address.x[1]), - ntohs(subnet->net.ipv6.address.x[2]), - ntohs(subnet->net.ipv6.address.x[3]), - ntohs(subnet->net.ipv6.address.x[4]), - ntohs(subnet->net.ipv6.address.x[5]), - ntohs(subnet->net.ipv6.address.x[6]), - ntohs(subnet->net.ipv6.address.x[7]), - subnet->net.ipv6.prefixlength, - subnet->weight); - break; - - default: - logger(LOG_ERR, - "net2str() was called with unknown subnet type %d, exiting!", - subnet->type); - exit(0); - } - - return true; -} - /* Subnet lookup routines */ subnet_t *lookup_subnet(const node_t *owner, const subnet_t *subnet) { @@ -331,26 +110,16 @@ subnet_t *lookup_subnet(const node_t *owner, const subnet_t *subnet) { } subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) { - subnet_t *p, *r = NULL; - splay_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_mac_valid[i]) - continue; - if(owner && cache_mac_subnet[i] && cache_mac_subnet[i]->owner != owner) - continue; - if(!memcmp(address, &cache_mac_address[i], sizeof *address)) - return cache_mac_subnet[i]; - } + if((r = hash_search(mac_cache, address))) + return r; // Search all subnets for a matching one - for(n = owner ? owner->subnet_tree->head : subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, owner ? owner->subnet_tree : subnet_tree) { if(!p || p->type != SUBNET_MAC) continue; @@ -363,33 +132,23 @@ subnet_t *lookup_subnet_mac(const node_t *owner, const mac_t *address) { // Cache the result - cache_mac_slot = !cache_mac_slot; - memcpy(&cache_mac_address[cache_mac_slot], address, sizeof *address); - cache_mac_subnet[cache_mac_slot] = r; - cache_mac_valid[cache_mac_slot] = true; + if(r) + hash_insert(mac_cache, address, r); return r; } subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { - subnet_t *p, *r = NULL; - splay_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_ipv4_valid[i]) - continue; - if(!memcmp(address, &cache_ipv4_address[i], sizeof *address)) - return cache_ipv4_subnet[i]; - } + if((r = hash_search(ipv4_cache, address))) + return r; // Search all subnets for a matching one - for(n = subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, subnet_tree) { if(!p || p->type != SUBNET_IPV4) continue; @@ -402,33 +161,23 @@ subnet_t *lookup_subnet_ipv4(const ipv4_t *address) { // Cache the result - cache_ipv4_slot = !cache_ipv4_slot; - memcpy(&cache_ipv4_address[cache_ipv4_slot], address, sizeof *address); - cache_ipv4_subnet[cache_ipv4_slot] = r; - cache_ipv4_valid[cache_ipv4_slot] = true; + if(r) + hash_insert(ipv4_cache, address, r); return r; } subnet_t *lookup_subnet_ipv6(const ipv6_t *address) { - subnet_t *p, *r = NULL; - splay_node_t *n; - int i; + subnet_t *r = NULL; // Check if this address is cached - for(i = 0; i < 2; i++) { - if(!cache_ipv6_valid[i]) - continue; - if(!memcmp(address, &cache_ipv6_address[i], sizeof *address)) - return cache_ipv6_subnet[i]; - } + if((r = hash_search(ipv6_cache, address))) + return r; // Search all subnets for a matching one - for(n = subnet_tree->head; n; n = n->next) { - p = n->data; - + for splay_each(subnet_t, p, subnet_tree) { if(!p || p->type != SUBNET_IPV6) continue; @@ -441,24 +190,20 @@ subnet_t *lookup_subnet_ipv6(const ipv6_t *address) { // Cache the result - cache_ipv6_slot = !cache_ipv6_slot; - memcpy(&cache_ipv6_address[cache_ipv6_slot], address, sizeof *address); - cache_ipv6_subnet[cache_ipv6_slot] = r; - cache_ipv6_valid[cache_ipv6_slot] = true; + if(r) + hash_insert(ipv6_cache, address, r); return r; } void subnet_update(node_t *owner, subnet_t *subnet, bool up) { - splay_node_t *node; - int i; - char *envp[9] = {NULL}; char netstr[MAXNETSTR]; char *name, *address, *port; char empty[] = ""; // Prepare environment variables to be passed to the script + char *envp[9] = {NULL}; xasprintf(&envp[0], "NETNAME=%s", netname ? : ""); xasprintf(&envp[1], "DEVICE=%s", device ? : ""); xasprintf(&envp[2], "INTERFACE=%s", iface ? : ""); @@ -469,15 +214,17 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { // 4 and 5 are reserved for SUBNET and WEIGHT xasprintf(&envp[6], "REMOTEADDRESS=%s", address); xasprintf(&envp[7], "REMOTEPORT=%s", port); + free(port); + free(address); } name = up ? "subnet-up" : "subnet-down"; if(!subnet) { - for(node = owner->subnet_tree->head; node; node = node->next) { - subnet = node->data; + for splay_each(subnet_t, subnet, owner->subnet_tree) { if(!net2str(netstr, sizeof netstr, subnet)) continue; + // Strip the weight from the subnet, and put it in its own environment variable char *weight = strchr(netstr, '#'); if(weight) @@ -512,20 +259,18 @@ void subnet_update(node_t *owner, subnet_t *subnet, bool up) { } } - for(i = 0; envp[i] && i < 8; i++) + for(int i = 0; envp[i] && i < 8; i++) free(envp[i]); } bool dump_subnets(connection_t *c) { - char netstr[MAXNETSTR]; - subnet_t *subnet; - splay_node_t *node; + for splay_each(subnet_t, subnet, subnet_tree) { + char netstr[MAXNETSTR]; - for(node = subnet_tree->head; node; node = node->next) { - subnet = node->data; if(!net2str(netstr, sizeof netstr, subnet)) continue; - send_request(c, "%d %d %s owner %s", + + send_request(c, "%d %d %s %s", CONTROL, REQ_DUMP_SUBNETS, netstr, subnet->owner->name); } diff --git a/src/subnet.h b/src/subnet.h index f22e6d5..9fd95b6 100644 --- a/src/subnet.h +++ b/src/subnet.h @@ -1,6 +1,6 @@ /* subnet.h -- header for subnet.c - Copyright (C) 2000-2009 Guus Sliepen , + Copyright (C) 2000-2012 Guus Sliepen , 2000-2005 Ivo Timmermans This program is free software; you can redistribute it and/or modify @@ -27,7 +27,7 @@ typedef enum subnet_type_t { SUBNET_MAC = 0, SUBNET_IPV4, SUBNET_IPV6, - SUBNET_TYPES /* Guardian */ + SUBNET_TYPES /* Guardian */ } subnet_type_t; typedef struct subnet_mac_t { @@ -47,11 +47,11 @@ typedef struct subnet_ipv6_t { #include "node.h" typedef struct subnet_t { - struct node_t *owner; /* the owner of this subnet */ + struct node_t *owner; /* the owner of this subnet */ - subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */ - time_t expires; /* expiry time */ - int weight; /* weight (higher value is higher priority) */ + subnet_type_t type; /* subnet type (IPv4? IPv6? MAC? something even weirder?) */ + time_t expires; /* expiry time */ + int weight; /* weight (higher value is higher priority) */ /* And now for the actual subnet: */ @@ -76,6 +76,10 @@ extern void free_subnet_tree(splay_tree_t *); extern void subnet_add(struct node_t *, subnet_t *); extern void subnet_del(struct node_t *, subnet_t *); extern void subnet_update(struct node_t *, subnet_t *, bool); +extern int maskcmp(const void *, const void *, int); +extern void maskcpy(void *, const void *, int, int); +extern void mask(void *, int, int); +extern bool maskcheck(const void *, int, int); extern bool net2str(char *, int, const subnet_t *); extern bool str2net(subnet_t *, const char *); extern subnet_t *lookup_subnet(const struct node_t *, const subnet_t *); @@ -85,4 +89,4 @@ extern subnet_t *lookup_subnet_ipv6(const ipv6_t *); extern bool dump_subnets(struct connection_t *); extern void subnet_cache_flush(void); -#endif /* __TINC_SUBNET_H__ */ +#endif /* __TINC_SUBNET_H__ */ diff --git a/src/subnet_parse.c b/src/subnet_parse.c new file mode 100644 index 0000000..f980180 --- /dev/null +++ b/src/subnet_parse.c @@ -0,0 +1,382 @@ +/* + subnet_parse.c -- handle subnet parsing + Copyright (C) 2000-2012 Guus Sliepen , + 2000-2005 Ivo Timmermans + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include "logger.h" +#include "net.h" +#include "netutl.h" +#include "subnet.h" +#include "utils.h" +#include "xalloc.h" + +/* Subnet mask handling */ + +int maskcmp(const void *va, const void *vb, int masklen) { + int i, m, result; + const char *a = va; + const char *b = vb; + + for(m = masklen, i = 0; m >= 8; m -= 8, i++) { + result = a[i] - b[i]; + if(result) + return result; + } + + if(m) + return (a[i] & (0x100 - (1 << (8 - m)))) - + (b[i] & (0x100 - (1 << (8 - m)))); + + return 0; +} + +void mask(void *va, int masklen, int len) { + int i; + char *a = va; + + i = masklen / 8; + masklen %= 8; + + if(masklen) + a[i++] &= (0x100 - (1 << (8 - masklen))); + + for(; i < len; i++) + a[i] = 0; +} + +void maskcpy(void *va, const void *vb, int masklen, int len) { + int i, m; + char *a = va; + const char *b = vb; + + for(m = masklen, i = 0; m >= 8; m -= 8, i++) + a[i] = b[i]; + + if(m) { + a[i] = b[i] & (0x100 - (1 << (8 - m))); + i++; + } + + for(; i < len; i++) + a[i] = 0; +} + +bool maskcheck(const void *va, int masklen, int len) { + int i; + const char *a = va; + + i = masklen / 8; + masklen %= 8; + + if(masklen && a[i++] & (0xff >> masklen)) + return false; + + for(; i < len; i++) + if(a[i] != 0) + return false; + + return true; +} + +/* Subnet comparison */ + +static int subnet_compare_mac(const subnet_t *a, const subnet_t *b) { + int result; + + result = memcmp(&a->net.mac.address, &b->net.mac.address, sizeof a->net.mac.address); + + if(result) + return result; + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) + return result; + + return strcmp(a->owner->name, b->owner->name); +} + +static int subnet_compare_ipv4(const subnet_t *a, const subnet_t *b) { + int result; + + result = b->net.ipv4.prefixlength - a->net.ipv4.prefixlength; + + if(result) + return result; + + result = memcmp(&a->net.ipv4.address, &b->net.ipv4.address, sizeof(ipv4_t)); + + if(result) + return result; + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) + return result; + + return strcmp(a->owner->name, b->owner->name); +} + +static int subnet_compare_ipv6(const subnet_t *a, const subnet_t *b) { + int result; + + result = b->net.ipv6.prefixlength - a->net.ipv6.prefixlength; + + if(result) + return result; + + result = memcmp(&a->net.ipv6.address, &b->net.ipv6.address, sizeof(ipv6_t)); + + if(result) + return result; + + result = a->weight - b->weight; + + if(result || !a->owner || !b->owner) + return result; + + return strcmp(a->owner->name, b->owner->name); +} + +int subnet_compare(const subnet_t *a, const subnet_t *b) { + int result; + + result = a->type - b->type; + + if(result) + return result; + + switch (a->type) { + case SUBNET_MAC: + return subnet_compare_mac(a, b); + case SUBNET_IPV4: + return subnet_compare_ipv4(a, b); + case SUBNET_IPV6: + return subnet_compare_ipv6(a, b); + default: + logger(DEBUG_ALWAYS, LOG_ERR, "subnet_compare() was called with unknown subnet type %d, exitting!", a->type); + exit(1); + } + + return 0; +} + +/* Ascii representation of subnets */ + +bool str2net(subnet_t *subnet, const char *subnetstr) { + int i, l; + uint16_t x[8]; + int weight = 10; + + if(sscanf(subnetstr, "%hu.%hu.%hu.%hu/%d#%d", + &x[0], &x[1], &x[2], &x[3], &l, &weight) >= 5) { + if(l < 0 || l > 32) + return false; + + subnet->type = SUBNET_IPV4; + subnet->net.ipv4.prefixlength = l; + subnet->weight = weight; + + for(int i = 0; i < 4; i++) { + if(x[i] > 255) + return false; + subnet->net.ipv4.address.x[i] = x[i]; + } + + return true; + } + + if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d", + &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], + &l, &weight) >= 9) { + if(l < 0 || l > 128) + return false; + + subnet->type = SUBNET_IPV6; + subnet->net.ipv6.prefixlength = l; + subnet->weight = weight; + + for(i = 0; i < 8; i++) + subnet->net.ipv6.address.x[i] = htons(x[i]); + + return true; + } + + if(sscanf(subnetstr, "%hu.%hu.%hu.%hu#%d", &x[0], &x[1], &x[2], &x[3], &weight) >= 4) { + subnet->type = SUBNET_IPV4; + subnet->net.ipv4.prefixlength = 32; + subnet->weight = weight; + + for(i = 0; i < 4; i++) { + if(x[i] > 255) + return false; + subnet->net.ipv4.address.x[i] = x[i]; + } + + return true; + } + + if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx#%d", + &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &x[6], &x[7], &weight) >= 8) { + subnet->type = SUBNET_IPV6; + subnet->net.ipv6.prefixlength = 128; + subnet->weight = weight; + + for(i = 0; i < 8; i++) + subnet->net.ipv6.address.x[i] = htons(x[i]); + + return true; + } + + if(sscanf(subnetstr, "%hx:%hx:%hx:%hx:%hx:%hx#%d", + &x[0], &x[1], &x[2], &x[3], &x[4], &x[5], &weight) >= 6) { + subnet->type = SUBNET_MAC; + subnet->weight = weight; + + for(i = 0; i < 6; i++) + subnet->net.mac.address.x[i] = x[i]; + + return true; + } + + // IPv6 short form + if(strstr(subnetstr, "::")) { + const char *p; + char *q; + int colons = 0; + + // Count number of colons + for(p = subnetstr; *p; p++) + if(*p == ':') + colons++; + + if(colons > 7) + return false; + + // Scan numbers before the double colon + p = subnetstr; + for(i = 0; i < colons; i++) { + if(*p == ':') + break; + x[i] = strtoul(p, &q, 0x10); + if(!q || p == q || *q != ':') + return false; + p = ++q; + } + + p++; + colons -= i; + if(!i) { + p++; + colons--; + } + + if(!*p || *p == '/' || *p == '#') + colons--; + + // Fill in the blanks + for(; i < 8 - colons; i++) + x[i] = 0; + + // Scan the remaining numbers + for(; i < 8; i++) { + x[i] = strtoul(p, &q, 0x10); + if(!q || p == q) + return false; + if(i == 7) { + p = q; + break; + } + if(*q != ':') + return false; + p = ++q; + } + + l = 128; + if(*p == '/') + sscanf(p, "/%d#%d", &l, &weight); + else if(*p == '#') + sscanf(p, "#%d", &weight); + + if(l < 0 || l > 128) + return false; + + subnet->type = SUBNET_IPV6; + subnet->net.ipv6.prefixlength = l; + subnet->weight = weight; + + for(i = 0; i < 8; i++) + subnet->net.ipv6.address.x[i] = htons(x[i]); + + return true; + } + + return false; +} + +bool net2str(char *netstr, int len, const subnet_t *subnet) { + if(!netstr || !subnet) { + logger(DEBUG_ALWAYS, LOG_ERR, "net2str() was called with netstr=%p, subnet=%p!", netstr, subnet); + return false; + } + + switch (subnet->type) { + case SUBNET_MAC: + snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx#%d", + subnet->net.mac.address.x[0], + subnet->net.mac.address.x[1], + subnet->net.mac.address.x[2], + subnet->net.mac.address.x[3], + subnet->net.mac.address.x[4], + subnet->net.mac.address.x[5], + subnet->weight); + break; + + case SUBNET_IPV4: + snprintf(netstr, len, "%hu.%hu.%hu.%hu/%d#%d", + subnet->net.ipv4.address.x[0], + subnet->net.ipv4.address.x[1], + subnet->net.ipv4.address.x[2], + subnet->net.ipv4.address.x[3], + subnet->net.ipv4.prefixlength, + subnet->weight); + break; + + case SUBNET_IPV6: + snprintf(netstr, len, "%hx:%hx:%hx:%hx:%hx:%hx:%hx:%hx/%d#%d", + ntohs(subnet->net.ipv6.address.x[0]), + ntohs(subnet->net.ipv6.address.x[1]), + ntohs(subnet->net.ipv6.address.x[2]), + ntohs(subnet->net.ipv6.address.x[3]), + ntohs(subnet->net.ipv6.address.x[4]), + ntohs(subnet->net.ipv6.address.x[5]), + ntohs(subnet->net.ipv6.address.x[6]), + ntohs(subnet->net.ipv6.address.x[7]), + subnet->net.ipv6.prefixlength, + subnet->weight); + break; + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "net2str() was called with unknown subnet type %d, exiting!", subnet->type); + exit(1); + } + + return true; +} diff --git a/src/tincctl.c b/src/tincctl.c index c055db5..c1cabdb 100644 --- a/src/tincctl.c +++ b/src/tincctl.c @@ -1,6 +1,6 @@ /* tincctl.c -- Controlling a running tincd - Copyright (C) 2007-2011 Guus Sliepen + Copyright (C) 2007-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -21,18 +21,32 @@ #include +#ifdef HAVE_READLINE +#include "readline/readline.h" +#include "readline/history.h" +#endif + #include "xalloc.h" #include "protocol.h" #include "control_common.h" #include "ecdsagen.h" +#include "info.h" #include "rsagen.h" #include "utils.h" #include "tincctl.h" #include "top.h" +#ifdef HAVE_MINGW +#define mkdir(a, b) mkdir(a) +#endif + + /* The name this program was run with. */ static char *program_name = NULL; +static char **orig_argv; +static int orig_argc; + /* If nonzero, display usage information and exit. */ static bool show_help = false; @@ -40,11 +54,24 @@ static bool show_help = false; static bool show_version = false; static char *name = NULL; -static char *identname = NULL; /* program name for syslog */ -static char *pidfilename = NULL; /* pid file location */ +static char *identname = NULL; /* program name for syslog */ +static char *pidfilename = NULL; /* pid file location */ +static char *confdir = NULL; static char controlcookie[1024]; char *netname = NULL; char *confbase = NULL; +static char *tinc_conf = NULL; +static char *hosts_dir = NULL; + +// Horrible global variables... +static int pid = 0; +static int fd = -1; +static char line[4096]; +static int code; +static int req; +static int result; +static bool force = false; +static bool tty = true; #ifdef HAVE_MINGW static struct WSAData wsa_state; @@ -52,13 +79,32 @@ static struct WSAData wsa_state; static struct option const long_options[] = { {"config", required_argument, NULL, 'c'}, + {"debug", optional_argument, NULL, 0}, + {"no-detach", no_argument, NULL, 0}, + {"mlock", no_argument, NULL, 0}, {"net", required_argument, NULL, 'n'}, {"help", no_argument, NULL, 1}, {"version", no_argument, NULL, 2}, {"pidfile", required_argument, NULL, 5}, + {"logfile", required_argument, NULL, 0}, + {"bypass-security", no_argument, NULL, 0}, + {"chroot", no_argument, NULL, 0}, + {"user", required_argument, NULL, 0}, + {"option", required_argument, NULL, 0}, + {"force", no_argument, NULL, 6}, {NULL, 0, NULL, 0} }; +static void version(void) { + printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, + VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR); + printf("Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others.\n" + "See the AUTHORS file for a complete list.\n\n" + "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" + "and you are welcome to redistribute it under certain conditions;\n" + "see the file COPYING for details.\n"); +} + static void usage(bool status) { if(status) fprintf(stderr, "Try `%s --help\' for more information.\n", @@ -73,10 +119,16 @@ static void usage(bool status) { " --version Output version information and exit.\n" "\n" "Valid commands are:\n" - " start Start tincd.\n" + " init [name] Create initial configuration files.\n" + " config Change configuration:\n" + " [get] VARIABLE - print current value of VARIABLE\n" + " [set] VARIABLE VALUE - set VARIABLE to VALUE\n" + " add VARIABLE VALUE - add VARIABLE with the given VALUE\n" + " del VARIABLE [VALUE] - remove VARIABLE [only ones with watching VALUE]\n" + " start [tincd options] Start tincd.\n" " stop Stop tincd.\n" " restart Restart tincd.\n" - " reload Reload configuration of running tincd.\n" + " reload Partially reload configuration of running tincd.\n" " pid Show PID of currently running tincd.\n" " generate-keys [bits] Generate new RSA and ECDSA public/private keypairs.\n" " generate-rsa-keys [bits] Generate a new RSA public/private keypair.\n" @@ -86,16 +138,20 @@ static void usage(bool status) { " edges - all known connections in the VPN\n" " subnets - all known subnets in the VPN\n" " connections - all meta connections with ourself\n" - " graph - graph of the VPN in dotty format\n" + " [di]graph - graph of the VPN in dotty format\n" + " info NODE|SUBNET|ADDRESS Give information about a particular NODE, SUBNET or ADDRESS.\n" " purge Purge unreachable nodes\n" " debug N Set debug level\n" " retry Retry all outgoing connections\n" - " reload Partial reload of configuration\n" " disconnect NODE Close meta connection with NODE\n" #ifdef HAVE_CURSES " top Show real-time statistics\n" #endif - " pcap Dump traffic in pcap format\n" + " pcap [snaplen] Dump traffic in pcap format [up to snaplen bytes per packet]\n" + " log [level] Dump log output [up to the specified level]\n" + " export Export host configuration of local node to standard output\n" + " export-all Export all host configuration files to standard output\n" + " import [--force] Import host configuration file(s) from standard input\n" "\n"); printf("Report bugs to tinc@tinc-vpn.org.\n"); } @@ -105,32 +161,36 @@ static bool parse_options(int argc, char **argv) { int r; int option_index = 0; - while((r = getopt_long(argc, argv, "c:n:", long_options, &option_index)) != EOF) { + while((r = getopt_long(argc, argv, "c:n:Dd::Lo:RU:", long_options, &option_index)) != EOF) { switch (r) { - case 0: /* long option */ + case 0: /* long option */ break; - case 'c': /* config file */ + case 'c': /* config file */ confbase = xstrdup(optarg); break; - case 'n': /* net name given */ + case 'n': /* net name given */ netname = xstrdup(optarg); break; - case 1: /* show help */ + case 1: /* show help */ show_help = true; break; - case 2: /* show version */ + case 2: /* show version */ show_version = true; break; - case 5: /* open control socket here */ + case 5: /* open control socket here */ pidfilename = xstrdup(optarg); break; - case '?': + case 6: /* force */ + force = true; + break; + + case '?': /* wrong options */ usage(true); return false; @@ -139,17 +199,109 @@ static bool parse_options(int argc, char **argv) { } } + if(!netname && (netname = getenv("NETNAME"))) + netname = xstrdup(netname); + + /* netname "." is special: a "top-level name" */ + + if(netname && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; + } + + if(netname && (strpbrk(netname, "\\/") || *netname == '.')) { + fprintf(stderr, "Invalid character in netname!\n"); + return false; + } + return true; } -FILE *ask_and_open(const char *filename, const char *what, const char *mode) { +static void disable_old_keys(const char *filename, const char *what) { + char tmpfile[PATH_MAX] = ""; + char buf[1024]; + bool disabled = false; + bool block = false; + bool error = false; + FILE *r, *w; + + r = fopen(filename, "r"); + if(!r) + return; + + snprintf(tmpfile, sizeof tmpfile, "%s.tmp", filename); + + w = fopen(tmpfile, "w"); + + while(fgets(buf, sizeof buf, r)) { + if(!block && !strncmp(buf, "-----BEGIN ", 11)) { + if((strstr(buf, " EC ") && strstr(what, "ECDSA")) || (strstr(buf, " RSA ") && strstr(what, "RSA"))) { + disabled = true; + block = true; + } + } + + bool ecdsapubkey = !strncasecmp(buf, "ECDSAPublicKey", 14) && strchr(" \t=", buf[14]) && strstr(what, "ECDSA"); + + if(ecdsapubkey) + disabled = true; + + if(w) { + if(block || ecdsapubkey) + fputc('#', w); + if(fputs(buf, w) < 0) { + error = true; + break; + } + } + + if(block && !strncmp(buf, "-----END ", 9)) + block = false; + } + + if(w) + if(fclose(w) < 0) + error = true; + if(ferror(r) || fclose(r) < 0) + error = true; + + if(disabled) { + if(!w || error) { + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + if(w) + unlink(tmpfile); + return; + } + +#ifdef HAVE_MINGW + // We cannot atomically replace files on Windows. + char bakfile[PATH_MAX] = ""; + snprintf(bakfile, sizeof bakfile, "%s.bak", filename); + if(rename(filename, bakfile) || rename(tmpfile, filename)) { + rename(bakfile, filename); +#else + if(rename(tmpfile, filename)) { +#endif + fprintf(stderr, "Warning: old key(s) found, remove them by hand!\n"); + } else { +#ifdef HAVE_MINGW + unlink(bakfile); +#endif + fprintf(stderr, "Warning: old key(s) found and disabled.\n"); + } + } + + unlink(tmpfile); +} + +static FILE *ask_and_open(const char *filename, const char *what, const char *mode, bool ask) { FILE *r; char *directory; char buf[PATH_MAX]; char buf2[PATH_MAX]; /* Check stdin and stdout */ - if(isatty(0) && isatty(1)) { + if(ask && tty) { /* Ask for a file and/or directory name. */ fprintf(stdout, "Please enter a file to save %s to [%s]: ", what, filename); @@ -176,11 +328,13 @@ FILE *ask_and_open(const char *filename, const char *what, const char *mode) { #endif /* The directory is a relative path or a filename. */ directory = get_current_dir_name(); - snprintf(buf2, sizeof buf2, "%s/%s", directory, filename); + snprintf(buf2, sizeof buf2, "%s" SLASH "%s", directory, filename); filename = buf2; } - umask(0077); /* Disallow everything for group and other */ + umask(0077); /* Disallow everything for group and other */ + + disable_old_keys(filename, what); /* Open it first to keep the inode busy */ @@ -198,10 +352,10 @@ FILE *ask_and_open(const char *filename, const char *what, const char *mode) { Generate a public/private ECDSA keypair, and ask for a file to store them in. */ -static bool ecdsa_keygen() { +static bool ecdsa_keygen(bool ask) { ecdsa_t key; FILE *f; - char *filename; + char *pubname, *privname; fprintf(stderr, "Generating ECDSA keypair:\n"); @@ -211,44 +365,38 @@ static bool ecdsa_keygen() { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/ecdsa_key.priv", confbase); - f = ask_and_open(filename, "private ECDSA key", "a"); + xasprintf(&privname, "%s" SLASH "ecdsa_key.priv", confbase); + f = ask_and_open(privname, "private ECDSA key", "a", ask); + free(privname); if(!f) return false; - + #ifdef HAVE_FCHMOD /* Make it unreadable for others. */ fchmod(fileno(f), 0600); #endif - - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); ecdsa_write_pem_private_key(&key, f); fclose(f); - free(filename); if(name) - xasprintf(&filename, "%s/hosts/%s", confbase, name); + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s/ecdsa_key.pub", confbase); + xasprintf(&pubname, "%s" SLASH "ecdsa_key.pub", confbase); - f = ask_and_open(filename, "public ECDSA key", "a"); + f = ask_and_open(pubname, "public ECDSA key", "a", ask); + free(pubname); if(!f) return false; - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); - char *pubkey = ecdsa_get_base64_public_key(&key); fprintf(f, "ECDSAPublicKey = %s\n", pubkey); free(pubkey); fclose(f); - free(filename); return true; } @@ -257,10 +405,10 @@ static bool ecdsa_keygen() { Generate a public/private RSA keypair, and ask for a file to store them in. */ -static bool rsa_keygen(int bits) { +static bool rsa_keygen(int bits, bool ask) { rsa_t key; FILE *f; - char *filename; + char *pubname, *privname; fprintf(stderr, "Generating %d bits keys:\n", bits); @@ -270,42 +418,36 @@ static bool rsa_keygen(int bits) { } else fprintf(stderr, "Done.\n"); - xasprintf(&filename, "%s/rsa_key.priv", confbase); - f = ask_and_open(filename, "private RSA key", "a"); + xasprintf(&privname, "%s" SLASH "rsa_key.priv", confbase); + f = ask_and_open(privname, "private RSA key", "a", ask); + free(privname); if(!f) return false; - + #ifdef HAVE_FCHMOD /* Make it unreadable for others. */ fchmod(fileno(f), 0600); #endif - - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); rsa_write_pem_private_key(&key, f); fclose(f); - free(filename); if(name) - xasprintf(&filename, "%s/hosts/%s", confbase, name); + xasprintf(&pubname, "%s" SLASH "hosts" SLASH "%s", confbase, name); else - xasprintf(&filename, "%s/rsa_key.pub", confbase); + xasprintf(&pubname, "%s" SLASH "rsa_key.pub", confbase); - f = ask_and_open(filename, "public RSA key", "a"); + f = ask_and_open(pubname, "public RSA key", "a", ask); + free(pubname); if(!f) return false; - if(ftell(f)) - fprintf(stderr, "Appending key to existing contents.\nMake sure only one key is stored in the file.\n"); - rsa_write_pem_public_key(&key, f); fclose(f); - free(filename); return true; } @@ -330,31 +472,40 @@ static void make_names(void) { if(!RegQueryValueEx(key, NULL, 0, 0, installdir, &len)) { if(!confbase) { if(netname) - xasprintf(&confbase, "%s/%s", installdir, netname); + xasprintf(&confbase, "%s" SLASH "%s", installdir, netname); else xasprintf(&confbase, "%s", installdir); } } if(!pidfilename) - xasprintf(&pidfilename, "%s/pid", confbase); + xasprintf(&pidfilename, "%s" SLASH "pid", confbase); RegCloseKey(key); - if(*installdir) - return; } + + if(!*installdir) { #endif + confdir = xstrdup(CONFDIR); if(!pidfilename) - xasprintf(&pidfilename, "%s/run/%s.pid", LOCALSTATEDIR, identname); + xasprintf(&pidfilename, "%s" SLASH "run" SLASH "%s.pid", LOCALSTATEDIR, identname); if(netname) { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc/%s", netname); + xasprintf(&confbase, CONFDIR SLASH "tinc" SLASH "%s", netname); else fprintf(stderr, "Both netname and configuration directory given, using the latter...\n"); } else { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc"); + xasprintf(&confbase, CONFDIR SLASH "tinc"); } + +#ifdef HAVE_MINGW + } else + confdir = xstrdup(installdir); +#endif + + xasprintf(&tinc_conf, "%s" SLASH "tinc.conf", confbase); + xasprintf(&hosts_dir, "%s" SLASH "hosts", confbase); } static char buffer[4096]; @@ -385,7 +536,7 @@ bool recvline(int fd, char *line, size_t len) { return true; } -bool recvdata(int fd, char *data, size_t len) { +static bool recvdata(int fd, char *data, size_t len) { while(blen < len) { int result = recv(fd, buffer + blen, sizeof buffer - blen, 0); if(result == -1 && errno == EINTR) @@ -428,11 +579,11 @@ bool sendline(int fd, char *format, ...) { blen -= result; } - return true; + return true; } -void pcap(int fd, FILE *out) { - sendline(fd, "%d %d", CONTROL, REQ_PCAP); +static void pcap(int fd, FILE *out, int snaplen) { + sendline(fd, "%d %d %d", CONTROL, REQ_PCAP, snaplen); char data[9018]; struct { @@ -447,7 +598,7 @@ void pcap(int fd, FILE *out) { 0xa1b2c3d4, 2, 4, 0, 0, - sizeof data, + snaplen ?: sizeof data, 1, }; @@ -482,6 +633,24 @@ void pcap(int fd, FILE *out) { } } +static void logcontrol(int fd, FILE *out, int level) { + sendline(fd, "%d %d %d", CONTROL, REQ_LOG, level); + char data[1024]; + char line[32]; + + while(recvline(fd, line, sizeof line)) { + int code, req, len; + int n = sscanf(line, "%d %d %d", &code, &req, &len); + if(n != 3 || code != CONTROL || req != REQ_LOG || len < 0 || len > sizeof data) + break; + if(!recvdata(fd, data, len)) + break; + fwrite(data, len, 1, out); + fputc('\n', out); + fflush(out); + } +} + #ifdef HAVE_MINGW static bool remove_service(void) { SC_HANDLE manager = NULL; @@ -517,85 +686,45 @@ static bool remove_service(void) { } #endif -int main(int argc, char *argv[], char *envp[]) { - int fd; - int result; - char host[128]; - char port[128]; - int pid; - - program_name = argv[0]; - - if(!parse_options(argc, argv)) - return 1; - - make_names(); - - if(show_version) { - printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, - VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR); - printf("Copyright (C) 1998-2009 Ivo Timmermans, Guus Sliepen and others.\n" - "See the AUTHORS file for a complete list.\n\n" - "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" - "and you are welcome to redistribute it under certain conditions;\n" - "see the file COPYING for details.\n"); - - return 0; +static bool connect_tincd(bool verbose) { + if(fd >= 0) { + fd_set r; + FD_ZERO(&r); + FD_SET(fd, &r); + struct timeval tv = {0, 0}; + if(select(fd + 1, &r, NULL, NULL, &tv)) { + fprintf(stderr, "Previous connection to tincd lost, reconnecting.\n"); + close(fd); + fd = -1; + } else { + return true; + } } - if(show_help) { - usage(false); - return 0; - } - - if(optind >= argc) { - fprintf(stderr, "Not enough arguments.\n"); - usage(true); - return 1; - } - - // First handle commands that don't involve connecting to a running tinc daemon. - - if(!strcasecmp(argv[optind], "generate-rsa-keys")) { - return !rsa_keygen(optind > argc ? atoi(argv[optind + 1]) : 2048); - } - - if(!strcasecmp(argv[optind], "generate-ecdsa-keys")) { - return !ecdsa_keygen(); - } - - if(!strcasecmp(argv[optind], "generate-keys")) { - return !(rsa_keygen(optind > argc ? atoi(argv[optind + 1]) : 2048) && ecdsa_keygen()); - } - - if(!strcasecmp(argv[optind], "start")) { - argv[optind] = NULL; - execve(SBINDIR "/tincd", argv, envp); - fprintf(stderr, "Could not start tincd: %s", strerror(errno)); - return 1; - } - - /* - * Now handle commands that do involve connecting to a running tinc daemon. - * Authenticate the server by ensuring the parent directory can be - * traversed only by root. Note this is not totally race-free unless all - * ancestors are writable only by trusted users, which we don't verify. - */ - FILE *f = fopen(pidfilename, "r"); if(!f) { - fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); - return 1; + if(verbose) + fprintf(stderr, "Could not open pid file %s: %s\n", pidfilename, strerror(errno)); + return false; } + + char host[128]; + char port[128]; + if(fscanf(f, "%20d %1024s %128s port %128s", &pid, controlcookie, host, port) != 4) { - fprintf(stderr, "Could not parse pid file %s\n", pidfilename); - return 1; + if(verbose) + fprintf(stderr, "Could not parse pid file %s\n", pidfilename); + fclose(f); + return false; } + fclose(f); + #ifdef HAVE_MINGW if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { - fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); - return 1; + if(verbose) + fprintf(stderr, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); + return false; } #endif @@ -609,225 +738,1460 @@ int main(int argc, char *argv[], char *envp[]) { struct addrinfo *res = NULL; if(getaddrinfo(host, port, &hints, &res) || !res) { - fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); - return 1; + if(verbose) + fprintf(stderr, "Cannot resolve %s port %s: %s", host, port, strerror(errno)); + return false; } fd = socket(res->ai_family, SOCK_STREAM, IPPROTO_TCP); if(fd < 0) { - fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); - return 1; + if(verbose) + fprintf(stderr, "Cannot create TCP socket: %s\n", sockstrerror(sockerrno)); + return false; } #ifdef HAVE_MINGW unsigned long arg = 0; if(ioctlsocket(fd, FIONBIO, &arg) != 0) { - fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); + if(verbose) + fprintf(stderr, "ioctlsocket failed: %s", sockstrerror(sockerrno)); } #endif if(connect(fd, res->ai_addr, res->ai_addrlen) < 0) { - fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); - return 1; + if(verbose) + fprintf(stderr, "Cannot connect to %s port %s: %s\n", host, port, sockstrerror(sockerrno)); + close(fd); + fd = -1; + return false; } freeaddrinfo(res); - char line[4096]; char data[4096]; - int code, version, req; + int version; if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %s %d", &code, data, &version) != 3 || code != 0) { - fprintf(stderr, "Cannot read greeting from control socket: %s\n", - sockstrerror(sockerrno)); - return 1; + if(verbose) + fprintf(stderr, "Cannot read greeting from control socket: %s\n", sockstrerror(sockerrno)); + close(fd); + fd = -1; + return false; } sendline(fd, "%d ^%s %d", ID, controlcookie, TINC_CTL_VERSION_CURRENT); - + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &version, &pid) != 3 || code != 4 || version != TINC_CTL_VERSION_CURRENT) { - fprintf(stderr, "Could not fully establish control socket connection\n"); + if(verbose) + fprintf(stderr, "Could not fully establish control socket connection\n"); + close(fd); + fd = -1; + return false; + } + + return true; +} + + +static int cmd_start(int argc, char *argv[]) { + if(connect_tincd(false)) { + if(netname) + fprintf(stderr, "A tincd is already running for net `%s' with pid %d.\n", netname, pid); + else + fprintf(stderr, "A tincd is already running with pid %d.\n", pid); + return 0; + } + + char *c; + char *slash = strrchr(program_name, '/'); + +#ifdef HAVE_MINGW + if ((c = strrchr(program_name, '\\')) > slash) + slash = c; +#endif + + if (slash++) + xasprintf(&c, "%.*stincd", (int)(slash - program_name), program_name); + else + c = "tincd"; + + int nargc = 0; + char **nargv = xmalloc_and_zero((optind + argc) * sizeof *nargv); + + nargv[nargc++] = c; + for(int i = 1; i < optind; i++) + nargv[nargc++] = orig_argv[i]; + for(int i = 1; i < argc; i++) + nargv[nargc++] = argv[i]; + +#ifdef HAVE_MINGW + execvp(c, nargv); + fprintf(stderr, "Error starting %s: %s\n", c, strerror(errno)); + return 1; +#else + pid_t pid = fork(); + if(pid == -1) { + fprintf(stderr, "Could not fork: %s\n", strerror(errno)); + free(nargv); return 1; } - if(!strcasecmp(argv[optind], "pid")) { - printf("%d\n", pid); - return 0; + if(!pid) + exit(execvp(c, nargv)); + + free(nargv); + + int status = -1; + if(waitpid(pid, &status, 0) != pid || !WIFEXITED(status) || WEXITSTATUS(status)) { + fprintf(stderr, "Error starting %s\n", c); + return 1; } - if(!strcasecmp(argv[optind], "stop")) { -#ifndef HAVE_MINGW - sendline(fd, "%d %d", CONTROL, REQ_STOP); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) { - fprintf(stderr, "Could not stop tinc daemon\n"); - return 1; - } -#else - if(!remove_service()) - return 1; + return 0; #endif - return 0; - } +} - if(!strcasecmp(argv[optind], "reload")) { - sendline(fd, "%d %d", CONTROL, REQ_RELOAD); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) { - fprintf(stderr, "Could not reload tinc daemon\n"); - return 1; - } - return 0; - } +static int cmd_stop(int argc, char *argv[]) { +#ifndef HAVE_MINGW + if(!connect_tincd(true)) { + if(pid) { + if(kill(pid, SIGTERM)) { + fprintf(stderr, "Could not send TERM signal to process with PID %u: %s\n", pid, strerror(errno)); + return 1; + } - if(!strcasecmp(argv[optind], "restart")) { - sendline(fd, "%d %d", CONTROL, REQ_RESTART); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RESTART || result) { - fprintf(stderr, "Could not restart tinc daemon\n"); - return 1; - } - return 0; - } - - if(!strcasecmp(argv[optind], "retry")) { - sendline(fd, "%d %d", CONTROL, REQ_RETRY); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) { - fprintf(stderr, "Could not retry outgoing connections\n"); - return 1; - } - return 0; - } - - if(!strcasecmp(argv[optind], "dump")) { - if(argc < optind + 2) { - fprintf(stderr, "Not enough arguments.\n"); - usage(true); - return 1; + fprintf(stderr, "Sent TERM signal to process with PID %u.\n", pid); + waitpid(pid, NULL, 0); + return 0; } - bool do_graph = false; + return 1; + } - if(!strcasecmp(argv[optind+1], "nodes")) - sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); - else if(!strcasecmp(argv[optind+1], "edges")) - sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); - else if(!strcasecmp(argv[optind+1], "subnets")) - sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); - else if(!strcasecmp(argv[optind+1], "connections")) - sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); - else if(!strcasecmp(argv[optind+1], "graph")) { - sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); - sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); - do_graph = true; - printf("digraph {\n"); + sendline(fd, "%d %d", CONTROL, REQ_STOP); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_STOP || result) { + fprintf(stderr, "Could not stop tinc daemon.\n"); + return 1; + } + + // Wait for tincd to close the connection... + fd_set r; + FD_ZERO(&r); + FD_SET(fd, &r); + select(fd + 1, &r, NULL, NULL, NULL); +#else + if(!remove_service()) + return 1; +#endif + close(fd); + pid = 0; + fd = -1; + + return 0; +} + +static int cmd_restart(int argc, char *argv[]) { + cmd_stop(argc, argv); + return cmd_start(argc, argv); +} + +static int cmd_reload(int argc, char *argv[]) { + if(!connect_tincd(true)) + return 1; + + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RELOAD || result) { + fprintf(stderr, "Could not reload configuration.\n"); + return 1; + } + + return 0; + +} + +static int cmd_dump(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + usage(true); + return 1; + } + + if(!connect_tincd(true)) + return 1; + + int do_graph = 0; + + if(!strcasecmp(argv[1], "nodes")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + else if(!strcasecmp(argv[1], "edges")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + else if(!strcasecmp(argv[1], "subnets")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + else if(!strcasecmp(argv[1], "connections")) + sendline(fd, "%d %d", CONTROL, REQ_DUMP_CONNECTIONS); + else if(!strcasecmp(argv[1], "graph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = 1; + } else if(!strcasecmp(argv[1], "digraph")) { + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_EDGES); + do_graph = 2; + } else { + fprintf(stderr, "Unknown dump type '%s'.\n", argv[1]); + usage(true); + return 1; + } + + if(do_graph == 1) + printf("graph {\n"); + else if(do_graph == 2) + printf("digraph {\n"); + + while(recvline(fd, line, sizeof line)) { + char node1[4096], node2[4096]; + int n = sscanf(line, "%d %d %s %s", &code, &req, node1, node2); + if(n == 2) { + if(do_graph && req == REQ_DUMP_NODES) + continue; + else { + if(do_graph) + printf("}\n"); + return 0; + } + } + if(n < 2) + break; + + char node[4096]; + char from[4096]; + char to[4096]; + char subnet[4096]; + char host[4096]; + char port[4096]; + char via[4096]; + char nexthop[4096]; + int cipher, digest, maclength, compression, distance, socket, weight; + short int pmtu, minmtu, maxmtu; + unsigned int options, status_int; + node_status_t status; + long int last_state_change; + + switch(req) { + case REQ_DUMP_NODES: { + int n = sscanf(line, "%*d %*d %s %s port %s %d %d %d %d %x %x %s %s %d %hd %hd %hd %ld", node, host, port, &cipher, &digest, &maclength, &compression, &options, &status_int, nexthop, via, &distance, &pmtu, &minmtu, &maxmtu, &last_state_change); + if(n != 16) { + fprintf(stderr, "Unable to parse node dump from tincd: %s\n", line); + return 1; + } + if(do_graph) { + memcpy(&status, &status_int, sizeof status); + const char *color = "black"; + if(!strcmp(host, "MYSELF")) + color = "green"; + else if(!status.reachable) + color = "red"; + else if(strcmp(via, node)) + color = "orange"; + else if(!status.validkey) + color = "black"; + else if(minmtu > 0) + color = "green"; + printf(" %s [label = \"%s\", color = \"%s\"%s];\n", node, node, color, strcmp(host, "MYSELF") ? "" : ", style = \"filled\""); + } else { + printf("%s at %s port %s cipher %d digest %d maclength %d compression %d options %x status %04x nexthop %s via %s distance %d pmtu %hd (min %hd max %hd)\n", + node, host, port, cipher, digest, maclength, compression, options, status_int, nexthop, via, distance, pmtu, minmtu, maxmtu); + } + } break; + + case REQ_DUMP_EDGES: { + int n = sscanf(line, "%*d %*d %s %s %s port %s %x %d", from, to, host, port, &options, &weight); + if(n != 6) { + fprintf(stderr, "Unable to parse edge dump from tincd.\n"); + return 1; + } + + if(do_graph) { + float w = 1 + 65536.0 / weight; + if(do_graph == 1 && strcmp(node1, node2) > 0) + printf(" %s -- %s [w = %f, weight = %f];\n", node1, node2, w, w); + else if(do_graph == 2) + printf(" %s -> %s [w = %f, weight = %f];\n", node1, node2, w, w); + } else { + printf("%s to %s at %s port %s options %x weight %d\n", from, to, host, port, options, weight); + } + } break; + + case REQ_DUMP_SUBNETS: { + int n = sscanf(line, "%*d %*d %s %s", subnet, node); + if(n != 2) { + fprintf(stderr, "Unable to parse subnet dump from tincd.\n"); + return 1; + } + printf("%s owner %s\n", strip_weight(subnet), node); + } break; + + case REQ_DUMP_CONNECTIONS: { + int n = sscanf(line, "%*d %*d %s %s port %s %x %d %x", node, host, port, &options, &socket, &status_int); + if(n != 6) { + fprintf(stderr, "Unable to parse connection dump from tincd.\n"); + return 1; + } + printf("%s at %s port %s options %x socket %d status %x\n", node, host, port, options, socket, status_int); + } break; + + default: + fprintf(stderr, "Unable to parse dump from tincd.\n"); + return 1; + } + } + + fprintf(stderr, "Error receiving dump.\n"); + return 1; +} + +static int cmd_purge(int argc, char *argv[]) { + if(!connect_tincd(true)) + return 1; + + sendline(fd, "%d %d", CONTROL, REQ_PURGE); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) { + fprintf(stderr, "Could not purge old information.\n"); + return 1; + } + + return 0; +} + +static int cmd_debug(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!connect_tincd(true)) + return 1; + + int debuglevel = atoi(argv[1]); + int origlevel; + + sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &origlevel) != 3 || code != CONTROL || req != REQ_SET_DEBUG) { + fprintf(stderr, "Could not set debug level.\n"); + return 1; + } + + fprintf(stderr, "Old level %d, new level %d.\n", origlevel, debuglevel); + return 0; +} + +static int cmd_retry(int argc, char *argv[]) { + if(!connect_tincd(true)) + return 1; + + sendline(fd, "%d %d", CONTROL, REQ_RETRY); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_RETRY || result) { + fprintf(stderr, "Could not retry outgoing connections.\n"); + return 1; + } + + return 0; +} + +static int cmd_connect(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!check_id(argv[1])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!connect_tincd(true)) + return 1; + + sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, argv[1]); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_CONNECT || result) { + fprintf(stderr, "Could not connect to %s.\n", argv[1]); + return 1; + } + + return 0; +} + +static int cmd_disconnect(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!check_id(argv[1])) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!connect_tincd(true)) + return 1; + + sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, argv[1]); + if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_DISCONNECT || result) { + fprintf(stderr, "Could not disconnect %s.\n", argv[1]); + return 1; + } + + return 0; +} + +static int cmd_top(int argc, char *argv[]) { +#ifdef HAVE_CURSES + if(!connect_tincd(true)) + return 1; + + top(fd); + return 0; +#else + fprintf(stderr, "This version of tincctl was compiled without support for the curses library.\n"); + return 1; +#endif +} + +static int cmd_pcap(int argc, char *argv[]) { + if(!connect_tincd(true)) + return 1; + + pcap(fd, stdout, argc > 1 ? atoi(argv[1]) : 0); + return 0; +} + +static int cmd_log(int argc, char *argv[]) { + if(!connect_tincd(true)) + return 1; + + logcontrol(fd, stdout, argc > 1 ? atoi(argv[1]) : -1); + return 0; +} + +static int cmd_pid(int argc, char *argv[]) { + if(!connect_tincd(true) && !pid) + return 1; + + printf("%d\n", pid); + return 0; +} + +static int rstrip(char *value) { + int len = strlen(value); + while(len && strchr("\t\r\n ", value[len - 1])) + value[--len] = 0; + return len; +} + +static char *get_my_name() { + FILE *f = fopen(tinc_conf, "r"); + if(!f) { + fprintf(stderr, "Could not open %s: %s\n", tinc_conf, strerror(errno)); + return NULL; + } + + char buf[4096]; + char *value; + while(fgets(buf, sizeof buf, f)) { + int len = strcspn(buf, "\t ="); + value = buf + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + if(!rstrip(value)) + continue; + buf[len] = 0; + if(strcasecmp(buf, "Name")) + continue; + if(*value) { + fclose(f); + return strdup(value); + } + } + + fclose(f); + fprintf(stderr, "Could not find Name in %s.\n", tinc_conf); + return NULL; +} + +#define VAR_SERVER 1 /* Should be in tinc.conf */ +#define VAR_HOST 2 /* Can be in host config file */ +#define VAR_MULTIPLE 4 /* Multiple statements allowed */ +#define VAR_OBSOLETE 8 /* Should not be used anymore */ + +static struct { + const char *name; + int type; +} const variables[] = { + /* Server configuration */ + {"AddressFamily", VAR_SERVER}, + {"BindToAddress", VAR_SERVER | VAR_MULTIPLE}, + {"BindToInterface", VAR_SERVER}, + {"Broadcast", VAR_SERVER}, + {"ConnectTo", VAR_SERVER | VAR_MULTIPLE}, + {"DecrementTTL", VAR_SERVER}, + {"Device", VAR_SERVER}, + {"DeviceType", VAR_SERVER}, + {"DirectOnly", VAR_SERVER}, + {"ECDSAPrivateKeyFile", VAR_SERVER}, + {"ExperimentalProtocol", VAR_SERVER}, + {"Forwarding", VAR_SERVER}, + {"GraphDumpFile", VAR_SERVER | VAR_OBSOLETE}, + {"Hostnames", VAR_SERVER}, + {"IffOneQueue", VAR_SERVER}, + {"Interface", VAR_SERVER}, + {"KeyExpire", VAR_SERVER}, + {"LocalDiscovery", VAR_SERVER}, + {"MACExpire", VAR_SERVER}, + {"MaxOutputBufferSize", VAR_SERVER}, + {"MaxTimeout", VAR_SERVER}, + {"Mode", VAR_SERVER}, + {"Name", VAR_SERVER}, + {"PingInterval", VAR_SERVER}, + {"PingTimeout", VAR_SERVER}, + {"PriorityInheritance", VAR_SERVER}, + {"PrivateKey", VAR_SERVER | VAR_OBSOLETE}, + {"PrivateKeyFile", VAR_SERVER}, + {"ProcessPriority", VAR_SERVER}, + {"Proxy", VAR_SERVER}, + {"ReplayWindow", VAR_SERVER}, + {"ScriptsExtension", VAR_SERVER}, + {"ScriptsInterpreter", VAR_SERVER}, + {"StrictSubnets", VAR_SERVER}, + {"TunnelServer", VAR_SERVER}, + {"UDPRcvBuf", VAR_SERVER}, + {"UDPSndBuf", VAR_SERVER}, + {"VDEGroup", VAR_SERVER}, + {"VDEPort", VAR_SERVER}, + /* Host configuration */ + {"Address", VAR_HOST | VAR_MULTIPLE}, + {"Cipher", VAR_SERVER | VAR_HOST}, + {"ClampMSS", VAR_SERVER | VAR_HOST}, + {"Compression", VAR_SERVER | VAR_HOST}, + {"Digest", VAR_SERVER | VAR_HOST}, + {"ECDSAPublicKey", VAR_HOST}, + {"ECDSAPublicKeyFile", VAR_SERVER | VAR_HOST}, + {"IndirectData", VAR_SERVER | VAR_HOST}, + {"MACLength", VAR_SERVER | VAR_HOST}, + {"PMTU", VAR_SERVER | VAR_HOST}, + {"PMTUDiscovery", VAR_SERVER | VAR_HOST}, + {"Port", VAR_HOST}, + {"PublicKey", VAR_HOST | VAR_OBSOLETE}, + {"PublicKeyFile", VAR_SERVER | VAR_HOST | VAR_OBSOLETE}, + {"Subnet", VAR_HOST | VAR_MULTIPLE}, + {"TCPOnly", VAR_SERVER | VAR_HOST}, + {"Weight", VAR_HOST}, + {NULL, 0} +}; + +static int cmd_config(int argc, char *argv[]) { + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + int action = -2; + if(!strcasecmp(argv[1], "get")) { + argv++, argc--; + } else if(!strcasecmp(argv[1], "add")) { + argv++, argc--, action = 1; + } else if(!strcasecmp(argv[1], "del")) { + argv++, argc--, action = -1; + } else if(!strcasecmp(argv[1], "replace") || !strcasecmp(argv[1], "set") || !strcasecmp(argv[1], "change")) { + argv++, argc--, action = 0; + } + + if(argc < 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + // Concatenate the rest of the command line + strncpy(line, argv[1], sizeof line - 1); + for(int i = 2; i < argc; i++) { + strncat(line, " ", sizeof line - 1 - strlen(line)); + strncat(line, argv[i], sizeof line - 1 - strlen(line)); + } + + // Liberal parsing into node name, variable name and value. + char *node = NULL; + char *variable; + char *value; + int len; + + len = strcspn(line, "\t ="); + value = line + len; + value += strspn(value, "\t "); + if(*value == '=') { + value++; + value += strspn(value, "\t "); + } + line[len] = '\0'; + variable = strchr(line, '.'); + if(variable) { + node = line; + *variable++ = 0; + } else { + variable = line; + } + + if(!*variable) { + fprintf(stderr, "No variable given.\n"); + return 1; + } + + if(action >= 0 && !*value) { + fprintf(stderr, "No value for variable given.\n"); + return 1; + } + + if(action < -1 && *value) + action = 0; + + /* Some simple checks. */ + bool found = false; + + for(int i = 0; variables[i].name; i++) { + if(strcasecmp(variables[i].name, variable)) + continue; + + found = true; + variable = (char *)variables[i].name; + + /* Discourage use of obsolete variables. */ + + if(variables[i].type & VAR_OBSOLETE && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is an obsolete variable!\n", variable); + } else { + fprintf(stderr, "%s is an obsolete variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Don't put server variables in host config files */ + + if(node && !(variables[i].type & VAR_HOST) && action >= 0) { + if(force) { + fprintf(stderr, "Warning: %s is not a host configuration variable!\n", variable); + } else { + fprintf(stderr, "%s is not a host configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + /* Should this go into our own host config file? */ + + if(!node && !(variables[i].type & VAR_SERVER)) { + node = get_my_name(); + if(!node) + return 1; + } + + break; + } + + if(node && !check_id(node)) { + fprintf(stderr, "Invalid name for node.\n"); + return 1; + } + + if(!found) { + if(force || action < 0) { + fprintf(stderr, "Warning: %s is not a known configuration variable!\n", variable); } else { - fprintf(stderr, "Unknown dump type '%s'.\n", argv[optind+1]); - usage(true); + fprintf(stderr, "%s: is not a known configuration variable! Use --force to use it anyway.\n", variable); + return 1; + } + } + + // Open the right configuration file. + char *filename; + if(node) + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, node); + else + filename = tinc_conf; + + FILE *f = fopen(filename, "r"); + if(!f) { + if(action < 0 || errno != ENOENT) { + fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno)); return 1; } - while(recvline(fd, line, sizeof line)) { - char node1[4096], node2[4096]; - int n = sscanf(line, "%d %d %s to %s", &code, &req, node1, node2); - if(n == 2) { - if(do_graph && req == REQ_DUMP_NODES) + // If it doesn't exist, create it. + f = fopen(filename, "a+"); + if(!f) { + fprintf(stderr, "Could not create configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } else { + fprintf(stderr, "Created configuration file %s.\n", filename); + } + } + + char *tmpfile = NULL; + FILE *tf = NULL; + + if(action >= -1) { + xasprintf(&tmpfile, "%s.config.tmp", filename); + tf = fopen(tmpfile, "w"); + if(!tf) { + fprintf(stderr, "Could not open temporary file %s: %s\n", tmpfile, strerror(errno)); + fclose(f); + return 1; + } + } + + // Copy the file, making modifications on the fly, unless we are just getting a value. + char buf1[4096]; + char buf2[4096]; + bool set = false; + bool removed = false; + found = false; + + while(fgets(buf1, sizeof buf1, f)) { + buf1[sizeof buf1 - 1] = 0; + strncpy(buf2, buf1, sizeof buf2); + + // Parse line in a simple way + char *bvalue; + int len; + + len = strcspn(buf2, "\t ="); + bvalue = buf2 + len; + bvalue += strspn(bvalue, "\t "); + if(*bvalue == '=') { + bvalue++; + bvalue += strspn(bvalue, "\t "); + } + rstrip(bvalue); + buf2[len] = '\0'; + + // Did it match? + if(!strcasecmp(buf2, variable)) { + // Get + if(action < -1) { + found = true; + printf("%s\n", bvalue); + // Del + } else if(action == -1) { + if(!*value || !strcasecmp(bvalue, value)) { + removed = true; continue; - else { - if(do_graph) - printf("}\n"); - return 0; + } + // Set + } else if(action == 0) { + // Already set? Delete the rest... + if(set) + continue; + // Otherwise, replace. + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + set = true; + continue; + } + } + + if(action >= -1) { + // Copy original line... + if(fputs(buf1, tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; + } + + // Add newline if it is missing... + if(*buf1 && buf1[strlen(buf1) - 1] != '\n') { + if(fputc('\n', tf) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; } } - if(n < 2) - break; - - if(!do_graph) - printf("%s\n", line + 5); - else { - if(req == REQ_DUMP_NODES) - printf(" %s [label = \"%s\"];\n", node1, node1); - else - printf(" %s -> %s;\n", node1, node2); - } } + } - fprintf(stderr, "Error receiving dump\n"); + // Make sure we read everything... + if(ferror(f) || !feof(f)) { + fprintf(stderr, "Error while reading from configuration file %s: %s\n", filename, strerror(errno)); return 1; } - if(!strcasecmp(argv[optind], "purge")) { - sendline(fd, "%d %d", CONTROL, REQ_PURGE); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_PURGE || result) { - fprintf(stderr, "Could not purge tinc daemon\n"); + if(fclose(f)) { + fprintf(stderr, "Error closing configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + // Add new variable if necessary. + if(action > 0 || (action == 0 && !set)) { + if(fprintf(tf, "%s = %s\n", variable, value) < 0) { + fprintf(stderr, "Error writing to temporary file %s: %s\n", tmpfile, strerror(errno)); return 1; } + } + + if(action < -1) { + if(!found) + fprintf(stderr, "No matching configuration variables found.\n"); return 0; } - if(!strcasecmp(argv[optind], "debug")) { - int debuglevel, origlevel; - - if(argc != optind + 2) { - fprintf(stderr, "Invalid arguments.\n"); - return 1; - } - debuglevel = atoi(argv[optind+1]); - - sendline(fd, "%d %d %d", CONTROL, REQ_SET_DEBUG, debuglevel); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &origlevel) != 3 || code != CONTROL || req != REQ_SET_DEBUG) { - fprintf(stderr, "Could not purge tinc daemon\n"); - return 1; - } - - fprintf(stderr, "Old level %d, new level %d\n", origlevel, debuglevel); - return 0; + // Make sure we wrote everything... + if(fclose(tf)) { + fprintf(stderr, "Error closing temporary file %s: %s\n", tmpfile, strerror(errno)); + return 1; } - if(!strcasecmp(argv[optind], "connect")) { - if(argc != optind + 2) { - fprintf(stderr, "Invalid arguments.\n"); - return 1; - } - char *name = argv[optind + 1]; - - sendline(fd, "%d %d %s", CONTROL, REQ_CONNECT, name); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_CONNECT || result) { - fprintf(stderr, "Could not connect to %s\n", name); - return 1; - } - return 0; + // Could we find what we had to remove? + if(action < 0 && !removed) { + remove(tmpfile); + fprintf(stderr, "No configuration variables deleted.\n"); + return *value; } - if(!strcasecmp(argv[optind], "disconnect")) { - if(argc != optind + 2) { - fprintf(stderr, "Invalid arguments.\n"); - return 1; - } - char *name = argv[optind + 1]; - - sendline(fd, "%d %d %s", CONTROL, REQ_DISCONNECT, name); - if(!recvline(fd, line, sizeof line) || sscanf(line, "%d %d %d", &code, &req, &result) != 3 || code != CONTROL || req != REQ_DISCONNECT || result) { - fprintf(stderr, "Could not disconnect %s\n", name); - return 1; - } - return 0; + // Replace the configuration file with the new one +#ifdef HAVE_MINGW + if(remove(filename)) { + fprintf(stderr, "Error replacing file %s: %s\n", filename, strerror(errno)); + return 1; + } +#endif + if(rename(tmpfile, filename)) { + fprintf(stderr, "Error renaming temporary file %s to configuration file %s: %s\n", tmpfile, filename, strerror(errno)); + return 1; } -#ifdef HAVE_CURSES - if(!strcasecmp(argv[optind], "top")) { - top(fd); - return 0; + // Silently try notifying a running tincd of changes. + if(connect_tincd(false)) + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + + return 0; +} + +bool check_id(const char *name) { + if(!name || !*name) + return false; + + for(int i = 0; i < strlen(name); i++) { + if(!isalnum(name[i]) && name[i] != '_') + return false; + } + + return true; +} + +static int cmd_init(int argc, char *argv[]) { + if(!access(tinc_conf, F_OK)) { + fprintf(stderr, "Configuration file %s already exists!\n", tinc_conf); + return 1; + } + + if(argc < 2) { + if(tty) { + char buf[1024]; + fprintf(stdout, "Enter the Name you want your tinc node to have: "); + fflush(stdout); + if(!fgets(buf, sizeof buf, stdin)) { + fprintf(stderr, "Error while reading stdin: %s\n", strerror(errno)); + return 1; + } + int len = rstrip(buf); + if(!len) { + fprintf(stderr, "No name given!\n"); + return 1; + } + name = strdup(buf); + } else { + fprintf(stderr, "No Name given!\n"); + return 1; + } + } else { + name = strdup(argv[1]); + if(!*name) { + fprintf(stderr, "No Name given!\n"); + return 1; + } + } + + if(!check_id(name)) { + fprintf(stderr, "Invalid Name! Only a-z, A-Z, 0-9 and _ are allowed characters.\n"); + return 1; + } + + if(mkdir(confdir, 0755) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", CONFDIR, strerror(errno)); + return 1; + } + + if(mkdir(confbase, 0755) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", confbase, strerror(errno)); + return 1; + } + + if(mkdir(hosts_dir, 0755) && errno != EEXIST) { + fprintf(stderr, "Could not create directory %s: %s\n", hosts_dir, strerror(errno)); + return 1; + } + + FILE *f = fopen(tinc_conf, "w"); + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", tinc_conf, strerror(errno)); + return 1; + } + + fprintf(f, "Name = %s\n", name); + fclose(f); + + if(!rsa_keygen(2048, false) || !ecdsa_keygen(false)) + return 1; + +#ifndef HAVE_MINGW + char *filename; + xasprintf(&filename, "%s" SLASH "tinc-up", confbase); + if(access(filename, F_OK)) { + FILE *f = fopen(filename, "w"); + if(!f) { + fprintf(stderr, "Could not create file %s: %s\n", filename, strerror(errno)); + return 1; + } + fchmod(fileno(f), 0755); + fprintf(f, "#!/bin/sh\n\necho 'Unconfigured tinc-up script, please edit!'\n\n#ifconfig $INTERFACE netmask \n"); + fclose(f); } #endif - if(!strcasecmp(argv[optind], "pcap")) { - pcap(fd, stdout); + return 0; + +} + +static int cmd_generate_keys(int argc, char *argv[]) { + return !(rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true) && ecdsa_keygen(true)); +} + +static int cmd_generate_rsa_keys(int argc, char *argv[]) { + return !rsa_keygen(argc > 1 ? atoi(argv[1]) : 2048, true); +} + +static int cmd_generate_ecdsa_keys(int argc, char *argv[]) { + return !ecdsa_keygen(true); +} + +static int cmd_help(int argc, char *argv[]) { + usage(false); + return 0; +} + +static int cmd_version(int argc, char *argv[]) { + version(); + return 0; +} + +static int cmd_info(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + if(!connect_tincd(true)) + return 1; + + return info(fd, argv[1]); +} + +static const char *conffiles[] = { + "tinc.conf", + "tinc-up", + "tinc-down", + "subnet-up", + "subnet-down", + "host-up", + "host-down", + NULL, +}; + +static int cmd_edit(int argc, char *argv[]) { + if(argc != 2) { + fprintf(stderr, "Invalid number of arguments.\n"); + return 1; + } + + char *filename = NULL; + + if(strncmp(argv[1], "hosts" SLASH, 6)) { + for(int i = 0; conffiles[i]; i++) { + if(!strcmp(argv[1], conffiles[i])) { + xasprintf(&filename, "%s" SLASH "%s", confbase, argv[1]); + break; + } + } + } else { + argv[1] += 6; + } + + if(!filename) { + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, argv[1]); + char *dash = strchr(argv[1], '-'); + if(dash) { + *dash++ = 0; + if((strcmp(dash, "up") && strcmp(dash, "down")) || !check_id(argv[1])) { + fprintf(stderr, "Invalid configuration filename.\n"); + return 1; + } + } + } + + char *command; +#ifndef HAVE_MINGW + xasprintf(&command, "\"%s\" \"%s\"", getenv("VISUAL") ?: getenv("EDITOR") ?: "vi", filename); +#else + xasprintf(&command, "edit \"%s\"", filename); +#endif + int result = system(command); + if(result) + return result; + + // Silently try notifying a running tincd of changes. + if(connect_tincd(false)) + sendline(fd, "%d %d", CONTROL, REQ_RELOAD); + + return 0; +} + +static int export(const char *name, FILE *out) { + char *filename; + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, name); + FILE *in = fopen(filename, "r"); + if(!in) { + fprintf(stderr, "Could not open configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + fprintf(out, "Name = %s\n", name); + char buf[4096]; + while(fgets(buf, sizeof buf, in)) { + if(strcspn(buf, "\t =") != 4 || strncasecmp(buf, "Name", 4)) + fputs(buf, out); + } + + if(ferror(in)) { + fprintf(stderr, "Error while reading configuration file %s: %s\n", filename, strerror(errno)); + fclose(in); + return 1; + } + + fclose(in); + return 0; +} + +static int cmd_export(int argc, char *argv[]) { + char *name = get_my_name(); + if(!name) + return 1; + + return export(name, stdout); +} + +static int cmd_export_all(int argc, char *argv[]) { + DIR *dir = opendir(hosts_dir); + if(!dir) { + fprintf(stderr, "Could not open host configuration directory %s: %s\n", hosts_dir, strerror(errno)); + return 1; + } + + bool first = true; + int result = 0; + struct dirent *ent; + + while((ent = readdir(dir))) { + if(!check_id(ent->d_name)) + continue; + + if(first) + first = false; + else + printf("#---------------------------------------------------------------#\n"); + + result |= export(ent->d_name, stdout); + } + + closedir(dir); + return result; +} + +static int cmd_import(int argc, char *argv[]) { + FILE *in = stdin; + FILE *out = NULL; + + char buf[4096]; + char name[4096]; + char *filename; + int count = 0; + bool firstline = true; + + while(fgets(buf, sizeof buf, in)) { + if(sscanf(buf, "Name = %s", name) == 1) { + if(!check_id(name)) { + fprintf(stderr, "Invalid Name in input!\n"); + return 1; + } + + if(out) + fclose(out); + + free(filename); + xasprintf(&filename, "%s" SLASH "%s", hosts_dir, name); + + if(!force && !access(filename, F_OK)) { + fprintf(stderr, "Host configuration file %s already exists, skipping.\n", filename); + out = NULL; + continue; + } + + out = fopen(filename, "w"); + if(!out) { + fprintf(stderr, "Error creating configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + + count++; + firstline = false; + continue; + } else if(firstline) { + fprintf(stderr, "Junk at the beginning of the input, ignoring.\n"); + firstline = false; + } + + + if(!strcmp(buf, "#---------------------------------------------------------------#\n")) + continue; + + if(out) { + if(fputs(buf, out) < 0) { + fprintf(stderr, "Error writing to host configuration file %s: %s\n", filename, strerror(errno)); + return 1; + } + } + } + + if(out) + fclose(out); + + if(count) { + fprintf(stderr, "Imported %d host configuration files.\n", count); return 0; + } else { + fprintf(stderr, "No host configuration files imported.\n"); + return 1; + } +} + +static const struct { + const char *command; + int (*function)(int argc, char *argv[]); +} commands[] = { + {"start", cmd_start}, + {"stop", cmd_stop}, + {"restart", cmd_restart}, + {"reload", cmd_reload}, + {"dump", cmd_dump}, + {"purge", cmd_purge}, + {"debug", cmd_debug}, + {"retry", cmd_retry}, + {"connect", cmd_connect}, + {"disconnect", cmd_disconnect}, + {"top", cmd_top}, + {"pcap", cmd_pcap}, + {"log", cmd_log}, + {"pid", cmd_pid}, + {"config", cmd_config}, + {"init", cmd_init}, + {"generate-keys", cmd_generate_keys}, + {"generate-rsa-keys", cmd_generate_rsa_keys}, + {"generate-ecdsa-keys", cmd_generate_ecdsa_keys}, + {"help", cmd_help}, + {"version", cmd_version}, + {"info", cmd_info}, + {"edit", cmd_edit}, + {"export", cmd_export}, + {"export-all", cmd_export_all}, + {"import", cmd_import}, + {NULL, NULL}, +}; + +#ifdef HAVE_READLINE +static char *complete_command(const char *text, int state) { + static int i; + + if(!state) + i = 0; + else + i++; + + while(commands[i].command) { + if(!strncasecmp(commands[i].command, text, strlen(text))) + return xstrdup(commands[i].command); + i++; + } + + return NULL; +} + +static char *complete_dump(const char *text, int state) { + const char *matches[] = {"nodes", "edges", "subnets", "connections", "graph", NULL}; + static int i; + + if(!state) + i = 0; + else + i++; + + while(matches[i]) { + if(!strncasecmp(matches[i], text, strlen(text))) + return xstrdup(matches[i]); + i++; + } + + return NULL; +} + +static char *complete_config(const char *text, int state) { + const char *sub[] = {"get", "set", "add", "del"}; + static int i; + if(!state) { + i = 0; + if(!strchr(rl_line_buffer + 7, ' ')) + i = -4; + else { + bool found = false; + for(int i = 0; i < 4; i++) { + if(!strncasecmp(rl_line_buffer + 7, sub[i], strlen(sub[i])) && rl_line_buffer[7 + strlen(sub[i])] == ' ') { + found = true; + break; + } + } + if(!found) + return NULL; + } + } else { + i++; + } + + while(i < 0 || variables[i].name) { + if(i < 0 && !strncasecmp(sub[i + 4], text, strlen(text))) + return xstrdup(sub[i + 4]); + if(i >= 0) { + char *dot = strchr(text, '.'); + if(dot) { + if((variables[i].type & VAR_HOST) && !strncasecmp(variables[i].name, dot + 1, strlen(dot + 1))) { + char *match; + xasprintf(&match, "%.*s.%s", dot - text, text, variables[i].name); + return match; + } + } else { + if(!strncasecmp(variables[i].name, text, strlen(text))) + return xstrdup(variables[i].name); + } + } + i++; + } + + return NULL; +} + +static char *complete_info(const char *text, int state) { + static int i; + if(!state) { + i = 0; + if(!connect_tincd(false)) + return NULL; + // Check the list of nodes + sendline(fd, "%d %d", CONTROL, REQ_DUMP_NODES); + sendline(fd, "%d %d", CONTROL, REQ_DUMP_SUBNETS); + } + + while(recvline(fd, line, sizeof line)) { + char item[4096]; + int n = sscanf(line, "%d %d %s", &code, &req, item); + if(n == 2) { + i++; + if(i >= 2) + break; + else + continue; + } + + if(n != 3) { + fprintf(stderr, "Unable to parse dump from tincd, n = %d, i = %d.\n", n, i); + break; + } + + if(!strncmp(item, text, strlen(text))) + return xstrdup(strip_weight(item)); + } + + return NULL; +} + +static char *complete_nothing(const char *text, int state) { + return NULL; +} + +static char **completion (const char *text, int start, int end) { + char **matches = NULL; + + if(!start) + matches = rl_completion_matches(text, complete_command); + else if(!strncasecmp(rl_line_buffer, "dump ", 5)) + matches = rl_completion_matches(text, complete_dump); + else if(!strncasecmp(rl_line_buffer, "config ", 7)) + matches = rl_completion_matches(text, complete_config); + else if(!strncasecmp(rl_line_buffer, "info ", 5)) + matches = rl_completion_matches(text, complete_info); + + return matches; +} +#endif + +static int cmd_shell(int argc, char *argv[]) { + char *prompt; + xasprintf(&prompt, "%s> ", identname); + int result = 0; + char buf[4096]; + char *line = NULL; + int maxargs = argc + 16; + char **nargv = xmalloc(maxargs * sizeof *nargv); + + for(int i = 0; i < argc; i++) + nargv[i] = argv[i]; + +#ifdef HAVE_READLINE + rl_readline_name = "tinc"; + rl_completion_entry_function = complete_nothing; + rl_attempted_completion_function = completion; + rl_filename_completion_desired = 0; + char *copy = NULL; +#endif + + while(true) { +#ifdef HAVE_READLINE + if(tty) { + free(copy); + free(line); + rl_basic_word_break_characters = "\t\n "; + line = readline(prompt); + if(line) + copy = xstrdup(line); + } else { + line = fgets(buf, sizeof buf, stdin); + } +#else + if(tty) + fputs(prompt, stdout); + + line = fgets(buf, sizeof buf, stdin); +#endif + + if(!line) + break; + + /* Ignore comments */ + + if(*line == '#') + continue; + + /* Split */ + + int nargc = argc; + char *p = line + strspn(line, " \t\n"); + char *next = strtok(p, " \t\n"); + + while(p && *p) { + if(nargc >= maxargs) { + fprintf(stderr, "next %p '%s', p %p '%s'\n", next, next, p, p); + abort(); + maxargs *= 2; + nargv = xrealloc(nargv, maxargs * sizeof *nargv); + } + + nargv[nargc++] = p; + p = next; + next = strtok(NULL, " \t\n"); + } + + if(nargc == argc) + continue; + + if(!strcasecmp(nargv[argc], "exit") || !strcasecmp(nargv[argc], "quit")) + return result; + + bool found = false; + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(nargv[argc], commands[i].command)) { + result |= commands[i].function(nargc - argc - 1, nargv + argc + 1); + found = true; + break; + } + } + +#ifdef HAVE_READLINE + if(tty && found) + add_history(copy); +#endif + + if(!found) { + fprintf(stderr, "Unknown command `%s'.\n", nargv[argc]); + result |= 1; + } + } + + free(nargv); + + if(tty) + printf("\n"); + return result; +} + + +int main(int argc, char *argv[]) { + program_name = argv[0]; + orig_argv = argv; + orig_argc = argc; + + if(!parse_options(argc, argv)) + return 1; + + make_names(); + + if(show_version) { + version(); + return 0; + } + + if(show_help) { + usage(false); + return 0; + } + + tty = isatty(0) && isatty(1); + + if(optind >= argc) + return cmd_shell(argc, argv); + + for(int i = 0; commands[i].command; i++) { + if(!strcasecmp(argv[optind], commands[i].command)) + return commands[i].function(argc - optind, argv + optind); } fprintf(stderr, "Unknown command `%s'.\n", argv[optind]); usage(true); - - close(fd); - - return 0; + return 1; } diff --git a/src/tincd.c b/src/tincd.c index 8401b20..9f94d89 100644 --- a/src/tincd.c +++ b/src/tincd.c @@ -1,7 +1,7 @@ /* tincd.c -- the main file for tincd Copyright (C) 1998-2005 Ivo Timmermans - 2000-2011 Guus Sliepen + 2000-2012 Guus Sliepen 2008 Max Rijevski 2009 Michael Tokarev 2010 Julien Muchembled @@ -87,10 +87,10 @@ static const char *switchuser = NULL; /* If nonzero, write log entries to a separate file. */ bool use_logfile = false; -char *identname = NULL; /* program name for syslog */ -char *logfilename = NULL; /* log file location */ +char *identname = NULL; /* program name for syslog */ +char *logfilename = NULL; /* log file location */ char *pidfilename = NULL; -char **g_argv; /* a copy of the cmdline arguments */ +char **g_argv; /* a copy of the cmdline arguments */ static int status = 1; @@ -107,6 +107,7 @@ static struct option const long_options[] = { {"user", required_argument, NULL, 'U'}, {"logfile", optional_argument, NULL, 4}, {"pidfile", required_argument, NULL, 5}, + {"option", required_argument, NULL, 'o'}, {NULL, 0, NULL, 0} }; @@ -122,18 +123,18 @@ static void usage(bool status) { program_name); else { printf("Usage: %s [option]...\n\n", program_name); - printf( " -c, --config=DIR Read configuration options from DIR.\n" - " -D, --no-detach Don't fork and detach.\n" - " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n" - " -n, --net=NETNAME Connect to net NETNAME.\n" - " -L, --mlock Lock tinc into main memory.\n" - " --logfile[=FILENAME] Write log entries to a logfile.\n" - " --pidfile=FILENAME Write PID and control socket cookie to FILENAME.\n" - " --bypass-security Disables meta protocol security, for debugging.\n" - " -o [HOST.]KEY=VALUE Set global/host configuration value.\n" - " -R, --chroot chroot to NET dir at startup.\n" - " -U, --user=USER setuid to given USER at startup.\n" " --help Display this help and exit.\n" - " --version Output version information and exit.\n\n"); + printf( " -c, --config=DIR Read configuration options from DIR.\n" + " -D, --no-detach Don't fork and detach.\n" + " -d, --debug[=LEVEL] Increase debug level or set it to LEVEL.\n" + " -n, --net=NETNAME Connect to net NETNAME.\n" + " -L, --mlock Lock tinc into main memory.\n" + " --logfile[=FILENAME] Write log entries to a logfile.\n" + " --pidfile=FILENAME Write PID and control socket cookie to FILENAME.\n" + " --bypass-security Disables meta protocol security, for debugging.\n" + " -o, --option[HOST.]KEY=VALUE Set global/host configuration value.\n" + " -R, --chroot chroot to NET dir at startup.\n" + " -U, --user=USER setuid to given USER at startup.\n" " --help Display this help and exit.\n" + " --version Output version information and exit.\n\n"); printf("Report bugs to tinc@tinc-vpn.org.\n"); } } @@ -148,77 +149,75 @@ static bool parse_options(int argc, char **argv) { while((r = getopt_long(argc, argv, "c:DLd::n:o:RU:", long_options, &option_index)) != EOF) { switch (r) { - case 0: /* long option */ + case 0: /* long option */ break; - case 'c': /* config file */ + case 'c': /* config file */ confbase = xstrdup(optarg); break; - case 'D': /* no detach */ + case 'D': /* no detach */ do_detach = false; break; - case 'L': /* no detach */ + case 'L': /* no detach */ #ifndef HAVE_MLOCKALL - logger(LOG_ERR, "%s not supported on this platform", "mlockall()"); + logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "mlockall()"); return false; #else do_mlock = true; break; #endif - case 'd': /* inc debug level */ + case 'd': /* inc debug level */ if(optarg) debug_level = atoi(optarg); else debug_level++; break; - case 'n': /* net name given */ - /* netname "." is special: a "top-level name" */ - netname = strcmp(optarg, ".") != 0 ? - xstrdup(optarg) : NULL; + case 'n': /* net name given */ + netname = xstrdup(optarg); break; - case 'o': /* option */ + case 'o': /* option */ cfg = parse_config_line(optarg, NULL, ++lineno); if (!cfg) return false; list_insert_tail(cmdline_conf, cfg); break; - case 'R': /* chroot to NETNAME dir */ + case 'R': /* chroot to NETNAME dir */ do_chroot = true; break; - case 'U': /* setuid to USER */ + case 'U': /* setuid to USER */ switchuser = optarg; break; - case 1: /* show help */ + case 1: /* show help */ show_help = true; break; - case 2: /* show version */ + case 2: /* show version */ show_version = true; break; - case 3: /* bypass security */ + case 3: /* bypass security */ bypass_security = true; break; - case 4: /* write log entries to a file */ + case 4: /* write log entries to a file */ use_logfile = true; if(optarg) logfilename = xstrdup(optarg); break; - case 5: /* open control socket here */ + case 5: /* open control socket here */ pidfilename = xstrdup(optarg); break; - case '?': + case '?': /* wrong options */ usage(true); return false; @@ -227,6 +226,21 @@ static bool parse_options(int argc, char **argv) { } } + if(!netname && (netname = getenv("NETNAME"))) + netname = xstrdup(netname); + + /* netname "." is special: a "top-level name" */ + + if(netname && (!*netname || !strcmp(netname, "."))) { + free(netname); + netname = NULL; + } + + if(netname && (strpbrk(netname, "\\/") || *netname == '.')) { + fprintf(stderr, "Invalid character in netname!\n"); + return false; + } + return true; } @@ -249,15 +263,15 @@ static void make_names(void) { if(!RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\tinc", 0, KEY_READ, &key)) { if(!RegQueryValueEx(key, NULL, 0, 0, installdir, &len)) { if(!logfilename) - xasprintf(&logfilename, "%s/log/%s.log", identname); + xasprintf(&logfilename, "%s" SLASH "log" SLASH "%s.log", identname); if(!confbase) { if(netname) - xasprintf(&confbase, "%s/%s", installdir, netname); + xasprintf(&confbase, "%s" SLASH "%s", installdir, netname); else xasprintf(&confbase, "%s", installdir); } if(!pidfilename) - xasprintf(&pidfilename, "%s/pid", confbase); + xasprintf(&pidfilename, "%s" SLASH "pid", confbase); } RegCloseKey(key); if(*installdir) @@ -266,19 +280,19 @@ static void make_names(void) { #endif if(!logfilename) - xasprintf(&logfilename, LOCALSTATEDIR "/log/%s.log", identname); + xasprintf(&logfilename, LOCALSTATEDIR SLASH "log" SLASH "%s.log", identname); if(!pidfilename) - xasprintf(&pidfilename, LOCALSTATEDIR "/run/%s.pid", identname); + xasprintf(&pidfilename, LOCALSTATEDIR SLASH "run" SLASH "%s.pid", identname); if(netname) { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc/%s", netname); + xasprintf(&confbase, CONFDIR SLASH "tinc" SLASH "%s", netname); else - logger(LOG_INFO, "Both netname and configuration directory given, using the latter..."); + logger(DEBUG_ALWAYS, LOG_INFO, "Both netname and configuration directory given, using the latter..."); } else { if(!confbase) - xasprintf(&confbase, CONFDIR "/tinc"); + xasprintf(&confbase, CONFDIR SLASH "tinc"); } } @@ -293,11 +307,11 @@ static void free_names(void) { static bool drop_privs(void) { #ifdef HAVE_MINGW if (switchuser) { - logger(LOG_ERR, "%s not supported on this platform", "-U"); + logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "-U"); return false; } if (do_chroot) { - logger(LOG_ERR, "%s not supported on this platform", "-R"); + logger(DEBUG_ALWAYS, LOG_ERR, "%s not supported on this platform", "-R"); return false; } #else @@ -305,23 +319,26 @@ static bool drop_privs(void) { if (switchuser) { struct passwd *pw = getpwnam(switchuser); if (!pw) { - logger(LOG_ERR, "unknown user `%s'", switchuser); + logger(DEBUG_ALWAYS, LOG_ERR, "unknown user `%s'", switchuser); return false; } uid = pw->pw_uid; if (initgroups(switchuser, pw->pw_gid) != 0 || setgid(pw->pw_gid) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "initgroups", strerror(errno)); return false; } +#ifndef __ANDROID__ +// Not supported in android NDK endgrent(); endpwent(); +#endif } if (do_chroot) { - tzset(); /* for proper timestamps in logs */ + tzset(); /* for proper timestamps in logs */ if (chroot(confbase) != 0 || chdir("/") != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "chroot", strerror(errno)); return false; } @@ -330,7 +347,7 @@ static bool drop_privs(void) { } if (switchuser) if (setuid(uid) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setuid", strerror(errno)); return false; } @@ -352,13 +369,13 @@ int main(int argc, char **argv) { if(!parse_options(argc, argv)) return 1; - + make_names(); if(show_version) { printf("%s version %s (built %s %s, protocol %d.%d)\n", PACKAGE, VERSION, __DATE__, __TIME__, PROT_MAJOR, PROT_MINOR); - printf("Copyright (C) 1998-2011 Ivo Timmermans, Guus Sliepen and others.\n" + printf("Copyright (C) 1998-2012 Ivo Timmermans, Guus Sliepen and others.\n" "See the AUTHORS file for a complete list.\n\n" "tinc comes with ABSOLUTELY NO WARRANTY. This is free software,\n" "and you are welcome to redistribute it under certain conditions;\n" @@ -374,20 +391,21 @@ int main(int argc, char **argv) { #ifdef HAVE_MINGW if(WSAStartup(MAKEWORD(2, 2), &wsa_state)) { - logger(LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "WSAStartup", winerror(GetLastError())); return 1; } #endif openlogger("tinc", use_logfile?LOGMODE_FILE:LOGMODE_STDERR); - if(!event_init()) { - logger(LOG_ERR, "Error initializing libevent!"); - return 1; - } - g_argv = argv; + if(getenv("LISTEN_PID") && atoi(getenv("LISTEN_PID")) == getpid()) + do_detach = false; +#ifdef HAVE_UNSETENV + unsetenv("LISTEN_PID"); +#endif + init_configuration(&config_tree); /* Slllluuuuuuurrrrp! */ @@ -400,7 +418,7 @@ int main(int argc, char **argv) { #ifdef HAVE_LZO if(lzo_init() != LZO_E_OK) { - logger(LOG_ERR, "Error initializing LZO compressor!"); + logger(DEBUG_ALWAYS, LOG_ERR, "Error initializing LZO compressor!"); return 1; } #endif @@ -416,6 +434,7 @@ int main2(int argc, char **argv) { InitializeCriticalSection(&mutex); EnterCriticalSection(&mutex); #endif + char *priority = NULL; if(!detach()) return 1; @@ -425,12 +444,17 @@ int main2(int argc, char **argv) { * This has to be done after daemon()/fork() so it works for child. * No need to do that in parent as it's very short-lived. */ if(do_mlock && mlockall(MCL_CURRENT | MCL_FUTURE) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "mlockall", + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "mlockall", strerror(errno)); return 1; } #endif + if(!event_init()) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error initializing libevent!"); + return 1; + } + /* Setup sockets and open device. */ if(!setup_network()) @@ -445,32 +469,27 @@ int main2(int argc, char **argv) { /* Change process priority */ - char *priority = NULL; - - if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) { - if(!strcasecmp(priority, "Normal")) { - if (setpriority(NORMAL_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); - goto end; - } - } else if(!strcasecmp(priority, "Low")) { - if (setpriority(BELOW_NORMAL_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); - goto end; - } - } else if(!strcasecmp(priority, "High")) { - if (setpriority(HIGH_PRIORITY_CLASS) != 0) { - logger(LOG_ERR, "System call `%s' failed: %s", - "setpriority", strerror(errno)); - goto end; - } - } else { - logger(LOG_ERR, "Invalid priority `%s`!", priority); - goto end; - } - } + if(get_config_string(lookup_config(config_tree, "ProcessPriority"), &priority)) { + if(!strcasecmp(priority, "Normal")) { + if (setpriority(NORMAL_PRIORITY_CLASS) != 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); + goto end; + } + } else if(!strcasecmp(priority, "Low")) { + if (setpriority(BELOW_NORMAL_PRIORITY_CLASS) != 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); + goto end; + } + } else if(!strcasecmp(priority, "High")) { + if (setpriority(HIGH_PRIORITY_CLASS) != 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "setpriority", strerror(errno)); + goto end; + } + } else { + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid priority `%s`!", priority); + goto end; + } + } /* drop privileges */ if (!drop_privs()) @@ -482,8 +501,8 @@ int main2(int argc, char **argv) { /* Shutdown properly. */ - ifdebug(CONNECTIONS) - dump_device_stats(); + if(debug_level >= DEBUG_CONNECTIONS) + devops.dump_stats(); close_network_connections(); @@ -491,11 +510,14 @@ end: exit_control(); end_nonet: - logger(LOG_NOTICE, "Terminating"); + logger(DEBUG_ALWAYS, LOG_NOTICE, "Terminating"); + + free(priority); crypto_exit(); exit_configuration(&config_tree); + free(cmdline_conf); free_names(); return status; diff --git a/src/top.c b/src/top.c index f14395e..8232c7a 100644 --- a/src/top.c +++ b/src/top.c @@ -1,6 +1,6 @@ /* top.c -- Show real-time statistics from a running tincd - Copyright (C) 2011 Guus Sliepen + Copyright (C) 2011-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -59,7 +59,6 @@ static bool cumulative = false; static list_t node_list; static struct timeval now, prev, diff; static int delay = 1000; -static bool running = true; static bool changed = true; static const char *unit = "bytes"; static float scale = 1; @@ -85,10 +84,8 @@ static void update(int fd) { uint64_t out_packets; uint64_t out_bytes; - for(list_node_t *i = node_list.head; i; i = i->next) { - nodestats_t *node = i->data; - node->known = false; - } + for list_each(nodestats_t, ns, &node_list) + ns->known = false; while(recvline(fd, line, sizeof line)) { int n = sscanf(line, "%d %d %s %"PRIu64" %"PRIu64" %"PRIu64" %"PRIu64, &code, &req, name, &in_packets, &in_bytes, &out_packets, &out_bytes); @@ -104,18 +101,17 @@ static void update(int fd) { nodestats_t *found = NULL; - for(list_node_t *i = node_list.head; i; i = i->next) { - nodestats_t *node = i->data; - int result = strcmp(name, node->name); + for list_each(nodestats_t, ns, &node_list) { + int result = strcmp(name, ns->name); if(result > 0) { continue; } if(result == 0) { - found = node; + found = ns; break; } else { found = xmalloc_and_zero(sizeof *found); found->name = xstrdup(name); - list_insert_before(&node_list, i, found); + list_insert_before(&node_list, node, found); changed = true; break; } @@ -153,14 +149,14 @@ static void redraw(void) { if(changed) { n = 0; sorted = xrealloc(sorted, node_list.count * sizeof *sorted); - for(list_node_t *i = node_list.head; i; i = i->next) - sorted[n++] = i->data; + for list_each(nodestats_t, ns, &node_list) + sorted[n++] = ns; changed = false; } for(int i = 0; i < n; i++) sorted[i]->i = i; - + int cmpfloat(float a, float b) { if(a < b) return -1; @@ -247,6 +243,7 @@ static void redraw(void) { void top(int fd) { initscr(); timeout(delay); + bool running = true; while(running) { update(fd); diff --git a/src/uml_socket/device.c b/src/uml_device.c similarity index 62% rename from src/uml_socket/device.c rename to src/uml_device.c index d8f13a5..afdded5 100644 --- a/src/uml_socket/device.c +++ b/src/uml_device.c @@ -1,7 +1,7 @@ /* device.c -- UML network socket Copyright (C) 2002-2005 Ivo Timmermans, - 2002-2009 Guus Sliepen + 2002-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -28,19 +28,17 @@ #include "logger.h" #include "utils.h" #include "route.h" +#include "xalloc.h" -int device_fd = -1; static int listen_fd = -1; static int request_fd = -1; static int data_fd = -1; static int write_fd = -1; static int state = 0; -char *device = NULL; -char *iface = NULL; static char *device_info; extern char *identname; -extern bool running; +extern volatile bool running; static uint64_t device_total_in = 0; static uint64_t device_total_out = 0; @@ -56,7 +54,7 @@ static struct request { static struct sockaddr_un data_sun; -bool setup_device(void) { +static bool setup_device(void) { struct sockaddr_un listen_sun; static const int one = 1; struct { @@ -74,29 +72,37 @@ bool setup_device(void) { device_info = "UML network socket"; if((write_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { - logger(LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open write %s: %s", device_info, strerror(errno)); running = false; return false; } +#ifdef FD_CLOEXEC + fcntl(write_fd, F_SETFD, FD_CLOEXEC); +#endif + setsockopt(write_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); if(fcntl(write_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); running = false; return false; } if((data_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { - logger(LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open data %s: %s", device_info, strerror(errno)); running = false; return false; } +#ifdef FD_CLOEXEC + fcntl(data_fd, F_SETFD, FD_CLOEXEC); +#endif + setsockopt(data_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); if(fcntl(data_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); running = false; return false; } @@ -107,42 +113,46 @@ bool setup_device(void) { name.usecs = tv.tv_usec; data_sun.sun_family = AF_UNIX; memcpy(&data_sun.sun_path, &name, sizeof name); - + if(bind(data_fd, (struct sockaddr *)&data_sun, sizeof data_sun) < 0) { - logger(LOG_ERR, "Could not bind data %s: %s", device_info, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind data %s: %s", device_info, strerror(errno)); running = false; return false; } if((listen_fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { - logger(LOG_ERR, "Could not open %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open %s: %s", device_info, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); return false; } listen_sun.sun_family = AF_UNIX; strncpy(listen_sun.sun_path, device, sizeof listen_sun.sun_path); if(bind(listen_fd, (struct sockaddr *)&listen_sun, sizeof listen_sun) < 0) { - logger(LOG_ERR, "Could not bind %s to %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind %s to %s: %s", device_info, device, strerror(errno)); return false; } if(listen(listen_fd, 1) < 0) { - logger(LOG_ERR, "Could not listen on %s %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not listen on %s %s: %s", device_info, device, strerror(errno)); return false; } device_fd = listen_fd; state = 0; - logger(LOG_INFO, "%s is a %s", device, device_info); + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); if(routing_mode == RMODE_ROUTER) overwrite_mac = true; @@ -169,22 +179,26 @@ void close_device(void) { if(iface) free(iface); } -bool read_packet(vpn_packet_t *packet) { +static bool read_packet(vpn_packet_t *packet) { int inlen; switch(state) { case 0: { struct sockaddr sa; - int salen = sizeof sa; + socklen_t salen = sizeof sa; request_fd = accept(listen_fd, &sa, &salen); if(request_fd < 0) { - logger(LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not accept connection to %s %s: %s", device_info, device, strerror(errno)); return false; } +#ifdef FD_CLOEXEC + fcntl(request_fd, F_SETFD, FD_CLOEXEC); +#endif + if(fcntl(listen_fd, F_SETFL, O_NONBLOCK) < 0) { - logger(LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "System call `%s' failed: %s", "fcntl", strerror(errno)); running = false; return false; } @@ -199,21 +213,21 @@ bool read_packet(vpn_packet_t *packet) { case 1: { if((inlen = read(request_fd, &request, sizeof request)) != sizeof request) { - logger(LOG_ERR, "Error while reading request from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading request from %s %s: %s", device_info, device, strerror(errno)); running = false; return false; } if(request.magic != 0xfeedface || request.version != 3 || request.type != REQ_NEW_CONTROL) { - logger(LOG_ERR, "Unknown magic %x, version %d, request type %d from %s %s", + logger(DEBUG_ALWAYS, LOG_ERR, "Unknown magic %x, version %d, request type %d from %s %s", request.magic, request.version, request.type, device_info, device); running = false; return false; } if(connect(write_fd, &request.sock, sizeof request.sock) < 0) { - logger(LOG_ERR, "Could not bind write %s: %s", device_info, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Could not bind write %s: %s", device_info, strerror(errno)); running = false; return false; } @@ -221,7 +235,7 @@ bool read_packet(vpn_packet_t *packet) { write(request_fd, &data_sun, sizeof data_sun); device_fd = data_fd; - logger(LOG_INFO, "Connection with UML established"); + logger(DEBUG_ALWAYS, LOG_INFO, "Connection with UML established"); state = 2; return false; @@ -229,7 +243,7 @@ bool read_packet(vpn_packet_t *packet) { case 2: { if((inlen = read(data_fd, packet->data, MTU)) <= 0) { - logger(LOG_ERR, "Error while reading from %s %s: %s", device_info, + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); running = false; return false; @@ -239,27 +253,31 @@ bool read_packet(vpn_packet_t *packet) { device_total_in += packet->len; - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); return true; } + + default: + logger(DEBUG_ALWAYS, LOG_ERR, "Invalid value for state variable in " __FILE__); + abort(); } } -bool write_packet(vpn_packet_t *packet) { +static bool write_packet(vpn_packet_t *packet) { if(state != 2) { - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Dropping packet of %d bytes to %s: not connected to UML yet", packet->len, device_info); return false; } - ifdebug(TRAFFIC) logger(LOG_DEBUG, "Writing packet of %d bytes to %s", + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Writing packet of %d bytes to %s", packet->len, device_info); if(write(write_fd, packet->data, packet->len) < 0) { if(errno != EINTR && errno != EAGAIN) { - logger(LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); running = false; } @@ -271,8 +289,16 @@ bool write_packet(vpn_packet_t *packet) { return true; } -void dump_device_stats(void) { - logger(LOG_DEBUG, "Statistics for %s %s:", device_info, device); - logger(LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); - logger(LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); } + +const devops_t uml_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/src/utils.c b/src/utils.c index 022ac25..aefec8c 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,7 +1,7 @@ /* utils.c -- gathering of some stupid small functions Copyright (C) 1999-2005 Ivo Timmermans - 2000-2009 Guus Sliepen + 2000-2012 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,7 +38,7 @@ static int charb64decode(char c) { return c - 'a' + 26; else if(c >= 'A') return c - 'A'; - else if(c >= '0') + else if(c >= '0') return c - '0' + 52; else if(c == '+') return 62; @@ -48,14 +48,13 @@ static int charb64decode(char c) { int hex2bin(const char *src, char *dst, int length) { int i; - for(i = 0; i < length && src[i * 2] && src[i * 2 + 1]; i++) + for(i = 0; i < length && isxdigit(src[i * 2]) && isxdigit(src[i * 2 + 1]); i++) dst[i] = charhex2bin(src[i * 2]) * 16 + charhex2bin(src[i * 2 + 1]); return i; } int bin2hex(const char *src, char *dst, int length) { - int i; - for(i = length - 1; i >= 0; i--) { + for(int i = length - 1; i >= 0; i--) { dst[i * 2 + 1] = hexadecimals[(unsigned char) src[i] & 15]; dst[i * 2] = hexadecimals[(unsigned char) src[i] >> 4]; } @@ -66,7 +65,7 @@ int bin2hex(const char *src, char *dst, int length) { int b64decode(const char *src, char *dst, int length) { int i; uint32_t triplet = 0; - unsigned char *udst = dst; + unsigned char *udst = (unsigned char *)dst; for(i = 0; i < length / 3 * 4 && src[i]; i++) { triplet |= charb64decode(src[i]) << (6 * (i & 3)); @@ -92,12 +91,12 @@ int b64decode(const char *src, char *dst, int length) { int b64encode(const char *src, char *dst, int length) { uint32_t triplet; - const unsigned char *usrc = src; + const unsigned char *usrc = (unsigned char *)src; int si = length / 3 * 3; int di = length / 3 * 4; switch(length % 3) { - case 2: + case 2: triplet = usrc[si] | usrc[si + 1] << 8; dst[di] = base64imals[triplet & 63]; triplet >>= 6; dst[di + 1] = base64imals[triplet & 63]; triplet >>= 6; @@ -137,15 +136,17 @@ int b64encode(const char *src, char *dst, int length) { #endif const char *winerror(int err) { - static char buf[1024], *newline; + static char buf[1024], *ptr; + + ptr = buf + sprintf(buf, "(%d) ", err); if (!FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), buf, sizeof(buf), NULL)) { + NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), ptr, sizeof(buf) - (ptr - buf), NULL)) { strncpy(buf, "(unable to format errormessage)", sizeof(buf)); }; - if((newline = strchr(buf, '\r'))) - *newline = '\0'; + if((ptr = strchr(buf, '\r'))) + *ptr = '\0'; return buf; } diff --git a/src/utils.h b/src/utils.h index 67c94f3..04e478a 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,7 +1,7 @@ /* utils.h -- header file for utils.c Copyright (C) 1999-2005 Ivo Timmermans - 2000-2009 Guus Sliepen + 2000-2011 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,4 +47,4 @@ extern const char *winerror(int); extern unsigned int bitfield_to_int(const void *bitfield, size_t size); -#endif /* __TINC_UTILS_H__ */ +#endif /* __TINC_UTILS_H__ */ diff --git a/src/vde_device.c b/src/vde_device.c new file mode 100644 index 0000000..815b956 --- /dev/null +++ b/src/vde_device.c @@ -0,0 +1,143 @@ +/* + device.c -- VDE plug + Copyright (C) 2012 Guus Sliepen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +*/ + +#include "system.h" + +#include + +#include "conf.h" +#include "device.h" +#include "net.h" +#include "logger.h" +#include "utils.h" +#include "route.h" +#include "xalloc.h" + +static struct vdepluglib plug; +static struct vdeconn *conn = NULL; +static int port = 0; +static char *group = NULL; +static char *device_info; + +extern char *identname; +extern volatile bool running; + +static uint64_t device_total_in = 0; +static uint64_t device_total_out = 0; + +static bool setup_device(void) { + libvdeplug_dynopen(plug); + + if(!plug.dl_handle) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open libvdeplug library!"); + return false; + } + + if(!get_config_string(lookup_config(config_tree, "Device"), &device)) + xasprintf(&device, LOCALSTATEDIR "/run/vde.ctl"); + + get_config_string(lookup_config(config_tree, "Interface"), &iface); + + get_config_int(lookup_config(config_tree, "VDEPort"), &port); + + get_config_string(lookup_config(config_tree, "VDEGroup"), &group); + + device_info = "VDE socket"; + + struct vde_open_args args = { + .port = port, + .group = group, + .mode = 0700, + }; + + conn = plug.vde_open(device, identname, &args); + if(!conn) { + logger(DEBUG_ALWAYS, LOG_ERR, "Could not open VDE socket %s", device); + return false; + } + + device_fd = plug.vde_datafd(conn); + +#ifdef FD_CLOEXEC + fcntl(device_fd, F_SETFD, FD_CLOEXEC); +#endif + + logger(DEBUG_ALWAYS, LOG_INFO, "%s is a %s", device, device_info); + + if(routing_mode == RMODE_ROUTER) + overwrite_mac = true; + + return true; +} + +static void close_device(void) { + if(conn) + plug.vde_close(conn); + + if(plug.dl_handle) + libvdeplug_dynclose(plug); + + free(device); + + free(iface); +} + +static bool read_packet(vpn_packet_t *packet) { + int lenin = (ssize_t)plug.vde_recv(conn, packet->data, MTU, 0); + if(lenin <= 0) { + logger(DEBUG_ALWAYS, LOG_ERR, "Error while reading from %s %s: %s", device_info, device, strerror(errno)); + running = false; + return false; + } + + packet->len = lenin; + device_total_in += packet->len; + logger(DEBUG_TRAFFIC, LOG_DEBUG, "Read packet of %d bytes from %s", packet->len, device_info); + + return true; +} + +static bool write_packet(vpn_packet_t *packet) { + if((ssize_t)plug.vde_send(conn, packet->data, packet->len, 0) < 0) { + if(errno != EINTR && errno != EAGAIN) { + logger(DEBUG_ALWAYS, LOG_ERR, "Can't write to %s %s: %s", device_info, device, strerror(errno)); + running = false; + } + + return false; + } + + device_total_out += packet->len; + + return true; +} + +static void dump_device_stats(void) { + logger(DEBUG_ALWAYS, LOG_DEBUG, "Statistics for %s %s:", device_info, device); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes in: %10"PRIu64, device_total_in); + logger(DEBUG_ALWAYS, LOG_DEBUG, " total bytes out: %10"PRIu64, device_total_out); +} + +const devops_t vde_devops = { + .setup = setup_device, + .close = close_device, + .read = read_packet, + .write = write_packet, + .dump_stats = dump_device_stats, +}; diff --git a/system.h b/system.h index 5dc1daf..c688622 100644 --- a/system.h +++ b/system.h @@ -1,7 +1,7 @@ /* system.h -- system headers Copyright (C) 1998-2005 Ivo Timmermans - 2003-2006 Guus Sliepen + 2003-2009 Guus Sliepen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by