69a8aaebd099eb180452eb601098a2d55462645c
[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 <sys/types.h>
20 #include <stdio.h>
21 #include <sys/time.h>
22 #include <sys/wait.h>
23 #include <unistd.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <netdb.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <errno.h>
31 #include <signal.h>
32 #include <string.h>
33 #include <termios.h>
34 #include <regex.h>
35
36 #include "sel.h"
37 #include "cmsg.h"
38 #include "chain.h"
39 #include "debug.h"
40
41 #define TEXT 1
42 #define MSG 2
43 #define MAXBUFL 1024
44
45 #ifndef MAXPATHLEN 
46 #define MAXPATHLEN 256
47 #endif
48
49 #define DEFPACLEN 236
50 #define MAXPACLEN 236
51 #define MAXCALLSIGN 9
52
53 #define DBUF 1
54 #define DMSG 2
55 #define DSTS 4
56
57 #define UC unsigned char
58
59 typedef struct 
60 {
61         int cnum;                                       /* the connection number */
62         int sort;                                       /* the type of connection either text or msg */
63         cmsg_t *in;                                     /* current input message being built up */
64         cmsg_t *out;                            /* current output message being sent */
65         cmsg_t *obuf;                           /* current output being buffered */
66         reft *inq;                                      /* input queue */
67         reft *outq;                                     /* output queue */
68         sel_t *sp;                                      /* my select fcb address */
69         struct termios t;                       /* any termios associated with this cnum */
70         char echo;                                      /* echo characters back to this cnum */
71         char t_set;                                     /* the termios structure is valid */
72         char buffer_it;                         /* buffer outgoing packets for paclen */
73 } fcb_t;
74
75 typedef struct 
76 {
77         char *in;
78         regex_t *regex;
79 } myregex_t;
80
81
82 char *node_addr = "localhost";  /* the node tcp address, can be overridden by DXSPIDER_HOST */
83 int node_port = 27754;                  /* the tcp port of the node at the above address can be overidden by DXSPIDER_PORT*/
84 char *call;                                             /* the caller's callsign */
85 char *connsort;                                 /* the type of connection */
86 fcb_t *in;                                              /* the fcb of 'stdin' that I shall use */
87 fcb_t *node;                                    /* the fcb of the msg system */
88 char nl = '\n';                                 /* line end character */
89 char mode = 1;                  /* 0 - ax25, 1 - normal telnet, 2 - nlonly telnet */
90 char ending = 0;                                /* set this to end the program */
91 char echo = 1;                                  /* echo characters on stdout from stdin */
92 char int_tabs = 0;                              /* interpret tabs -> spaces */
93 char *root = "/spider";         /* root of data tree, can be overridden by DXSPIDER_ROOT  */
94 int timeout = 60;                               /* default timeout for logins and things */
95 int paclen = DEFPACLEN;                 /* default buffer size for outgoing packets */
96 int tabsize = 8;                                /* default tabsize for text messages */
97 char *connsort = "local";               /* the connection variety */
98 int state = 0;                                  /* the current state of the connection */
99 int laststate = 0;                              /* the last state we were in */
100 char echocancel = 1;                    /* echo cancelling */
101 reft echobase;                                  /* the anti echo queue */
102 int maxecho = 5;                                /* the depth of the anti echo queue */
103 int echon;                                              /* no of entries in the anti echo queue */
104
105 #define CONNECTED 100
106 #define WAITLOGIN 1
107 #define WAITPASSWD 2
108 #define WAITINPUT 10
109 #define DOCHAT 20
110
111 myregex_t iscallreg[] = {               /* regexes to determine whether this is a reasonable callsign */
112         {
113                 "^[A-Z]+[0-9]+[A-Z]+[1-9]?$", 0                /* G1TLH G1TLH1 */
114         },
115         {
116                 "^[0-9]+[A-Z]+[0-9]+[A-Z]+[1-9]?$", 0          /* 2E0AAA 2E0AAA1 */
117         },
118         {
119                 "^[A-Z]+[0-9]+[A-Z]+-[0-9]$", 0                /* G1TLH-2 */
120         },
121         {
122                 "^[0-9]+[A-Z]+[0-9]+[A-Z]+-[0-9]$", 0          /* 2E0AAA-2 */
123         },
124         {
125                 "^[A-Z]+[0-9]+[A-Z]+-1[0-5]$", 0               /* G1TLH-11 */
126         },
127         {
128                 "^[0-9]+[A-Z]+[0-9]+[A-Z]+-1[0-5]$", 0         /* 2E0AAA-11 */
129         },
130         {
131                 0, 0
132         }
133 };
134
135 void terminate(int);
136
137 /*
138  * utility routines - various
139  */
140
141 void chgstate(int new)
142 {
143         laststate = state;
144         state = new;
145         dbg(DSTS, "chg state %d->%d", laststate, state);
146 }
147
148 void die(char *s, ...)
149 {
150         char buf[2000];
151         
152         va_list ap;
153         va_start(ap, s);
154         vsnprintf(buf, sizeof(buf)-1, s, ap);
155         va_end(ap);
156         fprintf(stderr,"%s\n", buf);
157         terminate(-1);
158 }
159
160 char *strupper(char *s)
161 {
162         char *d = malloc(strlen(s)+1);
163         char *p = d;
164         
165         if (!d)
166                 die("out of room in strupper");
167         while (*p++ = toupper(*s++)) ;
168         return d;
169 }
170
171 char *strlower(char *s)
172 {
173         char *d = malloc(strlen(s)+1);
174         char *p = d;
175         
176         if (!d)
177                 die("out of room in strlower");
178         while (*p++ = tolower(*s++)) ;
179         return d;
180 }
181
182 int eq(char *a, char *b)
183 {
184         return (strcmp(a, b) == 0);
185 }
186
187 FILE *xopen(char *dir, char *name, char *mode)
188 {
189         char fn[MAXPATHLEN+1];
190         snprintf(fn, MAXPATHLEN, "%s/%s/%s", root, dir, name);
191         return fopen(fn, mode);
192 }
193
194 int iscallsign(char *s)
195 {
196         myregex_t *rp;
197
198         if (strlen(s) > MAXCALLSIGN)
199                 return 0;
200         
201         for (rp = iscallreg; rp->in; ++rp) {
202                 if (regexec(rp->regex, s, 0, 0, 0) == 0)
203                         return 1;
204         }
205         return 0;
206 }
207
208 void reaper(int i)
209 {
210         pid_t mypid;
211         while ((mypid = waitpid(-1, 0, WNOHANG)) > 0) {
212                 ;
213         }
214 }
215
216 /*
217  * higher level send and receive routines
218  */
219
220 fcb_t *fcb_new(int cnum, int sort)
221 {
222         fcb_t *f = malloc(sizeof(fcb_t));
223         if (!f)
224                 die("no room in fcb_new");
225         memset (f, 0, sizeof(fcb_t));
226         f->cnum = cnum;
227         f->sort = sort;
228         f->inq = chain_new();
229         f->outq = chain_new();
230         return f;
231 }
232
233 void flush_text(fcb_t *f)
234 {
235         if (f->obuf) {
236                 /* save this onto the front of the echo chain */
237                 cmsg_t *imp = f->obuf;
238                 int size = imp->inp - imp->data;
239                 cmsg_t *emp = cmsg_new(size, imp->sort, imp->portp);
240
241                 emp->size = size;
242                 memcpy(emp->data, imp->data, size);
243                 emp->inp = emp->data + size; /* just in case */
244                 if (echocancel) {
245                         chain_add(&echobase, emp);
246                         if (++echon > maxecho) {
247                                 emp = cmsg_prev(&echobase);
248                                 cmsg_free(emp);
249                         }
250                 }
251                 
252                 /* queue it for sending */
253                 cmsg_send(f->outq, imp, 0);
254                 f->sp->flags |= SEL_OUTPUT;
255                 f->obuf = 0;
256         }
257 }
258
259 void send_text(fcb_t *f, char *s, int l, int nlreq)
260 {
261         cmsg_t *mp;
262         char *p;
263         
264         if (f->buffer_it && f->obuf) {
265                 mp = f->obuf;
266         } else {
267                 f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
268         }
269
270         /* remove trailing spaces  */
271         while (l > 0 &&isspace(s[l-1]))
272                 --l;
273
274         for (p = s; p < s+l; ) {
275                 if (mp->inp >= mp->data + paclen) {
276                         flush_text(f);
277                         f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
278                 }
279                 *mp->inp++ = *p++;
280         }
281         if (mp->inp >= mp->data + paclen) {
282                 flush_text(f);
283                 f->obuf = mp = cmsg_new(paclen+1, f->sort, f);
284         }
285         if (nlreq) {
286                 if (nl == '\r')
287                         *mp->inp++ = nl;
288                 else {
289                         if (mode != 2)
290                                 *mp->inp++ = '\r';
291                         *mp->inp++ = '\n';
292                 }
293         }
294         if (!f->buffer_it)
295                 flush_text(f);
296 }
297
298 void send_msg(fcb_t *f, char let, UC *s, int l)
299 {
300         cmsg_t *mp;
301         int ln;
302         int myl = strlen(call)+2+l;
303
304         mp = cmsg_new(myl+4+1, f->sort, f);
305         *mp->inp++ = let;
306         strcpy(mp->inp, call);
307         mp->inp += strlen(call);
308         *mp->inp++ = '|';
309         if (l > 0) {
310                 UC *p;
311                 for (p = s; p < s+l; ++p) {
312                         if (mp->inp >= mp->data + (myl - 4)) {
313                                 int off = mp->inp - mp->data;
314                                 myl += 256;
315                                 mp = realloc(mp, myl);
316                                 mp->inp = mp->data + off;
317                         }
318                         
319                         if (*p < 0x20 || *p > 0x7e || *p == '%') {
320                                 sprintf(mp->inp, "%%%02X", *p & 0xff);
321                                 mp->inp += strlen(mp->inp);
322                         } else 
323                                 *mp->inp++ = *p;
324                 }
325         } 
326         *mp->inp++ = '\n';
327         *mp->inp = 0;
328         cmsg_send(f->outq, mp, 0);
329         f->sp->flags |= SEL_OUTPUT;
330 }
331
332 /* 
333  * send a file out to the user
334  */
335 void send_file(char *name)
336 {
337         int i;
338         char buf[MAXPACLEN+1];
339         
340         FILE *f = xopen("data", name, "r");
341         if (f) {
342                 while (fgets(buf, paclen, f)) {
343                         i = strlen(buf);
344                         if (i && buf[i-1] == '\n') 
345                                 buf[--i] = 0;
346                         send_text(in, buf, i, 1);
347                 }
348                 fclose(f);
349         }
350 }
351
352 /*
353  * the callback (called by sel_run) that handles all the inputs and outputs
354  */
355
356 int fcb_handler(sel_t *sp, int in, int out, int err)
357 {
358         fcb_t *f = sp->fcb;
359         cmsg_t *mp, *omp;
360         UC c;
361         
362         /* input modes */
363         if (ending == 0 && in) {
364                 char *p, buf[MAXBUFL];
365                 int r;
366
367                 /* read what we have into a buffer */
368                 r = read(f->cnum, buf, MAXBUFL);
369                 if (r < 0) {
370                         switch (errno) {
371                         case EINTR:
372                         case EINPROGRESS:
373                         case EAGAIN:
374                                 goto lout;
375                         default:
376                                 dbg(DBUF,"got errno %d in input", errno);
377                                 ending++;
378                                 return 0;
379                         }
380                 } else if (r == 0) {
381                         dbg(DBUF, "ending normally");
382                         ending++;
383                         return 0;
384                 }
385
386                 dbgdump(DBUF, "in ->", buf, r);
387                 
388                 /* create a new message buffer if required */
389                 if (!f->in)
390                         f->in = cmsg_new(MAXBUFL+1, f->sort, f);
391                 mp = f->in;
392
393                 switch (f->sort) {
394                 case TEXT:
395                         p = buf;
396                         if (f->echo)
397                                 omp = cmsg_new(3*r+1, f->sort, f);
398                         while (r > 0 && p < &buf[r]) {
399
400                                 /* echo processing */
401                                 if (f->echo) {
402                                         switch (*p) {
403                                         case '\b':
404                                         case 0x7f:
405                                                 strcpy(omp->inp, "\b \b");
406                                                 omp->inp += strlen(omp->inp);
407                                                 break;
408                                         case '\r':
409                                                 break;
410                                         case '\n':
411                                                 strcpy(omp->inp, "\r\n");
412                                                 omp->inp += 2;
413                                                 break;
414                                         default:
415                                                 *omp->inp++ = *p;
416                                         }
417                                 }
418                                 
419                                 /* character processing */
420                                 switch (*p) {
421                                 case '\t':
422                                         if (int_tabs) {
423                                                 memset(mp->inp, ' ', tabsize);
424                                                 mp->inp += tabsize;
425                                                 ++p;
426                                         } else {
427                                                 *mp->inp++ = *p++;
428                                         }
429                                         break;
430                                 case 0x08:
431                                 case 0x7f:
432                                         if (mp->inp > mp->data)
433                                                 mp->inp--;
434                                         ++p;
435                                         break;
436                                 default:
437                                         if (nl == '\n' && *p == '\r') {   /* ignore \r in telnet mode (ugh) */
438                                                 p++;
439                                         } else if (nl == '\r' && *p == '\n') {  /* and ignore \n in ax25 mode (double ugh) */
440                                                 p++;
441                                         } else if (*p == nl) {
442                                                 if (mp->inp == mp->data)
443                                                         *mp->inp++ = ' ';
444                                                 *mp->inp = 0;              /* zero terminate it, but don't include it in the length */
445                                                 dbgdump(DMSG, "QUEUE TEXT", mp->data, mp->inp-mp->data);
446                                                 cmsg_send(f->inq, mp, 0);
447                                                 f->in = mp = cmsg_new(MAXBUFL+1, f->sort, f);
448                                                 ++p;
449                                         } else {
450                                                 if (mp->inp < &mp->data[MAXBUFL-8])
451                                                         *mp->inp++ = *p++;
452                                                 else {
453                                                         mp->inp = mp->data;
454                                                 }
455                                         }
456                                 }
457                         }
458                         
459                         /* queue any echo text */
460                         if (f->echo) {
461                                 dbgdump(DMSG, "QUEUE ECHO TEXT", omp->data, omp->inp - omp->data);
462                                 cmsg_send(f->outq, omp, 0);
463                                 f->sp->flags |= SEL_OUTPUT;
464                         }
465                         
466                         break;
467
468                 case MSG:
469                         p = buf;
470                         while (r > 0 && p < &buf[r]) {
471                                 UC ch = *p++;
472                                 
473                                 if (mp->inp >= mp->data + (MAXBUFL-1)) {
474                                         mp->state = 0;
475                                         mp->inp = mp->data;
476                                         dbg(DMSG, "Message longer than %d received", MAXBUFL);
477                                 }
478
479                                 switch (mp->state) {
480                                 case 0: 
481                                         if (ch == '%') {
482                                                 c = 0;
483                                                 mp->state = 1;
484                                         } else if (ch == '\n') {
485                                                 /* kick it upstairs */
486                                                 *mp->inp = 0;
487                                                 dbgdump(DMSG, "QUEUE MSG", mp->data, mp->inp - mp->data);
488                                                 cmsg_send(f->inq, mp, 0);
489                                                 mp = f->in = cmsg_new(MAXBUFL+1, f->sort, f);
490                                         } else if (ch < 0x20 || ch > 0x7e) {
491                                                 dbg(DMSG, "Illegal character (0x%02X) received", *p);
492                                                 mp->inp = mp->data;
493                                         } else {
494                                                 *mp->inp++ = ch;
495                                         }
496                                         break;
497
498                                 case 1:
499                                         mp->state = 2;
500                                         if (ch >= '0' && ch <= '9') 
501                                                 c = (ch - '0') << 4;
502                                         else if (ch >= 'A' && ch <= 'F')
503                                                 c = (ch - 'A' + 10) << 4;
504                                         else {
505                                                 dbg(DMSG, "Illegal hex char (%c) received in state 1", ch);
506                                                 mp->inp = mp->data;
507                                                 mp->state = 0;
508                                         }
509                                         break;
510                                         
511                                 case 2:
512                                         if (ch >= '0' && ch <= '9') 
513                                                 *mp->inp++ = c | (ch - '0');
514                                         else if (ch >= 'A' && ch <= 'F')
515                                                 *mp->inp++ = c | (ch - 'A' + 10);
516                                         else {
517                                                 dbg(DMSG, "Illegal hex char (%c) received in state 2", ch);
518                                                 mp->inp = mp->data;
519                                         }
520                                         mp->state = 0;
521                                 }
522                         }
523                         break;
524                         
525                 default:
526                         die("invalid sort (%d) in input handler", f->sort);
527                 }
528         }
529         
530         /* output modes */
531 lout:;
532         if (out) {
533                 int l, r;
534                 
535                 if (!f->out) {
536                         mp = f->out = cmsg_next(f->outq);
537                         if (!mp) {
538                                 sp->flags &= ~SEL_OUTPUT;
539                                 return 0;
540                         }
541                         mp->inp = mp->data;
542                 }
543                 l = mp->size - (mp->inp - mp->data);
544                 if (l > 0) {
545                         
546                         dbgdump(DBUF, "<-out", mp->inp, l);
547                         
548                         r = write(f->cnum, mp->inp, l);
549                         if (r < 0) {
550                                 switch (errno) {
551                                 case EINTR:
552                                 case EINPROGRESS:
553                                 case EAGAIN:
554                                         goto lend;
555                                 default:
556                                         dbg(DBUF,"got errno %d in output", errno);
557                                         ending++;
558                                         return;
559                                 }
560                         } else if (r > 0) {
561                                 mp->inp += r;
562                         }
563                 } else if (l < 0) 
564                         die("got negative length in handler on node");
565                 if (mp->inp - mp->data >= mp->size) {
566                         cmsg_callback(mp, 0);
567                         f->out = 0;
568                 }
569         }
570 lend:;
571         return 0;
572 }
573
574 /* 
575  * set up the various mode flags, NL endings and things
576  */
577 void setconntype(char *m)
578 {
579         connsort = strlower(m);
580         if (eq(connsort, "telnet") || eq(connsort, "local") || eq(connsort, "nlonly")) {
581                 nl = '\n';
582                 echo = 1;
583                 mode = eq(connsort, "nlonly") ? 2 : 1;
584         } else if (eq(connsort, "ax25")) {
585                 nl = '\r';
586                 echo = 0;
587                 mode = 0;
588         } else if (eq(connsort, "connect")) {
589                 nl = '\n';
590                 echo = 0;
591                 mode = 3;
592         } else {
593                 die("Connection type must be \"telnet\", \"nlonly\", \"ax25\", \"login\" or \"local\"");
594         }
595 }
596
597
598 /*
599  * things to do with ongoing processing of inputs
600  */
601
602 void process_stdin()
603 {
604         cmsg_t *wmp, *mp = cmsg_next(in->inq);
605         char *p, hasa, hasn, i;
606         char callsign[MAXCALLSIGN+1];
607         
608         if (mp) {
609                 dbg(DMSG, "MSG size: %d", mp->size);
610                 
611                 /* check for echos */
612                 if (echocancel) {
613                         for (wmp = 0; wmp = chain_get_next(&echobase, wmp); ) {
614                                 if (!memcmp(wmp->data, mp->data, wmp->size)) {
615                                         cmsg_callback(mp, 0);
616                                         return;
617                                 }
618                         }
619                 }
620
621                 switch (state) {
622                 case CONNECTED:
623                         if (mp->size > 0) {
624                                 send_msg(node, 'I', mp->data, mp->size);
625                         }
626                         break;
627                 case WAITLOGIN:
628                         for (i = 0; i < mp->size; ++i) {
629                                 UC ch = mp->data[i];
630                                 if (i < MAXCALLSIGN) {
631                                         if (isalpha(ch))
632                                                 ++hasa;
633                                         if (isdigit(ch))
634                                                 ++hasn;
635                                     if (isalnum(ch) || ch == '-')
636                                                 callsign[i] = ch;
637                                         else
638                                                 die("invalid callsign");
639                                 } else 
640                                         die("invalid callsign");
641                         }
642                         callsign[i]= 0;
643                         if (strlen(callsign) < 3)
644                                 die("invalid callsign");
645                         if (hasa && hasn)
646                                 ;
647                         else
648                                 die("invalid callsign");
649                         call = strupper(callsign);
650                         
651                         /* check the callsign against the regexes */
652                         if (!iscallsign(call)) {
653                                 die("Sorry, %s isn't a valid callsign", call);
654                         }
655                         
656                         /* strip off a '-0' at the end */
657                         i = strlen(call);
658                         if (call[i-1] == '0' && call[i-2] == '-')
659                                 call[i-2] = 0;
660
661                         alarm(0);
662                         signal(SIGALRM, SIG_IGN);
663                         
664                         /* tell the cluster who I am */
665                         send_msg(node, 'A', connsort, strlen(connsort));
666                         
667                         chgstate(CONNECTED);
668                         send_file("connected");
669                         break;
670                         
671                 case DOCHAT:
672
673                         break;
674                 }
675
676                 cmsg_callback(mp, 0);
677
678         }
679 }
680
681 void process_node()
682 {
683         cmsg_t *mp = cmsg_next(node->inq);
684         if (mp) {
685                 dbg(DMSG, "MSG size: %d", mp->size);
686         
687                 if (mp->size > 0 && mp->inp > mp->data) {
688                         char *p = strchr(mp->data, '|');
689                         if (p)
690                                 p++;
691                         switch (mp->data[0]) {
692                         case 'Z':
693                                 ending++;
694                                 return;
695                         case 'E':
696                                 if (isdigit(*p))
697                                         in->echo = *p - '0';
698                                 break;
699                         case 'B':
700                                 if (isdigit(*p))
701                                         in->buffer_it = *p - '0';
702                                 break;
703                         case 'D':
704                                 if (p) {
705                                         int l = mp->inp - (UC *) p;
706                                         send_text(in, p, l, 1);
707                                 }
708                                 break;
709                         default:
710                                 break;
711                         }
712                 }
713                 cmsg_callback(mp, 0);
714         } else {
715                 flush_text(in);
716         }
717 }
718
719 /*
720  * things to do with going away
721  */
722
723 void term_timeout(int i)
724 {
725         /* none of this is going to be reused so don't bother cleaning up properly */
726         if (in && in->t_set)
727                 tcsetattr(0, TCSANOW, &in->t);
728         if (node) {
729                 shutdown(node->cnum, 3);
730                 close(node->cnum);
731         }
732         exit(i);
733 }
734
735 void terminate(int i)
736 {
737         signal(SIGALRM, term_timeout);
738         alarm(10);
739         
740         while ((in && !is_chain_empty(in->outq)) ||
741                    (node && !is_chain_empty(node->outq))) {
742                 sel_run();
743         }
744         if (in && in->t_set)
745                 tcsetattr(0, TCSADRAIN, &in->t);
746         if (node) {
747                 shutdown(node->cnum, 3);
748                 close(node->cnum);
749         }
750         exit(i);
751 }
752
753 void login_timeout(int i)
754 {
755         write(0, "Timed Out", 10);
756         write(0, &nl, 1);
757         sel_run();                                      /* force a coordination */
758         if (in && in->t_set)
759                 tcsetattr(0, TCSANOW, &in->t);
760         exit(i);
761 }
762
763 /*
764  * things to do with initialisation
765  */
766
767 void initargs(int argc, char *argv[])
768 {
769         int i, c, err = 0;
770
771         while ((c = getopt(argc, argv, "eh:l:p:x:")) > 0) {
772                 switch (c) {
773                 case 'e':
774                         echocancel ^= 1;
775                         break;
776                 case 'h':
777                         node_addr = optarg;
778                         break;
779                 case 'l':
780                         paclen = atoi(optarg);
781                         if (paclen < 80)
782                                 paclen = 80;
783                         if (paclen > MAXPACLEN)
784                                 paclen = MAXPACLEN;
785                         break;
786                 case 'p':
787                         node_port = atoi(optarg);
788                         break;
789                 case 'x':
790                         dbginit("client");
791                         dbgset(atoi(optarg));
792                         break;
793                 default:
794                         ++err;
795                         goto lerr;
796                 }
797         }
798
799 lerr:
800         if (err) {
801                 die("usage: client [-e|-x n|-h<host>|-p<port>|-l<paclen>] <call>|login [local|telnet|ax25]");
802         }
803         
804         if (optind < argc) {
805                 call = strupper(argv[optind]);
806                 ++optind;
807         }
808         if (!call)
809                 die("Must have at least a callsign (for now)");
810
811         if (optind < argc) {
812                 setconntype(argv[optind]);              
813         } else {
814                 setconntype("local");
815         }
816
817         /* this is kludgy, but hey so is the rest of this! */
818         if (mode != 0 && paclen == DEFPACLEN) {
819                 paclen = MAXPACLEN;
820         }
821 }
822
823 void connect_to_node()
824 {
825         struct hostent *hp, *gethostbyname();
826         struct sockaddr_in server;
827         int nodef;
828         int one = 1;
829         sel_t *sp;
830         struct linger lg;
831                                         
832         if ((hp = gethostbyname(node_addr)) == 0) 
833                 die("Unknown host tcp host %s for printer", node_addr);
834
835         memset(&server, 0, sizeof server);
836         server.sin_family = AF_INET;
837         memcpy(&server.sin_addr, hp->h_addr, hp->h_length);
838         server.sin_port = htons(node_port);
839                                                 
840         nodef = socket(AF_INET, SOCK_STREAM, 0);
841         if (nodef < 0) 
842                 die("Can't open socket to %s port %d (%d)", node_addr, node_port, errno);
843
844         if (connect(nodef, (struct sockaddr *) &server, sizeof server) < 0) {
845                 die("Error on connect to %s port %d (%d)", node_addr, node_port, errno);
846         }
847
848         memset(&lg, 0, sizeof lg);
849         if (setsockopt(nodef, SOL_SOCKET, SO_LINGER, &lg, sizeof lg) < 0) {
850                 die("Error on SO_LINGER to %s port %d (%d)", node_addr, node_port, errno);
851         }
852         if (setsockopt(nodef, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one) < 0) {
853                 die("Error on SO_KEEPALIVE to %s port %d (%d)", node_addr, node_port, errno);
854         }
855         
856         node = fcb_new(nodef, MSG);
857         node->sp = sel_open(nodef, node, "Msg System", fcb_handler, MSG, SEL_INPUT);
858         
859 }
860
861 /*
862  * the program itself....
863  */
864
865 main(int argc, char *argv[])
866 {
867         /* compile regexes for iscallsign */
868         {
869                 myregex_t *rp;
870                 for (rp = iscallreg; rp->in; ++rp) {
871                         regex_t reg;
872                         int r = regcomp(&reg, rp->in, REG_EXTENDED|REG_ICASE|REG_NOSUB);
873                         if (r)
874                                 die("regcomp returned %d for '%s'", r, rp->in);
875                         rp->regex = malloc(sizeof(regex_t));
876                         if (!rp->regex)
877                                 die("out of room - compiling regexes");
878                         *rp->regex = reg;
879                 }
880         }
881         
882         /* set up environment */
883         {
884                 char *p = getenv("DXSPIDER_ROOT");
885                 if (p)
886                         root = p;
887                 p = getenv("DXSPIDER_HOST");
888                 if (p)
889                         node_addr = p;
890                 p = getenv("DXSPIDER_PORT");
891                 if (p)
892                         node_port = atoi(p);
893                 p = getenv("DXSPIDER_PACLEN");
894                 if (p) {
895                         paclen = atoi(p);
896                         if (paclen < 80)
897                                 paclen = 80;
898                         if (paclen > MAXPACLEN)
899                                 paclen = MAXPACLEN;
900                 }
901         }
902         
903         /* get program arguments, initialise stuff */
904         initargs(argc, argv);
905         sel_init(10, 0, 10000);
906
907         /* trap signals */
908         signal(SIGHUP, SIG_IGN);
909         signal(SIGINT, terminate);
910         signal(SIGQUIT, terminate);
911         signal(SIGTERM, terminate);
912 #ifdef SIGPWR
913         signal(SIGPWR, terminate);
914 #endif
915         
916         /* init a few things */
917         chain_init(&echobase);
918
919         /* connect up stdin */
920         in = fcb_new(0, TEXT);
921         in->sp = sel_open(0, in, "STDIN", fcb_handler, TEXT, SEL_INPUT);
922         if (tcgetattr(0, &in->t) < 0) {
923 /*              echo = 0; */
924                 in->echo = echo;
925                 in->t_set = 0;
926         } else {
927                 struct termios t = in->t;
928                 t.c_lflag &= ~(ECHO|ECHONL|ICANON);
929                 t.c_oflag = 0;
930                 if (tcsetattr(0, TCSANOW, &t) < 0) 
931                         die("tcsetattr (%d)", errno);
932                 in->echo = echo;
933                 in->t_set = 1;
934         }
935         in->buffer_it = 1;
936
937         /* connect up node */
938         connect_to_node();
939
940         /* is this a login? */
941         if (eq(call, "LOGIN") || eq(call, "login")) {
942                 send_file("issue");
943                 signal(SIGALRM, login_timeout);
944                 alarm(timeout);
945                 send_text(in, "login: ", 7, 0);
946                 chgstate(WAITLOGIN);
947         } else {
948                 int i;
949                 
950                 /* check the callsign against the regexes */
951                 if (!iscallsign(call)) {
952                         die("Sorry, %s isn't a valid callsign", call);
953                 }
954
955                 /* strip off a '-0' at the end */
956                 i = strlen(call);
957                 if (call[i-1] == '0' && call[i-2] == '-')
958                         call[i-2] = 0;
959                 
960                 /* tell the cluster who I am */
961                 send_msg(node, 'A', connsort, strlen(connsort));
962         
963                 chgstate(CONNECTED);
964                 send_file("connected");
965         }
966         
967
968         /* main processing loop */
969         while (ending == 0) {
970                 sel_run();
971                 if (ending == 0) {
972                         process_stdin();
973                         process_node();
974                 }
975         }
976         terminate(0);
977 }
978
979
980
981
982
983