]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/telnetd.c
snoopy(8): avoid extra spaces in dhcp filter output
[plan9front.git] / sys / src / cmd / ip / telnetd.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <auth.h>
5 #include <libsec.h>
6
7 #include "../ip/telnet.h"
8
9 /*  console state (for consctl) */
10 typedef struct Consstate        Consstate;
11 struct Consstate{
12         int raw;
13         int hold;
14 };
15 Consstate *cons;
16
17 int notefd;             /* for sending notes to the child */
18 int noproto;            /* true if we shouldn't be using the telnet protocol */
19 int trusted;            /* true if we need not authenticate - current user
20                                 is ok */
21 int nonone = 1;         /* don't allow none logins */
22 int noworldonly;        /* only noworld accounts */
23
24 enum
25 {
26         Maxpath=        256,
27         Maxuser=        64,
28         Maxvar=         32,
29 };
30
31 /* input and output buffers for network connection */
32 Biobuf  netib;
33 Biobuf  childib;
34 char    remotesys[Maxpath];     /* name of remote system */
35
36 int     alnum(int);
37 int     conssim(void);
38 int     fromchild(char*, int);
39 int     fromnet(char*, int);
40 int     termchange(Biobuf*, int);
41 int     termsub(Biobuf*, uchar*, int);
42 int     xlocchange(Biobuf*, int);
43 int     xlocsub(Biobuf*, uchar*, int);
44 int     challuser(char*);
45 int     noworldlogin(char*);
46 void*   share(ulong);
47 int     doauth(char*);
48
49 #define TELNETLOG "telnet"
50
51 void
52 logit(char *fmt, ...)
53 {
54         va_list arg;
55         char buf[8192];
56
57         va_start(arg, fmt);
58         vseprint(buf, buf + sizeof(buf) / sizeof(*buf), fmt, arg);
59         va_end(arg);
60         syslog(0, TELNETLOG, "(%s) %s", remotesys, buf);
61 }
62
63 void
64 getremote(char *dir)
65 {
66         int fd, n;
67         char remfile[Maxpath];
68
69         sprint(remfile, "%s/remote", dir);
70         fd = open(remfile, OREAD);
71         if(fd < 0)
72                 strcpy(remotesys, "unknown2");
73         n = read(fd, remotesys, sizeof(remotesys)-1);
74         if(n>0)
75                 remotesys[n-1] = 0;
76         else
77                 strcpy(remotesys, remfile);
78         close(fd);
79 }
80
81 void
82 main(int argc, char *argv[])
83 {
84         char buf[1024];
85         int fd;
86         char user[Maxuser];
87         int tries = 0;
88         int childpid;
89         int n, eofs;
90
91         memset(user, 0, sizeof(user));
92         ARGBEGIN {
93         case 'n':
94                 opt[Echo].local = 1;
95                 noproto = 1;
96                 break;
97         case 'p':
98                 noproto = 1;
99                 break;
100         case 'a':
101                 nonone = 0;
102                 break;
103         case 't':
104                 trusted = 1;
105                 strncpy(user, getuser(), sizeof(user)-1);
106                 break;
107         case 'u':
108                 strncpy(user, ARGF(), sizeof(user)-1);
109                 break;
110         case 'd':
111                 debug = 1;
112                 break;
113         case 'N':
114                 noworldonly = 1;
115                 break;
116         } ARGEND
117
118         if(argc)
119                 getremote(argv[argc-1]);
120         else
121                 strcpy(remotesys, "unknown");
122
123         /* options we need routines for */
124         opt[Term].change = termchange;
125         opt[Term].sub = termsub;
126         opt[Xloc].sub = xlocsub;
127
128         /* setup default telnet options */
129         if(!noproto){
130                 send3(1, Iac, Will, opt[Echo].code);
131                 send3(1, Iac, Do, opt[Term].code);
132                 send3(1, Iac, Do, opt[Xloc].code);
133         }
134
135         /* shared data for console state */
136         cons = share(sizeof(Consstate));
137         if(cons == 0)
138                 fatal("shared memory", 0, 0);
139
140         /* authenticate and create new name space */
141         Binit(&netib, 0, OREAD);
142         if (!trusted){
143                 while(doauth(user) < 0)
144                         if(++tries == 5){
145                                 logit("failed as %s: %r", user);
146                                 print("authentication failure:%r\r\n");
147                                 exits("authentication");
148                         }
149         }
150         logit("logged in as %s", user);
151         putenv("service", "con");
152
153         /* simulate /dev/consctl and /dev/cons using pipes */
154         fd = conssim();
155         if(fd < 0)
156                 fatal("simulating", 0, 0);
157         Binit(&childib, fd, OREAD);
158
159         /* start a shell in a different process group */
160         switch(childpid = rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
161         case -1:
162                 fatal("fork", 0, 0);
163         case 0:
164                 close(fd);
165                 fd = open("/dev/cons", OREAD);
166                 dup(fd, 0);
167                 close(fd);
168                 fd = open("/dev/cons", OWRITE);
169                 dup(fd, 1);
170                 dup(fd, 2);
171                 close(fd);
172                 segdetach(cons);
173                 execl("/bin/rc", "rc", "-il", nil);
174                 fatal("/bin/rc", 0, 0);
175         default:
176                 sprint(buf, "/proc/%d/notepg", childpid);
177                 notefd = open(buf, OWRITE);
178                 break;
179         }
180
181         /* two processes to shuttle bytes twixt children and network */
182         switch(fork()){
183         case -1:
184                 fatal("fork", 0, 0);
185         case 0:
186                 eofs = 0;
187                 for(;;){
188                         n = fromchild(buf, sizeof(buf));
189                         if(n <= 0){
190                                 if(eofs++ > 2)
191                                         break;
192                                 continue;
193                         }
194                         eofs = 0;
195                         if(write(1, buf, n) != n)
196                                 break;
197                 }
198                 break;
199         default:
200                 while((n = fromnet(buf, sizeof(buf))) >= 0)
201                         if(write(fd, buf, n) != n)
202                                 break;
203                 break;
204         }
205
206         /* kill off all server processes */
207         sprint(buf, "/proc/%d/notepg", getpid());
208         fd = open(buf, OWRITE);
209         write(fd, "die", 3);
210         exits(0);
211 }
212
213 void
214 prompt(char *p, char *b, int n, int raw)
215 {
216         char *e;
217         int i;
218         int echo;
219
220         echo = opt[Echo].local;
221         if(raw)
222                 opt[Echo].local = 0;
223         print("%s: ", p);
224         for(e = b+n; b < e;){
225                 i = fromnet(b, e-b);
226                 if(i <= 0)
227                         exits("fromnet: hungup");
228                 b += i;
229                 if(*(b-1) == '\n' || *(b-1) == '\r'){
230                         *(b-1) = 0;
231                         break;
232                 }
233         }
234         if(raw)
235                 opt[Echo].local = echo;
236 }
237
238 /*
239  *  challenge user
240  */
241 int
242 challuser(char *user)
243 {
244         char nchall[64];
245         char response[64];
246         Chalstate *ch;
247         AuthInfo *ai;
248
249         if(strcmp(user, "none") == 0){
250                 if(nonone)
251                         return -1;
252                 newns("none", nil);
253                 return 0;
254         }
255         if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
256                 return -1;
257         snprint(nchall, sizeof nchall, "challenge: %s\r\nresponse", ch->chal);
258         prompt(nchall, response, sizeof response, 0);
259         ch->resp = response;
260         ch->nresp = strlen(response);
261         ai = auth_response(ch);
262         auth_freechal(ch);
263         if(ai == nil){
264                 rerrstr(response, sizeof response);
265                 print("!%s\n", response);
266                 return -1;
267         }
268         if(auth_chuid(ai, nil) < 0)
269                 return -1;
270         return 0;
271 }
272 /*
273  *  use the in the clear apop password to change user id
274  */
275 int
276 noworldlogin(char *user)
277 {
278         char password[256];
279
280         prompt("password", password, sizeof(password), 1);
281         if(login(user, password, "/lib/namespace.noworld") < 0)
282                 return -1;
283         rfork(RFNOMNT); /* sandbox */
284         return 0;
285 }
286
287 int
288 doauth(char *user)
289 {
290         if(*user == 0)
291                 prompt("user", user, Maxuser, 0);
292         if(noworld(user))
293                 return noworldlogin(user);
294         if(noworldonly)
295                 return -1;
296         return challuser(user);
297                 
298 }
299
300 /*
301  *  Process some input from the child, add protocol if needed.  If
302  *  the input buffer goes empty, return.
303  */
304 int
305 fromchild(char *bp, int len)
306 {
307         int c;
308         char *start;
309
310         for(start = bp; bp-start < len-1; ){
311                 c = Bgetc(&childib);
312                 if(c < 0){
313                         if(bp == start)
314                                 return -1;
315                         else
316                                 break;
317                 }
318                 if(cons->raw == 0 && c == '\n')
319                         *bp++ = '\r';
320                 *bp++ = c;
321                 if(Bbuffered(&childib) == 0)
322                         break;
323         }
324         return bp-start;
325 }
326
327 /*
328  *  Read from the network up to a '\n' or some other break.
329  *
330  *  If in binary mode, buffer characters but don't 
331  *
332  *  The following characters are special:
333  *      '\r\n's and '\r's get turned into '\n's.
334  *      ^H erases the last character buffered.
335  *      ^U kills the whole line buffered.
336  *      ^W erases the last word
337  *      ^D causes a 0-length line to be returned.
338  *      Intr causes an "interrupt" note to be sent to the children.
339  */
340 #define ECHO(c) { *ebp++ = (c); }
341 int
342 fromnet(char *bp, int len)
343 {
344         int c;
345         char echobuf[1024];
346         char *ebp;
347         char *start;
348         static int crnl;
349         static int doeof;
350
351
352         /* simulate an EOF as a 0 length input */
353         if(doeof){
354                 doeof = 0;
355                 return 0;
356         }
357
358         for(ebp = echobuf,start = bp; bp-start < len && ebp-echobuf < sizeof(echobuf); ){
359                 c = Bgetc(&netib);
360                 if(c < 0){
361                         if(bp == start)
362                                 return -1;
363                         else
364                                 break;
365                 }
366
367                 /* telnet protocol only */
368                 if(!noproto){
369                         /* protocol messages */
370                         switch(c){
371                         case Iac:
372                                 crnl = 0;
373                                 c = Bgetc(&netib);
374                                 if(c == Iac)
375                                         break;
376                                 control(&netib, c);
377                                 continue;
378                         }
379
380                 }
381
382                 /* \r\n or \n\r become \n  */
383                 if(c == '\r' || c == '\n'){
384                         if(crnl && crnl != c){
385                                 crnl = 0;
386                                 continue;
387                         }
388                         if(cons->raw == 0 && opt[Echo].local){
389                                 ECHO('\r');
390                                 ECHO('\n');
391                         }
392                         crnl = c;
393                         if(cons->raw == 0)
394                                 *bp++ = '\n';
395                         else
396                                 *bp++ = c;
397                         break;
398                 } else
399                         crnl = 0;
400
401                 /* raw processing (each character terminates */
402                 if(cons->raw){
403                         *bp++ = c;
404                         break;
405                 }
406
407                 /* in binary mode, there are no control characters */
408                 if(opt[Binary].local){
409                         if(opt[Echo].local)
410                                 ECHO(c);
411                         *bp++ = c;
412                         continue;
413                 }
414
415                 /* cooked processing */
416                 switch(c){
417                 case 0x00:
418                         if(noproto)             /* telnet ignores nulls */
419                                 *bp++ = c;
420                         continue;
421                 case 0x04:
422                         if(bp != start)
423                                 doeof = 1;
424                         goto out;
425
426                 case 0x08:      /* ^H */
427                         if(start < bp)
428                                 bp--;
429                         if(opt[Echo].local)
430                                 ECHO(c);
431                         break;
432
433                 case 0x15:      /* ^U */
434                         bp = start;
435                         if(opt[Echo].local){
436                                 ECHO('^');
437                                 ECHO('U');
438                                 ECHO('\r');
439                                 ECHO('\n');
440                         }
441                         break;
442
443                 case 0x17:      /* ^W */
444                         if (opt[Echo].local) {
445                                 while (--bp >= start && !alnum(*bp))
446                                         ECHO('\b');
447                                 while (bp >= start && alnum(*bp)) {
448                                         ECHO('\b');
449                                         bp--;
450                                 }
451                                 bp++;
452                         }
453                         break;
454
455                 case 0x7f:      /* Del */
456                         write(notefd, "interrupt", 9);
457                         bp = start;
458                         break;
459
460                 default:
461                         if(opt[Echo].local)
462                                 ECHO(c);
463                         *bp++ = c;
464                 }
465                 if(ebp != echobuf)
466                         write(1, echobuf, ebp-echobuf);
467                 ebp = echobuf;
468         }
469 out:
470         if(ebp != echobuf)
471                 write(1, echobuf, ebp-echobuf);
472         return bp - start;
473 }
474
475 int
476 termchange(Biobuf *bp, int cmd)
477 {
478         char buf[8];
479         char *p = buf;
480
481         if(cmd != Will)
482                 return 0;
483
484         /* ask other side to send term type info */
485         *p++ = Iac;
486         *p++ = Sb;
487         *p++ = opt[Term].code;
488         *p++ = 1;
489         *p++ = Iac;
490         *p++ = Se;
491         return iwrite(Bfildes(bp), buf, p-buf);
492 }
493
494 int
495 termsub(Biobuf *bp, uchar *sub, int n)
496 {
497         char term[Maxvar];
498
499         USED(bp);
500         if(n-- < 1 || sub[0] != 0)
501                 return 0;
502         if(n >= sizeof term)
503                 n = sizeof term;
504         strncpy(term, (char*)sub, n);
505         putenv("TERM", term);
506         return 0;
507 }
508
509 int
510 xlocchange(Biobuf *bp, int cmd)
511 {
512         char buf[8];
513         char *p = buf;
514
515         if(cmd != Will)
516                 return 0;
517
518         /* ask other side to send x display info */
519         *p++ = Iac;
520         *p++ = Sb;
521         *p++ = opt[Xloc].code;
522         *p++ = 1;
523         *p++ = Iac;
524         *p++ = Se;
525         return iwrite(Bfildes(bp), buf, p-buf);
526 }
527
528 int
529 xlocsub(Biobuf *bp, uchar *sub, int n)
530 {
531         char xloc[Maxvar];
532
533         USED(bp);
534         if(n-- < 1 || sub[0] != 0)
535                 return 0;
536         if(n >= sizeof xloc)
537                 n = sizeof xloc;
538         strncpy(xloc, (char*)sub, n);
539         putenv("DISPLAY", xloc);
540         return 0;
541 }
542
543 /*
544  *  create a shared segment.
545  */
546 void*
547 share(ulong len)
548 {
549         void *v;
550
551         v = segattach(0, "shared", 0, len);
552         if(v == (void*)-1)
553                 return nil;
554         return v;
555 }
556
557 /*
558  *  bind a pipe onto consctl and keep reading it to
559  *  get changes to console state.
560  */
561 int
562 conssim(void)
563 {
564         int i, n;
565         int fd;
566         int tries;
567         char buf[128];
568         char *field[10];
569
570         /* a pipe to simulate the /dev/cons */
571         if(bind("#|", "/mnt/cons", MREPL) < 0)
572                 fatal("/dev/cons1", 0, 0);
573         if(bind("/mnt/cons/data1", "/dev/cons", MREPL) < 0)
574                 fatal("/dev/cons2", 0, 0);
575
576         /* a pipe to simulate consctl */
577         if(bind("#|", "/mnt/consctl", MBEFORE) < 0
578         || bind("/mnt/consctl/data1", "/dev/consctl", MREPL) < 0)
579                 fatal("/dev/consctl", 0, 0);
580
581         /* a process to read /dev/consctl and set the state in cons */
582         switch(fork()){
583         case -1:
584                 fatal("forking", 0, 0);
585         case 0:
586                 break;
587         default:
588                 return open("/mnt/cons/data", ORDWR);
589         }
590
591         for(tries = 0; tries < 100; tries++){
592                 cons->raw = 0;
593                 cons->hold = 0;
594                 fd = open("/mnt/consctl/data", OREAD);
595                 if(fd < 0)
596                         continue;
597                 tries = 0;
598                 for(;;){
599                         n = read(fd, buf, sizeof(buf)-1);
600                         if(n <= 0)
601                                 break;
602                         buf[n] = 0;
603                         n = getfields(buf, field, 10, 1, " ");
604                         for(i = 0; i < n; i++){
605                                 if(strcmp(field[i], "rawon") == 0) {
606                                         if(debug) fprint(2, "raw = 1\n");
607                                         cons->raw = 1;
608                                 } else if(strcmp(field[i], "rawoff") == 0) {
609                                         if(debug) fprint(2, "raw = 0\n");
610                                         cons->raw = 0;
611                                 } else if(strcmp(field[i], "holdon") == 0) {
612                                         cons->hold = 1;
613                                         if(debug) fprint(2, "raw = 1\n");
614                                 } else if(strcmp(field[i], "holdoff") == 0) {
615                                         cons->hold = 0;
616                                         if(debug) fprint(2, "raw = 0\n");
617                                 }
618                         }
619                 }
620                 close(fd);
621         }
622         exits(0);
623         return -1;
624 }
625
626 int
627 alnum(int c)
628 {
629         /*
630          * Hard to get absolutely right.  Use what we know about ASCII
631          * and assume anything above the Latin control characters is
632          * potentially an alphanumeric.
633          */
634         if(c <= ' ')
635                 return 0;
636         if(0x7F<=c && c<=0xA0)
637                 return 0;
638         if(strchr("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c))
639                 return 0;
640         return 1;
641 }