put in buffering
[spider.git] / src / client.c
1 /*
2  * C Client for the DX Spider cluster program
3  *
4  * Eventually this program will be a complete replacement
5  * for the perl version.
6  *
7  * This program provides the glue necessary to talk between
8  * an input (eg from telnet or ax25) and the perl DXSpider
9  * node.
10  *
11  * Currently, this program connects STDIN/STDOUT to the
12  * message system used by cluster.pl
13  *
14  * Copyright (c) 2000 Dirk Koopman G1TLH
15  *
16  * $Id$
17  */
18
19 #include <stdio.h>
20 #include <sys/time.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <ctype.h>
24 #include <stdlib.h>
25 #include <stdarg.h>
26 #include <netdb.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <errno.h>
30 #include <signal.h>
31 #include <string.h>
32 #include <termios.h>
33
34 #include "sel.h"
35 #include "cmsg.h"
36 #include "debug.h"
37
38 #define TEXT 1
39 #define MSG 2
40 #define MAXBUFL 1024
41
42 #ifndef MAXPATHLEN 
43 #define MAXPATHLEN 256
44 #endif
45
46 #define MAXPACLEN 236
47 #define DBUF 1
48 #define DMSG 2
49
50 typedef struct 
51 {
52         int cnum;                                       /* the connection number */
53         int sort;                                       /* the type of connection either text or msg */
54         cmsg_t *in;                                     /* current input message being built up */
55         cmsg_t *out;                            /* current output message being sent */
56         cmsg_t *obuf;                           /* current output being buffered */
57         reft *inq;                                      /* input queue */
58         reft *outq;                                     /* output queue */
59         sel_t *sp;                                      /* my select fcb address */
60         struct termios t;                       /* any termios associated with this cnum */
61         char echo;                                      /* echo characters back to this cnum */
62         char t_set;                                     /* the termios structure is valid */
63         char buffer_it;                         /* buffer outgoing packets for paclen */
64 } fcb_t;
65
66 char *node_addr = "localhost";  /* the node tcp address, can be overridden by DXSPIDER_HOST */
67 int node_port = 27754;                  /* the tcp port of the node at the above address can be overidden by DXSPIDER_PORT*/
68 char *call;                                             /* the caller's callsign */
69 char *connsort;                                 /* the type of connection */
70 fcb_t *in;                                              /* the fcb of 'stdin' that I shall use */
71 fcb_t *node;                                    /* the fcb of the msg system */
72 char nl = '\n';                                 /* line end character */
73 char ending = 0;                                /* set this to end the program */
74 char send_Z = 1;                                /* set a Z record to the node on termination */
75 char echo = 1;                                  /* echo characters on stdout from stdin */
76 char int_tabs = 0;                              /* interpret tabs -> spaces */
77 char *root = "/spider";         /* root of data tree, can be overridden by DXSPIDER_ROOT  */
78 int timeout = 60;                               /* default timeout for logins and things */
79 int paclen = 128;                               /* default buffer size for outgoing packets */
80 int tabsize = 8;                                /* default tabsize for text messages */
81
82 void terminate(int);
83
84 /*
85  * utility routines - various
86  */
87
88 void die(char *s, ...)
89 {
90         char buf[2000];
91         
92         va_list ap;
93         va_start(ap, s);
94         vsnprintf(buf, sizeof(buf)-1, s, ap);
95         va_end(ap);
96         fprintf(stderr,"%s\n", buf);
97         terminate(-1);
98 }
99
100 char *strupper(char *s)
101 {
102         char *d = malloc(strlen(s)+1);
103         char *p = d;
104         
105         if (!d)
106                 die("out of room in strupper");
107         while (*p++ = toupper(*s++)) ;
108         return d;
109 }
110
111 char *strlower(char *s)
112 {
113         char *d = malloc(strlen(s)+1);
114         char *p = d;
115         
116         if (!d)
117                 die("out of room in strlower");
118         while (*p++ = tolower(*s++)) ;
119         return d;
120 }
121
122 int eq(char *a, char *b)
123 {
124         return (strcmp(a, b) == 0);
125 }
126
127 int xopen(char *dir, char *name, int mode)
128 {
129         char fn[MAXPATHLEN+1];
130         snprintf(fn, MAXPATHLEN, "%s/%s/%s", root, dir, name);
131         return open(fn, mode);
132 }
133
134 int iscallsign(char *s)
135 {
136         char *p, ch, state = 0;
137         
138         ch = *p;
139         while (ch) {
140                 switch (state) {
141                 case 0:                                 /* initial numerics */
142                         if (isalpha(ch)) {
143                                 if (p == s) 
144                                         state = 10;
145                                 else
146                                         state = 1;
147                         } if (isdigit(ch)) {
148                                 ;
149                         } else 
150                                 goto lend;
151                         break;
152                 case 1:                                 /* letter(s) */
153                 case 10:                                /* had an initial character */
154                         if (isdigit(ch))
155                                 state = 11;
156                         
157                 }
158                 ch = ++*p;
159         }
160
161 lend:
162         return 1;
163 }
164
165 /*
166  * higher level send and receive routines
167  */
168
169 fcb_t *fcb_new(int cnum, int sort)
170 {
171         fcb_t *f = malloc(sizeof(fcb_t));
172         if (!f)
173                 die("no room in fcb_new");
174         memset (f, 0, sizeof(fcb_t));
175         f->cnum = cnum;
176         f->sort = sort;
177         f->inq = chain_new();
178         f->outq = chain_new();
179         return f;
180 }
181
182 void flush_text(fcb_t *f)
183 {
184         if (f->obuf) {
185                 cmsg_send(f->outq, f->obuf, 0);
186                 f->sp->flags |= SEL_OUTPUT;
187                 f->obuf = 0;
188         }
189 }
190
191 void send_text(fcb_t *f, char *s, int l)
192 {
193         cmsg_t *mp;
194         char *p;
195         
196         if (f->buffer_it && f->obuf) {
197                 mp = f->obuf;
198         } else {
199                 f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
200         }
201
202         for (p = s; p < s+l; ) {
203                 if (mp->inp >= mp->data + paclen) {
204                         flush_text(f);
205                         f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
206                 }
207                 *mp->inp++ = *p++;
208         }
209         if (mp->inp >= mp->data + paclen) {
210                 flush_text(f);
211                 f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
212         }
213         *mp->inp++ = nl;
214         if (!f->buffer_it)
215                 flush_text(f);
216 }
217
218 void send_msg(fcb_t *f, char let, char *s, int l)
219 {
220         cmsg_t *mp;
221         int ln;
222         int myl = strlen(call)+2+l;
223
224         mp = cmsg_new(myl+4+1, f->sort, f);
225         ln = htonl(myl);
226         memcpy(mp->inp, &ln, 4);
227         mp->inp += 4;
228         *mp->inp++ = let;
229         strcpy(mp->inp, call);
230         mp->inp += strlen(call);
231         *mp->inp++ = '|';
232         if (l > 0) {
233                 memcpy(mp->inp, s, l);
234                 mp->inp += l;
235         }
236         *mp->inp = 0;
237         cmsg_send(f->outq, mp, 0);
238         f->sp->flags |= SEL_OUTPUT;
239 }
240
241 /*
242  * the callback (called by sel_run) that handles all the inputs and outputs
243  */
244
245 int fcb_handler(sel_t *sp, int in, int out, int err)
246 {
247         fcb_t *f = sp->fcb;
248         cmsg_t *mp, *omp;
249         
250         /* input modes */
251         if (in) {
252                 char *p, buf[MAXBUFL];
253                 int r;
254
255                 /* read what we have into a buffer */
256                 r = read(f->cnum, buf, MAXBUFL);
257                 if (r < 0) {
258                         switch (errno) {
259                         case EINTR:
260                         case EINPROGRESS:
261                         case EAGAIN:
262                                 goto lout;
263                         default:
264                                 if (f->sort == MSG)
265                                         send_Z = 0;
266                                 ending++;
267                                 return 0;
268                         }
269                 } else if (r == 0) {
270                         if (f->sort == MSG)
271                                 send_Z = 0;
272                         ending++;
273                         return 0;
274                 }
275
276                 dbgdump(DBUF, "in ->", buf, r);
277                 
278                 /* create a new message buffer if required */
279                 if (!f->in)
280                         f->in = cmsg_new(MAXBUFL+1, f->sort, f);
281                 mp = f->in;
282
283                 switch (f->sort) {
284                 case TEXT:
285                         p = buf;
286                         if (f->echo)
287                                 omp = cmsg_new(3*r+1, f->sort, f);
288                         while (r > 0 && p < &buf[r]) {
289
290                                 /* echo processing */
291                                 if (f->echo) {
292                                         switch (*p) {
293                                         case '\b':
294                                         case 0x7f:
295                                                 strcpy(omp->inp, "\b \b");
296                                                 omp->inp += strlen(omp->inp);
297                                                 break;
298                                         default:
299                                                 *omp->inp++ = *p;
300                                         }
301                                 }
302                                 
303                                 /* character processing */
304                                 switch (*p) {
305                                 case '\t':
306                                         if (int_tabs) {
307                                                 memset(mp->inp, ' ', tabsize);
308                                                 mp->inp += tabsize;
309                                                 ++p;
310                                         } else {
311                                                 *mp->inp++ = *p++;
312                                         }
313                                         break;
314                                 case '\b':
315                                 case 0x7f:
316                                         if (mp->inp > mp->data)
317                                                 mp->inp--;
318                                         ++p;
319                                         break;
320                                 default:
321                                         if (nl == '\n' && *p == '\r') {   /* ignore \r in telnet mode (ugh) */
322                                                 p++;
323                                         } else if (*p == nl) {
324                                                 if (mp->inp == mp->data)
325                                                         *mp->inp++ = ' ';
326                                                 *mp->inp = 0;              /* zero terminate it, but don't include it in the length */
327                                                 dbgdump(DMSG, "QUEUE TEXT", mp->data, mp->inp-mp->data);
328                                                 cmsg_send(f->inq, mp, 0);
329                                                 f->in = mp = cmsg_new(MAXBUFL+1, f->sort, f);
330                                                 ++p;
331                                         } else {
332                                                 if (mp->inp < &mp->data[MAXBUFL-8])
333                                                         *mp->inp++ = *p++;
334                                                 else {
335                                                         mp->inp = mp->data;
336                                                 }
337                                         }
338                                 }
339                         }
340                         
341                         /* queue any echo text */
342                         if (f->echo) {
343                                 dbgdump(DMSG, "QUEUE ECHO TEXT", omp->data, omp->inp - omp->data);
344                                 cmsg_send(f->outq, omp, 0);
345                                 f->sp->flags |= SEL_OUTPUT;
346                         }
347                         
348                         break;
349
350                 case MSG:
351                         p = buf;
352                         while (r > 0 && p < &buf[r]) {
353
354                                 /* build up the size into the likely message length (yes I know it's a short) */
355                                 switch (mp->state) {
356                                 case 0:
357                                 case 1:
358                                         mp->state++;
359                                         break;
360                                 case 2:
361                                 case 3:
362                                         mp->size = (mp->size << 8) | (*p++ & 0xff);
363                                         if (mp->size > MAXBUFL)
364                                                 die("Message size too big from node (%d > %d)", mp->size, MAXBUFL);
365                                         mp->state++;
366                                         break;
367                                 default:
368                                         if (mp->inp - mp->data < mp->size) {
369                                                 *mp->inp++ = *p++;
370                                         } 
371                                         if (mp->inp - mp->data >= mp->size) {
372                                                 /* kick it upstairs */
373                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
374                                                 cmsg_send(f->inq, mp, 0);
375                                                 mp = f->in = cmsg_new(MAXBUFL+1, f->sort, f);
376                                         }
377                                 }
378                         }
379                         break;
380                         
381                 default:
382                         die("invalid sort (%d) in input handler", f->sort);
383                 }
384         }
385         
386         /* output modes */
387 lout:;
388         if (out) {
389                 int l, r;
390                 
391                 if (!f->out) {
392                         mp = f->out = cmsg_next(f->outq);
393                         if (!mp) {
394                                 sp->flags &= ~SEL_OUTPUT;
395                                 return 0;
396                         }
397                         mp->inp = mp->data;
398                 }
399                 l = mp->size - (mp->inp - mp->data);
400                 if (l > 0) {
401                         
402                         dbgdump(DBUF, "<-out", mp->inp, l);
403                         
404                         r = write(f->cnum, mp->inp, l);
405                         if (r < 0) {
406                                 switch (errno) {
407                                 case EINTR:
408                                 case EINPROGRESS:
409                                 case EAGAIN:
410                                         goto lend;
411                                 default:
412                                         if (f->sort == MSG)
413                                                 send_Z = 0;
414                                         ending++;
415                                         return;
416                                 }
417                         } else if (r > 0) {
418                                 mp->inp += r;
419                         }
420                 } else if (l < 0) 
421                         die("got negative length in handler on node");
422                 if (mp->inp - mp->data >= mp->size) {
423                         cmsg_callback(mp, 0);
424                         f->out = 0;
425                 }
426         }
427 lend:;
428         return 0;
429 }
430
431 /*
432  * things to do with initialisation
433  */
434
435 void initargs(int argc, char *argv[])
436 {
437         int i, c, err = 0;
438
439         while ((c = getopt(argc, argv, "h:p:x:")) > 0) {
440                 switch (c) {
441                 case 'h':
442                         node_addr = optarg;
443                         break;
444                 case 'l':
445                         paclen = atoi(optarg);
446                         if (paclen < 80)
447                                 paclen = 80;
448                         if (paclen > MAXPACLEN)
449                                 paclen = MAXPACLEN;
450                         break;
451                 case 'p':
452                         node_port = atoi(optarg);
453                         break;
454                 case 'x':
455                         dbginit("client");
456                         dbgset(atoi(optarg));
457                         break;
458                 default:
459                         ++err;
460                         goto lerr;
461                 }
462         }
463
464 lerr:
465         if (err) {
466                 die("usage: client [-x n|-h<host>|-p<port>|-l<paclen>] <call>|login [local|telnet|ax25]");
467         }
468         
469         if (optind < argc) {
470                 call = strupper(argv[optind]);
471                 ++optind;
472         }
473         if (!call)
474                 die("Must have at least a callsign (for now)");
475
476         if (optind < argc) {
477                 connsort = strlower(argv[optind]);
478                 if (eq(connsort, "telnet") || eq(connsort, "local")) {
479                         nl = '\n';
480                         echo = 1;
481                 } else if (eq(connsort, "ax25")) {
482                         nl = '\r';
483                         echo = 0;
484                 } else {
485                         die("2nd argument must be \"telnet\" or \"ax25\" or \"local\"");
486                 }
487         } else {
488                 connsort = "local";
489                 nl = '\n';
490                 echo = 1;
491         }
492 }
493
494 void connect_to_node()
495 {
496         struct hostent *hp, *gethostbyname();
497         struct sockaddr_in server;
498         int nodef;
499         sel_t *sp;
500                                 
501         if ((hp = gethostbyname(node_addr)) == 0) 
502                 die("Unknown host tcp host %s for printer", node_addr);
503
504         memset(&server, 0, sizeof server);
505         server.sin_family = AF_INET;
506         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
507         server.sin_port = htons(node_port);
508                                                 
509         nodef = socket(AF_INET, SOCK_STREAM, 0);
510         if (nodef < 0) 
511                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
512
513         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
514                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
515         }
516         node = fcb_new(nodef, MSG);
517         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
518         
519 }
520
521 /*
522  * things to do with going away
523  */
524
525 void term_timeout(int i)
526 {
527         /* none of this is going to be reused so don't bother cleaning up properly */
528         if (in && in->t_set)
529                 tcsetattr(0, TCSANOW, &in->t);
530         if (node) {
531                 close(node->cnum);
532         }
533         exit(i);
534 }
535
536 void terminate(int i)
537 {
538         if (send_Z && call) {
539                 send_msg(node, 'Z', "", 0);
540         }
541         
542         signal(SIGALRM, term_timeout);
543         alarm(10);
544         
545         while ((in && !is_chain_empty(in->outq)) ||
546                    (node && !is_chain_empty(node->outq))) {
547                 sel_run();
548         }
549         if (in && in->t_set)
550                 tcsetattr(0, TCSADRAIN, &in->t);
551         if (node) 
552                 close(node->cnum);
553         exit(i);
554 }
555
556 void login_timeout(int i)
557 {
558         write(0, "Timed Out", 10);
559         write(0, &nl, 1);
560         sel_run();                                      /* force a coordination */
561         if (in && in->t_set)
562                 tcsetattr(0, TCSANOW, &in->t);
563         exit(i);
564 }
565
566 /*
567  * things to do with ongoing processing of inputs
568  */
569
570 void process_stdin()
571 {
572         cmsg_t *mp = cmsg_next(in->inq);
573         if (mp) {
574                 dbg(DMSG, "MSG size: %d", mp->size);
575         
576                 if (mp->size > 0 && mp->inp > mp->data) {
577                         send_msg(node, 'I', mp->data, mp->size);
578                 }
579                 cmsg_callback(mp, 0);
580         }
581 }
582
583 void process_node()
584 {
585         cmsg_t *mp = cmsg_next(node->inq);
586         if (mp) {
587                 dbg(DMSG, "MSG size: %d", mp->size);
588         
589                 if (mp->size > 0 && mp->inp > mp->data) {
590                         char *p = strchr(mp->data, '|');
591                         if (p)
592                                 p++;
593                         switch (mp->data[0]) {
594                         case 'Z':
595                                 send_Z = 0;
596                                 ending++;
597                                 return;
598                         case 'E':
599                                 if (isdigit(*p))
600                                         in->echo = *p - '0';
601                                 break;
602                         case 'B':
603                                 if (isdigit(*p))
604                                         in->buffer_it = *p - '0';
605                                 break;
606                         case 'D':
607                                 if (p) {
608                                         int l = mp->inp - (unsigned char *) p;
609                                         send_text(in, p, l);
610                                 }
611                                 break;
612                         default:
613                                 break;
614                         }
615                 }
616                 cmsg_callback(mp, 0);
617         } else {
618                 flush_text(in);
619         }
620 }
621
622 /*
623  * the program itself....
624  */
625
626 main(int argc, char *argv[])
627 {
628         /* set up environment */
629         {
630                 char *p = getenv("DXSPIDER_ROOT");
631                 if (p)
632                         root = p;
633                 p = getenv("DXSPIDER_HOST");
634                 if (p)
635                         node_addr = p;
636                 p = getenv("DXSPIDER_PORT");
637                 if (p)
638                         node_port = atoi(p);
639                 p = getenv("DXSPIDER_PACLEN");
640                 if (p) {
641                         paclen = atoi(p);
642                         if (paclen < 80)
643                                 paclen = 80;
644                         if (paclen > MAXPACLEN)
645                                 paclen = MAXPACLEN;
646                 }
647         }
648         
649         /* get program arguments, initialise stuff */
650         initargs(argc, argv);
651         sel_init(10, 0, 10000);
652
653         /* trap signals */
654         signal(SIGHUP, SIG_IGN);
655         signal(SIGINT, terminate);
656         signal(SIGQUIT, terminate);
657         signal(SIGTERM, terminate);
658 #ifdef SIGPWR
659         signal(SIGPWR, terminate);
660 #endif
661
662         /* is this a login? */
663         if (eq(call, "LOGIN")) {
664                 char buf[MAXPACLEN+1];
665                 int r;
666                 int f = xopen("data", "issue", 0);
667                 if (f > 0) {
668                         while ((r = read(f, buf, paclen)) > 0) {
669                                 if (nl != '\n') {
670                                         char *p;
671                                         for (p = buf; p < &buf[r]; ++p) {
672                                                 if (*p == '\n')
673                                                         *p = nl;
674                                         }
675                                 }
676                                 write(0, buf, r);
677                         }
678                         close(f);
679                 }
680                 signal(SIGALRM, login_timeout);
681                 alarm(timeout);
682                 write(0, "login: ", 7);
683                 r = read(0, buf, 20);
684                 if (r <= 0)
685                         die("No login or error (%d)", errno);
686                 signal(SIGALRM, SIG_IGN);
687                 alarm(0);
688                 while (r > 0) {
689                         if (buf[r-1] == ' ' || buf[r-1] == '\r' || buf[r-1] == '\n')
690                                 --r;
691                         else
692                                 break;
693                 }
694                 buf[r] = 0;
695                 call = strupper(buf);
696                 if (!iscallsign(call)) {
697                         die("Sorry, %s isn't a valid callsign", buf);
698                 }
699         }
700         
701         /* connect up stdin */
702         in = fcb_new(0, TEXT);
703         in->sp = sel_open(0, in, "STDIN", fcb_handler, TEXT, SEL_INPUT);
704         if (tcgetattr(0, &in->t) < 0) {
705                 echo = 0;
706                 in->t_set = 0;
707         } else {
708                 struct termios t = in->t;
709                 t.c_lflag &= ~(ECHO|ECHONL|ICANON);
710                 if (tcsetattr(0, TCSANOW, &t) < 0) 
711                         die("tcsetattr (%d)", errno);
712                 in->echo = echo;
713                 in->t_set = 1;
714         }
715         in->buffer_it = 1;
716
717         /* connect up node */
718         connect_to_node();
719
720         /* tell the cluster who I am */
721         send_msg(node, 'A', connsort, strlen(connsort));
722         
723         /* main processing loop */
724         while (!ending) {
725                 sel_run();
726                 if (!ending) {
727                         process_stdin();
728                         process_node();
729                 }
730         }
731         terminate(0);
732 }
733
734
735
736
737
738