xref: /illumos-gate/usr/src/cmd/tip/tip.c (revision 1e094e1b)
1 /*
2  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 /*
7  * Copyright (c) 1983 Regents of the University of California.
8  * All rights reserved.  The Berkeley software License Agreement
9  * specifies the terms and conditions for redistribution.
10  */
11 
12 /*
13  * tip - UNIX link to other systems
14  *  tip [-v] [-speed] system-name
15  * or
16  *  cu phone-number [-s speed] [-l line] [-a acu]
17  */
18 #include "tip.h"
19 #include <sys/wait.h>
20 #include <locale.h>
21 
22 int	vflag;
23 int	cumode;
24 FILE	*phfd;
25 
26 /*
27  * Baud rate mapping table
28  */
29 int bauds[] = {
30 	0, 50, 75, 110, 134, 150, 200, 300, 600,
31 	1200, 1800, 2400, 4800, 9600, 19200, 38400,
32 	57600, 76800, 115200, 153600, 230400, 307200,
33 	460800, 921600, -1
34 };
35 
36 extern void	tipout(void)	__NORETURN;
37 extern void	timeout(void);
38 extern esctable_t	etable[];
39 extern unsigned char	evenpartab[];
40 
41 void	intprompt(void);
42 void	deadkid(void);
43 void	cleanup(void);
44 void	tipin(void)	__NORETURN;
45 unsigned char	escape(void);
46 char	*sname(char *);
47 char	PNbuf[256];		/* This limits the size of a number */
48 int	noparity = 0;
49 
50 int
51 main(int argc, char *argv[])
52 {
53 	char *system = NOSTR;
54 	int i;
55 	char *p;
56 	char sbuf[15];
57 
58 	gid = getgid();
59 	egid = getegid();
60 	uid = getuid();
61 	euid = geteuid();
62 	if (equal(sname(argv[0]), "cu")) {
63 		cumode = 1;
64 		cumain(argc, argv);
65 		goto cucommon;
66 	}
67 
68 	if (argc > 4) {
69 		(void) fprintf(stderr,
70 		    "usage: tip [-v] [-speed] [system-name]\n");
71 		return (1);
72 	}
73 	if (!isatty(0)) {
74 		(void) fprintf(stderr, "tip: must be interactive\n");
75 		return (1);
76 	}
77 
78 	for (; argc > 1; argv++, argc--) {
79 		if (argv[1][0] != '-')
80 			system = argv[1];
81 		else switch (argv[1][1]) {
82 
83 		case 'v':
84 			vflag++;
85 			break;
86 
87 		case '0': case '1': case '2': case '3': case '4':
88 		case '5': case '6': case '7': case '8': case '9':
89 			BR = atoi(&argv[1][1]);
90 			break;
91 
92 		default:
93 			(void) fprintf(stderr, "tip: %s, unknown option\n",
94 			    argv[1]);
95 			break;
96 		}
97 	}
98 
99 	(void) setlocale(LC_CTYPE, "");
100 
101 	if (system == NOSTR)
102 		goto notnumber;
103 	for (p = system; *p; p++)
104 		if (isalpha(*p))
105 			goto notnumber;
106 	/*
107 	 * System name is really a phone number...
108 	 * Copy the number then stomp on the original (in case the number
109 	 *	is private, we don't want 'ps' or 'w' to find it).
110 	 */
111 	if (strlen(system) > sizeof (PNbuf) - 1) {
112 		(void) fprintf(stderr,
113 		    "tip: phone number too long (max = %d bytes)\n",
114 		    sizeof (PNbuf) - 1);
115 		return (1);
116 	}
117 	(void) strncpy(PNbuf, system, sizeof (PNbuf) - 1);
118 	for (p = system; *p; p++)
119 		*p = '\0';
120 	PN = PNbuf;
121 	(void) snprintf(sbuf, sizeof (sbuf), "tip%d", BR);
122 	system = sbuf;
123 
124 notnumber:
125 	(void) signal(SIGINT, (sig_handler_t)cleanup);
126 	(void) signal(SIGQUIT, (sig_handler_t)cleanup);
127 	(void) signal(SIGHUP, (sig_handler_t)cleanup);
128 	(void) signal(SIGTERM, (sig_handler_t)cleanup);
129 
130 	if ((i = hunt(system)) == 0) {
131 		(void) printf("all ports busy\n");
132 		return (3);
133 	}
134 	if (i == -1) {
135 		(void) printf("link down\n");
136 		delock(uucplock);
137 		return (3);
138 	}
139 	setbuf(stdout, NULL);
140 	loginit();
141 
142 	/*
143 	 * Now that we have the logfile and the ACU open
144 	 *  return to the real uid and gid.  These things will
145 	 *  be closed on exit.  The saved-setuid uid and gid
146 	 *  allows us to get the original setuid permissions back
147 	 *  for removing the uucp lock.
148 	 */
149 	userperm();
150 
151 	/*
152 	 * Kludge, there's no easy way to get the initialization
153 	 *   in the right order, so force it here.
154 	 * Do the open here, before we change back to real uid.
155 	 * We will check whether the open succeeded later, when
156 	 * (and if) we actually go to use the file.
157 	 */
158 	if ((PH = getenv("PHONES")) == NOSTR) {
159 		myperm();
160 		PH = "/etc/phones";
161 	}
162 	phfd = fopen(PH, "r");
163 
164 	userperm();
165 
166 	vinit();				/* init variables */
167 	setparity("none");			/* set the parity table */
168 	if ((i = speed(number(value(BAUDRATE)))) == 0) {
169 		(void) printf("tip: bad baud rate %d\n",
170 		    number(value(BAUDRATE)));
171 		myperm();
172 		delock(uucplock);
173 		return (3);
174 	}
175 
176 
177 	/*
178 	 * Hardwired connections require the
179 	 *  line speed set before they make any transmissions
180 	 *  (this is particularly true of things like a DF03-AC)
181 	 */
182 	if (HW)
183 		ttysetup(i);
184 	if (p = connect()) {
185 		(void) printf("\07%s\n[EOT]\n", p);
186 		myperm();
187 		delock(uucplock);
188 		return (1);
189 	}
190 
191 	/*
192 	 * Always setup the tty again here in case hardware flow
193 	 *  control was selected, which can only be set after the
194 	 *  connection is made, or in case this is not a hardwired
195 	 *  modem (rare these days) that likewise can only be setup
196 	 *  after the connection is made.
197 	 */
198 	ttysetup(i);
199 cucommon:
200 	/*
201 	 * From here down the code is shared with
202 	 * the "cu" version of tip.
203 	 */
204 
205 	(void) ioctl(0, TCGETS, (char *)&defarg);
206 	arg = defarg;
207 	/* turn off input processing */
208 	arg.c_lflag &= ~(ICANON|ISIG|ECHO|IEXTEN);
209 	arg.c_cc[VMIN] = 1;
210 	arg.c_cc[VTIME] = 0;
211 	arg.c_iflag &= ~(INPCK|IXON|IXOFF|ICRNL);
212 	arg.c_oflag = 0;		/* turn off all output processing */
213 	/* handle tandem mode in case was set in remote file */
214 	if (boolean(value(TAND)))
215 		tandem("on");
216 	else
217 		tandem("off");
218 	raw();
219 
220 	(void) pipe(fildes); (void) pipe(repdes);
221 	(void) signal(SIGALRM, (sig_handler_t)timeout);
222 
223 	/*
224 	 * Everything's set up now:
225 	 *	connection established (hardwired or dialup)
226 	 *	line conditioned (baud rate, mode, etc.)
227 	 *	internal data structures (variables)
228 	 * so, fork one process for local side and one for remote.
229 	 */
230 	if (CM != NOSTR) {
231 		(void) sleep(2);	/* let line settle */
232 		parwrite(FD, (unsigned char *)CM, strlen(CM));
233 	}
234 	(void) printf(cumode ? "Connected\r\n" : "\07connected\r\n");
235 	(void) signal(SIGCHLD, (sig_handler_t)deadkid);
236 	if (pid = fork())
237 		tipin();
238 	else
239 		tipout();
240 	/*NOTREACHED*/
241 }
242 
243 void
244 deadkid(void)
245 {
246 
247 	if (pid >= 0 && waitpid(pid, NULL, WNOHANG) == pid)
248 		tip_abort("Connection Closed");
249 }
250 
251 void
252 cleanup(void)
253 {
254 
255 	if (uid != getuid()) {
256 		myperm();
257 	}
258 	delock(uucplock);
259 	exit(0);
260 }
261 
262 /*
263  * put the controlling keyboard into raw mode
264  */
265 void
266 raw(void)
267 {
268 
269 	(void) ioctl(0, TCSETSF, (char *)&arg);
270 }
271 
272 
273 /*
274  * return keyboard to normal mode
275  */
276 void
277 unraw(void)
278 {
279 
280 	(void) ioctl(0, TCSETSF, (char *)&defarg);
281 }
282 
283 /*
284  * switch to using invoking user's permissions
285  */
286 void
287 userperm(void)
288 {
289 
290 	(void) setegid(gid);
291 	(void) seteuid(uid);
292 }
293 
294 /*
295  * switch to using my special (setuid) permissions
296  */
297 void
298 myperm(void)
299 {
300 
301 	(void) setegid(egid);
302 	(void) seteuid(euid);
303 }
304 
305 static	sigjmp_buf promptbuf;
306 
307 /*
308  * Print string ``s'', then read a string
309  *  in from the terminal.  Handles signals & allows use of
310  *  normal erase and kill characters.
311  */
312 int
313 prompt(char *s, char *p, size_t len)
314 {
315 	char *b = p;
316 	int c;
317 	sig_handler_t	ointr, oquit;
318 
319 	stoprompt = 0;
320 	ointr = signal(SIGINT, (sig_handler_t)intprompt);
321 	oquit = signal(SIGQUIT, SIG_IGN);
322 	unraw();
323 	(void) printf("%s", s);
324 	if (sigsetjmp(promptbuf, 1) == 0)
325 		while (p < b + len - 1 &&
326 		    ((c = getchar()) != EOF) && (c != '\n'))
327 			*p++ = c;
328 	*p = '\0';
329 
330 	raw();
331 	(void) signal(SIGINT, ointr);
332 	(void) signal(SIGQUIT, oquit);
333 	return (stoprompt || p == b);
334 }
335 
336 /*
337  * Interrupt service routine during prompting
338  */
339 void
340 intprompt(void)
341 {
342 
343 	(void) signal(SIGINT, SIG_IGN);
344 	(void) signal(SIGQUIT, SIG_IGN);
345 	stoprompt = 1;
346 	(void) printf("\r\n");
347 	siglongjmp(promptbuf, 1);
348 }
349 
350 /*
351  * ****TIPIN   TIPIN****
352  */
353 void
354 tipin(void)
355 {
356 	unsigned char gch, c;
357 	int bol = 1;
358 
359 	/*
360 	 * Kinda klugey here...
361 	 *   check for scripting being turned on from the .tiprc file,
362 	 *   but be careful about just using setscript(), as we may
363 	 *   send a SIGEMT before tipout has a chance to set up catching
364 	 *   it; so wait a second, then setscript()
365 	 */
366 	if (boolean(value(SCRIPT))) {
367 		(void) sleep(1);
368 		setscript();
369 	}
370 
371 	for (;;) {
372 		gch = getchar()&0377;
373 		if ((gch == character(value(ESCAPE))) && bol) {
374 			if (!(gch = escape()))
375 				continue;
376 		} else if (!cumode && gch == character(value(RAISECHAR))) {
377 			boolean(value(RAISE)) = !boolean(value(RAISE));
378 			continue;
379 		} else if (gch == '\r') {
380 			bol = 1;
381 			parwrite(FD, &gch, 1);
382 			if (boolean(value(HALFDUPLEX)))
383 				(void) printf("\r\n");
384 			continue;
385 		} else if (!cumode && gch == character(value(FORCE)))
386 			gch = getchar()&0377;
387 		bol = any(gch, value(EOL));
388 		if (boolean(value(RAISE)) && islower(gch))
389 			gch = toupper(gch);
390 		c = gch;
391 		parwrite(FD, &gch, 1);
392 		if (boolean(value(HALFDUPLEX)))
393 			(void) putchar(c);
394 	}
395 }
396 
397 /*
398  * Escape handler --
399  *  called on recognition of ``escapec'' at the beginning of a line
400  */
401 unsigned char
402 escape(void)
403 {
404 	unsigned char gch;
405 	esctable_t *p;
406 	char c = character(value(ESCAPE));
407 
408 	gch = (getchar()&0377);
409 	for (p = etable; p->e_char; p++)
410 		if (p->e_char == gch) {
411 			if ((p->e_flags&PRIV) && uid)
412 				continue;
413 			(void) printf("%s", ctrl(c));
414 			(*p->e_func)(gch);
415 			return (0);
416 		}
417 	/* ESCAPE ESCAPE forces ESCAPE */
418 	if (c != gch)
419 		parwrite(FD, (unsigned char *)&c, 1);
420 	return (gch);
421 }
422 
423 int
424 speed(int n)
425 {
426 	int *p;
427 
428 	for (p = bauds; *p != -1;  p++)
429 		if (*p == n)
430 			return (p - bauds);
431 	return (0);
432 }
433 
434 int
435 any(char c, char *p)
436 {
437 	while (p && *p)
438 		if (*p++ == c)
439 			return (1);
440 	return (0);
441 }
442 
443 char *
444 interp(char *s)
445 {
446 	static char buf[256];
447 	char *p = buf, c, *q;
448 
449 	while (c = *s++) {
450 		for (q = "\nn\rr\tt\ff\033E\bb"; *q; q++)
451 			if (*q++ == c) {
452 				*p++ = '\\'; *p++ = *q;
453 				goto next;
454 			}
455 		if (c < 040) {
456 			*p++ = '^'; *p++ = c + 'A'-1;
457 		} else if (c == 0177) {
458 			*p++ = '^'; *p++ = '?';
459 		} else
460 			*p++ = c;
461 	next:
462 		;
463 	}
464 	*p = '\0';
465 	return (buf);
466 }
467 
468 char *
469 ctrl(char c)
470 {
471 	static char s[3];
472 
473 	if (c < 040 || c == 0177) {
474 		s[0] = '^';
475 		s[1] = c == 0177 ? '?' : c+'A'-1;
476 		s[2] = '\0';
477 	} else {
478 		s[0] = c;
479 		s[1] = '\0';
480 	}
481 	return (s);
482 }
483 
484 /*
485  * Help command
486  */
487 void
488 help(int c)
489 {
490 	esctable_t *p;
491 
492 	(void) printf("%c\r\n", c);
493 	for (p = etable; p->e_char; p++) {
494 		if ((p->e_flags&PRIV) && uid)
495 			continue;
496 		(void) printf("%2s", ctrl(character(value(ESCAPE))));
497 		(void) printf("%-2s %c   %s\r\n", ctrl(p->e_char),
498 		    p->e_flags&EXP ? '*': ' ', p->e_help);
499 	}
500 }
501 
502 /*
503  * Set up the "remote" tty's state
504  */
505 void
506 ttysetup(int speed)
507 {
508 	struct termios buf;
509 	char *loc;
510 
511 	(void) ioctl(FD, TCGETS, (char *)&buf);
512 	buf.c_cflag &= (CREAD|HUPCL|CLOCAL|CRTSCTS|CRTSXOFF);
513 	buf.c_cflag |= CS8;
514 	(void) cfsetospeed(&buf, speed);
515 	if (boolean(value(HARDWAREFLOW))) {
516 		int i = TIOCM_CAR;
517 
518 		/*
519 		 * Only set hardware flow control if carrier is up,
520 		 * because some devices require both CD and RTS to
521 		 * be up before sending.
522 		 */
523 		(void) ioctl(FD, TIOCMGET, &i);
524 		if (i & TIOCM_CAR)
525 			buf.c_cflag |= (CRTSCTS|CRTSXOFF);
526 	}
527 
528 	/*
529 	 * Careful to only penalize the 8-bit users here on the
530 	 * incoming tty port.  The default 7-bit users will
531 	 * still get the parity bit from the other side's login
532 	 * process (which happens to be the default for sun tip
533 	 * configurations).
534 	 */
535 	loc = setlocale(LC_CTYPE, NULL);
536 	if (noparity && loc != 0 && strcmp(loc, "C") != 0)
537 		buf.c_iflag = 0;
538 	else
539 		buf.c_iflag = ISTRIP;
540 	buf.c_oflag = 0;
541 	buf.c_lflag = 0;
542 	buf.c_cc[VMIN] = 1;
543 	buf.c_cc[VTIME] = 0;
544 	(void) ioctl(FD, TCSETSF, (char *)&buf);
545 }
546 
547 /*
548  * Return "simple" name from a file name,
549  * strip leading directories.
550  */
551 char *
552 sname(char *s)
553 {
554 	char *p = s;
555 
556 	while (*s)
557 		if (*s++ == '/')
558 			p = s;
559 	return (p);
560 }
561 
562 static char partab[0400];
563 
564 /*
565  * Do a write to the remote machine with the correct parity.
566  * We are doing 8 bit wide output, so we just generate a character
567  * with the right parity and output it.
568  */
569 void
570 parwrite(int fd, unsigned char *buf, int n)
571 {
572 	int i;
573 	unsigned char *bp;
574 
575 	bp = buf;
576 	for (i = 0; i < n; i++) {
577 		*bp = partab[(*bp)&0377];
578 		bp++;
579 	}
580 	if (write(fd, buf, n) < 0) {
581 		if (errno == EIO || errno == ENXIO)
582 			tip_abort("Lost carrier.");
583 		/* this is questionable */
584 		perror("write");
585 	}
586 }
587 
588 /*
589  * Build a parity table with appropriate high-order bit.
590  */
591 void
592 setparity(char *defparity)
593 {
594 	int i;
595 	char *parity;
596 
597 	if (value(PARITY) == NOSTR)
598 		value(PARITY) = defparity;
599 	parity = value(PARITY);
600 	for (i = 0; i < 0400; i++)
601 		partab[i] = evenpartab[i];
602 	if (equal(parity, "even")) {
603 		/* EMPTY */
604 	} else if (equal(parity, "odd")) {
605 		for (i = 0; i < 0400; i++)
606 			partab[i] ^= 0200;	/* reverse bit 7 */
607 	} else if (equal(parity, "none")) {
608 		/* Do nothing so we can pass thru 8-bit chars */
609 		noparity = 1;
610 		for (i = 0; i < 0400; i++)
611 			partab[i] = i;
612 	} else if (equal(parity, "zero")) {
613 		for (i = 0; i < 0400; i++)
614 			partab[i] &= ~0200;	/* turn off bit 7 */
615 	} else if (equal(parity, "one")) {
616 		for (i = 0; i < 0400; i++)
617 			partab[i] |= 0200;	/* turn on bit 7 */
618 	} else {
619 		(void) fprintf(stderr, "%s: unknown parity value\n", PA);
620 		(void) fflush(stderr);
621 	}
622 }
623