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