summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNate Straz <nstraz@redhat.com>2005-08-24 16:07:46 +0000
committerNate Straz <nstraz@redhat.com>2005-08-24 16:07:46 +0000
commit35ffc8bdc7ec6b166c0799f49d475803e6f285ff (patch)
tree4df7017959442635f3d5b1703849fd20db6a1c87
downloadqarsh-35ffc8bdc7ec6b166c0799f49d475803e6f285ff.tar.gz
qarsh-35ffc8bdc7ec6b166c0799f49d475803e6f285ff.tar.xz
qarsh-35ffc8bdc7ec6b166c0799f49d475803e6f285ff.zip
Add initial working version of the QA Remote SHell.
-rw-r--r--qarsh/Makefile30
-rw-r--r--qarsh/qarsh.c255
-rw-r--r--qarsh/qarsh_packet.c497
-rw-r--r--qarsh/qarsh_packet.h76
-rw-r--r--qarsh/qarshd.c193
-rw-r--r--qarsh/sockutil.c81
-rw-r--r--qarsh/sockutil.h6
7 files changed, 1138 insertions, 0 deletions
diff --git a/qarsh/Makefile b/qarsh/Makefile
new file mode 100644
index 0000000..0402db7
--- /dev/null
+++ b/qarsh/Makefile
@@ -0,0 +1,30 @@
+
+include ../../make/defines.mk
+.PHONY: clean clobber uninstall
+
+CFLAGS := -Wall -g -I/usr/include/libxml2
+LOADLIBES := -lxml2
+
+COMMON := qarsh_packet.c sockutil.c
+
+all: qarshd qarsh
+
+qarshd: qarshd.c $(COMMON)
+qarsh: qarsh.c $(COMMON)
+
+install: qarsh qarshd
+ @echo Installing qarsh daemon and client
+ install -d ${STSDIR}/bin/
+ install qarsh ${STSDIR}/bin
+ install qarshd ${STSDIR}/bin
+
+uninstall:
+ ${UNINSTALL} qarsh ${STSDIR}/bin
+ ${UNINSTALL} qarshd ${STSDIR}/bin
+
+clean:
+ /bin/rm -f *.o
+
+clobber:
+ /bin/rm -f qarsh
+ /bin/rm -f qarshd
diff --git a/qarsh/qarsh.c b/qarsh/qarsh.c
new file mode 100644
index 0000000..fcfda10
--- /dev/null
+++ b/qarsh/qarsh.c
@@ -0,0 +1,255 @@
+/*
+ * QA Remote Shell - client side
+ *
+ * Run a command on the server with lots of wizz-bang options
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <sys/select.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <syslog.h>
+
+
+#include "sockutil.h"
+#include "qarsh_packet.h"
+
+#define QARSH_MINPORT 5010
+
+/* Globals */
+int qarsh_fd = -1; /* The control connection to qarshd */
+
+
+char *
+copyargs(char **argv)
+{
+ int cc;
+ char **ap, *p;
+ char *args;
+
+ cc = 0;
+ for (ap = argv; *ap; ++ap)
+ cc += strlen(*ap) + 1;
+ args = malloc(cc);
+ if (!args) {
+ perror("qarsh: malloc failed in copyargs");
+ exit(1);
+ }
+ for (p = args, ap = argv; *ap; ++ap) {
+ for (p = strcpy(p, *ap); *p; ++p);
+ if (ap[1])
+ *p++ = ' ';
+ }
+ return(args);
+}
+
+
+int
+run_remote_cmd(char *cmdline)
+{
+ struct qa_packet *qp;
+ char *packetbuf;
+ int packetsize;
+ int rc;
+ int p_in, p_out, p_err; /* Port numbers */
+ int l_in, l_out, l_err; /* listening sockets */
+ int c_in, c_out, c_err; /* client sockets */
+ fd_set readfds, testfds;
+ int nset;
+ struct sockaddr_in caddr;
+ socklen_t clen;
+
+ l_in = bind_any(QARSH_MINPORT);
+ p_in = getsockport(l_in);
+ l_out = bind_any(QARSH_MINPORT);
+ p_out = getsockport(l_out);
+ l_err = bind_any(QARSH_MINPORT);
+ p_err = getsockport(l_err);
+
+ qp = make_qp_runcmd(cmdline, p_in, p_out, p_err);
+ qp->qp_seq = 1;
+ packetbuf = qptostr(qp, &packetbuf, &packetsize);
+ qpfree(qp);
+ write(qarsh_fd, packetbuf, packetsize);
+ free(packetbuf);
+
+ /* Get the stdin, stdout, and stderr connections up before we do work */
+ FD_ZERO(&readfds);
+ FD_SET(l_in, &readfds);
+ FD_SET(l_out, &readfds);
+ FD_SET(l_err, &readfds);
+ c_in = c_out = c_err = 0;
+
+ do {
+ testfds = readfds;
+ nset = select(FD_SETSIZE, &testfds, NULL, NULL, NULL);
+
+ if (FD_ISSET(l_in, &testfds)) {
+ clen = sizeof caddr;
+ c_in = accept(l_in, (struct sockaddr *)&caddr, &clen);
+ if (c_in == -1) {
+ fprintf(stderr,
+ "accept of l_in failed, %d: %s\n",
+ errno, strerror(errno));
+ continue;
+ }
+ }
+ if (FD_ISSET(l_out, &testfds)) {
+ clen = sizeof caddr;
+ c_out = accept(l_out, (struct sockaddr *)&caddr, &clen);
+ if (c_out == -1) {
+ fprintf(stderr,
+ "accept of l_out failed, %d: %s\n",
+ errno, strerror(errno));
+ continue;
+ }
+ }
+ if (FD_ISSET(l_err, &testfds)) {
+ clen = sizeof caddr;
+ c_err = accept(l_err, (struct sockaddr *)&caddr, &clen);
+ if (c_err == -1) {
+ fprintf(stderr,
+ "accept of l_err failed, %d: %s\n",
+ errno, strerror(errno));
+ continue;
+ }
+ }
+ } while (c_in == 0 || c_out == 0 || c_err == 0);
+ close(l_in);
+ close(l_out);
+ close(l_err);
+ l_in = l_out = l_err = -1;
+
+ /* Now we can start doing some real work */
+
+ FD_ZERO(&readfds);
+ FD_SET(qarsh_fd, &readfds);
+ FD_SET(c_out, &readfds);
+ FD_SET(c_err, &readfds);
+ FD_SET(fileno(stdin), &readfds);
+
+ if (fcntl(fileno(stdin), F_SETFL, O_NONBLOCK) != 0) {
+ fprintf(stderr, "fcntl stdin O_NONBLOCK failed, %d: %s\n", errno, strerror(errno));
+ }
+ packetbuf = malloc(1024);
+ memset(packetbuf, 0, 1024);
+
+ for (;;) {
+ testfds = readfds;
+ memset(packetbuf, 0, 1024);
+
+ nset = select(FD_SETSIZE, &testfds, NULL, NULL, NULL);
+ if (FD_ISSET(fileno(stdin), &testfds)) {
+ do {
+ packetsize = read(fileno(stdin), packetbuf, 1024);
+ write(c_in, packetbuf, packetsize);
+ } while (packetsize == 1024);
+ if (packetsize == 0) {
+ FD_CLR(fileno(stdin), &readfds);
+ close(c_in);
+ c_in = 0;
+ }
+ }
+ if (c_out && FD_ISSET(c_out, &testfds)) {
+ do {
+ packetsize = read(c_out, packetbuf, 1024);
+ write(fileno(stdout), packetbuf, packetsize);
+ } while (packetsize == 1024);
+ if (packetsize == 0) {
+ FD_CLR(c_out, &readfds);
+ close(c_out);
+ c_out = 0;
+ }
+ }
+ if (c_err && FD_ISSET(c_err, &testfds)) {
+ do {
+ packetsize = read(c_err, packetbuf, 1024);
+ write(fileno(stderr), packetbuf, packetsize);
+ } while (packetsize == 1024);
+ if (packetsize == 0) {
+ FD_CLR(c_err, &readfds);
+ close(c_err);
+ c_err = 0;
+ }
+ }
+ if (FD_ISSET(qarsh_fd, &testfds)) {
+ packetsize = read(qarsh_fd, packetbuf, 1024);
+ if (packetsize == 0) {
+ qp = NULL;
+ break;
+ }
+ qp = parse_packets(packetbuf, packetsize);
+
+ /* dump_qp(qp); */
+ if (qp && qp->qp_type == QP_CMDEXIT) {
+ break;
+ }
+ }
+
+ }
+ if (c_out) close(c_out);
+ if (c_err) close(c_err);
+ if (qp == NULL) {
+ fprintf(stderr, "Remote command exited with unknown state\n");
+ free(packetbuf);
+ return 127;
+ }
+ if (WIFSIGNALED(qp->qp_cmdexit.qp_status)) {
+ rc = 128 + WTERMSIG(qp->qp_cmdexit.qp_status);
+ } else {
+ rc = WEXITSTATUS(qp->qp_cmdexit.qp_status);
+ }
+ qpfree(qp);
+ free(packetbuf);
+ return rc;
+}
+
+int
+main(int argc, char *argv[])
+{
+
+ int c;
+ int port = 5008;
+ char *host;
+ char *args;
+ int ret;
+
+ openlog("qarsh", LOG_PID, LOG_DAEMON);
+
+ while ((c = getopt(argc, argv, "+p:")) != -1) {
+ switch (c) {
+ case 'p':
+ port = atoi(optarg);
+ break;
+ case '?':
+ default:
+ printf("Unknown option %c\n", (char)c);
+ } }
+
+ host = argv[optind++];
+
+ argc -= optind;
+ argv += optind;
+ args = copyargs(argv);
+
+ qarsh_fd = connect_to_host(host, port);
+ if (qarsh_fd == -1) {
+ fprintf(stderr, "Could not connect to %s:%d, %d: %s\n",
+ host, port, errno, strerror(errno));
+ return 127;
+ }
+
+ ret = run_remote_cmd(args);
+ close(qarsh_fd);
+ free(args);
+ return ret;
+}
diff --git a/qarsh/qarsh_packet.c b/qarsh/qarsh_packet.c
new file mode 100644
index 0000000..4fef7c0
--- /dev/null
+++ b/qarsh/qarsh_packet.c
@@ -0,0 +1,497 @@
+
+#include <string.h>
+#include <assert.h>
+#include <sys/wait.h>
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#include "qarsh_packet.h"
+
+/* Prototypes */
+static char *get_xpath_string(xmlXPathContextPtr ctxt, const char *xpath_query);
+
+int parse_qp_hello(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+int parse_qp_returncode(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+int parse_qp_ack(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+int parse_qp_runcmd(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+int parse_qp_cmdexit(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+
+void string_qp_hello(xmlNodePtr node, struct qa_packet *qp);
+void string_qp_returncode(xmlNodePtr node, struct qa_packet *qp);
+void string_qp_runcmd(xmlNodePtr node, struct qa_packet *qp);
+void string_qp_ack(xmlNodePtr node, struct qa_packet *qp);
+void string_qp_cmdexit(xmlNodePtr node, struct qa_packet *qp);
+
+void free_qp_hello(struct qa_packet *qp);
+void free_qp_returncode(struct qa_packet *qp);
+void free_qp_runcmd(struct qa_packet *qp);
+
+void dump_qp_ack(struct qa_packet *qp);
+void dump_qp_runcmd(struct qa_packet *qp);
+void dump_qp_returncode(struct qa_packet *qp);
+void dump_qp_cmdexit(struct qa_packet *qp);
+
+
+struct packet_internals {
+ char *pi_name;
+ int (*pi_parse)(xmlXPathContextPtr ctxt, struct qa_packet *qp);
+ void (*pi_string)(xmlNodePtr node, struct qa_packet *qp);
+ void (*pi_free)(struct qa_packet *qp);
+ void (*pi_dump)(struct qa_packet *qp);
+} qa_pi[] = {
+ {
+ .pi_name = "",
+ .pi_parse = NULL,
+ .pi_string = NULL,
+ .pi_free = NULL,
+ .pi_dump = NULL
+
+ }, {
+ .pi_name = "hello",
+ .pi_parse = parse_qp_hello,
+ .pi_string = string_qp_hello,
+ .pi_free = free_qp_hello
+ }, {
+ .pi_name = "returncode",
+ .pi_parse = parse_qp_returncode,
+ .pi_string = string_qp_returncode,
+ .pi_free = free_qp_returncode,
+ .pi_dump = dump_qp_returncode
+ }, {
+ .pi_name = "runcmd",
+ .pi_parse = parse_qp_runcmd,
+ .pi_string = string_qp_runcmd,
+ .pi_free = free_qp_runcmd,
+ .pi_dump = dump_qp_runcmd
+ }, {
+ .pi_name = "ack",
+ .pi_parse = parse_qp_ack,
+ .pi_string = string_qp_ack,
+ .pi_free = NULL,
+ .pi_dump = dump_qp_ack
+ }, {
+ .pi_name = "cmdexit",
+ .pi_parse = parse_qp_cmdexit,
+ .pi_string = string_qp_cmdexit,
+ .pi_free = NULL,
+ .pi_dump = dump_qp_cmdexit
+ }
+};
+
+#define QP_TYPES (sizeof qa_pi / sizeof *qa_pi)
+#define QP_NAME(n) qa_pi[n].pi_name
+
+
+/* XML Strings */
+const xmlChar *QP_QARSH_XML = (xmlChar *)"qarsh";
+const xmlChar *QP_PACKET_XML = (xmlChar *)"packet";
+const xmlChar *QP_TYPE_XML = (xmlChar *)"type";
+const xmlChar *QP_PARAM_XML = (xmlChar *)"param";
+const xmlChar *QP_NAME_XML = (xmlChar *)"name";
+const xmlChar *QP_SEQ_XML = (xmlChar *)"seq";
+
+enum qa_packet_type
+parse_packet_type(char *s)
+{
+ int i;
+ for (i = 0; i < QP_TYPES; i++) {
+ if (strcasecmp(s, QP_NAME(i)) == 0) {
+ return (enum qa_packet_type)i;
+ }
+ }
+ return QP_INVALID;
+}
+
+char *
+qp_packet_type(enum qa_packet_type t)
+{
+ return QP_NAME(t);
+}
+
+
+/*
+ * Packet parsing functions
+ */
+int
+parse_qp_hello(xmlXPathContextPtr ctxt, struct qa_packet *qp)
+{
+ qp->qp_hello.qp_greeting = get_xpath_string(ctxt,
+ "param[@name='greeting']");
+ return 0;
+}
+
+int
+parse_qp_returncode(xmlXPathContextPtr ctxt, struct qa_packet *qp)
+{
+ char *s;
+ s = get_xpath_string(ctxt, "param[@name='rc']");
+ qp->qp_returncode.qp_rc = atoi(s);
+ free(s);
+ s = get_xpath_string(ctxt, "param[@name='errno']");
+ qp->qp_returncode.qp_errno = atoi(s);
+ free(s);
+ qp->qp_returncode.qp_strerror = get_xpath_string(ctxt,
+ "param[@name='strerror']");
+ return 0;
+}
+
+int
+parse_qp_runcmd(xmlXPathContextPtr ctxt, struct qa_packet *qp)
+{
+ char *s;
+ qp->qp_runcmd.qp_cmdline = get_xpath_string(ctxt,
+ "param[@name='cmdline']");
+ s = get_xpath_string(ctxt, "param[@name='stdin']");
+ qp->qp_runcmd.qp_stdin_port = atoi(s);
+ free(s);
+ s = get_xpath_string(ctxt, "param[@name='stdout']");
+ qp->qp_runcmd.qp_stdout_port = atoi(s);
+ free(s);
+ s = get_xpath_string(ctxt, "param[@name='stderr']");
+ qp->qp_runcmd.qp_stderr_port = atoi(s);
+ free(s);
+ return 0;
+}
+
+int
+parse_qp_ack(xmlXPathContextPtr ctxt, struct qa_packet *qp)
+{
+ char *s;
+ s = get_xpath_string(ctxt, "param[@name='type']");
+ qp->qp_ack.qp_ack_type = parse_packet_type(s);
+ free(s);
+ s = get_xpath_string(ctxt, "param[@name='seq']");
+ qp->qp_ack.qp_ack_seq = atoi(s);
+ free(s);
+ return 0;
+}
+
+int
+parse_qp_cmdexit(xmlXPathContextPtr ctxt, struct qa_packet *qp)
+{
+ char *s;
+
+ s = get_xpath_string(ctxt, "param[@name='status']");
+ qp->qp_cmdexit.qp_status = atoi(s);
+ free(s);
+ return 0;
+}
+
+struct qa_packet *
+parse_packet(xmlXPathContextPtr ctxt)
+{
+ struct qa_packet *qp = NULL;
+ char *s;
+
+ qp = malloc(sizeof *qp);
+ assert(qp);
+ memset(qp, 0, sizeof *qp);
+
+ s = get_xpath_string(ctxt, "@type");
+ qp->qp_type = parse_packet_type(s);
+ free(s);
+ s = get_xpath_string(ctxt, "@seq");
+ qp->qp_seq = atoi(s);
+ free(s);
+
+ if (qa_pi[qp->qp_type].pi_parse)
+ qa_pi[qp->qp_type].pi_parse(ctxt, qp);
+ else {
+ printf("Packet type %s not implemented yet\n", s);
+ free(qp);
+ qp = NULL;
+ }
+ return qp;
+}
+
+struct qa_packet *
+parse_packets(char *buf, int n)
+{
+ xmlDocPtr doc;
+ xmlXPathContextPtr context;
+ xmlXPathObjectPtr obj = NULL;
+ struct qa_packet *qp = NULL;
+
+ doc = xmlParseMemory(buf, n);
+
+ /* If we can't parse the packet, we don't care about it */
+ if (doc == NULL) return NULL;
+
+ context = xmlXPathNewContext(doc);
+ obj = xmlXPathEvalExpression((xmlChar *)"//packet", context);
+ if (obj == NULL) {
+ printf("didn't find anything\n");
+ goto free_context;
+ }
+ if (obj->type != XPATH_NODESET || obj->nodesetval->nodeNr == 0) {
+ printf("didn't find anything\n");
+ goto free_obj;
+ }
+ if (obj->nodesetval->nodeNr > 1) {
+ printf("One packet at a time please!\n");
+ }
+ context->node = xmlXPathNodeSetItem(obj->nodesetval, 0);
+ qp = parse_packet(context);
+
+free_obj:
+ xmlXPathFreeObject(obj);
+free_context:
+ xmlXPathFreeContext(context);
+ xmlFreeDoc(doc);
+ return qp;
+}
+
+
+static char *
+get_xpath_string(xmlXPathContextPtr ctxt, const char *xpath_query)
+{
+ xmlXPathObjectPtr obj = NULL;
+ char querystr[512];
+ char *ret;
+
+ snprintf(querystr, 512, "string(%s)", xpath_query);
+ obj = xmlXPathEvalExpression((xmlChar *)querystr, ctxt);
+
+ if (obj == NULL) return NULL;
+ if (obj->type != XPATH_STRING) {
+ xmlXPathFreeObject(obj);
+ return NULL;
+ }
+
+ ret = strdup((char *)obj->stringval);
+ xmlXPathFreeObject(obj);
+ return ret;
+}
+
+/*
+ * Packet serialization functions
+ *
+ * for converting structs to XML
+ */
+xmlNodePtr
+make_param(char *name, char *value)
+{
+ xmlNodePtr param = xmlNewNode(NULL, QP_PARAM_XML);
+ xmlNewProp(param, QP_NAME_XML, (xmlChar *)name);
+ xmlNodeSetContent(param, (xmlChar *)value);
+ return param;
+}
+
+void string_qp_hello(xmlNodePtr node, struct qa_packet *qp)
+{
+ xmlAddChild(node, make_param("greeting", qp->qp_hello.qp_greeting));
+}
+void string_qp_returncode(xmlNodePtr node, struct qa_packet *qp)
+{
+ char tmpstr[32];
+
+ snprintf(tmpstr, 32, "%d", qp->qp_returncode.qp_rc);
+ xmlAddChild(node, make_param("rc", tmpstr));
+ snprintf(tmpstr, 32, "%d", qp->qp_returncode.qp_errno);
+ xmlAddChild(node, make_param("errno", tmpstr));
+ xmlAddChild(node, make_param("strerror", qp->qp_returncode.qp_strerror));
+}
+void string_qp_runcmd(xmlNodePtr node, struct qa_packet *qp)
+{
+ char tmpstr[32];
+ xmlAddChild(node, make_param("cmdline", qp->qp_runcmd.qp_cmdline));
+ snprintf(tmpstr, 32, "%d", qp->qp_runcmd.qp_stdin_port);
+ xmlAddChild(node, make_param("stdin", tmpstr));
+ snprintf(tmpstr, 32, "%d", qp->qp_runcmd.qp_stdout_port);
+ xmlAddChild(node, make_param("stdout", tmpstr));
+ snprintf(tmpstr, 32, "%d", qp->qp_runcmd.qp_stderr_port);
+ xmlAddChild(node, make_param("stderr", tmpstr));
+}
+void string_qp_ack(xmlNodePtr node, struct qa_packet *qp)
+{
+ char tmpstr[32];
+
+ xmlAddChild(node, make_param("type",
+ QP_NAME(qp->qp_ack.qp_ack_type)));
+ snprintf(tmpstr, 32, "%d", qp->qp_ack.qp_ack_seq);
+ xmlAddChild(node, make_param("seq", tmpstr));
+}
+
+void string_qp_cmdexit(xmlNodePtr node, struct qa_packet *qp)
+{
+ char tmpstr[32];
+
+ snprintf(tmpstr, 32, "%d", qp->qp_cmdexit.qp_pid);
+ xmlAddChild(node, make_param("pid", tmpstr));
+ snprintf(tmpstr, 32, "%d", qp->qp_cmdexit.qp_status);
+ xmlAddChild(node, make_param("status", tmpstr));
+}
+
+/* Must pass in a pointer, but not a malloc'ed pointer */
+char *
+qptostr(struct qa_packet *qp, char **qpstr, int *qpsize)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+ char tmpstr[32];
+
+ if (qp->qp_type == QP_INVALID) return NULL;
+
+ doc = xmlNewDoc((xmlChar *)XML_DEFAULT_VERSION);
+
+ /* Add <qarsh> */
+ node = xmlNewDocNode(doc, NULL, QP_QARSH_XML, NULL);
+ xmlDocSetRootElement(doc, node);
+
+ /* Add <packet type="foo"> */
+ node = xmlNewChild(node, NULL, QP_PACKET_XML, NULL);
+ xmlNewProp(node, QP_TYPE_XML, (xmlChar *)QP_NAME(qp->qp_type));
+ snprintf(tmpstr, 32, "%d", qp->qp_seq);
+ xmlNewProp(node, QP_SEQ_XML, (xmlChar *)tmpstr);
+
+ if (qa_pi[qp->qp_type].pi_string) {
+ qa_pi[qp->qp_type].pi_string(node, qp);
+ }
+ xmlDocDumpMemory(doc, (xmlChar **)qpstr, qpsize);
+ xmlFreeDoc(doc);
+ return *qpstr;
+}
+
+/*
+ * Packet construction functions
+ */
+struct qa_packet *
+make_qp_hello(char *greeting)
+{
+ struct qa_packet *qp;
+
+ qp = malloc(sizeof *qp);
+ assert(qp);
+ memset(qp, 0, sizeof *qp);
+
+ qp->qp_type = QP_HELLO;
+ qp->qp_hello.qp_greeting = strdup(greeting);
+
+ return qp;
+}
+
+struct qa_packet *
+make_qp_ack(enum qa_packet_type t, int i)
+{
+ struct qa_packet *qp;
+ qp = malloc(sizeof *qp);
+ assert(qp);
+ memset(qp, 0, sizeof *qp);
+
+ qp->qp_type = QP_ACK;
+ qp->qp_ack.qp_ack_type = t;
+ qp->qp_ack.qp_ack_seq = i;
+
+ return qp;
+}
+
+struct qa_packet *
+make_qp_runcmd(char *cmdline, int p_in, int p_out, int p_err)
+{
+ struct qa_packet *qp;
+ qp = malloc(sizeof *qp);
+ assert(qp);
+ memset(qp, 0, sizeof *qp);
+
+ qp->qp_type = QP_RUNCMD;
+ qp->qp_runcmd.qp_cmdline = strdup(cmdline);
+ qp->qp_runcmd.qp_stdin_port = p_in;
+ qp->qp_runcmd.qp_stdout_port = p_out;
+ qp->qp_runcmd.qp_stderr_port = p_err;
+
+ return qp;
+}
+
+struct qa_packet *
+make_qp_cmdexit(pid_t pid, int status)
+{
+ struct qa_packet *qp;
+ qp = malloc(sizeof *qp);
+ assert(qp);
+ memset(qp, 0, sizeof *qp);
+
+ qp->qp_type = QP_CMDEXIT;
+ qp->qp_cmdexit.qp_pid = pid;
+ qp->qp_cmdexit.qp_status = status;
+
+ return qp;
+}
+
+/*
+ * Packet deallocation functions
+ */
+#define condfree(x) if (x) { free(x); }
+void
+free_qp_hello(struct qa_packet *qp)
+{
+ condfree(qp->qp_hello.qp_greeting);
+}
+
+void
+free_qp_returncode(struct qa_packet *qp)
+{
+ condfree(qp->qp_returncode.qp_strerror);
+}
+void
+free_qp_runcmd(struct qa_packet *qp)
+{
+ condfree(qp->qp_runcmd.qp_cmdline);
+}
+
+void
+qpfree(struct qa_packet *qp)
+{
+ if (qp) {
+ if (qa_pi[qp->qp_type].pi_free) {
+ qa_pi[qp->qp_type].pi_free(qp);
+ }
+ free(qp);
+ }
+}
+#undef condfree
+
+/*
+ * Packet printing functions
+ *
+ * For printing out packets for debugging purposes
+ */
+void
+dump_qp_ack(struct qa_packet *qp)
+{
+ printf("\t%s #%d\n", QP_NAME(qp->qp_ack.qp_ack_type),
+ qp->qp_ack.qp_ack_seq);
+}
+
+void
+dump_qp_runcmd(struct qa_packet *qp)
+{
+ printf("\tcmdline: %s\n", qp->qp_runcmd.qp_cmdline);
+}
+
+void
+dump_qp_returncode(struct qa_packet *qp)
+{
+ printf("\trc: %d\n", qp->qp_returncode.qp_rc);
+}
+
+void
+dump_qp_cmdexit(struct qa_packet *qp)
+{
+ if (WIFEXITED(qp->qp_cmdexit.qp_status)) {
+ printf("\texited: %d\n", WEXITSTATUS(qp->qp_cmdexit.qp_status));
+ } else if (WIFSIGNALED(qp->qp_cmdexit.qp_status)) {
+ printf("\tsignaled: %d\n", WTERMSIG(qp->qp_cmdexit.qp_status));
+ } else {
+ printf("\tstatus: %d\n", qp->qp_cmdexit.qp_status);
+ }
+}
+
+void
+dump_qp(struct qa_packet *qp)
+{
+ printf("%s #%d\n", QP_NAME(qp->qp_type), qp->qp_seq);
+ if (qa_pi[qp->qp_type].pi_dump) {
+ qa_pi[qp->qp_type].pi_dump(qp);
+ }
+}
diff --git a/qarsh/qarsh_packet.h b/qarsh/qarsh_packet.h
new file mode 100644
index 0000000..b21661b
--- /dev/null
+++ b/qarsh/qarsh_packet.h
@@ -0,0 +1,76 @@
+#ifndef _QARSH_PACKET_H
+# define _QARSH_PACKET_H 1
+
+#include <sys/types.h>
+
+#define QARSH_MAX_PACKET_SIZE 32*1024
+
+enum qa_packet_type {
+ QP_INVALID = 0,
+ QP_HELLO = 1,
+ QP_RETURNCODE = 2,
+ QP_RUNCMD = 3,
+ QP_ACK = 4,
+ QP_CMDEXIT = 5
+};
+
+struct qp_hello_pkt {
+ char *qp_greeting;
+};
+
+struct qp_returncode_pkt {
+ int qp_rc;
+ int qp_errno;
+ char *qp_strerror;
+};
+
+struct qp_runcmd_pkt {
+ char *qp_cmdline;
+ int qp_stdin_port;
+ int qp_stdout_port;
+ int qp_stderr_port;
+};
+
+/* General packet for acknowledging a command worked */
+struct qp_ack_pkt {
+ enum qa_packet_type qp_ack_type;
+ int qp_ack_seq;
+};
+
+struct qp_cmdexit_pkt {
+ pid_t qp_pid;
+ int qp_status;
+};
+
+#define QP_VERSION 1
+
+struct qa_packet {
+ enum qa_packet_type qp_type;
+ int qp_seq; /* Sequence number for this packet */
+ union {
+ struct qp_hello_pkt hello;
+ struct qp_returncode_pkt returncode;
+ struct qp_runcmd_pkt runcmd;
+ struct qp_ack_pkt ack;
+ struct qp_cmdexit_pkt cmdexit;
+ } qp_u;
+};
+
+#define qp_hello qp_u.hello
+#define qp_returncode qp_u.returncode
+#define qp_runcmd qp_u.runcmd
+#define qp_ack qp_u.ack
+#define qp_cmdexit qp_u.cmdexit
+
+/* Prototypes */
+char *qp_packet_type(enum qa_packet_type t);
+struct qa_packet *parse_packets(char *buf, int n);
+struct qa_packet *make_qp_hello(char *greeting);
+struct qa_packet *make_qp_ack(enum qa_packet_type t, int i);
+struct qa_packet *make_qp_runcmd(char *cmdline, int p_in, int p_out, int p_err);
+struct qa_packet *make_qp_cmdexit(pid_t pid, int status);
+char *qptostr(struct qa_packet *qp, char **qpstr, int *qpsize);
+void qpfree(struct qa_packet *qp);
+void dump_qp(struct qa_packet *qp);
+
+#endif /* !_QARSH_PACKET_H */
diff --git a/qarsh/qarshd.c b/qarsh/qarshd.c
new file mode 100644
index 0000000..72db068
--- /dev/null
+++ b/qarsh/qarshd.c
@@ -0,0 +1,193 @@
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <assert.h>
+#include <syslog.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+#include "sockutil.h"
+#include "qarsh_packet.h"
+
+/*
+ * QA Remote Shell Daemon
+ */
+
+
+/* Globals */
+struct sockaddr_in peername;
+
+
+pid_t
+run_cmd(const char *cmd, int p_in, int p_out, int p_err)
+{
+ pid_t pid;
+ int new_in, new_out, new_err;
+
+ syslog(LOG_INFO, "Running cmdline: %s\n", cmd);
+ if ((pid = fork()) < 0) {
+ syslog(LOG_WARNING,
+ "Could not fork() in run_cmd(): %s\n", strerror(errno));
+ exit(1);
+ }
+
+ if (pid == 0) { /* child */
+
+ /* Connect stdin, stdout, and stderr to qarsh */
+ new_in = connect_to_peer(&peername, p_in);
+ if (new_in == -1) syslog(LOG_WARNING, "connect to new_in failed");
+ dup2(new_in, fileno(stdin));
+ new_out = connect_to_peer(&peername, p_out);
+ if (new_out == -1) syslog(LOG_WARNING, "connect to new_out failed");
+ dup2(new_out, fileno(stdout));
+ new_err = connect_to_peer(&peername, p_err);
+ if (new_err == -1) syslog(LOG_WARNING, "connect to new_err failed");
+ dup2(new_err, fileno(stderr));
+
+ /* If there are any shell-type characters in the
+ * cmdline such as '>', '<', '$', '|', '~','*' etc,
+ * then we exec a shell and run the cmd under a shell.
+ *
+ * Otherwise exec the cmd directly.
+ */
+
+ if (strpbrk(cmd, "\"';|<>$\\~*")) {
+ execlp("sh", "sh", "-c", cmd, NULL);
+ } else {
+ char **argv;
+ char *sp, *tmpcmd;
+ int argc = 1;
+
+ /* We need to split the string up */
+ tmpcmd = strdup(cmd);
+ for (sp = tmpcmd; (sp = strchr(sp, ' ')); argc++) {
+ if (*sp == ' ') sp++;
+ }
+ syslog(LOG_INFO, "cmdline split into %d args", argc);
+ argv = calloc(argc+1, sizeof *argv);
+ for (argc = 0; (sp = strsep(&tmpcmd, " ")); argc++) {
+ argv[argc] = sp;
+ }
+ argv[argc] = NULL;
+ execvp(argv[0], argv);
+ printf("exec of %s failed: %d, %s\n", argv[0], errno, strerror(errno));
+ }
+ exit(127);
+ }
+ return pid;
+}
+
+struct qa_packet *
+recv_packet(int fd)
+{
+ char *packetbuf;
+ int packetsize;
+ struct qa_packet *qp = NULL;
+
+ packetbuf = malloc(QARSH_MAX_PACKET_SIZE);
+ memset(packetbuf, 0, QARSH_MAX_PACKET_SIZE);
+
+ packetsize = read(fd, packetbuf, QARSH_MAX_PACKET_SIZE);
+ if (packetsize > 0) {
+ qp = parse_packets(packetbuf, packetsize);
+ }
+ free(packetbuf);
+ return qp;
+}
+
+int
+send_packet(int fd, struct qa_packet *qp)
+{
+ char *packetbuf;
+ int packetsize;
+
+ packetbuf = malloc(1024);
+ memset(packetbuf, 0, 1024);
+ packetbuf = qptostr(qp, &packetbuf, &packetsize);
+
+ return write(fd, packetbuf, packetsize);
+}
+
+void
+handle_packets(int infd)
+{
+ fd_set rfds;
+ int highfd, nfd;
+ struct timeval timeout;
+ struct qa_packet *qp = NULL, *rp = NULL;
+
+ pid_t child_pid = 0;
+ int child_status;
+
+ /* set up the select structures */
+ highfd = infd+1;
+
+ for (;;) {
+ FD_SET(infd, &rfds);
+ timeout.tv_sec = 5;
+ timeout.tv_usec = 0;
+
+ nfd = select(highfd, &rfds, NULL, NULL, &timeout);
+ if (nfd < 0) {
+ syslog(LOG_ERR, "select errno %d, %s\n", errno, strerror(errno));
+ } else if (nfd > 0) {
+ qp = recv_packet(infd);
+ if (qp == NULL) {
+ syslog(LOG_INFO, "That's enough\n");
+ break;
+ }
+ switch (qp->qp_type) {
+ case QP_RUNCMD:
+ child_pid = run_cmd(qp->qp_runcmd.qp_cmdline,
+ qp->qp_runcmd.qp_stdin_port,
+ qp->qp_runcmd.qp_stdout_port,
+ qp->qp_runcmd.qp_stderr_port);
+ waitpid(child_pid, &child_status, 0);
+ rp = make_qp_cmdexit(child_pid, child_status);
+ send_packet(fileno(stdout), rp);
+ break;
+ default:
+ syslog(LOG_WARNING,
+ "Packet type %s unimplemented",
+ qp_packet_type(qp->qp_type));
+ }
+ } else {
+ syslog(LOG_DEBUG, "Nothing to do\n");
+ }
+
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ int ch;
+ socklen_t peernamelen;
+
+ openlog("qarshd", LOG_PID, LOG_DAEMON);
+
+ while ((ch = getopt(argc, argv, "")) != -1) {
+ switch (ch) {
+ case '?':
+ default:
+ printf("unknown option '%c'\n", optopt);
+ exit(1);
+ }
+ }
+
+ /* daemon initialization */
+
+ getpeername(0, (struct sockaddr *)&peername, &peernamelen);
+ syslog(LOG_INFO, "Talking to peer %s:%d",
+ inet_ntoa(peername.sin_addr), ntohs(peername.sin_port));
+ /* Start reading packets from stdin */
+ handle_packets(0);
+
+ return 0;
+}
diff --git a/qarsh/sockutil.c b/qarsh/sockutil.c
new file mode 100644
index 0000000..e5ac22e
--- /dev/null
+++ b/qarsh/sockutil.c
@@ -0,0 +1,81 @@
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <string.h>
+#include <netdb.h>
+#include <errno.h>
+#include <syslog.h>
+
+/* Some generic socket related functions to make things easier */
+
+int
+getsockport(int sd)
+{
+ struct sockaddr_in addr;
+ socklen_t addrlen;
+
+ addrlen = sizeof addr;
+ if (getsockname(sd, (struct sockaddr *)&addr, &addrlen) == 0) {
+ return ntohs(addr.sin_port);
+ } else {
+ return -1;
+ }
+}
+
+int
+bind_any(int minport)
+{
+ int sd;
+ struct sockaddr_in addr;
+
+ sd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sd == -1) return -1;
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_ANY);
+ do {
+ addr.sin_port = htons(minport++);
+ } while (bind(sd, (struct sockaddr *)&addr, sizeof addr) != 0);
+ if (listen(sd, 0) == -1) {
+ syslog(LOG_WARNING, "listen error %d, %s", errno,
+ strerror(errno));
+ }
+ return sd;
+}
+
+int
+connect_to_host(char *hostname, int port)
+{
+ struct hostent *h;
+ struct sockaddr_in haddr;
+ int sd;
+
+ h = gethostbyname(hostname);
+ haddr.sin_family = h->h_addrtype;
+ haddr.sin_port = htons(port);
+ memcpy(&haddr.sin_addr, h->h_addr, h->h_length);
+
+ sd = socket(PF_INET, SOCK_STREAM, 0);
+ if (sd == -1) return -1;
+ if (connect(sd, (struct sockaddr *)&haddr, sizeof haddr) == -1) {
+ return -1;
+ }
+ return sd;
+}
+
+int
+connect_to_peer(struct sockaddr_in *peer, int port)
+{
+ struct sockaddr_in in_peer;
+ int sd;
+
+ in_peer.sin_family = AF_INET;
+ in_peer.sin_port = htons(port);
+ in_peer.sin_addr = peer->sin_addr;
+
+ sd = socket(PF_INET, SOCK_STREAM, 0);
+ if (sd == -1) return -1;
+ if (connect(sd, (struct sockaddr *)&in_peer, sizeof in_peer) == -1) {
+ return -1;
+ }
+ return sd;
+}
diff --git a/qarsh/sockutil.h b/qarsh/sockutil.h
new file mode 100644
index 0000000..36d021a
--- /dev/null
+++ b/qarsh/sockutil.h
@@ -0,0 +1,6 @@
+#include <netinet/in.h>
+
+int getsockport(int sd);
+int bind_any(int minport);
+int connect_to_host(char *hostname, int port);
+int connect_to_peer(struct sockaddr_in *peer, int port);