+/* -*-c-*-
+ *
+ * A simple SLIP implementation drivable from the command-line
+ *
+ * (c) 2008 Straylight/Edgeware
+ */
+
+/*----- Licensing notice --------------------------------------------------*
+ *
+ * This file is part of Trivial IP Encryption (TrIPE).
+ *
+ * TrIPE 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.
+ *
+ * TrIPE 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with TrIPE; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*----- Header files ------------------------------------------------------*/
+
+#include "config.h"
+
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+
+#include <mLib/alloc.h>
+#include <mLib/dstr.h>
+#include <mLib/fdflags.h>
+#include <mLib/mdwopt.h>
+#include <mLib/quis.h>
+#include <mLib/report.h>
+#include <mLib/sel.h>
+#include <mLib/sub.h>
+
+#include "slip.h"
+
+#undef sun
+
+/*----- Data structures ---------------------------------------------------*/
+
+typedef struct pkq_node {
+ struct pkq_node *next;
+ unsigned char *buf;
+ size_t n, sz;
+} pkq_node;
+
+typedef struct pkq {
+ pkq_node *head, **tail;
+} pkq;
+
+enum { START, OK, ESC, BAD, SYNC1, SYNC2 };
+
+typedef struct client {
+ sel_file f;
+ dstr d;
+ int mode;
+} client;
+
+typedef struct gobbler {
+ sel_file f;
+} gobbler;
+
+typedef struct dribbler {
+ sel_file f;
+ pkq q;
+ void (*done)(struct dribbler *, int, void *);
+ void *p;
+} dribbler;
+
+typedef struct waiter {
+ struct waiter *next;
+ int fd;
+ gobbler *g;
+} waiter;
+
+/*----- Static variables --------------------------------------------------*/
+
+static pkq q_in;
+static dribbler *dribble_out;
+static dstr slipbuf = DSTR_INIT;
+static int slipstate = SYNC1;
+static sel_file slip_in, listener;
+static sel_state sel;
+static unsigned reasons;
+static waiter *wait_head, **wait_tail = &wait_head;
+static unsigned char buf[16384];
+static const char *name;
+
+/*----- Utilities ---------------------------------------------------------*/
+
+static void socketaddr(struct sockaddr_un *sun, size_t *sz)
+{
+ size_t n = strlen(name) + 1;
+ if (n + offsetof(struct sockaddr_un, sun_path) > sizeof(*sun))
+ die(EXIT_FAILURE, "name too long: `%s'", name);
+ sun->sun_family = AF_UNIX;
+ memcpy(sun->sun_path, name, n);
+ if (sz)
+ *sz = n + offsetof(struct sockaddr_un, sun_path);
+}
+
+/*------ Packet queue -----------------------------------------------------*
+ *
+ * A packet queue contains a sequence of octet strings. Packets can be added
+ * to the end and read off the front.
+ */
+
+static void initqueue(pkq *q) { q->head = 0; q->tail = &q->head; }
+
+static pkq_node *make_pkqnode(void *p, size_t n)
+{
+ pkq_node *pn;
+
+ if (!n)
+ return (0);
+ pn = CREATE(pkq_node);
+ pn->next = 0;
+ pn->buf = xmalloc(n);
+ memcpy(pn->buf, p, n);
+ pn->sz = n;
+ pn->n = 0;
+ return (pn);
+}
+
+static int enqueue(pkq *q, pkq_node *pn)
+{
+ int rc = 0;
+
+ if (!pn)
+ return (0);
+ rc = !q->head;
+ pn->next = 0;
+ *q->tail = pn;
+ q->tail = &pn->next;
+ return (rc);
+}
+
+static void destroy_pkqnode(pkq_node *pn) { xfree(pn->buf); DESTROY(pn); }
+
+static int dequeue(pkq *q, int freep)
+{
+ pkq_node *pn = q->head;
+ assert(pn);
+ q->head = pn->next;
+ if (freep)
+ destroy_pkqnode(pn);
+ if (!q->head) {
+ q->tail = &q->head;
+ return (1);
+ }
+ return (0);
+}
+
+static void destroy_pkq(pkq *q)
+{
+ pkq_node *pn, *pnn;
+
+ for (pn = q->head; pn; pn = pnn) {
+ pnn = pn->next;
+ destroy_pkqnode(pn);
+ }
+ q->head = 0; q->tail = &q->head;
+}
+
+/*----- Gobblers ----------------------------------------------------------*
+ *
+ * A gobbler just eats everything it sees on its input descriptor.
+ * Eventually, when it sees end-of-file, it closes the input descriptor and
+ * quits.
+ */
+
+static void gobbler_close(gobbler *g)
+ { if (g->f.fd != -1) { sel_rmfile(&g->f); g->f.fd = -1; } }
+
+static void gobbler_destroy(gobbler *g) { gobbler_close(g); DESTROY(g); }
+
+static void do_gobble_in(int fd, unsigned mode, void *p)
+{
+ gobbler *g = p;
+ ssize_t n;
+
+ for (;;) {
+ n = read(fd, buf, sizeof(buf));
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ break;
+ else {
+ moan("read (gobble): %s", strerror(errno));
+ gobbler_close(g);
+ break;
+ }
+ } else if (n == 0) {
+ gobbler_close(g);
+ break;
+ }
+ }
+}
+
+static gobbler *make_gobbler(int fd)
+{
+ gobbler *g;
+
+ g = CREATE(gobbler);
+ sel_initfile(&sel, &g->f, fd, SEL_READ, do_gobble_in, g);
+ sel_addfile(&g->f);
+ do_gobble_in(fd, SEL_READ, g);
+ return (g);
+}
+
+/*----- Dribbler ----------------------------------------------------------*
+ *
+ * A dribbler hands out data from a packet queue to a file descriptor. It
+ * makes no attempt to preserve the record boundaries inherent in the packet
+ * queue structure. If the dribbler reaches the end of its queue, it invokes
+ * a user-supplied `done' function and stops selecting its output descriptor
+ * for writing.
+ */
+
+static void cripple_dribbler(dribbler *d) { close(d->f.fd); d->f.fd = -1; }
+
+static void destroy_dribbler(dribbler *d)
+ { cripple_dribbler(d); DESTROY(d); }
+
+static void dribble_done(dribbler *d, int err)
+{
+ if (d->q.head) {
+ sel_rmfile(&d->f);
+ destroy_pkq(&d->q);
+ }
+ d->done(d, err, d->p);
+}
+
+static void do_dribble_out(int fd, unsigned mode, void *p)
+{
+ dribbler *d = p;
+ ssize_t n;
+ pkq_node *pn;
+
+ for (;;) {
+ pn = d->q.head;
+ n = write(fd, pn->buf + pn->n, pn->sz - pn->n);
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ break;
+ else {
+ dribble_done(d, errno);
+ break;
+ }
+ }
+ pn->n += n;
+ if (pn->n == pn->sz && dequeue(&d->q, 1)) {
+ sel_rmfile(&d->f);
+ dribble_done(d, 0);
+ break;
+ }
+ }
+}
+
+static int enqueue_dribble(dribbler *d, pkq_node *pn)
+{
+ if (d->f.fd == -1) {
+ destroy_pkqnode(pn);
+ return (0);
+ }
+ if (enqueue(&d->q, pn)) {
+ sel_addfile(&d->f);
+ do_dribble_out(d->f.fd, SEL_WRITE, d);
+ return (1);
+ }
+ return (0);
+}
+
+static dribbler *make_dribbler(int fd,
+ void (*done)(dribbler *, int, void *),
+ void *p)
+{
+ dribbler *d = CREATE(dribbler);
+ sel_initfile(&sel, &d->f, fd, SEL_WRITE, do_dribble_out, d);
+ initqueue(&d->q);
+ d->done = done;
+ d->p = p;
+ return (d);
+}
+
+/*----- Clients -----------------------------------------------------------*/
+
+static void done_client_dribble(dribbler *d, int err, void *p)
+{
+ if (err)
+ moan("write (client): %s", strerror(err));
+ gobbler_destroy(p);
+ destroy_dribbler(d);
+ reasons--;
+}
+
+static void dequeue_to_waiter(void)
+{
+ waiter *w;
+ dribbler *d;
+ pkq_node *pn;
+
+ while (q_in.head && wait_head) {
+ w = wait_head;
+ wait_head = w->next;
+ if (!wait_head)
+ wait_tail = &wait_head;
+ d = make_dribbler(w->fd, done_client_dribble, w->g);
+ DESTROY(w);
+ pn = q_in.head;
+ if (dequeue(&q_in, 0))
+ reasons--;
+ enqueue_dribble(d, pn);
+ }
+}
+
+static void client_destroy(client *c)
+{
+ sel_rmfile(&c->f);
+ close(c->f.fd);
+ dstr_destroy(&c->d);
+ reasons--;
+ DESTROY(c);
+}
+
+static void do_client_in(int fd, unsigned mode, void *p)
+{
+ client *c = p;
+ ssize_t n, i, i0;
+ waiter *w;
+
+ /* --- Attention --- *
+ *
+ * The queue for outbound packets is SLIP-encoded; we need to encode it
+ * here. The queue for inbound packets is raw.
+ */
+
+ for (;;) {
+ n = read(fd, buf, sizeof(buf));
+ i0 = 0;
+ if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ break;
+ else {
+ moan("read (client): %s", strerror(errno));
+ client_destroy(c);
+ return;
+ }
+ } else if (n == 0) {
+ if (c->mode == '>') {
+ DPUTC(&c->d, SL_END);
+ if (enqueue_dribble(dribble_out, make_pkqnode(c->d.buf, c->d.len)))
+ reasons++;
+ }
+ client_destroy(c);
+ return;
+ }
+ if (c->mode == '?') {
+ switch (buf[0]) {
+ case '>':
+ i0 = 1;
+ c->mode = '>';
+ break;
+ case '<':
+ w = CREATE(waiter);
+ w->g = make_gobbler(fd);
+ w->next = 0;
+ w->fd = fd;
+ *wait_tail = w;
+ wait_tail = &w->next;
+ sel_rmfile(&c->f);
+ DESTROY(c);
+ dequeue_to_waiter();
+ return;
+ default:
+ moan("bad client mode `%c'", buf[0]);
+ client_destroy(c);
+ return;
+ }
+ }
+ for (i = i0; i < n; i++) {
+ switch (buf[i]) {
+ case SL_ESC:
+ DPUTC(&c->d, SL_ESC);
+ DPUTC(&c->d, SL_ESCESC);
+ break;
+ case SL_END:
+ DPUTC(&c->d, SL_ESC);
+ DPUTC(&c->d, SL_ESCEND);
+ break;
+ default:
+ DPUTC(&c->d, buf[i]);
+ break;
+ }
+ }
+ }
+}
+
+static void do_accept(int fd, unsigned mode, void *hunoz)
+{
+ client *c;
+ struct sockaddr_un sun;
+ socklen_t n = sizeof(sun);
+ int nfd;
+
+ if ((nfd = accept(fd, (struct sockaddr *)&sun, &n)) < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ return;
+ else
+ die(EXIT_FAILURE, "accept: %s", strerror(errno));
+ }
+ c = CREATE(client);
+ c->mode = '?';
+ dstr_create(&c->d);
+ fdflags(nfd, O_NONBLOCK, O_NONBLOCK, 0, 0);
+ sel_initfile(&sel, &c->f, nfd, SEL_READ, do_client_in, c);
+ sel_addfile(&c->f);
+ reasons++;
+}
+
+/*----- Main daemon -------------------------------------------------------*/
+
+static void done_slip_dribble(dribbler *d, int err, void *p)
+{
+ if (!err)
+ reasons--;
+ else if (err != EPIPE)
+ die(EXIT_FAILURE, "write (slip): %s", strerror(errno));
+ else
+ cripple_dribbler(d);
+}
+
+static void do_slip_in(int fd, unsigned mode, void *hunoz)
+{
+ ssize_t i, n;
+
+ /* --- Attention --- *
+ *
+ * The queue for inbound packets contains raw data; we need to decode it
+ * here. The queue for outbound packets is SLIP-encoded.
+ *
+ * TrIPE sends two empty packets on start-up, in order to resynchronize the
+ * target. We don't need this and it messes us up.
+ */
+
+ for (;;) {
+ n = read(fd, buf, sizeof(buf));
+ if (n == 0) {
+ switch (slipstate) {
+ case SYNC1:
+ case SYNC2:
+ case START:
+ case BAD:
+ break;
+ default:
+ moan("eof found while processing packet (discarding)");
+ break;
+ }
+ close(fd);
+ sel_rmfile(&slip_in);
+ reasons--;
+ return;
+ } else if (n < 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+ break;
+ die(EXIT_FAILURE, "read (slip in): %s", strerror(errno));
+ }
+ for (i = 0; i < n; i++) {
+ switch (slipstate) {
+ case SYNC1:
+ switch (buf[i]) {
+ case SL_END:
+ slipstate = SYNC2;
+ break;
+ default:
+ goto start;
+ }
+ break;
+ case SYNC2:
+ switch (buf[i]) {
+ case SL_END:
+ slipstate = START;
+ break;
+ default:
+ goto start;
+ }
+ break;
+ case BAD:
+ switch (buf[i]) {
+ case SL_END:
+ DRESET(&slipbuf);
+ slipstate = OK;
+ break;
+ default:
+ break;
+ }
+ break;
+ case ESC:
+ switch (buf[i]) {
+ case SL_ESCEND:
+ DPUTC(&slipbuf, SL_END);
+ break;
+ case SL_ESCESC:
+ DPUTC(&slipbuf, SL_ESC);
+ break;
+ case SL_END:
+ moan("found escaped end byte (discard packet and resync");
+ DRESET(&slipbuf);
+ slipstate = OK;
+ break;
+ default:
+ moan("unspected escape char 0x%02x", buf[i]);
+ slipstate = BAD;
+ break;
+ }
+ break;
+ case START:
+ case OK:
+ start:
+ switch (buf[i]) {
+ case SL_ESC:
+ slipstate = ESC;
+ break;
+ case SL_END:
+ if (enqueue(&q_in, make_pkqnode(slipbuf.buf, slipbuf.len)))
+ reasons++;
+ DRESET(&slipbuf);
+ dequeue_to_waiter();
+ slipstate = START;
+ break;
+ default:
+ DPUTC(&slipbuf, buf[i]);
+ slipstate = OK;
+ break;
+ }
+ break;
+ }
+ }
+ }
+}
+
+static void slipif(void)
+{
+ int fd;
+ dstr d = DSTR_INIT;
+ struct sockaddr_un sun;
+ size_t sz;
+
+ /* --- Make the socket --- */
+
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ die(EXIT_FAILURE, "socket: %s", strerror(errno));
+ socketaddr(&sun, &sz);
+ if (bind(fd, (struct sockaddr *)&sun, sz))
+ die(EXIT_FAILURE, "bind: %s", strerror(errno));
+ if (listen(fd, 5))
+ die(EXIT_FAILURE, "listen: %s", strerror(errno));
+
+ /* --- Set up listeners for things --- */
+
+ fdflags(fd, O_NONBLOCK, O_NONBLOCK, 0, 0);
+ sel_initfile(&sel, &listener, fd, SEL_READ, do_accept, 0);
+ sel_addfile(&listener);
+
+ fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
+ fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
+ sel_initfile(&sel, &slip_in, STDIN_FILENO, SEL_READ, do_slip_in, 0);
+ dribble_out = make_dribbler(STDOUT_FILENO, done_slip_dribble, 0);
+ sel_addfile(&slip_in);
+
+ initqueue(&q_in);
+ reasons++;
+
+ /* --- Write the interface name --- */
+
+ dstr_putf(&d, "%s-%s\n", QUIS, name);
+ if (enqueue_dribble(dribble_out, make_pkqnode(d.buf, d.len)))
+ reasons++;
+ dstr_destroy(&d);
+
+ /* --- Main loop --- */
+
+ while (reasons) {
+ if (sel_select(&sel))
+ die(EXIT_FAILURE, "select: %s", strerror(errno));
+ }
+
+ /* --- Done --- */
+
+ unlink(name);
+}
+
+/*----- Putting and getting -----------------------------------------------*/
+
+static int make_sock(int mode)
+{
+ struct sockaddr_un sun;
+ size_t sz;
+ int fd;
+ char ch;
+
+ if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
+ die(EXIT_FAILURE, "socket: %s", strerror(errno));
+ socketaddr(&sun, &sz);
+ if (connect(fd, (struct sockaddr *)&sun, sz))
+ die(EXIT_FAILURE, "connect: %s", strerror(errno));
+ ch = mode;
+ if (write(fd, &ch, 1) < 0)
+ die(EXIT_FAILURE, "write (mode): %s", strerror(errno));
+ return (fd);
+}
+
+static void shovel(int from, int to)
+{
+ ssize_t n;
+ size_t sz;
+ unsigned char *p;
+
+ for (;;) {
+ n = read(from, buf, sizeof(buf));
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ die(EXIT_FAILURE, "read (shovel): %s", strerror(errno));
+ } else if (n == 0)
+ break;
+
+ sz = n;
+ p = buf;
+ while (sz) {
+ n = write(to, p, sz);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ else
+ die(EXIT_FAILURE, "write (shovel): %s", strerror(errno));
+ }
+ p += n;
+ sz -= n;
+ }
+ }
+ close(from);
+ close(to);
+}
+
+static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
+static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
+
+/*----- Main code ---------------------------------------------------------*/
+
+static void usage(FILE *fp) { pquis(fp, "Usage: $ [-pg] SOCKET\n"); }
+
+static void version(void)
+ { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
+
+static void help(void)
+{
+ version();
+ fputc('\n', stdout);
+ usage(stdout);
+ puts("\n\
+With no options, provides a SLIP interface for TrIPE.\n\
+\n\
+Options:\n\
+ -p, --put Send packet on stdin to TrIPE.\n\
+ -g, --get Receive packet from TrIPE and write to stdout.");
+}
+
+int main(int argc, char *argv[])
+{
+ int mode = 'd';
+ int i;
+
+ ego(argv[0]);
+ for (;;) {
+ const struct option opt[] = {
+ { "help", 0, 0, 'h' },
+ { "version", 0, 0, 'v' },
+ { "put", 0, 0, 'p' },
+ { "get", 0, 0, 'g' },
+ { 0, 0, 0, 0 }
+ };
+ i = mdwopt(argc, argv, "hvpg", opt, 0, 0, 0);
+ if (i < 0)
+ break;
+ switch (i) {
+ case 'h': help(); return (0);
+ case 'v': version(); return (0);
+ case 'p': case 'g': mode = i; break;
+ default: usage(stderr); exit(EXIT_FAILURE); break;
+ }
+ }
+ if (argc - optind != 1) { usage(stderr); exit(EXIT_FAILURE); }
+ name = argv[optind];
+ signal(SIGPIPE, SIG_IGN);
+ switch (mode) {
+ case 'd': slipif(); break;
+ case 'p': put(); break;
+ case 'g': get(); break;
+ }
+ return (0);
+}
+
+/*----- That's all, folks -------------------------------------------------*/