579 lines
14 KiB
C
Executable File
579 lines
14 KiB
C
Executable File
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
|
|
/* All Rights Reserved */
|
|
|
|
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
|
|
/* The copyright notice above does not evidence any */
|
|
/* actual or intended publication of such source code. */
|
|
|
|
/* Portions Copyright (c) 1988, Sun Microsystems, Inc. */
|
|
/* All Rights Reserved. */
|
|
|
|
#ident "@(#)write.c 1.16 94/10/03 SMI" /* SVr4.0 1.19 */
|
|
|
|
/* Program to communicate with other users of the system. */
|
|
/* Usage: write user_name [terminal] */
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/utsname.h>
|
|
#include <euc.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <utmp.h>
|
|
#include <pwd.h>
|
|
#include <fcntl.h>
|
|
#include <stdarg.h>
|
|
#include <locale.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <wctype.h>
|
|
#include <errno.h>
|
|
#include <syslog.h>
|
|
|
|
#define TRUE 1
|
|
#define FALSE 0
|
|
#define FAILURE -1
|
|
#define DATE_FMT "%a %b %e %H:%M:%S"
|
|
#define UTMP_HACK /* Enable work around until utmp is world writable */
|
|
/*
|
|
* DATE-TIME format
|
|
* %a abbreviated weekday name
|
|
* %b abbreviated month name
|
|
* %e day of month
|
|
* %H hour - 24 hour clock
|
|
* %M minute
|
|
* %S second
|
|
*
|
|
*/
|
|
|
|
static int permit1(int);
|
|
static int permit(char *);
|
|
static int readeuc(int, char *, int);
|
|
static void setsignals();
|
|
static void shellcmd(char *);
|
|
static void openfail();
|
|
static void eof();
|
|
|
|
static struct utsname utsn;
|
|
|
|
static FILE *fp; /* File pointer for receipient's terminal */
|
|
static char *rterm, *receipient; /* Pointer to receipient's terminal & name */
|
|
static char *thissys;
|
|
|
|
void
|
|
main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
register int i;
|
|
register struct utmp *ubuf;
|
|
static struct utmp self;
|
|
char ownname[sizeof (self.ut_user) + 1];
|
|
static char rterminal[] = "/dev/\0 2345678901";
|
|
extern char *rterm, *receipient;
|
|
char *terminal, *ownterminal, *oterminal;
|
|
short count;
|
|
extern FILE *fp;
|
|
char input[134+MB_LEN_MAX];
|
|
register char *ptr;
|
|
long tod;
|
|
char time_buf[40];
|
|
struct passwd *passptr;
|
|
char badterm[20][20];
|
|
register int bad = 0;
|
|
uid_t myuid;
|
|
register char *bp;
|
|
unsigned int ibp;
|
|
int n;
|
|
wchar_t wc;
|
|
register c;
|
|
|
|
(void) setlocale(LC_ALL, "");
|
|
#if !defined(TEXT_DOMAIN)
|
|
#define TEXT_DOMAIN "SYS_TEST"
|
|
#endif
|
|
(void) textdomain(TEXT_DOMAIN);
|
|
|
|
while ((c = getopt(argc, argv, "")) != EOF)
|
|
switch (c) {
|
|
case '?':
|
|
(void) fprintf(stderr, "Usage: write %s\n",
|
|
gettext("user_name [terminal]"));
|
|
exit(2);
|
|
}
|
|
myuid = geteuid();
|
|
uname(&utsn);
|
|
thissys = utsn.nodename;
|
|
|
|
/* Set "rterm" to location where receipient's terminal will go. */
|
|
|
|
rterm = &rterminal[sizeof ("/dev/") - 1];
|
|
terminal = NULL;
|
|
|
|
if (--argc <= 0)
|
|
{
|
|
(void) fprintf(stderr, "Usage: write %s\n",
|
|
gettext("user_name [terminal]"));
|
|
exit(1);
|
|
}
|
|
else
|
|
{
|
|
receipient = *++argv;
|
|
}
|
|
|
|
/* Was a terminal name supplied? If so, save it. */
|
|
|
|
if (--argc > 1)
|
|
{
|
|
(void) fprintf(stderr, "Usage: write %s\n",
|
|
gettext("user_name [terminal]"));
|
|
exit(1);
|
|
}
|
|
else
|
|
terminal = *++argv;
|
|
|
|
/* One of the standard file descriptors must be attached to a */
|
|
/* terminal in "/dev". */
|
|
|
|
if ((ownterminal = ttyname(fileno(stdin))) == NULL &&
|
|
(ownterminal = ttyname(fileno(stdout))) == NULL &&
|
|
(ownterminal = ttyname(fileno(stderr))) == NULL)
|
|
{
|
|
(void) fprintf(stderr,
|
|
gettext("I cannot determine your terminal name."
|
|
" No reply possible.\n"));
|
|
ownterminal = "/dev/???";
|
|
}
|
|
|
|
/* Set "ownterminal" past the "/dev/" at the beginning of */
|
|
/* the device name. */
|
|
|
|
oterminal = ownterminal + sizeof ("/dev/")-1;
|
|
|
|
/* Scan through the "utmp" file for your own entry and the */
|
|
/* entry for the person we want to send to. */
|
|
|
|
for (self.ut_pid = 0, count = 0; (ubuf = getutent()) != NULL; )
|
|
{
|
|
/* Is this a USER_PROCESS entry? */
|
|
|
|
if (ubuf->ut_type == USER_PROCESS)
|
|
{
|
|
/* Is it our entry? (ie. The line matches ours?) */
|
|
|
|
if (strncmp(&ubuf->ut_line[0], oterminal,
|
|
sizeof (ubuf->ut_line)) == 0) self = *ubuf;
|
|
|
|
/* Is this the person we want to send to? */
|
|
|
|
if (strncmp(receipient, &ubuf->ut_user[0],
|
|
sizeof (ubuf->ut_user)) == 0)
|
|
{
|
|
/* If a terminal name was supplied, is this login at the correct */
|
|
/* terminal? If not, ignore. If it is right place, copy over the */
|
|
/* name. */
|
|
|
|
if (terminal != NULL)
|
|
{
|
|
if (strncmp(terminal, &ubuf->ut_line[0],
|
|
sizeof (ubuf->ut_line)) == 0)
|
|
{
|
|
strncpy(rterm, &ubuf->ut_line[0],
|
|
sizeof (ubuf->ut_line)+1);
|
|
if (myuid && !permit(rterminal)) {
|
|
bad++;
|
|
rterm[0] = '\0';
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If no terminal was supplied, then take this terminal if no */
|
|
/* other terminal has been encountered already. */
|
|
|
|
else
|
|
{
|
|
/* If this is the first encounter, copy the string into */
|
|
/* "rterminal". */
|
|
|
|
if (*rterm == '\0')
|
|
{
|
|
strncpy(rterm,
|
|
&ubuf->ut_line[0], sizeof (ubuf->ut_line)+1);
|
|
if (myuid && !permit(rterminal)) {
|
|
strcpy(badterm[bad++], rterm);
|
|
rterm[0] = '\0';
|
|
} else if (bad > 0) {
|
|
(void) fprintf(stderr,
|
|
gettext(
|
|
"%s is logged on more than one place.\n"
|
|
"You are connected to \"%s\".\nOther locations are:\n"),
|
|
receipient, rterm);
|
|
for (i = 0; i < bad; i++)
|
|
(void) fprintf(stderr, "%s\n", badterm[i]);
|
|
}
|
|
}
|
|
|
|
/* If this is the second terminal, print out the first. In all */
|
|
/* cases of multiple terminals, list out all the other terminals */
|
|
/* so the user can restart knowing what her/his choices are. */
|
|
|
|
else if (terminal == NULL)
|
|
{
|
|
if (count == 1 && bad == 0)
|
|
{
|
|
(void) fprintf(stderr,
|
|
gettext(
|
|
"%s is logged on more than one place.\n"
|
|
"You are connected to \"%s\".\nOther locations are:\n"),
|
|
receipient, rterm);
|
|
}
|
|
fwrite(&ubuf->ut_line[0], sizeof (ubuf->ut_line),
|
|
1, stderr);
|
|
(void) fprintf(stderr, "\n");
|
|
}
|
|
|
|
count++;
|
|
} /* End of "else" */
|
|
} /* End of "else if (strncmp" */
|
|
} /* End of "if (USER_PROCESS" */
|
|
} /* End of "for(count=0" */
|
|
|
|
/* Did we find a place to talk to? If we were looking for a */
|
|
/* specific spot and didn't find it, complain and quit. */
|
|
|
|
if (terminal != NULL && *rterm == '\0')
|
|
{
|
|
if (bad > 0) {
|
|
(void) fprintf(stderr, gettext("Permission denied.\n"));
|
|
exit(1);
|
|
} else {
|
|
#ifdef UTMP_HACK
|
|
strcat(rterminal, terminal);
|
|
strcpy(rterm, terminal);
|
|
if (self.ut_pid == 0) {
|
|
if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL) {
|
|
(void) fprintf(stderr,
|
|
gettext("Cannot determine who you are.\n"));
|
|
exit(1);
|
|
}
|
|
strncpy(&ownname[0], &passptr->pw_name[0],
|
|
sizeof (ownname));
|
|
}
|
|
if (!permit(rterminal)) {
|
|
(void) fprintf(stderr,
|
|
gettext("%s permission denied\n"), terminal);
|
|
exit(1);
|
|
}
|
|
#else
|
|
(void) fprintf(stderr, gettext("%s is not at \"%s\".\n"),
|
|
receipient, terminal);
|
|
exit(1);
|
|
#endif /* UTMP_HACK */
|
|
}
|
|
}
|
|
|
|
/* If we were just looking for anyplace to talk and didn't find */
|
|
/* one, complain and quit. */
|
|
/* If permissions prevent us from sending to this person - exit */
|
|
|
|
else if (*rterm == '\0')
|
|
{
|
|
if (bad > 0)
|
|
(void) fprintf(stderr, gettext("Permission denied.\n"));
|
|
else
|
|
(void) fprintf(stderr,
|
|
gettext("%s is not logged on.\n"), receipient);
|
|
exit(1);
|
|
}
|
|
|
|
/* Did we find our own entry? */
|
|
|
|
else if (self.ut_pid == 0)
|
|
{
|
|
/* Use the user id instead of utmp name if the entry in the */
|
|
/* utmp file couldn't be found. */
|
|
|
|
if ((passptr = getpwuid(getuid())) == (struct passwd *)NULL)
|
|
{
|
|
(void) fprintf(stderr,
|
|
gettext("Cannot determine who you are.\n"));
|
|
exit(1);
|
|
}
|
|
strncpy(&ownname[0], &passptr->pw_name[0], sizeof (ownname));
|
|
}
|
|
else
|
|
{
|
|
strncpy(&ownname[0], self.ut_user, sizeof (self.ut_user));
|
|
}
|
|
ownname[sizeof (ownname)-1] = '\0';
|
|
|
|
if (!permit1(1))
|
|
(void) fprintf(stderr,
|
|
gettext("Warning: You have your terminal set to \"mesg -n\"."
|
|
" No reply possible.\n"));
|
|
|
|
/* Try to open up the line to the receipient's terminal. */
|
|
|
|
signal(SIGALRM, openfail);
|
|
alarm(5);
|
|
fp = fopen(&rterminal[0], "w");
|
|
alarm(0);
|
|
|
|
/* Catch signals SIGHUP, SIGINT, SIGQUIT, and SIGTERM, and send */
|
|
/* <EOT> message to receipient before dying away. */
|
|
|
|
setsignals(eof);
|
|
|
|
/* Get the time of day, convert it to a string and throw away the */
|
|
/* year information at the end of the string. */
|
|
|
|
time(&tod);
|
|
cftime(time_buf, dcgettext(NULL, DATE_FMT, LC_TIME), &tod);
|
|
(void) fprintf(fp,
|
|
gettext("\n\007\007\007\tMessage from %s on %s (%s) [ %s ] ...\n"),
|
|
&ownname[0], thissys, oterminal, time_buf);
|
|
fflush(fp);
|
|
(void) fprintf(stderr, "\007\007");
|
|
|
|
/* Get input from user and send to receipient unless it begins */
|
|
/* with a !, when it is to be a shell command. */
|
|
while ((i = readeuc(0, &input[0], sizeof (input))) > 0) {
|
|
ptr = &input[0];
|
|
/* Is this a shell command? */
|
|
|
|
if (*ptr == '!')
|
|
shellcmd(++ptr);
|
|
|
|
/* Send line to the receipient. */
|
|
|
|
else {
|
|
if (myuid && !permit1(fileno(fp))) {
|
|
(void) fprintf(stderr,
|
|
gettext("Can no longer write to %s\n"), rterminal);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* All non-printable characters are displayed using a special notation:
|
|
* Control characters shall be displayed using the two character
|
|
* sequence of ^ (carat) and the ASCII character - decimal 64 greater
|
|
* that the character being encoded - eg., a \003 is displayed ^C.
|
|
* Characters with the eighth bit set shall be displayed using
|
|
* the three or four character meta notation - e.g., \372 is
|
|
* displayed M-z and \203 is displayed M-^C.
|
|
*/
|
|
|
|
|
|
for (bp = &input[0]; --i >= 0; bp++) {
|
|
ibp = (unsigned int) *bp;
|
|
if (*bp == '\n')
|
|
putc('\r', fp);
|
|
if (*bp == ' ' ||
|
|
*bp == '\t' || *bp == '\n' ||
|
|
*bp == '\r' || *bp == '\013' ||
|
|
*bp == '\007') {
|
|
putc(*bp, fp);
|
|
} else if (((n = mbtowc(&wc, bp, MB_LEN_MAX)) > 0) &&
|
|
iswprint(wc)) {
|
|
for (; n > 0; --n, --i, ++bp)
|
|
putc(*bp, fp);
|
|
bp--, ++i;
|
|
} else {
|
|
if (!isascii(*bp)) {
|
|
fputs("M-", fp);
|
|
*bp = toascii(*bp);
|
|
}
|
|
if (iscntrl(*bp)) {
|
|
putc('^', fp);
|
|
/* add decimal 64 to the control character */
|
|
putc(*bp + 0100, fp);
|
|
}
|
|
else
|
|
putc(*bp, fp);
|
|
}
|
|
if (*bp == '\n')
|
|
fflush(fp);
|
|
if (ferror(fp) || feof(fp)) {
|
|
printf(gettext(
|
|
"\n\007Write failed (%s logged out?)\n"),
|
|
receipient);
|
|
exit(1);
|
|
}
|
|
} /* for */
|
|
fflush(fp);
|
|
} /* else */
|
|
} /* while */
|
|
|
|
/* Since "end of file" received, send <EOT> message to receipient. */
|
|
|
|
eof();
|
|
}
|
|
|
|
|
|
static void
|
|
setsignals(catch)
|
|
void (*catch)();
|
|
{
|
|
signal(SIGHUP, catch);
|
|
signal(SIGINT, catch);
|
|
signal(SIGQUIT, catch);
|
|
signal(SIGTERM, catch);
|
|
}
|
|
|
|
|
|
static void
|
|
shellcmd(command)
|
|
char *command;
|
|
{
|
|
register pid_t child;
|
|
extern void eof();
|
|
|
|
if ((child = fork()) == (pid_t)FAILURE)
|
|
{
|
|
(void) fprintf(stderr,
|
|
gettext("Unable to fork. Try again later.\n"));
|
|
return;
|
|
} else if (child == (pid_t)0) {
|
|
/* Reset the signals to the default actions and exec a shell. */
|
|
|
|
if (setgid(getgid()) < 0)
|
|
exit(1);
|
|
execl("/usr/bin/sh", "sh", "-c", command, 0);
|
|
exit(0);
|
|
}
|
|
else
|
|
{
|
|
/* Allow user to type <del> and <quit> without dying during */
|
|
/* commands. */
|
|
|
|
signal(SIGINT, SIG_IGN);
|
|
signal(SIGQUIT, SIG_IGN);
|
|
|
|
/* As parent wait around for user to finish spunoff command. */
|
|
|
|
while (wait(NULL) != child);
|
|
|
|
/* Reset the signals to their normal state. */
|
|
|
|
setsignals(eof);
|
|
}
|
|
(void) fprintf(stdout, "!\n");
|
|
}
|
|
|
|
static void
|
|
openfail()
|
|
{
|
|
extern char *rterm, *receipient;
|
|
|
|
(void) fprintf(stderr,
|
|
gettext("Timeout trying to open %s's line(%s).\n"),
|
|
receipient, rterm);
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
eof()
|
|
{
|
|
extern FILE *fp;
|
|
|
|
(void) fprintf(fp, "%s\n", gettext("<EOT>"));
|
|
exit(0);
|
|
}
|
|
|
|
/*
|
|
* permit: check mode of terminal - if not writable by all disallow writing to
|
|
* (even the user him/herself cannot therefore write to their own tty)
|
|
*/
|
|
|
|
static int
|
|
permit(term)
|
|
char *term;
|
|
{
|
|
struct stat buf;
|
|
int fildes;
|
|
|
|
if ((fildes = open(term, O_WRONLY|O_NOCTTY)) < 0)
|
|
return (0);
|
|
/* check if the device really is a tty */
|
|
if (!isatty(fildes)) {
|
|
(void) fprintf(stderr,
|
|
gettext("%s in utmp is not a tty\n"), term);
|
|
openlog("write", 0, LOG_AUTH);
|
|
syslog(LOG_CRIT, "%s in utmp is not a tty\n", term);
|
|
closelog();
|
|
close(fildes);
|
|
return (0);
|
|
}
|
|
fstat(fildes, &buf);
|
|
close(fildes);
|
|
return (buf.st_mode & (S_IWGRP|S_IWOTH));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* permit1: check mode of terminal - if not writable by all disallow writing
|
|
* to (even the user him/herself cannot therefore write to their own tty)
|
|
*/
|
|
|
|
/* this is used with fstat (which is faster than stat) where possible */
|
|
|
|
static int
|
|
permit1(fildes)
|
|
int fildes;
|
|
{
|
|
struct stat buf;
|
|
|
|
fstat(fildes, &buf);
|
|
return (buf.st_mode & (S_IWGRP|S_IWOTH));
|
|
}
|
|
|
|
|
|
/*
|
|
Read a string of EUC characters from specified file.
|
|
* The requested # of bytes are attempted to read.
|
|
* readeuc() tries to complete the last multibyte character
|
|
* by calling read() again, when the first read() call
|
|
* left the last char imcomplete. The caller must reserve
|
|
* nbytereq+MB_LEN_MAX bytes for the buffer. When the attempt
|
|
* is failed, it truncate the last char.
|
|
* Returns the number of bytes that constitutes the valid EUC characters.
|
|
*/
|
|
|
|
static int readeuc(d, buf, nbytereq)
|
|
int d;
|
|
char *buf;
|
|
int nbytereq;
|
|
{
|
|
char *cp, *nextp, *lastp;
|
|
int n;
|
|
int delta;
|
|
|
|
n = read(d, buf, nbytereq);
|
|
if (n <= 0)
|
|
return (n);
|
|
|
|
/* Scan the result to test the completeness of each EUC characters. */
|
|
nextp = buf;
|
|
lastp = buf + n; /* Lastp points to the first junk byte. */
|
|
while (nextp < lastp) {
|
|
cp = nextp;
|
|
nextp += euclen((const unsigned char *)cp);
|
|
}
|
|
/* How many bytes needed to complete the last char? */
|
|
delta = nextp - lastp;
|
|
if (delta > 0) {
|
|
n = read(d, lastp, delta); /* Try reading that many bytes. */
|
|
if (n != delta) {
|
|
/* Failed! Pretend we dindn't read the garbage bytes. */
|
|
nextp = cp;
|
|
}
|
|
}
|
|
return (nextp-buf); /* Return # of bytes. */
|
|
}
|