xref: /plan9/sys/src/cmd/ip/telnet.c (revision aaee222289330fcec0b5b011953f18cc626114b4)
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