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