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