]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/telnet.c
snoopy(8): avoid extra spaces in dhcp filter output
[plan9front.git] / sys / src / cmd / ip / telnet.c
1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include "telnet.h"
5
6 int ctl = -1;           /* control fd (for break's) */
7 int consctl = -1;       /* consctl fd */
8
9 int ttypid;             /* pid's if the 2 processes (used to kill them) */
10 int netpid;
11 int interrupted;
12 int localecho;
13 int notkbd;
14 int returns;
15 int stopped;
16
17 static char *srv;
18
19 int     dodial(char*);
20 void    fromkbd(int);
21 void    fromnet(int);
22 int     menu(Biobuf*,  int);
23 void    notifyf(void*, char*);
24 void    rawoff(void);
25 void    rawon(void);
26 void    telnet(int);
27 char*   system(int, char*);
28 int     echochange(Biobuf*, int);
29 int     termsub(Biobuf*, uchar*, int);
30 int     xlocsub(Biobuf*, uchar*, int);
31
32 static int islikeatty(int);
33
34 void
35 usage(void)
36 {
37         fatal("usage: telnet [-Cdnr] [-s srv] net!host[!service]", 0, 0);
38 }
39
40 void
41 main(int argc, char *argv[])
42 {
43         returns = 1;
44         ARGBEGIN{
45         case 'C':
46                 opt[Echo].noway = 1;
47                 break;
48         case 'd':
49                 debug = 1;
50                 break;
51         case 'n':
52                 notkbd = 1;
53                 break; 
54         case 'r':
55                 returns = 0;
56                 break;
57         case 's':
58                 srv = EARGF(usage());
59                 break;
60         default:
61                 usage();
62         }ARGEND
63
64         if(argc != 1)
65                 usage();
66
67         /* options we need routines for */
68         opt[Echo].change = echochange;
69         opt[Term].sub = termsub;
70         opt[Xloc].sub = xlocsub;
71
72         telnet(dodial(argv[0]));
73 }
74
75 /*
76  *  dial and return a data connection
77  */
78 int
79 dodial(char *dest)
80 {
81         char *name;
82         int data;
83         char devdir[NETPATHLEN];
84
85         name = netmkaddr(dest, "tcp", "telnet");
86         data = dial(name, 0, devdir, 0);
87         if(data < 0)
88                 fatal("%s: %r", name, 0);
89         if(srv != nil){
90                 if(rfork(RFPROC | RFNOWAIT | RFNOTEG) != 0)
91                         exits("");
92         }
93         else
94                 fprint(2, "connected to %s on %s\n", name, devdir);
95         return data;
96 }
97
98 void
99 post(char *srv, int fd)
100 {
101         int f;
102         char buf[32];
103
104         f = create(srv, OWRITE, 0666);
105         if(f < 0)
106                 sysfatal("create %s: %r", srv);
107         snprint(buf, sizeof buf, "%d", fd);
108         if(write(f, buf, strlen(buf)) != strlen(buf))
109                 sysfatal("write %s: %r", srv);
110         close(f);
111 }
112
113 /*
114  *  two processes pass bytes back and forth between the
115  *  terminal and the network.
116  */
117 void
118 telnet(int net)
119 {
120         int pid;
121         int p[2];
122         char *svc;
123
124         rawoff();
125         svc = nil;
126         if (srv) {
127                 if(pipe(p) < 0)
128                         sysfatal("pipe: %r");
129                 if (srv[0] != '/')
130                         svc = smprint("/srv/%s", srv);
131                 else
132                         svc = srv;
133                 post(svc, p[0]);
134                 close(p[0]);
135                 dup(p[1], 0);
136                 dup(p[1], 1);
137                 /* pipe is now std in & out */
138         }
139         ttypid = getpid();
140         switch(pid = rfork(RFPROC|RFFDG|RFMEM)){
141         case -1:
142                 perror("con");
143                 exits("fork");
144         case 0:
145                 rawoff();
146                 notify(notifyf);
147                 fromnet(net);
148                 if (svc)
149                         remove(svc);
150                 sendnote(ttypid, "die");
151                 exits(0);
152         default:
153                 netpid = pid;
154                 notify(notifyf);
155                 fromkbd(net);
156                 if (notkbd)
157                         while(waitpid() != pid)
158                                 sleep(0);
159                 if (svc)
160                         remove(svc);
161                 sendnote(netpid, "die");
162                 exits(0);
163         }
164 }
165
166 /*
167  *  Read the keyboard and write it to the network.  '^\' gets us into
168  *  the menu.
169  */
170 void
171 fromkbd(int net)
172 {
173         Biobuf ib, ob;
174         int c, likeatty;
175         int eofs;
176
177         Binit(&ib, 0, OREAD);
178         Binit(&ob, net, OWRITE);
179
180         likeatty = islikeatty(0);
181         eofs = 0;
182         for(;;){
183                 c = Bgetc(&ib);
184
185                 /*
186                  *  with raw off, all ^D's get turned into Eof's.
187                  *  change them back.
188                  *  10 in a row implies that the terminal is really gone so
189                  *  just hang up.
190                  */
191                 if(c < 0){
192                         if(notkbd)
193                                 return;
194                         if(eofs++ > 10)
195                                 return;
196                         c = 004;
197                 } else
198                         eofs = 0;
199
200                 /*
201                  *  if not in binary mode, look for the ^\ escape to menu.
202                  *  also turn \n into \r\n
203                  */
204                 if(likeatty || !opt[Binary].local){
205                         if(c == 0034){ /* CTRL \ */
206                                 if(Bflush(&ob) < 0)
207                                         return;
208                                 if(menu(&ib, net) < 0)
209                                         return;
210                                 continue;
211                         }
212                 }
213                 if(!opt[Binary].local){
214                         if(c == '\n'){
215                                 /*
216                                  *  This is a very strange use of the SGA option.
217                                  *  I did this because some systems that don't
218                                  *  announce a willingness to supress-go-ahead
219                                  *  need the \r\n sequence to recognize input.
220                                  *  If someone can explain this to me, please
221                                  *  send me mail. - presotto
222                                  */
223                                 if(opt[SGA].remote){
224                                         c = '\r';
225                                 } else {
226                                         if(Bputc(&ob, '\r') < 0)
227                                                 return;
228                                 }
229                         }
230                 }
231                 if(Bputc(&ob, c) < 0)
232                         return;
233                 if(Bbuffered(&ib) == 0)
234                         if(Bflush(&ob) < 0)
235                                 return;
236         }
237 }
238
239 /*
240  *  Read from the network and write to the screen.  If 'stopped' is set
241  *  spin and don't read.  Filter out spurious carriage returns.
242  */
243 void
244 fromnet(int net)
245 {
246         int c;
247         int crnls = 0, freenl = 0, eofs;
248         Biobuf ib, ob;
249
250         Binit(&ib, net, OREAD);
251         Binit(&ob, 1, OWRITE);
252         eofs = 0;
253         for(;;){
254                 if(Bbuffered(&ib) == 0)
255                         Bflush(&ob);
256                 if(interrupted){
257                         interrupted = 0;
258                         send2(net, Iac, Interrupt);
259                 }
260                 c = Bgetc(&ib);
261                 if(c < 0){
262                         if(eofs++ >= 2)
263                                 return;
264                         continue;
265                 }
266                 eofs = 0;
267                 switch(c){
268                 case '\n':      /* skip nl after string of cr's */
269                         if(!opt[Binary].local && !returns){
270                                 ++crnls;
271                                 if(freenl == 0)
272                                         break;
273                                 freenl = 0;
274                                 continue;
275                         }
276                         break;
277                 case '\r':      /* first cr becomes nl, remainder dropped */
278                         if(!opt[Binary].local && !returns){
279                                 if(crnls++ == 0){
280                                         freenl = 1;
281                                         c = '\n';
282                                         break;
283                                 }
284                                 continue;
285                         }
286                         break;
287                 case 0:         /* remove nulls from crnl string */
288                         if(crnls)
289                                 continue;
290                         break;
291
292                 case Iac:
293                         crnls = 0;
294                         freenl = 0;
295                         c = Bgetc(&ib);
296                         if(c == Iac)
297                                 break;
298                         if(Bflush(&ob) < 0)
299                                 return;
300                         if(control(&ib, c) < 0)
301                                 return;
302                         continue;
303
304                 default:
305                         crnls = 0;
306                         freenl = 0;
307                         break;
308                 }
309                 if(Bputc(&ob, c) < 0)
310                         return;
311         }
312 }
313
314 void
315 consctlcmd(char *s)
316 {
317         if(srv != nil || notkbd)
318                 return;
319         if(debug)
320                 fprint(2, "consctl: %s\n", s);
321         if(consctl < 0)
322                 consctl = open("/dev/consctl", OWRITE);
323         if(consctl < 0){
324                 fprint(2, "can't open consctl: %r\n");
325                 return;
326         }
327         write(consctl, s, strlen(s));
328 }
329
330 /*
331  *  turn keyboard raw mode on
332  */
333 void
334 rawon(void)
335 {
336         consctlcmd("rawon");
337 }
338
339 /*
340  *  turn keyboard raw mode off
341  */
342 void
343 rawoff(void)
344 {
345         consctlcmd("rawoff");
346 }
347
348 /*
349  *  control menu
350  */
351 #define STDHELP "\t(b)reak, (i)nterrupt, (q)uit, (r)eturns, (!cmd), (.)continue\n"
352
353 int
354 menu(Biobuf *bp, int net)
355 {
356         char *cp;
357         int done;
358
359         stopped = 1;
360
361         rawoff();
362         fprint(2, ">>> ");
363         for(done = 0; !done; ){
364                 cp = Brdline(bp, '\n');
365                 if(cp == 0){
366                         stopped = 0;
367                         return -1;
368                 }
369                 cp[Blinelen(bp)-1] = 0;
370                 switch(*cp){
371                 case '!':
372                         system(Bfildes(bp), cp+1);
373                         done = 1;
374                         break;
375                 case '.':
376                         done = 1;
377                         break;
378                 case 'q':
379                         stopped = 0;
380                         return -1;
381                 case 'o':
382                         switch(*(cp+1)){
383                         case 'd':
384                                 send3(net, Iac, Do, atoi(cp+2));
385                                 break;
386                         case 'w':
387                                 send3(net, Iac, Will, atoi(cp+2));
388                                 break;
389                         }
390                         break;
391                 case 'r':
392                         returns = !returns;
393                         done = 1;
394                         break;
395                 case 'i':
396                         send2(net, Iac, Interrupt);
397                         break;
398                 case 'b':
399                         send2(net, Iac, Break);
400                         break;
401                 default:
402                         fprint(2, STDHELP);
403                         break;
404                 }
405                 if(!done)
406                         fprint(2, ">>> ");
407         }
408
409         rawon();
410         stopped = 0;
411         return 0;
412 }
413
414 /*
415  *  ignore interrupts
416  */
417 void
418 notifyf(void *a, char *msg)
419 {
420         USED(a);
421         if(strcmp(msg, "interrupt") == 0){
422                 interrupted = 1;
423                 noted(NCONT);
424         }
425         if(strcmp(msg, "hangup") == 0)
426                 noted(NCONT);
427         noted(NDFLT);
428 }
429
430 /*
431  *  run a command with the network connection as standard IO
432  */
433 char *
434 system(int fd, char *cmd)
435 {
436         int pid;
437         int p;
438         static Waitmsg msg;
439
440         if((pid = fork()) == -1){
441                 perror("con");
442                 return "fork failed";
443         }
444         else if(pid == 0){
445                 dup(fd, 0);
446                 close(ctl);
447                 close(fd);
448                 if(*cmd)
449                         execl("/bin/rc", "rc", "-c", cmd, nil);
450                 else
451                         execl("/bin/rc", "rc", nil);
452                 perror("con");
453                 exits("exec");
454         }
455         for(p = waitpid(); p >= 0; p = waitpid()){
456                 if(p == pid)
457                         return msg.msg; 
458         }
459         return "lost child";
460 }
461
462 /*
463  *  suppress local echo if the remote side is doing it
464  */
465 int
466 echochange(Biobuf *bp, int cmd)
467 {
468         USED(bp);
469
470         switch(cmd){
471         case Will:
472                 rawon();
473                 break;
474         case Wont:
475                 rawoff();
476                 break;
477         }
478         return 0;
479 }
480
481 /*
482  *  send terminal type to the other side
483  */
484 int
485 termsub(Biobuf *bp, uchar *sub, int n)
486 {
487         char buf[64];
488         char *term;
489         char *p = buf;
490
491         if(n < 1)
492                 return 0;
493         if(sub[0] == 1){
494                 *p++ = Iac;
495                 *p++ = Sb;
496                 *p++ = opt[Term].code;
497                 *p++ = 0;
498                 term = getenv("TERM");
499                 if(term == 0 || *term == 0)
500                         term = "p9win";
501                 strncpy(p, term, sizeof(buf) - (p - buf) - 2);
502                 buf[sizeof(buf)-2] = 0;
503                 p += strlen(p);
504                 *p++ = Iac;
505                 *p++ = Se;
506                 return iwrite(Bfildes(bp), buf, p-buf);
507         }
508         return 0;
509 }
510
511 /*
512  *  send an x display location to the other side
513  */
514 int
515 xlocsub(Biobuf *bp, uchar *sub, int n)
516 {
517         char buf[64];
518         char *term;
519         char *p = buf;
520
521         if(n < 1)
522                 return 0;
523         if(sub[0] == 1){
524                 *p++ = Iac;
525                 *p++ = Sb;
526                 *p++ = opt[Xloc].code;
527                 *p++ = 0;
528                 term = getenv("XDISP");
529                 if(term == 0 || *term == 0)
530                         term = "unknown";
531                 strncpy(p, term, p - buf - 2);
532                 p += strlen(term);
533                 *p++ = Iac;
534                 *p++ = Se;
535                 return iwrite(Bfildes(bp), buf, p-buf);
536         }
537         return 0;
538 }
539
540 static int
541 islikeatty(int fd)
542 {
543         char buf[64];
544
545         if(fd2path(fd, buf, sizeof buf) != 0)
546                 return 0;
547
548         /* might be /mnt/term/dev/cons */
549         return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
550 }