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