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