bankd: Add GSMTAP functionality for SIM traffic

If a --gsmtap-host (-g) address is specified on the command line, trace
SIM ATRs and APDUs to the given IP. If --gsmtap-slot (-G) is provided,
limit tracing to the specified bank slot number. This feature may be
useful when diagnosing issues with the remote SIM framework.

Added new log category: DGSMTAP

Also, cleaned up alignment in bankd --help output and removed unused -o option.

Change-Id: I05b599858d8758633aa56c3f12f258c27cf42d08
diff --git a/src/bankd/gsmtap.c b/src/bankd/gsmtap.c
new file mode 100644
index 0000000..8aced6a
--- /dev/null
+++ b/src/bankd/gsmtap.c
@@ -0,0 +1,109 @@
+/* gsmtap - How to encapsulate SIM protocol traces in GSMTAP
+ *
+ * (C) 2016-2019 by Harald Welte <hwelte@hmw-consulting.de>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/* among other things, bring in GNU-specific strerror_r() */
+#define _GNU_SOURCE
+
+#include <osmocom/core/gsmtap.h>
+#include <osmocom/core/gsmtap_util.h>
+#include <osmocom/core/logging.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/uio.h>
+
+#include "debug.h"
+
+/*! global GSMTAP instance */
+static struct gsmtap_inst *g_gti;
+
+/*! initialize the global GSMTAP instance for SIM traces
+ *
+ * \param[in] gsmtap_host Hostname to send GSMTAP packets
+ *
+ * \return 0 on success, non-zero on error
+ */
+int bankd_gsmtap_init(const char *gsmtap_host)
+{
+	if (g_gti)
+		return -EEXIST;
+
+	errno = 0;
+	g_gti = gsmtap_source_init(gsmtap_host, GSMTAP_UDP_PORT, 0);
+	if (!g_gti) {
+		LOGP(DGSMTAP, LOGL_ERROR, "unable to initialize GSMTAP\n");
+		return -EIO;
+	}
+	gsmtap_source_add_sink(g_gti);
+
+	LOGP(DGSMTAP, LOGL_INFO, "initialized GSMTAP to %s\n", gsmtap_host);
+
+	return 0;
+}
+
+/*! Log one APDU via the global GSMTAP instance by concatenating mdm_tpdu and sim_tpdu.
+ *
+ *  \param[in] sub_type     GSMTAP sub-type (GSMTAP_SIM_* constant)
+ *  \param[in] mdm_tpdu     User-provided buffer with ModemToCard TPDU to log. May be NULL.
+ *  \param[in] mdm_tpdu_len Length of ModemToCard TPDU, in bytes.
+ *  \param[in] sim_tpdu     User-provided buffer with CardToModem TPDU to log. May be NULL.
+ *  \param[in] sim_tpdu_len Length of CardToModem TPDU, in bytes.
+ *
+ *  \return number of bytes sent on success, -1 on failure
+ */
+int bankd_gsmtap_send_apdu(uint8_t sub_type, const uint8_t *mdm_tpdu, unsigned int mdm_tpdu_len,
+	const uint8_t *sim_tpdu, unsigned int sim_tpdu_len)
+{
+	const struct gsmtap_hdr gh = {
+		.version = GSMTAP_VERSION,
+		.hdr_len = sizeof(struct gsmtap_hdr)/4,
+		.type = GSMTAP_TYPE_SIM,
+		.sub_type = sub_type,
+	};
+
+	struct iovec iov[3];
+	unsigned int cnt = 0;
+
+	iov[cnt].iov_base = (void *)&gh;
+	iov[cnt].iov_len = sizeof(gh);
+	cnt++;
+
+	if (mdm_tpdu && mdm_tpdu_len) {
+		iov[cnt].iov_base = (void *)mdm_tpdu;
+		iov[cnt].iov_len = mdm_tpdu_len;
+		cnt++;
+	}
+
+	if (sim_tpdu && sim_tpdu_len) {
+		iov[cnt].iov_base = (void *)sim_tpdu;
+		iov[cnt].iov_len = sim_tpdu_len;
+		cnt++;
+	}
+
+	LOGP(DGSMTAP, LOGL_DEBUG, "sending APDU sub_type=%u, mdm_tpdu len=%u, sim_tpdu len=%u, iov cnt=%u\n",
+		sub_type, mdm_tpdu_len, sim_tpdu_len, cnt);
+
+	const int rc = writev(gsmtap_inst_fd(g_gti), iov, cnt);
+	if (rc < 0) {
+		char errtxt[128];
+		LOGP(DGSMTAP, LOGL_ERROR, "writev() failed with errno=%d: %s\n", errno, strerror_r(errno,
+			errtxt, sizeof(errtxt)));
+		return rc;
+	}
+
+	return 0;
+}