tidy up
[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 #define DBUF 1
43 #define DMSG 2
44
45 typedef struct 
46 {
47         int cnum;                                       /* the connection number */
48         int sort;                                       /* the type of connection either text or msg */
49         cmsg_t *in;                                     /* current input message being built up */
50         cmsg_t *out;                            /* current output message being sent */
51         reft *inq;                                      /* input queue */
52         reft *outq;                                     /* output queue */
53         sel_t *sp;                                      /* my select fcb address */
54         struct termios t;                       /* any termios associated with this cnum */
55         char echo;                                      /* echo characters back to this cnum */
56         char t_set;                                     /* the termios structure is valid */
57 } fcb_t;
58
59 char *node_addr = "localhost";  /* the node tcp address */
60 int node_port = 27754;                  /* the tcp port of the node at the above address */
61 char *call;                                             /* the caller's callsign */
62 char *connsort;                                 /* the type of connection */
63 fcb_t *in;                                              /* the fcb of 'stdin' that I shall use */
64 fcb_t *node;                                    /* the fcb of the msg system */
65 char nl = '\n';                                 /* line end character */
66 char ending = 0;                                /* set this to end the program */
67 char send_Z = 1;                                /* set a Z record to the node on termination */
68 char echo = 1;                                  /* echo characters on stdout from stdin */
69
70 void terminate(int);
71
72 /*
73  * utility routines - various
74  */
75
76 void die(char *s, ...)
77 {
78         char buf[2000];
79         
80         va_list ap;
81         va_start(ap, s);
82         vsprintf(buf, s, ap);
83         va_end(ap);
84         fprintf(stderr,"%s\n", buf);
85         terminate(-1);
86 }
87
88 char *strupper(char *s)
89 {
90         char *d = malloc(strlen(s)+1);
91         char *p = d;
92         
93         if (!d)
94                 die("out of room in strupper");
95         while (*p++ = toupper(*s++)) ;
96         return d;
97 }
98
99 char *strlower(char *s)
100 {
101         char *d = malloc(strlen(s)+1);
102         char *p = d;
103         
104         if (!d)
105                 die("out of room in strlower");
106         while (*p++ = tolower(*s++)) ;
107         return d;
108 }
109
110 int eq(char *a, char *b)
111 {
112         return (strcmp(a, b) == 0);
113 }
114
115 /*
116  * higher level send and receive routines
117  */
118
119 fcb_t *fcb_new(int cnum, int sort)
120 {
121         fcb_t *f = malloc(sizeof(fcb_t));
122         if (!f)
123                 die("no room in fcb_new");
124         memset (f, 0, sizeof(fcb_t));
125         f->cnum = cnum;
126         f->sort = sort;
127         f->inq = chain_new();
128         f->outq = chain_new();
129         return f;
130 }
131
132 void send_text(fcb_t *f, char *s, int l)
133 {
134         cmsg_t *mp;
135         mp = cmsg_new(l+1, f->sort, f);
136         memcpy(mp->inp, s, l);
137         mp->inp += l;
138         *mp->inp++ = nl;
139         cmsg_send(f->outq, mp, 0);
140         f->sp->flags |= SEL_OUTPUT;
141 }
142
143 void send_msg(fcb_t *f, char let, char *s, int l)
144 {
145         cmsg_t *mp;
146         int ln;
147         int myl = strlen(call)+2+l;
148
149         mp = cmsg_new(myl+4+1, f->sort, f);
150         ln = htonl(myl);
151         memcpy(mp->inp, &ln, 4);
152         mp->inp += 4;
153         *mp->inp++ = let;
154         strcpy(mp->inp, call);
155         mp->inp += strlen(call);
156         *mp->inp++ = '|';
157         if (l > 0) {
158                 memcpy(mp->inp, s, l);
159                 mp->inp += l;
160         }
161         *mp->inp = 0;
162         cmsg_send(f->outq, mp, 0);
163         f->sp->flags |= SEL_OUTPUT;
164 }
165
166 /*
167  * the callback (called by sel_run) that handles all the inputs and outputs
168  */
169
170 int fcb_handler(sel_t *sp, int in, int out, int err)
171 {
172         fcb_t *f = sp->fcb;
173         cmsg_t *mp, *omp;
174         
175         /* input modes */
176         if (in) {
177                 char *p, buf[MAXBUFL];
178                 int r;
179
180                 /* read what we have into a buffer */
181                 r = read(f->cnum, buf, MAXBUFL);
182                 if (r < 0) {
183                         switch (errno) {
184                         case EINTR:
185                         case EINPROGRESS:
186                         case EAGAIN:
187                                 goto lout;
188                         default:
189                                 if (f->sort == MSG)
190                                         send_Z = 0;
191                                 ending++;
192                                 return 0;
193                         }
194                 } else if (r == 0) {
195                         if (f->sort == MSG)
196                                 send_Z = 0;
197                         ending++;
198                         return 0;
199                 }
200
201                 dbgdump(DBUF, "in ->", buf, r);
202                 
203                 /* create a new message buffer if required */
204                 if (!f->in)
205                         f->in = cmsg_new(MAXBUFL+1, f->sort, f);
206                 mp = f->in;
207
208                 switch (f->sort) {
209                 case TEXT:
210                         p = buf;
211                         if (f->echo)
212                                 omp = cmsg_new(3*r+1, f->sort, f);
213                         while (r > 0 && p < &buf[r]) {
214
215                                 /* echo processing */
216                                 if (f->echo) {
217                                         switch (*p) {
218                                         case '\b':
219                                         case 0x7f:
220                                                 strcpy(omp->inp, "\b \b");
221                                                 omp->inp += strlen(omp->inp);
222                                                 break;
223                                         default:
224                                                 *omp->inp++ = *p;
225                                         }
226                                 }
227                                 
228                                 /* character processing */
229                                 switch (*p) {
230                                 case '\b':
231                                 case 0x7f:
232                                         if (mp->inp > mp->data)
233                                                 mp->inp--;
234                                         ++p;
235                                         break;
236                                 default:
237                                         if (nl == '\n' && *p == '\r') {   /* ignore \r in telnet mode (ugh) */
238                                                 p++;
239                                         } else if (*p == nl) {
240                                                 if (mp->inp == mp->data)
241                                                         *mp->inp++ = ' ';
242                                                 *mp->inp = 0;              /* zero terminate it, but don't include it in the length */
243                                                 dbgdump(DMSG, "QUEUE TEXT", mp->data, mp->inp-mp->data);
244                                                 cmsg_send(f->inq, mp, 0);
245                                                 f->in = mp = cmsg_new(MAXBUFL+1, f->sort, f);
246                                                 ++p;
247                                         } else {
248                                                 if (mp->inp < &mp->data[MAXBUFL])
249                                                         *mp->inp++ = *p++;
250                                                 else {
251                                                         mp->inp = mp->data;
252                                                 }
253                                         }
254                                 }
255                         }
256                         
257                         /* queue any echo text */
258                         if (f->echo) {
259                                 dbgdump(DMSG, "QUEUE ECHO TEXT", omp->data, omp->inp - omp->data);
260                                 cmsg_send(f->outq, omp, 0);
261                                 f->sp->flags |= SEL_OUTPUT;
262                         }
263                         
264                         break;
265
266                 case MSG:
267                         p = buf;
268                         while (r > 0 && p < &buf[r]) {
269
270                                 /* build up the size into the likely message length (yes I know it's a short) */
271                                 switch (mp->state) {
272                                 case 0:
273                                 case 1:
274                                         mp->state++;
275                                         break;
276                                 case 2:
277                                 case 3:
278                                         mp->size = (mp->size << 8) | (*p++ & 0xff);
279                                         if (mp->size > MAXBUFL)
280                                                 die("Message size too big from node (%d > %d)", mp->size, MAXBUFL);
281                                         mp->state++;
282                                         break;
283                                 default:
284                                         if (mp->inp - mp->data < mp->size) {
285                                                 *mp->inp++ = *p++;
286                                         } 
287                                         if (mp->inp - mp->data >= mp->size) {
288                                                 /* kick it upstairs */
289                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
290                                                 cmsg_send(f->inq, mp, 0);
291                                                 mp = f->in = cmsg_new(MAXBUFL+1, f->sort, f);
292                                         }
293                                 }
294                         }
295                         break;
296                         
297                 default:
298                         die("invalid sort (%d) in input handler", f->sort);
299                 }
300         }
301         
302         /* output modes */
303 lout:;
304         if (out) {
305                 int l, r;
306                 
307                 if (!f->out) {
308                         mp = f->out = cmsg_next(f->outq);
309                         if (!mp) {
310                                 sp->flags &= ~SEL_OUTPUT;
311                                 return 0;
312                         }
313                         mp->inp = mp->data;
314                 }
315                 l = mp->size - (mp->inp - mp->data);
316                 if (l > 0) {
317                         
318                         dbgdump(DBUF, "<-out", mp->inp, l);
319                         
320                         r = write(f->cnum, mp->inp, l);
321                         if (r < 0) {
322                                 switch (errno) {
323                                 case EINTR:
324                                 case EINPROGRESS:
325                                 case EAGAIN:
326                                         goto lend;
327                                 default:
328                                         if (f->sort == MSG)
329                                                 send_Z = 0;
330                                         ending++;
331                                         return;
332                                 }
333                         } else if (r > 0) {
334                                 mp->inp += r;
335                         }
336                 } else if (l < 0) 
337                         die("got negative length in handler on node");
338                 if (mp->inp - mp->data >= mp->size) {
339                         cmsg_callback(mp, 0);
340                         f->out = 0;
341                 }
342         }
343 lend:;
344         return 0;
345 }
346
347 /*
348  * things to do with initialisation
349  */
350
351 void initargs(int argc, char *argv[])
352 {
353         int i, c, err = 0;
354
355         while ((c = getopt(argc, argv, "h:p:x:")) > 0) {
356                 switch (c) {
357                 case 'h':
358                         node_addr = optarg;
359                         break;
360                 case 'p':
361                         node_port = atoi(optarg);
362                         break;
363                 case 'x':
364                         dbginit("client");
365                         dbgset(atoi(optarg));
366                         break;
367                 default:
368                         ++err;
369                         goto lerr;
370                 }
371         }
372
373 lerr:
374         if (err) {
375                 die("usage: client [-x n|-h<host>|-p<port>] <call>|login [local|telnet|ax25]");
376         }
377         
378         if (optind < argc) {
379                 call = strupper(argv[optind]);
380                 if (eq(call, "LOGIN"))
381                         die("login not implemented (yet)");
382                 ++optind;
383         }
384         if (!call)
385                 die("Must have at least a callsign (for now)");
386
387         if (optind < argc) {
388                 connsort = strlower(argv[optind]);
389                 if (eq(connsort, "telnet") || eq(connsort, "local")) {
390                         nl = '\n';
391                         echo = 1;
392                 } else if (eq(connsort, "ax25")) {
393                         nl = '\r';
394                         echo = 0;
395                 } else {
396                         die("2nd argument must be \"telnet\" or \"ax25\" or \"local\"");
397                 }
398         } else {
399                 connsort = "local";
400                 nl = '\n';
401                 echo = 1;
402         }
403 }
404
405 void connect_to_node()
406 {
407         struct hostent *hp, *gethostbyname();
408         struct sockaddr_in server;
409         int nodef;
410         sel_t *sp;
411                                 
412         if ((hp = gethostbyname(node_addr)) == 0) 
413                 die("Unknown host tcp host %s for printer", node_addr);
414
415         memset(&server, 0, sizeof server);
416         server.sin_family = AF_INET;
417         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
418         server.sin_port = htons(node_port);
419                                                 
420         nodef = socket(AF_INET, SOCK_STREAM, 0);
421         if (nodef < 0) 
422                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
423
424         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
425                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
426         }
427         node = fcb_new(nodef, MSG);
428         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
429         
430 }
431
432 /*
433  * things to do with going away
434  */
435
436 void term_timeout(int i)
437 {
438         /* none of this is going to be reused so don't bother cleaning up properly */
439         if (in && in->t_set)
440                 tcsetattr(0, TCSANOW, &in->t);
441         if (node) {
442                 close(node->cnum);
443         }
444         exit(i);
445 }
446
447 void terminate(int i)
448 {
449         if (send_Z && call) {
450                 send_msg(node, 'Z', "", 0);
451         }
452         
453         signal(SIGALRM, term_timeout);
454         alarm(10);
455         
456         while ((in && !is_chain_empty(in->outq)) ||
457                    (node && !is_chain_empty(node->outq))) {
458                 sel_run();
459         }
460         if (in && in->t_set)
461                 tcsetattr(0, TCSANOW, &in->t);
462         if (node) 
463                 close(node->cnum);
464         exit(i);
465 }
466
467 /*
468  * things to do with ongoing processing of inputs
469  */
470
471 void process_stdin()
472 {
473         cmsg_t *mp = cmsg_next(in->inq);
474         if (mp) {
475                 dbg(DMSG, "MSG size: %d", mp->size);
476         
477                 if (mp->size > 0 && mp->inp > mp->data) {
478                         send_msg(node, 'I', mp->data, mp->size);
479                 }
480                 cmsg_callback(mp, 0);
481         }
482 }
483
484 void process_node()
485 {
486         cmsg_t *mp = cmsg_next(node->inq);
487         if (mp) {
488                 dbg(DMSG, "MSG size: %d", mp->size);
489         
490                 if (mp->size > 0 && mp->inp > mp->data) {
491                         char *p = strchr(mp->data, '|');
492                         if (p)
493                                 p++;
494                         switch (mp->data[0]) {
495                         case 'Z':
496                                 send_Z = 0;
497                                 ending++;
498                                 return;
499                         case 'E':
500                                 if (isdigit(*p))
501                                         in->echo = *p - '0';
502                                 break;
503                         case 'D':
504                                 if (p) {
505                                         int l = mp->inp - (unsigned char *) p;
506                                         send_text(in, p, l);
507                                 }
508                                 break;
509                         default:
510                                 break;
511                         }
512                 }
513                 cmsg_callback(mp, 0);
514         }
515 }
516
517 /*
518  * the program itself....
519  */
520
521 main(int argc, char *argv[])
522 {
523         initargs(argc, argv);
524         sel_init(10, 0, 10000);
525
526         signal(SIGHUP, SIG_IGN);
527
528         signal(SIGINT, terminate);
529         signal(SIGQUIT, terminate);
530         signal(SIGTERM, terminate);
531 #ifdef SIGPWR
532         signal(SIGPWR, terminate);
533 #endif
534
535         /* connect up stdin, stdout and message system */
536         in = fcb_new(0, TEXT);
537         in->sp = sel_open(0, in, "STDIN", fcb_handler, TEXT, SEL_INPUT);
538         if (tcgetattr(0, &in->t) < 0) {
539                 echo = 0;
540                 in->t_set = 0;
541         } else {
542                 struct termios t = in->t;
543                 t.c_lflag &= ~(ECHO|ECHONL|ICANON);
544                 if (tcsetattr(0, TCSANOW, &t) < 0) 
545                         die("tcsetattr (%d)", errno);
546                 in->echo = echo;
547                 in->t_set = 1;
548         }
549         connect_to_node();
550
551         /* tell the cluster who I am */
552         send_msg(node, 'A', connsort, strlen(connsort));
553         
554         /* main processing loop */
555         while (!ending) {
556                 sel_run();
557                 if (!ending) {
558                         process_stdin();
559                         process_node();
560                 }
561         }
562         terminate(0);
563 }
564
565
566
567
568
569