added code to cope with 'telnet' connections
[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                                         mp->state++;
274                                         break;
275                                 default:
276                                         if (mp->inp - mp->data < mp->size) {
277                                                 *mp->inp++ = *p++;
278                                         } 
279                                         if (mp->inp - mp->data >= mp->size) {
280                                                 /* kick it upstairs */
281                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
282                                                 cmsg_send(f->inq, mp, 0);
283                                                 mp = f->in = cmsg_new(MAXBUFL, f->sort, f);
284                                         }
285                                 }
286                         }
287                         break;
288                         
289                 default:
290                         die("invalid sort (%d) in input handler", f->sort);
291                 }
292         }
293         
294         /* output modes */
295 lout:;
296         if (out) {
297                 int l, r;
298                 
299                 if (!f->out) {
300                         mp = f->out = cmsg_next(f->outq);
301                         if (!mp) {
302                                 sp->flags &= ~SEL_OUTPUT;
303                                 return 0;
304                         }
305                         mp->inp = mp->data;
306                 }
307                 l = mp->size - (mp->inp - mp->data);
308                 if (l > 0) {
309                         
310                         dbgdump(DBUF, "<-out", mp->inp, l);
311                         
312                         r = write(f->cnum, mp->inp, l);
313                         if (r < 0) {
314                                 switch (errno) {
315                                 case EINTR:
316                                 case EINPROGRESS:
317                                 case EAGAIN:
318                                         goto lend;
319                                 default:
320                                         if (f->sort == MSG)
321                                                 send_Z = 0;
322                                         ending++;
323                                         return;
324                                 }
325                         } else if (r > 0) {
326                                 mp->inp += r;
327                         }
328                 } else if (l < 0) 
329                         die("got negative length in handler on node");
330                 if (mp->inp - mp->data >= mp->size) {
331                         cmsg_callback(mp, 0);
332                         f->out = 0;
333 /*                      if (is_chain_empty(f->outq))
334                         sp->flags &= ~SEL_OUTPUT; */
335                 }
336         }
337 lend:;
338         return 0;
339 }
340
341 /*
342  * things to do with initialisation
343  */
344
345 void initargs(int argc, char *argv[])
346 {
347         int i, c, err = 0;
348
349         while ((c = getopt(argc, argv, "h:x:")) > 0) {
350                 switch (c) {
351                 case 'h':
352                         node_addr = optarg;
353                         break;
354                 case 'x':
355                         dbginit("client");
356                         dbgset(atoi(optarg));
357                         break;
358                 default:
359                         ++err;
360                         goto lerr;
361                 }
362         }
363
364 lerr:
365         if (err) {
366                 die("usage: client [-x nn] <call>|login [local|telnet|ax25]");
367         }
368         
369         if (optind < argc) {
370                 call = strupper(argv[optind]);
371                 if (eq(call, "LOGIN"))
372                         die("login not implemented (yet)");
373                 ++optind;
374         }
375         if (!call)
376                 die("Must have at least a callsign (for now)");
377
378         if (optind < argc) {
379                 connsort = strlower(argv[optind]);
380                 if (eq(connsort, "telnet") || eq(connsort, "local")) {
381                         nl = '\n';
382                         echo = 1;
383                 } else if (eq(connsort, "ax25")) {
384                         nl = '\r';
385                         echo = 0;
386                 } else {
387                         die("2nd argument must be \"telnet\" or \"ax25\" or \"local\"");
388                 }
389         } else {
390                 connsort = "local";
391                 nl = '\n';
392                 echo = 1;
393         }
394 }
395
396 void connect_to_node()
397 {
398         struct hostent *hp, *gethostbyname();
399         struct sockaddr_in server;
400         int nodef;
401         sel_t *sp;
402                                 
403         if ((hp = gethostbyname(node_addr)) == 0) 
404                 die("Unknown host tcp host %s for printer", node_addr);
405
406         memset(&server, 0, sizeof server);
407         server.sin_family = AF_INET;
408         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
409         server.sin_port = htons(node_port);
410                                                 
411         nodef = socket(AF_INET, SOCK_STREAM, 0);
412         if (nodef < 0) 
413                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
414
415         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
416                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
417         }
418         node = fcb_new(nodef, MSG);
419         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
420         
421 }
422
423 /*
424  * things to do with going away
425  */
426
427 void term_timeout(int i)
428 {
429         /* none of this is going to be reused so don't bother cleaning up properly */
430         if (in && in->t_set)
431                 tcsetattr(0, TCSANOW, &in->t);
432         if (node) {
433                 close(node->cnum);
434         }
435         exit(i);
436 }
437
438 void terminate(int i)
439 {
440         if (send_Z && call) {
441                 send_msg(node, 'Z', "", 0);
442         }
443         
444         signal(SIGALRM, term_timeout);
445         alarm(10);
446         
447         while ((in && !is_chain_empty(in->outq)) ||
448                    (node && !is_chain_empty(node->outq))) {
449                 sel_run();
450         }
451         if (in && in->t_set)
452                 tcsetattr(0, TCSANOW, &in->t);
453         if (node) 
454                 close(node->cnum);
455         exit(i);
456 }
457
458 /*
459  * things to do with ongoing processing of inputs
460  */
461
462 void process_stdin()
463 {
464         cmsg_t *mp = cmsg_next(in->inq);
465         if (mp) {
466                 dbg(DMSG, "MSG size: %d", mp->size);
467         
468                 if (mp->size > 0 && mp->inp > mp->data) {
469                         send_msg(node, 'I', mp->data, mp->size);
470                 }
471                 cmsg_callback(mp, 0);
472         }
473 }
474
475 void process_node()
476 {
477         cmsg_t *mp = cmsg_next(node->inq);
478         if (mp) {
479                 dbg(DMSG, "MSG size: %d", mp->size);
480         
481                 if (mp->size > 0 && mp->inp > mp->data) {
482                         char *p = strchr(mp->data, '|');
483                         if (p)
484                                 p++;
485                         switch (mp->data[0]) {
486                         case 'Z':
487                                 send_Z = 0;
488                                 ending++;
489                                 return;
490                         case 'E':
491                                 if (isdigit(*p))
492                                         in->echo = *p - '0';
493                                 break;
494                         case 'D':
495                                 if (p) {
496                                         int l = mp->inp - (unsigned char *) p;
497                                         send_text(in, p, l);
498                         }
499                                 break;
500                         default:
501                                 break;
502                         }
503                 }
504                 cmsg_callback(mp, 0);
505         }
506 }
507
508 /*
509  * the program itself....
510  */
511
512 main(int argc, char *argv[])
513 {
514         initargs(argc, argv);
515         sel_init(10, 0, 10000);
516
517         signal(SIGHUP, SIG_IGN);
518
519         signal(SIGINT, terminate);
520         signal(SIGQUIT, terminate);
521         signal(SIGTERM, terminate);
522         signal(SIGPWR, terminate);
523
524         /* connect up stdin, stdout and message system */
525         in = fcb_new(0, TEXT);
526         in->sp = sel_open(0, in, "STDIN", fcb_handler, TEXT, SEL_INPUT);
527         if (tcgetattr(0, &in->t) < 0) {
528                 echo = 0;
529                 in->t_set = 0;
530         } else {
531                 struct termios t = in->t;
532                 t.c_lflag &= ~(ECHO|ECHONL|ICANON);
533                 if (tcsetattr(0, TCSANOW, &t) < 0) 
534                         die("tcsetattr (%d)", errno);
535                 in->echo = echo;
536                 in->t_set = 1;
537         }
538         connect_to_node();
539
540         /* tell the cluster who I am */
541         send_msg(node, 'A', connsort, strlen(connsort));
542         
543         /* main processing loop */
544         while (!ending) {
545                 sel_run();
546                 if (!ending) {
547                         process_stdin();
548                         process_node();
549                 }
550         }
551         terminate(0);
552 }
553
554
555
556
557
558