18a16b7a1SPedro F. Giffuni /*-
28a16b7a1SPedro F. Giffuni * SPDX-License-Identifier: BSD-3-Clause
38a16b7a1SPedro F. Giffuni *
49b50d902SRodney W. Grimes * Copyright (c) 1989, 1993
59b50d902SRodney W. Grimes * The Regents of the University of California. All rights reserved.
69b50d902SRodney W. Grimes *
79b50d902SRodney W. Grimes * This code is derived from software contributed to Berkeley by
89b50d902SRodney W. Grimes * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory.
99b50d902SRodney W. Grimes *
109b50d902SRodney W. Grimes * Redistribution and use in source and binary forms, with or without
119b50d902SRodney W. Grimes * modification, are permitted provided that the following conditions
129b50d902SRodney W. Grimes * are met:
139b50d902SRodney W. Grimes * 1. Redistributions of source code must retain the above copyright
149b50d902SRodney W. Grimes * notice, this list of conditions and the following disclaimer.
159b50d902SRodney W. Grimes * 2. Redistributions in binary form must reproduce the above copyright
169b50d902SRodney W. Grimes * notice, this list of conditions and the following disclaimer in the
179b50d902SRodney W. Grimes * documentation and/or other materials provided with the distribution.
18fbbd9655SWarner Losh * 3. Neither the name of the University nor the names of its contributors
199b50d902SRodney W. Grimes * may be used to endorse or promote products derived from this software
209b50d902SRodney W. Grimes * without specific prior written permission.
219b50d902SRodney W. Grimes *
229b50d902SRodney W. Grimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
239b50d902SRodney W. Grimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
249b50d902SRodney W. Grimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
259b50d902SRodney W. Grimes * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
269b50d902SRodney W. Grimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
279b50d902SRodney W. Grimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
289b50d902SRodney W. Grimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
299b50d902SRodney W. Grimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
309b50d902SRodney W. Grimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
319b50d902SRodney W. Grimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
329b50d902SRodney W. Grimes * SUCH DAMAGE.
339b50d902SRodney W. Grimes */
349b50d902SRodney W. Grimes
359b50d902SRodney W. Grimes #include <sys/param.h>
3638adbfe6SConrad Meyer #include <sys/capsicum.h>
3738adbfe6SConrad Meyer #include <sys/filio.h>
389b50d902SRodney W. Grimes #include <sys/signal.h>
399b50d902SRodney W. Grimes #include <sys/stat.h>
409b50d902SRodney W. Grimes #include <sys/time.h>
4138adbfe6SConrad Meyer
4238adbfe6SConrad Meyer #include <capsicum_helpers.h>
439b50d902SRodney W. Grimes #include <ctype.h>
4429909ffeSPhilippe Charnier #include <err.h>
4538adbfe6SConrad Meyer #include <errno.h>
4677caf211SAndrey A. Chernov #include <locale.h>
472d754ac8SAlexander Langer #include <paths.h>
489b50d902SRodney W. Grimes #include <pwd.h>
499b50d902SRodney W. Grimes #include <stdio.h>
50421dfbcfSDavid Malone #include <stdlib.h>
519b50d902SRodney W. Grimes #include <string.h>
5229909ffeSPhilippe Charnier #include <unistd.h>
53ab90a4d1SEd Schouten #include <utmpx.h>
540de6400bSGleb Smirnoff #include <wchar.h>
550de6400bSGleb Smirnoff #include <wctype.h>
569b50d902SRodney W. Grimes
573f330d7dSWarner Losh void done(int);
5838adbfe6SConrad Meyer void do_write(int, char *, char *, const char *);
59*cccdaf50SAlfonso Gregory static void usage(void) __dead2;
6038adbfe6SConrad Meyer int term_chk(int, char *, int *, time_t *, int);
610de6400bSGleb Smirnoff void wr_fputs(wchar_t *s);
6238adbfe6SConrad Meyer void search_utmp(int, char *, char *, char *, uid_t);
633f330d7dSWarner Losh int utmp_chk(char *, char *);
649b50d902SRodney W. Grimes
6529909ffeSPhilippe Charnier int
main(int argc,char ** argv)66f4ac32deSDavid Malone main(int argc, char **argv)
679b50d902SRodney W. Grimes {
6838adbfe6SConrad Meyer unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME };
6938adbfe6SConrad Meyer cap_rights_t rights;
7038adbfe6SConrad Meyer struct passwd *pwd;
719b50d902SRodney W. Grimes time_t atime;
729b50d902SRodney W. Grimes uid_t myuid;
739b50d902SRodney W. Grimes int msgsok, myttyfd;
7429909ffeSPhilippe Charnier char tty[MAXPATHLEN], *mytty;
7538adbfe6SConrad Meyer const char *login;
7638adbfe6SConrad Meyer int devfd;
779b50d902SRodney W. Grimes
7877caf211SAndrey A. Chernov (void)setlocale(LC_CTYPE, "");
7977caf211SAndrey A. Chernov
8038adbfe6SConrad Meyer devfd = open(_PATH_DEV, O_RDONLY);
8138adbfe6SConrad Meyer if (devfd < 0)
8238adbfe6SConrad Meyer err(1, "open(/dev)");
8338adbfe6SConrad Meyer cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP,
8438adbfe6SConrad Meyer CAP_PWRITE);
85377421dfSMariusz Zaborski if (caph_rights_limit(devfd, &rights) < 0)
8638adbfe6SConrad Meyer err(1, "can't limit devfd rights");
8738adbfe6SConrad Meyer
8838adbfe6SConrad Meyer /*
8938adbfe6SConrad Meyer * Can't use capsicum helpers here because we need the additional
9038adbfe6SConrad Meyer * FIODGNAME ioctl.
9138adbfe6SConrad Meyer */
9238adbfe6SConrad Meyer cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ,
9338adbfe6SConrad Meyer CAP_WRITE);
94377421dfSMariusz Zaborski if (caph_rights_limit(STDIN_FILENO, &rights) < 0 ||
95377421dfSMariusz Zaborski caph_rights_limit(STDOUT_FILENO, &rights) < 0 ||
96377421dfSMariusz Zaborski caph_rights_limit(STDERR_FILENO, &rights) < 0 ||
97377421dfSMariusz Zaborski caph_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 ||
98377421dfSMariusz Zaborski caph_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 ||
99377421dfSMariusz Zaborski caph_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 ||
100377421dfSMariusz Zaborski caph_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 ||
101377421dfSMariusz Zaborski caph_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 ||
102377421dfSMariusz Zaborski caph_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0)
10338adbfe6SConrad Meyer err(1, "can't limit stdio rights");
10438adbfe6SConrad Meyer
10538adbfe6SConrad Meyer caph_cache_catpages();
10638adbfe6SConrad Meyer caph_cache_tzdata();
10738adbfe6SConrad Meyer
10838adbfe6SConrad Meyer /*
10938adbfe6SConrad Meyer * Cache UTX database fds.
11038adbfe6SConrad Meyer */
11138adbfe6SConrad Meyer setutxent();
11238adbfe6SConrad Meyer
11338adbfe6SConrad Meyer /*
11438adbfe6SConrad Meyer * Determine our login name before we reopen() stdout
11538adbfe6SConrad Meyer * and before entering capability sandbox.
11638adbfe6SConrad Meyer */
11738adbfe6SConrad Meyer myuid = getuid();
11838adbfe6SConrad Meyer if ((login = getlogin()) == NULL) {
11938adbfe6SConrad Meyer if ((pwd = getpwuid(myuid)))
12038adbfe6SConrad Meyer login = pwd->pw_name;
12138adbfe6SConrad Meyer else
12238adbfe6SConrad Meyer login = "???";
12338adbfe6SConrad Meyer }
12438adbfe6SConrad Meyer
1257672a014SMariusz Zaborski if (caph_enter() < 0)
12638adbfe6SConrad Meyer err(1, "cap_enter");
12738adbfe6SConrad Meyer
128759484baSTim J. Robbins while (getopt(argc, argv, "") != -1)
129759484baSTim J. Robbins usage();
130759484baSTim J. Robbins argc -= optind;
131759484baSTim J. Robbins argv += optind;
132759484baSTim J. Robbins
1339b50d902SRodney W. Grimes /* check that sender has write enabled */
1349b50d902SRodney W. Grimes if (isatty(fileno(stdin)))
1359b50d902SRodney W. Grimes myttyfd = fileno(stdin);
1369b50d902SRodney W. Grimes else if (isatty(fileno(stdout)))
1379b50d902SRodney W. Grimes myttyfd = fileno(stdout);
1389b50d902SRodney W. Grimes else if (isatty(fileno(stderr)))
1399b50d902SRodney W. Grimes myttyfd = fileno(stderr);
14029909ffeSPhilippe Charnier else
14129909ffeSPhilippe Charnier errx(1, "can't find your tty");
14229909ffeSPhilippe Charnier if (!(mytty = ttyname(myttyfd)))
14329909ffeSPhilippe Charnier errx(1, "can't find your tty's name");
144a4cc298aSJohn Baldwin if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV)))
145a4cc298aSJohn Baldwin mytty += strlen(_PATH_DEV);
14638adbfe6SConrad Meyer if (term_chk(devfd, mytty, &msgsok, &atime, 1))
1479b50d902SRodney W. Grimes exit(1);
14829909ffeSPhilippe Charnier if (!msgsok)
14929909ffeSPhilippe Charnier errx(1, "you have write permission turned off");
1509b50d902SRodney W. Grimes
1519b50d902SRodney W. Grimes /* check args */
1529b50d902SRodney W. Grimes switch (argc) {
153759484baSTim J. Robbins case 1:
15438adbfe6SConrad Meyer search_utmp(devfd, argv[0], tty, mytty, myuid);
15538adbfe6SConrad Meyer do_write(devfd, tty, mytty, login);
1569b50d902SRodney W. Grimes break;
157759484baSTim J. Robbins case 2:
158759484baSTim J. Robbins if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV)))
159759484baSTim J. Robbins argv[1] += strlen(_PATH_DEV);
160759484baSTim J. Robbins if (utmp_chk(argv[0], argv[1]))
161759484baSTim J. Robbins errx(1, "%s is not logged in on %s", argv[0], argv[1]);
16238adbfe6SConrad Meyer if (term_chk(devfd, argv[1], &msgsok, &atime, 1))
1639b50d902SRodney W. Grimes exit(1);
16429909ffeSPhilippe Charnier if (myuid && !msgsok)
165759484baSTim J. Robbins errx(1, "%s has messages disabled on %s", argv[0], argv[1]);
16638adbfe6SConrad Meyer do_write(devfd, argv[1], mytty, login);
1679b50d902SRodney W. Grimes break;
1689b50d902SRodney W. Grimes default:
16929909ffeSPhilippe Charnier usage();
1709b50d902SRodney W. Grimes }
17177caf211SAndrey A. Chernov done(0);
17229909ffeSPhilippe Charnier return (0);
17329909ffeSPhilippe Charnier }
17429909ffeSPhilippe Charnier
17529909ffeSPhilippe Charnier static void
usage(void)176f4ac32deSDavid Malone usage(void)
17729909ffeSPhilippe Charnier {
17829909ffeSPhilippe Charnier (void)fprintf(stderr, "usage: write user [tty]\n");
17929909ffeSPhilippe Charnier exit(1);
1809b50d902SRodney W. Grimes }
1819b50d902SRodney W. Grimes
1829b50d902SRodney W. Grimes /*
1839b50d902SRodney W. Grimes * utmp_chk - checks that the given user is actually logged in on
1849b50d902SRodney W. Grimes * the given tty
1859b50d902SRodney W. Grimes */
18629909ffeSPhilippe Charnier int
utmp_chk(char * user,char * tty)187f4ac32deSDavid Malone utmp_chk(char *user, char *tty)
1889b50d902SRodney W. Grimes {
18950950530SEd Schouten struct utmpx lu, *u;
1909b50d902SRodney W. Grimes
19150950530SEd Schouten strncpy(lu.ut_line, tty, sizeof lu.ut_line);
19250950530SEd Schouten while ((u = getutxline(&lu)) != NULL)
19350950530SEd Schouten if (u->ut_type == USER_PROCESS &&
19450950530SEd Schouten strcmp(user, u->ut_user) == 0) {
19550950530SEd Schouten endutxent();
1969b50d902SRodney W. Grimes return(0);
1979b50d902SRodney W. Grimes }
19850950530SEd Schouten endutxent();
1999b50d902SRodney W. Grimes return(1);
2009b50d902SRodney W. Grimes }
2019b50d902SRodney W. Grimes
2029b50d902SRodney W. Grimes /*
2039b50d902SRodney W. Grimes * search_utmp - search utmp for the "best" terminal to write to
2049b50d902SRodney W. Grimes *
2059b50d902SRodney W. Grimes * Ignores terminals with messages disabled, and of the rest, returns
2069b50d902SRodney W. Grimes * the one with the most recent access time. Returns as value the number
2079b50d902SRodney W. Grimes * of the user's terminals with messages enabled, or -1 if the user is
2089b50d902SRodney W. Grimes * not logged in at all.
2099b50d902SRodney W. Grimes *
2109b50d902SRodney W. Grimes * Special case for writing to yourself - ignore the terminal you're
2119b50d902SRodney W. Grimes * writing from, unless that's the only terminal with messages enabled.
2129b50d902SRodney W. Grimes */
21329909ffeSPhilippe Charnier void
search_utmp(int devfd,char * user,char * tty,char * mytty,uid_t myuid)21438adbfe6SConrad Meyer search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid)
2159b50d902SRodney W. Grimes {
21650950530SEd Schouten struct utmpx *u;
2179b50d902SRodney W. Grimes time_t bestatime, atime;
21850950530SEd Schouten int nloggedttys, nttys, msgsok, user_is_me;
2199b50d902SRodney W. Grimes
2209b50d902SRodney W. Grimes nloggedttys = nttys = 0;
2219b50d902SRodney W. Grimes bestatime = 0;
2229b50d902SRodney W. Grimes user_is_me = 0;
22350950530SEd Schouten
22450950530SEd Schouten while ((u = getutxent()) != NULL)
22550950530SEd Schouten if (u->ut_type == USER_PROCESS &&
22650950530SEd Schouten strcmp(user, u->ut_user) == 0) {
2279b50d902SRodney W. Grimes ++nloggedttys;
22838adbfe6SConrad Meyer if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0))
2299b50d902SRodney W. Grimes continue; /* bad term? skip */
2309b50d902SRodney W. Grimes if (myuid && !msgsok)
2319b50d902SRodney W. Grimes continue; /* skip ttys with msgs off */
23250950530SEd Schouten if (strcmp(u->ut_line, mytty) == 0) {
2339b50d902SRodney W. Grimes user_is_me = 1;
2349b50d902SRodney W. Grimes continue; /* don't write to yourself */
2359b50d902SRodney W. Grimes }
2369b50d902SRodney W. Grimes ++nttys;
2379b50d902SRodney W. Grimes if (atime > bestatime) {
2389b50d902SRodney W. Grimes bestatime = atime;
23950950530SEd Schouten (void)strlcpy(tty, u->ut_line, MAXPATHLEN);
2409b50d902SRodney W. Grimes }
2419b50d902SRodney W. Grimes }
24250950530SEd Schouten endutxent();
2439b50d902SRodney W. Grimes
24429909ffeSPhilippe Charnier if (nloggedttys == 0)
24529909ffeSPhilippe Charnier errx(1, "%s is not logged in", user);
2469b50d902SRodney W. Grimes if (nttys == 0) {
2479b50d902SRodney W. Grimes if (user_is_me) { /* ok, so write to yourself! */
24850950530SEd Schouten (void)strlcpy(tty, mytty, MAXPATHLEN);
2499b50d902SRodney W. Grimes return;
2509b50d902SRodney W. Grimes }
25129909ffeSPhilippe Charnier errx(1, "%s has messages disabled", user);
2529b50d902SRodney W. Grimes } else if (nttys > 1) {
25329909ffeSPhilippe Charnier warnx("%s is logged in more than once; writing to %s", user, tty);
2549b50d902SRodney W. Grimes }
2559b50d902SRodney W. Grimes }
2569b50d902SRodney W. Grimes
2579b50d902SRodney W. Grimes /*
2589b50d902SRodney W. Grimes * term_chk - check that a terminal exists, and get the message bit
2599b50d902SRodney W. Grimes * and the access time
2609b50d902SRodney W. Grimes */
26129909ffeSPhilippe Charnier int
term_chk(int devfd,char * tty,int * msgsokP,time_t * atimeP,int showerror)26238adbfe6SConrad Meyer term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror)
2639b50d902SRodney W. Grimes {
2649b50d902SRodney W. Grimes struct stat s;
2659b50d902SRodney W. Grimes
26638adbfe6SConrad Meyer if (fstatat(devfd, tty, &s, 0) < 0) {
2679b50d902SRodney W. Grimes if (showerror)
26838adbfe6SConrad Meyer warn("%s%s", _PATH_DEV, tty);
2699b50d902SRodney W. Grimes return(1);
2709b50d902SRodney W. Grimes }
2719b50d902SRodney W. Grimes *msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0; /* group write bit */
2729b50d902SRodney W. Grimes *atimeP = s.st_atime;
2739b50d902SRodney W. Grimes return(0);
2749b50d902SRodney W. Grimes }
2759b50d902SRodney W. Grimes
2769b50d902SRodney W. Grimes /*
2779b50d902SRodney W. Grimes * do_write - actually make the connection
2789b50d902SRodney W. Grimes */
27929909ffeSPhilippe Charnier void
do_write(int devfd,char * tty,char * mytty,const char * login)28038adbfe6SConrad Meyer do_write(int devfd, char *tty, char *mytty, const char *login)
2819b50d902SRodney W. Grimes {
2829ba3a235SMark Murray char *nows;
28329909ffeSPhilippe Charnier time_t now;
28438adbfe6SConrad Meyer char host[MAXHOSTNAMELEN];
2850de6400bSGleb Smirnoff wchar_t line[512];
28638adbfe6SConrad Meyer int fd;
2879b50d902SRodney W. Grimes
28838adbfe6SConrad Meyer fd = openat(devfd, tty, O_WRONLY);
28938adbfe6SConrad Meyer if (fd < 0)
29038adbfe6SConrad Meyer err(1, "openat(%s%s)", _PATH_DEV, tty);
29138adbfe6SConrad Meyer fclose(stdout);
29238adbfe6SConrad Meyer stdout = fdopen(fd, "w");
29338adbfe6SConrad Meyer if (stdout == NULL)
29438adbfe6SConrad Meyer err(1, "%s%s", _PATH_DEV, tty);
2959b50d902SRodney W. Grimes
2969b50d902SRodney W. Grimes (void)signal(SIGINT, done);
2979b50d902SRodney W. Grimes (void)signal(SIGHUP, done);
2989b50d902SRodney W. Grimes
2999b50d902SRodney W. Grimes /* print greeting */
3009b50d902SRodney W. Grimes if (gethostname(host, sizeof(host)) < 0)
3019b50d902SRodney W. Grimes (void)strcpy(host, "???");
3029b50d902SRodney W. Grimes now = time((time_t *)NULL);
3039b50d902SRodney W. Grimes nows = ctime(&now);
3049b50d902SRodney W. Grimes nows[16] = '\0';
3059b50d902SRodney W. Grimes (void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n",
3069b50d902SRodney W. Grimes login, host, mytty, nows + 11);
3079b50d902SRodney W. Grimes
3080de6400bSGleb Smirnoff while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL)
3099b50d902SRodney W. Grimes wr_fputs(line);
3109b50d902SRodney W. Grimes }
3119b50d902SRodney W. Grimes
3129b50d902SRodney W. Grimes /*
3139b50d902SRodney W. Grimes * done - cleanup and exit
3149b50d902SRodney W. Grimes */
3159b50d902SRodney W. Grimes void
done(int n __unused)316f4ac32deSDavid Malone done(int n __unused)
3179b50d902SRodney W. Grimes {
3189b50d902SRodney W. Grimes (void)printf("EOF\r\n");
3199b50d902SRodney W. Grimes exit(0);
3209b50d902SRodney W. Grimes }
3219b50d902SRodney W. Grimes
3229b50d902SRodney W. Grimes /*
3239b50d902SRodney W. Grimes * wr_fputs - like fputs(), but makes control characters visible and
3249b50d902SRodney W. Grimes * turns \n into \r\n
3259b50d902SRodney W. Grimes */
32629909ffeSPhilippe Charnier void
wr_fputs(wchar_t * s)3270de6400bSGleb Smirnoff wr_fputs(wchar_t *s)
3289b50d902SRodney W. Grimes {
3299b50d902SRodney W. Grimes
3300de6400bSGleb Smirnoff #define PUTC(c) if (putwchar(c) == WEOF) err(1, NULL);
3319b50d902SRodney W. Grimes
3320de6400bSGleb Smirnoff for (; *s != L'\0'; ++s) {
3330de6400bSGleb Smirnoff if (*s == L'\n') {
3340de6400bSGleb Smirnoff PUTC(L'\r');
3350de6400bSGleb Smirnoff PUTC(L'\n');
3360de6400bSGleb Smirnoff } else if (iswprint(*s) || iswspace(*s)) {
337b6c671c7SAndrey A. Chernov PUTC(*s);
3380de6400bSGleb Smirnoff } else {
3390de6400bSGleb Smirnoff wprintf(L"<0x%X>", *s);
3400de6400bSGleb Smirnoff }
3419b50d902SRodney W. Grimes }
3429b50d902SRodney W. Grimes return;
3439b50d902SRodney W. Grimes #undef PUTC
3449b50d902SRodney W. Grimes }
345