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