Signed-off-by: Ilari Liusvaara <ilari.liusvaara@xxxxxxxxxxx> --- Makefile | 25 +- git-over-tls/Makefile | 45 ++ git-over-tls/cbuffer.c | 504 +++++++++++++++ git-over-tls/cbuffer.h | 304 +++++++++ git-over-tls/user.c | 1597 ++++++++++++++++++++++++++++++++++++++++++++++++ git-over-tls/user.h | 367 +++++++++++ 6 files changed, 2839 insertions(+), 3 deletions(-) create mode 100644 git-over-tls/Makefile create mode 100644 git-over-tls/cbuffer.c create mode 100644 git-over-tls/cbuffer.h create mode 100644 git-over-tls/user.c create mode 100644 git-over-tls/user.h diff --git a/Makefile b/Makefile index 910f471..b11fed4 100644 --- a/Makefile +++ b/Makefile @@ -163,6 +163,10 @@ all:: # apostrophes to be ASCII so that cut&pasting examples to the shell # will work. # +# Define NO_GNUTLS if you don't want to use GnuTLS. +# +# Define NO_SRP if you don't want SRP support. +# # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's # MakeMaker (e.g. using ActiveState under Cygwin). # @@ -171,7 +175,6 @@ all:: # Define NO_PYTHON if you do not want Python scripts or libraries at all. # # Define NO_TCLTK if you do not want Tcl/Tk GUI. -# # The TCL_PATH variable governs the location of the Tcl interpreter # used to optimize git-gui for your system. Only used if NO_TCLTK # is not set. Defaults to the bare 'tclsh'. @@ -295,7 +298,7 @@ TCL_PATH = tclsh TCLTK_PATH = wish PTHREAD_LIBS = -lpthread -export TCL_PATH TCLTK_PATH +export TCL_PATH TCLTK_PATH CC # sparse is architecture-neutral, which means that we need to tell it # explicitly what architecture to check for. Fix this up for yours.. @@ -1284,6 +1287,9 @@ endif ifdef NO_IPV6 BASIC_CFLAGS += -DNO_IPV6 endif +ifdef NO_SRP + BASIC_CFLAGS += -DDISABLE_SRP +endif ifdef NO_UINTMAX_T BASIC_CFLAGS += -Duintmax_t=uint32_t endif @@ -1433,6 +1439,10 @@ TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) LIBS = $(GITLIBS) $(EXTLIBS) +# In case some subdirectory wants to use git API libs. +GIT_PROGRAM_LINKLIBS = $(LIBS) +export GIT_PROGRAM_LINKLIBS + BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) @@ -1456,7 +1466,7 @@ endif ALL_CFLAGS += $(BASIC_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) -export TAR INSTALL DESTDIR SHELL_PATH +export TAR INSTALL DESTDIR SHELL_PATH ALL_CFLAGS ### Build rules @@ -1476,6 +1486,9 @@ endif ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all endif +ifndef NO_GNUTLS + $(QUIET_SUBDIR0)git-over-tls $(QUIET_SUBDIR1) prefix='$(prefix_SQ)' all +endif ifndef NO_PYTHON $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all endif @@ -1969,6 +1982,9 @@ install: all $(INSTALL) -m 644 $(SCRIPT_LIB) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' $(INSTALL) $(install_bindir_programs) '$(DESTDIR_SQ)$(bindir_SQ)' $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install +ifndef NO_GNUTLS + $(MAKE) -C git-over-tls DESTDIR_BIN='$(DESTDIR_SQ)$(bindir_SQ)' DESTDIR_GITEXEC='$(DESTDIR_SQ)$(gitexec_instdir_SQ)' install +endif ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install endif @@ -2101,6 +2117,9 @@ ifndef NO_PERL $(RM) gitweb/gitweb.cgi gitweb/gitweb.min.* $(MAKE) -C perl clean endif +ifndef NO_GNUTLS + $(MAKE) -C git-over-tls clean +endif ifndef NO_PYTHON $(MAKE) -C git_remote_helpers clean endif diff --git a/git-over-tls/Makefile b/git-over-tls/Makefile new file mode 100644 index 0000000..69d1d33 --- /dev/null +++ b/git-over-tls/Makefile @@ -0,0 +1,45 @@ +GITLIBS2 = $(patsubst %.a,../%.a, $(GIT_PROGRAM_LINKLIBS)) +helper = git-remote-gits +programs = gits-get-key-id gits-generate-keypair +scripts = gits-send-special-command gits-send-special-command-nourl +flags= + +# These can't be inherited from upper level or they won't work right... +ifndef V + QUIET_CC = @echo ' ' CC $@; + QUIET_LINK = @echo ' ' LINK $@; +endif + +ifdef NO_SRP +flags += -DDISABLE_SRP +else +programs += gits-generate-srp-verifier +endif + +ssh_deps = ssh.o ssh_localkey.o pem.o base64.o home.o pem_decrypt.o srp_askpass.o prompt.o user.o cbuffer.o misc.o + +all: $(programs) $(helper) + +git-remote-gits: main.o user.o cbuffer.o keypairs.o hostkey.o certificate.o connect.o $(ssh_deps) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls + +gits-get-key-id: getkeyid.o certificate.o $(ssh_deps) + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls + +ifndef NO_SRP +gits-generate-srp-verifier: gensrpverifier.o prompt.o home.o + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls +endif + +gits-generate-keypair: genkeypair.o home.o mkcert.o cbuffer.o prompt.o + $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $^ $(GITLIBS2) -lgnutls + +%.o: %.c + $(QUIET_CC)$(CC) $(ALL_CFLAGS) -c -o $@ $< $(flags) -I.. + +install: all + $(INSTALL) $(programs) $(scripts) '$(DESTDIR_BIN)' + $(INSTALL) $(helper) '$(DESTDIR_GITEXEC)' + +clean: + $(RM) -f *.o $(programs) $(helper) diff --git a/git-over-tls/cbuffer.c b/git-over-tls/cbuffer.c new file mode 100644 index 0000000..e2adec7 --- /dev/null +++ b/git-over-tls/cbuffer.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) Ilari Liusvaara 2009 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include "cbuffer.h" +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#ifdef USE_UNIX_SCATTER_GATHER_IO +#include <sys/uio.h> +#endif +#include <errno.h> +#include <string.h> + +/* + * NOTE: This code may not crash, call exit or anything similar unless + * program state is corrupt or call parameters are completely invalid. + * It also may not call Git APIs. + */ + +struct cbuffer +{ + /* Base address for data area. */ + unsigned char *cb_base; + /* Amount of free space. */ + size_t cb_free; + /* Amount of used space. */ + size_t cb_used; + /* Get pointer (relative to base) */ + size_t cb_get; + /* Put pointer (relative to base) */ + size_t cb_put; + /* Total size */ + size_t cb_size; +}; + +/* Assert that invariants of circular buffer hold. */ +static void assert_invariants(struct cbuffer *cbuf) +{ + assert(cbuf->cb_free >= 0 && cbuf->cb_free <= cbuf->cb_size); + assert(cbuf->cb_used >= 0 && cbuf->cb_used <= cbuf->cb_size); + assert(cbuf->cb_free + cbuf->cb_used == cbuf->cb_size); + assert(cbuf->cb_get >= 0 && cbuf->cb_get <= cbuf->cb_size); + assert(cbuf->cb_put >= 0 && cbuf->cb_put <= cbuf->cb_size); + if (cbuf->cb_get == cbuf->cb_put) + assert(cbuf->cb_free == 0 || cbuf->cb_used == 0); + else if (cbuf->cb_get < cbuf->cb_put) + assert(cbuf->cb_get + cbuf->cb_used == cbuf->cb_put); + else + assert(cbuf->cb_put + cbuf->cb_free == cbuf->cb_get); +} + +/* Ack specified amount of data written. */ +static void ack_write(struct cbuffer *cbuf, size_t wsize) +{ + assert_invariants(cbuf); + assert(wsize <= cbuf->cb_free); + /* + * Writing data decreses free space, increases used space, + * increases put pointer, but it will wrap around if it + * goes past end of buffer. + */ + cbuf->cb_free -= wsize; + cbuf->cb_used += wsize; + cbuf->cb_put += wsize; + if (cbuf->cb_put >= cbuf->cb_size) + cbuf->cb_put -= cbuf->cb_size; + assert_invariants(cbuf); +} + +/* Ack specified amount of data read (removing it). */ +static void ack_read(struct cbuffer *cbuf, size_t rsize) +{ + assert_invariants(cbuf); + assert(rsize <= cbuf->cb_used); + /* + * Reading data decreses used space, increases free space, + * increases get pointer, but it will wrap around if it + * goes past end of buffer. + */ + cbuf->cb_free += rsize; + cbuf->cb_used -= rsize; + cbuf->cb_get += rsize; + if (cbuf->cb_get >= cbuf->cb_size) + cbuf->cb_get -= cbuf->cb_size; + assert_invariants(cbuf); +} + +struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize) +{ + struct cbuffer *cbuf = (struct cbuffer*)malloc(sizeof( + struct cbuffer)); + if (cbuf) { + cbuf->cb_base = data; + cbuf->cb_size = cbuf->cb_free = datasize; + cbuf->cb_used = cbuf->cb_get = cbuf->cb_put = 0; + assert_invariants(cbuf); + } + return cbuf; +} + +void cbuffer_destroy(struct cbuffer *cbuf) +{ + if (cbuf) { + assert_invariants(cbuf); + free(cbuf); + } +} + +size_t cbuffer_used(struct cbuffer *cbuf) +{ + assert_invariants(cbuf); + return cbuf->cb_used; +} + +size_t cbuffer_free(struct cbuffer *cbuf) +{ + assert_invariants(cbuf); + return cbuf->cb_free; +} + +int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread) +{ + assert_invariants(cbuf); + + /* Check that user doesn't try to read too much. */ + if (toread > cbuf->cb_used) + return -1; + + size_t tocopy = toread; + + /* + * Limit the amount of data read to amount fits inside single + * segment. If get pointer is not greater or equal to put pointer, + * then entiere used space is only single segment. + */ + if (cbuf->cb_get >= cbuf->cb_put) + if (tocopy > cbuf->cb_size - cbuf->cb_get) + tocopy = cbuf->cb_size - cbuf->cb_get; + + if (tocopy > 0) { + /* Copy the segment and mark it read. */ + memcpy(dest, cbuf->cb_base + cbuf->cb_get, tocopy); + ack_read(cbuf, tocopy); + /* Adjust the request for subsequent segments. */ + toread -= tocopy; + dest += tocopy; + } + + /* + * If the read was incomplete, repeat the read request for the + * non-read tail. + */ + if (toread > 0) + return cbuffer_read(cbuf, dest, toread); + + assert_invariants(cbuf); + + return 0; +} + +int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread) +{ + assert_invariants(cbuf); + + /* Check that user doesn't try to peek too much. */ + if (toread > cbuf->cb_used) + return -1; + + /* Is the used space as single segment or in two segments? */ + if (cbuf->cb_get + toread > cbuf->cb_size) { + /* Two. We have to compute where segment boundary is and + copy the data as two copies. */ + size_t firstseg = cbuf->cb_size - cbuf->cb_get; + memcpy(dest, cbuf->cb_base + cbuf->cb_get, firstseg); + memcpy(dest + firstseg, cbuf->cb_base, toread - firstseg); + } else { + /* One, data can be read as single copy. */ + memcpy(dest, cbuf->cb_base + cbuf->cb_get, toread); + } + + assert_invariants(cbuf); + + return 0; +} + +int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src, + size_t towrite) +{ + assert_invariants(cbuf); + + /* Check that user doesn't try to write too much. */ + if (towrite > cbuf->cb_free) + return -1; + + size_t tocopy = towrite; + + /* + * Limit the amount of data written to amount fits inside single + * segment. If put pointer is not greater or equal to get pointer, + * then entiere free space is only single segment. + */ + if (cbuf->cb_put >= cbuf->cb_get) + if (tocopy > cbuf->cb_size - cbuf->cb_put) + tocopy = cbuf->cb_size - cbuf->cb_put; + + if (tocopy > 0) { + /* Copy the segment and mark it written. */ + memcpy(cbuf->cb_base + cbuf->cb_put, src, tocopy); + ack_write(cbuf, tocopy); + /* Adjust the request for subsequent segments. */ + towrite -= tocopy; + src += tocopy; + } + + /* + * If the write was incomplete, repeat the write request for the + * non-written tail. + */ + if (towrite > 0) + return cbuffer_write(cbuf, src, towrite); + + assert_invariants(cbuf); + + return 0; +} + +int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove) +{ + assert_invariants(dest); + assert_invariants(src); + + /* Check that amount to move isn't too great. */ + if (tomove > dest->cb_free) + return -1; + if (tomove > src->cb_used) + return -1; + + size_t tocopy = tomove; + /* + * Compute maximum number of bytes that is less than amount to + * move and amount of used/free space in current segments in + * both buffers. + */ + if (dest->cb_put >= dest->cb_get) + if (tocopy > dest->cb_size - dest->cb_put) + tocopy = dest->cb_size - dest->cb_put; + if (src->cb_get >= src->cb_put) + if (tocopy > src->cb_size - src->cb_get) + tocopy = src->cb_size - src->cb_get; + + if (tocopy > 0) { + /* Move the segment. and mark it moved. */ + memcpy(dest->cb_base + dest->cb_put, + src->cb_base +src->cb_get, tocopy); + ack_read(src, tocopy); + ack_write(dest, tocopy); + /* Adjust request for subsequent segments. */ + tomove -= tocopy; + } + + /* If request was incomplete, move the yet unmoved tail. */ + if (tomove > 0) + return cbuffer_move(dest, src, tomove); + + assert_invariants(dest); + assert_invariants(src); + + return 0; +} + +ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd) +{ + ssize_t r; + + assert_invariants(cbuf); + + /* Generate EAGAIN if needed (on no free space). */ + if (cbuf->cb_free == 0) { + errno = EAGAIN; + return -1; + } + + /* + * If scatter-gather I/O is available, entiere buffer may be read at + * once. Otherwise only single segment can be read at time. + */ +#ifdef USE_UNIX_SCATTER_GATHER_IO + struct iovec areas[2]; + int touse; + + /* One or two segments? */ + if (cbuf->cb_put >= cbuf->cb_get) { + /* Two. */ + areas[0].iov_base = cbuf->cb_base + cbuf->cb_put; + areas[0].iov_len = cbuf->cb_size - cbuf->cb_put; + areas[1].iov_base = cbuf->cb_base; + areas[1].iov_len = cbuf->cb_get; + touse = 2; + } else { + /* One. */ + areas[0].iov_base = cbuf->cb_base + cbuf->cb_put; + areas[0].iov_len = cbuf->cb_get - cbuf->cb_put; + touse = 1; + } + r = readv(fd, areas, touse); +#else + /* Read into current segment. */ + if (cbuf->cb_put >= cbuf->cb_get) + r = read(fd, cbuf->cb_base + cbuf->cb_put, + cbuf->cb_size - cbuf->cb_put); + else + r = read(fd, cbuf->cb_base + cbuf->cb_put, + cbuf->cb_get - cbuf->cb_put); +#endif + /* Ack any successfully read data as written. */ + if (r > 0) + ack_write(cbuf, (size_t)r); + + assert_invariants(cbuf); + + return r; +} + +ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd) +{ + ssize_t r; + + assert_invariants(cbuf); + + /* Generate EAGAIN if needed (on no used space). */ + if (cbuf->cb_used == 0) { + errno = EAGAIN; + return -1; + } + + /* + * If scatter-gather I/O is available, entiere buffer may be written + * at once. Otherwise only single segment can be written at time. + */ +#ifdef USE_UNIX_SCATTER_GATHER_IO + struct iovec areas[2]; + int touse; + + /* One or two segments? */ + if (cbuf->cb_get >= cbuf->cb_put) { + /* Two. */ + areas[0].iov_base = cbuf->cb_base + cbuf->cb_get; + areas[0].iov_len = cbuf->cb_size - cbuf->cb_get; + areas[1].iov_base = cbuf->cb_base; + areas[1].iov_len = cbuf->cb_put; + touse = 2; + } else { + /* One. */ + areas[0].iov_base = cbuf->cb_base + cbuf->cb_get; + areas[0].iov_len = cbuf->cb_put - cbuf->cb_get; + touse = 1; + } + r = writev(fd, areas, touse); +#else + /* Write current segment. */ + if (cbuf->cb_get >= cbuf->cb_put) + r = write(fd, cbuf->cb_base + cbuf->cb_get, + cbuf->cb_size - cbuf->cb_get); + else + r = write(fd, cbuf->cb_base + cbuf->cb_get, + cbuf->cb_put - cbuf->cb_get); +#endif + /* Ack any successfully written data as read. */ + if (r > 0) + ack_read(cbuf, (size_t)r); + + assert_invariants(cbuf); + + return r; +} + +void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base, + size_t *length) +{ + assert_invariants(cbuf); + + /* Compute segment base. */ + *base = cbuf->cb_base + cbuf->cb_get; + + if (!cbuf->cb_used) { + /* No used space -> empty segment. */ + *length = 0; + } else if (cbuf->cb_get >= cbuf->cb_put) { + /* High segment is current. */ + *length = cbuf->cb_size - cbuf->cb_get; + } else { + /* Low segment is current. */ + *length = cbuf->cb_put - cbuf->cb_get; + } + + assert_invariants(cbuf); +} + +void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length) +{ + assert_invariants(cbuf); + /* + * This doesn't handle read being longer than single segment right, + * but that's undefined anyway. + */ + ack_read(cbuf, length); + assert_invariants(cbuf); +} + +void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base, + size_t *length) +{ + assert_invariants(cbuf); + + /* Compute segment base. */ + *base = cbuf->cb_base + cbuf->cb_put; + + if (!cbuf->cb_free) { + /* No free space -> empty segment. */ + *length = 0; + } else if (cbuf->cb_put >= cbuf->cb_get) { + /* High segment is current. */ + *length = cbuf->cb_size - cbuf->cb_put; + } else { + /* Low segment is current. */ + *length = cbuf->cb_get - cbuf->cb_put; + } + + assert_invariants(cbuf); +} + +void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length) +{ + assert_invariants(cbuf); + /* + * This doesn't handle write being longer than single segment right, + * but that's undefined anyway. + */ + ack_write(cbuf, length); + assert_invariants(cbuf); +} + + +void cbuffer_clear(struct cbuffer *cbuf) +{ + /* + * Just resetting pointers and values to initial defaults clears + * all data. + */ + cbuf->cb_used = cbuf->cb_put = cbuf->cb_get = 0; + cbuf->cb_free = cbuf->cb_size; + assert_invariants(cbuf); +} + +size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest, + size_t limit) +{ + /* Limit the request to maximum possible and do read request. */ + if (limit > cbuffer_used(cbuf)) + limit = cbuffer_used(cbuf); + cbuffer_read(cbuf, dest, limit); + return limit; +} + +size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src, + size_t limit) +{ + /* Limit the request to maximum possible and do write request. */ + if (limit > cbuffer_free(cbuf)) + limit = cbuffer_free(cbuf); + cbuffer_write(cbuf, src, limit); + return limit; +} + +size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src, + size_t limit) +{ + /* Limit the request to maximum possible and do move request. */ + if (limit > cbuffer_free(dest)) + limit = cbuffer_free(dest); + if (limit > cbuffer_used(src)) + limit = cbuffer_used(src); + cbuffer_move(dest, src, limit); + return limit; +} + +size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src) +{ + size_t limit; + /* + * Limit the request to maximum possible and do move request. + * The move lacks limit so use free space in destination as + * first limit. + */ + limit = cbuffer_free(dest); + if (limit > cbuffer_used(src)) + limit = cbuffer_used(src); + cbuffer_move(dest, src, limit); + return limit; +} diff --git a/git-over-tls/cbuffer.h b/git-over-tls/cbuffer.h new file mode 100644 index 0000000..4e07c5b --- /dev/null +++ b/git-over-tls/cbuffer.h @@ -0,0 +1,304 @@ +/* + * Copyright (C) Ilari Liusvaara 2009 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _cbuffer__h__included__ +#define _cbuffer__h__included__ + +#include <stdlib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Terminology: + * Segment: + * Memory-contigious range extending from get pointer, put + * pointer or start of circular buffer till used/free type + * changes. + * Current segment: + * Segment starting from get pointer or put pointer. + * + */ + +/* Primary circular buffer structure. Opaque type. */ +struct cbuffer; + +/* + * Create new circular buffer using specified data area. The data area + * is not copied and must remain until circular buffer is destroyed. + * + * Inputs: + * data The data area to back the circular buffer. + * datasize Size of backing data area. + * + * Outputs: + * return value The newly created circular buffer, or NULL + * if out of memory. + */ +struct cbuffer *cbuffer_create(unsigned char *data, size_t datasize); + + +/* + * Free circular buffer. Data area is not freed. + * + * Inputs: + * cbuf The circular buffer to free. + */ +void cbuffer_destroy(struct cbuffer *cbuf); + +/* + * Return number of bytes used in buffer (how many bytes can be read without + * writes). + * + * Inputs: + * cbuf The circular buffer to interrogate. + * + * Outputs: + * return value Number of bytes data in buffer. + */ +size_t cbuffer_used(struct cbuffer *cbuf); + +/* + * Return number of bytes free in buffer (how many bytes can be written + * without reads). + * + * Inputs: + * cbuf The circular buffer to interrogate. + * + * Outputs: + * return value Number of bytes free in buffer. + */ +size_t cbuffer_free(struct cbuffer *cbuf); + +/* + * Peek specified number of bytes from buffer. The bytes are not removed. + * + * Inputs: + * cbuf The circular buffer to peek. + * dest Destination buffer to store the peeked data to. + * toread Number of bytes to peek. + * + * Outputs: + * Return value 0 on success, -1 if buffer has insufficient + * amount of data. + * + */ +int cbuffer_peek(struct cbuffer *cbuf, unsigned char *dest, size_t toread); + +/* + * Read specified number of bytes from buffer. The bytes read are + * removed. + * + * Inputs: + * cbuf The circular buffer to read. + * dest Destination buffer to store the read data to. + * toread Number of bytes to read. + * + * Outputs: + * Return value 0 on success, -1 if buffer has insufficient + * amount of data. + */ +int cbuffer_read(struct cbuffer *cbuf, unsigned char *dest, size_t toread); + +/* + * Write specified number of bytes to buffer. + * + * Inputs: + * cbuf The circular buffer to write. + * src Buffer to read the written data from. + * towrite Number of bytes to write. + * + * Outputs: + * Return value 0 on success, -1 if buffer has insufficient + * free space. + */ +int cbuffer_write(struct cbuffer *cbuf, const unsigned char *src, + size_t towrite); + +/* + * Move specified number of bytes from buffer to another. The data is + * removed from source buffer. + * + * Inputs: + * dest Destination circular buffer. + * src Source cirrcular buffer. + * tomove Number of bytes to move. + * + * Outputs: + * Return value 0 on success, -1 if insufficient source buffer + * data or insufficient destination buffer space. + */ +int cbuffer_move(struct cbuffer *dest, struct cbuffer *src, size_t tomove); + +/* + * Call read on file descriptor. The call tries to read as much as + * possible in one go (up to entiere free space of destination + * buffer). + * + * Inputs: + * cbuf Circular buffer to store the data to. + * fd File descriptor to read. + * + * Outputs: + * Return value Number of bytes read (>0) on success, 0 if + * EOF on file descriptor or -1 if read failed. + * errno Set by read() or readv() on failure. + * EAGAIN if there is no free space in circular buffer. + */ +ssize_t cbuffer_read_fd(struct cbuffer *cbuf, int fd); + +/* + * Call write on file descriptor. The call tries to write as much as + * possible in one go (up to entiere used space of soruce + * buffer). + * + * Inputs: + * cbuf Circular buffer to read data from. + * fd File descriptor to write. + * + * Outputs: + * Return value Number of bytes written (>=0) on success, + * -1 if write failed. + * errno Set by write() or writev() on failure. + * EAGAIN if there is no used space in circular buffer. + */ +ssize_t cbuffer_write_fd(struct cbuffer *cbuf, int fd); + +/* + * Fill buffer read segment for direct from memory read operation on + * circular buffer. Any read operation on buffer invalidates segment. + * + * Inputs: + * cbuf Circular buffer to read from. + * + * Outputs: + * base Start address of segemnt is written here. + * length Length of segment is written here. + * + * Notes: + * - Returned length of 0 is only possible if there is no used + * space in buffer. + * - The entiere used space may not be returned in single segment. + */ +void cbuffer_fill_r_segment(struct cbuffer *cbuf, unsigned char **base, + size_t *length); + +/* + * Commit read segment after direct read operation, marking data as + * read. The read bytes are removed. + * + * Inputs: + * cbuf Circular buffer read from. + * length Length of segment to mark as read. Must + * be at most the length gotten from + * cbuffer_fill_r_segment. + */ +void cbuffer_commit_r_segment(struct cbuffer *cbuf, size_t length); + +/* + * Fill buffer write segment for direct to memory write operation on + * circular buffer. Any write operation on buffer invalidates segment. + * + * Inputs: + * cbuf Circular buffer to write to. + * + * Outputs: + * base Start address of segment is written here. + * length Length of segment is written here. + * + * Notes: + * - Returned length of 0 is only possible if there is no free + * space in buffer. + * - The entiere free space may not be returned in single segment. + */ +void cbuffer_fill_w_segment(struct cbuffer *cbuf, unsigned char **base, + size_t *length); + +/* + * Commit write segment after direct write operation, marking data as + * written + * + * Inputs: + * cbuf Circular buffer written to. + * length Length of segment to mark as written. Must + * be at most the length gotten from + * cbuffer_fill_w_segment. + */ +void cbuffer_commit_w_segment(struct cbuffer *cbuf, size_t length); + +/* + * Clear all data in cbuffer, freeing any used space. + * + * Inputs: + * cbuf Cirecular buffer to clear. + */ +void cbuffer_clear(struct cbuffer *cbuf); + +/* + * Read number of bytes from circular buffer smaller than limit. + * The read bytes are removed. + * + * Inputs: + * cbuf Circular buffer to read. + * dest Destination to store the read data. + * limit Maximum number of bytes to read. + * + * Outputs: + * Return value Number of bytes read. + */ +size_t cbuffer_read_max(struct cbuffer *cbuf, unsigned char *dest, + size_t limit); + +/* + * Write number of bytes to circular buffer smaller than limit. + * + * Inputs: + * cbuf Circular buffer to write. + * dest Source to read the wrritten data. + * limit Maximum number of bytes to write. + * + * Outputs: + * Return value Number of bytes written. + */ +size_t cbuffer_write_max(struct cbuffer *cbuf, const unsigned char *src, + size_t limit); + +/* + * Move number of bytes from circular buffer to another smaller than limit. + * The read bytes are removed from source circular buffer. + * + * Inputs: + * dest Destination circular buffer. + * src Source circular buffer. + * limit Maximum number of bytes to move. + * + * Outputs: + * Return value Number of bytes moved. + */ +size_t cbuffer_move_max(struct cbuffer *dest, struct cbuffer *src, + size_t limit); + +/* + * Move as much bytes from circular buffer to another as possible. + * The read bytes are removed from source circular buffer. + * + * Inputs: + * dest Destination circular buffer. + * src Source circular buffer. + * + * Outputs: + * Return value Number of bytes moved. + */ +size_t cbuffer_move_nolimit(struct cbuffer *dest, struct cbuffer *src); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/git-over-tls/user.c b/git-over-tls/user.c new file mode 100644 index 0000000..bcfeac1 --- /dev/null +++ b/git-over-tls/user.c @@ -0,0 +1,1597 @@ +/* + * Copyright (C) Ilari Liusvaara 2009 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include "user.h" +#include "cbuffer.h" +#include "misc.h" +#include <sys/select.h> +#include <sys/time.h> +#include <gnutls/gnutls.h> +#include <errno.h> +#include <stdarg.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdint.h> +#include <sys/socket.h> +#ifdef USE_TRAP_PAGING +#include <sys/mman.h> +#endif + +/* + * NOTE: This code may not crash, call exit or anything similar unless + * program state is corrupt or call parameters are completely invalid. + * It also may not call Git APIs. + */ + +/* Main buffer size. */ +#define BUFFERSIZE 65536 +/* + * Error buffer can be maximum of 65535-8 bytes and must be smaller or + * equal in size to main buffers. + */ +#define ERR_BUFFERSIZE (65535-8) + +struct user +{ + /* If 1, EOF received from black in at transport level. */ + unsigned u_black_in_eof : 1, + /* If 1, EOF sent to black out at transport level. */ + u_black_out_eof : 1, + /* If 1, EOF received from black in at TLS level. */ + u_black_in_d_eof : 1, + /* If 1, EOF sent to black out at TLS level. */ + u_black_out_d_eof : 1, + /* If 1, Assume that there is more input to come from red in. */ + u_red_assume_more : 1, + /* If 1, TLS is active and ready to transfer data. */ + u_tls_active : 1, + /* If 1, there has been data received from red in. */ + u_red_in_have_data : 1, + /* + * Result of last read of decrypted data. + * 0 => Read was successful. + * 1 => Read was blocked by insufficient input data. + * 2 => Read was blocked by insufficient output space. + * 3 => No read attempted. + */ + u_want_read : 2, + /* + * Result of last write of decrypted data. + * 0 => Write was successful. + * 1 => Write was blocked by insufficient input data. + * 2 => Write was blocked by insufficient output space. + * 3 => No write attempted. + */ + u_want_write : 2, + /* + * Result of last handshake attempt. + * 0 => Handshake was successful. + * 1 => Handshake was blocked by insufficient input data. + * 2 => Handshake was blocked by insufficient output space. + * 3 => No handshake attempted. + */ + u_want_hand : 2, + /* Number of bytes of error header sent (0-8). */ + u_red_err_hdr_sent : 4, + /* Delay TLS failure present. 1 for handshake, 2 otherwise */ + u_delay_tls_failure : 2, + /* Has seen any data received. */ + u_seen_input_data : 1; + + /* File descriptors. -1 if none. */ + int u_black_fd; /* Input/Output */ + int u_red_in_fd; /* Input */ + int u_red_out_fd; /* Output */ + int u_red_err_fd; /* Input */ + /* The backing buffer for all transfer buffers and its size. */ + unsigned char *u_buf_backing; + size_t u_buf_backing_size; + /* The actual transfer buffers. */ + struct cbuffer *u_black_in_buf; + struct cbuffer *u_black_out_buf; + struct cbuffer *u_red_in_buf; + struct cbuffer *u_red_out_buf; + struct cbuffer *u_red_err_buf; + /* Deadline. Tv_sec is -1 if there is no deadline. */ + struct timeval u_deadline; + /* + * Why the connection was torn down. Delay_failure is delayed + * failure that becomes real after output buffer is flushed + * so that TLS alerts are sent. Also stored is delayed TLS alert + * that couldn't be sent yet. + */ + int u_failure; + char *u_errmsg; + int u_delay_failure; + gnutls_alert_description_t u_delay_alert; + /* Active TLS session. NULL if no TLS. */ + gnutls_session_t u_tls_session; + /* Transfer counters. */ + uint64_t u_raw_tx; + uint64_t u_raw_rx; + uint64_t u_decoded_tx; + uint64_t u_decoded_rx; + struct timeval u_last_rx; + struct timeval u_last_tx; +}; + +/* Initiate delayed failure on user. */ +static void delay_cleanup_user(struct user *user, int failure, + const char *error) +{ + /* Store the cause of termination. */ + if (!user->u_errmsg) { + if (error) + user->u_errmsg = strdup(error); + else + user->u_errmsg = NULL; + } + user->u_delay_failure = failure; +} + +/* Clean up user connection immediately. */ +static void cleanup_user(struct user *user, int failure, const char *error) +{ + /* If there is TLS session, deallocate its resources. */ + if (user->u_tls_session) { + gnutls_deinit(user->u_tls_session); + user->u_tls_session = NULL; + } + /* Shutdown and close black input/output. */ + if (user->u_black_fd >= 0) { + shutdown(user->u_black_fd, SHUT_WR); + force_close(user->u_black_fd); + } + user->u_black_fd = -1; + + /* Close red inputs/outputs. */ + if (user->u_red_in_fd >= 0) + force_close(user->u_red_in_fd); + user->u_red_in_fd = -1; + if (user->u_red_out_fd >= 0) + force_close(user->u_red_out_fd); + user->u_red_out_fd = -1; + if (user->u_red_err_fd >= 0) + force_close(user->u_red_err_fd); + user->u_red_err_fd = -1; + + /* Store the cause of termination. */ + if (!user->u_errmsg) { + if (error) + user->u_errmsg = strdup(error); + else + user->u_errmsg = NULL; + } + user->u_failure = failure; +} + +/* + * Process delay failure. If there is delayed failure and output buffer is + * empty, make it real failure and disconnect user immediately. + */ +static int process_delay_failure(struct user *user) +{ + if (user->u_failure) + return 0; + if (!cbuffer_used(user->u_black_out_buf) && user->u_delay_failure) { + cleanup_user(user, user->u_delay_failure, NULL); + return 0; + } + return 0; +} + +/* Is this error from I/O syscall fatal? */ +static int is_fatal_error(int error) +{ + return (error != EINTR && error != EAGAIN && error != EWOULDBLOCK); +} + +/* Is this error from GnuTLS I/O operation fatal? */ +static int is_fatal_tls_error(int error) +{ + /* + * GNUTLS_E_INTERRUPTED should never be seen. since custom push and + * pull functions can't return EINTR, only EAGAIN. + */ + return (error < 0 && error != GNUTLS_E_AGAIN); +} + +/* Handle fatal error received from GnuTLS functions. */ +static int handle_tls_failure_code(struct user *user, int gnutls_code, + int handshaking) +{ + int x; + char error_buffer[8192]; + if (gnutls_code == GNUTLS_E_FATAL_ALERT_RECEIVED) { + /* Fatal alert. Get the description and format message. */ + gnutls_alert_description_t alert; + alert = gnutls_alert_get(user->u_tls_session); + sprintf(error_buffer, "TLS alert received: %s", + gnutls_alert_get_name(alert)); + + if (alert == GNUTLS_A_BAD_RECORD_MAC && handshaking) + sprintf(error_buffer, "TLS alert received: %s " + "(incorrect password?)", + gnutls_alert_get_name(alert)); + + /* Terminate the connection. */ + if (handshaking) + cleanup_user(user, USER_TLS_HAND_ERROR, + error_buffer); + else + cleanup_user(user, USER_TLS_ERROR, + error_buffer); + return 0; + } + /* + * Alerts use handshake readyness indicator, so try to set it + * to "perform I/O immediately". + */ + user->u_want_hand = 0; + /* + * Mark that delay TLS failure is present and get the alert + * that should be sent. Also set error message. + */ + user->u_delay_tls_failure = 1; +#ifndef DISABLE_SRP + /* GnuTLS doesn't seem to have proper code for this. */ + if (gnutls_code == GNUTLS_E_SRP_PWD_ERROR) + user->u_delay_alert = GNUTLS_A_UNKNOWN_PSK_IDENTITY; + else +#endif + user->u_delay_alert = (gnutls_alert_description_t) + gnutls_error_to_alert((int)gnutls_code, &x); + user->u_errmsg = strdup(gnutls_strerror_name((int)gnutls_code)); + return 1; +} + +/* Handle black input activity */ +static int black_in_handler(struct user *user, fd_set *rfds) +{ + /* Don't attempt to read if connection has failed. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* The file descrptor must be marked readable. */ + if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, rfds)) + return 0; + /* Don't attempt to read already EOF'd file descriptor. */ + if (user->u_black_in_eof) + return 0; + + ssize_t r = cbuffer_read_fd(user->u_black_in_buf, user->u_black_fd); + FD_CLR(user->u_black_fd, rfds); + if (r < 0 && is_fatal_error(errno)) { + /* Inbound connection faulted! */ + cleanup_user(user, USER_LAYER4_ERROR, strerror(errno)); + } else if (r == 0) { + /* Received EOF. If TLS is enabled, also assume output EOF. */ + user->u_black_in_eof = 1; + if (user->u_tls_session) + user->u_black_out_eof = 1; + } else if (r > 0) { + /* Received some data. */ + user->u_raw_rx += r; + user->u_seen_input_data = 1; + gettimeofday(&user->u_last_rx, NULL); + } + return 1; +} + +/* Handle black output activity. */ +static int black_out_handler(struct user *user, fd_set *wfds) +{ + /* + * Don't attempt to write if connection has failed. This is one of + * the very few handlers still to run in delayed failure. + */ + if (user->u_failure) + return 0; + /* The file descrptor must be marked as writable. */ + if (user->u_black_fd < 0 || !FD_ISSET(user->u_black_fd, wfds)) + return 0; + /* Don't attempt to write if EOF has already been sent. */ + if (user->u_black_out_eof) + return 0; + + ssize_t r = cbuffer_write_fd(user->u_black_out_buf, user->u_black_fd); + FD_CLR(user->u_black_fd, wfds); + if (r < 0 && is_fatal_error(errno)) { + /* Outbound connection faulted! */ + cleanup_user(user, USER_LAYER4_ERROR, strerror(errno)); + } else if (r > 0) { + /* Sent some data. */ + user->u_raw_tx += r; + gettimeofday(&user->u_last_tx, NULL); + } + return 1; +} + +/* Send black out EOF if needed (using TLS if it is active) */ +static int black_out_eof_handler(struct user *user) +{ + /* Don't operate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate anymore if EOF has been sent outbound. */ + if (user->u_black_out_eof || user->u_black_out_d_eof) + return 0; + /* Don't activate if there can be more data from red in. */ + if (user->u_red_assume_more || user->u_red_in_fd >= 0) + return 0; + /* Don't activate if there can be more data from red error. */ + if (user->u_red_err_fd >= 0) + return 0; + /* Don't activate if there is more data in red input. */ + if (cbuffer_used(user->u_red_in_buf)) + return 0; + /* Don't activate if there is more data in red error. */ + if (cbuffer_used(user->u_red_err_buf)) + return 0; + /* + * If not in TLS mode, don't activate if there is data in send + * buffer. + */ + if (!user->u_tls_session && cbuffer_used(user->u_black_out_buf)) + return 0; + + if (user->u_tls_session) { + int r; + /* Try to send the EOF at TLS level. */ + r = gnutls_bye(user->u_tls_session, GNUTLS_SHUT_WR); + if (r == 0) { + /* Suceeded. Mark the connection as EOF'd. */ + user->u_black_out_d_eof = 1; + return 1; + } else if (is_fatal_tls_error((int)r)) { + /* Fatal stream error! */ + return handle_tls_failure_code(user, (int)r, 0); + } + /* Otherwise we failed due to non-fatal TLS error. We'll try + again soon. */ + return 0; + } else { + /* Try to send normal transport EOF. */ + if (shutdown(user->u_black_fd, SHUT_WR) < 0) { + /* Failed, declare as stream error. */ + cleanup_user(user, USER_LAYER4_ERROR, + strerror(errno)); + return 0; + } + user->u_black_out_eof = 1; + return 1; + } + return 0; /* Should not be here. */ +} + +/* Send EOF to red output if needed. */ +static int red_out_eof_handler(struct user *user) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate unless input EOF has been received. */ + if (!user->u_black_in_eof && !user->u_black_in_d_eof) + return 0; + /* Don't activate anymore if red out has been closed. */ + if (user->u_red_out_fd < 0) + return 0; + /* If not in TLS mode, don't activate if there is data in receive + buffer. */ + if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf)) + return 0; + /* Don't activate if there is more data in red output. */ + if (cbuffer_used(user->u_red_out_buf)) + return 0; + + force_close(user->u_red_out_fd); + user->u_red_out_fd = -1; + return 1; +} + +/* Send data to file descriptor connected to red output. */ +static int red_out_handler(struct user *user, fd_set *wfds) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Red out must be writable fd. */ + if (user->u_red_out_fd < 0 || !FD_ISSET(user->u_red_out_fd, wfds)) + return 0; + + ssize_t r = cbuffer_write_fd(user->u_red_out_buf, user->u_red_out_fd); + FD_CLR(user->u_red_out_fd, wfds); + if (r < 0 && errno == EPIPE) { + /* EPIPE is treated as special type of EOF. We need to read + EOFs from process. */ + force_close(user->u_red_out_fd); + user->u_red_out_fd = -1; + return 1; + } else if (r < 0 && is_fatal_error(errno)) { + /* Red out connection faulted! */ + cleanup_user(user, USER_RED_FAILURE, strerror(errno)); + return 0; + } else if (r > 0) { + /* Sent some data to red output. */ + return 1; + } + /* Can't come here. */ + return 0; +} + +/* Recieve data from file descriptor connected to red input. */ +static int red_in_handler(struct user *user, fd_set *rfds) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Red in must be readable fd. */ + if (user->u_red_in_fd < 0 || !FD_ISSET(user->u_red_in_fd, rfds)) + return 0; + + ssize_t r = cbuffer_read_fd(user->u_red_in_buf, user->u_red_in_fd); + FD_CLR(user->u_red_in_fd, rfds); + if (r < 0 && is_fatal_error(errno)) { + /* Inbound red connection faulted! */ + cleanup_user(user, USER_RED_FAILURE, strerror(errno)); + return 0; + } else if (r == 0) { + /* Close it so we eventually send EOF. */ + user->u_red_assume_more = 0; + force_close(user->u_red_in_fd); + user->u_red_in_fd = -1; + return 1; + } else if (r > 0) { + /* Received some data. */ + user->u_red_in_have_data = 1; + + /* Clear the error buffer. */ + cbuffer_clear(user->u_red_err_buf); + } + return 1; +} + +#define DUMMYBUFSIZE 256 + +/* Receive data from file descriptor connected to red error. */ +static int red_err_handler(struct user *user, fd_set *rfds) +{ + ssize_t r; + char buf[DUMMYBUFSIZE]; + + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Red err must be readable fd. */ + if (user->u_red_err_fd < 0 || !FD_ISSET(user->u_red_err_fd, rfds)) + return 0; + + if (!user->u_red_in_have_data) { + /* Read the red error for real. */ + r = cbuffer_read_fd(user->u_red_err_buf, user->u_red_err_fd); + } else { + /* Just do dummy read and discard the reuslts. */ + r = read(user->u_red_err_fd, buf, DUMMYBUFSIZE); + } + FD_CLR(user->u_red_err_fd, rfds); + if (r < 0 && is_fatal_error(errno)) { + /* Inbound red connection faulted! */ + cleanup_user(user, USER_RED_FAILURE, strerror(errno)); + } else if (r == 0) { + /* Close it so we eventually send EOF. */ + force_close(user->u_red_err_fd); + user->u_red_err_fd = -1; + } else if (r > 0) { + /* + * Received some data. + * + * HACK ALERT: Some systems seem to like to send these + * non-errors on error channel. Ignore those and treat + * them like data from stdout. (35 is length of that + * string). + */ + const char *tmsg = "Initialized empty Git repository in"; + unsigned char tmp[35]; + if (cbuffer_peek(user->u_red_err_buf, tmp, 35) >= 0 && + !strncmp(tmsg, (char*)tmp, 35)) { + user->u_red_in_have_data = 1; + cbuffer_clear(user->u_red_err_buf); + } + } + return 1; +} + +/* Terminate the whole connection if needed EOFs have been seen. */ +static int connection_eof_handler(struct user *user) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate if red out is still open. */ + if (user->u_red_out_fd >= 0) + return 0; + /* Don't activate unless output EOF has been asserted. */ + if (!user->u_black_out_eof && !user->u_black_out_d_eof) + return 0; + /* Don't assert in TLS mode unless output buffer is empty. */ + if (user->u_tls_session && cbuffer_used(user->u_black_out_buf)) + return 0; + + /* Both half-connections have ended. End the entiere connection. */ + cleanup_user(user, USER_CONNECTION_END, NULL); + return 1; +} + +/* + * Should operation take place given last operation failed practicular way? + * Direction2 is 0 if last operation didn't fail, 1 if it failed due to + * insufficient data to read, 2 if it failed due to insufficient space to + * write. + */ +static int gnutls_blacks_activate2(struct user *user, int direction2) +{ + /* + * If not attempted yet, or last time it was successful, attempt + * immediately again. + */ + if (direction2 == 0 || direction2 == 3) + return 1; + /* + * If last time it failed due to insufficient read space, try + * again only if there is some data in read buffers now. + */ + if (direction2 == 1) { + if (cbuffer_used(user->u_black_in_buf) != 0) + return 1; + else if (!user->u_black_in_eof) + return 0; + else if (user->u_tls_session && user->u_seen_input_data) + cleanup_user(user, USER_TLS_ERROR, "Connection " + "closed unexpectedly"); + else if (user->u_tls_session) + cleanup_user(user, USER_LAYER4_ERROR, "Connection " + "broke before any data was received"); + else + return 0; + } + /* + * If last time it failed due to insufficient write space, try + * again only if there is some space in write buffers now. + */ + if (direction2 == 2) + return (cbuffer_free(user->u_black_out_buf) != 0); + return 0; +} + +/* Copy or decrypt data from black input to red output. */ +static int black_to_red_handler(struct user *user) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate if there's TLS but it isn't ready yet. */ + if (user->u_tls_session && !user->u_tls_active) + return 0; + /* Don't activate if there's no space in red out buffer. */ + if (!cbuffer_free(user->u_red_out_buf)) + return 0; + /* Don't activate if TLS-level EOF has been received. */ + if (user->u_black_in_d_eof) + return 0; + /* Check that we don't fail like last time. */ + if (!gnutls_blacks_activate2(user, user->u_want_read)) + return 0; + + if (!user->u_tls_session) { + /* + * no-TLS case. Just compute how much data we can move and + * then just move it from black in buffer to red out buffer. + */ + size_t amount; + amount = cbuffer_move_nolimit(user->u_red_out_buf, + user->u_black_in_buf); + if (amount > 0) { + /* Ok, some data to move. Just move it. */ + user->u_want_read = 0; + user->u_decoded_rx += amount; + return 1; + } else { + /* + * No data to transfer. Mark operation failed due to + * insufficient data to read. + */ + user->u_want_read = 1; + return 0; + } + } else { + unsigned char *ptr; + size_t size; + ssize_t r; + + /* + * Compute the segment for writing data to. And if we do get + * such segment, receive data to it. The size == 0 case should + * not happen because we checked that there's space in red + * out buffer above. + */ + cbuffer_fill_w_segment(user->u_red_out_buf, &ptr, &size); + r = gnutls_record_recv(user->u_tls_session, ptr, size); + if (r > 0) { + /* Received TLS data. */ + cbuffer_commit_w_segment(user->u_red_out_buf, r); + user->u_decoded_rx += r; + user->u_want_read = 0; + return 1; + } else if (r == 0) { + /* Received TLS EOF. */ + user->u_black_in_d_eof = 1; + user->u_want_read = 0; + return 1; + } else if (is_fatal_tls_error((int)r)) { + /* Fatal TLS error. */ + return handle_tls_failure_code(user, (int)r, 0); + } else if (r < 0) { + /* Temporary read failure. */ + user->u_want_read = 1 + gnutls_record_get_direction( + user->u_tls_session); + return 0; + } + } + /* Can't really come here. */ + return 0; +} + +/* Copy or encrypt data from red input to black output. */ +static int red_to_black_handler(struct user *user) +{ + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate if TLS is present but not active. */ + if (user->u_tls_session && !user->u_tls_active) + return 0; + /* Require data in red in buffer. */ + if (!cbuffer_used(user->u_red_in_buf)) + return 0; + /* Don't fail like last time. */ + if (!gnutls_blacks_activate2(user, user->u_want_write)) + return 0; + + if (!user->u_tls_session) { + /* + * No-TLS case. Just find maximum amount of data to move and + * then move the data. + */ + size_t amount; + amount = cbuffer_move_nolimit(user->u_black_out_buf, + user->u_red_in_buf); + if (amount > 0) { + /* Ok, some data to move. Just move it. */ + user->u_want_write = 0; + user->u_decoded_tx += amount; + return 1; + } else { + /* + * No data to transfer. Mark operation failed due to + * insufficient space to write. + */ + user->u_want_write = 2; + return 0; + } + } else { + unsigned char *ptr; + size_t size; + ssize_t r = 0; + + /* + * Compute the segment for reading data to. And if we do get + * such segment, send data from it. The size == 0 case should + * not happen because we checked that there's data in red + * in buffer above. + */ + cbuffer_fill_r_segment(user->u_red_in_buf, &ptr, &size); + if (user->u_want_write == 0 || user->u_want_write == 3) + /* Last was success, just send new record. */ + r = gnutls_record_send(user->u_tls_session, ptr, + size); + else + /* Last time it failed, try to resend the record. */ + r = gnutls_record_send(user->u_tls_session, NULL, 0); + if (r > 0) { + /* Sent some TLS data. */ + cbuffer_commit_r_segment(user->u_red_in_buf, r); + user->u_decoded_tx += r; + user->u_want_write = 0; + return 1; + } else if (is_fatal_tls_error((int)r)) { + /* Fatal TLS error. */ + return handle_tls_failure_code(user, (int)r, 0); + } else if (r < 0) { + /* Temporary send failure. */ + user->u_want_write = 1 + gnutls_record_get_direction(user->u_tls_session); + return 0; + } + } + /* Can't really come here. */ + return 0; +} + +static const char hexes[] = "0123456789abcdef"; + +#define TMPBUFSIZE 256 + +/* Copy or encrypt data from red error to black out. */ +static int rederr_to_black_handler(struct user *user) +{ + unsigned char hdrbuf[8]; + unsigned char buffer[TMPBUFSIZE]; + size_t bufusage = 0; + size_t pcktsize; + unsigned char *segstart; + size_t seglen; + size_t maxread; + + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate if TLS is not yet ready. */ + if (user->u_tls_session && !user->u_tls_active) + return 0; + /* Don't activate if there's no data in red error. */ + if (!cbuffer_used(user->u_red_err_buf)) + return 0; + /* Don't activate if red in hasn't been closed. */ + if (user->u_red_in_fd >= 0) + return 0; + /* Don't activate if red error can have more data. */ + if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) + return 0; + /* Don't fail like last write. */ + if (!gnutls_blacks_activate2(user, user->u_want_write)) + return 0; + + /* Safety hatch. Truncate error if too long. */ + if (!cbuffer_free(user->u_red_err_buf) && user->u_red_err_fd >= 0) { + force_close(user->u_red_err_fd); + user->u_red_err_fd = -1; + } + + /* Fill the header. */ + pcktsize = 8 + cbuffer_used(user->u_red_err_buf); + hdrbuf[0] = hexes[pcktsize / 4096 % 16]; + hdrbuf[1] = hexes[pcktsize / 256 % 16]; + hdrbuf[2] = hexes[pcktsize / 16 % 16]; + hdrbuf[3] = hexes[pcktsize % 16]; + hdrbuf[4] = 'E'; + hdrbuf[5] = 'R'; + hdrbuf[6] = 'R'; + hdrbuf[7] = ' '; + /* + * If header hasn't been sent yet, copy the remainder of header to + * transfer buffer. + */ + if (user->u_red_err_hdr_sent < 8) { + memcpy(buffer, hdrbuf + user->u_red_err_hdr_sent, + 8 - user->u_red_err_hdr_sent); + bufusage = (8 - user->u_red_err_hdr_sent); + } + maxread = TMPBUFSIZE - bufusage; + + /* + * Request read segment out of red error. There's always data in + * red error buffer by checks above. Then copy as much data from + * it as possible. + */ + cbuffer_fill_r_segment(user->u_red_err_buf, &segstart, &seglen); + if (maxread >= seglen) { + memcpy(buffer + bufusage, segstart, seglen); + bufusage += seglen; + } else { + memcpy(buffer + bufusage, segstart, maxread); + bufusage += maxread; + } + + /* + * Bufusage is always positive, since its either equal to seglen + * or maxread, and neither can be zero. + */ + if (!user->u_tls_session && !user->u_red_in_have_data) { + /* + * Compute maximum amount of data that can be copied to + * send buffer (no TLS case). + */ + bufusage = cbuffer_write_max(user->u_black_out_buf, buffer, + bufusage); + if (bufusage > 0) { + user->u_want_write = 0; + user->u_decoded_tx += bufusage; + } else { + /* No space, mark it failed due to write. */ + user->u_want_write = 2; + return 0; + } + } else if (!user->u_red_in_have_data) { + ssize_t r; + + if (user->u_want_write == 0 || user->u_want_write == 3) + /* Last was success, just send new record. */ + r = gnutls_record_send(user->u_tls_session, buffer, + bufusage); + else + /* Last time it failed, try to resend the record. */ + r = gnutls_record_send(user->u_tls_session, NULL, 0); + if (r > 0) { + /* Successfully sent data. Adjust bufusage. */ + bufusage = r; + user->u_decoded_tx += bufusage; + user->u_want_write = 0; + } else if (is_fatal_tls_error((int)r)) { + /* Fatal TLS error. */ + return handle_tls_failure_code(user, (int)r, 0); + } else if (r < 0) { + /* Temporary failure. Mark the failure. */ + user->u_want_write = 1 + gnutls_record_get_direction( + user->u_tls_session); + bufusage = 0; + return 0; + } + } + + /* Now bufusage is set to amount of bytes sent. ACK the data sent. */ + if ((size_t)user->u_red_err_hdr_sent + bufusage < 8) { + /* Partially sent header. ACK the header sent. */ + user->u_red_err_hdr_sent += bufusage; + bufusage = 0; + return 1; + } else { + /* + * Completely sent the header. ACK rest of it and substract + * it from actual data sent. + */ + bufusage -= (8 - user->u_red_err_hdr_sent); + user->u_red_err_hdr_sent = 8; + } + /* ACK the actual error data sent. */ + if (bufusage > 0) + cbuffer_commit_r_segment(user->u_red_err_buf, bufusage); + + return 1; +} + +/* Try to send delayed alert (No-TLS case). */ +static int tls_alert_handler_no_tls(struct user *user) +{ + //Count it as immediates success. + cleanup_user(user, USER_KILL, "Would send fatal alert"); + return 0; +} + +/* Try to send delayed alert. */ +static int tls_alert_handler(struct user *user) +{ + int r; + + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't do this without delayed tls failure. */ + if (!user->u_delay_tls_failure) + return 0; + /* Handle no-TLS case. */ + if (!user->u_tls_session) + return tls_alert_handler_no_tls(user); + /* Don't fail like last time. */ + if (!gnutls_blacks_activate2(user, user->u_want_hand)) + return 0; + + r = gnutls_alert_send(user->u_tls_session, GNUTLS_AL_FATAL, + user->u_delay_alert); + if (r == 0) { + /* The user->u_delay_tls_failure may be 2 too. */ + if (user->u_delay_tls_failure == 1) + delay_cleanup_user(user, USER_TLS_HAND_ERROR, + "Fatal alert sent"); + else + delay_cleanup_user(user, USER_TLS_ERROR, + "Fatal alert sent"); + return 0; + } else if (is_fatal_tls_error(r)) { + const char *err = gnutls_strerror_name((int)r); + delay_cleanup_user(user, USER_TLS_ERROR, err); + return 1; + } else if (r < 0) { + /* Still needs more attempts to send. */ + user->u_want_hand = 1 + gnutls_record_get_direction( + user->u_tls_session); + return 0; + } + /* Can't really come here. */ + return 0; +} + +/* Try to handshake TLS connection. */ +static int handshake_handler(struct user *user) +{ + int r; + + /* Don't activate on already failed connections. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Don't activate if TLS is ready or no TLS. */ + if (user->u_tls_active || !user->u_tls_session) + return 0; + /* Don't handshake anoymore with delayed TLS failure. */ + if (user->u_delay_tls_failure) + return 0; + /* Don't fail like last time. */ + if (!gnutls_blacks_activate2(user, user->u_want_hand)) + return 0; + + r = gnutls_handshake(user->u_tls_session); + if (r == 0) { + /* Handshake completed, TLS ready. */ + user->u_want_hand = 0; + user->u_tls_active = 1; + return 1; + } else if (is_fatal_tls_error(r)) { + /* Fatal TLS error. */ + return handle_tls_failure_code(user, (int)r, 1); + } else if (r < 0) { + /* Still needs more handshaking. */ + user->u_want_hand = 1 + gnutls_record_get_direction( + user->u_tls_session); + return 0; + } + /* Can't really come here. */ + return 0; +} + +/* + * Compare two timevals. Returns -1 if first is first, 0 if same, 1 otherwise + */ +static int tv_compare(const struct timeval *tv1, const struct timeval *tv2) +{ + if (tv1->tv_sec == -1) + return (tv2->tv_sec == -1) ? 0 : 1; + if (tv2->tv_sec == -1) + return -1; + if (tv1->tv_sec > tv2->tv_sec) + return 1; + if (tv1->tv_sec < tv2->tv_sec) + return -1; + if (tv1->tv_usec > tv2->tv_usec) + return 1; + if (tv1->tv_usec < tv2->tv_usec) + return -1; + return 0; +} + +/* Handle timeouts on connection. */ +static int timeout_handler(struct user *user) +{ + struct timeval t; + + gettimeofday(&t, NULL); + + /* Don't activate on already failed connection. */ + if (user->u_failure || user->u_delay_failure) + return 0; + /* Is it time to activate? */ + if (tv_compare(&user->u_deadline, &t) >= 0) + return 0; + + cleanup_user(user, USER_TIMEOUT, "Request timeout"); + return 0; +} + +/* GnuTLS push function. */ +static ssize_t gnutls_push_data(gnutls_transport_ptr_t ptr, const void *data, + size_t size) +{ + struct user *user = (struct user*)ptr; + + /* Compute maximum transfer. */ + if (size > cbuffer_free(user->u_black_out_buf)) + size = cbuffer_free(user->u_black_out_buf); + + /* If no data can be read, send EAGAIN. */ + if (size == 0) { + errno = EAGAIN; + return -1; + } + + cbuffer_write(user->u_black_out_buf, (unsigned char*)data, size); + return size; +} + +/* GnuTLS pull function. */ +static ssize_t gnutls_pull_data(gnutls_transport_ptr_t ptr, void *data, + size_t size) +{ + struct user *user = (struct user*)ptr; + + /* Compute maximum transfer. */ + if (size > cbuffer_used(user->u_black_in_buf)) + size = cbuffer_used(user->u_black_in_buf); + + /* If no data can be written, send EAGAIN. */ + if (size == 0) { + errno = EAGAIN; + return -1; + } + + cbuffer_read(user->u_black_in_buf, (unsigned char*)data, size); + return size; + +} + +void user_configure_tls(struct user *user, gnutls_session_t session) +{ + user->u_tls_session = session; + user->u_want_hand = 0; + user->u_tls_active = 0; + /* Configure the TLS session transport level. */ + gnutls_transport_set_ptr(session, user); + gnutls_transport_set_push_function(session, gnutls_push_data); + gnutls_transport_set_pull_function(session, gnutls_pull_data); + gnutls_transport_set_lowat(session, 0); + /* + * For security reasons, clear the red output as that can no + * longer be trusted. + */ + cbuffer_clear(user->u_red_out_buf); +} + +void user_add_to_sets(struct user *user, int *bound, fd_set *rfds, + fd_set *wfds, struct timeval *deadline) +{ + /* + * Execute all service handlers in case they alter what + * files should be waited on. + */ + user_service_nofd(user); + + /* Pretend that errored connections have immediate deadline. */ + if (user->u_failure) + deadline->tv_sec = 0; + /* Adjust deadline. */ + if (tv_compare(deadline, &user->u_deadline) > 0) + *deadline = user->u_deadline; + + if (user->u_red_out_fd >= 0 && cbuffer_used(user->u_red_out_buf)) { + /* Red out is writable. */ + FD_SET(user->u_red_out_fd, wfds); + if (*bound <= user->u_red_out_fd) + *bound = user->u_red_out_fd + 1; + } + if (user->u_black_fd >= 0 && !user->u_black_in_eof && + /* Black in is readable. */ + cbuffer_free(user->u_black_in_buf)) { + FD_SET(user->u_black_fd, rfds); + if (*bound <= user->u_black_fd) + *bound = user->u_black_fd + 1; + } + /* Intentionally bias red to black towards writing to black. */ + if (user->u_black_fd >= 0 && !user->u_black_out_eof && + /* Black out is writable. */ + cbuffer_used(user->u_black_out_buf)) { + FD_SET(user->u_black_fd, wfds); + if (*bound <= user->u_black_fd) + *bound = user->u_black_fd + 1; + } else if (user->u_red_in_fd >= 0 && + cbuffer_free(user->u_red_in_buf)) { + /* Red in is readable. */ + FD_SET(user->u_red_in_fd, rfds); + if (*bound <= user->u_red_in_fd) + *bound = user->u_red_in_fd + 1; + } + if (user->u_red_err_fd >= 0 && cbuffer_free(user->u_red_err_buf)) { + /* Red err is readable. */ + FD_SET(user->u_red_err_fd, rfds); + if (*bound <= user->u_red_err_fd) + *bound = user->u_red_err_fd + 1; + } +} + +void user_service(struct user *user, fd_set *rfds, fd_set *wfds) +{ + int newr = 1; + + /* Do this until no service handler makes any progress. */ + while (newr) { + newr = 0; + newr = newr | black_in_handler(user, rfds); + newr = newr | black_out_handler(user, wfds); + newr = newr | red_in_handler(user, rfds); + newr = newr | red_out_handler(user, wfds); + newr = newr | red_err_handler(user, rfds); + newr = newr | black_out_eof_handler(user); + newr = newr | red_out_eof_handler(user); + newr = newr | connection_eof_handler(user); + newr = newr | black_to_red_handler(user); + newr = newr | red_to_black_handler(user); + newr = newr | rederr_to_black_handler(user); + newr = newr | timeout_handler(user); + newr = newr | handshake_handler(user); + newr = newr | tls_alert_handler(user); + newr = newr | process_delay_failure(user); + } +} + +void user_service_nofd(struct user *user) +{ + fd_set rfds; + fd_set wfds; + + /* Clear the fd sets, we don't do I/O here. */ + FD_ZERO(&rfds); + FD_ZERO(&wfds); + user_service(user, &rfds, &wfds); +} + +gnutls_session_t user_get_tls(struct user *user) +{ + if (user->u_tls_session && user->u_tls_active) + return user->u_tls_session; + else + return NULL; +} + +void user_set_red_io(struct user *user, int red_in, int red_out, int red_err) +{ + user->u_red_in_fd = red_in; + user->u_red_out_fd = red_out; + user->u_red_err_fd = red_err; + if (red_in >= 0) + fcntl(red_in, F_SETFL, fcntl(red_in, F_GETFL) | O_NONBLOCK); + if (red_out >= 0) + fcntl(red_out, F_SETFL, fcntl(red_out, F_GETFL) | O_NONBLOCK); + if (red_err >= 0) + fcntl(red_err, F_SETFL, fcntl(red_err, F_GETFL) | O_NONBLOCK); +} + +void user_clear_red_io(struct user *user) +{ + if (user->u_red_out_fd >= 0) + force_close(user->u_red_out_fd); + user->u_red_out_fd = -1; +} + +struct cbuffer *user_get_red_in(struct user *user) +{ + if (user->u_red_in_fd >= 0) + return NULL; + else + return user->u_red_in_buf; +} + +struct cbuffer *user_get_red_out(struct user *user) +{ + if (user->u_red_out_fd >= 0) + return NULL; + else + return user->u_red_out_buf; +} + +int user_get_failure(struct user *user) +{ + return user->u_failure; +} + +const char *user_get_error(struct user *user) +{ + return user->u_errmsg; +} + +void user_send_red_in_eof(struct user *user) +{ + user->u_red_assume_more = 0; +} + +struct cbuffer *user_get_red_err(struct user *user) +{ + if (user->u_red_err_fd >= 0 || user->u_red_in_have_data) + return NULL; + else + return user->u_red_err_buf; +} + +void user_clear_deadline(struct user *user) +{ + user->u_deadline.tv_sec = -1; +} + +size_t round_up(size_t base, size_t divide) +{ + return base + (divide - base % divide) % divide; +} + +struct user *user_create(int black_fd, unsigned deadline_secs) +{ + struct timeval t; + size_t offset[5]; +#ifdef USE_TRAP_PAGING + size_t trap[6]; +#endif + int i; + + for (i = 0; i < 5; i++) + offset[i] = i * BUFFERSIZE; + + fcntl(black_fd, F_SETFL, fcntl(black_fd, F_GETFL) | O_NONBLOCK); + + /* Compute the deadline field value. */ + gettimeofday(&t, NULL); + t.tv_sec += deadline_secs; + + /* How much to allocate for buffers? */ + int r = 0; + size_t allocsize = 4 * BUFFERSIZE + ERR_BUFFERSIZE; + + /* Allocate the primary structure. */ + struct user *user = (struct user*)malloc(sizeof(struct user)); + if (!user) + return NULL; + + /* Allocate memory for buffer backing buffer. */ +#ifdef USE_TRAP_PAGING + allocsize = 4 * round_up(BUFFERSIZE, getpagesize()) + + round_up(ERR_BUFFERSIZE, getpagesize()) + + 6 * getpagesize(); + + for (i = 0; i < 5; i++) { + offset[i] = i * round_up(BUFFERSIZE, getpagesize()) + (i + 1) * getpagesize(); + trap[i] = offset[i] - getpagesize(); + } + trap[5] = allocsize - getpagesize(); + + user->u_buf_backing_size = allocsize; + user->u_buf_backing = (unsigned char*)mmap(NULL, + user->u_buf_backing_size, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); + if (user->u_buf_backing == MAP_FAILED) + r = -1; + for (i = 0; i < 6; i++) + if (mprotect(user->u_buf_backing + trap[i], getpagesize(), + PROT_NONE) < 0) { + munmap(user->u_buf_backing, user->u_buf_backing_size); + r = -1; + break; + } +#else + user->u_buf_backing = (unsigned char*)malloc(allocsize); + user->u_buf_backing_size = 0; + if (!user->u_buf_backing) + r = -1; +#endif + if (r < 0) { + free(user); + return NULL; + } + + user->u_black_in_eof = 0; + user->u_black_out_eof = 0; + user->u_black_in_d_eof = 0; + user->u_black_out_d_eof = 0; + user->u_want_read = 3; + user->u_want_write = 3; + user->u_want_hand = 3; + user->u_red_assume_more = 1; + user->u_red_in_have_data = 0; + user->u_red_err_hdr_sent = 0; + user->u_tls_active = 0; + user->u_black_fd = black_fd; + user->u_red_in_fd = -1; + user->u_red_out_fd = -1; + user->u_red_err_fd = -1; + user->u_black_in_buf = cbuffer_create(user->u_buf_backing + + offset[0], BUFFERSIZE); + user->u_black_out_buf = cbuffer_create(user->u_buf_backing + + offset[1], BUFFERSIZE); + user->u_red_in_buf = cbuffer_create(user->u_buf_backing + + offset[2], BUFFERSIZE); + user->u_red_out_buf = cbuffer_create(user->u_buf_backing + + offset[3], BUFFERSIZE); + user->u_red_err_buf = cbuffer_create(user->u_buf_backing + + offset[4], ERR_BUFFERSIZE); + if (!user->u_black_in_buf || !user->u_black_out_buf || + !user->u_red_in_buf || !user->u_red_out_buf || + !user->u_red_err_buf) { + /* Failed to allocate memory. */ + cbuffer_destroy(user->u_black_in_buf); + cbuffer_destroy(user->u_black_out_buf); + cbuffer_destroy(user->u_red_in_buf); + cbuffer_destroy(user->u_red_out_buf); + cbuffer_destroy(user->u_red_err_buf); +#ifdef USE_TRAP_PAGING + munmap(user->u_buf_backing, user->u_buf_backing_size); +#else + free(user->u_buf_backing); +#endif + free(user); + return NULL; + } + user->u_deadline = t; + user->u_failure = USER_STILL_ACTIVE; + user->u_delay_failure = USER_STILL_ACTIVE; + user->u_errmsg = NULL; + user->u_tls_session = NULL; + user->u_delay_tls_failure = 0; + user->u_raw_tx = 0; + user->u_raw_rx = 0; + user->u_decoded_tx = 0; + user->u_decoded_rx = 0; + return user; +} + + + +void user_release(struct user *user) +{ + if (!user->u_failure) + cleanup_user(user, USER_KILL, "User session killed"); + + cbuffer_destroy(user->u_black_in_buf); + cbuffer_destroy(user->u_black_out_buf); + cbuffer_destroy(user->u_red_in_buf); + cbuffer_destroy(user->u_red_out_buf); + cbuffer_destroy(user->u_red_err_buf); + free(user->u_errmsg); + +#ifdef USE_TRAP_PAGING + munmap(user->u_buf_backing, user->u_buf_backing_size); +#else + free(user->u_buf_backing); +#endif + free(user); +} + +int user_tls_configured(struct user *user) +{ + return (user->u_tls_session != NULL); +} + +const char *user_explain_failure(int code) +{ + switch(code) { + case USER_STILL_ACTIVE: + return "Still active"; + case USER_CONNECTION_END: + return "Connection closed"; + case USER_LAYER4_ERROR: + return "Transport error"; + case USER_TLS_ERROR: + return "TLS error"; + case USER_TLS_HAND_ERROR: + return "TLS handshake error"; + case USER_KILL: + return "User killed"; + case USER_RED_FAILURE: + return "Subprocess failure"; + case USER_TIMEOUT: + return "Timeout"; + default: + return "Unknown error"; + } +} + +struct cbuffer *user_get_red_in_force(struct user *user) +{ + return user->u_red_in_buf; +} + +struct cbuffer *user_get_red_out_force(struct user *user) +{ + return user->u_red_out_buf; +} + +struct cbuffer *user_get_red_err_force(struct user *user) +{ + return user->u_red_err_buf; +} + +void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert) +{ + char error_buffer[8192]; + + if (user->u_delay_tls_failure) + return; + + user->u_delay_tls_failure = 1; + user->u_delay_alert = alert; + sprintf(error_buffer, "TLS alert forced: %s", + gnutls_alert_get_name(alert)); + user->u_errmsg = strdup(error_buffer); +} + +int user_red_out_eofd(struct user *user) +{ + if (user->u_red_out_fd >= 0) + return 0; + if (user->u_tls_session && !user->u_black_in_d_eof) + return 0; + if (!user->u_tls_session && cbuffer_used(user->u_black_in_buf)) + return 0; + if (!user->u_tls_session && !user->u_black_in_eof) + return 0; + return 1; +} + +#define NAME_SPACE 60 + +void print_name(char *buffer, const char *name) +{ + unsigned i; + if(strlen(name) >= NAME_SPACE) { + strncpy(buffer, name, NAME_SPACE - 4); + for(i = NAME_SPACE - 4; i < NAME_SPACE - 1; i++) + buffer[NAME_SPACE - i] = '.'; + } else { + strcpy(buffer, name); + for(i = strlen(name); i < NAME_SPACE - 1; i++) + buffer[i] = ' '; + } + buffer[NAME_SPACE - 1] = ' '; +} + +void print_boolean(FILE *filp, const char *name, unsigned value) +{ + char buffer[NAME_SPACE + 10]; + print_name(buffer, name); + if (value) + strcpy(buffer + NAME_SPACE, "Set"); + else + strcpy(buffer + NAME_SPACE, "Clear"); + fprintf(filp, "%s\n", buffer); +} + +void print_status(FILE *filp, const char *name, unsigned value) +{ + char buffer[NAME_SPACE + 40]; + print_name(buffer, name); + if (value == 0) + strcpy(buffer + NAME_SPACE, "Success"); + else if (value == 1) + strcpy(buffer + NAME_SPACE, "Insufficent data"); + else if (value == 2) + strcpy(buffer + NAME_SPACE, "Insufficent space"); + else if (value == 3) + strcpy(buffer + NAME_SPACE, "Not attempted"); + else + sprintf(buffer + NAME_SPACE, "Unknown code %u", value); + fprintf(filp, "%s\n", buffer); +} + +void print_fd(FILE *filp, const char *name, int fd) +{ + char buffer[NAME_SPACE + 40]; + print_name(buffer, name); + if (fd >= 0) + sprintf(buffer + NAME_SPACE, "%i", fd); + else + strcpy(buffer + NAME_SPACE, "Not connected"); + fprintf(filp, "%s\n", buffer); +} + +void print_delayed_failure(FILE *filp, const char *name, unsigned dftype, + unsigned dfailure) +{ + char buffer[NAME_SPACE + 40]; + print_name(buffer, name); + if (dftype == 0) + strcpy(buffer + NAME_SPACE, "<None>"); + else if (dftype == 1) + sprintf(buffer + NAME_SPACE, "Code %u while handshaking", dfailure); + else if (dftype == 2) + sprintf(buffer + NAME_SPACE, "Code %u while transferring", dfailure); + else + sprintf(buffer + NAME_SPACE, "Code %u while in unknown %u", dfailure, dftype); + fprintf(filp, "%s\n", buffer); +} + +void print_format(FILE *filp, const char *name, const char *format, ...) +{ + va_list args; + char buffer[NAME_SPACE + 120]; + print_name(buffer, name); + va_start(args, format); + vsprintf(buffer + NAME_SPACE, format, args); + fprintf(filp, "%s\n", buffer); +} + +void print_since(FILE *filp, const char *name, struct timeval event, + struct timeval now) +{ + char buffer[NAME_SPACE + 40]; + uint64_t etime; + uint64_t ntime; + uint64_t tdelta; + print_name(buffer, name); + etime = (uint64_t)event.tv_sec * 1000000 + event.tv_usec; + ntime = (uint64_t)now.tv_sec * 1000000 + now.tv_usec; + if (etime < ntime) + tdelta = ntime - etime; + else + tdelta = 0; + if (tdelta < 1000000) { + sprintf(buffer + NAME_SPACE, "<1s ago"); + } else { + sprintf(buffer + NAME_SPACE, "%llu.%03us ago", + (unsigned long long)(tdelta / 1000000), + (unsigned)(tdelta % 1000000 / 1000)); + } + fprintf(filp, "%s\n", buffer); +} + +void user_debug(struct user *user, FILE* filp) +{ + struct timeval tv; + if (!user) { + fprintf(filp, "\n\n\nNo transport present\n\n\n\n"); + return; + } + gettimeofday(&tv, NULL); + + fprintf(filp, "\n\n\nTransport level debugging output (version 3):\n"); + print_boolean(filp, "Socket raw EOF received flag:", user->u_black_in_eof); + print_boolean(filp, "Socket raw EOF sent flag:", user->u_black_out_eof); + print_boolean(filp, "Encrypted EOF received flag:", user->u_black_in_d_eof); + print_boolean(filp, "Encrypted EOF sent flag:", user->u_black_out_d_eof); + print_boolean(filp, "Assume more local inptt flag:", user->u_red_assume_more); + print_boolean(filp, "TLS handshaked flag:", user->u_tls_active); + print_boolean(filp, "Received local input flag:", user->u_red_in_have_data); + print_status(filp, "Last read status:", user->u_want_read); + print_status(filp, "Last write status:", user->u_want_write); + print_status(filp, "Last handshake status:", user->u_want_hand); + print_format(filp, "Error header bytes sent:", "%u", user->u_red_err_hdr_sent); + print_delayed_failure(filp, "Delayed TLS failure:", user->u_delay_tls_failure, + user->u_delay_alert); + print_boolean(filp, "Seen input data flag:", user->u_seen_input_data); + print_fd(filp, "Socket fd:", user->u_black_fd); + print_fd(filp, "Local input fd:", user->u_red_in_fd); + print_fd(filp, "Local output fd:", user->u_red_out_fd); + print_fd(filp, "Local error fd:", user->u_red_err_fd); + if (user->u_deadline.tv_sec < 0) + print_format(filp, "Deadline:", "None set"); + else + print_format(filp, "Deadline:", "%lu.%06lu\n", + (unsigned long)user->u_deadline.tv_sec, + (unsigned long)user->u_deadline.tv_usec); + print_format(filp, "Failure code:", "%i", user->u_failure); + if (user->u_errmsg) + print_format(filp, "Detailed error:", "%s", user->u_errmsg); + else + print_format(filp, "Detailed error:", "<Not set>"); + print_format(filp, "Delayed failure code:", "%i", user->u_delay_failure); + if (user->u_tls_session) + print_format(filp, "TLS context:", "Present"); + else + print_format(filp, "TLS context:", "Absent"); + print_format(filp, "Socket input buffer:", "Used: %lu, free: %lu", + (unsigned long)cbuffer_used(user->u_black_in_buf), + (unsigned long)cbuffer_free(user->u_black_in_buf)); + print_format(filp, "Socket output buffer:", "Used: %lu, free: %lu", + (unsigned long)cbuffer_used(user->u_black_out_buf), + (unsigned long)cbuffer_free(user->u_black_out_buf)); + print_format(filp, "Local input buffer:", "Used: %lu, free: %lu", + (unsigned long)cbuffer_used(user->u_red_in_buf), + (unsigned long)cbuffer_free(user->u_red_in_buf)); + print_format(filp, "Local output buffer:", "Used: %lu, free: %lu", + (unsigned long)cbuffer_used(user->u_red_out_buf), + (unsigned long)cbuffer_free(user->u_red_out_buf)); + print_format(filp, "Local error buffer:", "Used: %lu, free: %lu", + (unsigned long)cbuffer_used(user->u_red_err_buf), + (unsigned long)cbuffer_free(user->u_red_err_buf)); + print_format(filp, "TX bytes:", "Raw: %llu, Decoded: %llu", + user->u_raw_tx, user->u_decoded_tx); + print_format(filp, "RX bytes:", "Raw: %llu, Decoded: %llu", + user->u_raw_rx, user->u_decoded_rx); + print_since(filp, "Last TX:", user->u_last_tx, tv); + print_since(filp, "Last RX:", user->u_last_rx, tv); + fprintf(filp,"\n\n\n"); +} diff --git a/git-over-tls/user.h b/git-over-tls/user.h new file mode 100644 index 0000000..ac0d1fa --- /dev/null +++ b/git-over-tls/user.h @@ -0,0 +1,367 @@ +/* + * Copyright (C) Ilari Liusvaara 2009 + * + * This code is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _user__h__included__ +#define _user__h__included__ + +#include <gnutls/gnutls.h> +#include "cbuffer.h" +#include <stdint.h> +#include <stdio.h> +#include <sys/time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Terminoloyy: + * black: + * Side of user session connected to socket. Transports + * unencrypted or TLS data between peers. + * red: + * Side of user session connected to server loop or to + * helper program. + * red input: + * Input buffer unencrypted data is read from. May be connected + * to file descriptor. In that case that file descriptor is + * read for data. + * red output: + * Output buffer decrypted data is written to. May be connected + * to file descriptor. In that cse that file descriptor is + * written with the data. + * red error: + * Input buffer error input is read from. May be connecte to + * file descriptor, which is read for input data. If red input + * file descriptor has data succesfully read, the red error buffer + * is cleared and any further error input is redirected to bit + * bucket. If red input closes with red error having data, then + * that data is sent as ERR packet when red error has associated + * file descriptor closed. + * red_in: + * File descriptor associated with red input. + * red_out: + * File descriptor associated with red output. + * red_err: + * File descriptor associated with red error. + * deadline: + * Time to disconnect (some) client on or perform some other + * service. + */ + +/* Main session structure. Opaque type. */ +struct user; + +/* Failure codes. */ +/* User still active. */ +#define USER_STILL_ACTIVE 0 +/* Connection normal end. */ +#define USER_CONNECTION_END 1 +/* Transport error. */ +#define USER_LAYER4_ERROR -1 +/* TLS error while not handshaking. */ +#define USER_TLS_ERROR -2 +/* TLS handshake error. */ +#define USER_TLS_HAND_ERROR -3 +/* User killed. */ +#define USER_KILL -4 +/* Red file descriptor I/O operation failure. */ +#define USER_RED_FAILURE -5 +/* User timed out. */ +#define USER_TIMEOUT -6 + +/* + * Create new user session. + * + * Input: + * black_fd Black fd. Must be socket. + * timeout_secs Initial timeout in seconds. + * + * Output: + * Return value Newly created session, or NULL on out of + * memory. + */ +struct user *user_create(int black_fd, unsigned timeout_secs); + +/* + * Configure session to use TLS. The TLS session must be preconfigured + * (credentials set, etc), but not handshaked. + * + * Input: + * user The user session to manipulate. + * session TLS session to assign. + */ +void user_configure_tls(struct user *user, gnutls_session_t session); + +/* + * Add current user session file descriptors that are ready to read or + * write to file descriptor sets for read or write. Also update dead- + * line if needed. + * + * Input: + * user The user session to handle. + * bound Current file descriptor bound. 0 if there are + * no file descriptors in either set. + * rfds Read file descriptor set. + * wfds Write file descriptor set. + * deadline Current deadline for select. + * + * Output: + * bound Updated file descriptor bound. + * rfds Updated read file descriptor set. + * wfds Updated write file descriptor set. + * deadline Updated deadline for select. + */ +void user_add_to_sets(struct user *user, int *bound, fd_set *rfds, + fd_set *wfds, struct timeval *deadline); + +/* + * Service this user. + * + * Input: + * user The user session to handle. + * rfds Ready to read file descriptors. + * wfds Ready to write file descriptors. + */ +void user_service(struct user *user, fd_set *rfds, fd_set *wfds); + +/* + * Service this user without doing any I/O + * + * Input: + * user The user session to handle. + */ +void user_service_nofd(struct user *user); + +/* + * Get failure class. + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value 0 if connection is active, 1 if end + * of connection, negative code if connection + * ended with error. See USER_* defintions. + */ +int user_get_failure(struct user *user); + +/* + * Get explanation of failure code. + * + * Input: + * code The failure code. + * + * Output: + * Return value String explaining the code. Do not + * free this. + */ +const char *user_explain_failure(int code); + +/* + * Get more detailed error message to explain the failure. + * + * Input: + * user The user session. + * + * Output: + * Return value Error message or NULL if there is + * no more detailed error message. Do + * not free this. + * + * Notes: + * - Error message can be NULL or not independently of + * main failure status. + */ +const char *user_get_error(struct user *user); + +/* + * Has TLS been configured? + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value Nonzero if configured, zero if not. + */ +/* Returns 1 if TLS has been configured, 0 otherwise. */ +int user_tls_configured(struct user *user); + +/* + * Return TLS session associated with user session. + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value If TLS is configured and has handshaked, the TLS + * session. Otherwise NULL. + */ +gnutls_session_t user_get_tls(struct user *user); + +/* + * Free user structure. If user is still active, disconnect user hard. + * + * Input: + * user The user session to release. + */ +void user_release(struct user *user); + +/* + * Set red I/O file descriptors. + * + * Input: + * user The user session to manipulate. + * red_in Red input file descriptor, -1 for none. + * red_out Red output file descritpor, -1 for none. + * red_err Red error file descriptor, -1 for none. + * + * Notes: + * - red_in and red_err must be read ends if present. + * - red_out must be write end if present. + */ +void user_set_red_io(struct user *user, int red_in, int red_out, int red_err); + +/* + * Clear red I/O by closing red output and marking no red output file + * descriptor. This may be neeeded, since red out can't close without + * input to transfer. + * + * Input: + * user The user session to manipulate. + */ +void user_clear_red_io(struct user *user); + +/* + * Get red input buffer. + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value If red_in is set to none, the buffer, otherwise + * NULL. + */ +struct cbuffer *user_get_red_in(struct user *user); + +/* + * Get red output buffer. + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value If red_out is set to none, the buffer, otherwise + * NULL. + */ +struct cbuffer *user_get_red_out(struct user *user); + +/* + * Get red error buffer. + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value If red_err is set to none and no data has been + * received through red_in, the buffer, otherwise + * NULL. + */ +struct cbuffer *user_get_red_err(struct user *user); + +/* + * Clear deadline (don't generate timeout anymore). + * + * Input: + * user The user session to manipulate. + */ +void user_clear_deadline(struct user *user); + +/* + * Send EOF to red input. + * + * Input: + * user The user session to manipulate. + * + * Notes: + * - Only works if red input has no file descriptor associated. + * - EOF is automatically sent if red_in encounters EOF. + */ +void user_send_red_in_eof(struct user *user); + +/* + * Force get red input buffer (even if it shouldn't be available). + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value Red input buffer. + */ +struct cbuffer *user_get_red_in_force(struct user *user); + +/* + * Force get red output buffer (even if it shouldn't be available). + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value Red output buffer. + */ +struct cbuffer *user_get_red_out_force(struct user *user); + +/* + * Force get red error buffer (even if it shouldn't be available). + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value Red error buffer. + */ +struct cbuffer *user_get_red_err_force(struct user *user); + +/* + * Send fatal TLS alert. + * + * Input: + * user The user session to manipulate. + * alert The alert to send. + * + * Notes: + * - Ignored if no TLS has been configured. + */ +void user_tls_send_alert(struct user *user, gnutls_alert_description_t alert); + +/* + * Has red output been EOF'd? + * + * Input: + * user The user session to interrogate. + * + * Output: + * Return value Nonzero if red out can receive no more data and + * not connected to file descriptor, otherwise zero + * (more data possible). + */ +int user_red_out_eofd(struct user *user); + +/* + * Print debugging output. + * + * Input: + * user The user session to interrogate. + * filp Where to write the output. + */ +void user_debug(struct user *user, FILE *filp); + +#ifdef __cplusplus +} +#endif + +#endif -- 1.7.1.rc2.10.g714149 -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html