xref: /illumos-gate/usr/src/cmd/write/write.c (revision bc54f855)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
23 /*	  All Rights Reserved  	*/
24 
25 
26 /*
27  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
28  * Use is subject to license terms.
29  * Copyright (c) 2016 by Delphix. All rights reserved.
30  * Copyright (c) 2018, Joyent, Inc.
31  */
32 
33 #pragma ident	"%Z%%M%	%I%	%E% SMI"
34 
35 #include	<ctype.h>
36 #include	<string.h>
37 #include	<stdio.h>
38 #include	<signal.h>
39 #include	<sys/wait.h>
40 #include	<sys/types.h>
41 #include	<sys/stat.h>
42 #include	<sys/utsname.h>
43 #include	<stdlib.h>
44 #include	<unistd.h>
45 #include	<time.h>
46 #include	<utmpx.h>
47 #include	<pwd.h>
48 #include	<fcntl.h>
49 #include	<stdarg.h>
50 #include	<locale.h>
51 #include	<stdlib.h>
52 #include	<limits.h>
53 #include	<wctype.h>
54 #include	<errno.h>
55 #include	<syslog.h>
56 
57 #define		TRUE	1
58 #define		FALSE	0
59 #define		FAILURE	-1
60 #define		DATE_FMT	"%a %b %e %H:%M:%S"
61 #define		UTMP_HACK  /* work around until utmpx is world writable */
62 /*
63  *	DATE-TIME format
64  *  %a	abbreviated weekday name
65  *  %b  abbreviated month name
66  *  %e  day of month
67  *  %H  hour - 24 hour clock
68  *  %M  minute
69  *  %S  second
70  *
71  */
72 
73 static int permit1(int);
74 static int permit(char *);
75 static int readcsi(int, char *, int);
76 static void setsignals();
77 static void shellcmd(char *);
78 static void openfail();
79 static void eof();
80 
81 static struct	utsname utsn;
82 
83 static FILE	*fp;	/* File pointer for receipient's terminal */
84 static char *rterm, *receipient; /* Pointer to receipient's terminal & name */
85 static char *thissys;
86 
87 int
main(int argc,char ** argv)88 main(int argc, char **argv)
89 {
90 	int i;
91 	struct utmpx *ubuf;
92 	static struct utmpx self;
93 	char ownname[sizeof (self.ut_user) + 1];
94 	static char rterminal[sizeof ("/dev/") + sizeof (self.ut_line)] =
95 	    "/dev/";
96 	extern char *rterm, *receipient;
97 	char *terminal, *ownterminal, *oterminal;
98 	short count;
99 	extern FILE *fp;
100 	char input[134+MB_LEN_MAX];
101 	char *ptr;
102 	time_t tod;
103 	char time_buf[40];
104 	struct passwd *passptr;
105 	char badterm[20][20];
106 	int bad = 0;
107 	uid_t	myuid;
108 	char *bp;
109 	int n;
110 	wchar_t wc;
111 	int c;
112 	int newline;
113 
114 	(void) setlocale(LC_ALL, "");
115 #if !defined(TEXT_DOMAIN)
116 #define	TEXT_DOMAIN "SYS_TEST"
117 #endif
118 	(void) textdomain(TEXT_DOMAIN);
119 
120 	while ((c = getopt(argc, argv, "")) != EOF)
121 		switch (c) {
122 			case '?':
123 				(void) fprintf(stderr, "Usage: write %s\n",
124 				gettext("user_name [terminal]"));
125 				exit(2);
126 		}
127 	myuid = geteuid();
128 	uname(&utsn);
129 	thissys = utsn.nodename;
130 
131 /*	Set "rterm" to location where receipient's terminal will go.	*/
132 
133 	rterm = &rterminal[sizeof ("/dev/") - 1];
134 	terminal = NULL;
135 
136 	if (--argc <= 0) {
137 	    (void) fprintf(stderr, "Usage: write %s\n",
138 		gettext("user_name [terminal]"));
139 	    exit(1);
140 	    }
141 	else
142 	    {
143 	    receipient = *++argv;
144 	    }
145 
146 /*	Was a terminal name supplied?  If so, save it.			*/
147 
148 	if (--argc > 1) {
149 	    (void) fprintf(stderr, "Usage: write %s\n",
150 		gettext("user_name [terminal]"));
151 	    exit(1);
152 	} else {
153 	    terminal = *++argv;
154 	}
155 
156 /*	One of the standard file descriptors must be attached to a	*/
157 /*	terminal in "/dev".						*/
158 
159 	if ((ownterminal = ttyname(fileno(stdin))) == NULL &&
160 	    (ownterminal = ttyname(fileno(stdout))) == NULL &&
161 	    (ownterminal = ttyname(fileno(stderr))) == NULL) {
162 		(void) fprintf(stderr,
163 			gettext("I cannot determine your terminal name."
164 					" No reply possible.\n"));
165 		ownterminal = "/dev/???";
166 	}
167 
168 	/*
169 	 * Set "ownterminal" past the "/dev/" at the beginning of
170 	 * the device name.
171 	 */
172 	oterminal = ownterminal + sizeof ("/dev/")-1;
173 
174 	/*
175 	 * Scan through the "utmpx" file for your own entry and the
176 	 * entry for the person we want to send to.
177 	 */
178 	for (self.ut_pid = 0, count = 0; (ubuf = getutxent()) != NULL; ) {
179 	/* Is this a USER_PROCESS entry? */
180 
181 	    if (ubuf->ut_type == USER_PROCESS) {
182 /*	Is it our entry?  (ie.  The line matches ours?)			*/
183 
184 		if (strncmp(&ubuf->ut_line[0], oterminal,
185 		    sizeof (ubuf->ut_line)) == 0) self = *ubuf;
186 
187 /*	Is this the person we want to send to?				*/
188 
189 		if (strncmp(receipient, &ubuf->ut_user[0],
190 		    sizeof (ubuf->ut_user)) == 0) {
191 /*	If a terminal name was supplied, is this login at the correct	*/
192 /*	terminal?  If not, ignore.  If it is right place, copy over the	*/
193 /*	name.								*/
194 
195 		    if (terminal != NULL) {
196 			if (strncmp(terminal, &ubuf->ut_line[0],
197 			    sizeof (ubuf->ut_line)) == 0) {
198 			    strlcpy(rterm, &ubuf->ut_line[0],
199 				sizeof (rterminal) - (rterm - rterminal));
200 			    if (myuid && !permit(rterminal)) {
201 				bad++;
202 				rterm[0] = '\0';
203 			    }
204 			    }
205 		    }
206 
207 /*	If no terminal was supplied, then take this terminal if no	*/
208 /*	other terminal has been encountered already.			*/
209 
210 		    else
211 		    {
212 /*	If this is the first encounter, copy the string into		*/
213 /*	"rterminal".							*/
214 
215 			if (*rterm == '\0') {
216 			    strlcpy(rterm, &ubuf->ut_line[0],
217 				sizeof (rterminal) - (rterm - rterminal));
218 			    if (myuid && !permit(rterminal)) {
219 				if (bad < 20) {
220 					strlcpy(badterm[bad++], rterm,
221 					    sizeof (badterm[bad++]));
222 				}
223 				rterm[0] = '\0';
224 			    } else if (bad > 0) {
225 				(void) fprintf(stderr,
226 				gettext(
227 				"%s is logged on more than one place.\n"
228 	"You are connected to \"%s\".\nOther locations are:\n"),
229 				    receipient, rterm);
230 				for (i = 0; i < bad; i++)
231 				    (void) fprintf(stderr, "%s\n", badterm[i]);
232 			    }
233 			}
234 
235 /*	If this is the second terminal, print out the first.  In all	*/
236 /*	cases of multiple terminals, list out all the other terminals	*/
237 /*	so the user can restart knowing what their choices are.		*/
238 
239 			else if (terminal == NULL) {
240 			    if (count == 1 && bad == 0) {
241 				(void) fprintf(stderr,
242 				gettext(
243 				"%s is logged on more than one place.\n"
244 	"You are connected to \"%s\".\nOther locations are:\n"),
245 				    receipient, rterm);
246 			    }
247 			    fwrite(&ubuf->ut_line[0], sizeof (ubuf->ut_line),
248 				1, stderr);
249 			    (void) fprintf(stderr, "\n");
250 			    }
251 
252 			count++;
253 		    }			/* End of "else" */
254 		    }			/* End of "else if (strncmp" */
255 	    }			/* End of "if (USER_PROCESS" */
256 	    }		/* End of "for(count=0" */
257 
258 /*	Did we find a place to talk to?  If we were looking for a	*/
259 /*	specific spot and didn't find it, complain and quit.		*/
260 
261 	if (terminal != NULL && *rterm == '\0') {
262 	    if (bad > 0) {
263 		(void) fprintf(stderr, gettext("Permission denied.\n"));
264 		exit(1);
265 		} else {
266 #ifdef UTMP_HACK
267 		if (strlcat(rterminal, terminal, sizeof (rterminal)) >=
268 		    sizeof (rterminal)) {
269 			(void) fprintf(stderr,
270 			    gettext("Terminal name too long.\n"));
271 			exit(1);
272 		}
273 		if (self.ut_pid == 0) {
274 			if ((passptr = getpwuid(getuid())) == NULL) {
275 			    (void) fprintf(stderr,
276 				gettext("Cannot determine who you are.\n"));
277 			    exit(1);
278 			}
279 			(void) strlcpy(&ownname[0], &passptr->pw_name[0],
280 			    sizeof (ownname));
281 		} else {
282 			(void) strlcpy(&ownname[0], self.ut_user,
283 			    sizeof (self.ut_user));
284 		}
285 		if (!permit(rterminal)) {
286 			(void) fprintf(stderr,
287 				gettext("%s permission denied\n"), terminal);
288 			exit(1);
289 		}
290 #else
291 		(void) fprintf(stderr, gettext("%s is not at \"%s\".\n"),
292 			receipient, terminal);
293 		exit(1);
294 #endif	/* UTMP_HACK */
295 	    }
296 	    }
297 
298 /*	If we were just looking for anyplace to talk and didn't find	*/
299 /*	one, complain and quit.						*/
300 /*	If permissions prevent us from sending to this person - exit	*/
301 
302 	else if (*rterm == '\0') {
303 	    if (bad > 0)
304 		(void) fprintf(stderr, gettext("Permission denied.\n"));
305 	    else
306 		(void) fprintf(stderr,
307 			gettext("%s is not logged on.\n"), receipient);
308 	    exit(1);
309 	    }
310 
311 /*	Did we find our own entry?					*/
312 
313 	else if (self.ut_pid == 0) {
314 /*	Use the user id instead of utmp name if the entry in the	*/
315 /*	utmp file couldn't be found.					*/
316 
317 	    if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL) {
318 		(void) fprintf(stderr,
319 			gettext("Cannot determine who you are.\n"));
320 		exit(1);
321 	    }
322 	    strncpy(&ownname[0], &passptr->pw_name[0], sizeof (ownname));
323 	    }
324 	else
325 	    {
326 	    strncpy(&ownname[0], self.ut_user, sizeof (self.ut_user));
327 	    }
328 	ownname[sizeof (ownname)-1] = '\0';
329 
330 	if (!permit1(1))
331 		(void) fprintf(stderr,
332 		gettext("Warning: You have your terminal set to \"mesg -n\"."
333 		    " No reply possible.\n"));
334 /*	Close the utmpx files.						*/
335 
336 	endutxent();
337 
338 /*	Try to open up the line to the receipient's terminal.		*/
339 
340 	signal(SIGALRM, openfail);
341 	alarm(5);
342 	fp = fopen(&rterminal[0], "w");
343 	alarm(0);
344 
345 /*	Make sure executed subshell doesn't inherit this fd - close-on-exec */
346 
347 	if (fcntl(fileno(fp), F_SETFD, FD_CLOEXEC) < 0)  {
348 		perror("fcntl(F_SETFD)");
349 		exit(1);
350 	}
351 
352 /*	Catch signals SIGHUP, SIGINT, SIGQUIT, and SIGTERM, and send	*/
353 /*	<EOT> message to receipient before dying away.			*/
354 
355 	setsignals(eof);
356 
357 /*	Get the time of day, convert it to a string and throw away the	*/
358 /*	year information at the end of the string.			*/
359 
360 	time(&tod);
361 	(void) strftime(time_buf, sizeof (time_buf),
362 	    dcgettext(NULL, DATE_FMT, LC_TIME), localtime(&tod));
363 
364 	(void) fprintf(fp,
365 	gettext("\n\007\007\007\tMessage from %s on %s (%s) [ %s ] ...\n"),
366 	    &ownname[0], thissys, oterminal, time_buf);
367 	fflush(fp);
368 	(void) fprintf(stderr, "\007\007");
369 
370 /*	Get input from user and send to receipient unless it begins	*/
371 /*	with a !, when it is to be a shell command.			*/
372 	newline = 1;
373 	while ((i = readcsi(0, &input[0], sizeof (input))) > 0) {
374 		ptr = &input[0];
375 /*	Is this a shell command?					*/
376 
377 		if ((newline) && (*ptr == '!'))
378 			shellcmd(++ptr);
379 
380 /*	Send line to the receipient.					*/
381 
382 		else {
383 			if (myuid && !permit1(fileno(fp))) {
384 				(void) fprintf(stderr,
385 			gettext("Can no longer write to %s\n"), rterminal);
386 				break;
387 			}
388 
389 /*
390  * All non-printable characters are displayed using a special notation:
391  * Control characters  shall be displayed using the two character
392  * sequence of ^ (carat) and the ASCII character - decimal 64 greater
393  * that the character being encoded - eg., a \003 is displayed ^C.
394  * Characters with the eighth bit set shall be displayed using
395  * the three or four character meta notation - e.g., \372 is
396  * displayed M-z and \203 is displayed M-^C.
397  */
398 
399 			newline = 0;
400 			for (bp = &input[0]; --i >= 0; bp++) {
401 			if (*bp == '\n') {
402 				newline = 1;
403 				putc('\r', fp);
404 			}
405 			if (*bp == ' ' ||
406 				 *bp == '\t' || *bp == '\n' ||
407 				 *bp == '\r' || *bp == '\013' ||
408 				 *bp == '\007') {
409 					putc(*bp, fp);
410 			} else if (((n = mbtowc(&wc, bp, MB_CUR_MAX)) > 0) &&
411 				iswprint(wc)) {
412 				for (; n > 0; --n, --i, ++bp)
413 					putc(*bp, fp);
414 				bp--, ++i;
415 			} else {
416 				if (!isascii(*bp)) {
417 					fputs("M-", fp);
418 					*bp = toascii(*bp);
419 				}
420 				if (iscntrl(*bp)) {
421 					putc('^', fp);
422 /*	add decimal 64 to the control character			*/
423 					putc(*bp + 0100, fp);
424 				}
425 				else
426 					putc(*bp, fp);
427 			}
428 			if (*bp == '\n')
429 				fflush(fp);
430 			if (ferror(fp) || feof(fp)) {
431 				printf(gettext(
432 				"\n\007Write failed (%s logged out?)\n"),
433 				receipient);
434 				exit(1);
435 			}
436 			} /* for */
437 			fflush(fp);
438 	} /* else */
439 	} /* while */
440 
441 /*	Since "end of file" received, send <EOT> message to receipient.	*/
442 
443 	eof();
444 	return (0);
445 }
446 
447 
448 static void
449 setsignals(catch)
450 void (*catch)();
451 {
452 	signal(SIGHUP, catch);
453 	signal(SIGINT, catch);
454 	signal(SIGQUIT, catch);
455 	signal(SIGTERM, catch);
456 }
457 
458 
459 static void
shellcmd(command)460 shellcmd(command)
461 char *command;
462 {
463 	register pid_t child;
464 	extern void eof();
465 
466 	if ((child = fork()) == (pid_t)FAILURE)
467 	    {
468 	    (void) fprintf(stderr,
469 	    gettext("Unable to fork.  Try again later.\n"));
470 	    return;
471 	    } else if (child == (pid_t)0) {
472 /*	Reset the signals to the default actions and exec a shell.	*/
473 
474 	    if (setgid(getgid()) < 0)
475 		exit(1);
476 	    execl("/usr/bin/sh", "sh", "-c", command, 0);
477 	    exit(0);
478 	    }
479 	else
480 	    {
481 /*	Allow user to type <del> and <quit> without dying during	*/
482 /*	commands.							*/
483 
484 	    signal(SIGINT, SIG_IGN);
485 	    signal(SIGQUIT, SIG_IGN);
486 
487 /*	As parent wait around for user to finish spunoff command.	*/
488 
489 	    while (wait(NULL) != child);
490 
491 /*	Reset the signals to their normal state.			*/
492 
493 	    setsignals(eof);
494 	    }
495 	(void) fprintf(stdout, "!\n");
496 }
497 
498 static void
openfail()499 openfail()
500 {
501 	extern char *rterm, *receipient;
502 
503 	(void) fprintf(stderr,
504 		gettext("Timeout trying to open %s's line(%s).\n"),
505 	    receipient, rterm);
506 	exit(1);
507 }
508 
509 static void
eof()510 eof()
511 {
512 	extern FILE *fp;
513 
514 	(void) fprintf(fp, "%s\n", gettext("<EOT>"));
515 	exit(0);
516 }
517 
518 /*
519  * permit: check mode of terminal - if not writable by all disallow writing to
520  * (even the user cannot therefore write to their own tty)
521  */
522 
523 static int
permit(term)524 permit(term)
525 char *term;
526 {
527 	struct stat buf;
528 	int fildes;
529 
530 	if ((fildes = open(term, O_WRONLY|O_NOCTTY)) < 0)
531 		return (0);
532 	/* check if the device really is a tty */
533 	if (!isatty(fildes)) {
534 		(void) fprintf(stderr,
535 		    gettext("%s in utmpx is not a tty\n"), term);
536 		openlog("write", 0, LOG_AUTH);
537 		syslog(LOG_CRIT, "%s in utmpx is not a tty\n", term);
538 		closelog();
539 		close(fildes);
540 		return (0);
541 	}
542 	fstat(fildes, &buf);
543 	close(fildes);
544 	return (buf.st_mode & (S_IWGRP|S_IWOTH));
545 }
546 
547 
548 
549 /*
550  * permit1: check mode of terminal - if not writable by all disallow writing
551  * to (even the user themself cannot therefore write to their own tty)
552  */
553 
554 /* this is used with fstat (which is faster than stat) where possible */
555 
556 static int
permit1(fildes)557 permit1(fildes)
558 int fildes;
559 {
560 	struct stat buf;
561 
562 	fstat(fildes, &buf);
563 	return (buf.st_mode & (S_IWGRP|S_IWOTH));
564 }
565 
566 
567 /*
568  * Read a string of multi-byte characters from specified file.
569  * The requested # of bytes are attempted to read.
570  * readcsi() tries to complete the last multibyte character
571  * by calling mbtowc(), if the leftovers form mbtowc(),
572  * left the last char imcomplete, moves into delta_spool to use later,
573  * next called. The caller must reserve
574  * nbytereq+MB_LEN_MAX bytes for the buffer.  When the attempt
575  * is failed, it truncate the last char.
576  * Returns the number of bytes that constitutes the valid multi-byte characters.
577  */
578 
579 
readcsi(d,buf,nbytereq)580 static int readcsi(d, buf, nbytereq)
581 int	d;
582 char	*buf;
583 int	nbytereq;
584 {
585 	static char	delta_pool[MB_LEN_MAX * 2];
586 	static char	delta_size;
587 	char	*cp, *nextp, *lastp;
588 	int	n;
589 	int	r_size;
590 
591 	if (delta_size) {
592 		memcpy(buf, delta_pool, delta_size);
593 		cp = buf + delta_size;
594 		r_size = nbytereq - delta_size;
595 	} else {
596 		cp = buf;
597 		r_size = nbytereq;
598 	}
599 
600 	if ((r_size = read(d, cp, r_size)) < 0)
601 		r_size = 0;
602 	if ((n = delta_size + r_size) <= 0)
603 		return (n);
604 
605 	/* Scan the result to test the completeness of each EUC characters. */
606 	nextp = buf;
607 	lastp = buf + n; /* Lastp points to the first junk byte. */
608 	while (nextp < lastp) {
609 		if ((n = (lastp - nextp)) > (unsigned int)MB_CUR_MAX)
610 			n = (unsigned int)MB_CUR_MAX;
611 		if ((n = mbtowc((wchar_t *)0, nextp, n)) <= 0) {
612 			if ((lastp - nextp) < (unsigned int)MB_CUR_MAX)
613 				break;
614 			n = 1;
615 		}
616 		nextp += n;
617 	}
618 	/* How many bytes needed to complete the last char? */
619 	delta_size = lastp - nextp;
620 	if (delta_size > 0) {
621 		if (nextp[delta_size - 1] != '\n') {
622 			/* the remnants store into delta_pool */
623 			memcpy(delta_pool, nextp, delta_size);
624 		} else
625 			nextp = lastp;
626 	}
627 	*nextp = '\0';
628 	return (nextp-buf); /* Return # of bytes. */
629 }
630