1b3461233058527a141c159c0d856d04077105b2
[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, 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) {
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 int fcb_handler(sel_t *sp, int in, int out, int err)
167 {
168         fcb_t *f = sp->fcb;
169         cmsg_t *mp, *omp;
170         
171         /* input modes */
172         if (in) {
173                 char *p, buf[MAXBUFL];
174                 int r;
175
176                 /* read what we have into a buffer */
177                 r = read(f->cnum, buf, MAXBUFL);
178                 if (r < 0) {
179                         switch (errno) {
180                         case EINTR:
181                         case EINPROGRESS:
182                         case EAGAIN:
183                                 goto lout;
184                         default:
185                                 if (f->sort == MSG)
186                                         send_Z = 0;
187                                 ending++;
188                                 return 0;
189                         }
190                 } else if (r == 0) {
191                         if (f->sort == MSG)
192                                 send_Z = 0;
193                         ending++;
194                         return 0;
195                 }
196
197                 dbgdump(DBUF, "in ->", buf, r);
198                 
199                 /* create a new message buffer if required */
200                 if (!f->in)
201                         f->in = cmsg_new(MAXBUFL, f->sort, f);
202                 mp = f->in;
203
204                 switch (f->sort) {
205                 case TEXT:
206                         p = buf;
207                         if (f->echo)
208                                 omp = cmsg_new(3*r, f->sort, f);
209                         while (r > 0 && p < &buf[r]) {
210
211                                 /* echo processing */
212                                 if (f->echo) {
213                                         switch (*p) {
214                                         case '\b':
215                                         case 0x7f:
216                                                 strcpy(omp->inp, "\b \b");
217                                                 omp->inp += strlen(omp->inp);
218                                                 break;
219                                         default:
220                                                 *omp->inp++ = *p;
221                                         }
222                                 }
223                                 
224                                 /* character processing */
225                                 switch (*p) {
226                                 case '\b':
227                                 case 0x7f:
228                                         if (mp->inp > mp->data)
229                                                 mp->inp--;
230                                         ++p;
231                                         break;
232                                 default:
233                                         if (nl == '\n' && *p == '\r') {   /* ignore \r in telnet mode (ugh) */
234                                                 p++;
235                                         } else if (*p == nl) {
236                                                 if (mp->inp == mp->data)
237                                                         *mp->inp++ = ' ';
238                                                 *mp->inp = 0;              /* zero terminate it, but don't include it in the length */
239                                                 dbgdump(DMSG, "QUEUE TEXT", mp->data, mp->inp-mp->data);
240                                                 cmsg_send(f->inq, mp, 0);
241                                                 f->in = mp = cmsg_new(MAXBUFL, f->sort, f);
242                                                 ++p;
243                                         } else {
244                                                 if (mp->inp < &mp->data[MAXBUFL])
245                                                         *mp->inp++ = *p++;
246                                                 else {
247                                                         mp->inp = mp->data;
248                                                 }
249                                         }
250                                 }
251                         }
252                         
253                         /* queue any echo text */
254                         if (f->echo) {
255                                 dbgdump(DMSG, "QUEUE ECHO TEXT", omp->data, omp->inp - omp->data);
256                                 cmsg_send(f->outq, omp, 0);
257                                 f->sp->flags |= SEL_OUTPUT;
258                         }
259                         
260                         break;
261
262                 case MSG:
263                         p = buf;
264                         while (r > 0 && p < &buf[r]) {
265
266                                 /* build up the size into the likely message length (yes I know it's a short) */
267                                 switch (mp->state) {
268                                 case 0:
269                                 case 1:
270                                         mp->state++;
271                                         break;
272                                 case 2:
273                                 case 3:
274                                         mp->size = (mp->size << 8) | (*p++ & 0xff);
275                                         if (mp->size > MAXBUFL)
276                                                 die("Message size too big from node (%d > %d)", mp->size, MAXBUFL);
277                                         mp->state++;
278                                         break;
279                                 default:
280                                         if (mp->inp - mp->data < mp->size) {
281                                                 *mp->inp++ = *p++;
282                                         } 
283                                         if (mp->inp - mp->data >= mp->size) {
284                                                 /* kick it upstairs */
285                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
286                                                 cmsg_send(f->inq, mp, 0);
287                                                 mp = f->in = cmsg_new(MAXBUFL, f->sort, f);
288                                         }
289                                 }
290                         }
291                         break;
292                         
293                 default:
294                         die("invalid sort (%d) in input handler", f->sort);
295                 }
296         }
297         
298         /* output modes */
299 lout:;
300         if (out) {
301                 int l, r;
302                 
303                 if (!f->out) {
304                         mp = f->out = cmsg_next(f->outq);
305                         if (!mp) {
306                                 sp->flags &= ~SEL_OUTPUT;
307                                 return 0;
308                         }
309                         mp->inp = mp->data;
310                 }
311                 l = mp->size - (mp->inp - mp->data);
312                 if (l > 0) {
313                         
314                         dbgdump(DBUF, "<-out", mp->inp, l);
315                         
316                         r = write(f->cnum, mp->inp, l);
317                         if (r < 0) {
318                                 switch (errno) {
319                                 case EINTR:
320                                 case EINPROGRESS:
321                                 case EAGAIN:
322                                         goto lend;
323                                 default:
324                                         if (f->sort == MSG)
325                                                 send_Z = 0;
326                                         ending++;
327                                         return;
328                                 }
329                         } else if (r > 0) {
330                                 mp->inp += r;
331                         }
332                 } else if (l < 0) 
333                         die("got negative length in handler on node");
334                 if (mp->inp - mp->data >= mp->size) {
335                         cmsg_callback(mp, 0);
336                         f->out = 0;
337 /*                      if (is_chain_empty(f->outq))
338                         sp->flags &= ~SEL_OUTPUT; */
339                 }
340         }
341 lend:;
342         return 0;
343 }
344
345 /*
346  * things to do with initialisation
347  */
348
349 void initargs(int argc, char *argv[])
350 {
351         int i, c, err = 0;
352
353         while ((c = getopt(argc, argv, "h:p:x:")) > 0) {
354                 switch (c) {
355                 case 'h':
356                         node_addr = optarg;
357                         break;
358                 case 'p':
359                         node_port = atoi(optarg);
360                         break;
361                 case 'x':
362                         dbginit("client");
363                         dbgset(atoi(optarg));
364                         break;
365                 default:
366                         ++err;
367                         goto lerr;
368                 }
369         }
370
371 lerr:
372         if (err) {
373                 die("usage: client [-x nn] <call>|login [local|telnet|ax25]");
374         }
375         
376         if (optind < argc) {
377                 call = strupper(argv[optind]);
378                 if (eq(call, "LOGIN"))
379                         die("login not implemented (yet)");
380                 ++optind;
381         }
382         if (!call)
383                 die("Must have at least a callsign (for now)");
384
385         if (optind < argc) {
386                 connsort = strlower(argv[optind]);
387                 if (eq(connsort, "telnet") || eq(connsort, "local")) {
388                         nl = '\n';
389                         echo = 1;
390                 } else if (eq(connsort, "ax25")) {
391                         nl = '\r';
392                         echo = 0;
393                 } else {
394                         die("2nd argument must be \"telnet\" or \"ax25\" or \"local\"");
395                 }
396         } else {
397                 connsort = "local";
398                 nl = '\n';
399                 echo = 1;
400         }
401 }
402
403 void connect_to_node()
404 {
405         struct hostent *hp, *gethostbyname();
406         struct sockaddr_in server;
407         int nodef;
408         sel_t *sp;
409                                 
410         if ((hp = gethostbyname(node_addr)) == 0) 
411                 die("Unknown host tcp host %s for printer", node_addr);
412
413         memset(&server, 0, sizeof server);
414         server.sin_family = AF_INET;
415         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
416         server.sin_port = htons(node_port);
417                                                 
418         nodef = socket(AF_INET, SOCK_STREAM, 0);
419         if (nodef < 0) 
420                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
421
422         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
423                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
424         }
425         node = fcb_new(nodef, MSG);
426         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
427         
428 }
429
430 /*
431  * things to do with going away
432  */
433
434 void term_timeout(int i)
435 {
436         /* none of this is going to be reused so don't bother cleaning up properly */
437         if (in && in->t_set)
438                 tcsetattr(0, TCSANOW, &in->t);
439         if (node) {
440                 close(node->cnum);
441         }
442         exit(i);
443 }
444
445 void terminate(int i)
446 {
447         if (send_Z && call) {
448                 send_msg(node, 'Z', "", 0);
449         }
450         
451         signal(SIGALRM, term_timeout);
452         alarm(10);
453         
454         while ((in && !is_chain_empty(in->outq)) ||
455                    (node && !is_chain_empty(node->outq))) {
456                 sel_run();
457         }
458         if (in && in->t_set)
459                 tcsetattr(0, TCSANOW, &in->t);
460         if (node) 
461                 close(node->cnum);
462         exit(i);
463 }
464
465 /*
466  * things to do with ongoing processing of inputs
467  */
468
469 void process_stdin()
470 {
471         cmsg_t *mp = cmsg_next(in->inq);
472         if (mp) {
473                 dbg(DMSG, "MSG size: %d", mp->size);
474         
475                 if (mp->size > 0 && mp->inp > mp->data) {
476                         send_msg(node, 'I', mp->data, mp->size);
477                 }
478                 cmsg_callback(mp, 0);
479         }
480 }
481
482 void process_node()
483 {
484         cmsg_t *mp = cmsg_next(node->inq);
485         if (mp) {
486                 dbg(DMSG, "MSG size: %d", mp->size);
487         
488                 if (mp->size > 0 && mp->inp > mp->data) {
489                         char *p = strchr(mp->data, '|');
490                         if (p)
491                                 p++;
492                         switch (mp->data[0]) {
493                         case 'Z':
494                                 send_Z = 0;
495                                 ending++;
496                                 return;
497                         case 'E':
498                                 if (isdigit(*p))
499                                         in->echo = *p - '0';
500                                 break;
501                         case 'D':
502                                 if (p) {
503                                         int l = mp->inp - (unsigned char *) p;
504                                         send_text(in, p, l);
505                                 }
506                                 break;
507                         default:
508                                 break;
509                         }
510                 }
511                 cmsg_callback(mp, 0);
512         }
513 }
514
515 /*
516  * the program itself....
517  */
518
519 main(int argc, char *argv[])
520 {
521         initargs(argc, argv);
522         sel_init(10, 0, 10000);
523
524         signal(SIGHUP, SIG_IGN);
525
526         signal(SIGINT, terminate);
527         signal(SIGQUIT, terminate);
528         signal(SIGTERM, terminate);
529 #ifdef SIGPWR
530         signal(SIGPWR, terminate);
531 #endif
532
533         /* connect up stdin, stdout and message system */
534         in = fcb_new(0, TEXT);
535         in->sp = sel_open(0, in, "STDIN", fcb_handler, TEXT, SEL_INPUT);
536         if (tcgetattr(0, &in->t) < 0) {
537                 echo = 0;
538                 in->t_set = 0;
539         } else {
540                 struct termios t = in->t;
541                 t.c_lflag &= ~(ECHO|ECHONL|ICANON);
542                 if (tcsetattr(0, TCSANOW, &t) < 0) 
543                         die("tcsetattr (%d)", errno);
544                 in->echo = echo;
545                 in->t_set = 1;
546         }
547         connect_to_node();
548
549         /* tell the cluster who I am */
550         send_msg(node, 'A', connsort, strlen(connsort));
551         
552         /* main processing loop */
553         while (!ending) {
554                 sel_run();
555                 if (!ending) {
556                         process_stdin();
557                         process_node();
558                 }
559         }
560         terminate(0);
561 }
562
563
564
565
566
567