xref: /plan9/sys/src/cmd/con/con.c (revision 3b86f2f88bade1f00206c7aa750b7add255f5724)
1 #include <u.h>
2 #include <libc.h>
3 
4 int debug;		/* true if debugging */
5 int ctl = -1;		/* control fd (for break's) */
6 int raw;		/* true if raw is on */
7 int consctl = -1;	/* control fd for cons */
8 int ttypid;		/* pid's if the 2 processes (used to kill them) */
9 int outfd = 1;		/* local output file descriptor */
10 int cooked;		/* non-zero forces cooked mode */
11 int returns;		/* non-zero forces carriage returns not to be filtered out */
12 int crtonl;			/* non-zero forces carriage returns to be converted to nls coming from net */
13 int	strip;		/* strip off parity bits */
14 char firsterr[2*ERRMAX];
15 char transerr[2*ERRMAX];
16 int limited;
17 char *remuser;		/* for BSD rlogin authentication */
18 int verbose;
19 int baud;
20 int notkbd;
21 int nltocr;		/* translate kbd nl to cr  and vice versa */
22 
23 static char *srv;
24 
25 #define MAXMSG (2*8192)
26 
27 int	dodial(char*, char*, char*);
28 void	fromkbd(int);
29 void	fromnet(int);
30 long	iread(int, void*, int);
31 long	iwrite(int, void*, int);
32 int	menu(int);
33 void	notifyf(void*, char*);
34 void	pass(int, int, int);
35 void	rawoff(void);
36 void	rawon(void);
37 void	stdcon(int);
38 char*	system(int, char*);
39 void	dosystem(int, char*);
40 int	wasintr(void);
41 void	punt(char*);
42 char*	syserr(void);
43 void	seterr(char*);
44 
45 /* protocols */
46 void	device(char*, char*);
47 void	rlogin(char*, char*);
48 void	simple(char*, char*);
49 
50 void
usage(void)51 usage(void)
52 {
53 	punt("usage: con [-CdnrRsTv] [-b baud] [-l [user]] [-c cmd] [-S svc] "
54 		"net!host[!service]");
55 }
56 
57 void
main(int argc,char * argv[])58 main(int argc, char *argv[])
59 {
60 	char *dest;
61 	char *cmd = 0;
62 
63 	returns = 1;
64 	ARGBEGIN{
65 	case 'b':
66 		baud = atoi(EARGF(usage()));
67 		break;
68 	case 'C':
69 		cooked = 1;
70 		break;
71 	case 'c':
72 		cmd = EARGF(usage());
73 		break;
74 	case 'd':
75 		debug = 1;
76 		break;
77 	case 'l':
78 		limited = 1;
79 		if(argv[1] != nil && argv[1][0] != '-')
80 			remuser = EARGF(usage());
81 		break;
82 	case 'n':
83 		notkbd = 1;
84 		break;
85 	case 'r':
86 		returns = 0;
87 		break;
88 	case 's':
89 		strip = 1;
90 		break;
91 	case 'S':
92 		srv = EARGF(usage());
93 		break;
94 	case 'R':
95 		nltocr = 1;
96 		break;
97 	case 'T':
98 		crtonl = 1;
99 		break;
100 	case 'v':
101 		verbose = 1;
102 		break;
103 	default:
104 		usage();
105 	}ARGEND
106 
107 	if(argc != 1){
108 		if(remuser == 0)
109 			usage();
110 		dest = remuser;
111 		remuser = 0;
112 	} else
113 		dest = argv[0];
114 	if(*dest == '/' && strchr(dest, '!') == 0)
115 		device(dest, cmd);
116 	else if(limited){
117 		simple(dest, cmd);	/* doesn't return if dialout succeeds */
118 		rlogin(dest, cmd);	/* doesn't return if dialout succeeds */
119 	} else {
120 		rlogin(dest, cmd);	/* doesn't return if dialout succeeds */
121 		simple(dest, cmd);	/* doesn't return if dialout succeeds */
122 	}
123 	punt(firsterr);
124 }
125 
126 /*
127  *  just dial and use as a byte stream with remote echo
128  */
129 void
simple(char * dest,char * cmd)130 simple(char *dest, char *cmd)
131 {
132 	int net;
133 
134 	net = dodial(dest, 0, 0);
135 	if(net < 0)
136 		return;
137 
138 	if(cmd)
139 		dosystem(net, cmd);
140 
141 	if(!cooked)
142 		rawon();
143 	stdcon(net);
144 	exits(0);
145 }
146 
147 /*
148  *  dial, do UCB authentication, use as a byte stream with local echo
149  *
150  *  return if dial failed
151  */
152 void
rlogin(char * dest,char * cmd)153 rlogin(char *dest, char *cmd)
154 {
155 	int net;
156 	char buf[128];
157 	char *p;
158 	char *localuser;
159 
160 	/* only useful on TCP */
161 	if(strchr(dest, '!')
162 	&& (strncmp(dest, "tcp!", 4)!=0 && strncmp(dest, "net!", 4)!=0))
163 		return;
164 
165 	net = dodial(dest, "tcp", "login");
166 	if(net < 0)
167 		return;
168 
169 	/*
170 	 *  do UCB rlogin authentication
171 	 */
172 	localuser = getuser();
173 	if(remuser == 0){
174 		if(limited)
175 			remuser = ":";
176 		else
177 			remuser = localuser;
178 	}
179 	p = getenv("TERM");
180 	if(p == 0)
181 		p = "p9";
182 	if(write(net, "", 1)<0
183 	|| write(net, localuser, strlen(localuser)+1)<0
184 	|| write(net, remuser, strlen(remuser)+1)<0
185 	|| write(net, p, strlen(p)+1)<0){
186 		close(net);
187 		punt("BSD authentication failed");
188 	}
189 	if(read(net, buf, 1) != 1)
190 		punt("BSD authentication failed1");
191 	if(buf[0] != 0){
192 		fprint(2, "con: remote error: ");
193 		while(read(net, buf, 1) == 1){
194 			write(2, buf, 1);
195 			if(buf[0] == '\n')
196 				break;
197 		}
198 		exits("read");
199 	}
200 
201 	if(cmd)
202 		dosystem(net, cmd);
203 
204 	if(!cooked)
205 		rawon();
206 	nltocr = 1;
207 	stdcon(net);
208 	exits(0);
209 }
210 
211 /*
212  *  just open a device and use it as a connection
213  */
214 void
device(char * dest,char * cmd)215 device(char *dest, char *cmd)
216 {
217 	int net;
218 	char cname[128];
219 
220 	net = open(dest, ORDWR);
221 	if(net < 0) {
222 		fprint(2, "con: cannot open %s: %r\n", dest);
223 		exits("open");
224 	}
225 	snprint(cname, sizeof cname, "%sctl", dest);
226 	ctl = open(cname, ORDWR);
227 	if (baud > 0) {
228 		if(ctl >= 0){
229 			/* set speed and use fifos if available */
230 			fprint(ctl, "b%d i1", baud);
231 		}
232 		else
233 			fprint(2, "con: cannot open %s: %r\n", cname);
234 	}
235 
236 	if(cmd)
237 		dosystem(net, cmd);
238 
239 	if(!cooked)
240 		rawon();
241 	stdcon(net);
242 	exits(0);
243 }
244 
245 /*
246  *  ignore interrupts
247  */
248 void
notifyf(void * a,char * msg)249 notifyf(void *a, char *msg)
250 {
251 	USED(a);
252 
253 	if(strstr(msg, "yankee"))
254 		noted(NDFLT);
255 	if(strstr(msg, "closed pipe")
256 	|| strcmp(msg, "interrupt") == 0
257 	|| strcmp(msg, "hangup") == 0)
258 		noted(NCONT);
259 	noted(NDFLT);
260 }
261 
262 /*
263  *  turn keyboard raw mode on
264  */
265 void
rawon(void)266 rawon(void)
267 {
268 	if(debug)
269 		fprint(2, "rawon\n");
270 	if(raw)
271 		return;
272 	if(consctl < 0)
273 		consctl = open("/dev/consctl", OWRITE);
274 	if(consctl < 0){
275 //		fprint(2, "can't open consctl\n");
276 		return;
277 	}
278 	write(consctl, "rawon", 5);
279 	raw = 1;
280 }
281 
282 /*
283  *  turn keyboard raw mode off
284  */
285 void
rawoff(void)286 rawoff(void)
287 {
288 	if(debug)
289 		fprint(2, "rawoff\n");
290 	if(raw == 0)
291 		return;
292 	if(consctl < 0)
293 		consctl = open("/dev/consctl", OWRITE);
294 	if(consctl < 0){
295 //		fprint(2, "can't open consctl\n");
296 		return;
297 	}
298 	write(consctl, "rawoff", 6);
299 	raw = 0;
300 }
301 
302 /*
303  *  control menu
304  */
305 #define STDHELP	"\t(b)reak, (q)uit, (i)nterrupt, toggle printing (r)eturns, (.)continue, (!cmd)\n"
306 
307 int
menu(int net)308 menu(int net)
309 {
310 	char buf[MAXMSG];
311 	long n;
312 	int done;
313 	int wasraw = raw;
314 
315 	if(wasraw)
316 		rawoff();
317 
318 	fprint(2, ">>> ");
319 	for(done = 0; !done; ){
320 		n = read(0, buf, sizeof(buf)-1);
321 		if(n <= 0)
322 			return -1;
323 		buf[n] = 0;
324 		switch(buf[0]){
325 		case '!':
326 			print(buf);
327 			system(net, buf+1);
328 			print("!\n");
329 			done = 1;
330 			break;
331 		case '.':
332 			done = 1;
333 			break;
334 		case 'q':
335 			return -1;
336 		case 'i':
337 			buf[0] = 0x1c;
338 			write(net, buf, 1);
339 			done = 1;
340 			break;
341 		case 'b':
342 			if(ctl >= 0)
343 				write(ctl, "k", 1);
344 			done = 1;
345 			break;
346 		case 'r':
347 			returns = 1-returns;
348 			done = 1;
349 			break;
350 		default:
351 			fprint(2, STDHELP);
352 			break;
353 		}
354 		if(!done)
355 			fprint(2, ">>> ");
356 	}
357 
358 	if(wasraw)
359 		rawon();
360 	else
361 		rawoff();
362 	return 0;
363 }
364 
365 void
post(char * srv,int fd)366 post(char *srv, int fd)
367 {
368 	int f;
369 	char buf[32];
370 
371 	f = create(srv, OWRITE /* |ORCLOSE */ , 0666);
372 	if(f < 0)
373 		sysfatal("create %s: %r", srv);
374 	snprint(buf, sizeof buf, "%d", fd);
375 	if(write(f, buf, strlen(buf)) != strlen(buf))
376 		sysfatal("write %s: %r", srv);
377 	close(f);
378 }
379 
380 /*
381  *  the real work.  two processes pass bytes back and forth between the
382  *  terminal and the network.
383  */
384 void
stdcon(int net)385 stdcon(int net)
386 {
387 	int netpid;
388 	int p[2];
389 	char *svc;
390 
391 	svc = nil;
392 	if (srv) {
393 		if(pipe(p) < 0)
394 			sysfatal("pipe: %r");
395 		if (srv[0] != '/')
396 			svc = smprint("/srv/%s", srv);
397 		else
398 			svc = srv;
399 		post(svc, p[0]);
400 		close(p[0]);
401 		dup(p[1], 0);
402 		dup(p[1], 1);
403 		/* pipe is now std in & out */
404 	}
405 	ttypid = getpid();
406 	switch(netpid = rfork(RFMEM|RFPROC)){
407 	case -1:
408 		perror("con");
409 		exits("fork");
410 	case 0:
411 		notify(notifyf);
412 		fromnet(net);
413 		if (svc)
414 			remove(svc);
415 		postnote(PNPROC, ttypid, "die yankee dog");
416 		exits(0);
417 	default:
418 		notify(notifyf);
419 		fromkbd(net);
420 		if (svc)
421 			remove(svc);
422 		if(notkbd)
423 			for(;;)
424 				sleep(0);
425 		postnote(PNPROC, netpid, "die yankee dog");
426 		exits(0);
427 	}
428 }
429 
430 /*
431  *  Read the keyboard and write it to the network.  '^\' gets us into
432  *  the menu.
433  */
434 void
fromkbd(int net)435 fromkbd(int net)
436 {
437 	long n;
438 	char buf[MAXMSG];
439 	char *p, *ep;
440 	int eofs;
441 
442 	eofs = 0;
443 	for(;;){
444 		n = read(0, buf, sizeof(buf));
445 		if(n < 0){
446 			if(wasintr()){
447 				if(!raw){
448 					buf[0] = 0x7f;
449 					n = 1;
450 				} else
451 					continue;
452 			} else
453 				return;
454 		}
455 		if(n == 0){
456 			if(++eofs > 32)
457 				return;
458 		} else
459 			eofs = 0;
460 		if(n && memchr(buf, 0x1c, n)){
461 			if(menu(net) < 0)
462 				return;
463 		}else{
464 			if(!raw && n==0){
465 				buf[0] = 0x4;
466 				n = 1;
467 			}
468 			if(nltocr){
469 				ep = buf+n;
470 				for(p = buf; p < ep; p++)
471 					switch(*p){
472 					case '\r':
473 						*p = '\n';
474 						break;
475 					case '\n':
476 						*p = '\r';
477 						break;
478 					}
479 			}
480 			if(iwrite(net, buf, n) != n)
481 				return;
482 		}
483 	}
484 }
485 
486 /*
487  *  Read from the network and write to the screen.
488  *  Filter out spurious carriage returns.
489  */
490 void
fromnet(int net)491 fromnet(int net)
492 {
493 	long n;
494 	char buf[MAXMSG];
495 	char *cp, *ep;
496 
497 	for(;;){
498 		n = iread(net, buf, sizeof(buf));
499 		if(n < 0)
500 			return;
501 		if(n == 0)
502 			continue;
503 
504 		if (strip)
505 			for (cp=buf; cp<buf+n; cp++)
506 				*cp &= 0177;
507 
508 		if(crtonl) {
509 			/* convert cr's to nl's */
510 			for (cp = buf; cp < buf + n; cp++)
511 				if (*cp == '\r')
512 					*cp = '\n';
513 		}
514 		else if(!returns){
515 			/* convert cr's to null's */
516 			cp = buf;
517 			ep = buf + n;
518 			while(cp < ep && (cp = memchr(cp, '\r', ep-cp))){
519 				memmove(cp, cp+1, ep-cp-1);
520 				ep--;
521 				n--;
522 			}
523 		}
524 
525 		if(n > 0 && iwrite(outfd, buf, n) != n){
526 			if(outfd == 1)
527 				return;
528 			outfd = 1;
529 			if(iwrite(1, buf, n) != n)
530 				return;
531 		}
532 	}
533 }
534 
535 /*
536  *  dial and return a data connection
537  */
538 int
dodial(char * dest,char * net,char * service)539 dodial(char *dest, char *net, char *service)
540 {
541 	char name[128];
542 	char devdir[128];
543 	int data;
544 
545 	devdir[0] = 0;
546 	strcpy(name, netmkaddr(dest, net, service));
547 	data = dial(name, 0, devdir, &ctl);
548 	if(data < 0){
549 		seterr(name);
550 		return -1;
551 	}
552 	fprint(2, "connected to %s on %s\n", name, devdir);
553 	return data;
554 }
555 
556 void
dosystem(int fd,char * cmd)557 dosystem(int fd, char *cmd)
558 {
559 	char *p;
560 
561 	p = system(fd, cmd);
562 	if(p){
563 		print("con: %s terminated with %s\n", cmd, p);
564 		exits(p);
565 	}
566 }
567 
568 /*
569  *  run a command with the network connection as standard IO
570  */
571 char *
system(int fd,char * cmd)572 system(int fd, char *cmd)
573 {
574 	int pid;
575 	int p;
576 	static Waitmsg msg;
577 	int pfd[2];
578 	int n;
579 	char buf[4096];
580 
581 	if(pipe(pfd) < 0){
582 		perror("pipe");
583 		return "pipe failed";
584 	}
585 	outfd = pfd[1];
586 
587 	close(consctl);
588 	consctl = -1;
589 	switch(pid = fork()){
590 	case -1:
591 		perror("con");
592 		return "fork failed";
593 	case 0:
594 		close(pfd[1]);
595 		dup(pfd[0], 0);
596 		dup(fd, 1);
597 		close(ctl);
598 		close(fd);
599 		close(pfd[0]);
600 		if(*cmd)
601 			execl("/bin/rc", "rc", "-c", cmd, nil);
602 		else
603 			execl("/bin/rc", "rc", nil);
604 		perror("con");
605 		exits("exec");
606 		break;
607 	default:
608 		close(pfd[0]);
609 		while((n = read(pfd[1], buf, sizeof(buf))) > 0)
610 			if(write(fd, buf, n) != n)
611 				break;
612 		p = waitpid();
613 		outfd = 1;
614 		close(pfd[1]);
615 		if(p < 0 || p != pid)
616 			return "lost child";
617 		break;
618 	}
619 	return msg.msg;
620 }
621 
622 int
wasintr(void)623 wasintr(void)
624 {
625 	return strcmp(syserr(), "interrupted") == 0;
626 }
627 
628 void
punt(char * msg)629 punt(char *msg)
630 {
631 	if(*msg == 0)
632 		msg = transerr;
633 	fprint(2, "con: %s\n", msg);
634 	exits(msg);
635 }
636 
637 char*
syserr(void)638 syserr(void)
639 {
640 	static char err[ERRMAX];
641 	errstr(err, sizeof err);
642 	return err;
643 }
644 
645 void
seterr(char * addr)646 seterr(char *addr)
647 {
648 	char *se = syserr();
649 
650 	if(verbose)
651 		fprint(2, "'%s' calling %s\n", se, addr);
652 	if(firsterr[0] && (strstr(se, "translate") ||
653 	 strstr(se, "file does not exist") ||
654 	 strstr(se, "unknown address") ||
655 	 strstr(se, "directory entry not found")))
656 		return;
657 	strcpy(firsterr, se);
658 }
659 
660 
661 long
iread(int f,void * a,int n)662 iread(int f, void *a, int n)
663 {
664 	long m;
665 
666 	for(;;){
667 		m = read(f, a, n);
668 		if(m >= 0 || !wasintr())
669 			break;
670 	}
671 	return m;
672 }
673 
674 long
iwrite(int f,void * a,int n)675 iwrite(int f, void *a, int n)
676 {
677 	long m;
678 
679 	m = write(f, a, n);
680 	if(m < 0 && wasintr())
681 		return n;
682 	return m;
683 }
684