chiark / gitweb /
853df5b2233ea853bc3a851d3373462432436240
[tripe] / uslip / uslip.c
1 /* -*-c-*-
2  *
3  * A simple SLIP implementation drivable from the command-line
4  *
5  * (c) 2008 Straylight/Edgeware
6  */
7
8 /*----- Licensing notice --------------------------------------------------*
9  *
10  * This file is part of Trivial IP Encryption (TrIPE).
11  *
12  * TrIPE is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by
14  * the Free Software Foundation; either version 2 of the License, or
15  * (at your option) any later version.
16  *
17  * TrIPE is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  * GNU General Public License for more details.
21  *
22  * You should have received a copy of the GNU General Public License
23  * along with TrIPE; if not, write to the Free Software Foundation,
24  * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25  */
26
27 /*----- Header files ------------------------------------------------------*/
28
29 #include "config.h"
30
31 #include <assert.h>
32 #include <ctype.h>
33 #include <errno.h>
34 #include <signal.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38
39 #include <sys/types.h>
40 #include <sys/time.h>
41 #include <unistd.h>
42
43 #include <sys/socket.h>
44 #include <sys/un.h>
45 #include <netinet/in.h>
46 #include <arpa/inet.h>
47 #include <netdb.h>
48
49 #include <mLib/alloc.h>
50 #include <mLib/bits.h>
51 #include <mLib/conn.h>
52 #include <mLib/dstr.h>
53 #include <mLib/fdflags.h>
54 #include <mLib/mdwopt.h>
55 #include <mLib/quis.h>
56 #include <mLib/report.h>
57 #include <mLib/sel.h>
58 #include <mLib/sub.h>
59 #include <mLib/tv.h>
60
61 #include "slip.h"
62
63 #undef sun
64
65 /*----- Data structures ---------------------------------------------------*/
66
67 typedef struct pkq_node {
68   struct pkq_node *next;
69   unsigned char *buf;
70   size_t n, sz;
71 } pkq_node;
72
73 typedef struct pkq {
74   pkq_node *head, **tail;
75 } pkq;
76
77 enum { START, OK, ESC, BAD, SYNC1, SYNC2 };
78
79 typedef struct client {
80   sel_file f;
81   dstr d;
82   int mode;
83 } client;
84
85 typedef struct gobbler {
86   sel_file f;
87   void (*done)(struct gobbler *, int, void *);
88   void *p;
89 } gobbler;
90
91 typedef struct dribbler {
92   sel_file f;
93   pkq q;
94   void (*done)(struct dribbler *, int, void *);
95   void *p;
96 } dribbler;
97
98 typedef struct waiter {
99   struct waiter *next;
100   int fd;
101   gobbler *g;
102 } waiter;
103
104 /*----- Static variables --------------------------------------------------*/
105
106 static pkq q_in;
107 static dribbler *dribble_out;
108 static dstr slipbuf = DSTR_INIT;
109 static int slipstate = SYNC1;
110 static sel_file slip_in, listener;
111 static sel_state sel;
112 static unsigned reasons;
113 static waiter *wait_head, **wait_tail = &wait_head;
114 static unsigned char buf[16384];
115 static const char *name;
116
117 /*----- Utilities ---------------------------------------------------------*/
118
119 static void socketaddr(struct sockaddr_un *sun, size_t *sz)
120 {
121   size_t n = strlen(name) + 1;
122   if (n + offsetof(struct sockaddr_un, sun_path) > sizeof(*sun))
123     die(EXIT_FAILURE, "name too long: `%s'", name);
124   sun->sun_family = AF_UNIX;
125   memcpy(sun->sun_path, name, n);
126   if (sz)
127     *sz = n + offsetof(struct sockaddr_un, sun_path);
128 }
129
130 /*------ Packet queue -----------------------------------------------------*
131  *
132  * A packet queue contains a sequence of octet strings.  Packets can be added
133  * to the end and read off the front.
134  */
135
136 static void initqueue(pkq *q) { q->head = 0; q->tail = &q->head; }
137
138 static pkq_node *make_pkqnode(const void *p, size_t n)
139 {
140   pkq_node *pn;
141
142   if (!n) return (0);
143   pn = CREATE(pkq_node);
144   pn->next = 0;
145   pn->buf = xmalloc(n);
146   if (p) memcpy(pn->buf, p, n);
147   pn->sz = n;
148   pn->n = 0;
149   return (pn);
150 }
151
152 static int enqueue(pkq *q, pkq_node *pn)
153 {
154   int rc = 0;
155
156   if (!pn) return (0);
157   rc = !q->head;
158   pn->next = 0;
159   *q->tail = pn;
160   q->tail = &pn->next;
161   return (rc);
162 }
163
164 static void destroy_pkqnode(pkq_node *pn) { xfree(pn->buf); DESTROY(pn); }
165
166 static int dequeue(pkq *q, int freep)
167 {
168   pkq_node *pn = q->head;
169   assert(pn);
170   q->head = pn->next;
171   if (freep)
172     destroy_pkqnode(pn);
173   if (!q->head) {
174     q->tail = &q->head;
175     return (1);
176   }
177   return (0);
178 }
179
180 static void destroy_pkq(pkq *q)
181 {
182   pkq_node *pn, *pnn;
183
184   for (pn = q->head; pn; pn = pnn) {
185     pnn = pn->next;
186     destroy_pkqnode(pn);
187   }
188   q->head = 0; q->tail = &q->head;
189 }
190
191 /*----- Gobblers ----------------------------------------------------------*
192  *
193  * A gobbler just eats everything it sees on its input descriptor.
194  * Eventually, when it sees end-of-file, it closes the input descriptor,
195  * calls a user-supplied calback function, and quits.
196  */
197
198 static void close_gobbler(gobbler *g)
199   { if (g->f.fd != -1) { sel_rmfile(&g->f); close(g->f.fd); g->f.fd = -1; } }
200
201 static void destroy_gobbler(gobbler *g) { close_gobbler(g); DESTROY(g); }
202
203 static void do_gobble_in(int fd, unsigned mode, void *p)
204 {
205   gobbler *g = p;
206   ssize_t n;
207
208   for (;;) {
209     n = read(fd, buf, sizeof(buf));
210     if (n < 0) {
211       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
212         break;
213       else {
214         moan("read (gobble): %s", strerror(errno));
215         if (g->done) g->done(g, errno, g->p);
216         close_gobbler(g);
217         break;
218       }
219     } else if (n == 0) {
220       if (g->done) g->done(g, 0, g->p);
221       close_gobbler(g);
222       break;
223     }
224   }
225 }
226
227 static gobbler *make_gobbler(int fd,
228                              void (*done)(gobbler *, int, void *),
229                              void *p)
230 {
231   gobbler *g;
232
233   g = CREATE(gobbler);
234   g->done = done;
235   g->p = p;
236   sel_initfile(&sel, &g->f, fd, SEL_READ, do_gobble_in, g);
237   sel_addfile(&g->f);
238   do_gobble_in(fd, SEL_READ, g);
239   return (g);
240 }
241
242 /*----- Dribbler ----------------------------------------------------------*
243  *
244  * A dribbler hands out data from a packet queue to a file descriptor.  It
245  * makes no attempt to preserve the record boundaries inherent in the packet
246  * queue structure.  If the dribbler reaches the end of its queue, it invokes
247  * a user-supplied `done' function and stops selecting its output descriptor
248  * for writing.
249  */
250
251 static void cripple_dribbler(dribbler *d) { close(d->f.fd); d->f.fd = -1; }
252
253 static void destroy_dribbler(dribbler *d)
254   { cripple_dribbler(d); DESTROY(d); }
255
256 static void dribble_done(dribbler *d, int err)
257 {
258   if (d->q.head) {
259     sel_rmfile(&d->f);
260     destroy_pkq(&d->q);
261   }
262   d->done(d, err, d->p);
263 }
264
265 static void do_dribble_out(int fd, unsigned mode, void *p)
266 {
267   dribbler *d = p;
268   ssize_t n;
269   pkq_node *pn;
270
271   for (;;) {
272     pn = d->q.head;
273     n = write(fd, pn->buf + pn->n, pn->sz - pn->n);
274     if (n < 0) {
275       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
276         break;
277       else {
278         dribble_done(d, errno);
279         break;
280       }
281     }
282     pn->n += n;
283     if (pn->n == pn->sz && dequeue(&d->q, 1)) {
284       sel_rmfile(&d->f);
285       dribble_done(d, 0);
286       break;
287     }
288   }
289 }
290
291 static int enqueue_dribble(dribbler *d, pkq_node *pn)
292 {
293   if (d->f.fd == -1) {
294     destroy_pkqnode(pn);
295     return (0);
296   }
297   if (enqueue(&d->q, pn)) {
298     sel_addfile(&d->f);
299     do_dribble_out(d->f.fd, SEL_WRITE, d);
300     return (1);
301   }
302   return (0);
303 }
304
305 static dribbler *make_dribbler(int fd,
306                                void (*done)(dribbler *, int, void *),
307                                void *p)
308 {
309   dribbler *d = CREATE(dribbler);
310   sel_initfile(&sel, &d->f, fd, SEL_WRITE, do_dribble_out, d);
311   initqueue(&d->q);
312   d->done = done;
313   d->p = p;
314   return (d);
315 }
316
317 /*----- Clients -----------------------------------------------------------*/
318
319 static void done_client_dribble(dribbler *d, int err, void *p)
320 {
321   if (err)
322     moan("write (client): %s", strerror(err));
323   destroy_gobbler(p);
324   destroy_dribbler(d);
325   reasons--;
326 }
327
328 static void dequeue_to_waiter(void)
329 {
330   waiter *w;
331   dribbler *d;
332   pkq_node *pn;
333
334   while (q_in.head && wait_head) {
335     w = wait_head;
336     wait_head = w->next;
337     if (!wait_head)
338       wait_tail = &wait_head;
339     d = make_dribbler(w->fd, done_client_dribble, w->g);
340     DESTROY(w);
341     pn = q_in.head;
342     if (dequeue(&q_in, 0))
343       reasons--;
344     enqueue_dribble(d, pn);
345   }
346 }
347
348 static void destroy_client(client *c)
349 {
350   sel_rmfile(&c->f);
351   close(c->f.fd);
352   dstr_destroy(&c->d);
353   reasons--;
354   DESTROY(c);
355 }
356
357 static void do_client_in(int fd, unsigned mode, void *p)
358 {
359   client *c = p;
360   ssize_t n, i, i0;
361   waiter *w;
362
363   /* --- Attention --- *
364    *
365    * The queue for outbound packets is SLIP-encoded; we need to encode it
366    * here. The queue for inbound packets is raw.
367    */
368
369   for (;;) {
370     n = read(fd, buf, sizeof(buf));
371     i0 = 0;
372     if (n < 0) {
373       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
374         break;
375       else {
376         moan("read (client): %s", strerror(errno));
377         destroy_client(c);
378         return;
379       }
380     } else if (n == 0) {
381       if (c->mode == '>') {
382         DPUTC(&c->d, SL_END);
383         if (enqueue_dribble(dribble_out, make_pkqnode(c->d.buf, c->d.len)))
384           reasons++;
385       }
386       destroy_client(c);
387       return;
388     }
389     if (c->mode == '?') {
390       switch (buf[0]) {
391         case '>':
392           i0 = 1;
393           c->mode = '>';
394           break;
395         case '<':
396           w = CREATE(waiter);
397           w->g = make_gobbler(fd, 0, 0);
398           w->next = 0;
399           w->fd = fd;
400           *wait_tail = w;
401           wait_tail = &w->next;
402           sel_rmfile(&c->f);
403           DESTROY(c);
404           dequeue_to_waiter();
405           return;
406         default:
407           moan("bad client mode `%c'", buf[0]);
408           destroy_client(c);
409           return;
410       }
411     }
412     for (i = i0; i < n; i++) {
413       switch (buf[i]) {
414         case SL_ESC:
415           DPUTC(&c->d, SL_ESC);
416           DPUTC(&c->d, SL_ESCESC);
417           break;
418         case SL_END:
419           DPUTC(&c->d, SL_ESC);
420           DPUTC(&c->d, SL_ESCEND);
421           break;
422         default:
423           DPUTC(&c->d, buf[i]);
424           break;
425       }
426     }
427   }
428 }
429
430 static void do_accept(int fd, unsigned mode, void *hunoz)
431 {
432   client *c;
433   struct sockaddr_un sun;
434   socklen_t n = sizeof(sun);
435   int nfd;
436
437   if ((nfd = accept(fd, (struct sockaddr *)&sun, &n)) < 0) {
438     if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
439       return;
440     else
441       die(EXIT_FAILURE, "accept: %s", strerror(errno));
442   }
443   c = CREATE(client);
444   c->mode = '?';
445   dstr_create(&c->d);
446   fdflags(nfd, O_NONBLOCK, O_NONBLOCK, 0, 0);
447   sel_initfile(&sel, &c->f, nfd, SEL_READ, do_client_in, c);
448   sel_addfile(&c->f);
449   reasons++;
450 }
451
452 /*----- Main daemon -------------------------------------------------------*/
453
454 static void done_slip_dribble(dribbler *d, int err, void *hunoz)
455 {
456   if (!err)
457     reasons--;
458   else if (err != EPIPE)
459     die(EXIT_FAILURE, "write (slip): %s", strerror(errno));
460   else
461     cripple_dribbler(d);
462 }
463
464 static void close_slip(int fd)
465 {
466   switch (slipstate) {
467     case SYNC1: case SYNC2: case START: case BAD: break;
468     default: moan("eof found while processing packet (discarding)"); break;
469   }
470   close(fd); sel_rmfile(&slip_in);
471   reasons--;
472 }
473
474 static void do_slip_in(int fd, unsigned mode, void *hunoz)
475 {
476   ssize_t i, n;
477
478   /* --- Attention --- *
479    *
480    * The queue for inbound packets contains raw data; we need to decode it
481    * here.  The queue for outbound packets is SLIP-encoded.
482    *
483    * TrIPE sends two empty packets on start-up, in order to resynchronize the
484    * target.  We don't need this and it messes us up.
485    */
486
487   for (;;) {
488     n = read(fd, buf, sizeof(buf));
489     if (n == 0) {
490       close_slip(fd);
491       return;
492     } else if (n < 0) {
493       if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
494         break;
495       die(EXIT_FAILURE, "read (slip in): %s", strerror(errno));
496     }
497     for (i = 0; i < n; i++) {
498       switch (slipstate) {
499         case SYNC1:
500           switch (buf[i]) {
501             case SL_END:
502               slipstate = SYNC2;
503               break;
504             default:
505               goto start;
506           }
507           break;
508         case SYNC2:
509           switch (buf[i]) {
510             case SL_END:
511               slipstate = START;
512               break;
513             default:
514               goto start;
515           }
516           break;
517         case BAD:
518           switch (buf[i]) {
519             case SL_END:
520               DRESET(&slipbuf);
521               slipstate = OK;
522               break;
523             default:
524               break;
525           }
526           break;
527         case ESC:
528           switch (buf[i]) {
529             case SL_ESCEND:
530               DPUTC(&slipbuf, SL_END);
531               slipstate = OK;
532               break;
533             case SL_ESCESC:
534               DPUTC(&slipbuf, SL_ESC);
535               slipstate = OK;
536               break;
537             case SL_END:
538               moan("found escaped end byte (discard packet and resync");
539               DRESET(&slipbuf);
540               slipstate = OK;
541               break;
542             default:
543               moan("unexpected escape char 0x%02x", buf[i]);
544               slipstate = BAD;
545               break;
546           }
547           break;
548         case START:
549         case OK:
550         start:
551           switch (buf[i]) {
552             case SL_ESC:
553               slipstate = ESC;
554               break;
555             case SL_END:
556               if (enqueue(&q_in, make_pkqnode(slipbuf.buf, slipbuf.len)))
557                 reasons++;
558               DRESET(&slipbuf);
559               dequeue_to_waiter();
560               slipstate = START;
561               break;
562             default:
563               DPUTC(&slipbuf, buf[i]);
564               slipstate = OK;
565               break;
566           }
567           break;
568       }
569     }
570   }
571 }
572
573 static void slipif(void)
574 {
575   int fd;
576   dstr d = DSTR_INIT;
577   struct sockaddr_un sun;
578   size_t sz;
579
580   /* --- Make the socket --- */
581
582   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
583     die(EXIT_FAILURE, "socket: %s", strerror(errno));
584   socketaddr(&sun, &sz);
585   if (bind(fd, (struct sockaddr *)&sun, sz))
586     die(EXIT_FAILURE, "bind: %s", strerror(errno));
587   if (listen(fd, 5))
588     die(EXIT_FAILURE, "listen: %s", strerror(errno));
589
590   /* --- Set up listeners for things --- */
591
592   fdflags(fd, O_NONBLOCK, O_NONBLOCK, 0, 0);
593   sel_initfile(&sel, &listener, fd, SEL_READ, do_accept, 0);
594   sel_addfile(&listener);
595
596   fdflags(STDIN_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
597   fdflags(STDOUT_FILENO, O_NONBLOCK, O_NONBLOCK, 0, 0);
598   sel_initfile(&sel, &slip_in, STDIN_FILENO, SEL_READ, do_slip_in, 0);
599   dribble_out = make_dribbler(STDOUT_FILENO, done_slip_dribble, 0);
600   sel_addfile(&slip_in);
601
602   initqueue(&q_in);
603   reasons++;
604
605   /* --- Write the interface name --- */
606
607   dstr_putf(&d, "%s-%s\n", QUIS, name);
608   if (enqueue_dribble(dribble_out, make_pkqnode(d.buf, d.len)))
609     reasons++;
610   dstr_destroy(&d);
611
612   /* --- Main loop --- */
613
614   while (reasons) {
615     if (sel_select(&sel))
616       die(EXIT_FAILURE, "select: %s", strerror(errno));
617   }
618
619   /* --- Done --- */
620
621   unlink(name);
622 }
623
624 /*----- Putting and getting -----------------------------------------------*/
625
626 static int make_sock(int mode)
627 {
628   struct sockaddr_un sun;
629   size_t sz;
630   int fd;
631   char ch;
632
633   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
634     die(EXIT_FAILURE, "socket: %s", strerror(errno));
635   socketaddr(&sun, &sz);
636   if (connect(fd, (struct sockaddr *)&sun, sz))
637     die(EXIT_FAILURE, "connect: %s", strerror(errno));
638   ch = mode;
639   if (write(fd, &ch, 1) < 0)
640     die(EXIT_FAILURE, "write (mode): %s", strerror(errno));
641   return (fd);
642 }
643
644 static void shovel(int from, int to)
645 {
646   ssize_t n;
647   size_t sz;
648   unsigned char *p;
649
650   for (;;) {
651     n = read(from, buf, sizeof(buf));
652     if (n < 0) {
653       if (errno == EINTR)
654         continue;
655       else
656         die(EXIT_FAILURE, "read (shovel): %s", strerror(errno));
657     } else if (n == 0)
658       break;
659
660     sz = n;
661     p = buf;
662     while (sz) {
663       n = write(to, p, sz);
664       if (n < 0) {
665         if (errno == EINTR)
666           continue;
667         else
668           die(EXIT_FAILURE, "write (shovel): %s", strerror(errno));
669       }
670       p += n;
671       sz -= n;
672     }
673   }
674   close(from);
675   close(to);
676 }
677
678 static void put(void) { shovel(STDIN_FILENO, make_sock('>')); }
679 static void get(void) { shovel(make_sock('<'), STDOUT_FILENO); }
680
681 /*----- Flooding and sinking ----------------------------------------------*/
682
683 /* --- Connection jobs --- */
684
685 typedef struct conninfo {
686   struct conninfo *next;
687   conn c;
688 } conninfo;
689
690 #define MAXCONN 32
691 static conninfo conns[MAXCONN], *freeconns;
692
693 static void (*connhook)(int, conninfo *);
694
695 static void connrecycle(conninfo *c) { c->next = freeconns; freeconns = c; }
696
697 static void connerr(conninfo *c)
698 {
699   if (errno == EAGAIN) connrecycle(c);
700   else die(EXIT_FAILURE, "connect: %s", strerror(errno));
701 }
702
703 static void connected(int fd, void *p)
704   { if (fd == -1) connerr(p); else connhook(fd, p); }
705
706 static void conndoconnect(conninfo *c)
707 {
708   int fd;
709   struct sockaddr_un sun;
710   size_t sz;
711
712   if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0)
713     die(EXIT_FAILURE, "socket: %s", strerror(errno));
714   socketaddr(&sun, &sz);
715   if (conn_init(&c->c, &sel, fd, (struct sockaddr *)&sun, sz, connected, c))
716     connerr(c);
717 }
718
719 static int timerflag;
720
721 static void conndrip(struct timeval *tv, void *hunoz)
722 {
723   conninfo *c, *cc;
724
725   timerflag = 0;
726   while (freeconns) {
727     c = freeconns; freeconns = 0;
728     while (c) { cc = c->next; conndoconnect(c); c = cc; }
729   }
730 }
731
732 static void connloop(void (*connected)(int, conninfo *))
733 {
734   int i;
735   conninfo **cc;
736   sel_timer t;
737   struct timeval tv;
738
739   connhook = connected;
740   for (i = 0, cc = &freeconns; i < MAXCONN; i++) {
741     *cc = &conns[i];
742     cc = &conns[i].next;
743   }
744   *cc = 0;
745
746   for (;;) {
747     if (freeconns && !timerflag) {
748       gettimeofday(&tv, 0);
749       TV_ADDL(&tv, &tv, 0, 10000);
750       sel_addtimer(&sel, &t, &tv, conndrip, 0);
751       timerflag = 1;
752     }
753     if (sel_select(&sel))
754       die(EXIT_FAILURE, "select: %s", strerror(errno));
755   }
756 }
757
758 /* --- Sinking (glug glug) --- */
759
760 static void close_sink(gobbler *g, int err, void *p)
761 {
762   static char baton[4] = "/-\\|";
763   static int pos = 0;
764   static int count = 0;
765   static int state = '?';
766
767   conninfo *c = p;
768
769   if (!err) {
770     if (state == '?')
771       state = isatty(STDOUT_FILENO) ? 'y' : 'n';
772     if (state == 'y') {
773       if (count) count--;
774       else {
775         putchar(baton[pos]);
776         putchar('\b');
777         fflush(stdout);
778         pos++;
779         if (pos >= sizeof(baton)) pos = 0;
780         count = 128;
781       }
782     }
783   }
784   connrecycle(c);
785 }
786
787 static void sink_connected(int fd, conninfo *c)
788 {
789   char dir = '<';
790
791   if (write(fd, &dir, 1) != 1) {
792     moan("write: %s (continuing)", strerror(errno));
793     close(fd);
794     connrecycle(c);
795     return;
796   }
797   make_gobbler(fd, close_sink, c);
798 }
799
800 static void sink(void) { connloop(sink_connected); }
801
802 /* --- Flooding --- */
803
804 static void close_flood(dribbler *d, int err, void *p)
805 {
806   conninfo *c = p;
807
808   if (err) moan("write: %s (continuing)\n", strerror(errno));
809   destroy_dribbler(d);
810   connrecycle(c);
811 }
812
813 static void flood_connected(int fd, conninfo *c)
814 {
815   static uint32 seq;
816
817   dribbler *d;
818   pkq_node *pn;
819   int i;
820
821 #define FLOOD_PKSZ 1024
822
823   pn = make_pkqnode(0, 1 + FLOOD_PKSZ);
824   pn->buf[0] = '>';
825   STORE32(pn->buf + 1, seq);
826   for (i = 4; i < FLOOD_PKSZ; i++)
827     pn->buf[i + 1] = i & 0xff;
828   seq++;
829   d = make_dribbler(fd, close_flood, c);
830   enqueue_dribble(d, pn);
831 }
832
833 static void flood(void) { connloop(flood_connected); }
834
835 /*----- Main code ---------------------------------------------------------*/
836
837 static void usage(FILE *fp) { pquis(fp, "Usage: $ [-fgps] SOCKET\n"); }
838
839 static void version(void)
840   { pquis(stdout, "$ (" PACKAGE " version " VERSION")\n"); }
841
842 static void help(void)
843 {
844   version();
845   fputc('\n', stdout);
846   usage(stdout);
847   puts("\n\
848 With no options, provides a SLIP interface for TrIPE.\n\
849 \n\
850 Options:\n\
851   -f, --flood   Send packets to TrIPE as fast as possible.\n\
852   -g, --get     Receive packet from TrIPE and write to stdout.\n\
853   -p, --put     Send packet on stdin to TrIPE.\n\
854   -s, --sink    Slurp packets out of TrIPE and display progress.");
855 }
856
857 int main(int argc, char *argv[])
858 {
859   int mode = 'd';
860   int i;
861
862   ego(argv[0]);
863   for (;;) {
864     const struct option opt[] = {
865       { "help",                 0,              0,              'h' },
866       { "version",              0,              0,              'v' },
867       { "put",                  0,              0,              'p' },
868       { "get",                  0,              0,              'g' },
869       { "flood",                0,              0,              'f' },
870       { "sink",                 0,              0,              's' },
871       { 0,                      0,              0,              0 }
872     };
873     i = mdwopt(argc, argv, "hvpgfs", opt, 0, 0, 0);
874     if (i < 0)
875       break;
876     switch (i) {
877       case 'h': help(); return (0);
878       case 'v': version(); return (0);
879       case 'p': case 'g': case 's': case 'f': mode = i; break;
880       default: usage(stderr); exit(EXIT_FAILURE); break;
881     }
882   }
883   if (argc - optind != 1) { usage(stderr); exit(EXIT_FAILURE); }
884   name = argv[optind];
885   signal(SIGPIPE, SIG_IGN);
886   switch (mode) {
887     case 'd': slipif(); break;
888     case 'p': put(); break;
889     case 'g': get(); break;
890     case 'f': flood(); break;
891     case 's': sink(); break;
892   }
893   return (0);
894 }
895
896 /*----- That's all, folks -------------------------------------------------*/