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
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.
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]
22 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
23 /* All Rights Reserved */
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.
32 #pragma ident "%Z%%M% %I% %E% SMI"
39 #include <sys/types.h>
41 #include <sys/utsname.h>
59 #define DATE_FMT "%a %b %e %H:%M:%S"
60 #define UTMP_HACK /* work around until utmpx is world writable */
63 * %a abbreviated weekday name
64 * %b abbreviated month name
66 * %H hour - 24 hour clock
72 static int permit1(int);
73 static int permit(char *);
74 static int readcsi(int, char *, int);
75 static void setsignals();
76 static void shellcmd(char *);
77 static void openfail();
80 static struct utsname utsn
;
82 static FILE *fp
; /* File pointer for receipient's terminal */
83 static char *rterm
, *receipient
; /* Pointer to receipient's terminal & name */
87 main(int argc
, char **argv
)
91 static struct utmpx self
;
92 char ownname
[sizeof (self
.ut_user
) + 1];
93 static char rterminal
[sizeof ("/dev/") + sizeof (self
.ut_line
)] =
95 extern char *rterm
, *receipient
;
96 char *terminal
, *ownterminal
, *oterminal
;
99 char input
[134+MB_LEN_MAX
];
103 struct passwd
*passptr
;
104 char badterm
[20][20];
113 (void) setlocale(LC_ALL
, "");
114 #if !defined(TEXT_DOMAIN)
115 #define TEXT_DOMAIN "SYS_TEST"
117 (void) textdomain(TEXT_DOMAIN
);
119 while ((c
= getopt(argc
, argv
, "")) != EOF
)
122 (void) fprintf(stderr
, "Usage: write %s\n",
123 gettext("user_name [terminal]"));
128 thissys
= utsn
.nodename
;
130 /* Set "rterm" to location where receipient's terminal will go. */
132 rterm
= &rterminal
[sizeof ("/dev/") - 1];
136 (void) fprintf(stderr
, "Usage: write %s\n",
137 gettext("user_name [terminal]"));
142 receipient
= *++argv
;
145 /* Was a terminal name supplied? If so, save it. */
148 (void) fprintf(stderr
, "Usage: write %s\n",
149 gettext("user_name [terminal]"));
155 /* One of the standard file descriptors must be attached to a */
156 /* terminal in "/dev". */
158 if ((ownterminal
= ttyname(fileno(stdin
))) == NULL
&&
159 (ownterminal
= ttyname(fileno(stdout
))) == NULL
&&
160 (ownterminal
= ttyname(fileno(stderr
))) == NULL
) {
161 (void) fprintf(stderr
,
162 gettext("I cannot determine your terminal name."
163 " No reply possible.\n"));
164 ownterminal
= "/dev/???";
168 * Set "ownterminal" past the "/dev/" at the beginning of
171 oterminal
= ownterminal
+ sizeof ("/dev/")-1;
174 * Scan through the "utmpx" file for your own entry and the
175 * entry for the person we want to send to.
177 for (self
.ut_pid
= 0, count
= 0; (ubuf
= getutxent()) != NULL
; ) {
178 /* Is this a USER_PROCESS entry? */
180 if (ubuf
->ut_type
== USER_PROCESS
) {
181 /* Is it our entry? (ie. The line matches ours?) */
183 if (strncmp(&ubuf
->ut_line
[0], oterminal
,
184 sizeof (ubuf
->ut_line
)) == 0) self
= *ubuf
;
186 /* Is this the person we want to send to? */
188 if (strncmp(receipient
, &ubuf
->ut_user
[0],
189 sizeof (ubuf
->ut_user
)) == 0) {
190 /* If a terminal name was supplied, is this login at the correct */
191 /* terminal? If not, ignore. If it is right place, copy over the */
194 if (terminal
!= NULL
) {
195 if (strncmp(terminal
, &ubuf
->ut_line
[0],
196 sizeof (ubuf
->ut_line
)) == 0) {
197 strlcpy(rterm
, &ubuf
->ut_line
[0],
198 sizeof (rterminal
) - (rterm
- rterminal
));
199 if (myuid
&& !permit(rterminal
)) {
206 /* If no terminal was supplied, then take this terminal if no */
207 /* other terminal has been encountered already. */
211 /* If this is the first encounter, copy the string into */
214 if (*rterm
== '\0') {
215 strlcpy(rterm
, &ubuf
->ut_line
[0],
216 sizeof (rterminal
) - (rterm
- rterminal
));
217 if (myuid
&& !permit(rterminal
)) {
219 strlcpy(badterm
[bad
++], rterm
,
220 sizeof (badterm
[bad
++]));
223 } else if (bad
> 0) {
224 (void) fprintf(stderr
,
226 "%s is logged on more than one place.\n"
227 "You are connected to \"%s\".\nOther locations are:\n"),
229 for (i
= 0; i
< bad
; i
++)
230 (void) fprintf(stderr
, "%s\n", badterm
[i
]);
234 /* If this is the second terminal, print out the first. In all */
235 /* cases of multiple terminals, list out all the other terminals */
236 /* so the user can restart knowing what their choices are. */
238 else if (terminal
== NULL
) {
239 if (count
== 1 && bad
== 0) {
240 (void) fprintf(stderr
,
242 "%s is logged on more than one place.\n"
243 "You are connected to \"%s\".\nOther locations are:\n"),
246 fwrite(&ubuf
->ut_line
[0], sizeof (ubuf
->ut_line
),
248 (void) fprintf(stderr
, "\n");
252 } /* End of "else" */
253 } /* End of "else if (strncmp" */
254 } /* End of "if (USER_PROCESS" */
255 } /* End of "for(count=0" */
257 /* Did we find a place to talk to? If we were looking for a */
258 /* specific spot and didn't find it, complain and quit. */
260 if (terminal
!= NULL
&& *rterm
== '\0') {
262 (void) fprintf(stderr
, gettext("Permission denied.\n"));
266 if (strlcat(rterminal
, terminal
, sizeof (rterminal
)) >=
267 sizeof (rterminal
)) {
268 (void) fprintf(stderr
,
269 gettext("Terminal name too long.\n"));
272 if (self
.ut_pid
== 0) {
273 if ((passptr
= getpwuid(getuid())) == NULL
) {
274 (void) fprintf(stderr
,
275 gettext("Cannot determine who you are.\n"));
278 (void) strlcpy(&ownname
[0], &passptr
->pw_name
[0],
281 (void) strlcpy(&ownname
[0], self
.ut_user
,
282 sizeof (self
.ut_user
));
284 if (!permit(rterminal
)) {
285 (void) fprintf(stderr
,
286 gettext("%s permission denied\n"), terminal
);
290 (void) fprintf(stderr
, gettext("%s is not at \"%s\".\n"),
291 receipient
, terminal
);
293 #endif /* UTMP_HACK */
297 /* If we were just looking for anyplace to talk and didn't find */
298 /* one, complain and quit. */
299 /* If permissions prevent us from sending to this person - exit */
301 else if (*rterm
== '\0') {
303 (void) fprintf(stderr
, gettext("Permission denied.\n"));
305 (void) fprintf(stderr
,
306 gettext("%s is not logged on.\n"), receipient
);
310 /* Did we find our own entry? */
312 else if (self
.ut_pid
== 0) {
313 /* Use the user id instead of utmp name if the entry in the */
314 /* utmp file couldn't be found. */
316 if ((passptr
= getpwuid(getuid())) == (struct passwd
*)NULL
) {
317 (void) fprintf(stderr
,
318 gettext("Cannot determine who you are.\n"));
321 strncpy(&ownname
[0], &passptr
->pw_name
[0], sizeof (ownname
));
325 strncpy(&ownname
[0], self
.ut_user
, sizeof (self
.ut_user
));
327 ownname
[sizeof (ownname
)-1] = '\0';
330 (void) fprintf(stderr
,
331 gettext("Warning: You have your terminal set to \"mesg -n\"."
332 " No reply possible.\n"));
333 /* Close the utmpx files. */
337 /* Try to open up the line to the receipient's terminal. */
339 signal(SIGALRM
, openfail
);
341 fp
= fopen(&rterminal
[0], "w");
344 /* Make sure executed subshell doesn't inherit this fd - close-on-exec */
346 if (fcntl(fileno(fp
), F_SETFD
, FD_CLOEXEC
) < 0) {
347 perror("fcntl(F_SETFD)");
351 /* Catch signals SIGHUP, SIGINT, SIGQUIT, and SIGTERM, and send */
352 /* <EOT> message to receipient before dying away. */
356 /* Get the time of day, convert it to a string and throw away the */
357 /* year information at the end of the string. */
360 (void) strftime(time_buf
, sizeof (time_buf
),
361 dcgettext(NULL
, DATE_FMT
, LC_TIME
), localtime(&tod
));
364 gettext("\n\007\007\007\tMessage from %s on %s (%s) [ %s ] ...\n"),
365 &ownname
[0], thissys
, oterminal
, time_buf
);
367 (void) fprintf(stderr
, "\007\007");
369 /* Get input from user and send to receipient unless it begins */
370 /* with a !, when it is to be a shell command. */
372 while ((i
= readcsi(0, &input
[0], sizeof (input
))) > 0) {
374 /* Is this a shell command? */
376 if ((newline
) && (*ptr
== '!'))
379 /* Send line to the receipient. */
382 if (myuid
&& !permit1(fileno(fp
))) {
383 (void) fprintf(stderr
,
384 gettext("Can no longer write to %s\n"), rterminal
);
389 * All non-printable characters are displayed using a special notation:
390 * Control characters shall be displayed using the two character
391 * sequence of ^ (carat) and the ASCII character - decimal 64 greater
392 * that the character being encoded - eg., a \003 is displayed ^C.
393 * Characters with the eighth bit set shall be displayed using
394 * the three or four character meta notation - e.g., \372 is
395 * displayed M-z and \203 is displayed M-^C.
399 for (bp
= &input
[0]; --i
>= 0; bp
++) {
405 *bp
== '\t' || *bp
== '\n' ||
406 *bp
== '\r' || *bp
== '\013' ||
409 } else if (((n
= mbtowc(&wc
, bp
, MB_CUR_MAX
)) > 0) &&
411 for (; n
> 0; --n
, --i
, ++bp
)
421 /* add decimal 64 to the control character */
422 putc(*bp
+ 0100, fp
);
429 if (ferror(fp
) || feof(fp
)) {
431 "\n\007Write failed (%s logged out?)\n"),
440 /* Since "end of file" received, send <EOT> message to receipient. */
451 signal(SIGHUP
, catch);
452 signal(SIGINT
, catch);
453 signal(SIGQUIT
, catch);
454 signal(SIGTERM
, catch);
462 register pid_t child
;
465 if ((child
= fork()) == (pid_t
)FAILURE
)
467 (void) fprintf(stderr
,
468 gettext("Unable to fork. Try again later.\n"));
470 } else if (child
== (pid_t
)0) {
471 /* Reset the signals to the default actions and exec a shell. */
473 if (setgid(getgid()) < 0)
475 execl("/usr/bin/sh", "sh", "-c", command
, 0);
480 /* Allow user to type <del> and <quit> without dying during */
483 signal(SIGINT
, SIG_IGN
);
484 signal(SIGQUIT
, SIG_IGN
);
486 /* As parent wait around for user to finish spunoff command. */
488 while (wait(NULL
) != child
);
490 /* Reset the signals to their normal state. */
494 (void) fprintf(stdout
, "!\n");
500 extern char *rterm
, *receipient
;
502 (void) fprintf(stderr
,
503 gettext("Timeout trying to open %s's line(%s).\n"),
513 (void) fprintf(fp
, "%s\n", gettext("<EOT>"));
518 * permit: check mode of terminal - if not writable by all disallow writing to
519 * (even the user cannot therefore write to their own tty)
529 if ((fildes
= open(term
, O_WRONLY
|O_NOCTTY
)) < 0)
531 /* check if the device really is a tty */
532 if (!isatty(fildes
)) {
533 (void) fprintf(stderr
,
534 gettext("%s in utmpx is not a tty\n"), term
);
535 openlog("write", 0, LOG_AUTH
);
536 syslog(LOG_CRIT
, "%s in utmpx is not a tty\n", term
);
543 return (buf
.st_mode
& (S_IWGRP
|S_IWOTH
));
549 * permit1: check mode of terminal - if not writable by all disallow writing
550 * to (even the user themself cannot therefore write to their own tty)
553 /* this is used with fstat (which is faster than stat) where possible */
562 return (buf
.st_mode
& (S_IWGRP
|S_IWOTH
));
567 * Read a string of multi-byte characters from specified file.
568 * The requested # of bytes are attempted to read.
569 * readcsi() tries to complete the last multibyte character
570 * by calling mbtowc(), if the leftovers form mbtowc(),
571 * left the last char imcomplete, moves into delta_spool to use later,
572 * next called. The caller must reserve
573 * nbytereq+MB_LEN_MAX bytes for the buffer. When the attempt
574 * is failed, it truncate the last char.
575 * Returns the number of bytes that constitutes the valid multi-byte characters.
579 static int readcsi(d
, buf
, nbytereq
)
584 static char delta_pool
[MB_LEN_MAX
* 2];
585 static char delta_size
;
586 char *cp
, *nextp
, *lastp
;
591 memcpy(buf
, delta_pool
, delta_size
);
592 cp
= buf
+ delta_size
;
593 r_size
= nbytereq
- delta_size
;
599 if ((r_size
= read(d
, cp
, r_size
)) < 0)
601 if ((n
= delta_size
+ r_size
) <= 0)
604 /* Scan the result to test the completeness of each EUC characters. */
606 lastp
= buf
+ n
; /* Lastp points to the first junk byte. */
607 while (nextp
< lastp
) {
608 if ((n
= (lastp
- nextp
)) > (unsigned int)MB_CUR_MAX
)
609 n
= (unsigned int)MB_CUR_MAX
;
610 if ((n
= mbtowc((wchar_t *)0, nextp
, n
)) <= 0) {
611 if ((lastp
- nextp
) < (unsigned int)MB_CUR_MAX
)
617 /* How many bytes needed to complete the last char? */
618 delta_size
= lastp
- nextp
;
619 if (delta_size
> 0) {
620 if (nextp
[delta_size
- 1] != '\n') {
621 /* the remnants store into delta_pool */
622 memcpy(delta_pool
, nextp
, delta_size
);
627 return (nextp
-buf
); /* Return # of bytes. */