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