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