Add sideband implementation.
authorAndre Noll <maan@systemlinux.org>
Sun, 18 Dec 2011 19:18:49 +0000 (20:18 +0100)
committerAndre Noll <maan@systemlinux.org>
Sat, 5 May 2012 10:54:53 +0000 (12:54 +0200)
This adds the new source files sideband.[ch] to the repository and
links the corresponding object file into para_server, para_audiod
and para_client.

The sideband API enables the command handlers of para_server to send a
multiplexed output stream. The client side (para_client or para_audiod)
demultiplex the received stream afterwards.

The design is as simple as it can be: Each data buffer is preceded
by a 5-byte header containing the length and the band designator,
a one-byte number which specifies the type of the data which follows.

The server-side and the client-side implement their own variant of the
send_sb() and recv_sb() functions for sending and receiving sideband
packets. Two implementations are necessary because the underlying
file descriptor is blocking on the server side (command handlers of
para_server) while it is non-blocking on the client side (para_client
and para_audiod).

The sideband API allows to optionally filter all data (including the
sideband header) through an arbitrary transformation. This patch adds
a suitable transformation as a new public function performs RC4 to
the crypto API.

To keep the patch within reasonable size, this commit does not contain
any users of the new sideband API yet. Subsequent commits will teach
the authentication handshake code and the command handlers to send
and receive data as sideband packets.

19 files changed:
afs.c
afs.cmd
aft.c
attribute.c
blob.c
client_common.c
command.c
command.h
configure.ac
crypt.c
crypt.h
crypt_common.c
error.h
gcc-compat.h
gcrypt.c
para.h
server.cmd
sideband.c [new file with mode: 0644]
sideband.h [new file with mode: 0644]

diff --git a/afs.c b/afs.c
index 013fdfe..9cef049 100644 (file)
--- a/afs.c
+++ b/afs.c
@@ -26,6 +26,7 @@
 #include "signal.h"
 #include "fd.h"
 #include "mood.h"
+#include "sideband.h"
 #include "command.h"
 
 /** The osl tables used by afs. \sa blob.c. */
diff --git a/afs.cmd b/afs.cmd
index c780804..11b1631 100644 (file)
--- a/afs.cmd
+++ b/afs.cmd
@@ -4,7 +4,7 @@ HC: Prototypes for the commands of the audio file selector.
 CC: Array of commands for the audio file selector.
 AT: server_command
 SI: osl regex
-IN: para error crypt command string afh afs server list user_list
+IN: para error crypt sideband command string afh afs server list user_list
 SN: list of afs commands
 TM: mood lyr img pl
 ---
diff --git a/aft.c b/aft.c
index ba65bc2..fb40a7b 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -21,6 +21,7 @@
 #include "fd.h"
 #include "ipc.h"
 #include "portable_io.h"
+#include "sideband.h"
 #include "command.h"
 
 static struct osl_table *audio_file_table;
index 7532711..edf3baf 100644 (file)
@@ -16,6 +16,7 @@
 #include "afh.h"
 #include "afs.h"
 #include "ipc.h"
+#include "sideband.h"
 #include "command.h"
 
 static struct osl_table *attribute_table;
diff --git a/blob.c b/blob.c
index 89d993d..bc89869 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -18,6 +18,7 @@
 #include "afs.h"
 #include "ipc.h"
 #include "portable_io.h"
