1 /*- 2 * SPDX-License-Identifier: BSD-3-Clause 3 * 4 * Copyright (c) 1989, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * This code is derived from software contributed to Berkeley by 8 * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of the University nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 32 * SUCH DAMAGE. 33 */ 34 35 #ifndef lint 36 static const char copyright[] = 37 "@(#) Copyright (c) 1989, 1993\n\ 38 The Regents of the University of California. All rights reserved.\n"; 39 #endif 40 41 #if 0 42 #endif 43 44 #include <sys/cdefs.h> 45 #include <sys/param.h> 46 #include <sys/capsicum.h> 47 #include <sys/filio.h> 48 #include <sys/signal.h> 49 #include <sys/stat.h> 50 #include <sys/time.h> 51 52 #include <capsicum_helpers.h> 53 #include <ctype.h> 54 #include <err.h> 55 #include <errno.h> 56 #include <locale.h> 57 #include <paths.h> 58 #include <pwd.h> 59 #include <stdio.h> 60 #include <stdlib.h> 61 #include <string.h> 62 #include <unistd.h> 63 #include <utmpx.h> 64 #include <wchar.h> 65 #include <wctype.h> 66 67 void done(int); 68 void do_write(int, char *, char *, const char *); 69 static void usage(void) __dead2; 70 int term_chk(int, char *, int *, time_t *, int); 71 void wr_fputs(wchar_t *s); 72 void search_utmp(int, char *, char *, char *, uid_t); 73 int utmp_chk(char *, char *); 74 75 int 76 main(int argc, char **argv) 77 { 78 unsigned long cmds[] = { TIOCGETA, TIOCGWINSZ, FIODGNAME }; 79 cap_rights_t rights; 80 struct passwd *pwd; 81 time_t atime; 82 uid_t myuid; 83 int msgsok, myttyfd; 84 char tty[MAXPATHLEN], *mytty; 85 const char *login; 86 int devfd; 87 88 (void)setlocale(LC_CTYPE, ""); 89 90 devfd = open(_PATH_DEV, O_RDONLY); 91 if (devfd < 0) 92 err(1, "open(/dev)"); 93 cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_LOOKUP, 94 CAP_PWRITE); 95 if (caph_rights_limit(devfd, &rights) < 0) 96 err(1, "can't limit devfd rights"); 97 98 /* 99 * Can't use capsicum helpers here because we need the additional 100 * FIODGNAME ioctl. 101 */ 102 cap_rights_init(&rights, CAP_FCNTL, CAP_FSTAT, CAP_IOCTL, CAP_READ, 103 CAP_WRITE); 104 if (caph_rights_limit(STDIN_FILENO, &rights) < 0 || 105 caph_rights_limit(STDOUT_FILENO, &rights) < 0 || 106 caph_rights_limit(STDERR_FILENO, &rights) < 0 || 107 caph_ioctls_limit(STDIN_FILENO, cmds, nitems(cmds)) < 0 || 108 caph_ioctls_limit(STDOUT_FILENO, cmds, nitems(cmds)) < 0 || 109 caph_ioctls_limit(STDERR_FILENO, cmds, nitems(cmds)) < 0 || 110 caph_fcntls_limit(STDIN_FILENO, CAP_FCNTL_GETFL) < 0 || 111 caph_fcntls_limit(STDOUT_FILENO, CAP_FCNTL_GETFL) < 0 || 112 caph_fcntls_limit(STDERR_FILENO, CAP_FCNTL_GETFL) < 0) 113 err(1, "can't limit stdio rights"); 114 115 caph_cache_catpages(); 116 caph_cache_tzdata(); 117 118 /* 119 * Cache UTX database fds. 120 */ 121 setutxent(); 122 123 /* 124 * Determine our login name before we reopen() stdout 125 * and before entering capability sandbox. 126 */ 127 myuid = getuid(); 128 if ((login = getlogin()) == NULL) { 129 if ((pwd = getpwuid(myuid))) 130 login = pwd->pw_name; 131 else 132 login = "???"; 133 } 134 135 if (caph_enter() < 0) 136 err(1, "cap_enter"); 137 138 while (getopt(argc, argv, "") != -1) 139 usage(); 140 argc -= optind; 141 argv += optind; 142 143 /* check that sender has write enabled */ 144 if (isatty(fileno(stdin))) 145 myttyfd = fileno(stdin); 146 else if (isatty(fileno(stdout))) 147 myttyfd = fileno(stdout); 148 else if (isatty(fileno(stderr))) 149 myttyfd = fileno(stderr); 150 else 151 errx(1, "can't find your tty"); 152 if (!(mytty = ttyname(myttyfd))) 153 errx(1, "can't find your tty's name"); 154 if (!strncmp(mytty, _PATH_DEV, strlen(_PATH_DEV))) 155 mytty += strlen(_PATH_DEV); 156 if (term_chk(devfd, mytty, &msgsok, &atime, 1)) 157 exit(1); 158 if (!msgsok) 159 errx(1, "you have write permission turned off"); 160 161 /* check args */ 162 switch (argc) { 163 case 1: 164 search_utmp(devfd, argv[0], tty, mytty, myuid); 165 do_write(devfd, tty, mytty, login); 166 break; 167 case 2: 168 if (!strncmp(argv[1], _PATH_DEV, strlen(_PATH_DEV))) 169 argv[1] += strlen(_PATH_DEV); 170 if (utmp_chk(argv[0], argv[1])) 171 errx(1, "%s is not logged in on %s", argv[0], argv[1]); 172 if (term_chk(devfd, argv[1], &msgsok, &atime, 1)) 173 exit(1); 174 if (myuid && !msgsok) 175 errx(1, "%s has messages disabled on %s", argv[0], argv[1]); 176 do_write(devfd, argv[1], mytty, login); 177 break; 178 default: 179 usage(); 180 } 181 done(0); 182 return (0); 183 } 184 185 static void 186 usage(void) 187 { 188 (void)fprintf(stderr, "usage: write user [tty]\n"); 189 exit(1); 190 } 191 192 /* 193 * utmp_chk - checks that the given user is actually logged in on 194 * the given tty 195 */ 196 int 197 utmp_chk(char *user, char *tty) 198 { 199 struct utmpx lu, *u; 200 201 strncpy(lu.ut_line, tty, sizeof lu.ut_line); 202 while ((u = getutxline(&lu)) != NULL) 203 if (u->ut_type == USER_PROCESS && 204 strcmp(user, u->ut_user) == 0) { 205 endutxent(); 206 return(0); 207 } 208 endutxent(); 209 return(1); 210 } 211 212 /* 213 * search_utmp - search utmp for the "best" terminal to write to 214 * 215 * Ignores terminals with messages disabled, and of the rest, returns 216 * the one with the most recent access time. Returns as value the number 217 * of the user's terminals with messages enabled, or -1 if the user is 218 * not logged in at all. 219 * 220 * Special case for writing to yourself - ignore the terminal you're 221 * writing from, unless that's the only terminal with messages enabled. 222 */ 223 void 224 search_utmp(int devfd, char *user, char *tty, char *mytty, uid_t myuid) 225 { 226 struct utmpx *u; 227 time_t bestatime, atime; 228 int nloggedttys, nttys, msgsok, user_is_me; 229 230 nloggedttys = nttys = 0; 231 bestatime = 0; 232 user_is_me = 0; 233 234 while ((u = getutxent()) != NULL) 235 if (u->ut_type == USER_PROCESS && 236 strcmp(user, u->ut_user) == 0) { 237 ++nloggedttys; 238 if (term_chk(devfd, u->ut_line, &msgsok, &atime, 0)) 239 continue; /* bad term? skip */ 240 if (myuid && !msgsok) 241 continue; /* skip ttys with msgs off */ 242 if (strcmp(u->ut_line, mytty) == 0) { 243 user_is_me = 1; 244 continue; /* don't write to yourself */ 245 } 246 ++nttys; 247 if (atime > bestatime) { 248 bestatime = atime; 249 (void)strlcpy(tty, u->ut_line, MAXPATHLEN); 250 } 251 } 252 endutxent(); 253 254 if (nloggedttys == 0) 255 errx(1, "%s is not logged in", user); 256 if (nttys == 0) { 257 if (user_is_me) { /* ok, so write to yourself! */ 258 (void)strlcpy(tty, mytty, MAXPATHLEN); 259 return; 260 } 261 errx(1, "%s has messages disabled", user); 262 } else if (nttys > 1) { 263 warnx("%s is logged in more than once; writing to %s", user, tty); 264 } 265 } 266 267 /* 268 * term_chk - check that a terminal exists, and get the message bit 269 * and the access time 270 */ 271 int 272 term_chk(int devfd, char *tty, int *msgsokP, time_t *atimeP, int showerror) 273 { 274 struct stat s; 275 276 if (fstatat(devfd, tty, &s, 0) < 0) { 277 if (showerror) 278 warn("%s%s", _PATH_DEV, tty); 279 return(1); 280 } 281 *msgsokP = (s.st_mode & (S_IWRITE >> 3)) != 0; /* group write bit */ 282 *atimeP = s.st_atime; 283 return(0); 284 } 285 286 /* 287 * do_write - actually make the connection 288 */ 289 void 290 do_write(int devfd, char *tty, char *mytty, const char *login) 291 { 292 char *nows; 293 time_t now; 294 char host[MAXHOSTNAMELEN]; 295 wchar_t line[512]; 296 int fd; 297 298 fd = openat(devfd, tty, O_WRONLY); 299 if (fd < 0) 300 err(1, "openat(%s%s)", _PATH_DEV, tty); 301 fclose(stdout); 302 stdout = fdopen(fd, "w"); 303 if (stdout == NULL) 304 err(1, "%s%s", _PATH_DEV, tty); 305 306 (void)signal(SIGINT, done); 307 (void)signal(SIGHUP, done); 308 309 /* print greeting */ 310 if (gethostname(host, sizeof(host)) < 0) 311 (void)strcpy(host, "???"); 312 now = time((time_t *)NULL); 313 nows = ctime(&now); 314 nows[16] = '\0'; 315 (void)printf("\r\n\007\007\007Message from %s@%s on %s at %s ...\r\n", 316 login, host, mytty, nows + 11); 317 318 while (fgetws(line, sizeof(line)/sizeof(wchar_t), stdin) != NULL) 319 wr_fputs(line); 320 } 321 322 /* 323 * done - cleanup and exit 324 */ 325 void 326 done(int n __unused) 327 { 328 (void)printf("EOF\r\n"); 329 exit(0); 330 } 331 332 /* 333 * wr_fputs - like fputs(), but makes control characters visible and 334 * turns \n into \r\n 335 */ 336 void 337 wr_fputs(wchar_t *s) 338 { 339 340 #define PUTC(c) if (putwchar(c) == WEOF) err(1, NULL); 341 342 for (; *s != L'\0'; ++s) { 343 if (*s == L'\n') { 344 PUTC(L'\r'); 345 PUTC(L'\n'); 346 } else if (iswprint(*s) || iswspace(*s)) { 347 PUTC(*s); 348 } else { 349 wprintf(L"<0x%X>", *s); 350 } 351 } 352 return; 353 #undef PUTC 354 } 355