diff --git a/BUILDING b/BUILDING new file mode 100644 index 0000000..446b450 --- /dev/null +++ b/BUILDING @@ -0,0 +1,36 @@ +To build and install spiped, run: +# make BINDIR=/path/to/target/directory install + +To install man pages, add MAN1DIR=/path/to/man.1/directory to the command +line (e.g., MAN1DIR=/usr/local/man/man1 on FreeBSD). + +Spiped should build and run on any IEEE Std 1003.1 (POSIX) compliant +system which +1. Includes the Software Development Utilities option, +2. Has OpenSSL available via -lcrypto and #include , and +3. Provides /dev/urandom. + +On some platforms (Solaris, maybe others), additional compiler and/or linker +options are required to find OpenSSL or system libraries; these can be +provided by adding e.g., CFLAGS="-I/path/to/openssl/headers" (compiler option) +or LDADD_EXTRA="-L/usr/sfw/lib -lsocket -lnsl" (linker option) to the make +command line. + +On some platforms (OpenBSD prior to 5.4, and possibly others) you will need to +add #include at the start of + lib/dnsthread/dnsthread.c + lib/util/sock_util.c + proto/proto_conn.c + spipe/main.c + spipe/pushbits.c +due to a POSIX-compliance bug on those platforms. + +On some platforms (mostly Linuxes) it is possible to install OpenSSL libaries +wihout the associated header files; the header files are usually in packages +named "openssl-devel", "libssl-dev", or similar. + +If your OS provides random bytes via some mechanism other than /dev/urandom, +please make local changes to lib/util/entropy.c and notify the author. + +If spiped fails to build or run for other reasons, please notify the +author. diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..1038497 --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,34 @@ +spiped-1.3.1 +* Fix build by adding missing #include. +* Minor code cleanups. +spiped-1.3.0 +* Bug fix: spiped now correctly closes connections which have been reset; + in earlier versions spiped could erronously hold "dead" connections open + as long as they remained idle. +* Man pages added. +* Protocol-layer keep-alives are now enabled by default. +* New option -j (spipe/spiped): Disable protocol-layer keep-alives. +* In spiped the target address is now re-resolved every 60 seconds by default. +* New option -R (spiped): Do not re-resolve target address. +* New option -r (spiped): Re-resolve target address every + seconds. +spiped-1.2.2 +* Build fixes for some strictly POSIX-conforming platforms. +* Detect and work around compilers which are POSIX-noncompliant in their + handling of -rt and -lxnet options. +* Minor documentation and typo fixes. +spiped-1.2.1 +* Fix build by adding missing #include. +spiped-1.2.0 +* New utility "spipe": A client for the spiped protocol, handling a single + connection with standard input/output as one end. +* Code rearrangement with no functional consequences. +* Minor bug and documentation fixes. +spiped-1.1.0 +* New option -D: Wait until DNS lookups succeed. +* New option -F: Don't daemonize. +* Use SO_REUSEADDR to avoid 'socket address already in use' error (most + importantly, if spiped is killed and restarted). +* Minor bug and style fixes. +spiped-1.0.0 +* Initial release diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..d96ceb3 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,32 @@ +The included code and documentation ("spiped") is distributed under the +following terms: + +Copyright 2005-2013 Colin Percival. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +In addition to the above, some files are: + +Copyright 2012 Andreas Olsson + +and distributed under the same terms. Such files contain individual +copyright statements and licenses. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..eb5c6ec --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +.POSIX: + +PROGS= spiped spipe +BINDIR_DEFAULT= /usr/local/bin +CFLAGS_DEFAULT= -O2 + +all: + if [ -z "${CFLAGS}" ]; then \ + CFLAGS=${CFLAGS_DEFAULT}; \ + else \ + CFLAGS="${CFLAGS}"; \ + fi; \ + LDADD_POSIX=`export CC=${CC}; cd POSIX && command -p sh posix-l.sh`; \ + for D in ${PROGS}; do \ + ( cd $${D} && \ + make CFLAGS="$${CFLAGS}" \ + LDADD_POSIX="$${LDADD_POSIX}" all ) || \ + exit 2; \ + done + +install: all + if [ -z "${BINDIR}" ]; then \ + BINDIR=${BINDIR_DEFAULT}; \ + else \ + BINDIR="${BINDIR}"; \ + fi; \ + for D in ${PROGS}; do \ + ( cd $${D} && make BINDIR="$${BINDIR}" install ) || exit 2; \ + done + +clean: + for D in ${PROGS}; do \ + ( cd $${D} && make clean ) || exit 2; \ + done diff --git a/POSIX/README b/POSIX/README new file mode 100644 index 0000000..9032f05 --- /dev/null +++ b/POSIX/README @@ -0,0 +1,10 @@ +POSIX compatibility code +------------------------ + +This code exists to work around some common POSIX compatibility issues. + +POSIX specifies that if the first line of a Makefile is ".POSIX:" then the +Makefile should be processed according to POSIX rules, including with CC=c99. +Further, c99 is required to understand the -lrt and -lxnet options (and ignore +them if the routines they specify linkage for are already in the standard C +library). Unfortunately some systems fail or one or both of these accounts. diff --git a/POSIX/posix-l.c b/POSIX/posix-l.c new file mode 100644 index 0000000..1923668 --- /dev/null +++ b/POSIX/posix-l.c @@ -0,0 +1 @@ +int main() {return 0;} diff --git a/POSIX/posix-l.sh b/POSIX/posix-l.sh new file mode 100755 index 0000000..b3f0e31 --- /dev/null +++ b/POSIX/posix-l.sh @@ -0,0 +1,14 @@ +# Should be sourced by `command -p sh posix-l.sh` from within a Makefile. +FIRST=YES +for LIB in rt xnet; do + if ${CC} -l${LIB} posix-l.c 2>/dev/null; then + if [ ${FIRST} = "NO" ]; then + printf " "; + fi + printf "%s" "-l${LIB}"; + FIRST=NO; + else + echo "WARNING: POSIX violation: make's CC doesn't understand -l${LIB}" >/dev/stderr + fi + rm -f a.out +done diff --git a/README b/README new file mode 100644 index 0000000..dd9bded --- /dev/null +++ b/README @@ -0,0 +1,198 @@ +spiped design +============= + +Introduction +------------ + +spiped (pronounced "ess-pipe-dee") is a utility for creating symmetrically +encrypted and authenticated pipes between socket addresses, so that one may +connect to one address (e.g., a UNIX socket on localhost) and transparently +have a connection established to another address (e.g., a UNIX socket on a +different system). This is similar to 'ssh -L' functionality, but does not +use SSH and requires a pre-shared symmetric key. + +spipe (pronounced "ess-pipe") is a utility which acts as an spiped protocol +client (i.e., connects to an spiped daemon), taking input from the standard +input and writing data read back to the standard output. + +Note that spiped: +1. Requires a strong key file: The file specified via the -k option should +have at least 256 bits of entropy. ('dd if=/dev/urandom bs=32 count=1' is +your friend.) +2. Requires strong entropy from /dev/urandom. (Make sure your kernel's +random number generator is seeded at boot time!) +3. Does not provide any protection against information leakage via packet +timing: Running telnet over spiped will protect a password from being directly +read from the network, but will not obscure the typing rhythm. +4. Can significantly increase bandwidth usage for interactive sessions: It +sends data in packets of 1024 bytes, and pads smaller messages up to this +length, so a 1 byte write could be expanded to 1024 bytes if it cannot be +coalesced with adjacent bytes. +5. Uses a symmetric key -- so anyone who can connect to an spiped "server" is +also able to impersonate it. + +Example usage +------------- + +To set up an encrypted and authenticated pipe for sending email between two +systems (in the author's case, from many systems around the internet to his +central SMTP server, which then relays email to the rest of the world), one +might run + +# dd if=/dev/urandom bs=32 count=1 of=keyfile +# spiped -d -s '[0.0.0.0]:8025' -t '[127.0.0.1]:25' -k keyfile + +on a server and after copying keyfile to the local system, run + +# spiped -e -s '[127.0.0.1]:25' -t $SERVERNAME:8025 -k keyfile + +at which point mail delivered via localhost:25 on the local system will be +securely transmitted to port 25 on the server. + +You can also use spiped to protect SSH servers from attackers: Since data is +authenticated before being forwarded to the target, this can allow you to SSH +to a host while protecting you in the event that someone finds an exploitable +bug in the SSH daemon -- this serves the same purpose as port knocking or a +firewall which restricts source IP addresses which can connect to SSH. On the +SSH server, run + +# dd if=/dev/urandom bs=32 count=1 of=/etc/ssh/spiped.key +# spiped -d -s '[0.0.0.0]:8022' -t '[127.0.0.1]:22' -k /etc/ssh/spiped.key + +then copy the server's /etc/ssh/spiped.key to ~/.ssh/spiped_HOSTNAME_key on +your local system and add the lines + + Host HOSTNAME + ProxyCommand spipe -t %h:8022 -k ~/.ssh/spiped_%h_key + +to the ~/.ssh/config file. This will cause "ssh HOSTNAME" to automatically +connect using the spipe client via the spiped daemon; you can then firewall +off all incoming traffic on port tcp/22. + +For a detailed list of the command-line options to spiped and spipe, see the +README files in the respective subdirectories. + +Security requirements +--------------------- + +The user is responsible for ensuring that: +1. The key file contains 256 or more bits of entropy. +2. The same key file is not used for more than 2^64 connections. +3. Any individual connection does not transmit more than 2^64 bytes. + +Encrypted protocol +------------------ + +The client and server share a key file with 256 or more bits of entropy. On +launch, they read the key file and compute + K = SHA256(key file). + +When a connection is established: +C1. The client generates a 256-bit random value nonce_C and sends it. +S1. The server generates a 256-bit random value nonce_S and sends it. + +C2. The client receives a 256-bit value nonce_S. +S2. The server receives a 256-bit value nonce_C. + +C3/S3. Both parties now compute the 512-bit value + dk_1 = PBKDF2-SHA256(K, nonce_C || nonce_S, 1) +and parse it as a pair of 256-bit values + dhmac_C || dhmac_S = dk_1. + +C4. The client picks* a value x_C and computes** y_C = 2^x_C mod p, where p is +the Diffie-Hellman "group #14" modulus, and h_C = HMAC-SHA256(dhmac_C, y_C). +The client sends y_C || h_C to the server. +S4. The server receives a 2304-bit value which it parses as y_C || h_C, where +y_C is 2048 bits and h_C is 256 bits; and drops the connection if h_C is not +equal to HMAC-SHA256(dhmac_C, y_C) or y_C >= p. + +S5. The server picks* a value x_S and computes** y_S = 2^x_S mod p and +h_S = HMAC-SHA256(dhmac_S, y_S). The server sends y_S || h_S to the client. +C5. The client receives a 2304-bit value which it parses as y_S || h_S, where +y_S is 2048 bits and h_S is 256 bits; and drops the connection if h_S is not +equal to HMAC-SHA256(dhmac_S, y_S) or y_S >= p. + +C6. The client computes** y_SC = y_S^x_C mod p. +S6. The server computes** y_SC = y_C^x_S mod p. +(Note that these two compute values are identical.) + +C7/S7. Both parties now compute the 1024-bit value + dk_2 = PBKDF2-SHA256(K, nonce_C || nonce_S || y_SC, 1) +and parse it as a 4-tuple of 256-bit values + E_C || H_C || E_S || H_S. + +Thereafter, the client and server exchange 1060-byte packets P generated from +plaintext messages M of 1--1024 bytes + msg_padded = M || ( 0x00 x (1024 - length(M))) || bigendian32(length(M)) + msg_encrypted = AES256-CTR(E, msg_padded, packet#) + P = msg_encrypted || HMAC-SHA256(H, msg_encrypted || bigendian64(packet#)) +where E and H are E_C and H_C or E_S and H_S depending on whether the packet +is being sent by the client or the server, and AES256-CTR is computed with +nonce equal to the packet #, which starts at zero and increments for each +packet sent in the same direction. + +* The values x_C, x_S picked must either be 0 (if forward perfect secrecy +is not desired) or have 256 bits of entropy (if forward perfect secrecy is +desired). + +** The values y_C, y_S, and y_SC are 2048 bits and big-endian. + +Security proof +-------------- +1. Under the random oracle model, K has at least 255 bits of entropy (it's a +256-bit hash computed from a value with at least 256 bits of entropy). + +2. Provided that at least one party is following the protocol and the key +file has been used for fewer than 2^64 connections, the probability of the +tuple (K, nonce_C, nonce_S) being non-unique is less than 2^(-192). + +3. Under the random oracle model, the probability of an attacker without +access to K guessing either of dhmac_C and dhmac_S is less than + P(attacker guesses K) + + P(the tuple has been input to the oracle before) + + P(the attacker directly guesses), +which is less than + 2^(-255) + 2^(-192) + 2^(-255) = 2^(-192) + 2^(-254). + +4. Consequently, in order for an attacker to convince a protocol-obeying +party that a tuple (y, h) is legitimate, the attacker must do at least 2^190 +expected work (which we consider to be computationally infeasible and do not +consider any further). + +5. If one of the parties opts to not have perfect forward secrecy, then the +value y_SC will be equal to 1 and dk_2 will have the same security properties +as dk_1, i.e., it will be computationally infeasible for an attacker without +access to K to compute dk_2. + +6. If both parties opt for perfect forward secrecy, an attacker who can +compute y_SC has solved a Diffie-Hellman problem over the 2048-bit group #14, +which is (under the CDH assumption) computationally infeasible. + +7. Consequently, if both parties opt for perfect forward secrecy, an attacker +who obtains access to K after the handshake has completed will continue to be +unable to compute dk_2 from information exchanged during the handshake. + +8. Under the random oracle model, the packets P are indistinguishable from +random 1060-byte packets; thus no information about the keys used or the +plaintext being transmitted is revealed by post-key-exchange communications. + +9. Because the values (msg_encrypted || bigendian(packet#)) are distinct for +each packet, under the random oracle model it is infeasible for an attacker +without access to the value H to generate a packet which will be accepted as +valid. + +Code layout +----------- + +spiped/* -- Code specific to the spiped utility. + main.c -- Command-line parsing, initialization, and event loop. + dispatch.c -- Accepts connections and hands them off to protocol code. +spipe/* -- Code specific to the spipe utility. + main.c -- Command-line parsing, initialization, and event loop. + pushbits.c -- Copies data between standard input/output and a socket. +proto/* -- Implements the spiped protocol. + _conn.c -- Manages the lifecycle of a connection. + _handshake.c -- Performs the handshaking portion of the protocol. + _pipe.c -- Performs the data-shuttling portion of the protocol. + _crypt.c -- Does the cryptographic bits needed by _handshake and _pipe. +lib/* -- Library code (mostly originating from tarsnap and kivaloo). diff --git a/STYLE b/STYLE new file mode 100644 index 0000000..d2a59bc --- /dev/null +++ b/STYLE @@ -0,0 +1,139 @@ +Code style +========== + +In general, FreeBSD style(9) should be followed unless it is irrelevant +(e.g., $FreeBSD$ tags). + +Functions with external linkage are declared like this: + /** + * module_func(arg1, arg2): + * Description of what the function does, referring to arguments as + * ${arg1} or suchlike. + */ + int module_func(void *, int); + +The identical comment appears in the C file where the function is defined. + +Static functions may have the above form of comment, or simply a +/* Brief description of what the function does. */ +line before the function. + +In general, functions should return (int)(-1) or NULL to indicate error. + +Errors should be printed via warnp (if errno is relevant) or warn0 (if errno +is not relevant) when they are first detected and also at higher levels where +useful. As an exception to this, malloc failures (i.e., errno = ENOMEM) can +result in failure being passed back up the call chain without being printed +immediately. (Naturally, other errors can be passed back where a function +definition so specifies; e.g., ENOENT in cases where a file not existing is +not erronous.) + +The first statement in main(), after variable declarations, should be +"WARNP_INIT;" in order to set the program name used for printing warnings. + +In general, functions should be structured with one return statement per +status, e.g., one return() for success and one return() for failure. Errors +should be handled by using goto to enter the error return path, e.g., + int + foo(int bar) + { + + if (something fails) + goto err0; + /* ... */ + if (something else fails) + goto err1; + /* ... */ + if (yet another operation fails) + goto err2; + + /* Success! */ + return (0); + + err2: + /* Clean up something. */ + err1: + /* Clean up something else. */ + err0: + /* Failure! */ + return (-1); + } + +As an exception to the above, if there is only one way for the function to +fail, the idioms + return (baz(bar)); +and + int rc; + + rc = baz(bar); + /* ... cleanup code here ... */ + return (rc); +are allowed; furthermore, in cases such as foo_free(), the idiom + if (we shouldn't do anything) + return; +is preferred over + if (we shouldn't do anything) + goto done; +at the start of a function. + +Headers should be included in the following groups, with a blank line after +each (non-empty) group: +1. , with first followed by others alphabetically. +2. , in alphabetical order. +3. <*.h>, in alphabetical order. +4. header files from /lib/, in alphabetical order. +5. header files from the program being built, in alphabetical order. +6. header files (usually just one) defining the interface for this C file. + +If ssize_t is needed, should be included to provide it. + +If size_t is needed, should be included to provide it unless +, , , or is already required. + +If the C99 integer types (uint8_t, int64_t, etc.) are required, +should be included to provide them unless is already required. + +The type 'char' should only be used to represent human-readable characters +(input from users, output to users, pathnames, et cetera). The type +'char *' should normally be a NUL-terminated string. The types 'signed +char' and 'unsigned char' should never be used; C99 integer types should +be used instead. + +When a struct is referenced, the idiom + /* Opaque types. */ + struct foo; + + struct bar * bar_from_foo(struct foo *); +is preferable to + #include "foo.h" /* needed for struct foo */ + + struct bar * bar_from_foo(struct foo *); +unless there is some reason why the internal layout of struct foo is needed +(e.g., if struct bar contains a struct foo rather than a struct foo *). Such +struct declarations should be sorted alphabetically. + +The file foo.c should only export symbols of the following forms: + foo_* -- most symbols should be of this form. + FOO_* / BAR_FOO_* + -- allowed in cases where FOO or BAR_FOO is idiomatic (e.g., + MD5, HMAC_SHA256). + foo() / defoo() / unfoo() + -- where "foo" is a verb and this improves code clarity. + +Functions named foo_free should return void, and foo_free(NULL) should have +no effect. + +If static variables need to be initialized to 0 (or NULL) then they should be +explicitly declared that way; implicit initialization should not be used. + +In non-trivial code, comments should be included which describe in English +what is being done by the surrounding code with sufficient detail that if the +code were removed, it could be replaced based on reading the comments without +requiring any significant creativity. + +Comments and documentation should be written in en-GB-oed; i.e., with +the 'u' included in words such as "honour", "colour", and "neighbour", +and the ending '-ize' in words such as "organize" and "realize". The +Oxford (aka. serial) comma should be used in lists. Quotation marks +should be placed logically, i.e., not including punctuation marks which +do not form a logical part of the quoted text. diff --git a/lib/alg/sha256.c b/lib/alg/sha256.c new file mode 100644 index 0000000..9c4d4a9 --- /dev/null +++ b/lib/alg/sha256.c @@ -0,0 +1,436 @@ +#include +#include + +#include "sysendian.h" + +#include "sha256.h" + +/* + * Encode a length len/4 vector of (uint32_t) into a length len vector of + * (uint8_t) in big-endian form. Assumes len is a multiple of 4. + */ +static void +be32enc_vect(uint8_t *dst, const uint32_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + be32enc(dst + i * 4, src[i]); +} + +/* + * Decode a big-endian length len vector of (uint8_t) into a length + * len/4 vector of (uint32_t). Assumes len is a multiple of 4. + */ +static void +be32dec_vect(uint32_t *dst, const uint8_t *src, size_t len) +{ + size_t i; + + for (i = 0; i < len / 4; i++) + dst[i] = be32dec(src + i * 4); +} + +/* Elementary functions used by SHA256 */ +#define Ch(x, y, z) ((x & (y ^ z)) ^ z) +#define Maj(x, y, z) ((x & (y | z)) | (y & z)) +#define SHR(x, n) (x >> n) +#define ROTR(x, n) ((x >> n) | (x << (32 - n))) +#define S0(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S1(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) +#define s0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define s1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +/* SHA256 round function */ +#define RND(a, b, c, d, e, f, g, h, k) \ + t0 = h + S1(e) + Ch(e, f, g) + k; \ + t1 = S0(a) + Maj(a, b, c); \ + d += t0; \ + h = t0 + t1; + +/* Adjusted round function for rotating state */ +#define RNDr(S, W, i, k) \ + RND(S[(64 - i) % 8], S[(65 - i) % 8], \ + S[(66 - i) % 8], S[(67 - i) % 8], \ + S[(68 - i) % 8], S[(69 - i) % 8], \ + S[(70 - i) % 8], S[(71 - i) % 8], \ + W[i] + k) + +/* + * SHA256 block compression function. The 256-bit state is transformed via + * the 512-bit input block to produce a new state. + */ +static void +SHA256_Transform(uint32_t * state, const uint8_t block[64]) +{ + uint32_t W[64]; + uint32_t S[8]; + uint32_t t0, t1; + int i; + + /* 1. Prepare message schedule W. */ + be32dec_vect(W, block, 64); + for (i = 16; i < 64; i++) + W[i] = s1(W[i - 2]) + W[i - 7] + s0(W[i - 15]) + W[i - 16]; + + /* 2. Initialize working variables. */ + memcpy(S, state, 32); + + /* 3. Mix. */ + RNDr(S, W, 0, 0x428a2f98); + RNDr(S, W, 1, 0x71374491); + RNDr(S, W, 2, 0xb5c0fbcf); + RNDr(S, W, 3, 0xe9b5dba5); + RNDr(S, W, 4, 0x3956c25b); + RNDr(S, W, 5, 0x59f111f1); + RNDr(S, W, 6, 0x923f82a4); + RNDr(S, W, 7, 0xab1c5ed5); + RNDr(S, W, 8, 0xd807aa98); + RNDr(S, W, 9, 0x12835b01); + RNDr(S, W, 10, 0x243185be); + RNDr(S, W, 11, 0x550c7dc3); + RNDr(S, W, 12, 0x72be5d74); + RNDr(S, W, 13, 0x80deb1fe); + RNDr(S, W, 14, 0x9bdc06a7); + RNDr(S, W, 15, 0xc19bf174); + RNDr(S, W, 16, 0xe49b69c1); + RNDr(S, W, 17, 0xefbe4786); + RNDr(S, W, 18, 0x0fc19dc6); + RNDr(S, W, 19, 0x240ca1cc); + RNDr(S, W, 20, 0x2de92c6f); + RNDr(S, W, 21, 0x4a7484aa); + RNDr(S, W, 22, 0x5cb0a9dc); + RNDr(S, W, 23, 0x76f988da); + RNDr(S, W, 24, 0x983e5152); + RNDr(S, W, 25, 0xa831c66d); + RNDr(S, W, 26, 0xb00327c8); + RNDr(S, W, 27, 0xbf597fc7); + RNDr(S, W, 28, 0xc6e00bf3); + RNDr(S, W, 29, 0xd5a79147); + RNDr(S, W, 30, 0x06ca6351); + RNDr(S, W, 31, 0x14292967); + RNDr(S, W, 32, 0x27b70a85); + RNDr(S, W, 33, 0x2e1b2138); + RNDr(S, W, 34, 0x4d2c6dfc); + RNDr(S, W, 35, 0x53380d13); + RNDr(S, W, 36, 0x650a7354); + RNDr(S, W, 37, 0x766a0abb); + RNDr(S, W, 38, 0x81c2c92e); + RNDr(S, W, 39, 0x92722c85); + RNDr(S, W, 40, 0xa2bfe8a1); + RNDr(S, W, 41, 0xa81a664b); + RNDr(S, W, 42, 0xc24b8b70); + RNDr(S, W, 43, 0xc76c51a3); + RNDr(S, W, 44, 0xd192e819); + RNDr(S, W, 45, 0xd6990624); + RNDr(S, W, 46, 0xf40e3585); + RNDr(S, W, 47, 0x106aa070); + RNDr(S, W, 48, 0x19a4c116); + RNDr(S, W, 49, 0x1e376c08); + RNDr(S, W, 50, 0x2748774c); + RNDr(S, W, 51, 0x34b0bcb5); + RNDr(S, W, 52, 0x391c0cb3); + RNDr(S, W, 53, 0x4ed8aa4a); + RNDr(S, W, 54, 0x5b9cca4f); + RNDr(S, W, 55, 0x682e6ff3); + RNDr(S, W, 56, 0x748f82ee); + RNDr(S, W, 57, 0x78a5636f); + RNDr(S, W, 58, 0x84c87814); + RNDr(S, W, 59, 0x8cc70208); + RNDr(S, W, 60, 0x90befffa); + RNDr(S, W, 61, 0xa4506ceb); + RNDr(S, W, 62, 0xbef9a3f7); + RNDr(S, W, 63, 0xc67178f2); + + /* 4. Mix local working variables into global state. */ + for (i = 0; i < 8; i++) + state[i] += S[i]; + + /* Clean the stack. */ + memset(W, 0, 256); + memset(S, 0, 32); + t0 = t1 = 0; +} + +static uint8_t PAD[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +/* Add padding and terminating bit-count. */ +static void +SHA256_Pad(SHA256_CTX * ctx) +{ + uint8_t len[8]; + uint32_t r, plen; + + /* + * Convert length to a vector of bytes -- we do this now rather + * than later because the length will change after we pad. + */ + be32enc_vect(len, ctx->count, 8); + + /* Add 1--64 bytes so that the resulting length is 56 mod 64. */ + r = (ctx->count[1] >> 3) & 0x3f; + plen = (r < 56) ? (56 - r) : (120 - r); + SHA256_Update(ctx, PAD, (size_t)plen); + + /* Add the terminating bit-count. */ + SHA256_Update(ctx, len, 8); +} + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void +SHA256_Init(SHA256_CTX * ctx) +{ + + /* Zero bits processed so far. */ + ctx->count[0] = ctx->count[1] = 0; + + /* Magic initialization constants. */ + ctx->state[0] = 0x6A09E667; + ctx->state[1] = 0xBB67AE85; + ctx->state[2] = 0x3C6EF372; + ctx->state[3] = 0xA54FF53A; + ctx->state[4] = 0x510E527F; + ctx->state[5] = 0x9B05688C; + ctx->state[6] = 0x1F83D9AB; + ctx->state[7] = 0x5BE0CD19; +} + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +void +SHA256_Update(SHA256_CTX * ctx, const void *in, size_t len) +{ + uint32_t bitlen[2]; + uint32_t r; + const uint8_t *src = in; + + /* Return immediately if we have nothing to do. */ + if (len == 0) + return; + + /* Number of bytes left in the buffer from previous updates. */ + r = (ctx->count[1] >> 3) & 0x3f; + + /* Convert the length into a number of bits. */ + bitlen[1] = ((uint32_t)len) << 3; + bitlen[0] = (uint32_t)(len >> 29); + + /* Update number of bits. */ + if ((ctx->count[1] += bitlen[1]) < bitlen[1]) + ctx->count[0]++; + ctx->count[0] += bitlen[0]; + + /* Handle the case where we don't need to perform any transforms. */ + if (len < 64 - r) { + memcpy(&ctx->buf[r], src, len); + return; + } + + /* Finish the current block. */ + memcpy(&ctx->buf[r], src, 64 - r); + SHA256_Transform(ctx->state, ctx->buf); + src += 64 - r; + len -= 64 - r; + + /* Perform complete blocks. */ + while (len >= 64) { + SHA256_Transform(ctx->state, src); + src += 64; + len -= 64; + } + + /* Copy left over data into buffer. */ + memcpy(ctx->buf, src, len); +} + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void +SHA256_Final(uint8_t digest[32], SHA256_CTX * ctx) +{ + + /* Add padding. */ + SHA256_Pad(ctx); + + /* Write the hash. */ + be32enc_vect(digest, ctx->state, 32); + + /* Clear the context state. */ + memset((void *)ctx, 0, sizeof(*ctx)); +} + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from $in} and write it to ${digest}. + */ +void +SHA256_Buf(const void * in, size_t len, uint8_t digest[32]) +{ + SHA256_CTX ctx; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, in, len); + SHA256_Final(digest, &ctx); +} + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +void +HMAC_SHA256_Init(HMAC_SHA256_CTX * ctx, const void * _K, size_t Klen) +{ + uint8_t pad[64]; + uint8_t khash[32]; + const uint8_t * K = _K; + size_t i; + + /* If Klen > 64, the key is really SHA256(K). */ + if (Klen > 64) { + SHA256_Init(&ctx->ictx); + SHA256_Update(&ctx->ictx, K, Klen); + SHA256_Final(khash, &ctx->ictx); + K = khash; + Klen = 32; + } + + /* Inner SHA256 operation is SHA256(K xor [block of 0x36] || data). */ + SHA256_Init(&ctx->ictx); + memset(pad, 0x36, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->ictx, pad, 64); + + /* Outer SHA256 operation is SHA256(K xor [block of 0x5c] || hash). */ + SHA256_Init(&ctx->octx); + memset(pad, 0x5c, 64); + for (i = 0; i < Klen; i++) + pad[i] ^= K[i]; + SHA256_Update(&ctx->octx, pad, 64); + + /* Clean the stack. */ + memset(khash, 0, 32); + memset(pad, 0, 64); +} + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +void +HMAC_SHA256_Update(HMAC_SHA256_CTX * ctx, const void *in, size_t len) +{ + + /* Feed data to the inner SHA256 operation. */ + SHA256_Update(&ctx->ictx, in, len); +} + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void +HMAC_SHA256_Final(uint8_t digest[32], HMAC_SHA256_CTX * ctx) +{ + uint8_t ihash[32]; + + /* Finish the inner SHA256 operation. */ + SHA256_Final(ihash, &ctx->ictx); + + /* Feed the inner hash to the outer SHA256 operation. */ + SHA256_Update(&ctx->octx, ihash, 32); + + /* Finish the outer SHA256 operation. */ + SHA256_Final(digest, &ctx->octx); + + /* Clean the stack. */ + memset(ihash, 0, 32); +} + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void +HMAC_SHA256_Buf(const void * K, size_t Klen, const void * in, size_t len, + uint8_t digest[32]) +{ + HMAC_SHA256_CTX ctx; + + HMAC_SHA256_Init(&ctx, K, Klen); + HMAC_SHA256_Update(&ctx, in, len); + HMAC_SHA256_Final(digest, &ctx); +} + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void +PBKDF2_SHA256(const uint8_t * passwd, size_t passwdlen, const uint8_t * salt, + size_t saltlen, uint64_t c, uint8_t * buf, size_t dkLen) +{ + HMAC_SHA256_CTX PShctx, hctx; + size_t i; + uint8_t ivec[4]; + uint8_t U[32]; + uint8_t T[32]; + uint64_t j; + int k; + size_t clen; + + /* Compute HMAC state after processing P and S. */ + HMAC_SHA256_Init(&PShctx, passwd, passwdlen); + HMAC_SHA256_Update(&PShctx, salt, saltlen); + + /* Iterate through the blocks. */ + for (i = 0; i * 32 < dkLen; i++) { + /* Generate INT(i + 1). */ + be32enc(ivec, (uint32_t)(i + 1)); + + /* Compute U_1 = PRF(P, S || INT(i)). */ + memcpy(&hctx, &PShctx, sizeof(HMAC_SHA256_CTX)); + HMAC_SHA256_Update(&hctx, ivec, 4); + HMAC_SHA256_Final(U, &hctx); + + /* T_i = U_1 ... */ + memcpy(T, U, 32); + + for (j = 2; j <= c; j++) { + /* Compute U_j. */ + HMAC_SHA256_Init(&hctx, passwd, passwdlen); + HMAC_SHA256_Update(&hctx, U, 32); + HMAC_SHA256_Final(U, &hctx); + + /* ... xor U_j ... */ + for (k = 0; k < 32; k++) + T[k] ^= U[k]; + } + + /* Copy as many bytes as necessary into buf. */ + clen = dkLen - i * 32; + if (clen > 32) + clen = 32; + memcpy(&buf[i * 32], T, clen); + } + + /* Clean PShctx, since we never called _Final on it. */ + memset(&PShctx, 0, sizeof(HMAC_SHA256_CTX)); +} diff --git a/lib/alg/sha256.h b/lib/alg/sha256.h new file mode 100644 index 0000000..5e8c56b --- /dev/null +++ b/lib/alg/sha256.h @@ -0,0 +1,95 @@ +#ifndef _SHA256_H_ +#define _SHA256_H_ + +#include +#include + +/* + * Use #defines in order to avoid namespace collisions with anyone else's + * SHA256 code (e.g., the code in OpenSSL). + */ +#define SHA256_Init libcperciva_SHA256_Init +#define SHA256_Update libcperciva_SHA256_Update +#define SHA256_Final libcperciva_SHA256_Final +#define SHA256_Buf libcperciva_SHA256_Buf +#define SHA256_CTX libcperciva_SHA256_CTX +#define HMAC_SHA256_Init libcperciva_HMAC_SHA256_Init +#define HMAC_SHA256_Update libcperciva_HMAC_SHA256_Update +#define HMAC_SHA256_Final libcperciva_HMAC_SHA256_Final +#define HMAC_SHA256_Buf libcperciva_HMAC_SHA256_Buf +#define HMAC_SHA256_CTX libcperciva_HMAC_SHA256_CTX + +/* Context structure for SHA256 operations. */ +typedef struct { + uint32_t state[8]; + uint32_t count[2]; + uint8_t buf[64]; +} SHA256_CTX; + +/** + * SHA256_Init(ctx): + * Initialize the SHA256 context ${ctx}. + */ +void SHA256_Init(SHA256_CTX *); + +/** + * SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the SHA256 context ${ctx}. + */ +void SHA256_Update(SHA256_CTX *, const void *, size_t); + +/** + * SHA256_Final(digest, ctx): + * Output the SHA256 hash of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void SHA256_Final(uint8_t[32], SHA256_CTX *); + +/** + * SHA256_Buf(in, len, digest): + * Compute the SHA256 hash of ${len} bytes from $in} and write it to ${digest}. + */ +void SHA256_Buf(const void *, size_t, uint8_t[32]); + +/* Context structure for HMAC-SHA256 operations. */ +typedef struct { + SHA256_CTX ictx; + SHA256_CTX octx; +} HMAC_SHA256_CTX; + +/** + * HMAC_SHA256_Init(ctx, K, Klen): + * Initialize the HMAC-SHA256 context ${ctx} with ${Klen} bytes of key from + * ${K}. + */ +void HMAC_SHA256_Init(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Update(ctx, in, len): + * Input ${len} bytes from ${in} into the HMAC-SHA256 context ${ctx}. + */ +void HMAC_SHA256_Update(HMAC_SHA256_CTX *, const void *, size_t); + +/** + * HMAC_SHA256_Final(digest, ctx): + * Output the HMAC-SHA256 of the data input to the context ${ctx} into the + * buffer ${digest}. + */ +void HMAC_SHA256_Final(uint8_t[32], HMAC_SHA256_CTX *); + +/** + * HMAC_SHA256_Buf(K, Klen, in, len, digest): + * Compute the HMAC-SHA256 of ${len} bytes from ${in} using the key ${K} of + * length ${Klen}, and write the result to ${digest}. + */ +void HMAC_SHA256_Buf(const void *, size_t, const void *, size_t, uint8_t[32]); + +/** + * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): + * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and + * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). + */ +void PBKDF2_SHA256(const uint8_t *, size_t, const uint8_t *, size_t, + uint64_t, uint8_t *, size_t); + +#endif /* !_SHA256_H_ */ diff --git a/lib/crypto/crypto_aesctr.c b/lib/crypto/crypto_aesctr.c new file mode 100644 index 0000000..2590523 --- /dev/null +++ b/lib/crypto/crypto_aesctr.c @@ -0,0 +1,124 @@ +#include +#include + +#include + +#include "sysendian.h" + +#include "crypto_aesctr.h" + +struct crypto_aesctr { + const AES_KEY * key; + uint64_t nonce; + uint64_t bytectr; + uint8_t buf[16]; +}; + +/** + * crypto_aesctr_init(key, nonce): + * Prepare to encrypt/decrypt data with AES in CTR mode, using the provided + * expanded key and nonce. The key provided must remain valid for the + * lifetime of the stream. + */ +struct crypto_aesctr * +crypto_aesctr_init(const AES_KEY * key, uint64_t nonce) +{ + struct crypto_aesctr * stream; + + /* Allocate memory. */ + if ((stream = malloc(sizeof(struct crypto_aesctr))) == NULL) + goto err0; + + /* Initialize values. */ + stream->key = key; + stream->nonce = nonce; + stream->bytectr = 0; + + /* Success! */ + return (stream); + +err0: + /* Failure! */ + return (NULL); +} + +/** + * crypto_aesctr_stream(stream, inbuf, outbuf, buflen): + * Generate the next ${buflen} bytes of the AES-CTR stream and xor them with + * bytes from ${inbuf}, writing the result into ${outbuf}. If the buffers + * ${inbuf} and ${outbuf} overlap, they must be identical. + */ +void +crypto_aesctr_stream(struct crypto_aesctr * stream, const uint8_t * inbuf, + uint8_t * outbuf, size_t buflen) +{ + uint8_t pblk[16]; + size_t pos; + int bytemod; + + for (pos = 0; pos < buflen; pos++) { + /* How far through the buffer are we? */ + bytemod = stream->bytectr % 16; + + /* Generate a block of cipherstream if needed. */ + if (bytemod == 0) { + be64enc(pblk, stream->nonce); + be64enc(pblk + 8, stream->bytectr / 16); + AES_encrypt(pblk, stream->buf, stream->key); + } + + /* Encrypt a byte. */ + outbuf[pos] = inbuf[pos] ^ stream->buf[bytemod]; + + /* Move to the next byte of cipherstream. */ + stream->bytectr += 1; + } +} + +/** + * crypto_aesctr_free(stream): + * Free the provided stream object. + */ +void +crypto_aesctr_free(struct crypto_aesctr * stream) +{ + int i; + + /* Be compatible with free(NULL). */ + if (stream == NULL) + return; + + /* Zero potentially sensitive information. */ + for (i = 0; i < 16; i++) + stream->buf[i] = 0; + stream->bytectr = stream->nonce = 0; + + /* Free the stream. */ + free(stream); +} + +/** + * crypto_aesctr_buf(key, nonce, inbuf, outbuf, buflen): + * Equivalent to init(key, nonce); stream(inbuf, outbuf, buflen); free. + */ +void +crypto_aesctr_buf(const AES_KEY * key, uint64_t nonce, + const uint8_t * inbuf, uint8_t * outbuf, size_t buflen) +{ + struct crypto_aesctr stream_rec; + struct crypto_aesctr * stream = &stream_rec; + int i; + + /* Initialize values. */ + stream->key = key; + stream->nonce = nonce; + stream->bytectr = 0; + + /* Perform the encryption. */ + crypto_aesctr_stream(stream, inbuf, outbuf, buflen); + + /* Zero potentially sensitive information. */ + for (i = 0; i < 16; i++) + stream->buf[i] = 0; + stream->bytectr = stream->nonce = 0; +} diff --git a/lib/crypto/crypto_aesctr.h b/lib/crypto/crypto_aesctr.h new file mode 100644 index 0000000..4607b4c --- /dev/null +++ b/lib/crypto/crypto_aesctr.h @@ -0,0 +1,39 @@ +#ifndef _CRYPTO_AESCTR_H_ +#define _CRYPTO_AESCTR_H_ + +#include +#include + +#include + +/** + * crypto_aesctr_init(key, nonce): + * Prepare to encrypt/decrypt data with AES in CTR mode, using the provided + * expanded key and nonce. The key provided must remain valid for the + * lifetime of the stream. + */ +struct crypto_aesctr * crypto_aesctr_init(const AES_KEY *, uint64_t); + +/** + * crypto_aesctr_stream(stream, inbuf, outbuf, buflen): + * Generate the next ${buflen} bytes of the AES-CTR stream and xor them with + * bytes from ${inbuf}, writing the result into ${outbuf}. If the buffers + * ${inbuf} and ${outbuf} overlap, they must be identical. + */ +void crypto_aesctr_stream(struct crypto_aesctr *, const uint8_t *, + uint8_t *, size_t); + +/** + * crypto_aesctr_free(stream): + * Free the provided stream object. + */ +void crypto_aesctr_free(struct crypto_aesctr *); + +/** + * crypto_aesctr_buf(key, nonce, inbuf, outbuf, buflen): + * Equivalent to init(key, nonce); stream(inbuf, outbuf, buflen); free. + */ +void crypto_aesctr_buf(const AES_KEY *, uint64_t, + const uint8_t *, uint8_t *, size_t); + +#endif /* !_CRYPTO_AESCTR_H_ */ diff --git a/lib/crypto/crypto_dh.c b/lib/crypto/crypto_dh.c new file mode 100644 index 0000000..825ddb7 --- /dev/null +++ b/lib/crypto/crypto_dh.c @@ -0,0 +1,293 @@ +#include +#include + +#include +#include + +#include "warnp.h" + +#include "crypto_entropy.h" +#include "crypto_dh_group14.h" + +#include "crypto_dh.h" + +static int blinded_modexp(uint8_t r[CRYPTO_DH_PUBLEN], BIGNUM * a, + const uint8_t priv[CRYPTO_DH_PRIVLEN]); + +/* Big-endian representation of 2^256. */ +static uint8_t two_exp_256[] = { + 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +/** + * blinded_modexp(r, a, priv): + * Compute ${r} = ${a}^(2^258 + ${priv}), where ${r} and ${priv} are treated + * as big-endian integers; and avoid leaking timing data in this process. + */ +static int +blinded_modexp(uint8_t r[CRYPTO_DH_PUBLEN], BIGNUM * a, + const uint8_t priv[CRYPTO_DH_PRIVLEN]) +{ + BIGNUM * two_exp_256_bn; + BIGNUM * priv_bn; + uint8_t blinding[CRYPTO_DH_PRIVLEN]; + BIGNUM * blinding_bn; + BIGNUM * priv_blinded; + BIGNUM * m_bn; + BN_CTX * ctx; + BIGNUM * r1; + BIGNUM * r2; + size_t rlen; + + /* Construct 2^256 in BN representation. */ + if ((two_exp_256_bn = BN_bin2bn(two_exp_256, 33, NULL)) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err0; + } + + /* Construct 2^258 + ${priv} in BN representation. */ + if ((priv_bn = BN_bin2bn(priv, CRYPTO_DH_PRIVLEN, NULL)) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err1; + } + if ((!BN_add(priv_bn, priv_bn, two_exp_256_bn)) || + (!BN_add(priv_bn, priv_bn, two_exp_256_bn)) || + (!BN_add(priv_bn, priv_bn, two_exp_256_bn)) || + (!BN_add(priv_bn, priv_bn, two_exp_256_bn))) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err2; + } + + /* Generate blinding exponent. */ + if (crypto_entropy_read(blinding, CRYPTO_DH_PRIVLEN)) + goto err2; + if ((blinding_bn = BN_bin2bn(blinding, + CRYPTO_DH_PRIVLEN, NULL)) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err2; + } + if (!BN_add(blinding_bn, blinding_bn, two_exp_256_bn)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err3; + } + + /* Generate blinded exponent. */ + if ((priv_blinded = BN_new()) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err3; + } + if (!BN_sub(priv_blinded, priv_bn, blinding_bn)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err4; + } + + /* Construct group #14 modulus in BN representation. */ + if ((m_bn = BN_bin2bn(crypto_dh_group14, 256, NULL)) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err4; + } + + /* Allocate BN context. */ + if ((ctx = BN_CTX_new()) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err5; + } + + /* Allocate space for storing results of exponentiations. */ + if ((r1 = BN_new()) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err6; + } + if ((r2 = BN_new()) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err7; + } + + /* Perform modular exponentiations. */ + if (!BN_mod_exp(r1, a, blinding_bn, m_bn, ctx)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err8; + } + if (!BN_mod_exp(r2, a, priv_blinded, m_bn, ctx)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err8; + } + + /* Compute final result and export to big-endian integer format. */ + if (!BN_mod_mul(r1, r1, r2, m_bn, ctx)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err8; + } + rlen = BN_num_bytes(r1); + if (rlen > CRYPTO_DH_PUBLEN) { + warn0("Exponent result too large!"); + goto err8; + } + memset(r, 0, CRYPTO_DH_PUBLEN - rlen); + BN_bn2bin(r1, r + CRYPTO_DH_PUBLEN - rlen); + + /* Free space allocated by BN_new. */ + BN_free(r2); + BN_free(r1); + + /* Free context allocated by BN_CTX_new. */ + BN_CTX_free(ctx); + + /* Free space allocated by BN_bin2bn. */ + BN_free(m_bn); + + /* Free space allocated by BN_new. */ + BN_free(priv_blinded); + + /* Free space allocated by BN_bin2bn. */ + BN_free(blinding_bn); + BN_free(priv_bn); + BN_free(two_exp_256_bn); + + /* Success! */ + return (0); + +err8: + BN_free(r2); +err7: + BN_free(r1); +err6: + BN_CTX_free(ctx); +err5: + BN_free(m_bn); +err4: + BN_free(priv_blinded); +err3: + BN_free(blinding_bn); +err2: + BN_free(priv_bn); +err1: + BN_free(two_exp_256_bn); +err0: + /* Failure! */ + return (-1); +} + +/** + * crypto_dh_generate_pub(pub, priv): + * Compute ${pub} equal to 2^(2^258 + ${priv}) in Diffie-Hellman group #14. + */ +int +crypto_dh_generate_pub(uint8_t pub[CRYPTO_DH_PUBLEN], + const uint8_t priv[CRYPTO_DH_PRIVLEN]) +{ + BIGNUM * two; + + /* Generate BN representation for 2. */ + if ((two = BN_new()) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err0; + } + if (!BN_set_word(two, 2)) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err1; + } + + /* Compute pub = two^(2^258 + priv). */ + if (blinded_modexp(pub, two, priv)) + goto err1; + + /* Free storage allocated by BN_new. */ + BN_free(two); + + /* Success! */ + return (0); + +err1: + BN_free(two); +err0: + /* Failure! */ + return (-1); +} + +/** + * crypto_dh_generate(pub, priv): + * Generate a 256-bit private key ${priv}, and compute ${pub} equal to + * 2^(2^258 + ${priv}) mod p where p is the Diffie-Hellman group #14 modulus. + * Both values are stored as big-endian integers. + */ +int +crypto_dh_generate(uint8_t pub[CRYPTO_DH_PUBLEN], + uint8_t priv[CRYPTO_DH_PRIVLEN]) +{ + + /* Generate a random private key. */ + if (crypto_entropy_read(priv, CRYPTO_DH_PRIVLEN)) + goto err0; + + /* Compute the public key. */ + if (crypto_dh_generate_pub(pub, priv)) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * crypto_dh_compute(pub, priv, key): + * In the Diffie-Hellman group #14, compute ${pub}^(2^258 + ${priv}) and + * write the result into ${key}. All values are big-endian. Note that the + * value ${pub} is the public key produced the call to crypto_dh_generate + * made by the *other* participant in the key exchange. + */ +int +crypto_dh_compute(const uint8_t pub[CRYPTO_DH_PUBLEN], + const uint8_t priv[CRYPTO_DH_PRIVLEN], uint8_t key[CRYPTO_DH_KEYLEN]) +{ + BIGNUM * a; + + /* Convert ${pub} into BN representation. */ + if ((a = BN_bin2bn(pub, CRYPTO_DH_PUBLEN, NULL)) == NULL) { + warn0("%s", ERR_error_string(ERR_get_error(), NULL)); + goto err0; + } + + /* Compute key = pub^(2^258 + priv). */ + if (blinded_modexp(key, a, priv)) + goto err1; + + /* Free storage allocated by BN_bin2bn. */ + BN_free(a); + + /* Success! */ + return (0); + +err1: + BN_free(a); +err0: + /* Failure! */ + return (-1); +} + +/** + * crypto_dh_sanitycheck(pub): + * Sanity-check the Diffie-Hellman public value ${pub} by checking that it + * is less than the group #14 modulus. Return 0 if sane, -1 if insane. + */ +int +crypto_dh_sanitycheck(const uint8_t pub[CRYPTO_DH_PUBLEN]) +{ + + if (memcmp(pub, crypto_dh_group14, 256) >= 0) + return (-1); + + /* Value is sane. */ + return (0); +} diff --git a/lib/crypto/crypto_dh.h b/lib/crypto/crypto_dh.h new file mode 100644 index 0000000..d2a358e --- /dev/null +++ b/lib/crypto/crypto_dh.h @@ -0,0 +1,43 @@ +#ifndef _CRYPTO_DH_H_ +#define _CRYPTO_DH_H_ + +#include + +/* Sizes of Diffie-Hellman private, public, and exchanged keys. */ +#define CRYPTO_DH_PRIVLEN 32 +#define CRYPTO_DH_PUBLEN 256 +#define CRYPTO_DH_KEYLEN 256 + +/** + * crypto_dh_generate_pub(pub, priv): + * Compute ${pub} equal to 2^(2^258 + ${priv}) in Diffie-Hellman group #14. + */ +int crypto_dh_generate_pub(uint8_t[CRYPTO_DH_PUBLEN], + const uint8_t[CRYPTO_DH_PRIVLEN]); + +/** + * crypto_dh_generate(pub, priv): + * Generate a 256-bit private key ${priv}, and compute ${pub} equal to + * 2^(2^258 + ${priv}) mod p where p is the Diffie-Hellman group #14 modulus. + * Both values are stored as big-endian integers. + */ +int crypto_dh_generate(uint8_t[CRYPTO_DH_PUBLEN], uint8_t[CRYPTO_DH_PRIVLEN]); + +/** + * crypto_dh_compute(pub, priv, key): + * In the Diffie-Hellman group #14, compute ${pub}^(2^258 + ${priv}) and + * write the result into ${key}. All values are big-endian. Note that the + * value ${pub} is the public key produced the call to crypto_dh_generate + * made by the *other* participant in the key exchange. + */ +int crypto_dh_compute(const uint8_t[CRYPTO_DH_PUBLEN], + const uint8_t[CRYPTO_DH_PRIVLEN], uint8_t[CRYPTO_DH_KEYLEN]); + +/** + * crypto_dh_sanitycheck(pub): + * Sanity-check the Diffie-Hellman public value ${pub} by checking that it + * is less than the group #14 modulus. Return 0 if sane, -1 if insane. + */ +int crypto_dh_sanitycheck(const uint8_t[CRYPTO_DH_PUBLEN]); + +#endif /* !_CRYPTO_DH_H_ */ diff --git a/lib/crypto/crypto_dh_group14.c b/lib/crypto/crypto_dh_group14.c new file mode 100644 index 0000000..b6d81d8 --- /dev/null +++ b/lib/crypto/crypto_dh_group14.c @@ -0,0 +1,46 @@ +#include + +#include "crypto_dh_group14.h" + +/** + * This is the big-endian representation of + * p = 2^2048 - 2^1984 + 2^64 * floor(2^1918 pi + 124476) - 1 + * where the value 124476 is chosen to be the least positive integer such + * that both p and (p - 1)/2 are prime. Diffie-Hellman operations are done + * in the group of quadratic residues modulo p, and the integer 2 is a + * generator of this group. + */ +uint8_t crypto_dh_group14[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, + 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, + 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, + 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, + 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, + 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, + 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, + 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, + 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, + 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, + 0x0b, 0xff, 0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, + 0xee, 0x38, 0x6b, 0xfb, 0x5a, 0x89, 0x9f, 0xa5, + 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b, 0x1f, 0xe6, + 0x49, 0x28, 0x66, 0x51, 0xec, 0xe4, 0x5b, 0x3d, + 0xc2, 0x00, 0x7c, 0xb8, 0xa1, 0x63, 0xbf, 0x05, + 0x98, 0xda, 0x48, 0x36, 0x1c, 0x55, 0xd3, 0x9a, + 0x69, 0x16, 0x3f, 0xa8, 0xfd, 0x24, 0xcf, 0x5f, + 0x83, 0x65, 0x5d, 0x23, 0xdc, 0xa3, 0xad, 0x96, + 0x1c, 0x62, 0xf3, 0x56, 0x20, 0x85, 0x52, 0xbb, + 0x9e, 0xd5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6d, + 0x67, 0x0c, 0x35, 0x4e, 0x4a, 0xbc, 0x98, 0x04, + 0xf1, 0x74, 0x6c, 0x08, 0xca, 0x18, 0x21, 0x7c, + 0x32, 0x90, 0x5e, 0x46, 0x2e, 0x36, 0xce, 0x3b, + 0xe3, 0x9e, 0x77, 0x2c, 0x18, 0x0e, 0x86, 0x03, + 0x9b, 0x27, 0x83, 0xa2, 0xec, 0x07, 0xa2, 0x8f, + 0xb5, 0xc5, 0x5d, 0xf0, 0x6f, 0x4c, 0x52, 0xc9, + 0xde, 0x2b, 0xcb, 0xf6, 0x95, 0x58, 0x17, 0x18, + 0x39, 0x95, 0x49, 0x7c, 0xea, 0x95, 0x6a, 0xe5, + 0x15, 0xd2, 0x26, 0x18, 0x98, 0xfa, 0x05, 0x10, + 0x15, 0x72, 0x8e, 0x5a, 0x8a, 0xac, 0xaa, 0x68, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff +}; diff --git a/lib/crypto/crypto_dh_group14.h b/lib/crypto/crypto_dh_group14.h new file mode 100644 index 0000000..563bcf8 --- /dev/null +++ b/lib/crypto/crypto_dh_group14.h @@ -0,0 +1,9 @@ +#ifndef _CRYPTO_DH_GROUP14_H_ +#define _CRYPTO_DH_GROUP14_H_ + +#include + +/* Diffie-Hellman group #14, from RFC 3526. */ +extern uint8_t crypto_dh_group14[]; + +#endif /* !_CRYPTO_DH_GROUP14_H_ */ diff --git a/lib/crypto/crypto_entropy.c b/lib/crypto/crypto_entropy.c new file mode 100644 index 0000000..ac01cc9 --- /dev/null +++ b/lib/crypto/crypto_entropy.c @@ -0,0 +1,214 @@ +#include +#include +#include + +#include "entropy.h" + +#include "sha256.h" + +#include "crypto_entropy.h" + +/** + * This system implements the HMAC_DRBG pseudo-random number generator as + * specified in section 10.1.2 of the NIST SP 800-90 standard. In this + * implementation, the optional personalization_string and additional_input + * specified in the standard are not implemented. + */ + +/* Internal HMAC_DRBG state. */ +static struct { + uint8_t Key[32]; + uint8_t V[32]; + uint32_t reseed_counter; +} drbg; + +/* Set to non-zero once the PRNG has been instantiated. */ +static int instantiated = 0; + +/* Could be as high as 2^48 if we wanted... */ +#define RESEED_INTERVAL 256 + +/* Limited to 2^16 by specification. */ +#define GENERATE_MAXLEN 65536 + +static int instantiate(void); +static void update(uint8_t *, size_t); +static int reseed(void); +static void generate(uint8_t *, size_t); + +/** + * instantiate(void): + * Initialize the DRBG state. (Section 10.1.2.3) + */ +static int +instantiate(void) +{ + uint8_t seed_material[48]; + + /* Obtain random seed_material = (entropy_input || nonce). */ + if (entropy_read(seed_material, 48)) + return (-1); + + /* Initialize Key, V, and reseed_counter. */ + memset(drbg.Key, 0x00, 32); + memset(drbg.V, 0x01, 32); + drbg.reseed_counter = 1; + + /* Mix the random seed into the state. */ + update(seed_material, 48); + + /* Clean the stack. */ + memset(seed_material, 0, 48); + + /* Success! */ + return (0); +} + +/** + * update(data, datalen): + * Update the DRBG state using the provided data. (Section 10.1.2.2) + */ +static void +update(uint8_t * data, size_t datalen) +{ + HMAC_SHA256_CTX ctx; + uint8_t K[32]; + uint8_t Vx[33]; + + /* Load (Key, V) into (K, Vx). */ + memcpy(K, drbg.Key, 32); + memcpy(Vx, drbg.V, 32); + + /* K <- HMAC(K, V || 0x00 || data). */ + Vx[32] = 0x00; + HMAC_SHA256_Init(&ctx, K, 32); + HMAC_SHA256_Update(&ctx, Vx, 33); + HMAC_SHA256_Update(&ctx, data, datalen); + HMAC_SHA256_Final(K, &ctx); + + /* V <- HMAC(K, V). */ + HMAC_SHA256_Buf(K, 32, Vx, 32, Vx); + + /* If the provided data is non-Null, perform another mixing stage. */ + if (datalen != 0) { + /* K <- HMAC(K, V || 0x01 || data). */ + Vx[32] = 0x01; + HMAC_SHA256_Init(&ctx, K, 32); + HMAC_SHA256_Update(&ctx, Vx, 33); + HMAC_SHA256_Update(&ctx, data, datalen); + HMAC_SHA256_Final(K, &ctx); + + /* V <- HMAC(K, V). */ + HMAC_SHA256_Buf(K, 32, Vx, 32, Vx); + } + + /* Copy (K, Vx) back to (Key, V). */ + memcpy(drbg.Key, K, 32); + memcpy(drbg.V, Vx, 32); + + /* Clean the stack. */ + memset(K, 0, 32); + memset(Vx, 0, 33); +} + +/** + * reseed(void): + * Reseed the DRBG state (mix in new entropy). (Section 10.1.2.4) + */ +static int +reseed(void) +{ + uint8_t seed_material[32]; + + /* Obtain random seed_material = entropy_input. */ + if (entropy_read(seed_material, 32)) + return (-1); + + /* Mix the random seed into the state. */ + update(seed_material, 32); + + /* Reset the reseed_counter. */ + drbg.reseed_counter = 1; + + /* Clean the stack. */ + memset(seed_material, 0, 32); + + /* Success! */ + return (0); +} + +/** + * generate(buf, buflen): + * Fill the provided buffer with random bits, assuming that reseed_counter + * is less than RESEED_INTERVAL (the caller is responsible for calling + * reseed() as needed) and ${buflen} is less than 2^16 (the caller is + * responsible for splitting up larger requests). (Section 10.1.2.5) + */ +static void +generate(uint8_t * buf, size_t buflen) +{ + size_t bufpos; + + assert(buflen <= GENERATE_MAXLEN); + assert(drbg.reseed_counter <= RESEED_INTERVAL); + + /* Iterate until we've filled the buffer. */ + for (bufpos = 0; bufpos < buflen; bufpos += 32) { + HMAC_SHA256_Buf(drbg.Key, 32, drbg.V, 32, drbg.V); + if (buflen - bufpos >= 32) + memcpy(&buf[bufpos], drbg.V, 32); + else + memcpy(&buf[bufpos], drbg.V, buflen - bufpos); + } + + /* Mix up state. */ + update(NULL, 0); + + /* We're one data-generation step closer to needing a reseed. */ + drbg.reseed_counter += 1; +} + +/** + * crypto_entropy_read(buf, buflen): + * Fill the buffer with unpredictable bits. + */ +int +crypto_entropy_read(uint8_t * buf, size_t buflen) +{ + size_t bytes_to_provide; + + /* Instantiate if needed. */ + if (instantiated == 0) { + /* Try to instantiate the PRNG. */ + if (instantiate()) + return (-1); + + /* We have instantiated the PRNG. */ + instantiated = 1; + } + + /* Loop until we've filled the buffer. */ + while (buflen > 0) { + /* Do we need to reseed? */ + if (drbg.reseed_counter > RESEED_INTERVAL) { + if (reseed()) + return (-1); + } + + /* How much data are we generating in this step? */ + if (buflen > GENERATE_MAXLEN) + bytes_to_provide = GENERATE_MAXLEN; + else + bytes_to_provide = buflen; + + /* Generate bytes. */ + generate(buf, bytes_to_provide); + + /* We've done part of the buffer. */ + buf += bytes_to_provide; + buflen -= bytes_to_provide; + } + + /* Success! */ + return (0); +} diff --git a/lib/crypto/crypto_entropy.h b/lib/crypto/crypto_entropy.h new file mode 100644 index 0000000..1137710 --- /dev/null +++ b/lib/crypto/crypto_entropy.h @@ -0,0 +1,14 @@ +#ifndef _CRYPTO_ENTROPY_H_ +#define _CRYPTO_ENTROPY_H_ + +#include +#include + +/** + * crypto_entropy_read(buf, buflen): + * Fill the buffer with unpredictable bits. The value ${buflen} must be + * less than 2^16. + */ +int crypto_entropy_read(uint8_t *, size_t); + +#endif /* !_CRYPTO_ENTROPY_H_ */ diff --git a/lib/crypto/crypto_verify_bytes.c b/lib/crypto/crypto_verify_bytes.c new file mode 100644 index 0000000..ff3136a --- /dev/null +++ b/lib/crypto/crypto_verify_bytes.c @@ -0,0 +1,21 @@ +#include +#include + +#include "crypto_verify_bytes.h" + +/** + * crypto_verify_bytes(buf0, buf1, len): + * Return zero if and only if buf0[0 .. len - 1] and buf1[0 .. len - 1] are + * identical. Do not leak any information via timing side channels. + */ +uint8_t +crypto_verify_bytes(const uint8_t * buf0, const uint8_t * buf1, size_t len) +{ + uint8_t rc = 0; + size_t i; + + for (i = 0; i < len; i++) + rc = rc | (buf0[i] ^ buf1[i]); + + return (rc); +} diff --git a/lib/crypto/crypto_verify_bytes.h b/lib/crypto/crypto_verify_bytes.h new file mode 100644 index 0000000..da14187 --- /dev/null +++ b/lib/crypto/crypto_verify_bytes.h @@ -0,0 +1,14 @@ +#ifndef _CRYPTO_VERIFY_BYTES_H_ +#define _CRYPTO_VERIFY_BYTES_H_ + +#include +#include + +/** + * crypto_verify_bytes(buf0, buf1, len): + * Return zero if and only if buf0[0 .. len - 1] and buf1[0 .. len - 1] are + * identical. Do not leak any information via timing side channels. + */ +uint8_t crypto_verify_bytes(const uint8_t *, const uint8_t *, size_t); + +#endif /* !_CRYPTO_VERIFY_BYTES_H_ */ diff --git a/lib/datastruct/elasticarray.c b/lib/datastruct/elasticarray.c new file mode 100644 index 0000000..3388919 --- /dev/null +++ b/lib/datastruct/elasticarray.c @@ -0,0 +1,250 @@ +#include +#include +#include +#include + +#include "elasticarray.h" + +struct elasticarray { + size_t size; + size_t alloc; + void * buf; +}; + +/** + * resize(EA, nsize): + * Resize the virtual buffer for ${EA} to length ${nsize} bytes. The actual + * buffer may or may not need to be resized. On failure, the buffer will be + * unmodified. + */ +static int +resize(struct elasticarray * EA, size_t nsize) +{ + size_t nalloc; + void * nbuf; + + /* Figure out how large an allocation we want. */ + if (EA->alloc < nsize) { + /* We need to enlarge the buffer. */ + nalloc = EA->alloc * 2; + if (nalloc < nsize) + nalloc = nsize; + } else if (EA->alloc > nsize * 4) { + /* We need to shrink the buffer. */ + nalloc = nsize * 2; + } else { + nalloc = EA->alloc; + } + + /* Reallocate if necessary. */ + if (nalloc != EA->alloc) { + nbuf = realloc(EA->buf, nalloc); + if ((nbuf == NULL) && (nalloc > 0)) + goto err0; + EA->buf = nbuf; + EA->alloc = nalloc; + } + + /* Record the new array size. */ + EA->size = nsize; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * elasticarray_init(nrec, reclen): + * Create and return an elastic array holding ${nrec} (uninitialized) records + * of length ${reclen}. Takes O(nrec * reclen) time. + */ +struct elasticarray * +elasticarray_init(size_t nrec, size_t reclen) +{ + struct elasticarray * EA; + + /* Allocate structure. */ + if ((EA = malloc(sizeof(struct elasticarray))) == NULL) + goto err0; + + /* The array is empty for now. */ + EA->size = EA->alloc = 0; + EA->buf = NULL; + + /* Reallocate to the requested length. */ + if (elasticarray_resize(EA, nrec, reclen)) + goto err1; + + /* Success! */ + return (EA); + +err1: + elasticarray_free(EA); +err0: + /* Failure! */ + return (NULL); +} + +/** + * elasticarray_resize(EA, nrec, reclen): + * Resize the elastic array pointed to by ${EA} to hold ${nrec} records of + * length ${reclen}. If ${nrec} exceeds the number of records previously + * held by the array, the additional records will be uninitialized. Takes + * O(nrec * reclen) time. + */ +int +elasticarray_resize(struct elasticarray * EA, size_t nrec, size_t reclen) +{ + + /* Check for overflow. */ + if (nrec > SIZE_MAX / reclen) { + errno = ENOMEM; + goto err0; + } + + /* Resize the buffer. */ + if (resize(EA, nrec * reclen)) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * elasticarray_getsize(EA, reclen): + * Return the number of length-${reclen} records in the array, rounding down + * if there is a partial record (which can only occur if elasticarray_* + * functions have been called with different values of reclen). + */ +size_t +elasticarray_getsize(struct elasticarray * EA, size_t reclen) +{ + + return (EA->size / reclen); +} + +/** + * elasticarray_append(EA, buf, nrec, reclen): + * Append to the elastic array ${EA} the ${nrec} records of length ${reclen} + * stored in ${buf}. Takes O(nrec * reclen) amortized time. + */ +int +elasticarray_append(struct elasticarray * EA, + const void * buf, size_t nrec, size_t reclen) +{ + size_t bufpos = EA->size; + + /* Check for overflow. */ + if ((nrec > SIZE_MAX / reclen) || + (nrec * reclen > SIZE_MAX - EA->size)) { + errno = ENOMEM; + goto err0; + } + + /* Resize the buffer. */ + if (resize(EA, EA->size + nrec * reclen)) + goto err0; + + /* Copy bytes in. */ + memcpy((void *)((uintptr_t)(EA->buf) + bufpos), buf, nrec * reclen); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * elasticarray_shrink(EA, nrec, reclen): + * Delete the final ${nrec} records of length ${reclen} from the elastic + * array ${EA}. If there are fewer than ${nrec} records, all records + * present will be deleted. + * + * As an exception to the normal rule, an elastic array may occupy more than + * 4 times the optimal storage immediately following an elasticarray_shrink + * call; but only if realloc(3) failed to shrink a memory allocation. + */ +void +elasticarray_shrink(struct elasticarray * EA, size_t nrec, size_t reclen) +{ + size_t nsize; + + /* Figure out how much to keep. */ + if ((nrec > SIZE_MAX / reclen) || + (nrec * reclen > EA->size)) + nsize = 0; + else + nsize = EA->size - nrec * reclen; + + /* Resize the buffer... */ + if (resize(EA, nsize)) { + /* + * ... and if we fail to reallocate, just record the new + * length and continue using the old buffer. + */ + EA->size = nsize; + } +} + +/** + * elasticarray_truncate(EA): + * Release any spare space in the elastic array ${EA}. + */ +int +elasticarray_truncate(struct elasticarray * EA) +{ + void * nbuf; + + /* If there is spare space, reallocate. */ + if (EA->alloc > EA->size) { + nbuf = realloc(EA->buf, EA->size); + if ((nbuf == NULL) && (EA->size > 0)) + goto err0; + EA->buf = nbuf; + EA->alloc = EA->size; + } + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * elasticarray_get(EA, pos, reclen): + * Return a pointer to record number ${pos} of length ${reclen} in the + * elastic array ${EA}. Takes O(1) time. + */ +void * +elasticarray_get(struct elasticarray * EA, size_t pos, size_t reclen) +{ + + return ((void *)((uintptr_t)(EA->buf) + pos * reclen)); +} + +/** + * elasticarray_free(EA): + * Free the elastic array ${EA}. Takes O(1) time. + */ +void +elasticarray_free(struct elasticarray * EA) +{ + + /* Be compatible with free(NULL). */ + if (EA == NULL) + return; + + free(EA->buf); + free(EA); +} diff --git a/lib/datastruct/elasticarray.h b/lib/datastruct/elasticarray.h new file mode 100644 index 0000000..e1cc222 --- /dev/null +++ b/lib/datastruct/elasticarray.h @@ -0,0 +1,153 @@ +#ifndef _ELASTICARRAY_H_ +#define _ELASTICARRAY_H_ + +#include + +/** + * Elastic Arrays are dynamically resizing arrays which remain within a + * factor of 4 of the optimal size for the data they contain and have (within + * a constant factor) amortized optimal running time providing that all of + * the allocated space is accessed at some point. Functions return NULL or + * (int)(-1) on error and set errno; other return types indicate that failure + * is not possible. On error, the array will be unmodified. + * + * The ELASTICARRAY_DECL(type, prefix, rectype) macro can be used to create a + * more friendly interface, at the expense of restricting the array to only + * holding a single data type. + */ + +/* Opaque elastic array type. */ +struct elasticarray; + +/** + * elasticarray_init(nrec, reclen): + * Create and return an elastic array holding ${nrec} (uninitialized) records + * of length ${reclen}. Takes O(nrec * reclen) time. + */ +struct elasticarray * elasticarray_init(size_t, size_t); + +/** + * elasticarray_resize(EA, nrec, reclen): + * Resize the elastic array pointed to by ${EA} to hold ${nrec} records of + * length ${reclen}. If ${nrec} exceeds the number of records previously + * held by the array, the additional records will be uninitialized. Takes + * O(nrec * reclen) time. + */ +int elasticarray_resize(struct elasticarray *, size_t, size_t); + +/** + * elasticarray_getsize(EA, reclen): + * Return the number of length-${reclen} records in the array, rounding down + * if there is a partial record (which can only occur if elasticarray_* + * functions have been called with different values of reclen). The value + * ${reclen} must be positive. + */ +size_t elasticarray_getsize(struct elasticarray *, size_t); + +/** + * elasticarray_append(EA, buf, nrec, reclen): + * Append to the elastic array ${EA} the ${nrec} records of length ${reclen} + * stored in ${buf}. Takes O(nrec * reclen) amortized time. + */ +int elasticarray_append(struct elasticarray *, const void *, size_t, size_t); + +/** + * elasticarray_shrink(EA, nrec, reclen): + * Delete the final ${nrec} records of length ${reclen} from the elastic + * array ${EA}. If there are fewer than ${nrec} records, all records + * present will be deleted. + * + * As an exception to the normal rule, an elastic array may occupy more than + * 4 times the optimal storage immediately following an elasticarray_shrink + * call; but only if realloc(3) failed to shrink a memory allocation. + */ +void elasticarray_shrink(struct elasticarray *, size_t, size_t); + +/** + * elasticarray_truncate(EA): + * Release any spare space in the elastic array ${EA}. + */ +int elasticarray_truncate(struct elasticarray *); + +/** + * elasticarray_get(EA, pos, reclen): + * Return a pointer to record number ${pos} of length ${reclen} in the + * elastic array ${EA}. Takes O(1) time. + */ +void * elasticarray_get(struct elasticarray *, size_t, size_t); + +/** + * elasticarray_free(EA): + * Free the elastic array ${EA}. Takes O(1) time. + */ +void elasticarray_free(struct elasticarray *); + +/** + * ELASTICARRAY_DECL(type, prefix, rectype): + * Declare the type ${type} and the following functions: + * ${type} ${prefix}_init(size_t nrec); + * int ${prefix}_resize(${type} EA, size_t nrec); + * size_t ${prefix}_getsize(${type} EA); + * int ${prefix}_append(${type} EA, const void * buf, size_t nrec); + * void ${prefix}_shrink(${type} EA, size_t nrec); + * int ${prefix}_truncate(${type} EA); + * ${rectype} * ${prefix}_get(${type} EA, size_t pos); + * void ${prefix}_free(${type} EA); + */ +#define ELASTICARRAY_DECL(type, prefix, rectype) \ + static inline struct prefix##_struct * \ + prefix##_init(size_t nrec) \ + { \ + struct elasticarray * EA; \ + \ + EA = elasticarray_init(nrec, sizeof(rectype)); \ + return ((struct prefix##_struct *)EA); \ + } \ + static inline int \ + prefix##_resize(struct prefix##_struct * EA, size_t nrec) \ + { \ + return (elasticarray_resize((struct elasticarray *)EA, \ + nrec, sizeof(rectype))); \ + } \ + static inline size_t \ + prefix##_getsize(struct prefix##_struct * EA) \ + { \ + return (elasticarray_getsize((struct elasticarray *)EA, \ + sizeof(rectype))); \ + } \ + static inline int \ + prefix##_append(struct prefix##_struct * EA, \ + rectype const * buf, size_t nrec) \ + { \ + return (elasticarray_append((struct elasticarray *)EA, \ + buf, nrec, sizeof(rectype))); \ + } \ + static inline void \ + prefix##_shrink(struct prefix##_struct * EA, size_t nrec) \ + { \ + elasticarray_shrink((struct elasticarray *)EA, \ + nrec, sizeof(rectype)); \ + } \ + static inline int \ + prefix##_truncate(struct prefix##_struct * EA) \ + { \ + return (elasticarray_truncate( \ + (struct elasticarray *)EA)); \ + } \ + static inline rectype * \ + prefix##_get(struct prefix##_struct * EA, size_t pos) \ + { \ + rectype * rec; \ + \ + rec = elasticarray_get((struct elasticarray *)EA, \ + pos, sizeof(rectype)); \ + return (rec); \ + } \ + static inline void \ + prefix##_free(struct prefix##_struct * EA) \ + { \ + elasticarray_free((struct elasticarray *)EA); \ + } \ + typedef struct prefix##_struct * type + +#endif /* !_ELASTICARRAY_H_ */ diff --git a/lib/datastruct/mpool.h b/lib/datastruct/mpool.h new file mode 100644 index 0000000..0935477 --- /dev/null +++ b/lib/datastruct/mpool.h @@ -0,0 +1,85 @@ +#ifndef _MPOOL_H_ +#define _MPOOL_H_ + +#include + +/** + * Memory allocator cache. Memory allocations can be returned to the pool + * and reused by a subsequent allocation without returning all the way to + * free/malloc. In effect, this is an optimization for the case where we + * know we will want another allocation of the same size soon, at the expense + * of allowing the memory to be reused by some other code. + */ + +/** + * MPOOL(name, type, size): + * Define the functions + * + * ${type} * mpool_${name}_malloc(void); + * void mpool_${name}_free(${type} *); + * + * which allocate and free structures of type ${type}. Up to ${size} + * such structures are kept cached after _free is called in order to + * allow future _malloc calls to be rapidly serviced. + * + * Cached structures will be freed at program exit time in order to aid + * in the detection of memory leaks. + */ +#define MPOOL(name, type, size) \ +static struct mpool_##name##_struct { \ + size_t stacklen; \ + void * top; \ + int atexit_set; \ +} mpool_##name##_rec = {0, NULL, 0}; \ + \ +static void \ +mpool_##name##_atexit(void) \ +{ \ + void * top; \ + \ + while ((top = mpool_##name##_rec.top) != NULL) { \ + mpool_##name##_rec.top = *(void **)top; \ + free(top); \ + } \ +} \ + \ +static inline type * \ +mpool_##name##_malloc(void) \ +{ \ + type * p; \ + \ + if (mpool_##name##_rec.stacklen) { \ + p = mpool_##name##_rec.top; \ + mpool_##name##_rec.top = *(void **)p; \ + mpool_##name##_rec.stacklen -= 1; \ + } else { \ + if (mpool_##name##_rec.atexit_set == 0) { \ + atexit(mpool_##name##_atexit); \ + mpool_##name##_rec.atexit_set = 1; \ + } \ + p = malloc((sizeof(type) > sizeof(void *)) ? \ + sizeof(type) : sizeof(void *)); \ + } \ + \ + return (p); \ +} \ + \ +static inline void \ +mpool_##name##_free(type * p) \ +{ \ + \ + if (p == NULL) \ + return; \ + \ + if (mpool_##name##_rec.stacklen < size) { \ + *(void **)p = mpool_##name##_rec.top; \ + mpool_##name##_rec.top = p; \ + mpool_##name##_rec.stacklen += 1; \ + } else { \ + free(p); \ + } \ +} \ + \ +struct mpool_##name##_dummy + +#endif /* !_MPOOL_H_ */ diff --git a/lib/datastruct/ptrheap.c b/lib/datastruct/ptrheap.c new file mode 100644 index 0000000..3efa580 --- /dev/null +++ b/lib/datastruct/ptrheap.c @@ -0,0 +1,334 @@ +#include + +#include "elasticarray.h" + +#include "ptrheap.h" + +ELASTICARRAY_DECL(PTRLIST, ptrlist, void *); + +struct ptrheap { + int (* compar)(void *, const void *, const void *); + void (* setreccookie)(void *, void *, size_t); + void * cookie; + PTRLIST elems; + size_t nelems; +}; + +/** + * swap(elems, i, j, setreccookie, cookie): + * Swap elements ${i} and ${j} in ${elems}. If ${setreccookie} is non-NULL, + * invoke ${setreccookie}(${cookie}, elem, pos) for each of the elements and + * their new positions in the tree. + */ +static void +swap(PTRLIST elems, size_t i, size_t j, + void (* setreccookie)(void *, void *, size_t), void * cookie) +{ + void * tmp; + + /* Swap the elements. */ + tmp = *ptrlist_get(elems, i); + *ptrlist_get(elems, i) = *ptrlist_get(elems, j); + *ptrlist_get(elems, j) = tmp; + + /* Notify about the moved elements. */ + if (setreccookie != NULL) { + setreccookie(cookie, *ptrlist_get(elems, i), i); + setreccookie(cookie, *ptrlist_get(elems, j), j); + } +} + +/** + * heapifyup(elems, i, compar, setreccookie, cookie): + * Sift up element ${i} of the elements ${elems}, using the comparison + * function ${compar} and the cookie ${cookie}. If elements move and + * ${setreccookie} is non-NULL, use it to notify about the updated position + * of elements in the heap. + */ +static void +heapifyup(PTRLIST elems, size_t i, + int (* compar)(void *, const void *, const void *), + void (* setreccookie)(void *, void *, size_t), void * cookie) +{ + + /* Iterate up the tree. */ + do { + /* If we're at the root, we have nothing to do. */ + if (i == 0) + break; + + /* If this is >= its parent, we're done. */ + if (compar(cookie, *ptrlist_get(elems, i), + *ptrlist_get(elems, (i - 1) / 2)) >= 0) + break; + + /* Swap with the parent. */ + swap(elems, i, (i - 1) / 2, setreccookie, cookie); + + /* Move up the tree. */ + i = (i - 1) / 2; + } while (1); +} + +/** + * heapify(elems, i, N, compar, setreccookie, cookie): + * Sift down element number ${i} out of ${N} of the elements ${elems}, using + * the comparison function ${compar} and the cookie ${cookie}. If elements + * move and ${setreccookie} is non-NULL, use it to notify about the updated + * position of elements in the heap. + */ +static void +heapify(PTRLIST elems, size_t i, size_t N, + int (* compar)(void *, const void *, const void *), + void (* setreccookie)(void *, void *, size_t), void * cookie) +{ + size_t min; + + /* Iterate down the tree. */ + do { + /* Look for the minimum element out of {i, 2i+1, 2i+2}. */ + min = i; + + /* Is this bigger than element 2i+1? */ + if ((2 * i + 1 < N) && + (compar(cookie, *ptrlist_get(elems, min), + *ptrlist_get(elems, 2 * i + 1)) > 0)) + min = 2 * i + 1; + + /* Is this bigger than element 2i+2? */ + if ((2 * i + 2 < N) && + (compar(cookie, *ptrlist_get(elems, min), + *ptrlist_get(elems, 2 * i + 2)) > 0)) + min = 2 * i + 2; + + /* If the minimum is i, we have heap-property. */ + if (min == i) + break; + + /* Move the minimum into position i. */ + swap(elems, min, i, setreccookie, cookie); + + /* Move down the tree. */ + i = min; + } while (1); +} + +/** + * ptrheap_init(compar, setreccookie, cookie): + * Create and return an empty heap. The function ${compar}(${cookie}, x, y) + * should return less than, equal to, or greater than 0 depending on whether + * x is less than, equal to, or greater than y; and if ${setreccookie} is + * non-zero it will be called as ${setreccookie}(${cookie}, ${ptr}, ${rc}) to + * indicate that the value ${rc} is the current record cookie for the pointer + * ${ptr}. The function ${setreccookie} may not make any ptrheap_* calls. + */ +struct ptrheap * +ptrheap_init(int (* compar)(void *, const void *, const void *), + void (* setreccookie)(void *, void *, size_t), void * cookie) +{ + + /* Let ptrheap_create handle this. */ + return (ptrheap_create(compar, setreccookie, cookie, 0, NULL)); +} + +/** + * ptrheap_create(compar, setreccookie, cookie, N, ptrs): + * Create and return a heap, as in ptrheap_init, but with the ${N} pointers + * in ${ptrs} as heap elements. This is faster than creating an empty heap + * and adding the elements individually. + */ +struct ptrheap * +ptrheap_create(int (* compar)(void *, const void *, const void *), + void (* setreccookie)(void *, void *, size_t), void * cookie, + size_t N, void ** ptrs) +{ + struct ptrheap * H; + size_t i; + + /* Allocate structure. */ + if ((H = malloc(sizeof(struct ptrheap))) == NULL) + goto err0; + + /* Store parameters. */ + H->compar = compar; + H->setreccookie = setreccookie; + H->cookie = cookie; + + /* We will have N elements. */ + H->nelems = N; + + /* Allocate space for N heap elements. */ + if ((H->elems = ptrlist_init(N)) == NULL) + goto err1; + + /* Copy the heap elements in. */ + for (i = 0; i < N; i++) + *ptrlist_get(H->elems, i) = ptrs[i]; + + /* Turn this into a heap. */ + for (i = N - 1; i < N; i--) + heapify(H->elems, i, N, H->compar, NULL, H->cookie); + + /* Advise the caller about the record cookies. */ + if (H->setreccookie != NULL) + for (i = 0; i < N; i++) + (H->setreccookie)(H->cookie, + *ptrlist_get(H->elems, i), i); + + /* Success! */ + return (H); + +err1: + free(H); +err0: + /* Failure! */ + return (NULL); +} + +/** + * ptrheap_add(H, ptr): + * Add the pointer ${ptr} to the heap ${H}. + */ +int +ptrheap_add(struct ptrheap * H, void * ptr) +{ + + /* Add the element to the end of the heap. */ + if (ptrlist_append(H->elems, &ptr, 1)) + goto err0; + H->nelems += 1; + + /* Advise the caller about the current location of this record. */ + if (H->setreccookie != NULL) + (H->setreccookie)(H->cookie, ptr, H->nelems - 1); + + /* Move the new element up in the tree if necessary. */ + heapifyup(H->elems, H->nelems - 1, + H->compar, H->setreccookie, H->cookie); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * ptrheap_getmin(H): + * Return the minimum pointer in the heap ${H}. If the heap is empty, NULL + * is returned. + */ +void * +ptrheap_getmin(struct ptrheap * H) +{ + + /* If we have any elements, the minimum is in position 0. */ + if (H->nelems) + return (*ptrlist_get(H->elems, 0)); + else + return (NULL); +} + +/** + * ptrheap_delete(H, rc): + * Delete from the heap ${H} the element ptr for which the function call + * setreccookie(cookie, ptr, ${rc}) was most recently made. + */ +void +ptrheap_delete(struct ptrheap * H, size_t rc) +{ + + /* + * If the element we're deleting is not at the end of the heap, + * replace it with the element which is currently at the end. + */ + if (rc != H->nelems - 1) { + /* Move ptr from position H->nelems - 1 into position rc. */ + *ptrlist_get(H->elems, rc) = + *ptrlist_get(H->elems, H->nelems - 1); + if (H->setreccookie != NULL) + (H->setreccookie)(H->cookie, + *ptrlist_get(H->elems, rc), rc); + + /* Is this too small to be in position ${rc}? */ + if ((rc > 0) && + (H->compar(H->cookie, *ptrlist_get(H->elems, rc), + *ptrlist_get(H->elems, (rc - 1) / 2)) < 0)) { + /* Swap with the parent, and keep moving up. */ + swap(H->elems, rc, (rc - 1) / 2, + H->setreccookie, H->cookie); + heapifyup(H->elems, (rc - 1) / 2, + H->compar, H->setreccookie, H->cookie); + } else { + /* Maybe we need to move it down instead? */ + heapify(H->elems, rc, H->nelems, + H->compar, H->setreccookie, H->cookie); + } + } + + /* + * We've got everything we want to keep in positions 0 .. nelems - 2, + * and we have heap-nature, so all we need to do is strip off the + * final pointer. + */ + ptrlist_shrink(H->elems, 1); + H->nelems--; +} + +/** + * ptrheap_deletemin(H): + * Delete the minimum element in the heap ${H}. + */ +void +ptrheap_deletemin(struct ptrheap * H) +{ + + /* Let ptrheap_delete handle this. */ + ptrheap_delete(H, 0); +} + +/** + * ptrheap_increase(H, rc): + * Adjust the heap ${H} to account for the fact that the element ptr for + * which the function call setreccookie(cookie, ptr, ${rc}) was most recently + * made has incrased. + */ +void +ptrheap_increase(struct ptrheap * H, size_t rc) +{ + + /* Move the element down if necessary. */ + heapify(H->elems, rc, H->nelems, + H->compar, H->setreccookie, H->cookie); +} + +/** + * ptrheap_increasemin(H): + * Adjust the heap ${H} to account for the fact that the (formerly) minimum + * element has increased. + */ +void +ptrheap_increasemin(struct ptrheap * H) +{ + + /* Move the element down if necessary. */ + heapify(H->elems, 0, H->nelems, + H->compar, H->setreccookie, H->cookie); +} + +/** + * ptrheap_free(H): + * Free the pointer heap ${H}. + */ +void +ptrheap_free(struct ptrheap * H) +{ + + /* Be compatible with free(NULL). */ + if (H == NULL) + return; + + ptrlist_free(H->elems); + free(H); +} diff --git a/lib/datastruct/ptrheap.h b/lib/datastruct/ptrheap.h new file mode 100644 index 0000000..5a0ad41 --- /dev/null +++ b/lib/datastruct/ptrheap.h @@ -0,0 +1,88 @@ +#ifndef _PTRHEAP_H_ +#define _PTRHEAP_H_ + +#include + +/** + * Pointer-heap data structure. Arbitrary pointers can be inserted and are + * compared using a provided callback; the usual heapy getmin / increasemin / + * deletemin algorithms are supported. In addition, a setreccookie callback + * can also be provided, in which case the most recent cookie provided for a + * heap element can be used to delete or increase that element. Functions + * return NULL or (int)(-1) on error and set errno; other return types + * indicate that failure is not possible. On error, the heap will be + * unmodified. + */ + +/* Opaque pointer-heap type. */ +struct ptrheap; + +/** + * ptrheap_init(compar, setreccookie, cookie): + * Create and return an empty heap. The function ${compar}(${cookie}, x, y) + * should return less than, equal to, or greater than 0 depending on whether + * x is less than, equal to, or greater than y; and if ${setreccookie} is + * non-zero it will be called as ${setreccookie}(${cookie}, ${ptr}, ${rc}) to + * indicate that the value ${rc} is the current record cookie for the pointer + * ${ptr}. The function ${setreccookie} may not make any ptrheap_* calls. + */ +struct ptrheap * ptrheap_init(int (*)(void *, const void *, const void *), + void (*)(void *, void *, size_t), void *); + +/** + * ptrheap_create(compar, setreccookie, cookie, N, ptrs): + * Create and return a heap, as in ptrheap_init, but with the ${N} pointers + * in ${ptrs} as heap elements. This is faster than creating an empty heap + * and adding the elements individually. + */ +struct ptrheap * ptrheap_create(int (*)(void *, const void *, const void *), + void (*)(void *, void *, size_t), void *, size_t, void **); + +/** + * ptrheap_add(H, ptr): + * Add the pointer ${ptr} to the heap ${H}. + */ +int ptrheap_add(struct ptrheap *, void *); + +/** + * ptrheap_getmin(H): + * Return the minimum pointer in the heap ${H}. If the heap is empty, NULL + * is returned. + */ +void * ptrheap_getmin(struct ptrheap *); + +/** + * ptrheap_delete(H, rc): + * Delete from the heap ${H} the element ptr for which the function call + * setreccookie(cookie, ptr, ${rc}) was most recently made. + */ +void ptrheap_delete(struct ptrheap *, size_t); + +/** + * ptrheap_deletemin(H): + * Delete the minimum element in the heap ${H}. + */ +void ptrheap_deletemin(struct ptrheap *); + +/** + * ptrheap_increase(H, rc): + * Adjust the heap ${H} to account for the fact that the element ptr for + * which the function call setreccookie(cookie, ptr, ${rc}) was most recently + * made has incrased. + */ +void ptrheap_increase(struct ptrheap *, size_t); + +/** + * ptrheap_increasemin(H): + * Adjust the heap ${H} to account for the fact that the (formerly) minimum + * element has increased. + */ +void ptrheap_increasemin(struct ptrheap *); + +/** + * ptrheap_free(H): + * Free the pointer heap ${H}. + */ +void ptrheap_free(struct ptrheap *); + +#endif /* !_PTRHEAP_H_ */ diff --git a/lib/datastruct/timerqueue.c b/lib/datastruct/timerqueue.c new file mode 100644 index 0000000..58bd703 --- /dev/null +++ b/lib/datastruct/timerqueue.c @@ -0,0 +1,241 @@ +#include + +#include +#include + +#include "ptrheap.h" + +#include "timerqueue.h" + +struct timerqueue { + struct ptrheap * H; +}; + +struct timerrec { + struct timeval tv; + size_t rc; + void * ptr; +}; + +/* Compare two timevals. */ +static int +tvcmp(const struct timeval * x, const struct timeval * y) +{ + + /* Does one have more seconds? */ + if (x->tv_sec > y->tv_sec) + return (1); + if (x->tv_sec < y->tv_sec) + return (-1); + + /* Does one have more microseconds? */ + if (x->tv_usec > y->tv_usec) + return (1); + if (x->tv_usec < y->tv_usec) + return (-1); + + /* They must be equal. */ + return (0); +} + +/* Record-comparison callback from ptrheap. */ +static int +compar(void * cookie, const void * x, const void * y) +{ + const struct timerrec *_x = x; + const struct timerrec *_y = y; + + (void)cookie; /* UNUSED */ + + return (tvcmp(&_x->tv, &_y->tv)); +} + +/* Cookie-recording callback from ptrheap. */ +static void +setreccookie(void * cookie, void * ptr, size_t rc) +{ + struct timerrec * rec = ptr; + + (void)cookie; /* UNUSED */ + + rec->rc = rc; +} + +/** + * timerqueue_init(void): + * Create and return an empty timer priority queue. + */ +struct timerqueue * +timerqueue_init(void) +{ + struct timerqueue * Q; + + /* Allocate structure. */ + if ((Q = malloc(sizeof(struct timerqueue))) == NULL) + goto err0; + + /* Allocate heap. */ + if ((Q->H = ptrheap_init(compar, setreccookie, Q)) == NULL) + goto err1; + + /* Success! */ + return (Q); + +err1: + free(Q); +err0: + /* Failure! */ + return (NULL); +} + +/** + * timerqueue_add(Q, tv, ptr): + * Add the pair (${tv}, ${ptr}) to the priority queue ${Q}. Returns a cookie + * which can be passed to timerqueue_delete or timerqueue_increase. + */ +void * +timerqueue_add(struct timerqueue * Q, const struct timeval * tv, void * ptr) +{ + struct timerrec * r; + + /* Allocate (timeval, ptr) pair record. */ + if ((r = malloc(sizeof(struct timerrec))) == NULL) + goto err0; + + /* Fill in values. */ + memcpy(&r->tv, tv, sizeof(struct timeval)); + r->ptr = ptr; + + /* + * Add the record to the heap. The value r->rc will be filled in + * by setreccookie which will be called by ptrheap_add. + */ + if (ptrheap_add(Q->H, r)) + goto err1; + + /* Success! */ + return (r); + +err1: + free(r); +err0: + /* Failure! */ + return (NULL); +} + +/** + * timerqueue_delete(Q, cookie): + * Delete the (timeval, ptr) pair associated with the cookie ${cookie} from + * the priority queue ${Q}. + */ +void +timerqueue_delete(struct timerqueue * Q, void * cookie) +{ + struct timerrec * r = cookie; + + /* Remove the record from the heap. */ + ptrheap_delete(Q->H, r->rc); + + /* Free the record. */ + free(r); +} + +/** + * timerqueue_increase(Q, cookie, tv): + * Increase the timer associated with the cookie ${cookie} in the priority + * queue ${Q} to ${tv}. + */ +void +timerqueue_increase(struct timerqueue * Q, void * cookie, + const struct timeval * tv) +{ + struct timerrec * r = cookie; + + /* Adjust timer value. */ + memcpy(&r->tv, tv, sizeof(struct timeval)); + + /* Inform the heap that the record value has increased. */ + ptrheap_increase(Q->H, r->rc); +} + +/** + * timerqueue_getmin(Q): + * Return a pointer to the least timeval in ${Q}, or NULL if the priority + * queue is empty. The pointer will remain valid until the next call to a + * timerqueue_* function. This function cannot fail. + */ +const struct timeval * +timerqueue_getmin(struct timerqueue * Q) +{ + struct timerrec * r; + + /* Get the minimum element from the heap. */ + r = ptrheap_getmin(Q->H); + + /* If we have an element, return its timeval; otherwise, NULL. */ + if (r != NULL) + return (&r->tv); + else + return (NULL); +} + +/** + * timerqueue_getptr(Q, tv): + * If the least timeval in ${Q} is less than or equal to ${tv}, return the + * associated pointer and remove the pair from the priority queue. If not, + * return NULL. + */ +void * +timerqueue_getptr(struct timerqueue * Q, const struct timeval * tv) +{ + struct timerrec * r; + void * ptr; + + /* + * Get the minimum element from the heap. Return NULL if the heap + * has no minimum element (i.e., is empty). + */ + if ((r = ptrheap_getmin(Q->H)) == NULL) + return (NULL); + + /* If the minimum timeval is greater than ${tv}, return NULL. */ + if (tvcmp(&r->tv, tv) > 0) + return (NULL); + + /* Remove this record from the heap. */ + ptrheap_deletemin(Q->H); + + /* Extract its pointer. */ + ptr = r->ptr; + + /* Free the record. */ + free(r); + + /* + * And finally return the pointer which was associated with the + * (formerly) minimum timeval in the heap. + */ + return (ptr); +} + +/** + * timerqueue_free(Q): + * Free the timer priority queue ${Q}. + */ +void +timerqueue_free(struct timerqueue * Q) +{ + struct timerrec * r; + + /* Extract elements from the heap and free them one by one. */ + while ((r = ptrheap_getmin(Q->H)) != NULL) { + free(r); + ptrheap_deletemin(Q->H); + } + + /* Free the heap. */ + ptrheap_free(Q->H); + + /* Free the timer priority queue structure. */ + free(Q); +} diff --git a/lib/datastruct/timerqueue.h b/lib/datastruct/timerqueue.h new file mode 100644 index 0000000..0bdd5be --- /dev/null +++ b/lib/datastruct/timerqueue.h @@ -0,0 +1,60 @@ +#ifndef _TIMERQUEUE_H_ +#define _TIMERQUEUE_H_ + +#include + +/* Timer priority queue. Contains (timeval, ptr) pairs. */ + +/* Opaque timer priority queue type. */ +struct timerqueue; + +/** + * timerqueue_init(void): + * Create and return an empty timer priority queue. + */ +struct timerqueue * timerqueue_init(void); + +/** + * timerqueue_add(Q, tv, ptr): + * Add the pair (${tv}, ${ptr}) to the priority queue ${Q}. Returns a cookie + * which can be passed to timerqueue_delete or timerqueue_increase. + */ +void * timerqueue_add(struct timerqueue *, const struct timeval *, void *); + +/** + * timerqueue_delete(Q, cookie): + * Delete the (timeval, ptr) pair associated with the cookie ${cookie} from + * the priority queue ${Q}. + */ +void timerqueue_delete(struct timerqueue *, void *); + +/** + * timerqueue_increase(Q, cookie, tv): + * Increase the timer associated with the cookie ${cookie} in the priority + * queue ${Q} to ${tv}. + */ +void timerqueue_increase(struct timerqueue *, void *, const struct timeval *); + +/** + * timerqueue_getmin(Q): + * Return a pointer to the least timeval in ${Q}, or NULL if the priority + * queue is empty. The pointer will remain valid until the next call to a + * timerqueue_* function. This function cannot fail. + */ +const struct timeval * timerqueue_getmin(struct timerqueue *); + +/** + * timerqueue_getptr(Q, tv): + * If the least timeval in ${Q} is less than or equal to ${tv}, return the + * associated pointer and remove the pair from the priority queue. If not, + * return NULL. This function cannot fail. + */ +void * timerqueue_getptr(struct timerqueue *, const struct timeval *); + +/** + * timerqueue_free(Q): + * Free the timer priority queue ${Q}. + */ +void timerqueue_free(struct timerqueue *); + +#endif /* !_TIMERQUEUE_H_ */ diff --git a/lib/dnsthread/dnsthread.c b/lib/dnsthread/dnsthread.c new file mode 100644 index 0000000..15d365d --- /dev/null +++ b/lib/dnsthread/dnsthread.c @@ -0,0 +1,464 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "events.h" +#include "noeintr.h" +#include "sock.h" +#include "warnp.h" + +#include "dnsthread.h" + +/* Thread management structure. */ +struct dnsthread_internal { + /* Threading glue. */ + pthread_t thr; /* Thread ID. */ + pthread_mutex_t mtx; /* Controls access to this structure. */ + pthread_cond_t cv; /* Thread sleeps on this. */ + + /* State management. */ + int state; /* THREAD_* as below. */ + int wakeupsock[2]; /* Writes to [0], reads from [1]. */ + + /* Address resolution variables. */ + char * addr; /* Address to be resolved. */ + struct sock_addr ** sas; /* Results. */ + int res_errno; /* Errno to be passed back on failure. */ + + /* Callback to occur when resolution is complete. */ + int (*callback)(void *, struct sock_addr **); /* Callback. */ + void * cookie; /* Cookie. */ +}; + +/* Cookie used by dnsthread_resolve. */ +struct resolve_cookie { + int (*callback)(void *, struct sock_addr **); + void * cookie; + DNSTHREAD T; +}; + +/* + * Thread states. _resolveone moves the thread from SLEEPING to HASWORK; + * workthread moves the thread from HASWORK to SLEEPING; and _kill moves the + * thread from either state to SUICIDE. + */ +#define THREAD_SLEEPING 0 +#define THREAD_HASWORK 1 +#define THREAD_SUICIDE 2 + +/* Callback functions used below. */ +static int callback_resolveone(void * cookie); +static int callback_resolve(void *, struct sock_addr **); + +/* Address resolution thread. */ +static void * +workthread(void * cookie) +{ + struct dnsthread_internal * T = cookie; + char * addr; + struct sock_addr ** sas; + int res_errno = 0; + int rc; + uint8_t zero = 0; + + /* Grab the mutex. */ + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + exit(1); + } + + /* Infinite loop doing work until told to suicide. */ + do { + /* + * Sleep on the condition variable as long as we're in the + * SLEEPING state. + */ + while (T->state == THREAD_SLEEPING) { + /* Sleep until we're woken up. */ + if ((rc = pthread_cond_wait(&T->cv, &T->mtx)) != 0) { + warn0("pthread_cond_wait: %s", strerror(rc)); + exit(1); + } + } + + /* If we need to kill ourself, stop looping. */ + if (T->state == THREAD_SUICIDE) + break; + + /* Grab the work. */ + addr = T->addr; + + /* Release the mutex. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + exit(1); + } + + /* Perform the address resolution. */ + if ((sas = sock_resolve(addr)) == NULL) + res_errno = errno; + + /* Grab the mutex again. */ + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + exit(1); + } + + /* Write the answer back. */ + T->sas = sas; + T->res_errno = res_errno; + + /* Send a completion message. */ + if (noeintr_write(T->wakeupsock[0], &zero, 1) != 1) { + warnp("Error writing to wakeup socket"); + exit(1); + } + + /* Return to sleeping, unless we were instructed to die. */ + if (T->state != THREAD_SUICIDE) + T->state = THREAD_SLEEPING; + } while (1); + + /* Close the socket pair. */ + close(T->wakeupsock[1]); + close(T->wakeupsock[0]); + + /* Destroy the condition variable. */ + if ((rc = pthread_cond_destroy(&T->cv)) != 0) { + warn0("pthread_cond_destroy: %s", strerror(rc)); + exit(1); + } + + /* Release the mutex. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + exit(1); + } + + /* Destroy the mutex. */ + if ((rc = pthread_mutex_destroy(&T->mtx)) != 0) { + warn0("pthread_mutex_destroy: %s", strerror(rc)); + exit(1); + } + + /* Free the control structure. */ + free(T); + + /* Successful thread termination. */ + return (NULL); +} + +/** + * dnsthread_spawn(void): + * Spawn a thread for performing address resolution. Return a token which can + * be passed to dnsthread_resolveone and dnsthread_kill. + */ +DNSTHREAD +dnsthread_spawn(void) +{ + struct dnsthread_internal * T; + int rc; + + /* Allocate a thread management structure. */ + if ((T = malloc(sizeof(struct dnsthread_internal))) == NULL) + goto err0; + + /* Create and lock a mutex. */ + if ((rc = pthread_mutex_init(&T->mtx, NULL)) != 0) { + warn0("pthread_mutex_init: %s", strerror(rc)); + goto err1; + } + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + goto err2; + } + + /* Create state-changed condition variable. */ + if ((rc = pthread_cond_init(&T->cv, NULL)) != 0) { + warn0("pthread_cond_init: %s", strerror(rc)); + goto err3; + } + + /* Create wakeup socketpair. */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, T->wakeupsock)) { + warnp("socketpair"); + goto err4; + } + + /* The thread starts out sleeping. */ + T->state = THREAD_SLEEPING; + + /* Create the thread. */ + if ((rc = pthread_create(&T->thr, NULL, workthread, T)) != 0) { + warn0("pthread_create: %s", strerror(rc)); + goto err5; + } + + /* Unlock the mutex. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + goto err0; + } + + /* Success! */ + return (T); + +err5: + close(T->wakeupsock[1]); + close(T->wakeupsock[0]); +err4: + pthread_cond_destroy(&T->cv); +err3: + pthread_mutex_unlock(&T->mtx); +err2: + pthread_mutex_destroy(&T->mtx); +err1: + free(T); +err0: + /* Failure! */ + return (NULL); +} + +/** + * dnsthread_resolveone(T, addr, callback, cookie): + * Using the thread for which ${T} was returned by dnsthread_spawn, resolve + * the address ${addr}, which must be in one of the forms accepted by the + * sock_resolve function. If ${T} is already resolving an address, do not + * resolve this address and instead return with errno == EALREADY. Upon + * completion, invoke ${callback}(${cookie}, sas), where ${sas} is a + * NULL-terminated array of pointers to sock_addr structures or NULL on + * resolution failure. + */ +int +dnsthread_resolveone(DNSTHREAD T, const char * addr, + int (* callback)(void *, struct sock_addr **), void * cookie) +{ + int err = 0; + int rc; + + /* Grab the mutex. */ + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + goto err0; + } + + /* If the resolver is already busy, fail. */ + if (T->state == THREAD_HASWORK) { + err = EALREADY; + goto ealready; + } + + /* Duplicate the address to be resolved. */ + if ((T->addr = strdup(addr)) == NULL) + goto err1; + + /* Remember what callback we'll need to do eventually. */ + T->callback = callback; + T->cookie = cookie; + + /* There is now work for the thread to do. */ + T->state = THREAD_HASWORK; + + /* Wake up the worker thread. */ + if ((rc = pthread_cond_signal(&T->cv)) != 0) { + warn0("pthread_cond_signal: %s", strerror(rc)); + goto err1; + } + + /* We want a callback when the worker thread pokes us. */ + if (events_network_register(callback_resolveone, T, T->wakeupsock[1], + EVENTS_NETWORK_OP_READ)) { + warnp("Error registering wakeup listener"); + goto err1; + } + +ealready: + /* Release the mutex. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + goto err0; + } + + /* If err was set earlier, store the value in errno. */ + if (err) + errno = err; + + /* Success! */ + return (0); + +err1: + pthread_mutex_unlock(&T->mtx); +err0: + /* Failure! */ + return (-1); +} + +/* Callback for dnsthread_resolveone, from wakeup socket. */ +static int +callback_resolveone(void * cookie) +{ + struct dnsthread_internal * T = cookie; + struct sock_addr ** sas; + uint8_t zero; + int res_errno = 0; + int (*callback)(void *, struct sock_addr **); + void * cb_cookie; + int rc; + + /* Drain the byte from the socketpair. */ + if (read(T->wakeupsock[1], &zero, 1) != 1) { + warn0("Error reading from wakeup socket"); + goto err0; + } + + /* Grab the mutex. */ + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + goto err0; + } + + /* Free the (strduped) address which was to be resolved. */ + free(T->addr); + + /* Grab the result. */ + sas = T->sas; + res_errno = T->res_errno; + + /* Grab the callback. */ + callback = T->callback; + cb_cookie = T->cookie; + + /* Release the mutex. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + goto err0; + } + + /* Perform the callback. */ + if (sas == NULL) + errno = res_errno; + return ((callback)(cb_cookie, sas)); + +err0: + /* Failure! */ + return (-1); +} + +/** + * dnsthread_kill(T): + * Instruct an address resolution thread to die. If the thread does not have + * an address resolution operation currently pending, wait for the thread to + * die before returning. + */ +int +dnsthread_kill(DNSTHREAD T) +{ + int rc; + int ostate; + pthread_t thr; + + /* Lock the control structure. */ + if ((rc = pthread_mutex_lock(&T->mtx)) != 0) { + warn0("pthread_mutex_lock: %s", strerror(rc)); + goto err0; + } + + /* Remember what state the thread is currently in. */ + ostate = T->state; + + /* Grab the thread ID. */ + thr = T->thr; + + /* Tell the thread to die, and wake it up. */ + T->state = THREAD_SUICIDE; + if ((rc = pthread_cond_signal(&T->cv)) != 0) { + warn0("pthread_cond_signal: %s", strerror(rc)); + goto err1; + } + + /* Unlock the control structure. */ + if ((rc = pthread_mutex_unlock(&T->mtx)) != 0) { + warn0("pthread_mutex_unlock: %s", strerror(rc)); + goto err0; + } + + /* If the thread was sleeping, wait for it to wake up and die. */ + if (ostate == THREAD_SLEEPING) { + if ((rc = pthread_join(thr, NULL)) != 0) { + warn0("pthread_join: %s", strerror(rc)); + goto err0; + } + } + + /* Success! */ + return (0); + +err1: + pthread_mutex_unlock(&T->mtx); +err0: + /* Failure! */ + return (-1); +} + +/** + * dnsthread_resolve(addr, callback, cookie): + * Perform a non-blocking address resolution of ${addr}. This function may + * spawn a thread internally. + */ +int +dnsthread_resolve(const char * addr, + int (* callback)(void *, struct sock_addr **), void * cookie) +{ + struct resolve_cookie * R; + + /* Bake a cookie. */ + if ((R = malloc(sizeof(struct resolve_cookie))) == NULL) + goto err0; + R->callback = callback; + R->cookie = cookie; + + /* Spawn a thread. */ + if ((R->T = dnsthread_spawn()) == NULL) + goto err1; + + /* Launch the request. */ + if (dnsthread_resolveone(R->T, addr, callback_resolve, R)) + goto err2; + + /* Success! */ + return (0); + +err2: + dnsthread_kill(R->T); +err1: + free(R); +err0: + /* Failure! */ + return (-1); +} + +/* Callback for dnsthread_resolve, from dnsthread_resolveone. */ +static int +callback_resolve(void * cookie, struct sock_addr ** sas) +{ + struct resolve_cookie * R = cookie; + int rc; + + /* Invoke the upstream callback. */ + rc = (R->callback)(R->cookie, sas); + + /* Kill the resolver thread. */ + if (dnsthread_kill(R->T)) + rc = -1; + + /* Free our cookie. */ + free(R); + + /* Return upstream return code or failure. */ + return (rc); +} diff --git a/lib/dnsthread/dnsthread.h b/lib/dnsthread/dnsthread.h new file mode 100644 index 0000000..428b8b9 --- /dev/null +++ b/lib/dnsthread/dnsthread.h @@ -0,0 +1,45 @@ +#ifndef _DNSTHREAD_H_ +#define _DNSTHREAD_H_ + +/* Opaque address structure. */ +struct sock_addr; + +/* Opaque thread token. */ +typedef struct dnsthread_internal * DNSTHREAD; + +/** + * dnsthread_spawn(void): + * Spawn a thread for performing address resolution. Return a token which can + * be passed to dnsthread_resolveone and dnsthread_kill. + */ +DNSTHREAD dnsthread_spawn(void); + +/** + * dnsthread_resolveone(T, addr, callback, cookie): + * Using the thread for which ${T} was returned by dnsthread_spawn, resolve + * the address ${addr}, which must be in one of the forms accepted by the + * sock_resolve function. If ${T} is already resolving an address, fail with + * EALREADY. Upon completion, invoke ${callback}(${cookie}, sas), where + * ${sas} is a NULL-terminated array of pointers to sock_addr structures or + * NULL on resolution failure. + */ +int dnsthread_resolveone(DNSTHREAD, const char *, + int (*)(void *, struct sock_addr **), void *); + +/** + * dnsthread_kill(T): + * Instruct an address resolution thread to die. If the thread does not have + * an address resolution operation currently pending, wait for the thread to + * die before returning. + */ +int dnsthread_kill(DNSTHREAD); + +/** + * dnsthread_resolve(addr, callback, cookie): + * Perform a non-blocking address resolution of ${addr}. This function may + * spawn a thread internally. + */ +int dnsthread_resolve(const char *, + int (*)(void *, struct sock_addr **), void *); + +#endif /* !_DNSTHREAD_H_ */ diff --git a/lib/events/events.c b/lib/events/events.c new file mode 100644 index 0000000..1d22a32 --- /dev/null +++ b/lib/events/events.c @@ -0,0 +1,200 @@ +#include + +#include + +#include "mpool.h" + +#include "events_internal.h" +#include "events.h" + +/* Event structure. */ +struct eventrec { + int (*func)(void *); + void * cookie; +}; + +MPOOL(eventrec, struct eventrec, 4096); + +/* Zero timeval, for use with non-blocking event runs. */ +struct timeval tv_zero = {0, 0}; + +/** + * events_mkrec(func, cookie): + * Package ${func}, ${cookie} into a struct eventrec. + */ +struct eventrec * +events_mkrec(int (*func)(void *), void * cookie) +{ + struct eventrec * r; + + /* Allocate structure. */ + if ((r = mpool_eventrec_malloc()) == NULL) + goto err0; + + /* Initialize. */ + r->func = func; + r->cookie = cookie; + + /* Success! */ + return (r); + +err0: + /* Failure! */ + return (NULL); +} + +/** + * events_freerec(r): + * Free the eventrec ${r}. + */ +void +events_freerec(struct eventrec * r) +{ + + mpool_eventrec_free(r); +} + +/* Do an event. This makes events_run cleaner. */ +static inline int +doevent(struct eventrec * r) +{ + int rc; + + /* Invoke the callback. */ + rc = (r->func)(r->cookie); + + /* Free the event record. */ + mpool_eventrec_free(r); + + /* Return the status code from the callback. */ + return (rc); +} + +/** + * events_run(void): + * Run events. Events registered via events_immediate_register will be run + * first, in order of increasing ${prio} values; then events associated with + * ready sockets registered via events_network_register; finally, events + * associated with expired timers registered via events_timer_register will + * be run. If any event function returns a non-zero result, no further + * events will be run and said non-zero result will be returned; on error, + * -1 will be returned. + */ +int +events_run(void) +{ + struct eventrec * r; + struct timeval * tv; + int rc = 0; + + /* If we have any immediate events, process them and return. */ + if ((r = events_immediate_get()) != NULL) { + while (r != NULL) { + /* Process the event. */ + if ((rc = doevent(r)) != 0) + goto done; + + /* Get the next event. */ + r = events_immediate_get(); + } + + /* We've processed at least one event; time to return. */ + goto done; + } + + /* + * Figure out the maximum duration to block, and wait up to that + * duration for network events to become available. + */ + if (events_timer_min(&tv)) + goto err0; + if (events_network_select(tv)) + goto err1; + free(tv); + + /* + * Check for available immediate events, network events, and timer + * events, in that order of priority; exit only when no more events + * are available. + */ + do { + /* Run an immediate event, if one is available. */ + if ((r = events_immediate_get()) != NULL) { + if ((rc = doevent(r)) != 0) + goto done; + continue; + } + + /* Run a network event, if one is available. */ + if ((r = events_network_get()) != NULL) { + if ((rc = doevent(r)) != 0) + goto done; + continue; + } + + /* Check if any new network events are available. */ + if (events_network_select(&tv_zero)) + goto err0; + if ((r = events_network_get()) != NULL) { + if ((rc = doevent(r)) != 0) + goto done; + continue; + } + + /* Run a timer event, if one is available. */ + if (events_timer_get(&r)) + goto err0; + if (r != NULL) { + if ((rc = doevent(r)) != 0) + goto done; + continue; + } + + /* No events available. */ + break; + } while (1); + +done: + /* Success! */ + return (rc); + +err1: + free(tv); +err0: + /* Failure! */ + return (-1); +} + +/** + * events_spin(done): + * Run events until ${done} is non-zero (and return 0), an error occurs (and + * return -1), or a callback returns a non-zero status (and return the status + * code from the callback). + */ +int +events_spin(int * done) +{ + int rc = 0; + + /* Loop until we're done or have a non-zero status. */ + while ((done[0] == 0) && (rc == 0)) { + /* Run events. */ + rc = events_run(); + } + + /* Return status code. */ + return (rc); +} + +/** + * events_shutdown(void): + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void +events_shutdown(void) +{ + + events_network_shutdown(); + events_timer_shutdown(); +} diff --git a/lib/events/events.h b/lib/events/events.h new file mode 100644 index 0000000..374a466 --- /dev/null +++ b/lib/events/events.h @@ -0,0 +1,99 @@ +#ifndef _EVENTS_H_ +#define _EVENTS_H_ + +#include + +/** + * events_immediate_register(func, cookie, prio): + * Register ${func}(${cookie}) to be run the next time events_run is invoked, + * after immediate events with smaller ${prio} values and before events with + * larger ${prio} values. The value ${prio} must be in the range [0, 31]. + * Return a cookie which can be passed to events_immediate_cancel. + */ +void * events_immediate_register(int (*)(void *), void *, int); + +/** + * events_immediate_cancel(cookie): + * Cancel the immediate event for which the cookie ${cookie} was returned by + * events_immediate_register. + */ +void events_immediate_cancel(void *); + +/* "op" parameter to events_network_register. */ +#define EVENTS_NETWORK_OP_READ 0 +#define EVENTS_NETWORK_OP_WRITE 1 + +/** + * events_network_register(func, cookie, s, op): + * Register ${func}(${cookie}) to be run when socket ${s} is ready for + * reading or writing depending on whether ${op} is EVENTS_NETWORK_OP_READ or + * EVENTS_NETWORK_OP_WRITE. If there is already an event registration for + * this ${s}/${op} pair, errno will be set to EEXIST and the function will + * fail. + */ +int events_network_register(int (*)(void *), void *, int, int); + +/** + * events_network_cancel(s, op): + * Cancel the event registered for the socket/operation pair ${s}/${op}. If + * there is no such registration, errno will be set to ENOENT and the + * function will fail. + */ +int events_network_cancel(int, int); + +/** + * events_timer_register(func, cookie, timeo): + * Register ${func}(${cookie}) to be run ${timeo} in the future. Return a + * cookie which can be passed to events_timer_cancel or events_timer_reset. + */ +void * events_timer_register(int (*)(void *), void *, const struct timeval *); + +/** + * events_timer_register_double(func, cookie, timeo): + * As events_timer_register, but ${timeo} is a double-precision floating point + * value specifying a number of seconds. + */ +void * events_timer_register_double(int (*)(void *), void *, double); + +/** + * events_timer_cancel(cookie): + * Cancel the timer for which the cookie ${cookie} was returned by + * events_timer_register. + */ +void events_timer_cancel(void *); + +/** + * events_timer_reset(cookie): + * Reset the timer for which the cookie ${cookie} was returned by + * events_timer_register to its initial value. + */ +int events_timer_reset(void *); + +/** + * events_run(void): + * Run events. Events registered via events_immediate_register will be run + * first, in order of increasing ${prio} values; then events associated with + * ready sockets registered via events_network_register; finally, events + * associated with expired timers registered via events_timer_register will + * be run. If any event function returns a non-zero result, no further + * events will be run and said non-zero result will be returned; on error, + * -1 will be returned. + */ +int events_run(void); + +/** + * events_spin(done): + * Run events until ${done} is non-zero (and return 0), an error occurs (and + * return -1), or a callback returns a non-zero status (and return the status + * code from the callback). + */ +int events_spin(int *); + +/** + * events_shutdown(void): + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void events_shutdown(void); + +#endif /* !_EVENTS_H_ */ diff --git a/lib/events/events_immediate.c b/lib/events/events_immediate.c new file mode 100644 index 0000000..0b1348b --- /dev/null +++ b/lib/events/events_immediate.c @@ -0,0 +1,153 @@ +#include +#include +#include + +#include "mpool.h" + +#include "events_internal.h" +#include "events.h" + +struct eventq { + struct eventrec * r; + struct eventq * next; + struct eventq * prev; + int prio; +}; + +MPOOL(eventq, struct eventq, 4096); + +/* First nodes in the linked lists. */ +static struct eventq * heads[32] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +/* For non-NULL heads[i], tails[i] is the last node in the list. */ +static struct eventq * tails[32]; + +/* For i < minq, heads[i] == NULL. */ +static int minq = 0; + +/** + * events_immediate_register(func, cookie, prio): + * Register ${func}(${cookie}) to be run the next time events_run is invoked, + * after immediate events with smaller ${prio} values and before events with + * larger ${prio} values. The value ${prio} must be in the range [0, 31]. + * Return a cookie which can be passed to events_immediate_cancel. + */ +void * +events_immediate_register(int (*func)(void *), void * cookie, int prio) +{ + struct eventrec * r; + struct eventq * q; + + /* Sanity check. */ + assert((prio >= 0) && (prio < 32)); + + /* Bundle into an eventrec record. */ + if ((r = events_mkrec(func, cookie)) == NULL) + goto err0; + + /* Create a linked list node. */ + if ((q = mpool_eventq_malloc()) == NULL) + goto err1; + q->r = r; + q->next = NULL; + q->prev = NULL; + q->prio = prio; + + /* Add to the queue. */ + if (heads[prio] == NULL) { + heads[prio] = q; + if (prio < minq) + minq = prio; + } else { + tails[prio]->next = q; + q->prev = tails[prio]; + } + tails[prio] = q; + + /* Success! */ + return (q); + +err1: + events_freerec(r); +err0: + /* Failure! */ + return (NULL); +} + +/** + * events_immediate_cancel(cookie): + * Cancel the immediate event for which the cookie ${cookie} was returned by + * events_immediate_register. + */ +void +events_immediate_cancel(void * cookie) +{ + struct eventq * q = cookie; + int prio = q->prio; + + /* If we have a predecessor, point it at our successor. */ + if (q->prev != NULL) + q->prev->next = q->next; + else + heads[prio] = q->next; + + /* If we have a successor, point it at our predecessor. */ + if (q->next != NULL) + q->next->prev = q->prev; + else + tails[prio] = q->prev; + + /* Free the eventrec. */ + events_freerec(q->r); + + /* Return the node to the malloc pool. */ + mpool_eventq_free(q); +} + +/** + * events_immediate_get(void): + * Remove and return an eventrec structure from the immediate event queue, + * or return NULL if there are no such events. The caller is responsible for + * freeing the returned memory. + */ +struct eventrec * +events_immediate_get(void) +{ + struct eventq * q; + struct eventrec * r; + int prio; + + /* Scan through priorities until we find an event or run out. */ + for (prio = minq; prio < 32; prio++) { + /* Did we find an event? */ + if (heads[prio] != NULL) + break; + + /* This queue is empty; move on to the next one. */ + minq++; + } + + /* Are there any events? */ + if (prio == 32) + return (NULL); + + /* Remove the first node from the linked list. */ + q = heads[prio]; + heads[prio] = q->next; + if (heads[prio] != NULL) + heads[prio]->prev = NULL; + + /* Extract the eventrec. */ + r = q->r; + + /* Return the node to the malloc pool. */ + mpool_eventq_free(q); + + /* Return the eventrec. */ + return (r); +} diff --git a/lib/events/events_internal.h b/lib/events/events_internal.h new file mode 100644 index 0000000..ba6a6ff --- /dev/null +++ b/lib/events/events_internal.h @@ -0,0 +1,75 @@ +#ifndef _EVENTS_INTERNAL_H_ +#define _EVENTS_INTERNAL_H_ + +#include + +/* Opaque event structure. */ +struct eventrec; + +/** + * events_mkrec(func, cookie): + * Package ${func}, ${cookie} into a struct eventrec. + */ +struct eventrec * events_mkrec(int (*)(void *), void *); + +/** + * events_freerec(r): + * Free the eventrec ${r}. + */ +void events_freerec(struct eventrec *); + +/** + * events_immediate_get(void): + * Remove and return an eventrec structure from the immediate event queue, + * or return NULL if there are no such events. The caller is responsible for + * freeing the returned memory. + */ +struct eventrec * events_immediate_get(void); + +/** + * events_network_select(tv): + * Check for socket readiness events, waiting up to ${tv} time if there are + * no sockets immediately ready, or indefinitely if ${tv} is NULL. + */ +int events_network_select(struct timeval *); + +/** + * events_network_get(void): + * Find a socket readiness event which was identified by a previous call to + * events_network_select, and return it as an eventrec structure; or return + * NULL if there are no such events available. The caller is responsible for + * freeing the returned memory. + */ +struct eventrec * events_network_get(void); + +/** + * events_network_shutdown(void) + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void events_network_shutdown(void); + +/** + * events_timer_min(timeo): + * Return via ${timeo} a pointer to the minimum time which must be waited + * before a timer will expire; or to NULL if there are no timers. The caller + * is responsible for freeing the returned pointer. + */ +int events_timer_min(struct timeval **); + +/** + * events_timer_get(r): + * Return via ${r} a pointer to an eventrec structure corresponding to an + * expired timer, and delete said timer; or to NULL if there are no expired + * timers. The caller is responsible for freeing the returned pointer. + */ +int events_timer_get(struct eventrec **); + +/** + * events_timer_shutdown(void): + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void events_timer_shutdown(void); + +#endif /* !_EVENTS_INTERNAL_H_ */ diff --git a/lib/events/events_network.c b/lib/events/events_network.c new file mode 100644 index 0000000..a9d6a51 --- /dev/null +++ b/lib/events/events_network.c @@ -0,0 +1,319 @@ +#include + +#include +#include + +#include "elasticarray.h" +#include "warnp.h" + +#include "events_internal.h" +#include "events.h" + +/* Structure for holding readability and writability events for a socket. */ +struct socketrec { + struct eventrec * reader; + struct eventrec * writer; +}; + +/* List of sockets. */ +ELASTICARRAY_DECL(SOCKETLIST, socketlist, struct socketrec); +static SOCKETLIST S = NULL; + +/* File descriptor sets containing unevented ready sockets. */ +static fd_set readfds; +static fd_set writefds; + +/* Position to which events_network_get has scanned in *fds. */ +static size_t fdscanpos; + +/* Initialize the socket list if we haven't already done so. */ +static int +initsocketlist(void) +{ + + /* If we're already initialized, do nothing. */ + if (S != NULL) + goto done; + + /* Initialize the socket list. */ + if ((S = socketlist_init(0)) == NULL) + goto err0; + + /* There are no unevented ready sockets. */ + fdscanpos = FD_SETSIZE; + +done: + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/* Grow the socket list and initialize new records. */ +static int +growsocketlist(size_t nrec) +{ + size_t i; + + /* Get the old size. */ + i = socketlist_getsize(S); + + /* Grow the list. */ + if (socketlist_resize(S, nrec)) + goto err0; + + /* Initialize new members. */ + for (; i < nrec; i++) { + socketlist_get(S, i)->reader = NULL; + socketlist_get(S, i)->writer = NULL; + } + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_network_register(func, cookie, s, op): + * Register ${func}(${cookie}) to be run when socket ${s} is ready for + * reading or writing depending on whether ${op} is EVENTS_NETWORK_OP_READ or + * EVENTS_NETWORK_OP_WRITE. If there is already an event registration for + * this ${s}/${op} pair, errno will be set to EEXIST and the function will + * fail. + */ +int +events_network_register(int (*func)(void *), void * cookie, int s, int op) +{ + struct eventrec ** r; + + /* Initialize if necessary. */ + if (initsocketlist()) + goto err0; + + /* Sanity-check socket number. */ + if ((s < 0) || (s >= (int)FD_SETSIZE)) { + warn0("Invalid file descriptor for network event: %d", s); + goto err0; + } + + /* Sanity-check operation. */ + if ((op != EVENTS_NETWORK_OP_READ) && + (op != EVENTS_NETWORK_OP_WRITE)) { + warn0("Invalid operation for network event: %d", op); + goto err0; + } + + /* Grow the array if necessary. */ + if (((size_t)(s) >= socketlist_getsize(S)) && + (growsocketlist(s + 1) != 0)) + goto err0; + + /* Look up the relevant event pointer. */ + if (op == EVENTS_NETWORK_OP_READ) + r = &socketlist_get(S, s)->reader; + else + r = &socketlist_get(S, s)->writer; + + /* Error out if we already have an event registered. */ + if (*r != NULL) { + errno = EEXIST; + goto err0; + } + + /* Register the new event. */ + if ((*r = events_mkrec(func, cookie)) == NULL) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_network_cancel(s, op): + * Cancel the event registered for the socket/operation pair ${s}/${op}. If + * there is no such registration, errno will be set to ENOENT and the + * function will fail. + */ +int +events_network_cancel(int s, int op) +{ + struct eventrec ** r; + + /* Initialize if necessary. */ + if (initsocketlist()) + goto err0; + + /* Sanity-check socket number. */ + if ((s < 0) || (s >= (int)FD_SETSIZE)) { + warn0("Invalid file descriptor for network event: %d", s); + goto err0; + } + + /* Sanity-check operation. */ + if ((op != EVENTS_NETWORK_OP_READ) && + (op != EVENTS_NETWORK_OP_WRITE)) { + warn0("Invalid operation for network event: %d", op); + goto err0; + } + + /* We have no events registered beyond the end of the array. */ + if ((size_t)(s) >= socketlist_getsize(S)) { + errno = ENOENT; + goto err0; + } + + /* Look up the relevant event pointer. */ + if (op == EVENTS_NETWORK_OP_READ) + r = &socketlist_get(S, s)->reader; + else + r = &socketlist_get(S, s)->writer; + + /* Check if we have an event. */ + if (*r == NULL) { + errno = ENOENT; + goto err0; + } + + /* Free the event. */ + events_freerec(*r); + *r = NULL; + + /* + * Since there is no longer an event registered for this socket / + * operation pair, it doesn't make any sense for it to be ready. + */ + if (op == EVENTS_NETWORK_OP_READ) + FD_CLR(s, &readfds); + else + FD_CLR(s, &writefds); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_network_select(tv): + * Check for socket readiness events, waiting up to ${tv} time if there are + * no sockets immediately ready, or indefinitely if ${tv} is NULL. + */ +int +events_network_select(struct timeval * tv) +{ + size_t i; + + /* Initialize if necessary. */ + if (initsocketlist()) + goto err0; + + /* Zero the fd sets... */ + FD_ZERO(&readfds); + FD_ZERO(&writefds); + + /* ... and add the ones we care about. */ + for (i = 0; i < socketlist_getsize(S); i++) { + if (socketlist_get(S, i)->reader) + FD_SET(i, &readfds); + if (socketlist_get(S, i)->writer) + FD_SET(i, &writefds); + } + + /* Select. */ + while (select(socketlist_getsize(S), &readfds, &writefds, + NULL, tv) == -1) { + /* EINTR is harmless. */ + if (errno == EINTR) + continue; + + /* Anything else is an error. */ + warnp("select()"); + goto err0; + } + + /* We should start scanning for events at the beginning. */ + fdscanpos = 0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_network_get(void): + * Find a socket readiness event which was identified by a previous call to + * events_network_select, and return it as an eventrec structure; or return + * NULL if there are no such events available. The caller is responsible for + * freeing the returned memory. + */ +struct eventrec * +events_network_get(void) +{ + struct eventrec * r; + size_t nfds = socketlist_getsize(S); + + /* We haven't found any events yet. */ + r = NULL; + + /* Scan through the fd sets looking for ready sockets. */ + for (; fdscanpos < nfds; fdscanpos++) { + /* Are we ready for reading? */ + if (FD_ISSET(fdscanpos, &readfds)) { + r = socketlist_get(S, fdscanpos)->reader; + socketlist_get(S, fdscanpos)->reader = NULL; + FD_CLR(fdscanpos, &readfds); + break; + } + + /* Are we ready for writing? */ + if (FD_ISSET(fdscanpos, &writefds)) { + r = socketlist_get(S, fdscanpos)->writer; + socketlist_get(S, fdscanpos)->writer = NULL; + FD_CLR(fdscanpos, &writefds); + break; + } + } + + /* Return the event we found, or NULL if we didn't find any. */ + return (r); +} + +/** + * events_network_shutdown(void) + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void +events_network_shutdown(void) +{ + size_t i; + + /* If we're not initialized, do nothing. */ + if (S == NULL) + return; + + /* If we have any registered events, do nothing. */ + for (i = 0; i < socketlist_getsize(S); i++) { + if ((socketlist_get(S, i)->reader != NULL) || + (socketlist_get(S, i)->writer != NULL)) + return; + } + + /* Free the socket list. */ + socketlist_free(S); + S = NULL; +} diff --git a/lib/events/events_timer.c b/lib/events/events_timer.c new file mode 100644 index 0000000..5dcd734 --- /dev/null +++ b/lib/events/events_timer.c @@ -0,0 +1,262 @@ +#include + +#include +#include + +#include "monoclock.h" +#include "timerqueue.h" + +#include "events_internal.h" +#include "events.h" + +struct timerrec { + struct eventrec * r; + void * cookie; + struct timeval tv_orig; +}; + +static struct timerqueue * Q = NULL; + +/** + * events_timer_register(func, cookie, timeo): + * Register ${func}(${cookie}) to be run ${timeo} in the future. Return a + * cookie which can be passed to events_timer_cancel or events_timer_reset. + */ +void * +events_timer_register(int (*func)(void *), void * cookie, + const struct timeval * timeo) +{ + struct eventrec * r; + struct timerrec * t; + struct timeval tv; + + /* Create the timer queue if it doesn't exist yet. */ + if (Q == NULL) { + if ((Q = timerqueue_init()) == NULL) + goto err0; + } + + /* Bundle into an eventrec record. */ + if ((r = events_mkrec(func, cookie)) == NULL) + goto err0; + + /* Create a timer record. */ + if ((t = malloc(sizeof(struct timerrec))) == NULL) + goto err1; + t->r = r; + memcpy(&t->tv_orig, timeo, sizeof(struct timeval)); + + /* Compute the absolute timeout. */ + if (monoclock_get(&tv)) + goto err2; + tv.tv_sec += t->tv_orig.tv_sec; + if ((tv.tv_usec += t->tv_orig.tv_usec) >= 1000000) { + tv.tv_usec -= 1000000; + tv.tv_sec += 1; + } + + /* Add this to the timer queue. */ + if ((t->cookie = timerqueue_add(Q, &tv, t)) == NULL) + goto err2; + + /* Success! */ + return (t); + +err2: + free(t); +err1: + events_freerec(r); +err0: + /* Failure! */ + return (NULL); +} + +/** + * events_timer_register_double(func, cookie, timeo): + * As events_timer_register, but ${timeo} is a double-precision floating point + * value specifying a number of seconds. + */ +void * +events_timer_register_double(int (*func)(void *), void * cookie, + double timeo) +{ + struct timeval tv; + + /* Convert timeo to a struct timeval. */ + tv.tv_sec = timeo; + tv.tv_usec = (timeo - tv.tv_sec) * 1000000.0; + + /* Schedule the timeout. */ + return (events_timer_register(func, cookie, &tv)); +} + +/** + * events_timer_cancel(cookie): + * Cancel the timer for which the cookie ${cookie} was returned by + * events_timer_register. + */ +void +events_timer_cancel(void * cookie) +{ + struct timerrec * t = cookie; + + /* Remove from the timer queue. */ + timerqueue_delete(Q, t->cookie); + + /* Free the eventrec and timer records. */ + events_freerec(t->r); + free(t); +} + +/** + * events_timer_reset(cookie): + * Reset the timer for which the cookie ${cookie} was returned by + * events_timer_register to its initial value. + */ +int +events_timer_reset(void * cookie) +{ + struct timerrec * t = cookie; + struct timeval tv; + + /* Compute the new timeout. */ + if (monoclock_get(&tv)) + goto err0; + tv.tv_sec += t->tv_orig.tv_sec; + if ((tv.tv_usec += t->tv_orig.tv_usec) >= 1000000) { + tv.tv_usec -= 1000000; + tv.tv_sec += 1; + } + + /* Adjust the timer. */ + timerqueue_increase(Q, t->cookie, &tv); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_timer_min(timeo): + * Return via ${timeo} a pointer to the minimum time which must be waited + * before a timer will expire; or to NULL if there are no timers. The caller + * is responsible for freeing the returned pointer. + */ +int +events_timer_min(struct timeval ** timeo) +{ + struct timeval tnow; + const struct timeval * tv; + + /* If we have no queue, we have no timers; return NULL. */ + if (Q == NULL) { + *timeo = NULL; + goto done; + } + + /* Get the minimum timer from the queue. */ + tv = timerqueue_getmin(Q); + + /* If there are no timers, return NULL. */ + if (tv == NULL) { + *timeo = NULL; + goto done; + } + + /* Allocate space for holding the returned timeval. */ + if ((*timeo = malloc(sizeof(struct timeval))) == NULL) + goto err0; + + /* Get the current time... */ + if (monoclock_get(&tnow)) + goto err1; + + /* ... and compare it to the minimum timer. */ + if ((tnow.tv_sec > tv->tv_sec) || + ((tnow.tv_sec == tv->tv_sec) && (tnow.tv_usec > tv->tv_usec))) { + /* The timer has already expired, so return zero. */ + (*timeo)->tv_sec = 0; + (*timeo)->tv_usec = 0; + } else { + /* Compute the difference. */ + (*timeo)->tv_sec = tv->tv_sec - tnow.tv_sec; + (*timeo)->tv_usec = tv->tv_usec - tnow.tv_usec; + if (tv->tv_usec < tnow.tv_usec) { + (*timeo)->tv_usec += 1000000; + (*timeo)->tv_sec -= 1; + } + } + +done: + /* Success! */ + return (0); + +err1: + free(*timeo); +err0: + /* Failure! */ + return (-1); +} + +/** + * events_timer_get(r): + * Return via ${r} a pointer to an eventrec structure corresponding to an + * expired timer, and delete said timer; or to NULL if there are no expired + * timers. The caller is responsible for freeing the returned pointer. + */ +int +events_timer_get(struct eventrec ** r) +{ + struct timeval tnow; + struct timerrec * t; + + /* If we have no queue, we have no timers; return NULL. */ + if (Q == NULL) { + *r = NULL; + goto done; + } + + /* Get current time. */ + if (monoclock_get(&tnow)) + goto err0; + + /* Get an expired timer, if there is one. */ + t = timerqueue_getptr(Q, &tnow); + + /* If there is an expired timer... */ + if (t != NULL) { + /* ... pass back the eventrec and free the timer. */ + *r = t->r; + free(t); + } else { + /* Otherwise, return NULL. */ + *r = NULL; + } + +done: + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * events_timer_shutdown(void): + * Clean up and free memory. This call is not necessary on program exit and + * is only expected to be useful when checking for memory leaks. + */ +void +events_timer_shutdown(void) +{ + + /* If we have a queue and it is empty, free it. */ + if ((Q != NULL) && (timerqueue_getmin(Q) == NULL)) { + timerqueue_free(Q); + Q = NULL; + } +} diff --git a/lib/network/network.h b/lib/network/network.h new file mode 100644 index 0000000..6821344 --- /dev/null +++ b/lib/network/network.h @@ -0,0 +1,84 @@ +#ifndef _NETWORK_H_ +#define _NETWORK_H_ + +#include +#include +#include + +#include "sock.h" + +/** + * network_accept(fd, callback, cookie): + * Asynchronously accept a connection on the socket ${fd}, which must be + * already marked as listening and non-blocking. When a connection has been + * accepted or an error occurs, invoke ${callback}(${cookie}, s) where s is + * the accepted connection or -1 on error. Return a cookie which can be + * passed to network_accept_cancel in order to cancel the accept. + */ +void * network_accept(int, int (*)(void *, int), void *); + +/** + * network_accept_cancel(cookie); + * Cancel the connection accept for which the cookie ${cookie} was returned + * by network_accept. Do not invoke the callback associated with the accept. + */ +void network_accept_cancel(void *); + +/** + * network_connect(sas, callback, cookie): + * Iterate through the addresses in ${sas}, attempting to create and connect + * a non-blocking socket. Once connected, invoke ${callback}(${cookie}, s) + * where s is the connected socket; upon fatal error or if there are no + * addresses remaining to attempt, invoke ${callback}(${cookie}, -1). Return + * a cookie which can be passed to network_connect_cancel in order to cancel + * the connection attempt. + */ +void * network_connect(struct sock_addr * const *, + int (*)(void *, int), void *); + +/** + * network_connect_cancel(cookie): + * Cancel the connection attempt for which ${cookie} was returned by + * network_connect. Do not invoke the associated callback. + */ +void network_connect_cancel(void *); + +/** + * network_read(fd, buf, buflen, minread, callback, cookie): + * Asynchronously read up to ${buflen} bytes of data from ${fd} into ${buf}. + * When at least ${minread} bytes have been read or on error, invoke + * ${callback}(${cookie}, lenread), where lenread is 0 on EOF or -1 on error, + * and the number of bytes read (between ${minread} and ${buflen} inclusive) + * otherwise. Return a cookie which can be passed to network_read_cancel in + * order to cancel the read. + */ +void * network_read(int, uint8_t *, size_t, size_t, + int (*)(void *, ssize_t), void *); + +/** + * network_read_cancel(cookie): + * Cancel the buffer read for which the cookie ${cookie} was returned by + * network_read. Do not invoke the callback associated with the read. + */ +void network_read_cancel(void *); + +/** + * network_write(fd, buf, buflen, minwrite, callback, cookie): + * Asynchronously write up to ${buflen} bytes of data from ${buf} to ${fd}. + * When at least ${minwrite} bytes have been written or on error, invoke + * ${callback}(${cookie}, lenwrit), where lenwrit is -1 on error and the + * number of bytes written (between ${minwrite} and ${buflen} inclusive) + * otherwise. Return a cookie which can be passed to network_write_cancel in + * order to cancel the write. + */ +void * network_write(int, const uint8_t *, size_t, size_t, + int (*)(void *, ssize_t), void *); + +/** + * network_write_cancel(cookie): + * Cancel the buffer write for which the cookie ${cookie} was returned by + * network_write. Do not invoke the callback associated with the write. + */ +void network_write_cancel(void *); + +#endif /* !_NETWORK_H_ */ diff --git a/lib/network/network_accept.c b/lib/network/network_accept.c new file mode 100644 index 0000000..2c25938 --- /dev/null +++ b/lib/network/network_accept.c @@ -0,0 +1,103 @@ +#include +#include + +#include +#include + +#include "events.h" + +#include "network.h" + +struct accept_cookie { + int (* callback)(void *, int); + void * cookie; + int fd; +}; + +/* Accept the connection and invoke the callback. */ +static int +callback_accept(void * cookie) +{ + struct accept_cookie * C = cookie; + int s; + int rc; + + /* Attempt to accept a new connection. */ + if ((s = accept(C->fd, NULL, NULL)) == -1) { + /* If a connection isn't available, reset the callback. */ + if ((errno == EAGAIN) || + (errno == EWOULDBLOCK) || + (errno == ECONNABORTED) || + (errno == EINTR)) + goto tryagain; + } + + /* Call the upstream callback. */ + rc = (C->callback)(C->cookie, s); + + /* Free the cookie. */ + free(C); + + /* Return status from upstream callback. */ + return (rc); + +tryagain: + /* Reset the callback. */ + return (events_network_register(callback_accept, C, C->fd, + EVENTS_NETWORK_OP_READ)); +} + +/** + * network_accept(fd, callback, cookie): + * Asynchronously accept a connection on the socket ${fd}, which must be + * already marked as listening and non-blocking. When a connection has been + * accepted or an error occurs, invoke ${callback}(${cookie}, s) where s is + * the accepted connection or -1 on error. Return a cookie which can be + * passed to network_accept_cancel in order to cancel the accept. + */ +void * +network_accept(int fd, int (* callback)(void *, int), void * cookie) +{ + struct accept_cookie * C; + + /* Bake a cookie. */ + if ((C = malloc(sizeof(struct accept_cookie))) == NULL) + goto err0; + C->callback = callback; + C->cookie = cookie; + C->fd = fd; + + /* + * Register a network event. A connection arriving on a listening + * socket is treated by select(2) as the socket becoming readable. + */ + if (events_network_register(callback_accept, C, C->fd, + EVENTS_NETWORK_OP_READ)) + goto err1; + + /* Success! */ + return (C); + +err1: + free(C); +err0: + /* Failure! */ + return (NULL); +} + +/** + * network_accept_cancel(cookie); + * Cancel the connection accept for which the cookie ${cookie} was returned + * by network_accept. Do not invoke the callback associated with the accept. + */ +void +network_accept_cancel(void * cookie) +{ + struct accept_cookie * C = cookie; + + /* Cancel the network event. */ + events_network_cancel(C->fd, EVENTS_NETWORK_OP_READ); + + /* Free the cookie. */ + free(C); +} diff --git a/lib/network/network_buf.c b/lib/network/network_buf.c new file mode 100644 index 0000000..570951f --- /dev/null +++ b/lib/network/network_buf.c @@ -0,0 +1,297 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "events.h" +#include "warnp.h" + +#include "network.h" + +/** + * We use three methods to prevent SIGPIPE from being sent, in order of + * preference: + * 1. The MSG_NOSIGNAL send(2) flag. + * 2. The SO_NOSIGPIPE socket option. + * 3. Blocking the SIGPIPE signal. + */ +#ifdef MSG_NOSIGNAL +#define USE_MSG_NOSIGNAL +#else /* !MSG_NOSIGNAL */ +#define MSG_NOSIGNAL 0 +#ifdef SO_NOSIGPIPE +#define USE_SO_NOSIGPIPE +#else /* !MSG_NOSIGNAL, !SO_NOSIGPIPE */ +#define USE_SIGNAL +#endif /* !MSG_NOSIGNAL, !SO_NOSIGPIPE */ +#endif /* !MSG_NOSIGNAL */ + +struct network_buf_cookie { + int (*callback)(void *, ssize_t); + void * cookie; + int fd; + uint8_t * buf; + size_t buflen; + size_t minlen; + size_t bufpos; + ssize_t (* sendrecv)(int, void *, size_t, int); + int op; + int flags; +}; + +static int docallback(struct network_buf_cookie *, size_t); +static int callback_buf(void *); +static struct network_buf_cookie * network_buf(int, uint8_t *, size_t, + size_t, int (*)(void *, ssize_t), void *, + ssize_t (*)(int, void *, size_t, int), int, int); +static void cancel(void *); + +/* Invoke the callback, clean up, and return the callback's status. */ +static int +docallback(struct network_buf_cookie * C, size_t nbytes) +{ + int rc; + + /* Invoke the callback. */ + rc = (C->callback)(C->cookie, nbytes); + + /* Clean up. */ + free(C); + + /* Return the callback's status. */ + return (rc); +} + +/* The socket is ready for reading/writing. */ +static int +callback_buf(void * cookie) +{ + struct network_buf_cookie * C = cookie; + size_t oplen; + ssize_t len; +#ifdef USE_SO_NOSIGPIPE + int val; +#endif +#ifdef USE_SIGNAL + void (*oldsig)(int); +#endif + + /* Make sure we don't get a SIGPIPE. */ +#ifdef USE_SO_NOSIGPIPE + val = 1; + if (setsockopt(C->fd, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(int))) { + warnp("setsockopt(SO_NOSIGPIPE)"); + goto failed; + } +#endif +#ifdef USE_SIGNAL + if ((oldsig = signal(SIGPIPE, SIG_IGN)) == SIG_ERR) { + warnp("signal(SIGPIPE)"); + goto failed; + } +#endif + + /* Attempt to read/write data to/from the buffer. */ + oplen = C->buflen - C->bufpos; + len = (C->sendrecv)(C->fd, C->buf + C->bufpos, oplen, C->flags); + + /* Undo whatever we did to prevent SIGPIPEs. */ +#ifdef USE_SO_NOSIGPIPE + val = 0; + if (setsockopt(C->fd, SOL_SOCKET, SO_NOSIGPIPE, &val, sizeof(int))) { + warnp("setsockopt(SO_NOSIGPIPE)"); + goto failed; + } +#endif +#ifdef USE_SIGNAL + if (signal(SIGPIPE, oldsig) == SIG_ERR) { + warnp("signal(SIGPIPE)"); + goto failed; + } +#endif + + /* Failure? */ + if (len == -1) { + /* Was it really an error, or just a try-again? */ + if ((errno == EAGAIN) || + (errno == EWOULDBLOCK) || + (errno == EINTR)) + goto tryagain; + + /* Something went wrong. */ + goto failed; + } else if (len == 0) { + /* The socket was shut down by the remote host. */ + goto eof; + } + + /* We processed some data. Do we need to keep going? */ + if ((C->bufpos += len) < C->minlen) + goto tryagain; + + /* Invoke the callback and return. */ + return (docallback(C, C->bufpos)); + +tryagain: + /* Reset the event. */ + if (events_network_register(callback_buf, C, C->fd, C->op)) + goto failed; + + /* Callback was reset. */ + return (0); + +eof: + /* Sanity-check: This should only occur for reads. */ + assert(C->op == EVENTS_NETWORK_OP_READ); + + /* Invoke the callback with an EOF status and return. */ + return (docallback(C, 0)); + +failed: + /* Invoke the callback with a failure status and return. */ + return (docallback(C, -1)); +} + +/** + * network_buf(fd, buf, buflen, minlen, callback, cookie, sendrecv, op, flags): + * Asynchronously read/write up to ${buflen} bytes of data from/to ${fd} + * to/from ${buf}. When at least ${minlen} bytes have been read/written, + * invoke ${callback}(${cookie}, nbytes), where nbytes is 0 on EOF or -1 on + * error and the number of bytes read/written (between ${minlen} and ${buflen} + * inclusive) otherwise. Return a cookie which can be passed to buf_cancel + * in order to cancel the read/write. + */ +static struct network_buf_cookie * +network_buf(int fd, uint8_t * buf, size_t buflen, size_t minlen, + int (* callback)(void *, ssize_t), void * cookie, + ssize_t (* sendrecv)(int, void *, size_t, int), int op, int flags) +{ + struct network_buf_cookie * C; + + /* Sanity-check: # bytes must fit into a ssize_t. */ + assert(buflen <= SSIZE_MAX); + + /* Bake a cookie. */ + if ((C = malloc(sizeof(struct network_buf_cookie))) == NULL) + goto err0; + C->callback = callback; + C->cookie = cookie; + C->fd = fd; + C->buf = buf; + C->buflen = buflen; + C->minlen = minlen; + C->bufpos = 0; + C->sendrecv = sendrecv; + C->op = op; + C->flags = flags; + + /* Register a callback for network readiness. */ + if (events_network_register(callback_buf, C, C->fd, C->op)) + goto err1; + + /* Success! */ + return (C); + +err1: + free(C); +err0: + /* Failure! */ + return (NULL); +} + +/* Cancel the read/write. */ +static void +cancel(void * cookie) +{ + struct network_buf_cookie * C = cookie; + + /* Kill the network event. */ + events_network_cancel(C->fd, C->op); + + /* Free the cookie. */ + free(C); +} + +/** + * network_read(fd, buf, buflen, minread, callback, cookie): + * Asynchronously read up to ${buflen} bytes of data from ${fd} into ${buf}. + * When at least ${minread} bytes have been read or on error, invoke + * ${callback}(${cookie}, lenread), where lenread is 0 on EOF or -1 on error, + * and the number of bytes read (between ${minread} and ${buflen} inclusive) + * otherwise. Return a cookie which can be passed to network_read_cancel in + * order to cancel the read. + */ +void * +network_read(int fd, uint8_t * buf, size_t buflen, size_t minread, + int (* callback)(void *, ssize_t), void * cookie) +{ + + /* Make sure buflen is non-zero. */ + if (buflen == 0) { + warn0("Programmer error: Cannot read zero-byte buffer"); + return (NULL); + } + + /* Get network_buf to set things up for us. */ + return (network_buf(fd, buf, buflen, minread, callback, cookie, recv, + EVENTS_NETWORK_OP_READ, 0)); +} + +/** + * network_read_cancel(cookie): + * Cancel the buffer read for which the cookie ${cookie} was returned by + * network_read. Do not invoke the callback associated with the read. + */ +void +network_read_cancel(void * cookie) +{ + + /* Get cancel to do the work for us. */ + cancel(cookie); +} + +/** + * network_write(fd, buf, buflen, minwrite, callback, cookie): + * Asynchronously write up to ${buflen} bytes of data from ${buf} to ${fd}. + * When at least ${minwrite} bytes have been written or on error, invoke + * ${callback}(${cookie}, lenwrit), where lenwrit is -1 on error and the + * number of bytes written (between ${minwrite} and ${buflen} inclusive) + * otherwise. Return a cookie which can be passed to network_write_cancel in + * order to cancel the write. + */ +void * +network_write(int fd, const uint8_t * buf, size_t buflen, size_t minwrite, + int (* callback)(void *, ssize_t), void * cookie) +{ + + /* Make sure buflen is non-zero. */ + if (buflen == 0) { + warn0("Programmer error: Cannot write zero-byte buffer"); + return (NULL); + } + + /* Get network_buf to set things up for us. */ + return (network_buf(fd, (uint8_t *)(uintptr_t)buf, buflen, minwrite, + callback, cookie, + (ssize_t (*)(int, void *, size_t, int))send, + EVENTS_NETWORK_OP_WRITE, MSG_NOSIGNAL)); +} + +/** + * network_write_cancel(cookie): + * Cancel the buffer write for which the cookie ${cookie} was returned by + * network_write. Do not invoke the callback associated with the write. + */ +void +network_write_cancel(void * cookie) +{ + + /* Get cancel to do the work for us. */ + cancel(cookie); +} diff --git a/lib/network/network_connect.c b/lib/network/network_connect.c new file mode 100644 index 0000000..d5b31cf --- /dev/null +++ b/lib/network/network_connect.c @@ -0,0 +1,186 @@ +#include +#include + +#include +#include +#include + +#include "events.h" +#include "sock.h" + +#include "network.h" + +struct connect_cookie { + int (* callback)(void *, int); + void * cookie; + struct sock_addr * const * sas; + void * cookie_immediate; + int s; +}; + +static int tryconnect(struct connect_cookie *); + +/* Invoke the upstream callback and clean up. */ +static int +docallback(void * cookie) +{ + struct connect_cookie * C = cookie; + int rc; + + /* Invoke the upstream callback. */ + rc = (C->callback)(C->cookie, C->s); + + /* Free the cookie. */ + free(C); + + /* Return status from upstream callback. */ + return (rc); +} + +/* Callback when connect(2) succeeds or fails. */ +static int +callback_connect(void * cookie) +{ + struct connect_cookie * C = cookie; + int sockerr; + socklen_t sockerrlen = sizeof(int); + + /* Did we succeed? */ + if (getsockopt(C->s, SOL_SOCKET, SO_ERROR, &sockerr, &sockerrlen)) + goto err1; + if (sockerr != 0) + goto failed; + + /* + * Perform the callback (this can be done here rather than being + * scheduled as an immediate callback, as we're already running from + * callback context). + */ + return (docallback(C)); + +failed: + /* Close the socket which failed to connect. */ + close(C->s); + + /* We don't have an open socket any more. */ + C->s = -1; + + /* This address didn't work. */ + C->sas++; + + /* Try other addresses until we run out of options. */ + return (tryconnect(C)); + +err1: + close(C->s); + free(C); + + /* Fatal error! */ + return (-1); +} + +/* Try to launch a connection. Free the cookie on fatal errors. */ +static int +tryconnect(struct connect_cookie * C) +{ + + /* Try addresses until we find one which doesn't fail immediately. */ + for (; C->sas[0] != NULL; C->sas++) { + /* Can we try to connect to this address? */ + if ((C->s = sock_connect_nb(C->sas[0])) != -1) + break; + } + + /* Did we run out of addresses to try? */ + if (C->sas[0] == NULL) + goto failed; + + /* Wait until this socket connects or fails to do so. */ + if (events_network_register(callback_connect, C, C->s, + EVENTS_NETWORK_OP_WRITE)) + goto err1; + + /* Success! */ + return (0); + +failed: + /* Schedule a callback. */ + if ((C->cookie_immediate = + events_immediate_register(docallback, C, 0)) == NULL) + goto err1; + + /* Failure successfully handled. */ + return (0); + +err1: + if (C->s != -1) + close(C->s); + free(C); + + /* Fatal error. */ + return (-1); +} + +/** + * network_connect(sas, callback, cookie): + * Iterate through the addresses in ${sas}, attempting to create and connect + * a non-blocking socket. Once connected, invoke ${callback}(${cookie}, s) + * where s is the connected socket; upon fatal error or if there are no + * addresses remaining to attempt, invoke ${callback}(${cookie}, -1). Return + * a cookie which can be passed to network_connect_cancel in order to cancel + * the connection attempt. + */ +void * +network_connect(struct sock_addr * const * sas, + int (* callback)(void *, int), void * cookie) +{ + struct connect_cookie * C; + + /* Bake a cookie. */ + if ((C = malloc(sizeof(struct connect_cookie))) == NULL) + goto err0; + C->callback = callback; + C->cookie = cookie; + C->sas = sas; + C->cookie_immediate = NULL; + C->s = -1; + + /* Try to connect to the first address. */ + if (tryconnect(C)) + goto err0; + + /* Success! */ + return (C); + +err0: + /* Failure! */ + return (NULL); +} + +/** + * network_connect_cancel(cookie): + * Cancel the connection attempt for which ${cookie} was returned by + * network_connect. Do not invoke the associated callback. + */ +void +network_connect_cancel(void * cookie) +{ + struct connect_cookie * C = cookie; + + /* We should have either an immediate callback or a socket. */ + assert((C->cookie_immediate != NULL) || (C->s != -1)); + assert((C->cookie_immediate == NULL) || (C->s == -1)); + + /* Cancel any immediate callback. */ + if (C->cookie_immediate != NULL) + events_immediate_cancel(C->cookie_immediate); + + /* Close any socket. */ + if (C->s != -1) { + events_network_cancel(C->s, EVENTS_NETWORK_OP_WRITE); + close(C->s); + } + + /* Free the cookie. */ + free(C); +} diff --git a/lib/util/asprintf.c b/lib/util/asprintf.c new file mode 100644 index 0000000..1bec431 --- /dev/null +++ b/lib/util/asprintf.c @@ -0,0 +1,49 @@ +#include +#include +#include + +#include "asprintf.h" + +/** + * asprintf(ret, format, ...): + * Do asprintf(3) like GNU and BSD do. + */ +int +asprintf(char **ret, const char *format, ...) +{ + va_list ap; + int len; + size_t buflen; + + /* Figure out how long the string needs to be. */ + va_start(ap, format); + len = vsnprintf(NULL, 0, format, ap); + va_end(ap); + + /* Did we fail? */ + if (len < 0) + goto err0; + buflen = (size_t)(len) + 1; + + /* Allocate memory. */ + if ((*ret = malloc(buflen)) == NULL) + goto err0; + + /* Actually generate the string. */ + va_start(ap, format); + len = vsnprintf(*ret, buflen, format, ap); + va_end(ap); + + /* Did we fail? */ + if (len < 0) + goto err1; + + /* Success! */ + return (len); + +err1: + free(*ret); +err0: + /* Failure! */ + return (-1); +} diff --git a/lib/util/asprintf.h b/lib/util/asprintf.h new file mode 100644 index 0000000..59f3074 --- /dev/null +++ b/lib/util/asprintf.h @@ -0,0 +1,16 @@ +#ifndef _ASPRINTF_H_ +#define _ASPRINTF_H_ + +/* Avoid namespace collisions with BSD/GNU asprintf. */ +#ifdef asprintf +#undef asprintf +#endif +#define asprintf compat_asprintf + +/** + * asprintf(ret, format, ...): + * Do asprintf(3) like GNU and BSD do. + */ +int asprintf(char **, const char *, ...); + +#endif /* !_ASPRINTF_H_ */ diff --git a/lib/util/ctassert.h b/lib/util/ctassert.h new file mode 100644 index 0000000..073d365 --- /dev/null +++ b/lib/util/ctassert.h @@ -0,0 +1,14 @@ +#ifndef _CTASSERT_H_ +#define _CTASSERT_H_ + +/* + * CTASSERT(foo) will produce a compile-time error if "foo" is not a constant + * expression which evaluates to a non-zero value. + */ +#ifndef CTASSERT +#define CTASSERT(x) _CTASSERT(x, __LINE__) +#define _CTASSERT(x, y) __CTASSERT(x, y) +#define __CTASSERT(x, y) typedef char __assert ## y[(x) ? 1 : -1] +#endif + +#endif /* !_CTASSERT_H_ */ diff --git a/lib/util/daemonize.c b/lib/util/daemonize.c new file mode 100644 index 0000000..2d82fdd --- /dev/null +++ b/lib/util/daemonize.c @@ -0,0 +1,134 @@ +#include +#include +#include + +#include "noeintr.h" +#include "warnp.h" + +#include "daemonize.h" + +/** + * daemonize(spid): + * Daemonize and write the process ID in decimal to a file named ${spid}. + * The parent process will exit only after the child has written its pid. + * On success, the child will return 0; on failure, the parent will return + * -1. + */ +int +daemonize(const char * spid) +{ + FILE * f; + int fd[2]; + char dummy = 0; + + /* + * Create a pipe for the child to notify the parent when it has + * finished daemonizing. + */ + if (pipe(fd)) { + warnp("pipe"); + goto err0; + } + + /* + * Fork into the parent process (which waits for a poke and exits) + * and the child process (which keeps going). + */ + switch (fork()) { + case -1: + /* Fork failed. */ + warnp("fork"); + goto err2; + case 0: + /* In child process. */ + break; + default: + /* + * In parent process. Close write end of pipe so that if the + * client dies we will notice the pipe being reset. + */ + while (close(fd[1])) { + if (errno == EINTR) + continue; + warnp("close"); + goto err1; + } + do { + switch (read(fd[0], &dummy, 1)) { + case -1: + /* Error in read. */ + break; + case 0: + /* EOF -- the child died without poking us. */ + goto err1; + case 1: + /* We have been poked by the child. Exit. */ + _exit(0); + } + + /* Anything other than EINTR is bad. */ + if (errno != EINTR) { + warnp("read"); + goto err1; + } + } while (1); + } + + /* Set ourselves to be a session leader. */ + if (setsid() == -1) { + warnp("setsid"); + goto die; + } + + /* Write out our pid file. */ + if ((f = fopen(spid, "w")) == NULL) { + warnp("fopen(%s)", spid); + goto die; + } + if (fprintf(f, "%d", getpid()) < 0) { + warnp("fprintf"); + goto die; + } + if (fclose(f)) { + warnp("fclose"); + goto die; + } + + /* Tell the parent to suicide. */ + if (noeintr_write(fd[1], &dummy, 1) == -1) { + warnp("write"); + goto die; + } + + /* Close the pipe. */ + while (close(fd[0])) { + if (errno == EINTR) + continue; + warnp("close"); + goto die; + } + while (close(fd[1])) { + if (errno == EINTR) + continue; + warnp("close"); + goto die; + } + + /* Success! */ + return (0); + +err2: + close(fd[1]); +err1: + close(fd[0]); +err0: + /* Failure! */ + return (-1); + +die: + /* + * We're in the child and something bad happened; the parent will be + * notified when we die thanks to the pipe being closed. + */ + _exit(0); +} diff --git a/lib/util/daemonize.h b/lib/util/daemonize.h new file mode 100644 index 0000000..517a624 --- /dev/null +++ b/lib/util/daemonize.h @@ -0,0 +1,10 @@ +#ifndef _DAEMONIZE_H_ +#define _DAEMONIZE_H_ + +/** + * daemonize(spid): + * Daemonize and write the process ID in decimal to a file named ${spid}. + */ +int daemonize(const char *); + +#endif /* !_DAEMONIZE_H_ */ diff --git a/lib/util/entropy.c b/lib/util/entropy.c new file mode 100644 index 0000000..f824d13 --- /dev/null +++ b/lib/util/entropy.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +#include "warnp.h" + +#include "entropy.h" + +/** + * XXX Portability + * XXX We obtain random bytes from the operating system by opening + * XXX /dev/urandom and reading them from that device; this works on + * XXX modern UNIX-like operating systems but not on systems like + * XXX win32 where there is no concept of /dev/urandom. + */ + +/** + * entropy_read(buf, buflen): + * Fill the given buffer with random bytes provided by the operating system. + */ +int +entropy_read(uint8_t * buf, size_t buflen) +{ + int fd; + ssize_t lenread; + + /* Sanity-check the buffer size. */ + if (buflen > SSIZE_MAX) { + warn0("Programmer error: " + "Trying to read insane amount of random data: %zu", + buflen); + goto err0; + } + + /* Open /dev/urandom. */ + if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { + warnp("open(/dev/urandom)"); + goto err0; + } + + /* Read bytes until we have filled the buffer. */ + while (buflen > 0) { + if ((lenread = read(fd, buf, buflen)) == -1) { + warnp("read(/dev/urandom)"); + goto err1; + } + + /* The random device should never EOF. */ + if (lenread == 0) { + warn0("EOF on /dev/urandom?"); + goto err1; + } + + /* We're partly done. */ + buf += lenread; + buflen -= lenread; + } + + /* Close the device. */ + while (close(fd) == -1) { + if (errno != EINTR) { + warnp("close(/dev/urandom)"); + goto err0; + } + } + + /* Success! */ + return (0); + +err1: + close(fd); +err0: + /* Failure! */ + return (-1); +} diff --git a/lib/util/entropy.h b/lib/util/entropy.h new file mode 100644 index 0000000..fa6f1cf --- /dev/null +++ b/lib/util/entropy.h @@ -0,0 +1,13 @@ +#ifndef _ENTROPY_H_ +#define _ENTROPY_H_ + +#include +#include + +/** + * entropy_read(buf, buflen): + * Fill the given buffer with random bytes provided by the operating system. + */ +int entropy_read(uint8_t *, size_t); + +#endif /* !_ENTROPY_H_ */ diff --git a/lib/util/imalloc.h b/lib/util/imalloc.h new file mode 100644 index 0000000..6c6ffc0 --- /dev/null +++ b/lib/util/imalloc.h @@ -0,0 +1,33 @@ +#ifndef _IMALLOC_H_ +#define _IMALLOC_H_ + +#include +#include +#include + +/** + * imalloc(nrec, reclen): + * Allocate ${nrec} records of length ${reclen}. Check for size_t overflow. + */ +static inline void * +imalloc(size_t nrec, size_t reclen) +{ + + if (nrec > SIZE_MAX / reclen) { + errno = ENOMEM; + return (NULL); + } else { + return (malloc(nrec * reclen)); + } +} + +/** + * IMALLOC(p, nrec, type): + * Allocate ${nrec} records of type ${type} and store the pointer in ${p}. + * Return non-zero on failure. + */ +#define IMALLOC(p, nrec, type) \ + ((((p) = (type *)imalloc((nrec), sizeof(type))) == NULL) && \ + ((nrec) > 0)) + +#endif /* !_IMALLOC_H_ */ diff --git a/lib/util/monoclock.c b/lib/util/monoclock.c new file mode 100644 index 0000000..56b0a33 --- /dev/null +++ b/lib/util/monoclock.c @@ -0,0 +1,41 @@ +#include + +#include + +#include "warnp.h" + +#include "monoclock.h" + +/** + * monoclock_get(tv): + * Store the current time in ${tv}. If CLOCK_MONOTONIC is available, use + * that clock; otherwise, use gettimeofday(2). + */ +int +monoclock_get(struct timeval * tv) +{ +#ifdef CLOCK_MONOTONIC + struct timespec tp; +#endif + +#ifdef CLOCK_MONOTONIC + if (clock_gettime(CLOCK_MONOTONIC, &tp)) { + warnp("clock_gettime(CLOCK_MONOTONIC)"); + goto err0; + } + tv->tv_sec = tp.tv_sec; + tv->tv_usec = tp.tv_nsec / 1000; +#else + if (gettimeofday(tv, NULL)) { + warnp("gettimeofday"); + goto err0; + } +#endif + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} diff --git a/lib/util/monoclock.h b/lib/util/monoclock.h new file mode 100644 index 0000000..aac2126 --- /dev/null +++ b/lib/util/monoclock.h @@ -0,0 +1,13 @@ +#ifndef _MONOCLOCK_H_ +#define _MONOCLOCK_H_ + +#include + +/** + * monoclock_get(tv): + * Store the current time in ${tv}. If CLOCK_MONOTONIC is available, use + * that clock; otherwise, use gettimeofday(2). + */ +int monoclock_get(struct timeval *); + +#endif /* !_MONOCLOCK_H_ */ diff --git a/lib/util/noeintr.c b/lib/util/noeintr.c new file mode 100644 index 0000000..34bb60c --- /dev/null +++ b/lib/util/noeintr.c @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include + +#include "noeintr.h" + +/** + * noeintr_write(d, buf, nbytes): + * Write ${nbytes} bytes of data from ${buf} to the file descriptor ${d} per + * the write(2) system call, but looping until completion if interrupted by + * a signal. Return ${nbytes} on success or -1 on error. + */ +ssize_t +noeintr_write(int d, const void * buf, size_t nbyte) +{ + const uint8_t * p = buf; + size_t len = nbyte; + ssize_t lenwrit; + + /* Implementation-defined: Don't allow oversized writes. */ + assert(nbyte <= SSIZE_MAX); + + /* Loop until we have no data left to write. */ + while (len > 0) { + if ((lenwrit = write(d, p, len)) == -1) { + /* EINTR is harmless. */ + if (errno == EINTR) + continue; + + /* Anything else isn't. */ + goto err0; + } + + /* Sanity check. */ + assert(lenwrit >= 0); + assert(lenwrit <= (ssize_t)len); + + /* + * We might have done a partial write; advance the buffer + * pointer and adjust the remaining write length. + */ + p += lenwrit; + len -= lenwrit; + } + + /* Success! */ + return (nbyte); + +err0: + /* Failure! */ + return (-1); +} diff --git a/lib/util/noeintr.h b/lib/util/noeintr.h new file mode 100644 index 0000000..e79929a --- /dev/null +++ b/lib/util/noeintr.h @@ -0,0 +1,14 @@ +#ifndef _NOEINTR_H_ +#define _NOEINTR_H_ + +#include + +/** + * noeintr_write(d, buf, nbytes): + * Write ${nbytes} bytes of data from ${buf} to the file descriptor ${d} per + * the write(2) system call, but looping until completion if interrupted by + * a signal. Return ${nbytes} on success or -1 on error. + */ +ssize_t noeintr_write(int, const void *, size_t); + +#endif /* _NOEINTR_H_ */ diff --git a/lib/util/sock.c b/lib/util/sock.c new file mode 100644 index 0000000..4202cf7 --- /dev/null +++ b/lib/util/sock.c @@ -0,0 +1,477 @@ +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "imalloc.h" +#include "warnp.h" + +#include "sock.h" +#include "sock_internal.h" + +/* Convert a path into a socket address. */ +static struct sock_addr ** +sock_resolve_unix(const char * addr) +{ + struct sock_addr ** sas; + struct sock_addr * sa; + struct sockaddr_un * sa_un; + + /* Allocate and populate a sockaddr_un structure. */ + if ((sa_un = malloc(sizeof(struct sockaddr_un))) == NULL) + goto err0; + memset(sa_un, 0, sizeof(struct sockaddr_un)); + sa_un->sun_family = AF_UNIX; + if (strlen(addr) >= sizeof(sa_un->sun_path)) { + warn0("socket path too long: %s", addr); + goto err1; + } + strcpy(sa_un->sun_path, addr); + + /* Allocate and populate our wrapper. */ + if ((sa = malloc(sizeof(struct sock_addr))) == NULL) + goto err1; + sa->ai_family = AF_UNIX; + sa->ai_socktype = SOCK_STREAM; + sa->name = (struct sockaddr *)sa_un; + sa->namelen = sizeof(struct sockaddr_un); + + /* Allocate and populate an array of pointers. */ + if ((sas = malloc(2 * sizeof(struct sock_addr *))) == NULL) + goto err2; + sas[0] = sa; + sas[1] = NULL; + + /* Success! */ + return (sas); + +err2: + free(sa); +err1: + free(sa_un); +err0: + /* Failure! */ + return (NULL); +} + +/* Resolve a host into a list of socket addresses. */ +static struct sock_addr ** +sock_resolve_host(const char * addr, const char * ports) +{ + struct addrinfo hints; + struct addrinfo * res; + struct addrinfo * r; + struct sock_addr ** sas; + size_t n; + int error; + + /* Create hints structure. */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + /* Perform DNS lookup. */ + if ((error = getaddrinfo(addr, ports, &hints, &res)) != 0) { + warn0("Error looking up %s: %s", addr, gai_strerror(error)); + goto err0; + } + + /* Count addresses returned. */ + for (n = 0, r = res; r != NULL; r = r->ai_next) + n++; + + /* Allocate our response array. */ + if (IMALLOC(sas, n + 1, struct sock_addr *)) + goto err1; + + /* Create address structures. */ + for (n = 0, r = res; r != NULL; n++, r = r->ai_next) { + /* Allocate a structure. */ + if ((sas[n] = malloc(sizeof(struct sock_addr))) == NULL) + goto err2; + + /* Copy in the address metadata. */ + sas[n]->ai_family = r->ai_family; + sas[n]->ai_socktype = r->ai_socktype; + sas[n]->namelen = r->ai_addrlen; + + /* Duplicate the address. */ + if ((sas[n]->name = malloc(sas[n]->namelen)) == NULL) + goto err3; + memcpy(sas[n]->name, r->ai_addr, sas[n]->namelen); + } + + /* Terminate array with a NULL. */ + sas[n] = NULL; + + /* Free the linked list of addresses returned by getaddrinfo. */ + freeaddrinfo(res); + + /* Success! */ + return (sas); + +err3: + free(sas[n]); +err2: + for (; n > 0; n--) { + free(sas[n - 1]->name); + free(sas[n - 1]); + } + free(sas); +err1: + freeaddrinfo(res); +err0: + /* Failure! */ + return (NULL); +} + +/* Parse an IPv6 address into a socket address. */ +static struct sock_addr ** +sock_resolve_ipv6(const char * addr, in_port_t p) +{ + struct sock_addr ** sas; + struct sock_addr * sa; + struct sockaddr_in6 * sin6; + + /* Allocate and populate a sockaddr_in6 structure. */ + if ((sin6 = malloc(sizeof(struct sockaddr_in6))) == NULL) + goto err0; + memset(sin6, 0, sizeof(struct sockaddr_in6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons(p); + if (inet_pton(AF_INET6, addr, &sin6->sin6_addr) != 1) { + warn0("Error parsing IP address: %s", addr); + goto err1; + } + + /* Allocate and populate our wrapper. */ + if ((sa = malloc(sizeof(struct sock_addr))) == NULL) + goto err1; + sa->ai_family = AF_INET6; + sa->ai_socktype = SOCK_STREAM; + sa->name = (struct sockaddr *)sin6; + sa->namelen = sizeof(struct sockaddr_in6); + + /* Allocate and populate an array of pointers. */ + if ((sas = malloc(2 * sizeof(struct sock_addr *))) == NULL) + goto err2; + sas[0] = sa; + sas[1] = NULL; + + /* Success! */ + return (sas); + +err2: + free(sa); +err1: + free(sin6); +err0: + /* Failure! */ + return (NULL); +} + +/* Parse an IPv4 address into a socket address. */ +static struct sock_addr ** +sock_resolve_ipv4(const char * addr, in_port_t p) +{ + struct sock_addr ** sas; + struct sock_addr * sa; + struct sockaddr_in * sin; + + /* Allocate and populate a sockaddr_in structure. */ + if ((sin = malloc(sizeof(struct sockaddr_in))) == NULL) + goto err0; + memset(sin, 0, sizeof(struct sockaddr_in)); + sin->sin_family = AF_INET; + sin->sin_port = htons(p); + if (inet_pton(AF_INET, addr, &sin->sin_addr) != 1) { + warn0("Error parsing IP address: %s", addr); + goto err1; + } + + /* Allocate and populate our wrapper. */ + if ((sa = malloc(sizeof(struct sock_addr))) == NULL) + goto err1; + sa->ai_family = AF_INET; + sa->ai_socktype = SOCK_STREAM; + sa->name = (struct sockaddr *)sin; + sa->namelen = sizeof(struct sockaddr_in); + + /* Allocate and populate an array of pointers. */ + if ((sas = malloc(2 * sizeof(struct sock_addr *))) == NULL) + goto err2; + sas[0] = sa; + sas[1] = NULL; + + /* Success! */ + return (sas); + +err2: + free(sa); +err1: + free(sin); +err0: + /* Failure! */ + return (NULL); +} + +/** + * sock_resolve(addr): + * Return a NULL-terminated array of pointers to sock_addr structures. + */ +struct sock_addr ** +sock_resolve(const char * addr) +{ + struct sock_addr ** res; + char * s; + char * ports; + char * ips; + long p; + + /* If the address starts with '/', it's a unix socket. */ + if (addr[0] == '/') { + res = sock_resolve_unix(addr); + goto done; + } + + /* Copy the address so that we can mangle it. */ + if ((s = strdup(addr)) == NULL) + goto err0; + + /* The address should end with :port. Look for the last ':'. */ + if ((ports = strrchr(s, ':')) == NULL) { + warn0("Address must contain port number: %s", s); + goto err1; + } + *ports++ = '\0'; + + /* If the address doesn't start with '[', it's a host name. */ + if (s[0] != '[') { + res = sock_resolve_host(s, ports); + goto done1; + } + + /* The address (sans :port) should end with ']'. */ + if (s[strlen(s) - 1] != ']') { + warn0("Invalid [IP address]: %s", s); + goto err1; + } + + /* Extract the IP address string. */ + ips = &s[1]; + ips[strlen(ips) - 1] = '\0'; + + /* + * Parse the port number. If strtol fails to parse the port number, + * it will return 0; but that's fine since port 0 is invalid anyway. + */ + p = strtol(ports, NULL, 10); + if ((p <= 0) || (p >= 65536)) { + warn0("Invalid port number: %s", ports); + goto err1; + } + + /* If the IP address contains ':', it's IPv6; otherwise, IPv4. */ + if (strrchr(ips, ':') != NULL) + res = sock_resolve_ipv6(ips, p); + else + res = sock_resolve_ipv4(ips, p); + +done1: + /* Free string allocated by strdup. */ + free(s); +done: + /* Return result from sock_resolve_foo. */ + return (res); + +err1: + free(s); +err0: + /* Failure! */ + return (NULL); +} + +/** + * sock_listener(sa): + * Create a socket, set SO_REUSEADDR, bind it to the socket address ${sa}, + * mark it for listening, and mark it as non-blocking. + */ +int +sock_listener(const struct sock_addr * sa) +{ + int s; + int val = 1; + + /* Create a socket. */ + if ((s = socket(sa->ai_family, sa->ai_socktype, 0)) == -1) { + warnp("socket(%d, %d)", sa->ai_family, sa->ai_socktype); + goto err0; + } + + /* Set SO_REUSEADDR. */ + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) { + warnp("setsockopt(SO_REUSEADDR)"); + goto err1; + } + + /* Bind the socket. */ + if (bind(s, sa->name, sa->namelen)) { + warnp("error binding socket"); + goto err1; + } + + /* Mark the socket as listening. */ + if (listen(s, 10)) { + warnp("error marking socket as listening"); + goto err1; + } + + /* Make the socket as non-blocking. */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { + warnp("error marking socket as non-blocking"); + goto err1; + } + + /* Success! */ + return (s); + +err1: + close(s); +err0: + /* Failure! */ + return (-1); +} + +/** + * sock_connect(sas): + * Iterate through the addresses in ${sas}, attempting to create a socket and + * connect (blockingly). Once connected, stop iterating, mark the socket as + * non-blocking, and return it. + */ +int +sock_connect(struct sock_addr * const * sas) +{ + int s = -1; + + /* Iterate through the addresses provided. */ + for (; sas[0] != NULL; sas++) { + /* Create a socket. */ + if ((s = socket(sas[0]->ai_family, + sas[0]->ai_socktype, 0)) == -1) + continue; + + /* Attempt to connect. */ + if (connect(s, sas[0]->name, sas[0]->namelen) == 0) + break; + + /* Close the socket; this address didn't work. */ + close(s); + } + + /* Did we manage to connect? */ + if (sas[0] == NULL) { + warn0("Could not connect"); + goto err0; + } + + /* Mark the socket as non-blocking. */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { + warnp("Cannot make connection non-blocking"); + goto err1; + } + + /* Success! */ + return (s); + +err1: + close(s); +err0: + /* Failure! */ + return (-1); +} + +/** + * sock_connect_nb(sa): + * Create a socket, mark it as non-blocking, and attempt to connect to the + * address ${sa}. Return the socket (connected or in the process of + * connecting) or -1 on error. + */ +int +sock_connect_nb(const struct sock_addr * sa) +{ + int s; + + /* Create a socket. */ + if ((s = socket(sa->ai_family, sa->ai_socktype, 0)) == -1) + goto err0; + + /* Mark the socket as non-blocking. */ + if (fcntl(s, F_SETFL, O_NONBLOCK) == -1) { + warnp("Cannot make socket non-blocking"); + goto err1; + } + + /* Attempt to connect. */ + if ((connect(s, sa->name, sa->namelen) == -1) && + (errno != EINPROGRESS) && + (errno == EINTR)) + goto err1; + + /* We have a connect(ed|ing) socket. */ + return (s); + +err1: + close(s); +err0: + /* We failed to connect to this address. */ + return (-1); +} + +/** + * sock_addr_free(sa): + * Free the provided sock_addr structure. + */ +void +sock_addr_free(struct sock_addr * sa) +{ + + /* Compatibility with free(NULL). */ + if (sa == NULL) + return; + + /* Free the protocol-specific address structure and our struct. */ + free(sa->name); + free(sa); +} + +/** + * sock_addr_freelist(sas): + * Free the provided NULL-terminated array of sock_addr structures. + */ +void +sock_addr_freelist(struct sock_addr ** sas) +{ + struct sock_addr ** p; + + /* Compatibility with free(NULL). */ + if (sas == NULL) + return; + + /* Free structures until we hit NULL. */ + for (p = sas; *p != NULL; p++) + sock_addr_free(*p); + + /* Free the list. */ + free(sas); +} diff --git a/lib/util/sock.h b/lib/util/sock.h new file mode 100644 index 0000000..f4ea3b0 --- /dev/null +++ b/lib/util/sock.h @@ -0,0 +1,56 @@ +#ifndef _SOCK_H_ +#define _SOCK_H_ + +/** + * Address strings are of the following forms: + * /path/to/unix/socket + * [ip.v4.ad.dr]:port + * [ipv6:add::ress]:port + * host.name:port + */ + +/* Opaque address structure. */ +struct sock_addr; + +/** + * sock_resolve(addr): + * Return a NULL-terminated array of pointers to sock_addr structures. + */ +struct sock_addr ** sock_resolve(const char *); + +/** + * sock_listener(sa): + * Create a socket, set SO_REUSEADDR, bind it to the socket address ${sa}, + * mark it for listening, and mark it as non-blocking. + */ +int sock_listener(const struct sock_addr *); + +/** + * sock_connect(sas): + * Iterate through the addresses in ${sas}, attempting to create a socket and + * connect (blockingly). Once connected, stop iterating, mark the socket as + * non-blocking, and return it. + */ +int sock_connect(struct sock_addr * const *); + +/** + * sock_connect_nb(sa): + * Create a socket, mark it as non-blocking, and attempt to connect to the + * address ${sa}. Return the socket (connected or in the process of + * connecting) or -1 on error. + */ +int sock_connect_nb(const struct sock_addr *); + +/** + * sock_addr_free(sa): + * Free the provided sock_addr structure. + */ +void sock_addr_free(struct sock_addr *); + +/** + * sock_addr_freelist(sas): + * Free the provided NULL-terminated array of sock_addr structures. + */ +void sock_addr_freelist(struct sock_addr **); + +#endif /* !_SOCK_H_ */ diff --git a/lib/util/sock_internal.h b/lib/util/sock_internal.h new file mode 100644 index 0000000..5af9a75 --- /dev/null +++ b/lib/util/sock_internal.h @@ -0,0 +1,14 @@ +#ifndef _SOCK_INTERNAL_H_ +#define _SOCK_INTERNAL_H_ + +#include + +/* Socket address structure. */ +struct sock_addr { + int ai_family; + int ai_socktype; + struct sockaddr * name; + socklen_t namelen; +}; + +#endif /* !_SOCK_INTERNAL_H_ */ diff --git a/lib/util/sock_util.c b/lib/util/sock_util.c new file mode 100644 index 0000000..c5ce316 --- /dev/null +++ b/lib/util/sock_util.c @@ -0,0 +1,258 @@ +#include +#include + +#include + +#include + +#include +#include + +#include "asprintf.h" +#include "sock.h" + +#include "sock_util.h" +#include "sock_internal.h" + +/** + * sock_addr_cmp(sa1, sa2): + * Return non-zero iff the socket addresses ${sa1} and ${sa2} are different. + */ +int +sock_addr_cmp(const struct sock_addr * sa1, const struct sock_addr * sa2) +{ + + /* Family, socket type, and name length must match. */ + if ((sa1->ai_family != sa2->ai_family) || + (sa1->ai_socktype != sa2->ai_socktype) || + (sa1->namelen != sa2->namelen)) + return (1); + + /* The required length of the sockaddr must match. */ + if (memcmp(sa1->name, sa2->name, sa1->namelen) != 0) + return (1); + + /* Everything matched. */ + return (0); +} + +/** + * sock_addr_dup(sa): + * Duplicate the provided socket address. + */ +struct sock_addr * +sock_addr_dup(const struct sock_addr * sa) +{ + struct sock_addr * sa2; + + /* Allocate a struct sock_addr and copy fields. */ + if ((sa2 = malloc(sizeof(struct sock_addr))) == NULL) + goto err0; + sa2->ai_family = sa->ai_family; + sa2->ai_socktype = sa->ai_socktype; + sa2->namelen = sa->namelen; + + /* Allocate and copy the sockaddr. */ + if ((sa2->name = malloc(sa2->namelen)) == NULL) + goto err1; + memcpy(sa2->name, sa->name, sa2->namelen); + + /* Success! */ + return (sa2); + +err1: + free(sa2); +err0: + /* Failure! */ + return (NULL); +} + +/** + * sock_addr_duplist(sas): + * Duplicate the provided list of socket addresses. + */ +struct sock_addr ** +sock_addr_duplist(struct sock_addr * const * sas) +{ + struct sock_addr ** sas2; + size_t i; + + /* Count socket addresses. */ + for (i = 0; sas[i] != NULL; i++) + continue; + + /* Allocate the list to hold addresses plus a NULL terminator. */ + if ((sas2 = malloc((i + 1) * sizeof(struct sock_addr *))) == NULL) + goto err0; + + /* Fill the list with NULLs to make error-path cleanup simpler. */ + for (i = 0; sas[i] != NULL; i++) + sas2[i] = NULL; + sas2[i] = NULL; + + /* Duplicate addresses. */ + for (i = 0; sas[i] != NULL; i++) { + if ((sas2[i] = sock_addr_dup(sas[i])) == NULL) + goto err1; + } + + /* Success! */ + return (sas2); + +err1: + /* + * Regardless of how many addresses we managed to duplicate before + * failing and being sent here, we have a valid socket address list, + * so we can free it and its constituent addresses easily. + */ + sock_addr_freelist(sas2); +err0: + /* Failure! */ + return (NULL); +} + +/** + * sock_addr_serialize(sa, buf, buflen): + * Allocate a buffer and serialize the socket address ${sa} into it. Return + * the buffer via ${buf} and its length via ${buflen}. The serialization is + * machine and operating system dependent. + */ +int +sock_addr_serialize(const struct sock_addr * sa, + uint8_t ** buf, size_t * buflen) +{ + uint8_t * p; + + /* Compute buffer length and allocate buffer. */ + *buflen = 2 * sizeof(int) + sizeof(socklen_t) + sa->namelen; + if ((p = *buf = malloc(*buflen)) == NULL) + goto err0; + + /* Copy in data. */ + memcpy(p, &sa->ai_family, sizeof(int)); + p += sizeof(int); + memcpy(p, &sa->ai_socktype, sizeof(int)); + p += sizeof(int); + memcpy(p, &sa->namelen, sizeof(socklen_t)); + p += sizeof(socklen_t); + memcpy(p, sa->name, sa->namelen); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * sock_addr_deserialize(buf, buflen): + * Deserialize the ${buflen}-byte serialized socket address from ${buf}. + */ +struct sock_addr * +sock_addr_deserialize(const uint8_t * buf, size_t buflen) +{ + struct sock_addr * sa; + + /* Sanity check. */ + if (buflen < 2 * sizeof(int) + sizeof(socklen_t)) + goto err0; + + /* Allocate a structure and copy in fields. */ + if ((sa = malloc(sizeof(struct sock_addr))) == NULL) + goto err0; + memcpy(&sa->ai_family, buf, sizeof(int)); + buf += sizeof(int); + memcpy(&sa->ai_socktype, buf, sizeof(int)); + buf += sizeof(int); + memcpy(&sa->namelen, buf, sizeof(socklen_t)); + buf += sizeof(socklen_t); + + /* Allocate and copy the sockaddr. */ + if (buflen != 2 * sizeof(int) + sizeof(socklen_t) + sa->namelen) + goto err1; + if ((sa->name = malloc(sa->namelen)) == NULL) + goto err1; + memcpy(sa->name, buf, sa->namelen); + + /* Success! */ + return (sa); + +err1: + free(sa); +err0: + /* Failure! */ + return (NULL); +} + +/* Prettyprint an IPv4 address. */ +static char * +prettyprint_ipv4(struct sockaddr_in * name) +{ + char addr[INET_ADDRSTRLEN]; + char * s; + + /* Convert IP address to string. */ + if (inet_ntop(AF_INET, &name->sin_addr, addr, sizeof(addr)) == NULL) + return (NULL); + + /* Construct address string. */ + if (asprintf(&s, "[%s]:%d", addr, ntohs(name->sin_port)) == -1) + return (NULL); + + /* Success! */ + return (s); +} + +/* Prettyprint an IPv6 address. */ +static char * +prettyprint_ipv6(struct sockaddr_in6 * name) +{ + char addr[INET6_ADDRSTRLEN]; + char * s; + + /* Convert IPv6 address to string. */ + if (inet_ntop(AF_INET6, &name->sin6_addr, addr, sizeof(addr)) == NULL) + return (NULL); + + /* Construct address string. */ + if (asprintf(&s, "[%s]:%d", addr, ntohs(name->sin6_port)) == -1) + return (NULL); + + /* Success! */ + return (s); +} + +/* Prettyprint a UNIX address. */ +static char * +prettyprint_unix(struct sockaddr_un * name) +{ + + /* Just strdup the path. */ + return (strdup(name->sun_path)); +} + +/** + * sock_addr_prettyprint(sa): + * Allocate and return a string in one of the forms + * /path/to/unix/socket + * [ip.v4.ad.dr]:port + * [ipv6:add::ress]:port + * representing the provided socket address. + */ +char * +sock_addr_prettyprint(const struct sock_addr * sa) +{ + + /* Handle different types of addresses differently. */ + switch (sa->ai_family) { + case AF_INET: + return (prettyprint_ipv4((struct sockaddr_in *)(sa->name))); + case AF_INET6: + return (prettyprint_ipv6((struct sockaddr_in6 *)(sa->name))); + case AF_UNIX: + return (prettyprint_unix((struct sockaddr_un *)(sa->name))); + default: + return (strdup("Unknown address")); + } +} diff --git a/lib/util/sock_util.h b/lib/util/sock_util.h new file mode 100644 index 0000000..13ce1d0 --- /dev/null +++ b/lib/util/sock_util.h @@ -0,0 +1,51 @@ +#ifndef _SOCK_UTIL_H_ +#define _SOCK_UTIL_H_ + +#include + +/* Opaque address structure. */ +struct sock_addr; + +/** + * sock_addr_cmp(sa1, sa2): + * Return non-zero iff the socket addresses ${sa1} and ${sa2} are different. + */ +int sock_addr_cmp(const struct sock_addr *, const struct sock_addr *); + +/** + * sock_addr_dup(sa): + * Duplicate the provided socket address. + */ +struct sock_addr * sock_addr_dup(const struct sock_addr *); + +/** + * sock_addr_duplist(sas): + * Duplicate the provided list of socket addresses. + */ +struct sock_addr ** sock_addr_duplist(struct sock_addr * const *); + +/** + * sock_addr_serialize(sa, buf, buflen): + * Allocate a buffer and serialize the socket address ${sa} into it. Return + * the buffer via ${buf} and its length via ${buflen}. The serialization is + * machine and operating system dependent. + */ +int sock_addr_serialize(const struct sock_addr *, uint8_t **, size_t *); + +/** + * sock_addr_deserialize(buf, buflen): + * Deserialize the ${buflen}-byte serialized socket address from ${buf}. + */ +struct sock_addr * sock_addr_deserialize(const uint8_t *, size_t); + +/** + * sock_addr_prettyprint(sa): + * Allocate and return a string in one of the forms + * /path/to/unix/socket + * [ip.v4.ad.dr]:port + * [ipv6:add::ress]:port + * representing the provided socket address. + */ +char * sock_addr_prettyprint(const struct sock_addr *); + +#endif /* !_SOCK_H_ */ diff --git a/lib/util/sysendian.h b/lib/util/sysendian.h new file mode 100644 index 0000000..9f0ab1e --- /dev/null +++ b/lib/util/sysendian.h @@ -0,0 +1,146 @@ +#ifndef _SYSENDIAN_H_ +#define _SYSENDIAN_H_ + +#include + +/* Avoid namespace collisions with BSD . */ +#define be16dec compat_be16dec +#define be16enc compat_be16enc +#define be32dec compat_be32dec +#define be32enc compat_be32enc +#define be64dec compat_be64dec +#define be64enc compat_be64enc +#define le16dec compat_le16dec +#define le16enc compat_le16enc +#define le32dec compat_le32dec +#define le32enc compat_le32enc +#define le64dec compat_le64dec +#define le64enc compat_le64enc + +static inline uint16_t +be16dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint16_t)(p[1]) + ((uint16_t)(p[0]) << 8)); +} + +static inline void +be16enc(void *pp, uint16_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[1] = x & 0xff; + p[0] = (x >> 8) & 0xff; +} + +static inline uint32_t +be32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[3]) + ((uint32_t)(p[2]) << 8) + + ((uint32_t)(p[1]) << 16) + ((uint32_t)(p[0]) << 24)); +} + +static inline void +be32enc(void *pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[3] = x & 0xff; + p[2] = (x >> 8) & 0xff; + p[1] = (x >> 16) & 0xff; + p[0] = (x >> 24) & 0xff; +} + +static inline uint64_t +be64dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint64_t)(p[7]) + ((uint64_t)(p[6]) << 8) + + ((uint64_t)(p[5]) << 16) + ((uint64_t)(p[4]) << 24) + + ((uint64_t)(p[3]) << 32) + ((uint64_t)(p[2]) << 40) + + ((uint64_t)(p[1]) << 48) + ((uint64_t)(p[0]) << 56)); +} + +static inline void +be64enc(void *pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[7] = x & 0xff; + p[6] = (x >> 8) & 0xff; + p[5] = (x >> 16) & 0xff; + p[4] = (x >> 24) & 0xff; + p[3] = (x >> 32) & 0xff; + p[2] = (x >> 40) & 0xff; + p[1] = (x >> 48) & 0xff; + p[0] = (x >> 56) & 0xff; +} + +static inline uint16_t +le16dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint16_t)(p[0]) + ((uint16_t)(p[1]) << 8)); +} + +static inline void +le16enc(void *pp, uint16_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; +} + +static inline uint32_t +le32dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint32_t)(p[0]) + ((uint32_t)(p[1]) << 8) + + ((uint32_t)(p[2]) << 16) + ((uint32_t)(p[3]) << 24)); +} + +static inline void +le32enc(void *pp, uint32_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; +} + +static inline uint64_t +le64dec(const void *pp) +{ + const uint8_t *p = (uint8_t const *)pp; + + return ((uint64_t)(p[0]) + ((uint64_t)(p[1]) << 8) + + ((uint64_t)(p[2]) << 16) + ((uint64_t)(p[3]) << 24) + + ((uint64_t)(p[4]) << 32) + ((uint64_t)(p[5]) << 40) + + ((uint64_t)(p[6]) << 48) + ((uint64_t)(p[7]) << 56)); +} + +static inline void +le64enc(void *pp, uint64_t x) +{ + uint8_t * p = (uint8_t *)pp; + + p[0] = x & 0xff; + p[1] = (x >> 8) & 0xff; + p[2] = (x >> 16) & 0xff; + p[3] = (x >> 24) & 0xff; + p[4] = (x >> 32) & 0xff; + p[5] = (x >> 40) & 0xff; + p[6] = (x >> 48) & 0xff; + p[7] = (x >> 56) & 0xff; +} + +#endif /* !_SYSENDIAN_H_ */ diff --git a/lib/util/warnp.c b/lib/util/warnp.c new file mode 100644 index 0000000..2ec5a57 --- /dev/null +++ b/lib/util/warnp.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "warnp.h" + +static int initialized = 0; +static char * name = NULL; + +/* Free the name string. */ +static void +done(void) +{ + + free(name); + name = NULL; +} + +/** + * warnp_setprogname(progname): + * Set the program name to be used by warn() and warnx() to ${progname}. + */ +void +warnp_setprogname(const char * progname) +{ + const char * p; + + /* Free the name if we already have one. */ + free(name); + + /* Find the last segment of the program name. */ + for (p = progname; progname[0] != '\0'; progname++) + if (progname[0] == '/') + p = progname + 1; + + /* Copy the name string. */ + name = strdup(p); + + /* If we haven't already done so, register our exit handler. */ + if (initialized == 0) { + atexit(done); + initialized = 1; + } +} + +void +warn(const char * fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s", (name != NULL) ? name : "(unknown)"); + if (fmt != NULL) { + fprintf(stderr, ": "); + vfprintf(stderr, fmt, ap); + } + fprintf(stderr, ": %s\n", strerror(errno)); + va_end(ap); +} + +void +warnx(const char * fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "%s", (name != NULL) ? name : "(unknown)"); + if (fmt != NULL) { + fprintf(stderr, ": "); + vfprintf(stderr, fmt, ap); + } + fprintf(stderr, "\n"); + va_end(ap); +} diff --git a/lib/util/warnp.h b/lib/util/warnp.h new file mode 100644 index 0000000..a741230 --- /dev/null +++ b/lib/util/warnp.h @@ -0,0 +1,59 @@ +#ifndef _WARNP_H_ +#define _WARNP_H_ + +#include + +/* Avoid namespace collisions with BSD . */ +#define warn compat_warn +#define warnx compat_warnx + +/** + * warnp_setprogname(progname): + * Set the program name to be used by warn() and warnx() to ${progname}. + */ +void warnp_setprogname(const char *); +#define WARNP_INIT do { \ + if (argv[0] != NULL) \ + warnp_setprogname(argv[0]); \ +} while (0) + +/* As in BSD . */ +void warn(const char *, ...); +void warnx(const char *, ...); + +/* + * If compiled with DEBUG defined, print __FILE__ and __LINE__. + */ +#ifdef DEBUG +#define warnline do { \ + warnx("%s, %d", __FILE__, __LINE__); \ +} while (0) +#else +#define warnline +#endif + +/* + * Call warn(3) or warnx(3) depending upon whether errno == 0; and clear + * errno (so that the standard error message isn't repeated later). + */ +#define warnp(...) do { \ + warnline; \ + if (errno != 0) { \ + warn(__VA_ARGS__); \ + errno = 0; \ + } else \ + warnx(__VA_ARGS__); \ +} while (0) + +/* + * Call warnx(3) and set errno == 0. Unlike warnp, this should be used + * in cases where we're reporting a problem which we discover ourselves + * rather than one which is reported to us from a library or the kernel. + */ +#define warn0(...) do { \ + warnline; \ + warnx(__VA_ARGS__); \ + errno = 0; \ +} while (0) + +#endif /* !_WARNP_H_ */ diff --git a/proto/proto_conn.c b/proto/proto_conn.c new file mode 100644 index 0000000..f657544 --- /dev/null +++ b/proto/proto_conn.c @@ -0,0 +1,357 @@ +#include + +#include +#include +#include + +#include "events.h" +#include "network.h" +#include "sock.h" + +#include "proto_handshake.h" +#include "proto_pipe.h" +#include "proto_crypt.h" + +#include "proto_conn.h" + +struct conn_state { + int (* callback_dead)(void *); + void * cookie; + struct sock_addr ** sas; + int decr; + int nofps; + int nokeepalive; + const struct proto_secret * K; + double timeo; + int s; + int t; + void * connect_cookie; + void * connect_timeout_cookie; + void * handshake_cookie; + void * handshake_timeout_cookie; + struct proto_keys * k_f; + struct proto_keys * k_r; + void * pipe_f; + void * pipe_r; + int stat_f; + int stat_r; +}; + +static int callback_connect_done(void *, int); +static int callback_connect_timeout(void *); +static int callback_handshake_done(void *, struct proto_keys *, + struct proto_keys *); +static int callback_handshake_timeout(void *); +static int callback_pipestatus(void *); + +/* Start a handshake. */ +static int +starthandshake(struct conn_state * C, int s, int decr) +{ + + /* Start the handshake timer. */ + if ((C->handshake_timeout_cookie = events_timer_register_double( + callback_handshake_timeout, C, C->timeo)) == NULL) + goto err0; + + /* Start the handshake. */ + if ((C->handshake_cookie = proto_handshake(s, decr, C->nofps, + C->K, callback_handshake_done, C)) == NULL) + goto err1; + + /* Success! */ + return (0); + +err1: + events_timer_cancel(C->handshake_timeout_cookie); + C->handshake_timeout_cookie = NULL; +err0: + /* Failure! */ + return (-1); +} + +/* Launch the two pipes. */ +static int +launchpipes(struct conn_state * C) +{ + int on = C->nokeepalive ? 0 : 1; + + /* + * Attempt to turn keepalives on or off as requested. We ignore + * failures here since the sockets might not be of a type for which + * SO_KEEPALIVE is valid -- it is a socket level option, but protocol + * specific. In particular, it has no sensible meaning for UNIX + * sockets. + */ + (void)setsockopt(C->s, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); + (void)setsockopt(C->t, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)); + + /* Create two pipes. */ + if ((C->pipe_f = proto_pipe(C->s, C->t, C->decr, C->k_f, + &C->stat_f, callback_pipestatus, C)) == NULL) + goto err0; + if ((C->pipe_r = proto_pipe(C->t, C->s, !C->decr, C->k_r, + &C->stat_r, callback_pipestatus, C)) == NULL) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/* Drop a connection. */ +static int +dropconn(struct conn_state * C) +{ + int rc; + + /* Close the incoming connection. */ + close(C->s); + + /* Close the outgoing connection if it is open. */ + if (C->t != -1) + close(C->t); + + /* Stop connecting if a connection is in progress. */ + if (C->connect_cookie != NULL) + network_connect_cancel(C->connect_cookie); + + /* Free the target addresses if we haven't already done so. */ + sock_addr_freelist(C->sas); + + /* Stop handshaking if a handshake is in progress. */ + if (C->handshake_cookie != NULL) + proto_handshake_cancel(C->handshake_cookie); + + /* Kill timeouts if they are pending. */ + if (C->connect_timeout_cookie != NULL) + events_timer_cancel(C->connect_timeout_cookie); + if (C->handshake_timeout_cookie != NULL) + events_timer_cancel(C->handshake_timeout_cookie); + + /* Free protocol keys. */ + proto_crypt_free(C->k_f); + proto_crypt_free(C->k_r); + + /* Shut down pipes. */ + if (C->pipe_f != NULL) + proto_pipe_cancel(C->pipe_f); + if (C->pipe_r != NULL) + proto_pipe_cancel(C->pipe_r); + + /* Notify the upstream that we've dropped a connection. */ + rc = (C->callback_dead)(C->cookie); + + /* Free the connection cookie. */ + free(C); + + /* Return success/fail status. */ + return (rc); +} + +/** + * proto_conn_create(s, sas, decr, nofps, nokeepalive, K, timeo, + * callback_dead, cookie): + * Create a connection with one end at ${s} and the other end connecting to + * the target addresses ${sas}. If ${decr} is 0, encrypt the outgoing data; + * if ${decr} is nonzero, decrypt the outgoing data. If ${nofps} is non-zero, + * don't use perfect forward secrecy. Enable transport layer keep-alives (if + * applicable) on both sockets if and only if ${nokeepalive} is zero. Drop the + * connection if the handshake or connecting to the target takes more than + * ${timeo} seconds. When the connection is dropped, invoke + * ${callback_dead}(${cookie}). Free ${sas} once it is no longer needed. + */ +int +proto_conn_create(int s, struct sock_addr ** sas, int decr, int nofps, + int nokeepalive, const struct proto_secret * K, double timeo, + int (* callback_dead)(void *), void * cookie) +{ + struct conn_state * C; + + /* Bake a cookie for this connection. */ + if ((C = malloc(sizeof(struct conn_state))) == NULL) + goto err0; + C->callback_dead = callback_dead; + C->cookie = cookie; + C->sas = sas; + C->decr = decr; + C->nofps = nofps; + C->nokeepalive = nokeepalive; + C->K = K; + C->timeo = timeo; + C->s = s; + C->t = -1; + C->connect_cookie = NULL; + C->handshake_cookie = NULL; + C->connect_timeout_cookie = C->handshake_timeout_cookie = NULL; + C->k_f = C->k_r = NULL; + C->pipe_f = C->pipe_r = NULL; + C->stat_f = C->stat_r = 1; + + /* Start the connect timer. */ + if ((C->connect_timeout_cookie = events_timer_register_double( + callback_connect_timeout, C, C->timeo)) == NULL) + goto err1; + + /* Connect to target. */ + if ((C->connect_cookie = + network_connect(C->sas, callback_connect_done, C)) == NULL) + goto err2; + + /* If we're decrypting, start the handshake. */ + if (C->decr) { + if (starthandshake(C, C->s, C->decr)) + goto err3; + } + + /* Success! */ + return (0); + +err3: + network_connect_cancel(C->connect_cookie); +err2: + events_timer_cancel(C->connect_timeout_cookie); +err1: + free(C); +err0: + /* Failure! */ + return (-1); +} + +/* We have connected to the target. */ +static int +callback_connect_done(void * cookie, int t) +{ + struct conn_state * C = cookie; + + /* This connection attempt is no longer pending. */ + C->connect_cookie = NULL; + + /* Don't need the target address any more. */ + sock_addr_freelist(C->sas); + C->sas = NULL; + + /* We beat the clock. */ + events_timer_cancel(C->connect_timeout_cookie); + C->connect_timeout_cookie = NULL; + + /* Did we manage to connect? */ + if ((C->t = t) == -1) + return (dropconn(C)); + + /* If we're encrypting, start the handshake. */ + if (!C->decr) { + if (starthandshake(C, C->t, C->decr)) + goto err1; + } + + /* If we have connections and keys, start shuttling data. */ + if ((C->t != -1) && (C->k_f != NULL) && (C->k_r != NULL)) { + if (launchpipes(C)) + goto err1; + } + + /* Success! */ + return (0); + +err1: + dropconn(C); + + /* Failure! */ + return (-1); +} + +/* Connecting to the target took too long. */ +static int +callback_connect_timeout(void * cookie) +{ + struct conn_state * C = cookie; + + /* This timeout is no longer pending. */ + C->connect_timeout_cookie = NULL; + + /* + * We could free C->sas here, but from a semantic point of view it + * could still be in use by the not-yet-cancelled connect operation. + * Instead, we free it in dropconn, after cancelling the connect. + */ + + /* Drop the connection. */ + return (dropconn(C)); +} + +/* We have performed the protocol handshake. */ +static int +callback_handshake_done(void * cookie, struct proto_keys * f, + struct proto_keys * r) +{ + struct conn_state * C = cookie; + + /* The handshake is no longer in progress. */ + C->handshake_cookie = NULL; + + /* We beat the clock. */ + events_timer_cancel(C->handshake_timeout_cookie); + C->handshake_timeout_cookie = NULL; + + /* If the protocol handshake failed, drop the connection. */ + if ((f == NULL) && (r == NULL)) + return (dropconn(C)); + + /* We should have two keys. */ + assert(f != NULL); + assert(r != NULL); + + /* Record the keys so we can free them later. */ + C->k_f = f; + C->k_r = r; + + /* If we have connections and keys, start shuttling data. */ + if ((C->t != -1) && (C->k_f != NULL) && (C->k_r != NULL)) { + if (launchpipes(C)) + goto err1; + } + + /* Success! */ + return (0); + +err1: + dropconn(C); + + /* Failure! */ + return (-1); +} + +/* The protocol handshake took too long. */ +static int +callback_handshake_timeout(void * cookie) +{ + struct conn_state * C = cookie; + + /* This timeout is no longer pending. */ + C->handshake_timeout_cookie = NULL; + + /* Drop the connection. */ + return (dropconn(C)); +} + +/* The status of one of the directions has changed. */ +static int +callback_pipestatus(void * cookie) +{ + struct conn_state * C = cookie; + + /* If we have an error in either direction, kill the connection. */ + if ((C->stat_f == -1) || (C->stat_r == -1)) + return (dropconn(C)); + + /* If both directions have been shut down, kill the connection. */ + if ((C->stat_f == 0) && (C->stat_r == 0)) + return (dropconn(C)); + + /* Nothing to do. */ + return (0); +} diff --git a/proto/proto_conn.h b/proto/proto_conn.h new file mode 100644 index 0000000..5030d1d --- /dev/null +++ b/proto/proto_conn.h @@ -0,0 +1,23 @@ +#ifndef _PROTO_CONN_H_ +#define _PROTO_CONN_H_ + +/* Opaque structures. */ +struct proto_secret; +struct sock_addr; + +/** + * proto_conn_create(s, sas, decr, nofps, nokeepalive, K, timeo, + * callback_dead, cookie): + * Create a connection with one end at ${s} and the other end connecting to + * the target addresses ${sas}. If ${decr} is 0, encrypt the outgoing data; + * if ${decr} is nonzero, decrypt the outgoing data. If ${nofps} is non-zero, + * don't use perfect forward secrecy. Enable transport layer keep-alives (if + * applicable) on both sockets if and only if ${nokeepalive} is zero. Drop the + * connection if the handshake or connecting to the target takes more than + * ${timeo} seconds. When the connection is dropped, invoke + * ${callback_dead}(${cookie}). Free ${sas} once it is no longer needed. + */ +int proto_conn_create(int, struct sock_addr **, int, int, int, + const struct proto_secret *, double, int (*)(void *), void *); + +#endif /* !_CONN_H_ */ diff --git a/proto/proto_crypt.c b/proto/proto_crypt.c new file mode 100644 index 0000000..d674bfc --- /dev/null +++ b/proto/proto_crypt.c @@ -0,0 +1,354 @@ +#include +#include +#include +#include +#include + +#include + +#include "crypto_aesctr.h" +#include "crypto_verify_bytes.h" +#include "sha256.h" +#include "sysendian.h" +#include "warnp.h" + +#include "proto_crypt.h" + +struct proto_secret { + uint8_t K[32]; +}; + +struct proto_keys { + AES_KEY k_aes; + uint8_t k_hmac[32]; + uint64_t pnum; +}; + +/** + * mkkeypair(kbuf): + * Convert the 64 bytes of ${kbuf} into a protocol key structure. + */ +static struct proto_keys * +mkkeypair(uint8_t kbuf[64]) +{ + struct proto_keys * k; + + /* Allocate a structure. */ + if ((k = malloc(sizeof(struct proto_keys))) == NULL) + goto err0; + + /* Expand the AES key. */ + if (AES_set_encrypt_key(&kbuf[0], 256, &k->k_aes)) { + warn0("Error in AES_set_encrypt_key"); + goto err1; + } + + /* Fill in HMAC key. */ + memcpy(k->k_hmac, &kbuf[32], 32); + + /* The first packet will be packet number zero. */ + k->pnum = 0; + + /* Success! */ + return (k); + +err1: + free(k); +err0: + /* Failure! */ + return (NULL); +} + +/** + * proto_crypt_secret(filename): + * Read the key file ${filename} and return a protocol secret structure. + */ +struct proto_secret * +proto_crypt_secret(const char * filename) +{ + SHA256_CTX ctx; + FILE * f; + struct proto_secret * K; + uint8_t buf[BUFSIZ]; + size_t lenread; + + /* Allocate a protocol secret structure. */ + if ((K = malloc(sizeof(struct proto_secret))) == NULL) + goto err0; + + /* Open the file. */ + if ((f = fopen(filename, "r")) == NULL) { + warnp("Cannot open file: %s", filename); + goto err1; + } + + /* Initialize the SHA256 hash context. */ + SHA256_Init(&ctx); + + /* Read the file until we hit EOF. */ + while ((lenread = fread(buf, 1, BUFSIZ, f)) > 0) + SHA256_Update(&ctx, buf, lenread); + + /* Did we hit EOF? */ + if (! feof(f)) { + warnp("Error reading file: %s", filename); + goto err2; + } + + /* Close the file. */ + fclose(f); + + /* Compute the final hash. */ + SHA256_Final(K->K, &ctx); + + /* Success! */ + return (K); + +err2: + fclose(f); +err1: + free(K); +err0: + /* Failure! */ + return (NULL); +} + +/** + * proto_crypt_dhmac(K, nonce_l, nonce_r, dhmac_l, dhmac_r, decr): + * Using the key file hash ${K}, and the local and remote nonces ${nonce_l} + * and ${nonce_r}, compute the local and remote diffie-hellman parameter MAC + * keys ${dhmac_l} and ${dhmac_r}. If ${decr} is non-zero, "local" == "S" + * and "remote" == "C"; otherwise the assignments are opposite. + */ +void proto_crypt_dhmac(const struct proto_secret * K, + const uint8_t nonce_l[PCRYPT_NONCE_LEN], + const uint8_t nonce_r[PCRYPT_NONCE_LEN], + uint8_t dhmac_l[PCRYPT_DHMAC_LEN], uint8_t dhmac_r[PCRYPT_DHMAC_LEN], + int decr) +{ + uint8_t nonce_CS[PCRYPT_NONCE_LEN * 2]; + uint8_t dk_1[PCRYPT_DHMAC_LEN * 2]; + + /* Copy in nonces (in the right order). */ + memcpy(&nonce_CS[0], decr ? nonce_r : nonce_l, PCRYPT_NONCE_LEN); + memcpy(&nonce_CS[PCRYPT_NONCE_LEN], decr ? nonce_l : nonce_r, + PCRYPT_NONCE_LEN); + + /* Compute dk_1. */ + PBKDF2_SHA256(K->K, 32, nonce_CS, PCRYPT_NONCE_LEN * 2, 1, + dk_1, PCRYPT_DHMAC_LEN * 2); + + /* Copy out diffie-hellman parameter MAC keys (in the right order). */ + memcpy(decr ? dhmac_r : dhmac_l, &dk_1[0], PCRYPT_DHMAC_LEN); + memcpy(decr ? dhmac_l : dhmac_r, &dk_1[PCRYPT_DHMAC_LEN], + PCRYPT_DHMAC_LEN); +} + +/** + * proto_crypt_dh_validate(yh_r, dhmac_r): + * Return non-zero if the value ${yh_r} received from the remote party is not + * correctly MACed using the diffie-hellman parameter MAC key ${dhmac_r}, or + * if the included y value is >= the diffie-hellman group modulus. + */ +int +proto_crypt_dh_validate(const uint8_t yh_r[PCRYPT_YH_LEN], + const uint8_t dhmac_r[PCRYPT_DHMAC_LEN]) +{ + uint8_t hbuf[32]; + + /* Compute HMAC. */ + HMAC_SHA256_Buf(dhmac_r, PCRYPT_DHMAC_LEN, yh_r, CRYPTO_DH_PUBLEN, + hbuf); + + /* Check that the MAC matches. */ + if (crypto_verify_bytes(&yh_r[CRYPTO_DH_PUBLEN], hbuf, 32)) + return (1); + + /* Sanity-check the diffie-hellman value. */ + return (crypto_dh_sanitycheck(&yh_r[0])); +} + +/** + * proto_crypt_dh_generate(yh_l, x, dhmac_l, nofps): + * Using the MAC key ${dhmac_l}, generate the MACed diffie-hellman handshake + * parameter ${yh_l}. Store the diffie-hellman private value in ${x}. If + * ${nofps} is non-zero, skip diffie-hellman generation and use y = 1. + */ +int +proto_crypt_dh_generate(uint8_t yh_l[PCRYPT_YH_LEN], uint8_t x[PCRYPT_X_LEN], + const uint8_t dhmac_l[PCRYPT_DHMAC_LEN], int nofps) +{ + + /* Are we skipping the diffie-hellman generation? */ + if (nofps) { + /* Set y_l to a big-endian 1. */ + memset(yh_l, 0, CRYPTO_DH_PUBLEN - 1); + yh_l[CRYPTO_DH_PUBLEN - 1] = 1; + } else { + /* Generate diffie-hellman parameters x and y. */ + if (crypto_dh_generate(yh_l, x)) + goto err0; + } + + /* Append an HMAC. */ + HMAC_SHA256_Buf(dhmac_l, PCRYPT_DHMAC_LEN, yh_l, CRYPTO_DH_PUBLEN, + &yh_l[CRYPTO_DH_PUBLEN]); + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/** + * proto_crypt_mkkeys(K, nonce_l, nonce_r, yh_r, x, nofps, decr, eh_c, eh_s): + * Using the protocol secret ${K}, the local and remote nonces ${nonce_l} and + * ${nonce_r}, the remote MACed diffie-hellman handshake parameter ${yh_r}, + * and the local diffie-hellman secret ${x}, generate the keys ${eh_c} and + * ${eh_s}. If ${nofps} is non-zero, we are performing weak handshaking and + * y_SC is set to 1 rather than being computed. If ${decr} is non-zero, + * "local" == "S" and "remote" == "C"; otherwise the assignments are opposite. + */ +int +proto_crypt_mkkeys(const struct proto_secret * K, + const uint8_t nonce_l[PCRYPT_NONCE_LEN], + const uint8_t nonce_r[PCRYPT_NONCE_LEN], + const uint8_t yh_r[PCRYPT_YH_LEN], const uint8_t x[PCRYPT_X_LEN], + int nofps, int decr, + struct proto_keys ** eh_c, struct proto_keys ** eh_s) +{ + uint8_t nonce_y[PCRYPT_NONCE_LEN * 2 + CRYPTO_DH_KEYLEN]; + uint8_t dk_2[128]; + + /* Copy in nonces (in the right order). */ + memcpy(&nonce_y[0], decr ? nonce_r : nonce_l, PCRYPT_NONCE_LEN); + memcpy(&nonce_y[PCRYPT_NONCE_LEN], decr ? nonce_l : nonce_r, + PCRYPT_NONCE_LEN); + + /* Are we bypassing the diffie-hellman computation? */ + if (nofps) { + /* We sent y_l = 1, so y_SC is also 1. */ + memset(&nonce_y[PCRYPT_NONCE_LEN * 2], 0, + CRYPTO_DH_KEYLEN - 1); + nonce_y[PCRYPT_NONCE_LEN * 2 + CRYPTO_DH_KEYLEN - 1] = 1; + } else { + /* Perform the diffie-hellman computation. */ + if (crypto_dh_compute(yh_r, x, + &nonce_y[PCRYPT_NONCE_LEN * 2])) + goto err0; + } + + /* Compute dk_2. */ + PBKDF2_SHA256(K->K, 32, nonce_y, + PCRYPT_NONCE_LEN * 2 + CRYPTO_DH_KEYLEN, 1, dk_2, 128); + + /* Create key structures. */ + if ((*eh_c = mkkeypair(&dk_2[0])) == NULL) + goto err0; + if ((*eh_s = mkkeypair(&dk_2[64])) == NULL) + goto err1; + + /* Success! */ + return (0); + +err1: + proto_crypt_free(*eh_c); +err0: + /* Failure! */ + return (-1); +} + +/** + * proto_crypt_enc(ibuf, len, obuf, k): + * Encrypt ${len} bytes from ${ibuf} into PCRYPT_ESZ bytes using the keys in + * ${k}, and write the result into ${obuf}. + */ +void +proto_crypt_enc(uint8_t * ibuf, size_t len, uint8_t obuf[PCRYPT_ESZ], + struct proto_keys * k) +{ + HMAC_SHA256_CTX ctx; + uint8_t pnum_exp[8]; + + /* Sanity-check the length. */ + assert(len <= PCRYPT_MAXDSZ); + + /* Copy the decrypted data into the encrypted buffer. */ + memcpy(obuf, ibuf, len); + + /* Pad up to PCRYPT_MAXDSZ with zeroes. */ + memset(&obuf[len], 0, PCRYPT_MAXDSZ - len); + + /* Add the length. */ + be32enc(&obuf[PCRYPT_MAXDSZ], len); + + /* Encrypt the buffer in-place. */ + crypto_aesctr_buf(&k->k_aes, k->pnum, obuf, obuf, PCRYPT_MAXDSZ + 4); + + /* Append an HMAC. */ + be64enc(pnum_exp, k->pnum); + HMAC_SHA256_Init(&ctx, k->k_hmac, 32); + HMAC_SHA256_Update(&ctx, obuf, PCRYPT_MAXDSZ + 4); + HMAC_SHA256_Update(&ctx, pnum_exp, 8); + HMAC_SHA256_Final(&obuf[PCRYPT_MAXDSZ + 4], &ctx); + + /* Increment packet number. */ + k->pnum += 1; +} + +/** + * proto_crypt_dec(ibuf, obuf, k): + * Decrypt PCRYPT_ESZ bytes from ${ibuf} using the keys in ${k}. If the data + * is valid, write it into ${obuf} and return the length; otherwise, return + * -1. + */ +ssize_t proto_crypt_dec(uint8_t ibuf[PCRYPT_ESZ], uint8_t * obuf, + struct proto_keys * k) +{ + HMAC_SHA256_CTX ctx; + uint8_t hbuf[32]; + uint8_t pnum_exp[8]; + size_t len; + + /* Verify HMAC. */ + be64enc(pnum_exp, k->pnum); + HMAC_SHA256_Init(&ctx, k->k_hmac, 32); + HMAC_SHA256_Update(&ctx, ibuf, PCRYPT_MAXDSZ + 4); + HMAC_SHA256_Update(&ctx, pnum_exp, 8); + HMAC_SHA256_Final(hbuf, &ctx); + if (crypto_verify_bytes(hbuf, &ibuf[PCRYPT_MAXDSZ + 4], 32)) + return (-1); + + /* Decrypt the buffer in-place. */ + crypto_aesctr_buf(&k->k_aes, k->pnum, ibuf, ibuf, PCRYPT_MAXDSZ + 4); + + /* Increment packet number. */ + k->pnum += 1; + + /* Parse length. */ + len = be32dec(&ibuf[PCRYPT_MAXDSZ]); + + /* Make sure nobody is being evil here... */ + if ((len == 0) || (len > PCRYPT_MAXDSZ)) + return (-1); + + /* Copy the bytes into the output buffer. */ + memcpy(obuf, ibuf, len); + + /* Return the decrypted length. */ + return (len); +} + +/** + * proto_crypt_free(k): + * Free the protocol key structure ${k}. + */ +void +proto_crypt_free(struct proto_keys * k) +{ + + /* Free the key structure. */ + free(k); +} diff --git a/proto/proto_crypt.h b/proto/proto_crypt.h new file mode 100644 index 0000000..8801aa7 --- /dev/null +++ b/proto/proto_crypt.h @@ -0,0 +1,101 @@ +#ifndef _PCRYPT_H_ +#define _PCRYPT_H_ + +#include +#include + +#include "crypto_dh.h" + +struct proto_keys; +struct proto_secret; + +/* Size of nonce. */ +#define PCRYPT_NONCE_LEN 32 + +/* Size of temporary MAC keys used for Diffie-Hellman parameters. */ +#define PCRYPT_DHMAC_LEN 32 + +/* Size of private Diffie-Hellman value. */ +#define PCRYPT_X_LEN CRYPTO_DH_PRIVLEN + +/* Size of MACed Diffie-Hellman parameter. */ +#define PCRYPT_YH_LEN (CRYPTO_DH_PUBLEN + 32) + +/** + * proto_crypt_secret(filename): + * Read the key file ${filename} and return a protocol secret structure. + */ +struct proto_secret * proto_crypt_secret(const char *); + +/** + * proto_crypt_dhmac(K, nonce_l, nonce_r, dhmac_l, dhmac_r, decr): + * Using the protocol secret ${K}, and the local and remote nonces ${nonce_l} + * and ${nonce_r}, compute the local and remote diffie-hellman parameter MAC + * keys ${dhmac_l} and ${dhmac_r}. If ${decr} is non-zero, "local" == "S" + * and "remote" == "C"; otherwise the assignments are opposite. + */ +void proto_crypt_dhmac(const struct proto_secret *, + const uint8_t[PCRYPT_NONCE_LEN], const uint8_t[PCRYPT_NONCE_LEN], + uint8_t[PCRYPT_DHMAC_LEN], uint8_t[PCRYPT_DHMAC_LEN], int); + +/** + * proto_crypt_dh_validate(yh_r, dhmac_r): + * Return non-zero if the value ${yh_r} received from the remote party is not + * correctly MACed using the diffie-hellman parameter MAC key ${dhmac_r}, or + * if the included y value is >= the diffie-hellman group modulus. + */ +int proto_crypt_dh_validate(const uint8_t[PCRYPT_YH_LEN], + const uint8_t[PCRYPT_DHMAC_LEN]); + +/** + * proto_crypt_dh_generate(yh_l, x, dhmac_l, nofps): + * Using the MAC key ${dhmac_l}, generate the MACed diffie-hellman handshake + * parameter ${yh_l}. Store the diffie-hellman private value in ${x}. If + * ${nofps} is non-zero, skip diffie-hellman generation and use y = 1. + */ +int proto_crypt_dh_generate(uint8_t[PCRYPT_YH_LEN], uint8_t[PCRYPT_X_LEN], + const uint8_t[PCRYPT_DHMAC_LEN], int); + +/** + * proto_crypt_mkkeys(K, nonce_l, nonce_r, yh_r, x, nofps, decr, eh_c, eh_s): + * Using the protocol secret ${K}, the local and remote nonces ${nonce_l} and + * ${nonce_r}, the remote MACed diffie-hellman handshake parameter ${yh_r}, + * and the local diffie-hellman secret ${x}, generate the keys ${eh_c} and + * ${eh_s}. If ${nofps} is non-zero, we are performing weak handshaking and + * y_SC is set to 1 rather than being computed. If ${decr} is non-zero, + * "local" == "S" and "remote" == "C"; otherwise the assignments are opposite. + */ +int proto_crypt_mkkeys(const struct proto_secret *, + const uint8_t[PCRYPT_NONCE_LEN], const uint8_t[PCRYPT_NONCE_LEN], + const uint8_t[PCRYPT_YH_LEN], const uint8_t[PCRYPT_X_LEN], int, int, + struct proto_keys **, struct proto_keys **); + +/* Maximum size of an unencrypted packet. */ +#define PCRYPT_MAXDSZ 1024 + +/* Size of an encrypted packet. */ +#define PCRYPT_ESZ (PCRYPT_MAXDSZ + 4 /* len */ + 32 /* hmac */) + +/** + * proto_crypt_enc(ibuf, len, obuf, k): + * Encrypt ${len} bytes from ${ibuf} into PCRYPT_ESZ bytes using the keys in + * ${k}, and write the result into ${obuf}. + */ +void proto_crypt_enc(uint8_t *, size_t, uint8_t[PCRYPT_ESZ], + struct proto_keys *); + +/** + * proto_crypt_dec(ibuf, obuf, k): + * Decrypt PCRYPT_ESZ bytes from ${ibuf} using the keys in ${k}. If the data + * is valid, write it into ${obuf} and return the length; otherwise, return + * -1. + */ +ssize_t proto_crypt_dec(uint8_t[PCRYPT_ESZ], uint8_t *, struct proto_keys *); + +/** + * proto_crypt_free(k): + * Free the protocol key structure ${k}. + */ +void proto_crypt_free(struct proto_keys *); + +#endif /* !_PCRYPT_H_ */ diff --git a/proto/proto_handshake.c b/proto/proto_handshake.c new file mode 100644 index 0000000..0c87caa --- /dev/null +++ b/proto/proto_handshake.c @@ -0,0 +1,323 @@ +#include +#include +#include +#include + +#include "crypto_entropy.h" +#include "network.h" + +#include "proto_crypt.h" + +#include "proto_handshake.h" + +struct handshake_cookie { + int (* callback)(void *, struct proto_keys *, struct proto_keys *); + void * cookie; + int s; + int decr; + int nofps; + const struct proto_secret * K; + uint8_t nonce_local[PCRYPT_NONCE_LEN]; + uint8_t nonce_remote[PCRYPT_NONCE_LEN]; + uint8_t dhmac_local[PCRYPT_DHMAC_LEN]; + uint8_t dhmac_remote[PCRYPT_DHMAC_LEN]; + uint8_t x[PCRYPT_X_LEN]; + uint8_t yh_local[PCRYPT_YH_LEN]; + uint8_t yh_remote[PCRYPT_YH_LEN]; + void * read_cookie; + void * write_cookie; +}; + +static int callback_nonce_write(void *, ssize_t); +static int callback_nonce_read(void *, ssize_t); +static int gotnonces(struct handshake_cookie *); +static int dhread(struct handshake_cookie *); +static int callback_dh_read(void *, ssize_t); +static int dhwrite(struct handshake_cookie *); +static int callback_dh_write(void *, ssize_t); +static int handshakedone(struct handshake_cookie *); + +/* The handshake failed. Call back and clean up. */ +static int +handshakefail(struct handshake_cookie * H) +{ + int rc; + + /* Cancel any pending network read or write. */ + if (H->read_cookie != NULL) + network_read_cancel(H->read_cookie); + if (H->write_cookie != NULL) + network_write_cancel(H->write_cookie); + + /* Perform the callback. */ + rc = (H->callback)(H->cookie, NULL, NULL); + + /* Free the cookie. */ + free(H); + + /* Return status from callback. */ + return (rc); +} + +/** + * proto_handshake(s, decr, nofps, K, callback, cookie): + * Perform a protocol handshake on socket ${s}. If ${decr} is non-zero we are + * at the receiving end of the connection; otherwise at the sending end. If + * ${nofps} is non-zero, perform a "weak" handshake without forward perfect + * secrecy. The shared protocol secret is ${K}. Upon completion, invoke + * ${callback}(${cookie}, f, r) where f contains the keys needed for the + * forward direction and r contains the keys needed for the reverse direction; + * or w = r = NULL if the handshake failed. Return a cookie which can be + * passed to proto_handshake_cancel to cancel the handshake. + */ +void * +proto_handshake(int s, int decr, int nofps, const struct proto_secret * K, + int (* callback)(void *, struct proto_keys *, struct proto_keys *), + void * cookie) +{ + struct handshake_cookie * H; + + /* Bake a cookie. */ + if ((H = malloc(sizeof(struct handshake_cookie))) == NULL) + goto err0; + H->callback = callback; + H->cookie = cookie; + H->s = s; + H->decr = decr; + H->nofps = nofps; + H->K = K; + + /* Generate a 32-byte connection nonce. */ + if (crypto_entropy_read(H->nonce_local, 32)) + goto err1; + + /* Send our nonce. */ + if ((H->write_cookie = network_write(s, H->nonce_local, 32, 32, + callback_nonce_write, H)) == NULL) + goto err1; + + /* Read the other party's nonce. */ + if ((H->read_cookie = network_read(s, H->nonce_remote, 32, 32, + callback_nonce_read, H)) == NULL) + goto err2; + + /* Success! */ + return (H); + +err2: + network_write_cancel(H->write_cookie); +err1: + free(H); +err0: + /* Failure! */ + return (NULL); +} + +/* We've written our nonce. */ +static int +callback_nonce_write(void * cookie, ssize_t len) +{ + struct handshake_cookie * H = cookie; + + /* This write is no longer pending. */ + H->write_cookie = NULL; + + /* Did we successfully write? */ + if (len < 32) + return (handshakefail(H)); + + /* If the nonce read is also done, move on to the next step. */ + if (H->read_cookie == NULL) + return (gotnonces(H)); + + /* Nothing to do. */ + return (0); +} + +/* We've read a nonce. */ +static int +callback_nonce_read(void * cookie, ssize_t len) +{ + struct handshake_cookie * H = cookie; + + /* This read is no longer pending. */ + H->read_cookie = NULL; + + /* Did we successfully read? */ + if (len < 32) + return (handshakefail(H)); + + /* If the nonce write is also done, move on to the next step. */ + if (H->write_cookie == NULL) + return (gotnonces(H)); + + /* Nothing to do. */ + return (0); +} + +/* We have two nonces. Start the DH exchange. */ +static int +gotnonces(struct handshake_cookie * H) +{ + + /* Compute the diffie-hellman parameter MAC keys. */ + proto_crypt_dhmac(H->K, H->nonce_local, H->nonce_remote, + H->dhmac_local, H->dhmac_remote, H->decr); + + /* + * If we're the server, we need to read the client's diffie-hellman + * parameter. If we're the client, we need to generate and send our + * diffie-hellman parameter. + */ + if (H->decr) + return (dhread(H)); + else + return (dhwrite(H)); + + /* NOTREACHED */ +} + +/* Read a diffie-hellman parameter. */ +static int +dhread(struct handshake_cookie * H) +{ + + /* Read the remote signed diffie-hellman parameter. */ + if ((H->read_cookie = network_read(H->s, H->yh_remote, PCRYPT_YH_LEN, + PCRYPT_YH_LEN, callback_dh_read, H)) == NULL) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/* We have read a diffie-hellman parameter. */ +static int +callback_dh_read(void * cookie, ssize_t len) +{ + struct handshake_cookie * H = cookie; + + /* This read is no longer pending. */ + H->read_cookie = NULL; + + /* Did we successfully read? */ + if (len < PCRYPT_YH_LEN) + return (handshakefail(H)); + + /* Is the value we read valid? */ + if (proto_crypt_dh_validate(H->yh_remote, H->dhmac_remote)) + return (handshakefail(H)); + + /* + * If we're the server, we need to send our diffie-hellman parameter + * next. If we're the client, move on to the final computation. + */ + if (H->decr) + return (dhwrite(H)); + else + return (handshakedone(H)); + + /* NOTREACHED */ +} + +/* Generate and write a diffie-hellman parameter. */ +static int +dhwrite(struct handshake_cookie * H) +{ + + /* Generate a signed diffie-hellman parameter. */ + if (proto_crypt_dh_generate(H->yh_local, H->x, H->dhmac_local, + H->nofps)) + goto err0; + + /* Write our signed diffie-hellman parameter. */ + if ((H->write_cookie = network_write(H->s, H->yh_local, PCRYPT_YH_LEN, + PCRYPT_YH_LEN, callback_dh_write, H)) == NULL) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/* We have written our diffie-hellman parameter. */ +static int +callback_dh_write(void * cookie, ssize_t len) +{ + struct handshake_cookie * H = cookie; + + /* This write is no longer pending. */ + H->write_cookie = NULL; + + /* Did we successfully write? */ + if (len < PCRYPT_YH_LEN) + return (handshakefail(H)); + + /* + * If we're the server, move on to the final computation. If we're + * the client, we need to read the server's parameter next. + */ + if (H->decr) + return (handshakedone(H)); + else + return (dhread(H)); + + /* NOTREACHED */ +} + +/* We've got all the bits; do the final computation and callback. */ +static int +handshakedone(struct handshake_cookie * H) +{ + struct proto_keys * c; + struct proto_keys * s; + int rc; + + /* Sanity-check: There should be no callbacks in progress. */ + assert(H->read_cookie == NULL); + assert(H->write_cookie == NULL); + + /* Perform the final computation. */ + if (proto_crypt_mkkeys(H->K, H->nonce_local, H->nonce_remote, + H->yh_remote, H->x, H->nofps, H->decr, &c, &s)) + goto err0; + + /* Perform the callback. */ + rc = (H->callback)(H->cookie, c, s); + + /* Free the cookie. */ + free(H); + + /* Return status code from callback. */ + return (rc); + +err0: + /* Failure! */ + return (-1); +} + +/** + * proto_handshake_cancel(cookie): + * Cancel the handshake for which proto_handshake returned ${cookie}. + */ +void +proto_handshake_cancel(void * cookie) +{ + struct handshake_cookie * H = cookie; + + /* Cancel any in-progress network operations. */ + if (H->read_cookie != NULL) + network_read_cancel(H->read_cookie); + if (H->write_cookie != NULL) + network_write_cancel(H->write_cookie); + + /* Free the cookie. */ + free(H); +} diff --git a/proto/proto_handshake.h b/proto/proto_handshake.h new file mode 100644 index 0000000..80d3813 --- /dev/null +++ b/proto/proto_handshake.h @@ -0,0 +1,28 @@ +#ifndef _PROTO_HANDSHAKE_H_ +#define _PROTO_HANDSHAKE_H_ + +/* Opaque structures. */ +struct proto_keys; +struct proto_secret; + +/** + * proto_handshake(s, decr, nofps, K, callback, cookie): + * Perform a protocol handshake on socket ${s}. If ${decr} is non-zero we are + * at the receiving end of the connection; otherwise at the sending end. If + * ${nofps} is non-zero, perform a "weak" handshake without forward perfect + * secrecy. The shared protocol secret is ${K}. Upon completion, invoke + * ${callback}(${cookie}, f, r) where f contains the keys needed for the + * forward direction and r contains the keys needed for the reverse direction; + * or w = r = NULL if the handshake failed. Return a cookie which can be + * passed to proto_handshake_cancel to cancel the handshake. + */ +void * proto_handshake(int, int, int, const struct proto_secret *, + int (*)(void *, struct proto_keys *, struct proto_keys *), void *); + +/** + * proto_handshake_cancel(cookie): + * Cancel the handshake for which proto_handshake returned ${cookie}. + */ +void proto_handshake_cancel(void *); + +#endif /* !_PROTO_HANDSHAKE_H_ */ diff --git a/proto/proto_pipe.c b/proto/proto_pipe.c new file mode 100644 index 0000000..019f62b --- /dev/null +++ b/proto/proto_pipe.c @@ -0,0 +1,202 @@ +#include +#include + +#include +#include +#include +#include + +#include "network.h" + +#include "proto_crypt.h" + +#include "proto_pipe.h" + +struct pipe_cookie { + int (* callback)(void *); + void * cookie; + int * status; + int s_in; + int s_out; + int decr; + struct proto_keys * k; + uint8_t dbuf[PCRYPT_MAXDSZ]; + uint8_t ebuf[PCRYPT_ESZ]; + void * read_cookie; + void * write_cookie; + ssize_t wlen; +}; + +static int callback_pipe_read(void *, ssize_t); +static int callback_pipe_write(void *, ssize_t); + +/** + * proto_pipe(s_in, s_out, decr, k, status, callback, cookie): + * Read bytes from ${s_in} and write them to ${s_out}. If ${decr} is non-zero + * then use ${k} to decrypt the bytes; otherwise use ${k} to encrypt them. + * If EOF is read, set ${status} to 0, and if an error is encountered set + * ${status} to -1; in either case, invoke ${callback}(${cookie}). Return a + * cookie which can be passed to proto_pipe_cancel. + */ +void * +proto_pipe(int s_in, int s_out, int decr, struct proto_keys * k, + int * status, int (* callback)(void *), void * cookie) +{ + struct pipe_cookie * P; + + /* Bake a cookie. */ + if ((P = malloc(sizeof(struct pipe_cookie))) == NULL) + goto err0; + P->callback = callback; + P->cookie = cookie; + P->status = status; + P->s_in = s_in; + P->s_out = s_out; + P->decr = decr; + P->k = k; + P->read_cookie = NULL; + P->write_cookie = NULL; + + /* Start reading. */ + if (P->decr) { + if ((P->read_cookie = network_read(P->s_in, P->ebuf, + PCRYPT_ESZ, PCRYPT_ESZ, callback_pipe_read, P)) == NULL) + goto err1; + } else { + if ((P->read_cookie = network_read(P->s_in, P->dbuf, + PCRYPT_MAXDSZ, 1, callback_pipe_read, P)) == NULL) + goto err1; + } + + /* Success! */ + return (P); + +err1: + free(P); +err0: + /* Failure! */ + return (NULL); +} + +/* Some data has been read. */ +static int +callback_pipe_read(void * cookie, ssize_t len) +{ + struct pipe_cookie * P = cookie; + + /* This read is no longer in progress. */ + P->read_cookie = NULL; + + /* Did we read EOF? */ + if (len == 0) + goto eof; + + /* Did the read fail? */ + if (len == -1) + goto fail; + + /* Did a packet read end prematurely? */ + if ((P->decr) && (len < PCRYPT_ESZ)) + goto fail; + + /* Encrypt or decrypt the data. */ + if (P->decr) { + if ((P->wlen = proto_crypt_dec(P->ebuf, P->dbuf, P->k)) == -1) + goto fail; + } else { + proto_crypt_enc(P->dbuf, len, P->ebuf, P->k); + P->wlen = PCRYPT_ESZ; + } + + /* Write the encrypted or decrypted data. */ + if (P->decr) { + if ((P->write_cookie = network_write(P->s_out, P->dbuf, + P->wlen, P->wlen, callback_pipe_write, P)) == NULL) + goto err0; + } else { + if ((P->write_cookie = network_write(P->s_out, P->ebuf, + P->wlen, P->wlen, callback_pipe_write, P)) == NULL) + goto err0; + } + + /* Success! */ + return (0); + +fail: + /* Record that this connection is broken. */ + *(P->status) = -1; + + /* Inform the upstream that our status has changed. */ + return ((P->callback)(P->cookie)); + +eof: + /* We aren't going to write any more. */ + shutdown(P->s_out, SHUT_WR); + + /* Record that we have reached EOF. */ + *(P->status) = 0; + + /* Inform the upstream that our status has changed. */ + return ((P->callback)(P->cookie)); + +err0: + /* Failure! */ + return (-1); +} + +static int +callback_pipe_write(void * cookie, ssize_t len) +{ + struct pipe_cookie * P = cookie; + + /* This write is no longer in progress. */ + P->write_cookie = NULL; + + /* Did we fail to write everything? */ + if (len < P->wlen) + goto fail; + + /* Launch another read. */ + if (P->decr) { + if ((P->read_cookie = network_read(P->s_in, P->ebuf, + PCRYPT_ESZ, PCRYPT_ESZ, callback_pipe_read, P)) == NULL) + goto err0; + } else { + if ((P->read_cookie = network_read(P->s_in, P->dbuf, + PCRYPT_MAXDSZ, 1, callback_pipe_read, P)) == NULL) + goto err0; + } + + /* Success! */ + return (0); + +fail: + /* Record that this connection is broken. */ + *(P->status) = -1; + + /* Inform the upstream that our status has changed. */ + return ((P->callback)(P->cookie)); + +err0: + /* Failure! */ + return (-1); +} + +/** + * proto_pipe_cancel(cookie): + * Shut down the pipe created by proto_pipe for which ${cookie} was returned. + */ +void +proto_pipe_cancel(void * cookie) +{ + struct pipe_cookie * P = cookie; + + /* If a read or write is in progress, cancel it. */ + if (P->read_cookie) + network_read_cancel(P->read_cookie); + if (P->write_cookie) + network_write_cancel(P->write_cookie); + + /* Free the cookie. */ + free(P); +} diff --git a/proto/proto_pipe.h b/proto/proto_pipe.h new file mode 100644 index 0000000..1284f50 --- /dev/null +++ b/proto/proto_pipe.h @@ -0,0 +1,23 @@ +#ifndef _PROTO_PIPE_H_ +#define _PROTO_PIPE_H_ + +struct proto_keys; + +/** + * proto_pipe(s_in, s_out, decr, k, status, callback, cookie): + * Read bytes from ${s_in} and write them to ${s_out}. If ${decr} is non-zero + * then use ${k} to decrypt the bytes; otherwise use ${k} to encrypt them. + * If EOF is read, set ${status} to 0, and if an error is encountered set + * ${status} to -1; in either case, invoke ${callback}(${cookie}). Return a + * cookie which can be passed to proto_pipe_cancel. + */ +void * proto_pipe(int, int, int, struct proto_keys *, int *, + int (*)(void *), void *); + +/** + * proto_pipe_cancel(cookie): + * Shut down the pipe created by proto_pipe for which ${cookie} was returned. + */ +void proto_pipe_cancel(void *); + +#endif /* !_PROTO_PIPE_H_ */ diff --git a/spipe/Makefile b/spipe/Makefile new file mode 100644 index 0000000..d7feb7e --- /dev/null +++ b/spipe/Makefile @@ -0,0 +1,83 @@ +.POSIX: +PROG=spipe +MAN1=spipe.1 +SRCS=main.c pushbits.c proto_conn.c proto_crypt.c proto_handshake.c proto_pipe.c sha256.c elasticarray.c ptrheap.c timerqueue.c asprintf.c entropy.c monoclock.c noeintr.c sock.c warnp.c events_immediate.c events_network.c events_timer.c events.c network_buf.c network_connect.c crypto_aesctr.c crypto_dh.c crypto_dh_group14.c crypto_entropy.c crypto_verify_bytes.c +IDIRS=-I ../proto -I ../lib/alg -I ../lib/datastruct -I ../lib/util -I ../lib/events -I ../lib/network -I ../lib/crypto +LDADD_REQ=-lcrypto -lpthread + +all: ${PROG} + +install:${PROG} + mkdir -p ${BINDIR} + cp ${PROG} ${BINDIR}/_inst.${PROG}.$$$$_ && \ + strip ${BINDIR}/_inst.${PROG}.$$$$_ && \ + chmod 0555 ${BINDIR}/_inst.${PROG}.$$$$_ && \ + mv -f ${BINDIR}/_inst.${PROG}.$$$$_ ${BINDIR}/${PROG} + if ! [ -z "${MAN1DIR}" ]; then \ + for MPAGE in ${MAN1}; do \ + cp $$MPAGE ${MAN1DIR}/_inst.$$MPAGE.$$$$_ && \ + chmod 0444 ${MAN1DIR}/_inst.$$MPAGE.$$$$_ && \ + mv -f ${MAN1DIR}/_inst.$$MPAGE.$$$$_ ${MAN1DIR}/$$MPAGE; \ + done; \ + fi + +clean: + rm -f ${PROG} ${SRCS:.c=.o} + +${PROG}:${SRCS:.c=.o} + ${CC} -o ${PROG} ${SRCS:.c=.o} ${LDADD_EXTRA} ${LDADD_REQ} ${LDADD_POSIX} + +main.o: main.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c main.c -o main.o +pushbits.o: pushbits.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c pushbits.c -o pushbits.o +proto_conn.o: ../proto/proto_conn.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_conn.c -o proto_conn.o +proto_crypt.o: ../proto/proto_crypt.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_crypt.c -o proto_crypt.o +proto_handshake.o: ../proto/proto_handshake.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_handshake.c -o proto_handshake.o +proto_pipe.o: ../proto/proto_pipe.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_pipe.c -o proto_pipe.o +sha256.o: ../lib/alg/sha256.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/alg/sha256.c -o sha256.o +elasticarray.o: ../lib/datastruct/elasticarray.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/elasticarray.c -o elasticarray.o +ptrheap.o: ../lib/datastruct/ptrheap.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/ptrheap.c -o ptrheap.o +timerqueue.o: ../lib/datastruct/timerqueue.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/timerqueue.c -o timerqueue.o +asprintf.o: ../lib/util/asprintf.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/asprintf.c -o asprintf.o +entropy.o: ../lib/util/entropy.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/entropy.c -o entropy.o +monoclock.o: ../lib/util/monoclock.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/monoclock.c -o monoclock.o +noeintr.o: ../lib/util/noeintr.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/noeintr.c -o noeintr.o +sock.o: ../lib/util/sock.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/sock.c -o sock.o +warnp.o: ../lib/util/warnp.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/warnp.c -o warnp.o +events_immediate.o: ../lib/events/events_immediate.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_immediate.c -o events_immediate.o +events_network.o: ../lib/events/events_network.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_network.c -o events_network.o +events_timer.o: ../lib/events/events_timer.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_timer.c -o events_timer.o +events.o: ../lib/events/events.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events.c -o events.o +network_buf.o: ../lib/network/network_buf.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/network/network_buf.c -o network_buf.o +network_connect.o: ../lib/network/network_connect.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/network/network_connect.c -o network_connect.o +crypto_aesctr.o: ../lib/crypto/crypto_aesctr.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_aesctr.c -o crypto_aesctr.o +crypto_dh.o: ../lib/crypto/crypto_dh.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_dh.c -o crypto_dh.o +crypto_dh_group14.o: ../lib/crypto/crypto_dh_group14.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_dh_group14.c -o crypto_dh_group14.o +crypto_entropy.o: ../lib/crypto/crypto_entropy.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_entropy.c -o crypto_entropy.o +crypto_verify_bytes.o: ../lib/crypto/crypto_verify_bytes.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_verify_bytes.c -o crypto_verify_bytes.o diff --git a/spipe/README b/spipe/README new file mode 100644 index 0000000..05c8642 --- /dev/null +++ b/spipe/README @@ -0,0 +1,20 @@ +spipe usage +=========== + +usage: spipe -t -k [-fj] [-o ] + +Options: + -t + Address to which spipe should connect. + -k + Use the provided key file to authenticate and encrypt. + -f + Use fast/weak handshaking: This reduces the CPU time spent in the + initial connection setup, at the expense of losing perfect forward + secrecy. + -j + Disable transport layer keep-alives. (By default they are enabled.) + -o + Timeout, in seconds, after which an attempt to connect to the target + or a protocol handshake will be aborted (and the connection dropped) + if not completed. Defaults to 5s. diff --git a/spipe/main.c b/spipe/main.c new file mode 100644 index 0000000..960f421 --- /dev/null +++ b/spipe/main.c @@ -0,0 +1,170 @@ +#include + +#include +#include +#include +#include +#include + +#include "asprintf.h" +#include "events.h" +#include "sha256.h" +#include "sock.h" +#include "warnp.h" + +#include "proto_conn.h" +#include "proto_crypt.h" + +#include "pushbits.h" + +static int +callback_conndied(void * cookie) +{ + + (void)cookie; /* UNUSED */ + + /* We're done! */ + exit(0); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: spipe -t -k " + " [-fj] [-o ]\n"); + exit(1); +} + +/* Simplify error-handling in command-line parse loop. */ +#define OPT_EPARSE(opt, arg) do { \ + warnp("Error parsing argument: -%c %s", opt, arg); \ + exit(1); \ +} while (0) + +int +main(int argc, char * argv[]) +{ + /* Command-line parameters. */ + int opt_f = 0; + int opt_j = 0; + const char * opt_k = NULL; + double opt_o = 0.0; + const char * opt_t = NULL; + + /* Working variables. */ + struct sock_addr ** sas_t; + struct proto_secret * K; + int ch; + int s[2]; + + WARNP_INIT; + + /* Parse the command line. */ + while ((ch = getopt(argc, argv, "fjk:o:t:")) != -1) { + switch (ch) { + case 'f': + if (opt_f) + usage(); + opt_f = 1; + break; + case 'j': + if (opt_j) + usage(); + opt_j = 1; + break; + case 'k': + if (opt_k) + usage(); + opt_k = optarg; + break; + case 'o': + if (opt_o != 0.0) + usage(); + if ((opt_o = strtod(optarg, NULL)) == 0.0) { + warn0("Invalid option: -o %s", optarg); + exit(1); + } + break; + case 't': + if (opt_t) + usage(); + opt_t = optarg; + break; + default: + usage(); + } + } + + /* We should have processed all the arguments. */ + if (argc != optind) + usage(); + + /* Set defaults. */ + if (opt_o == 0.0) + opt_o = 5.0; + + /* Sanity-check options. */ + if (opt_k == NULL) + usage(); + if (!(opt_o > 0.0)) + usage(); + if (opt_t == NULL) + usage(); + + /* Resolve target address. */ + if ((sas_t = sock_resolve(opt_t)) == NULL) { + warnp("Error resolving socket address: %s", opt_t); + exit(1); + } + if (sas_t[0] == NULL) { + warn0("No addresses found for %s", opt_t); + exit(1); + } + + /* Load the keying data. */ + if ((K = proto_crypt_secret(opt_k)) == NULL) { + warnp("Error reading shared secret"); + exit(1); + } + + /* + * Create a socket pair to push bits through. The spiped protocol + * code expects to be handed a socket to read/write bits to, and our + * stdin/stdout might not be sockets (in fact, almost certainly aren't + * sockets); so we'll hand one end of the socket pair to the spiped + * protocol code and shuttle bits between stdin/stdout and the other + * end of the socket pair ourselves. + */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, s)) { + warnp("socketpair"); + exit(1); + } + + /* Set up a connection. */ + if (proto_conn_create(s[1], sas_t, 0, opt_f, opt_j, K, opt_o, + callback_conndied, NULL)) { + warnp("Could not set up connection"); + exit(1); + } + + /* Push bits from stdin into the socket. */ + if (pushbits(STDIN_FILENO, s[0]) || pushbits(s[0], STDOUT_FILENO)) { + warnp("Could not push bits"); + exit(1); + } + + /* Loop until we die. */ + do { + if (events_run()) { + warnp("Error running event loop"); + exit(1); + } + } while(1); + + /* NOTREACHED */ + /* + * If we could reach this point, we would free memory, close sockets, + * and otherwise clean up here. + */ +} diff --git a/spipe/pushbits.c b/spipe/pushbits.c new file mode 100644 index 0000000..79910da --- /dev/null +++ b/spipe/pushbits.c @@ -0,0 +1,101 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "noeintr.h" +#include "warnp.h" + +#include "pushbits.h" + +struct push { + uint8_t buf[BUFSIZ]; + int in; + int out; +}; + +/* Bit-pushing thread. */ +static void * +workthread(void * cookie) +{ + struct push * P = cookie; + ssize_t readlen; + + /* Infinite loop unless we hit EOF or an error. */ + do { + /* Read data and die on error. */ + if ((readlen = read(P->in, P->buf, BUFSIZ)) == -1) { + if (errno == EINTR) + continue; + warnp("Error reading"); + exit(1); + } + + /* If we hit EOF, exit the loop. */ + if (readlen == 0) + break; + + /* Write the data back out. */ + if (noeintr_write(P->out, &P->buf, readlen) != readlen) { + warnp("Error writing"); + exit(1); + } + } while (1); + + /* Close the descriptor we hit EOF on. */ + close(P->in); + + /* + * Try to shut down the descriptor we're writing to. Ignore ENOTSOCK, + * since it might, indeed, not be a socket. + */ + if (shutdown(P->out, SHUT_WR)) { + if (errno != ENOTSOCK) { + warnp("Error shutting down socket"); + exit(1); + } + } + + /* Free our parameters. */ + free(P); + + /* We're done. */ + return (NULL); +} + +/** + * pushbits(in, out): + * Create a thread which copies data from ${in} to ${out}. + */ +int +pushbits(int in, int out) +{ + struct push * P; + pthread_t thr; + int rc; + + /* Allocate structure. */ + if ((P = malloc(sizeof(struct push))) == NULL) + goto err0; + P->in = in; + P->out = out; + + /* Create thread. */ + if ((rc = pthread_create(&thr, NULL, workthread, P)) != 0) { + warn0("pthread_create: %s", strerror(rc)); + goto err1; + } + + /* Success! */ + return (0); + +err1: + free(P); +err0: + /* Failure! */ + return (-1); +} diff --git a/spipe/pushbits.h b/spipe/pushbits.h new file mode 100644 index 0000000..6110b00 --- /dev/null +++ b/spipe/pushbits.h @@ -0,0 +1,10 @@ +#ifndef _PUSHBITS_H_ +#define _PUSHBITS_H_ + +/** + * pushbits(in, out): + * Create a thread which copies data from ${in} to ${out}. + */ +int pushbits(int, int); + +#endif /* !_PUSHBITS_H_ */ diff --git a/spipe/spipe.1 b/spipe/spipe.1 new file mode 100644 index 0000000..5086d6b --- /dev/null +++ b/spipe/spipe.1 @@ -0,0 +1,55 @@ +.\"- +.\" Copyright (c) 2012 Andreas Olsson +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.TH SPIPE 1 "April 2, 2013" "spiped 1.3.0" "spipe README" +.SH NAME +spipe \- spiped client utility +.SH SYNOPSIS +.B spipe +\-t +\-k +[\-fj] +[\-o ] +.SH OPTIONS +.TP +.B \-t +Address to which spipe should connect. +.TP +.B \-k +Use the provided key file to authenticate and encrypt. +.TP +.B \-f +Use fast/weak handshaking: This reduces the CPU time spent in the +initial connection setup, at the expense of losing perfect forward +secrecy. +.TP +.B \-j +Disable transport layer keep-alives. +(By default they are enabled.) +.TP +.B \-o +Timeout, in seconds, after which an attempt to connect to the target +or a protocol handshake will be aborted (and the connection dropped) +if not completed. Defaults to 5s. +.SH SEE ALSO +.BR spiped (1). diff --git a/spiped/Makefile b/spiped/Makefile new file mode 100644 index 0000000..4650eb9 --- /dev/null +++ b/spiped/Makefile @@ -0,0 +1,91 @@ +.POSIX: +PROG=spiped +MAN1=spiped.1 +SRCS=main.c dispatch.c proto_conn.c proto_crypt.c proto_handshake.c proto_pipe.c sha256.c elasticarray.c ptrheap.c timerqueue.c dnsthread.c asprintf.c daemonize.c entropy.c monoclock.c noeintr.c sock.c sock_util.c warnp.c events_immediate.c events_network.c events_timer.c events.c network_accept.c network_buf.c network_connect.c crypto_aesctr.c crypto_dh.c crypto_dh_group14.c crypto_entropy.c crypto_verify_bytes.c +IDIRS=-I ../proto -I ../lib/alg -I ../lib/datastruct -I ../lib/dnsthread -I ../lib/util -I ../lib/events -I ../lib/network -I ../lib/crypto +LDADD_REQ=-lcrypto -lpthread + +all: ${PROG} + +install:${PROG} + mkdir -p ${BINDIR} + cp ${PROG} ${BINDIR}/_inst.${PROG}.$$$$_ && \ + strip ${BINDIR}/_inst.${PROG}.$$$$_ && \ + chmod 0555 ${BINDIR}/_inst.${PROG}.$$$$_ && \ + mv -f ${BINDIR}/_inst.${PROG}.$$$$_ ${BINDIR}/${PROG} + if ! [ -z "${MAN1DIR}" ]; then \ + for MPAGE in ${MAN1}; do \ + cp $$MPAGE ${MAN1DIR}/_inst.$$MPAGE.$$$$_ && \ + chmod 0444 ${MAN1DIR}/_inst.$$MPAGE.$$$$_ && \ + mv -f ${MAN1DIR}/_inst.$$MPAGE.$$$$_ ${MAN1DIR}/$$MPAGE; \ + done; \ + fi + +clean: + rm -f ${PROG} ${SRCS:.c=.o} + +${PROG}:${SRCS:.c=.o} + ${CC} -o ${PROG} ${SRCS:.c=.o} ${LDADD_EXTRA} ${LDADD_REQ} ${LDADD_POSIX} + +main.o: main.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c main.c -o main.o +dispatch.o: dispatch.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c dispatch.c -o dispatch.o +proto_conn.o: ../proto/proto_conn.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_conn.c -o proto_conn.o +proto_crypt.o: ../proto/proto_crypt.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_crypt.c -o proto_crypt.o +proto_handshake.o: ../proto/proto_handshake.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_handshake.c -o proto_handshake.o +proto_pipe.o: ../proto/proto_pipe.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../proto/proto_pipe.c -o proto_pipe.o +sha256.o: ../lib/alg/sha256.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/alg/sha256.c -o sha256.o +elasticarray.o: ../lib/datastruct/elasticarray.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/elasticarray.c -o elasticarray.o +ptrheap.o: ../lib/datastruct/ptrheap.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/ptrheap.c -o ptrheap.o +timerqueue.o: ../lib/datastruct/timerqueue.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/datastruct/timerqueue.c -o timerqueue.o +dnsthread.o: ../lib/dnsthread/dnsthread.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/dnsthread/dnsthread.c -o dnsthread.o +asprintf.o: ../lib/util/asprintf.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/asprintf.c -o asprintf.o +daemonize.o: ../lib/util/daemonize.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/daemonize.c -o daemonize.o +entropy.o: ../lib/util/entropy.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/entropy.c -o entropy.o +monoclock.o: ../lib/util/monoclock.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/monoclock.c -o monoclock.o +noeintr.o: ../lib/util/noeintr.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/noeintr.c -o noeintr.o +sock.o: ../lib/util/sock.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/sock.c -o sock.o +sock_util.o: ../lib/util/sock_util.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/sock_util.c -o sock_util.o +warnp.o: ../lib/util/warnp.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/util/warnp.c -o warnp.o +events_immediate.o: ../lib/events/events_immediate.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_immediate.c -o events_immediate.o +events_network.o: ../lib/events/events_network.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_network.c -o events_network.o +events_timer.o: ../lib/events/events_timer.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events_timer.c -o events_timer.o +events.o: ../lib/events/events.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/events/events.c -o events.o +network_accept.o: ../lib/network/network_accept.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/network/network_accept.c -o network_accept.o +network_buf.o: ../lib/network/network_buf.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/network/network_buf.c -o network_buf.o +network_connect.o: ../lib/network/network_connect.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/network/network_connect.c -o network_connect.o +crypto_aesctr.o: ../lib/crypto/crypto_aesctr.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_aesctr.c -o crypto_aesctr.o +crypto_dh.o: ../lib/crypto/crypto_dh.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_dh.c -o crypto_dh.o +crypto_dh_group14.o: ../lib/crypto/crypto_dh_group14.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_dh_group14.c -o crypto_dh_group14.o +crypto_entropy.o: ../lib/crypto/crypto_entropy.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_entropy.c -o crypto_entropy.o +crypto_verify_bytes.o: ../lib/crypto/crypto_verify_bytes.c + ${CC} ${CFLAGS} -D_POSIX_C_SOURCE=200809L ${IDIRS} -c ../lib/crypto/crypto_verify_bytes.c -o crypto_verify_bytes.o diff --git a/spiped/README b/spiped/README new file mode 100644 index 0000000..2cc68b2 --- /dev/null +++ b/spiped/README @@ -0,0 +1,59 @@ +spiped usage +============ + +usage: spiped {-e | -d} -s -t -k + [-DfFj] [-n ] [-o ] [-p ] + [{-r | -R}] + +Options: + -e + Take unencrypted connections from the source socket and send + encrypted connections to the target socket. + -d + Take encrypted connections from the source socket and send + unencrypted connections to the target socket. + -s + Address on which spiped should listen for incoming connections. + Must be in one of the following formats: + /absolute/path/to/unix/socket + host.name:port + [ip.v4.ad.dr]:port + [ipv6::addr]:port + Note that hostnames are resolved when spiped is launched and are not + re-resolved later; thus if DNS entries change spiped will continue to + connect to the expired address. + -t + Address to which spiped should connect. + -k + Use the provided key file to authenticate and encrypt. + -D + Wait for DNS. Normally when spiped is launched it resolves addresses + and binds to its source socket before the parent process returns; with + this option it will daemonize first and retry failed DNS lookups until + they succeed. This allows spiped to launch even if DNS isn't set up + yet, but at the expense of losing the guarantee that once spiped has + finished launching it will be ready to create pipes. + -f + Use fast/weak handshaking: This reduces the CPU time spent in the + initial connection setup, at the expense of losing perfect forward + secrecy. + -F + Run in foreground. This can be useful with systems like daemontools. + -j + Disable transport layer keep-alives. (By default they are enabled.) + -n + Limit on the number of simultaneous connections allowed. Defaults + to 100 connections. + -o + Timeout, in seconds, after which an attempt to connect to the target + or a protocol handshake will be aborted (and the connection dropped) + if not completed. Defaults to 5s. + -p + File to which spiped's process ID should be written. Defaults to + .pid (in the current directory if is + not an absolute path). + -r + Re-resolve the address of every seconds. + Defaults to re-resolution every 60 seconds. + -R + Do not re-resolve the address of . diff --git a/spiped/dispatch.c b/spiped/dispatch.c new file mode 100644 index 0000000..e21540b --- /dev/null +++ b/spiped/dispatch.c @@ -0,0 +1,211 @@ +#include +#include +#include + +#include "dnsthread.h" +#include "events.h" +#include "network.h" +#include "sock.h" +#include "sock_util.h" +#include "warnp.h" + +#include "proto_conn.h" + +#include "dispatch.h" + +struct accept_state { + int s; + const char * tgt; + struct sock_addr ** sas; + double rtime; + int decr; + int nofps; + int nokeepalive; + const struct proto_secret * K; + size_t nconn; + size_t nconn_max; + double timeo; + void * accept_cookie; + DNSTHREAD T; +}; + +static int callback_gotconn(void *, int); +static int callback_resolveagain(void *); + +/* Callback from address resolution. */ +static int +callback_resolve(void * cookie, struct sock_addr ** sas) +{ + struct accept_state * A = cookie; + + /* If the address resolution succeeded... */ + if (sas != NULL) { + /* Free the old addresses. */ + sock_addr_freelist(A->sas); + + /* Use the new addresses. */ + A->sas = sas; + } + + /* Wait a while before resolving again. */ + if (events_timer_register_double(callback_resolveagain, + A, A->rtime) == NULL) + goto err0; + + /* Success! */ + return (0); + +err0: + /* Failure! */ + return (-1); +} + +/* Timer callback to trigger another address resolution. */ +static int +callback_resolveagain(void * cookie) +{ + struct accept_state * A = cookie; + + /* Re-resolve the target address. */ + return (dnsthread_resolveone(A->T, A->tgt, callback_resolve, A)); +} + +/* Non-blocking accept, if we can have more connections. */ +static int +doaccept(struct accept_state * A) +{ + int rc = 0; + + /* If we can, accept a new connection. */ + if ((A->nconn < A->nconn_max) && (A->accept_cookie == NULL)) { + if ((A->accept_cookie = + network_accept(A->s, callback_gotconn, A)) == NULL) + rc = -1; + } + + /* Return success/fail status. */ + return (rc); +} + +/* A connection has closed. Accept more if necessary. */ +static int +callback_conndied(void * cookie) +{ + struct accept_state * A = cookie; + + /* We've lost a connection. */ + A->nconn -= 1; + + /* Maybe accept more connections. */ + return (doaccept(A)); +} + +/* Handle an incoming connection. */ +static int +callback_gotconn(void * cookie, int s) +{ + struct accept_state * A = cookie; + struct sock_addr ** sas; + + /* This accept is no longer in progress. */ + A->accept_cookie = NULL; + + /* If we got a -1 descriptor, something went seriously wrong. */ + if (s == -1) { + warnp("network_accept failed"); + goto err0; + } + + /* We have gained a connection. */ + A->nconn += 1; + + /* Duplicate the target address list. */ + if ((sas = sock_addr_duplist(A->sas)) == NULL) + goto err1; + + /* Create a new connection. */ + if (proto_conn_create(s, sas, A->decr, A->nofps, A->nokeepalive, + A->K, A->timeo, callback_conndied, A)) { + warnp("Failure setting up new connection"); + goto err2; + } + + /* Accept another connection if we can. */ + if (doaccept(A)) + goto err0; + + /* Success! */ + return (0); + +err2: + sock_addr_freelist(sas); +err1: + A->nconn -= 1; + close(s); +err0: + /* Failure! */ + return (-1); +} + +/** + * dispatch_accept(s, tgt, rtime, sas, decr, nofps, nokeepalive, K, nconn_max, + * timeo): + * Start accepting connections on the socket ${s}. Connect to the target + * ${tgt}, re-resolving it every ${rtime} seconds if ${rtime} > 0; on address + * resolution failure use the most recent successfully obtained addresses, or + * the addresses ${sas}. If ${decr} is 0, encrypt the outgoing connections; if + * ${decr} is non-zero, decrypt the incoming connections. Don't accept more + * than ${nconn_max} connections. If ${nofps} is non-zero, don't use perfect + * forward secrecy. Enable transport layer keep-alives (if applicable) if and + * only if ${nokeepalive} is zero. Drop connections if the handshake or + * connecting to the target takes more than ${timeo} seconds. + */ +int +dispatch_accept(int s, const char * tgt, double rtime, struct sock_addr ** sas, + int decr, int nofps, int nokeepalive, const struct proto_secret * K, + size_t nconn_max, double timeo) +{ + struct accept_state * A; + + /* Bake a cookie. */ + if ((A = malloc(sizeof(struct accept_state))) == NULL) + goto err0; + A->s = s; + A->tgt = tgt; + A->sas = sas; + A->rtime = rtime; + A->decr = decr; + A->nofps = nofps; + A->nokeepalive = nokeepalive; + A->K = K; + A->nconn = 0; + A->nconn_max = nconn_max; + A->timeo = timeo; + A->accept_cookie = NULL; + + /* If address re-resolution is enabled... */ + if (rtime > 0.0) { + /* Launch an address resolution thread. */ + A->T = dnsthread_spawn(); + + /* Re-resolve the target address after a while. */ + if (events_timer_register_double(callback_resolveagain, + A, A->rtime) == NULL) + goto err1; + } + + /* Accept a connection. */ + if (doaccept(A)) + goto err1; + + /* Success! */ + return (0); + +err1: + if (rtime > 0.0) + dnsthread_kill(A->T); + free(A); +err0: + /* Failure! */ + return (-1); +} diff --git a/spiped/dispatch.h b/spiped/dispatch.h new file mode 100644 index 0000000..e2a1680 --- /dev/null +++ b/spiped/dispatch.h @@ -0,0 +1,26 @@ +#ifndef _DISPATCH_H_ +#define _DISPATCH_H_ + +#include + +/* Opaque structures. */ +struct proto_secret; +struct sock_addr; + +/** + * dispatch_accept(s, tgt, rtime, sas, decr, nofps, nokeepalive, K, nconn_max, + * timeo): + * Start accepting connections on the socket ${s}. Connect to the target + * ${tgt}, re-resolving it every ${rtime} seconds if ${rtime} > 0; on address + * resolution failure use the most recent successfully obtained addresses, or + * the addresses ${sas}. If ${decr} is 0, encrypt the outgoing connections; if + * ${decr} is non-zero, decrypt the incoming connections. Don't accept more + * than ${nconn_max} connections. If ${nofps} is non-zero, don't use perfect + * forward secrecy. Enable transport layer keep-alives (if applicable) if and + * only if ${nokeepalive} is zero. Drop connections if the handshake or + * connecting to the target takes more than ${timeo} seconds. + */ +int dispatch_accept(int, const char *, double, struct sock_addr **, int, int, + int, const struct proto_secret *, size_t, double); + +#endif /* !_DISPATCH_H_ */ diff --git a/spiped/main.c b/spiped/main.c new file mode 100644 index 0000000..4ceeb85 --- /dev/null +++ b/spiped/main.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include + +#include "asprintf.h" +#include "daemonize.h" +#include "events.h" +#include "sock.h" +#include "warnp.h" + +#include "dispatch.h" +#include "proto_crypt.h" + +static void +usage(void) +{ + + fprintf(stderr, "usage: spiped {-e | -d} -s " + "-t -k \n" + " [-DfFj] [-n ] " + "[-o ] [-p ]\n" + " [{-r | -R}]\n"); + exit(1); +} + +/* Simplify error-handling in command-line parse loop. */ +#define OPT_EPARSE(opt, arg) do { \ + warnp("Error parsing argument: -%c %s", opt, arg); \ + exit(1); \ +} while (0) + +int +main(int argc, char * argv[]) +{ + /* Command-line parameters. */ + int opt_d = 0; + int opt_D = 0; + int opt_e = 0; + int opt_f = 0; + int opt_F = 0; + int opt_j = 0; + const char * opt_k = NULL; + intmax_t opt_n = 0; + double opt_o = 0.0; + char * opt_p = NULL; + double opt_r = 0.0; + int opt_R = 0; + const char * opt_s = NULL; + const char * opt_t = NULL; + + /* Working variables. */ + struct sock_addr ** sas_s; + struct sock_addr ** sas_t; + struct proto_secret * K; + int ch; + int s; + + WARNP_INIT; + + /* Parse the command line. */ + while ((ch = getopt(argc, argv, "dDefFjk:n:o:r:Rp:s:t:")) != -1) { + switch (ch) { + case 'd': + if (opt_d || opt_e) + usage(); + opt_d = 1; + break; + case 'D': + if (opt_D) + usage(); + opt_D = 1; + break; + case 'e': + if (opt_d || opt_e) + usage(); + opt_e = 1; + break; + case 'f': + if (opt_f) + usage(); + opt_f = 1; + break; + case 'F': + if (opt_F) + usage(); + opt_F = 1; + break; + case 'j': + if (opt_j) + usage(); + opt_j = 1; + break; + case 'k': + if (opt_k) + usage(); + opt_k = optarg; + break; + case 'n': + if (opt_n != 0) + usage(); + if ((opt_n = strtoimax(optarg, NULL, 0)) == 0) { + warn0("Invalid option: -n %s", optarg); + exit(1); + } + break; + case 'o': + if (opt_o != 0.0) + usage(); + if ((opt_o = strtod(optarg, NULL)) == 0.0) { + warn0("Invalid option: -o %s", optarg); + exit(1); + } + break; + case 'p': + if (opt_p) + usage(); + if ((opt_p = strdup(optarg)) == NULL) + OPT_EPARSE(ch, optarg); + break; + case 'r': + if (opt_r != 0.0) + usage(); + if ((opt_r = strtod(optarg, NULL)) == 0.0) { + warn0("Invalid option: -r %s", optarg); + exit(1); + } + break; + case 'R': + if (opt_R) + usage(); + opt_R = 1; + break; + case 's': + if (opt_s) + usage(); + opt_s = optarg; + break; + case 't': + if (opt_t) + usage(); + opt_t = optarg; + break; + default: + usage(); + } + } + + /* We should have processed all the arguments. */ + if (argc != optind) + usage(); + + /* Set defaults. */ + if (opt_n == 0) + opt_n = 100; + if (opt_o == 0.0) + opt_o = 5.0; + if (opt_r == 0.0) + opt_r = 60.0; + + /* Sanity-check options. */ + if (!opt_d && !opt_e) + usage(); + if (opt_k == NULL) + usage(); + if ((opt_n < 0) || (opt_n > 500)) + usage(); + if (!(opt_o > 0.0)) + usage(); + if ((opt_r != 60.0) && opt_R) + usage(); + if (opt_s == NULL) + usage(); + if (opt_t == NULL) + usage(); + + /* Figure out where our pid should be written. */ + if (opt_p == NULL) { + if (asprintf(&opt_p, "%s.pid", opt_s) == -1) { + warnp("asprintf"); + exit(1); + } + } + + /* Daemonize early if we're going to wait for DNS to be ready. */ + if (opt_D && !opt_F && daemonize(opt_p)) { + warnp("Failed to daemonize"); + exit(1); + } + + /* Resolve source address. */ + while ((sas_s = sock_resolve(opt_s)) == NULL) { + if (!opt_D) { + warnp("Error resolving socket address: %s", opt_s); + exit(1); + } + sleep(1); + } + if (sas_s[0] == NULL) { + warn0("No addresses found for %s", opt_s); + exit(1); + } + + /* Resolve target address. */ + while ((sas_t = sock_resolve(opt_t)) == NULL) { + if (!opt_D) { + warnp("Error resolving socket address: %s", opt_t); + exit(1); + } + sleep(1); + } + if (sas_t[0] == NULL) { + warn0("No addresses found for %s", opt_t); + exit(1); + } + + /* Load the keying data. */ + if ((K = proto_crypt_secret(opt_k)) == NULL) { + warnp("Error reading shared secret"); + exit(1); + } + + /* Create and bind a socket, and mark it as listening. */ + if (sas_s[1] != NULL) + warn0("Listening on first of multiple addresses found for %s", + opt_s); + if ((s = sock_listener(sas_s[0])) == -1) + exit(1); + + /* Daemonize and write pid. */ + if (!opt_F && daemonize(opt_p)) { + warnp("Failed to daemonize"); + exit(1); + } + + /* Start accepting connections. */ + if (dispatch_accept(s, opt_t, opt_R ? 0.0 : opt_r, sas_t, opt_d, + opt_f, opt_j, K, opt_n, opt_o)) { + warnp("Failed to initialize connection acceptor"); + exit(1); + } + + /* Infinite loop handling events. */ + do { + if (events_run()) { + warnp("Error running event loop"); + exit(1); + } + } while (1); + + /* NOTREACHED */ + /* + * If we could reach this point, we would free memory, close sockets, + * and otherwise clean up here. + */ +} diff --git a/spiped/spiped.1 b/spiped/spiped.1 new file mode 100644 index 0000000..21b193f --- /dev/null +++ b/spiped/spiped.1 @@ -0,0 +1,106 @@ +.\"- +.\" Copyright (c) 2012 Andreas Olsson +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.TH SPIPED 1 "April 2, 2013" "spiped 1.3.0" "spiped README" +.SH NAME +spiped \- secure pipe daemon +.SH SYNOPSIS +.B spiped +{\-e | \-d} \-s +\-t +\-k +.br +[\-DfFj] +[\-n ] +[\-o ] +[\-p ] +[{\-r | \-R}] +.SH OPTIONS +.TP +.B \-e +Take unencrypted connections from the source socket and send +encrypted connections to the target socket. +.TP +.B \-d +Take encrypted connections from the source socket and send +unencrypted connections to the target socket. +.TP +.B \-s +Address on which spiped should listen for incoming connections. +Must be in one of the following formats: +/absolute/path/to/unix/socket +host.name:port +[ip.v4.ad.dr]:port +[ipv6::addr]:port +Note that hostnames are resolved when spiped is launched and are not +re\-resolved later; thus if DNS entries change spiped will continue to +connect to the expired address. +.TP +.B \-t +Address to which spiped should connect. +.TP +.B \-k +Use the provided key file to authenticate and encrypt. +.TP +.B \-D +Wait for DNS. Normally when spiped is launched it resolves addresses +and binds to its source socket before the parent process returns; with +this option it will daemonize first and retry failed DNS lookups until +they succeed. This allows spiped to launch even if DNS isn't set up +yet, but at the expense of losing the guarantee that once spiped has +finished launching it will be ready to create pipes. +.TP +.B \-f +Use fast/weak handshaking: This reduces the CPU time spent in the +initial connection setup, at the expense of losing perfect forward +secrecy. +.TP +.B \-F +Run in foreground. This can be useful with systems like daemontools. +.TP +.B \-j +Disable transport layer keep-alives. +(By default they are enabled.) +.TP +.B \-n +Limit on the number of simultaneous connections allowed. Defaults +to 100 connections. +.TP +.B \-o +Timeout, in seconds, after which an attempt to connect to the target +or a protocol handshake will be aborted (and the connection dropped) +if not completed. Defaults to 5s. +.TP +.B \-p +File to which spiped's process ID should be written. Defaults to +.pid (in the current directory if is +not an absolute path). +.TP +.B \-r +Re-resolve the address of every seconds. +Defaults to re-resolution every 60 seconds. +.TP +.B \-R +Disable target address re-resolution. +.SH SEE ALSO +.BR spipe (1).