+#include "sideband.h"
 #include "command.h"
 
 /**
index e34ed64..ba4c269 100644 (file)
@@ -17,6 +17,7 @@
 #include "crypt.h"
 #include "net.h"
 #include "fd.h"
+#include "sideband.h"
 #include "string.h"
 #include "client.cmdline.h"
 #include "client.h"
@@ -153,6 +154,69 @@ static int client_recv_buffer(struct client_task *ct, fd_set *rfds,
        return 0;
 }
 
+static int send_sb(struct client_task *ct, void *buf, size_t numbytes,
+               enum sb_designator band, bool dont_free)
+{
+       int ret, fd = ct->scc.fd;
+       struct iovec iov[2];
+
+       if (!ct->sbc) {
+               struct sb_buffer sbb;
+               sb_transformation trafo = ct->status < CL_RECEIVED_PROCEED?
+                       NULL : sc_trafo;
+               sbb = (typeof(sbb))SBB_INIT(band, buf, numbytes);
+               ct->sbc = sb_new_send(&sbb, dont_free, trafo, ct->scc.send);
+       }
+       ret = sb_get_send_buffers(ct->sbc, iov);
+       ret = xwritev(fd, iov, ret);
+       if (ret < 0) {
+               sb_free(ct->sbc);
+               ct->sbc = NULL;
+               return ret;
+       }
+       if (sb_sent(ct->sbc, ret)) {
+               ct->sbc = NULL;
+               return 1;
+       }
+       return 0;
+}
+
+static int recv_sb(struct client_task *ct, fd_set *rfds,
+               struct sb_buffer *result)
+{
+       int ret;
+       size_t n;
+       sb_transformation trafo;
+       void *trafo_context;
+       struct iovec iov;
+
+       if (!FD_ISSET(ct->scc.fd, rfds))
+               return 0;
+       if (ct->status < CL_SENT_CH_RESPONSE)
+               trafo = trafo_context = NULL;
+       else {
+               trafo = sc_trafo;
+               trafo_context = ct->scc.recv;
+       }
+       if (!ct->sbc)
+               ct->sbc = sb_new_recv(0, trafo, trafo_context);
+again:
+       sb_get_recv_buffer(ct->sbc, &iov);
+       ret = read_nonblock(ct->scc.fd, iov.iov_base, iov.iov_len, rfds, &n);
+       if (ret < 0) {
+               sb_free(ct->sbc);
+               ct->sbc = NULL;
+               return ret;
+       }
+       if (n == 0)
+               return 0;
+       if (!sb_received(ct->sbc, n, result))
+               goto again;
+       ct->sbc = NULL;
+       return 1;
+}
+
+
 static char **parse_features(char *buf)
 {
        int i;
index cdc42fa..9cf1967 100644 (file)
--- a/command.c
+++ b/command.c
@@ -15,6 +15,7 @@
 #include "para.h"
 #include "error.h"
 #include "crypt.h"
+#include "sideband.h"
 #include "command.h"
 #include "server.cmdline.h"
 #include "string.h"
@@ -191,6 +192,118 @@ static int check_sender_args(int argc, char * const * argv, struct sender_comman
        return 1;
 }
 
+/**
+ * Send a sideband packet through a blocking file descriptor.
+ *
+ * \param scc fd and crypto keys.
+ * \param buf The buffer to send.
+ * \param numbytes The size of \a buf.
+ * \param band The sideband designator of this packet.
+ * \param dont_free If true, never deallocate \a buf.
+ *
+ * The nonblock flag must be disabled for the file descriptor given by \a scc.
+ *
+ * Stream cipher encryption is automatically activated if neccessary via the
+ * sideband transformation, depending on the value of \a band.
+ *
+ * \return Standard.
+ *
+ * \sa \ref send_sb_va().
+ */
+int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes,
+               int band, bool dont_free)
+{
+       int ret;
+       struct sb_context *sbc;
+       struct iovec iov[2];
+       struct sb_buffer sbb = SBB_INIT(band, buf, numbytes);
+       sb_transformation trafo = band < SBD_PROCEED? NULL : sc_trafo;
+
+       sbc = sb_new_send(&sbb, dont_free, trafo, scc->send);
+       do {
+               ret = sb_get_send_buffers(sbc, iov);
+               ret = xwritev(scc->fd, iov, ret);
+               if (ret < 0)
+                       goto fail;
+       } while (sb_sent(sbc, ret) == false);
+       return 1;
+fail:
+       sb_free(sbc);
+       return ret;
+}
+
+/**
+ * Create a variable sized buffer and send it as a sideband packet.
+ *
+ * \param scc Passed to \ref send_sb.
+ * \param band See \ref send_sb.
+ * \param fmt The format string.
+ *
+ * \return The return value of the underlying call to \ref send_sb.
+ */
+__printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band,
+               const char *fmt, ...)
+{
+       va_list ap;
+       char *msg;
+       int ret;
+
+       va_start(ap, fmt);
+       ret = xvasprintf(&msg, fmt, ap);
+       va_end(ap);
+       return send_sb(scc, msg, ret, band, false);
+}
+
+/**
+ * Send a sideband packet through a blocking file descriptor.
+ *
+ * \param scc fd and crypto keys.
+ * \param expected_band The expected band designator.
+ * \param max_size Passed to \ref sb_new_recv().
+ * \param result Body of the sideband packet is returned here.
+ *
+ * If \a expected_band is not \p SBD_ANY, the band designator of the received
+ * sideband packet is compared to \a expected_band and a mismatch is considered
+ * an error.
+ *
+ * \return Standard.
+ */
+int recv_sb(struct stream_cipher_context *scc,
+               enum sb_designator expected_band,
+               size_t max_size, struct iovec *result)
+{
+       int ret;
+       struct sb_context *sbc;
+       struct iovec iov;
+       struct sb_buffer sbb;
+       sb_transformation trafo;
+
+       trafo = expected_band != SBD_ANY && expected_band < SBD_PROCEED?
+               NULL : sc_trafo;
+       sbc = sb_new_recv(max_size, trafo, scc->recv);
+       for (;;) {
+               sb_get_recv_buffer(sbc, &iov);
+               ret = recv_bin_buffer(scc->fd, iov.iov_base, iov.iov_len);
+               if (ret == 0)
+                       ret = -E_EOF;
+               if (ret < 0)
+                       goto fail;
+               ret = sb_received(sbc, ret, &sbb);
+               if (ret < 0)
+                       goto fail;
+               if (ret > 0)
+                       break;
+       }
+       ret = -E_BAD_BAND;
+       if (expected_band != SBD_ANY && sbb.band != expected_band)
+               goto fail;
+       *result = sbb.iov;
+       return 1;
+fail:
+       sb_free(sbc);
+       return ret;
+}
+
 int com_sender(struct command_context *cc)
 {
        int i, ret;
index 851c00b..851b8f1 100644 (file)
--- a/command.h
+++ b/command.h
@@ -35,3 +35,11 @@ struct server_command {
        /** The long help text. */
        const char *help;
 };
+
+int send_sb(struct stream_cipher_context *scc, void *buf, size_t numbytes,
+               int band, bool dont_free);
+__printf_3_4 int send_sb_va(struct stream_cipher_context *scc, int band,
+               const char *fmt, ...);
+int recv_sb(struct stream_cipher_context *scc,
+               enum sb_designator expected_band,
+               size_t max_size, struct iovec *result);
index a57d8d7..c616b0a 100644 (file)
@@ -100,7 +100,7 @@ all_errlist_objs="mp3_afh afh_common net string signal time daemon
        exec send_common ggo udp_recv color fec fecdec_filter
        prebuffer_filter audiod_command_list bitstream imdct
        wma_afh wma_common wmadec_filter buffer_tree crypt_common
-       gui gui_theme"
+       gui gui_theme sideband"
 
 executables="recv filter audioc write client afh audiod"
 
@@ -122,7 +122,7 @@ audioc_errlist_objs="audioc string net fd"
 audioc_ldflags=""
 
 audiod_cmdline_objs="add_cmdline(audiod compress_filter http_recv dccp_recv file_write client amp_filter udp_recv prebuffer_filter)"
-audiod_errlist_objs="audiod signal string daemon stat net crypt_common
+audiod_errlist_objs="audiod signal string daemon stat net crypt_common sideband
        time grab_client filter_common wav_filter compress_filter amp_filter http_recv dccp_recv
        recv_common fd sched write_common file_write audiod_command fecdec_filter
        client_common ggo udp_recv color fec prebuffer_filter audiod_command_list
@@ -142,7 +142,7 @@ writers=" file"
 default_writer="FILE_WRITE"
 
 client_cmdline_objs="add_cmdline(client)"
-client_errlist_objs="client net string fd sched stdin stdout time
+client_errlist_objs="client net string fd sched stdin stdout time sideband
        client_common buffer_tree crypt_common"
 client_ldflags=""
 
@@ -270,7 +270,7 @@ else
                crypt_common ipc dccp_send fd user_list chunk_queue
                afs aft mood score attribute blob playlist sched acl
                send_common udp_send color fec server_command_list
-               afs_command_list wma_afh wma_common"
+               afs_command_list wma_afh wma_common sideband"
 
        all_errlist_objs="$all_errlist_objs server vss command
                http_send close_on_fork mm ipc dccp_send user_list
diff --git a/crypt.c b/crypt.c
index f33f769..b754c09 100644 (file)
--- a/crypt.c
+++ b/crypt.c
@@ -317,6 +317,23 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
        return ret;
 }
 
