]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/telnetd.c
[9front] walk: properly format permissions
[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         Dir nd;
249
250         if(strcmp(user, "none") == 0){
251                 if(nonone)
252                         return -1;
253                 newns("none", nil);
254                 return 0;
255         }
256         if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
257                 return -1;
258         snprint(nchall, sizeof nchall, "challenge: %s\r\nresponse", ch->chal);
259         prompt(nchall, response, sizeof response, 0);
260         ch->resp = response;
261         ch->nresp = strlen(response);
262         ai = auth_response(ch);
263         auth_freechal(ch);
264         if(ai == nil || auth_chuid(ai, nil) < 0){
265                 rerrstr(response, sizeof response);
266                 print("!%s\n", response);
267
268                 auth_freeAI(ai);
269                 return -1;
270         }
271         /* chown network connection */
272         nulldir(&nd);
273         nd.mode = 0660;
274         nd.uid = ai->cuid;
275         dirfwstat(0, &nd);
276
277         auth_freeAI(ai);
278         return 0;
279 }
280 /*
281  *  use the in the clear apop password to change user id
282  */
283 int
284 noworldlogin(char *user)
285 {
286         char password[256];
287
288         prompt("password", password, sizeof(password), 1);
289         if(login(user, password, "/lib/namespace.noworld") < 0)
290                 return -1;
291         rfork(RFNOMNT); /* sandbox */
292         return 0;
293 }
294
295 int
296 doauth(char *user)
297 {
298         if(*user == 0)
299                 prompt("user", user, Maxuser, 0);
300         if(noworld(user))
301                 return noworldlogin(user);
302         if(noworldonly)
303                 return -1;
304         return challuser(user);
305                 
306 }
307
308 /*
309  *  Process some input from the child, add protocol if needed.  If
310  *  the input buffer goes empty, return.
311  */
312 int
313 fromchild(char *bp, int len)
314 {
315         int c;
316         char *start;
317
318         for(start = bp; bp-start < len-1; ){
319                 c = Bgetc(&childib);
320                 if(c < 0){
321                         if(bp == start)
322                                 return -1;
323                         else
324                                 break;
325                 }
326                 if(cons->raw == 0 && c == '\n')
327                         *bp++ = '\r';
328                 *bp++ = c;
329                 if(Bbuffered(&childib) == 0)
330                         break;
331         }
332         return bp-start;
333 }
334
335 /*
336  *  Read from the network up to a '\n' or some other break.
337  *
338  *  If in binary mode, buffer characters but don't 
339  *
340  *  The following characters are special:
341  *      '\r\n's and '\r's get turned into '\n's.
342  *      ^H erases the last character buffered.
343  *      ^U kills the whole line buffered.
344  *      ^W erases the last word
345  *      ^D causes a 0-length line to be returned.
346  *      Intr causes an "interrupt" note to be sent to the children.
347  */
348 #define ECHO(c) { *ebp++ = (c); }
349 int
350 fromnet(char *bp, int len)
351 {
352         int c;
353         char echobuf[1024];
354         char *ebp;
355         char *start;
356         static int crnl;
357         static int doeof;
358
359
360         /* simulate an EOF as a 0 length input */
361         if(doeof){
362                 doeof = 0;
363                 return 0;
364         }
365
366         for(ebp = echobuf,start = bp; bp-start < len && ebp-echobuf < sizeof(echobuf); ){
367                 c = Bgetc(&netib);
368                 if(c < 0){
369                         if(bp == start)
370                                 return -1;
371                         else
372                                 break;
373                 }
374
375                 /* telnet protocol only */
376                 if(!noproto){
377                         /* protocol messages */
378                         switch(c){
379                         case Iac:
380                                 crnl = 0;
381                                 c = Bgetc(&netib);
382                                 if(c == Iac)
383                                         break;
384                                 control(&netib, c);
385                                 continue;
386                         }
387
388                 }
389
390                 /* \r\n or \n\r become \n  */
391                 if(c == '\r' || c == '\n'){
392                         if(crnl && crnl != c){
393                                 crnl = 0;
394                                 continue;
395                         }
396                         if(cons->raw == 0 && opt[Echo].local){
397                                 ECHO('\r');
398                                 ECHO('\n');
399                         }
400                         crnl = c;
401                         if(cons->raw == 0)
402                                 *bp++ = '\n';
403                         else
404                                 *bp++ = c;
405                         break;
406                 } else
407                         crnl = 0;
408
409                 /* raw processing (each character terminates */
410                 if(cons->raw){
411                         *bp++ = c;
412                         break;
413                 }
414
415                 /* in binary mode, there are no control characters */
416                 if(opt[Binary].local){
417                         if(opt[Echo].local)
418                                 ECHO(c);
419                         *bp++ = c;
420                         continue;
421                 }
422
423                 /* cooked processing */
424                 switch(c){
425                 case 0x00:
426                         if(noproto)             /* telnet ignores nulls */
427                                 *bp++ = c;
428                         continue;
429                 case 0x04:
430                         if(bp != start)
431                                 doeof = 1;
432                         goto out;
433
434                 case 0x08:      /* ^H */
435                         if(start < bp)
436                                 bp--;
437                         if(opt[Echo].local)
438                                 ECHO(c);
439                         break;
440
441                 case 0x15:      /* ^U */
442                         bp = start;
443                         if(opt[Echo].local){
444                                 ECHO('^');
445                                 ECHO('U');
446                                 ECHO('\r');
447                                 ECHO('\n');
448                         }
449                         break;
450
451                 case 0x17:      /* ^W */
452                         if (opt[Echo].local) {
453                                 while (--bp >= start && !alnum(*bp))
454                                         ECHO('\b');
455                                 while (bp >= start && alnum(*bp)) {
456                                         ECHO('\b');
457                                         bp--;
458                                 }
459                                 bp++;
460                         }
461                         break;
462
463                 case 0x7f:      /* Del */
464                         write(notefd, "interrupt", 9);
465                         bp = start;
466                         break;
467
468                 default:
469                         if(opt[Echo].local)
470                                 ECHO(c);
471                         *bp++ = c;
472                 }
473                 if(ebp != echobuf)
474                         write(1, echobuf, ebp-echobuf);
475                 ebp = echobuf;
476         }
477 out:
478         if(ebp != echobuf)
479                 write(1, echobuf, ebp-echobuf);
480         return bp - start;
481 }
482
483 int
484 termchange(Biobuf *bp, int cmd)
485 {
486         char buf[8];
487         char *p = buf;
488
489         if(cmd != Will)
490                 return 0;
491
492         /* ask other side to send term type info */
493         *p++ = Iac;
494         *p++ = Sb;
495         *p++ = opt[Term].code;
496         *p++ = 1;
497         *p++ = Iac;
498         *p++ = Se;
499         return iwrite(Bfildes(bp), buf, p-buf);
500 }
501
502 int
503 termsub(Biobuf *bp, uchar *sub, int n)
504 {
505         char term[Maxvar];
506
507         USED(bp);
508         if(n-- < 1 || sub[0] != 0)
509                 return 0;
510         if(n >= sizeof term)
511                 n = sizeof term;
512         strncpy(term, (char*)sub, n);
513         putenv("TERM", term);
514         return 0;
515 }
516
517 int
518 xlocchange(Biobuf *bp, int cmd)
519 {
520         char buf[8];
521         char *p = buf;
522
523         if(cmd != Will)
524                 return 0;
525
526         /* ask other side to send x display info */
527         *p++ = Iac;
528         *p++ = Sb;
529         *p++ = opt[Xloc].code;
530         *p++ = 1;
531         *p++ = Iac;
532         *p++ = Se;
533         return iwrite(Bfildes(bp), buf, p-buf);
534 }
535
536 int
537 xlocsub(Biobuf *bp, uchar *sub, int n)
538 {
539         char xloc[Maxvar];
540
541         USED(bp);
542         if(n-- < 1 || sub[0] != 0)
543                 return 0;
544         if(n >= sizeof xloc)
545                 n = sizeof xloc;
546         strncpy(xloc, (char*)sub, n);
547         putenv("DISPLAY", xloc);
548         return 0;
549 }
550
551 /*
552  *  create a shared segment.
553  */
554 void*
555 share(ulong len)
556 {
557         void *v;
558
559         v = segattach(0, "shared", 0, len);
560         if(v == (void*)-1)
561                 return nil;
562         return v;
563 }
564
565 /*
566  *  bind a pipe onto consctl and keep reading it to
567  *  get changes to console state.
568  */
569 int
570 conssim(void)
571 {
572         int i, n;
573         int fd;
574         int tries;
575         char buf[128];
576         char *field[10];
577
578         /* a pipe to simulate the /dev/cons */
579         if(bind("#|", "/mnt/cons", MREPL) == -1)
580                 fatal("/dev/cons1", 0, 0);
581         if(bind("/mnt/cons/data1", "/dev/cons", MREPL) == -1)
582                 fatal("/dev/cons2", 0, 0);
583
584         /* a pipe to simulate consctl */
585         if(bind("#|", "/mnt/consctl", MBEFORE) == -1
586         || bind("/mnt/consctl/data1", "/dev/consctl", MREPL) == -1)
587                 fatal("/dev/consctl", 0, 0);
588
589         /* a process to read /dev/consctl and set the state in cons */
590         switch(fork()){
591         case -1:
592                 fatal("forking", 0, 0);
593         case 0:
594                 break;
595         default:
596                 return open("/mnt/cons/data", ORDWR);
597         }
598
599         for(tries = 0; tries < 100; tries++){
600                 cons->raw = 0;
601                 cons->hold = 0;
602                 fd = open("/mnt/consctl/data", OREAD);
603                 if(fd < 0)
604                         continue;
605                 tries = 0;
606                 for(;;){
607                         n = read(fd, buf, sizeof(buf)-1);
608                         if(n <= 0)
609                                 break;
610                         buf[n] = 0;
611                         n = getfields(buf, field, 10, 1, " ");
612                         for(i = 0; i < n; i++){
613                                 if(strcmp(field[i], "rawon") == 0) {
614                                         if(debug) fprint(2, "raw = 1\n");
615                                         cons->raw = 1;
616                                 } else if(strcmp(field[i], "rawoff") == 0) {
617                                         if(debug) fprint(2, "raw = 0\n");
618                                         cons->raw = 0;
619                                 } else if(strcmp(field[i], "holdon") == 0) {
620                                         cons->hold = 1;
621                                         if(debug) fprint(2, "raw = 1\n");
622                                 } else if(strcmp(field[i], "holdoff") == 0) {
623                                         cons->hold = 0;
624                                         if(debug) fprint(2, "raw = 0\n");
625                                 }
626                         }
627                 }
628                 close(fd);
629         }
630         exits(0);
631         return -1;
632 }
633
634 int
635 alnum(int c)
636 {
637         /*
638          * Hard to get absolutely right.  Use what we know about ASCII
639          * and assume anything above the Latin control characters is
640          * potentially an alphanumeric.
641          */
642         if(c <= ' ')
643                 return 0;
644         if(0x7F<=c && c<=0xA0)
645                 return 0;
646         if(strchr("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c))
647                 return 0;
648         return 1;
649 }