diff options
-rw-r--r-- | qarsh/Makefile | 30 | ||||
-rw-r--r-- | qarsh/qarsh.c | 255 | ||||
-rw-r--r-- | qarsh/qarsh_packet.c | 497 | ||||
-rw-r--r-- | qarsh/qarsh_packet.h | 76 | ||||
-rw-r--r-- | qarsh/qarshd.c | 193 | ||||
-rw-r--r-- | qarsh/sockutil.c | 81 | ||||
-rw-r--r-- | qarsh/sockutil.h | 6 |
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); |