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 * Kevin Fall. 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 #include <sys/capsicum.h> 36 #include <sys/param.h> 37 #include <sys/stat.h> 38 #ifndef NO_UDOM_SUPPORT 39 #include <sys/socket.h> 40 #include <sys/un.h> 41 #include <netdb.h> 42 #endif 43 44 #include <capsicum_helpers.h> 45 #include <ctype.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <fcntl.h> 49 #include <locale.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 #include <unistd.h> 54 #include <wchar.h> 55 #include <wctype.h> 56 57 #include <libcasper.h> 58 #include <casper/cap_fileargs.h> 59 #include <casper/cap_net.h> 60 61 static int bflag, Eflag, lflag, nflag, sflag, Tflag, vflag; 62 static int rval; 63 static const char *filename; 64 static fileargs_t *fa; 65 66 static void usage(void) __dead2; 67 static void scanfiles(char *argv[], int cooked); 68 #ifndef BOOTSTRAP_CAT 69 static void cook_cat(FILE *); 70 static ssize_t in_kernel_copy(int); 71 #endif 72 static void raw_cat(int); 73 74 #ifndef NO_UDOM_SUPPORT 75 static cap_channel_t *capnet; 76 77 static int udom_open(const char *path, int flags); 78 #endif 79 80 /* 81 * Memory strategy threshold, in pages: if physmem is larger than this, 82 * use a large buffer. 83 */ 84 #define PHYSPAGES_THRESHOLD (32 * 1024) 85 86 /* Maximum buffer size in bytes - do not allow it to grow larger than this. */ 87 #define BUFSIZE_MAX (2 * 1024 * 1024) 88 89 /* 90 * Small (default) buffer size in bytes. It's inefficient for this to be 91 * smaller than MAXPHYS. 92 */ 93 #define BUFSIZE_SMALL (MAXPHYS) 94 95 96 /* 97 * For the bootstrapped cat binary (needed for locked appending to METALOG), we 98 * disable all flags except -l and -u to avoid non-portable function calls. 99 * In the future we may instead want to write a small portable bootstrap tool 100 * that locks the output file before writing to it. However, for now 101 * bootstrapping cat without multibyte support is the simpler solution. 102 */ 103 #ifdef BOOTSTRAP_CAT 104 #define SUPPORTED_FLAGS "lu" 105 #else 106 #define SUPPORTED_FLAGS "belnstuvAET" 107 #endif 108 109 #ifndef NO_UDOM_SUPPORT 110 static void 111 init_casper_net(cap_channel_t *casper) 112 { 113 cap_net_limit_t *limit; 114 int familylimit; 115 116 capnet = cap_service_open(casper, "system.net"); 117 if (capnet == NULL) 118 err(EXIT_FAILURE, "unable to create network service"); 119 120 limit = cap_net_limit_init(capnet, CAPNET_NAME2ADDR | 121 CAPNET_CONNECTDNS); 122 if (limit == NULL) 123 err(EXIT_FAILURE, "unable to create limits"); 124 125 familylimit = AF_LOCAL; 126 cap_net_limit_name2addr_family(limit, &familylimit, 1); 127 128 if (cap_net_limit(limit) != 0) 129 err(EXIT_FAILURE, "unable to apply limits"); 130 } 131 #endif 132 133 static void 134 init_casper(int argc, char *argv[]) 135 { 136 cap_channel_t *casper; 137 cap_rights_t rights; 138 139 casper = cap_init(); 140 if (casper == NULL) 141 err(EXIT_FAILURE, "unable to create Casper"); 142 143 fa = fileargs_cinit(casper, argc, argv, O_RDONLY, 0, 144 cap_rights_init(&rights, CAP_READ, CAP_FSTAT, CAP_FCNTL, CAP_SEEK), 145 FA_OPEN | FA_REALPATH); 146 if (fa == NULL) 147 err(EXIT_FAILURE, "unable to create fileargs"); 148 149 #ifndef NO_UDOM_SUPPORT 150 init_casper_net(casper); 151 #endif 152 153 cap_close(casper); 154 } 155 156 int 157 main(int argc, char *argv[]) 158 { 159 int ch; 160 struct flock stdout_lock; 161 162 setlocale(LC_CTYPE, ""); 163 164 while ((ch = getopt(argc, argv, SUPPORTED_FLAGS)) != -1) 165 switch (ch) { 166 case 'b': 167 bflag = nflag = 1; /* -b implies -n */ 168 break; 169 case 'A': 170 Eflag = Tflag = vflag = 1; /* -A implies -v -E -T */ 171 break; 172 case 'e': 173 Eflag = vflag = 1; /* -e implies -v */ 174 break; 175 case 'E': 176 Eflag = 1; 177 break; 178 case 'l': 179 lflag = 1; 180 break; 181 case 'n': 182 nflag = 1; 183 break; 184 case 's': 185 sflag = 1; 186 break; 187 case 't': 188 Tflag = vflag = 1; /* -t implies -v */ 189 break; 190 case 'T': 191 Tflag = 1; 192 break; 193 case 'u': 194 setbuf(stdout, NULL); 195 break; 196 case 'v': 197 vflag = 1; 198 break; 199 default: 200 usage(); 201 } 202 argv += optind; 203 argc -= optind; 204 205 if (lflag) { 206 stdout_lock.l_len = 0; 207 stdout_lock.l_start = 0; 208 stdout_lock.l_type = F_WRLCK; 209 stdout_lock.l_whence = SEEK_SET; 210 if (fcntl(STDOUT_FILENO, F_SETLKW, &stdout_lock) != 0) 211 err(EXIT_FAILURE, "stdout"); 212 } 213 214 init_casper(argc, argv); 215 216 caph_cache_catpages(); 217 218 if (caph_enter_casper() != 0) 219 err(EXIT_FAILURE, "capsicum"); 220 221 if (bflag || Eflag || nflag || sflag || Tflag || vflag) 222 scanfiles(argv, 1); 223 else 224 scanfiles(argv, 0); 225 if (fclose(stdout)) 226 err(1, "stdout"); 227 exit(rval); 228 /* NOTREACHED */ 229 } 230 231 static void 232 usage(void) 233 { 234 235 fprintf(stderr, "usage: cat [-" SUPPORTED_FLAGS "] [file ...]\n"); 236 exit(1); 237 /* NOTREACHED */ 238 } 239 240 static void 241 scanfiles(char *argv[], int cooked __unused) 242 { 243 int fd, i; 244 char *path; 245 #ifndef BOOTSTRAP_CAT 246 FILE *fp; 247 #endif 248 249 i = 0; 250 fd = -1; 251 while ((path = argv[i]) != NULL || i == 0) { 252 if (path == NULL || strcmp(path, "-") == 0) { 253 filename = "stdin"; 254 fd = STDIN_FILENO; 255 } else { 256 filename = path; 257 fd = fileargs_open(fa, path); 258 #ifndef NO_UDOM_SUPPORT 259 if (fd < 0 && errno == EOPNOTSUPP) 260 fd = udom_open(path, O_RDONLY); 261 #endif 262 } 263 if (fd < 0) { 264 warn("%s", path); 265 rval = 1; 266 #ifndef BOOTSTRAP_CAT 267 } else if (cooked) { 268 if (fd == STDIN_FILENO) 269 cook_cat(stdin); 270 else { 271 if ((fp = fdopen(fd, "r")) == NULL) 272 err(1, "fdopen"); 273 cook_cat(fp); 274 fclose(fp); 275 } 276 #endif 277 } else { 278 #ifndef BOOTSTRAP_CAT 279 if (in_kernel_copy(fd) != 0) { 280 if (errno == EINVAL || errno == EBADF || 281 errno == EISDIR) 282 raw_cat(fd); 283 else 284 err(1, "%s", filename); 285 } 286 #else 287 raw_cat(fd); 288 #endif 289 if (fd != STDIN_FILENO) 290 close(fd); 291 } 292 if (path == NULL) 293 break; 294 ++i; 295 } 296 } 297 298 #ifndef BOOTSTRAP_CAT 299 static void 300 cook_cat(FILE *fp) 301 { 302 int ch, gobble, line, prev; 303 wint_t wch; 304 305 /* Reset EOF condition on stdin. */ 306 if (fp == stdin && feof(stdin)) 307 clearerr(stdin); 308 309 line = gobble = 0; 310 for (prev = '\n'; (ch = getc(fp)) != EOF; prev = ch) { 311 if (prev == '\n') { 312 if (sflag) { 313 if (ch == '\n') { 314 if (gobble) 315 continue; 316 gobble = 1; 317 } else 318 gobble = 0; 319 } 320 if (nflag) { 321 if (!bflag || ch != '\n') { 322 (void)fprintf(stdout, "%6d\t", ++line); 323 if (ferror(stdout)) 324 break; 325 } else if (Eflag) { 326 (void)fprintf(stdout, "%6s\t", ""); 327 if (ferror(stdout)) 328 break; 329 } 330 } 331 } 332 if (ch == '\n') { 333 if (Eflag && putchar('$') == EOF) 334 break; 335 } else if (ch == '\t') { 336 if (Tflag) { 337 if (putchar('^') == EOF || putchar('I') == EOF) 338 break; 339 continue; 340 } 341 } else if (vflag) { 342 (void)ungetc(ch, fp); 343 /* 344 * Our getwc(3) doesn't change file position 345 * on error. 346 */ 347 if ((wch = getwc(fp)) == WEOF) { 348 if (ferror(fp) && errno == EILSEQ) { 349 clearerr(fp); 350 /* Resync attempt. */ 351 memset(&fp->_mbstate, 0, sizeof(mbstate_t)); 352 if ((ch = getc(fp)) == EOF) 353 break; 354 wch = ch; 355 goto ilseq; 356 } else 357 break; 358 } 359 if (!iswascii(wch) && !iswprint(wch)) { 360 ilseq: 361 if (putchar('M') == EOF || putchar('-') == EOF) 362 break; 363 wch = toascii(wch); 364 } 365 if (iswcntrl(wch)) { 366 ch = toascii(wch); 367 ch = (ch == '\177') ? '?' : (ch | 0100); 368 if (putchar('^') == EOF || putchar(ch) == EOF) 369 break; 370 continue; 371 } 372 if (putwchar(wch) == WEOF) 373 break; 374 ch = -1; 375 continue; 376 } 377 if (putchar(ch) == EOF) 378 break; 379 } 380 if (ferror(fp)) { 381 warn("%s", filename); 382 rval = 1; 383 clearerr(fp); 384 } 385 if (ferror(stdout)) 386 err(1, "stdout"); 387 } 388 389 static ssize_t 390 in_kernel_copy(int rfd) 391 { 392 int wfd; 393 ssize_t ret; 394 395 wfd = fileno(stdout); 396 ret = 1; 397 398 while (ret > 0) 399 ret = copy_file_range(rfd, NULL, wfd, NULL, SSIZE_MAX, 0); 400 401 return (ret); 402 } 403 #endif /* BOOTSTRAP_CAT */ 404 405 static void 406 raw_cat(int rfd) 407 { 408 long pagesize; 409 int off, wfd; 410 ssize_t nr, nw; 411 static size_t bsize; 412 static char *buf = NULL; 413 struct stat sbuf; 414 415 wfd = fileno(stdout); 416 if (buf == NULL) { 417 if (fstat(wfd, &sbuf)) 418 err(1, "stdout"); 419 if (S_ISREG(sbuf.st_mode)) { 420 /* If there's plenty of RAM, use a large copy buffer */ 421 if (sysconf(_SC_PHYS_PAGES) > PHYSPAGES_THRESHOLD) 422 bsize = MIN(BUFSIZE_MAX, MAXPHYS * 8); 423 else 424 bsize = BUFSIZE_SMALL; 425 } else { 426 bsize = sbuf.st_blksize; 427 pagesize = sysconf(_SC_PAGESIZE); 428 if (pagesize > 0) 429 bsize = MAX(bsize, (size_t)pagesize); 430 } 431 if ((buf = malloc(bsize)) == NULL) 432 err(1, "malloc() failure of IO buffer"); 433 } 434 while ((nr = read(rfd, buf, bsize)) > 0) 435 for (off = 0; nr; nr -= nw, off += nw) 436 if ((nw = write(wfd, buf + off, (size_t)nr)) < 0) 437 err(1, "stdout"); 438 if (nr < 0) { 439 warn("%s", filename); 440 rval = 1; 441 } 442 } 443 444 #ifndef NO_UDOM_SUPPORT 445 446 static int 447 udom_open(const char *path, int flags) 448 { 449 struct addrinfo hints, *res, *res0; 450 char rpath[PATH_MAX]; 451 int error, fd, serrno; 452 cap_rights_t rights; 453 454 /* 455 * Construct the unix domain socket address and attempt to connect. 456 */ 457 bzero(&hints, sizeof(hints)); 458 hints.ai_family = AF_LOCAL; 459 460 if (fileargs_realpath(fa, path, rpath) == NULL) 461 return (-1); 462 463 error = cap_getaddrinfo(capnet, rpath, NULL, &hints, &res0); 464 if (error) { 465 warn("%s", gai_strerror(error)); 466 errno = EINVAL; 467 return (-1); 468 } 469 cap_rights_init(&rights, CAP_CONNECT, CAP_READ, CAP_WRITE, 470 CAP_SHUTDOWN, CAP_FSTAT, CAP_FCNTL); 471 472 /* Default error if something goes wrong. */ 473 serrno = EINVAL; 474 475 for (res = res0; res != NULL; res = res->ai_next) { 476 fd = socket(res->ai_family, res->ai_socktype, 477 res->ai_protocol); 478 if (fd < 0) { 479 serrno = errno; 480 freeaddrinfo(res0); 481 errno = serrno; 482 return (-1); 483 } 484 if (caph_rights_limit(fd, &rights) != 0) { 485 serrno = errno; 486 close(fd); 487 freeaddrinfo(res0); 488 errno = serrno; 489 return (-1); 490 } 491 error = cap_connect(capnet, fd, res->ai_addr, res->ai_addrlen); 492 if (error == 0) 493 break; 494 else { 495 serrno = errno; 496 close(fd); 497 } 498 } 499 freeaddrinfo(res0); 500 501 if (res == NULL) { 502 errno = serrno; 503 return (-1); 504 } 505 506 /* 507 * handle the open flags by shutting down appropriate directions 508 */ 509 510 switch (flags & O_ACCMODE) { 511 case O_RDONLY: 512 cap_rights_clear(&rights, CAP_WRITE); 513 if (shutdown(fd, SHUT_WR) != 0) 514 warn(NULL); 515 break; 516 case O_WRONLY: 517 cap_rights_clear(&rights, CAP_READ); 518 if (shutdown(fd, SHUT_RD) != 0) 519 warn(NULL); 520 break; 521 default: 522 break; 523 } 524 525 cap_rights_clear(&rights, CAP_CONNECT, CAP_SHUTDOWN); 526 if (caph_rights_limit(fd, &rights) != 0) { 527 serrno = errno; 528 close(fd); 529 errno = serrno; 530 return (-1); 531 } 532 return (fd); 533 } 534 535 #endif 536