+void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
+{
+       RC4_KEY *key = &sc->key;
+
+       *dst = (typeof(*dst)) {
+               /*
+                * Add one for the terminating zero byte. Integer overflow is
+                * no problem here as para_malloc() aborts when given a zero
+                * size argument.
+                */
+               .iov_base = para_malloc(src->iov_len + 1),
+               .iov_len = src->iov_len
+       };
+       RC4(key, src->iov_len, src->iov_base, dst->iov_base);
+       ((char *)dst->iov_base)[dst->iov_len] = '\0';
+}
+
 void hash_function(const char *data, unsigned long len, unsigned char *hash)
 {
        SHA_CTX c;
diff --git a/crypt.h b/crypt.h
index c466669..ac11088 100644 (file)
--- a/crypt.h
+++ b/crypt.h
@@ -128,6 +128,36 @@ struct stream_cipher_context {
  */
 struct stream_cipher *sc_new(const unsigned char *data, int len);
 
+/**
+ * Encrypt or decrypt a buffer using a stream cipher.
+ *
+ * \param sc Crypto key.
+ * \param src The source buffer and length.
+ * \param dst The destination buffer and length, filled out by the function.
+ *
+ * It is up to the implementation to decide whether the crypt operation is
+ * performed in place. The caller can tell by looking if the buffers given by
+ * \a src and \a dst coincide after the call. If (and only if) the crypt
+ * operation was not performed in place, the function allocated a new buffer
+ * for the result, so dst->iov_base is different from src->iov_base. In this
+ * case, the destination buffer must be freed by the caller when it is no
+ * longer needed.
+ */
+void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst);
+
+/**
+ * Wrapper for \ref sc_crypt() that can be used as a sideband transformation.
+ *
+ * \param src Passed verbatim to \ref sc_crypt().
+ * \param dst Passed verbatim to \ref sc_crypt().
+ * \param trafo_context Must point to an initialized stream cipher.
+ */
+_static_inline_ void sc_trafo(struct iovec *src, struct iovec *dst,
+               void *trafo_context)
+{
+       sc_crypt(trafo_context, src, dst);
+}
+
 /**
  * Deallocate a stream cipher structure.
  *
@@ -200,7 +230,6 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
  */
 int sc_recv_buffer(struct stream_cipher_context *scc, char *buf, size_t size);
 
-
 /** Size of the hash value in bytes. */
 #define HASH_SIZE 20
 
index 5ad4d43..705c724 100644 (file)
@@ -11,8 +11,8 @@
 #include "para.h"
 #include "error.h"
 #include "string.h"
-#include "crypt_backend.h"
 #include "crypt.h"
+#include "crypt_backend.h"
 
 /** If the key begins with this text, we treat it as an ssh key. */
 #define KEY_TYPE_TXT "ssh-rsa"
diff --git a/error.h b/error.h
index a79cb37..8ebf4ef 100644 (file)
--- a/error.h
+++ b/error.h
@@ -39,6 +39,11 @@ DEFINE_ERRLIST_OBJECT_ENUM;
 
 extern const char **para_errlist[];
 
+#define SIDEBAND_ERRORS \
+       PARA_ERROR(BAD_BAND, "invalid or unexpected band designator"), \
+       PARA_ERROR(SB_PACKET_SIZE, "invalid sideband packet size or protocol error"), \
+
+
 #define FLACDEC_FILTER_ERRORS \
        PARA_ERROR(FLACDEC_DECODER_ALLOC, "could not allocate stream decoder"), \
        PARA_ERROR(FLACDEC_DECODER_INIT, "could not init stream decoder"), \
index 5d20728..dd6afe1 100644 (file)
  * As direct use of __printf(p,q) confuses doxygen, here are some extra macros
  * for those values p,q that are actually used.
  */
-#define  __printf_2_0 __printf(2,0)
-#define  __printf_1_2 __printf(1,2)
-#define  __printf_2_3 __printf(2,3)
+#define __printf_2_0 __printf(2,0)
+#define __printf_1_2 __printf(1,2)
+#define __printf_2_3 __printf(2,3)
+#define __printf_3_4 __printf(3,4)
 
 # if __GNUC__ > 3  || (__GNUC__ == 3 && __GNUC_MINOR__ > 3)
 # define __must_check  __attribute__ ((warn_unused_result))
index d11e94c..926eb15 100644 (file)
--- a/gcrypt.c
+++ b/gcrypt.c
@@ -972,3 +972,15 @@ int sc_recv_bin_buffer(struct stream_cipher_context *scc, char *buf,
        assert(gret == 0);
        return ret;
 }
+
+void sc_crypt(struct stream_cipher *sc, struct iovec *src, struct iovec *dst)
+{
+       gcry_cipher_hd_t handle = sc->handle;
+       gcry_error_t gret;
+
+       /* perform in-place encryption */
+       *dst = *src;
+       gret = gcry_cipher_encrypt(handle, src->iov_base, src->iov_len,
+               NULL, 0);
+       assert(gret == 0);
+}
diff --git a/para.h b/para.h
index 0a14b99..5e0501a 100644 (file)
--- a/para.h
+++ b/para.h
@@ -131,6 +131,18 @@ _static_inline_ long int para_random(unsigned max)
        return ((max + 0.0) * (random() / (RAND_MAX + 1.0)));
 }
 
