take off any trailing \r with telnet mode
[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 (*p == nl) {
234                                                 if (mp->inp == mp->data)
235                                                         *mp->inp++ = ' ';
236                                                 *mp->inp = 0;              /* zero terminate it, but don't include it in the length */
237                                                 dbgdump(DMSG, "QUEUE TEXT", mp->data, mp->inp-mp->data);
238                                                 cmsg_send(f->inq, mp, 0);
239                                                 f->in = mp = cmsg_new(MAXBUFL, f->sort, f);
240                                                 ++p;
241                                         } else {
242                                                 if (mp->inp < &mp->data[MAXBUFL])
243                                                         *mp->inp++ = *p++;
244                                                 else {
245                                                         mp->inp = mp->data;
246                                                 }
247                                         }
248                                 }
249                         }
250                         
251                         /* queue any echo text */
252                         if (f->echo) {
253                                 dbgdump(DMSG, "QUEUE ECHO TEXT", omp->data, omp->inp - omp->data);
254                                 cmsg_send(f->outq, omp, 0);
255                                 f->sp->flags |= SEL_OUTPUT;
256                         }
257                         
258                         break;
259
260                 case MSG:
261                         p = buf;
262                         while (r > 0 && p < &buf[r]) {
263
264                                 /* build up the size into the likely message length (yes I know it's a short) */
265                                 switch (mp->state) {
266                                 case 0:
267                                 case 1:
268                                         mp->state++;
269                                         break;
270                                 case 2:
271                                 case 3:
272                                         mp->size = (mp->size << 8) | (*p++ & 0xff);
273                                         if (mp->size > MAXBUFL)
274                                                 die("Message size too big from node (%d > %d)", mp->size, MAXBUFL);
275                                         mp->state++;
276                                         break;
277                                 default:
278                                         if (mp->inp - mp->data < mp->size) {
279                                                 *mp->inp++ = *p++;
280                                         } 
281                                         if (mp->inp - mp->data >= mp->size) {
282                                                 /* kick it upstairs */
283                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
284                                                 cmsg_send(f->inq, mp, 0);
285                                                 mp = f->in = cmsg_new(MAXBUFL, f->sort, f);
286                                         }
287                                 }
288                         }
289                         break;
290                         
291                 default:
292                         die("invalid sort (%d) in input handler", f->sort);
293                 }
294         }
295         
296         /* output modes */
297 lout:;
298         if (out) {
299                 int l, r;
300                 
301                 if (!f->out) {
302                         mp = f->out = cmsg_next(f->outq);
303                         if (!mp) {
304                                 sp->flags &= ~SEL_OUTPUT;
305                                 return 0;
306                         }
307                         mp->inp = mp->data;
308                 }
309                 l = mp->size - (mp->inp - mp->data);
310                 if (l > 0) {
311                         
312                         dbgdump(DBUF, "<-out", mp->inp, l);
313                         
314                         r = write(f->cnum, mp->inp, l);
315                         if (r < 0) {
316                                 switch (errno) {
317                                 case EINTR:
318                                 case EINPROGRESS:
319                                 case EAGAIN:
320                                         goto lend;
321                                 default:
322                                         if (f->sort == MSG)
323                                                 send_Z = 0;
324                                         ending++;
325                                         return;
326                                 }
327                         } else if (r > 0) {
328                                 mp->inp += r;
329                         }
330                 } else if (l < 0) 
331                         die("got negative length in handler on node");
332                 if (mp->inp - mp->data >= mp->size) {
333                         cmsg_callback(mp, 0);
334                         f->out = 0;
335 /*                      if (is_chain_empty(f->outq))
336                         sp->flags &= ~SEL_OUTPUT; */
337                 }
338         }
339 lend:;
340         return 0;
341 }
342
343 /*
344  * things to do with initialisation
345  */
346
347 void initargs(int argc, char *argv[])
348 {
349         int i, c, err = 0;
350
351         while ((c = getopt(argc, argv, "h:p:x:")) > 0) {
352                 switch (c) {
353                 case 'h':
354                         node_addr = optarg;
355                         break;
356                 case 'p':
357                         node_port = atoi(optarg);
358                         break;
359                 case 'x':
360                         dbginit("client");
361                         dbgset(atoi(optarg));
362                         break;
363                 default:
364                         ++err;
365                         goto lerr;
366                 }
367         }
368
369 lerr:
370         if (err) {
371                 die("usage: client [-x nn] <call>|login [local|telnet|ax25]");
372         }
373         
374         if (optind < argc) {
375                 call = strupper(argv[optind]);
376                 if (eq(call, "LOGIN"))
377                         die("login not implemented (yet)");
378                 ++optind;
379         }
380         if (!call)
381                 die("Must have at least a callsign (for now)");
382
383         if (optind < argc) {
384                 connsort = strlower(argv[optind]);
385                 if (eq(connsort, "telnet") || eq(connsort, "local")) {
386                         nl = '\n';
387                         echo = 1;
388                 } else if (eq(connsort, "ax25")) {
389                         nl = '\r';
390                         echo = 0;
391                 } else {
392                         die("2nd argument must be \"telnet\" or \"ax25\" or \"local\"");
393                 }
394         } else {
395                 connsort = "local";
396                 nl = '\n';
397                 echo = 1;
398         }
399 }
400
401 void connect_to_node()
402 {
403         struct hostent *hp, *gethostbyname();
404         struct sockaddr_in server;
405         int nodef;
406         sel_t *sp;
407                                 
408         if ((hp = gethostbyname(node_addr)) == 0) 
409                 die("Unknown host tcp host %s for printer", node_addr);
410
411         memset(&server, 0, sizeof server);
412         server.sin_family = AF_INET;
413         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
414         server.sin_port = htons(node_port);
415                                                 
416         nodef = socket(AF_INET, SOCK_STREAM, 0);
417         if (nodef < 0) 
418                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
419
420         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
421                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
422         }
423         node = fcb_new(nodef, MSG);
424         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
425         
426 }
427
428 /*
429  * things to do with going away
430  */
431
432 void term_timeout(int i)
433 {
434         /* none of this is going to be reused so don't bother cleaning up properly */
435         if (in && in->t_set)
436                 tcsetattr(0, TCSANOW, &in->t);
437         if (node) {
438                 close(node->cnum);
439         }
440         exit(i);
441 }
442
443 void terminate(int i)
444 {
445         if (send_Z && call) {
446                 send_msg(node, 'Z', "", 0);
447         }
448         
449         signal(SIGALRM, term_timeout);
450         alarm(10);
451         
452         while ((in && !is_chain_empty(in->outq)) ||
453                    (node && !is_chain_empty(node->outq))) {
454                 sel_run();
455         }
456         if (in && in->t_set)
457                 tcsetattr(0, TCSANOW, &in->t);
458         if (node) 
459                 close(node->cnum);
460         exit(i);
461 }
462
463 /*
464  * things to do with ongoing processing of inputs
465  */
466
467 void process_stdin()
468 {
469         cmsg_t *mp = cmsg_next(in->inq);
470         if (mp) {
471                 dbg(DMSG, "MSG size: %d", mp->size);
472         
473                 if (mp->size > 0 && mp->inp > mp->data) {
474                         send_msg(node, 'I', mp->data, mp->size);
475                 }
476                 cmsg_callback(mp, 0);
477         }
478 }
479
480 void process_node()
481 {
482         cmsg_t *mp = cmsg_next(node->inq);
483         if (mp) {
484                 dbg(DMSG, "MSG size: %d", mp->size);
485         
486                 if (mp->size > 0 && mp->inp > mp->data) {
487                         char *p = strchr(mp->data, '|');
488                         if (p)
489                                 p++;
490                         switch (mp->data[0]) {
491                         case 'Z':
492                                 send_Z = 0;
493                                 ending++;
494                                 return;
495                         case 'E':
496                                 if (isdigit(*p))
497                                         in->echo = *p - '0';
498                                 break;
499                         case 'D':
500                                 if (p) {
501                                         int l = mp->inp - (unsigned char *) p;
502                                         if (nl == '\n' && l >= 1 && p[l-1] == '\r')       /* kludge for GB7DXM */
503                                                 l--;
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