+/**
+ * Simple sanity check for I/O vectors.
+ *
+ * \param iov Pointer to the I/O vector to check.
+ *
+ * \return True if \a iov points to a non-empty buffer.
+ */
+_static_inline_ bool iov_valid(const struct iovec *iov)
+{
+       return iov && iov->iov_len > 0 && iov->iov_base;
+}
+
 /** Round up x to next multiple of y. */
 #define ROUND_UP(x, y) ({ \
        const typeof(y) _divisor = y; \
index c9b34d1..d0e8f27 100644 (file)
@@ -4,7 +4,7 @@ HC: prototypes for the server command handlers
 CC: array of server commands
 AT: server_command
 SI: osl regex
-IN: para error crypt command string afh afs server list user_list
+IN: para error crypt sideband command string afh afs server list user_list
 SN: list of server commands
 ---
 N: ff
diff --git a/sideband.c b/sideband.c
new file mode 100644 (file)
index 0000000..bf99088
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file sideband.c Implementation of the sideband API. */
+
+#include <regex.h>
+#include <sys/uio.h>
+
+#include "para.h"
+#include "error.h"
+#include "portable_io.h"
+#include "string.h"
+#include "sideband.h"
+
+/** Each sideband packet consists of a header and a data part. */
+#define SIDEBAND_HEADER_SIZE 5
+
+struct sb_context {
+       char header[SIDEBAND_HEADER_SIZE];
+       size_t bytes_dispatched; /* including header */
+       sb_transformation trafo;
+       void *trafo_context;
+       struct sb_buffer sbb;
+       size_t max_size;
+       bool dont_free;
+};
+
+/**
+ * Prepare to receive a sideband packet.
+ *
+ * \param max_size Do not allocate more than this many bytes.
+ * \param t Optional sideband transformation.
+ * \param trafo_context Passed verbatim to \a t.
+ *
+ * \a trafo_context is ignored if \a t is \p NULL.
+ *
+ * \return An opaque sideband handle.
+ */
+struct sb_context *sb_new_recv(size_t max_size, sb_transformation t,
+               void *trafo_context)
+{
+       struct sb_context *c = para_calloc(sizeof(*c));
+
+       c->max_size = max_size;
+       c->trafo = t;
+       c->trafo_context = trafo_context;
+       return c;
+}
+
+/**
+ * Prepare to write a sideband packet.
+ *
+ * \param sbb Data and meta data to send.
+ * \param dont_free Do not try to deallocate the sideband buffer.
+ * \param t See \ref sb_new_recv().
+ * \param trafo_context See \ref sb_new_recv().
+ *
+ * It's OK to supply a zero-sized buffer in \a sbb. In this case only the band
+ * designator is sent through the sideband channel. Otherwise, if \a dont_free
+ * is false, the buffer of \a sbb is freed after the data has been sent.
+ *
+ * \return See \ref sb_new_recv().
+ */
+struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free,
+               sb_transformation t, void *trafo_context)
+{
+       struct sb_context *c = para_calloc(sizeof(*c));
+       struct iovec src, dst, *srcp, *dstp;
+
+       assert(sbb);
+       c->trafo = t;
+       c->trafo_context = trafo_context;
+       c->dont_free = dont_free;
+       c->sbb = *sbb;
+       write_u32(c->header, sbb->iov.iov_len);
+       write_u8(c->header + 4, sbb->band);
+       if (!t)
+               goto out;
+       src = (typeof(src)){.iov_base = c->header, .iov_len = SIDEBAND_HEADER_SIZE};
+       t(&src, &dst, trafo_context);
+       if (src.iov_base != dst.iov_base) {
+               memcpy(c->header, dst.iov_base, SIDEBAND_HEADER_SIZE);
+               free(dst.iov_base);
+       }
+       if (!iov_valid(&sbb->iov))
+               goto out;
+       srcp = &sbb->iov;
+       dstp = &c->sbb.iov;
+       t(srcp, dstp, trafo_context);
+       if (srcp->iov_base != dstp->iov_base) {
+               if (!c->dont_free)
+                       free(srcp->iov_base);
+               c->dont_free = false;
+       }
+out:
+       return c;
+}
+
+/**
+ * Deallocate all memory associated with a sideband handle.
+ *
+ * \param c The sideband handle.
+ *
+ * \a c must point to a handle previously returned by \ref sb_new_recv() or
+ * \ref sb_new_send(). It \a c is \p NULL, the function does nothing.
+ */
+void sb_free(struct sb_context *c)
+{
+       if (!c)
+               return;
+       if (!c->dont_free)
+               free(c->sbb.iov.iov_base);
+       free(c);
+}
+
+/**
+ * Obtain pointer(s) to the sideband send buffer(s).
+ *
+ * \param c The sideband handle.
+ * \param iov Array of two I/O vectors.
+ *
+ * \return The number of buffers that need to be sent, either 1 or 2.
+ *
+ * This function fills out the buffers described by \a iov. The result can be
+ * passed to \ref xwritev() or similar.
+ *
+ * \sa \ref sb_get_recv_buffer().
+ */
+int sb_get_send_buffers(struct sb_context *c, struct iovec iov[2])
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched;
+
+       if (n < SIDEBAND_HEADER_SIZE) {
+               iov[0].iov_base = c->header + n;
+               iov[0].iov_len = SIDEBAND_HEADER_SIZE - n;
+               if (!iov_valid(&sbb->iov))
+                       goto out;
+               iov[1] = sbb->iov;
+               return 2;
+       }
+       n -= SIDEBAND_HEADER_SIZE;
+       assert(n < sbb->iov.iov_len);
+       iov[0].iov_base = sbb->iov.iov_base + n;
+       iov[0].iov_len = sbb->iov.iov_len - n;
+out:
+       iov[1].iov_base = NULL;
+       iov[1].iov_len = 0;
+       return 1;
+}
+
+/**
+ * Update the sideband context after data has been sent.
+ *
+ * \param c The sideband handle.
+ * \param nbytes The number of sent bytes.
+ *
+ * \return False if more data must be sent to complete the sideband transfer,
+ * true if the transfer is complete. In this case all resources are freed and
+ * the sideband handle must not be used any more.
+ */
+bool sb_sent(struct sb_context *c, size_t nbytes)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len;
+
+       assert(c->bytes_dispatched + nbytes <= sz);
+       c->bytes_dispatched += nbytes;
+       if (c->bytes_dispatched < sz)
+               return false;
+       sb_free(c);
+       return true;
+}
+
+/**
+ * Obtain a pointer to the next sideband read buffer.
+ *
+ * \param c The sideband handle.
+ * \param iov Result IO vector.
+ *
+ * This fills in \a iov to point to the buffer to which the next chunk of
+ * received data should be written.
+ */
+void sb_get_recv_buffer(struct sb_context *c, struct iovec *iov)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched;
+
+       if (n < SIDEBAND_HEADER_SIZE) {
+               iov->iov_base = c->header + n;
+               iov->iov_len = SIDEBAND_HEADER_SIZE - n;
+               return;
+       }
+       n -= SIDEBAND_HEADER_SIZE;
+       assert(sbb->iov.iov_base);
+       assert(sbb->iov.iov_len > n);
+       iov->iov_base = sbb->iov.iov_base + n;
+       iov->iov_len = sbb->iov.iov_len - n;
+}
+
+/**
+ * Update the sideband context after data has been received.
+ *
+ * \param c The sideband handle.
+ * \param nbytes The number of bytes that have been received.
+ * \param result The received sideband packet.
+ *
+ * \return Negative on errors, zero if more data needs to be read to complete
+ * this sideband packet, one if the sideband packet has been received
+ * completely.
+ *
+ * Only if the function returns one, the sideband buffer pointed to by \a
+ * result is set to point to the received data.
+ */
+int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result)
+{
+       struct sb_buffer *sbb = &c->sbb;
+       size_t n = c->bytes_dispatched,
+               sz = SIDEBAND_HEADER_SIZE + sbb->iov.iov_len;
+
+       assert(n + nbytes <= sz);
+       c->bytes_dispatched += nbytes;
+       if (c->bytes_dispatched < SIDEBAND_HEADER_SIZE)
+               return 0;
+       if (n >= SIDEBAND_HEADER_SIZE) { /* header has already been received */
+               if (c->bytes_dispatched < sz) /* need to recv more body data */
+                       return 0;
+               /* received everything, decrypt and return sbb */
+               if (c->trafo) {
+                       struct iovec dst;
+                       c->trafo(&sbb->iov, &dst, c->trafo_context);
+                       if (sbb->iov.iov_base != dst.iov_base) {
+                               free(sbb->iov.iov_base);
+                               sbb->iov.iov_base = dst.iov_base;
+                       }
+               }
+               ((char *)(sbb->iov.iov_base))[sbb->iov.iov_len] = '\0';
+               goto success;
+       }
+       /* header has been received, decrypt and decode it */
+       if (c->trafo) { /* decrypt */
+               struct iovec dst, src = (typeof(src)) {
+                       .iov_base = c->header,
+                       .iov_len = SIDEBAND_HEADER_SIZE
+               };
+               c->trafo(&src, &dst, c->trafo_context);
+               if (src.iov_base != dst.iov_base) {
+                       memcpy(c->header, dst.iov_base,
+                               SIDEBAND_HEADER_SIZE);
+                       free(dst.iov_base);
+               }
+       }
+       /* Decode header, setup sbb */
+       sbb->iov.iov_len = read_u32(c->header);
+       sbb->band = read_u8(c->header + 4);
+       sbb->iov.iov_base = NULL;
+       if (sbb->iov.iov_len == 0) /* zero-sized msg */
+               goto success;
+       if (c->max_size > 0 && sbb->iov.iov_len > c->max_size) {
+               PARA_ERROR_LOG("packet too big (is %zu, max %zu)\n",
+                       sbb->iov.iov_len, c->max_size);
+               return -E_SB_PACKET_SIZE;
+       }
+       /*
+        * We like to reserve one extra byte for the terminating NULL
+        * character. However, we must make sure the +1 below does not
+        * overflow iov_len.
+        */
+       if (sbb->iov.iov_len == (size_t)-1)
+               return -E_SB_PACKET_SIZE;
+       sbb->iov.iov_base = para_malloc(sbb->iov.iov_len + 1);
+       return 0; /* ready to read body */
+success:
+       *result = c->sbb;
+       free(c);
+       return 1;
+}
diff --git a/sideband.h b/sideband.h
new file mode 100644 (file)
index 0000000..4f4ed0a
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2012 Andre Noll <maan@systemlinux.org>
+ *
+ * Licensed under the GPL v2. For licencing details see COPYING.
+ */
+
+/** \file sideband.h exported symbols from sideband.c */
+
+/**
+ * The valid sideband designators.
+ *
+ * A sideband packet consists of a header and a body. The header is sent prior
+ * to the data and contains the length of the buffer and the sideband
+ * designator, an integer. This scheme allows receivers to never read more data
+ * than what was specified in the header.
+ *
+ * The sideband designator indicates the type of the data. The authentication
+ * handshake between the command handler (child of para_server) and the client
+ * requires to exchange several small packages. Each such package is sent as a
+ * sideband package with a dedicated designator.
+ *
+ * Other designators are employed for normal command output and for log
+ * messages, where each loglevel corresponds to a sideband designator.
+ *
+ * Note that the values of this enum are part of the ABI, so never change
+ * or remove entries. Appending is OK though.
+ */
+#define SB_DESIGNATORS \
+       /* Special value for receiving only: Accept any band. */ \
+       DESIGNATOR(ANY), \
+       /* This packet contains the authentication challenge. */ \
+       DESIGNATOR(CHALLENGE), \
+       /* I solved your challenge and here is the proof. */ \
+       DESIGNATOR(CHALLENGE_RESPONSE), \
+       /* Congratulations, you are authenticated. */ \
+       DESIGNATOR(PROCEED), \
+       /* This is the command I want you to execute. */ \
+       DESIGNATOR(COMMAND), \
+       /* Ready to receive a blob (addblob commands only). */ \
+       DESIGNATOR(AWAITING_DATA), \
+       /* Normal command output (default). */ \
+       DESIGNATOR(OUTPUT), \
+       /* LL_DEBUG. */ \
+       DESIGNATOR(DEBUG_LOG), \
+       /* LL_INFO. */ \
+       DESIGNATOR(INFO_LOG), \
+       /* LL_NOTICE. */ \
+       DESIGNATOR(NOTICE_LOG), \
+       /* LL_WARNING, */ \
+       DESIGNATOR(WARNING_LOG), \
+       /* LL_ERROR. */ \
+       DESIGNATOR(ERROR_LOG), \
+       /* LL_CRIT. */ \
+       DESIGNATOR(CRIT_LOG), \
+       /* LL_EMERG. */ \
+       DESIGNATOR(EMERG_LOG), \
+       /* Command terminated successfully. */ \
+       DESIGNATOR(EXIT__SUCCESS), \
+       /* Command failed. */ \
+       DESIGNATOR(EXIT__FAILURE), \
+
+/** Just prefix with \p SBD_. */
+#define DESIGNATOR(x) SBD_ ## x
+
+/** All valid sideband designators. */
+enum sb_designator {SB_DESIGNATORS NUM_SB_DESIGNATORS};
+#undef DESIGNATOR
+/** One stringified sideband designator. */
+#define DESIGNATOR(x) #x
+/** List of stringified sidedband designators. */
+#define SB_DESIGNATORS_ARRAY SB_DESIGNATORS
+
+/**
+ * The information contained in a sideband buffer.
+ *
+ * This structure is used for both sending and receiving data through a
+ * sideband channel. A pointer to a sideband buffer is passed to the sending
+ * side of a sideband while the receiving end yields a filled out structure
+ * once all data has been received.
+ */
+struct sb_buffer {
+       /** Data and length. */
+       struct iovec iov;
+       /** The band designator. */
+       uint8_t band;
+};
+
+/**
+ * The opaque sideband context structure.
+ *
+ * A pointer to a structure of this type is returned by the two \a sb_new_xxx
+ * functions as an opaque handle. Other public functions of the sideband API
+ * take such a handle as a parameter.
+ */
+struct sb_context;
+
+/**
+ * The type of a sideband transformation.
+ *
+ * The sideband API allows to filter all data through an arbitrary
+ * transformation, which is useful for crypto purposes. The transformation may
+ * either transform the data in place, or return a pointer to a new buffer
+ * which contains the transformed source buffer. The internal sideband
+ * functions can tell the two types of transformations apart by checking
+ * whether the destination buffer coincides with the source buffer.
+ *
+ * If the transformation allocates a new buffer, it _must_ allocate one extra
+ * byte for \p NULL termination.
+ */
+typedef void (*sb_transformation)(struct iovec *src, struct iovec *dst,
+               void *trafo_context);
+
+
+/** Initialize a sideband buffer. */
+#define SBB_INIT(_band, _buf, _numbytes) \
+       { \
+               .band = band, \
+               .iov = { \
+                       .iov_base = _buf, \
+                       .iov_len = _numbytes \
+               } \
+       };
+
+/* receiving */
+struct sb_context *sb_new_recv(size_t max_size, sb_transformation t,
+               void *trafo_context);
+void sb_get_recv_buffer(struct sb_context *c, struct iovec *iov);
+int sb_received(struct sb_context *c, size_t nbytes, struct sb_buffer *result);
+
+/* sending */
+struct sb_context *sb_new_send(struct sb_buffer *sbb, bool dont_free,
+               sb_transformation t, void *trafo_context);
+int sb_get_send_buffers(struct sb_context *c, struct iovec iov[2]);
+bool sb_sent(struct sb_context *c, size_t nbytes);
+
+void sb_free(struct sb_context *c);