1 /* $OpenBSD: file.c,v 1.61 2017/06/28 15:40:54 deraadt Exp $ */ 2 3 /* 4 * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/ioctl.h> 21 #include <sys/mman.h> 22 #include <sys/queue.h> 23 #include <sys/socket.h> 24 #include <sys/stat.h> 25 #include <sys/uio.h> 26 #include <sys/wait.h> 27 28 #include <err.h> 29 #include <errno.h> 30 #include <fcntl.h> 31 #include <getopt.h> 32 #include <libgen.h> 33 #include <limits.h> 34 #include <pwd.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 #include <unistd.h> 39 40 #include "file.h" 41 #include "magic.h" 42 #include "xmalloc.h" 43 44 struct input_file { 45 struct magic *m; 46 47 const char *path; 48 struct stat sb; 49 int fd; 50 int error; 51 52 char link_path[PATH_MAX]; 53 int link_error; 54 int link_target; 55 56 void *base; 57 size_t size; 58 int mapped; 59 char *result; 60 }; 61 62 extern char *__progname; 63 64 __dead void usage(void); 65 66 static void prepare_input(struct input_file *, const char *); 67 68 static void read_link(struct input_file *, const char *); 69 70 static void test_file(struct input_file *, size_t); 71 72 static int try_stat(struct input_file *); 73 static int try_empty(struct input_file *); 74 static int try_access(struct input_file *); 75 static int try_text(struct input_file *); 76 static int try_magic(struct input_file *); 77 static int try_unknown(struct input_file *); 78 79 static int bflag; 80 static int cflag; 81 static int iflag; 82 static int Lflag; 83 static int sflag; 84 static int Wflag; 85 86 static struct option longopts[] = { 87 { "brief", no_argument, NULL, 'b' }, 88 { "dereference", no_argument, NULL, 'L' }, 89 { "mime", no_argument, NULL, 'i' }, 90 { "mime-type", no_argument, NULL, 'i' }, 91 { NULL, 0, NULL, 0 } 92 }; 93 94 __dead void 95 usage(void) 96 { 97 fprintf(stderr, "usage: %s [-bchiLsW] file ...\n", __progname); 98 exit(1); 99 } 100 101 int 102 main(int argc, char **argv) 103 { 104 int opt, idx; 105 char *home, *magicpath; 106 struct passwd *pw; 107 FILE *magicfp = NULL; 108 struct magic *m; 109 struct input_file *inf = NULL; 110 size_t len, width = 0; 111 112 if (pledge("stdio rpath getpw id", NULL) == -1) 113 err(1, "pledge"); 114 115 for (;;) { 116 opt = getopt_long(argc, argv, "bchiLsW", longopts, NULL); 117 if (opt == -1) 118 break; 119 switch (opt) { 120 case 'b': 121 bflag = 1; 122 break; 123 case 'c': 124 cflag = 1; 125 break; 126 case 'h': 127 Lflag = 0; 128 break; 129 case 'i': 130 iflag = 1; 131 break; 132 case 'L': 133 Lflag = 1; 134 break; 135 case 's': 136 sflag = 1; 137 break; 138 case 'W': 139 Wflag = 1; 140 break; 141 default: 142 usage(); 143 } 144 } 145 argc -= optind; 146 argv += optind; 147 if (cflag) { 148 if (argc != 0) 149 usage(); 150 } else if (argc == 0) 151 usage(); 152 153 if (geteuid() != 0 && !issetugid()) { 154 home = getenv("HOME"); 155 if (home == NULL || *home == '\0') { 156 pw = getpwuid(getuid()); 157 if (pw != NULL) 158 home = pw->pw_dir; 159 else 160 home = NULL; 161 } 162 if (home != NULL) { 163 xasprintf(&magicpath, "%s/.magic", home); 164 magicfp = fopen(magicpath, "r"); 165 if (magicfp == NULL) 166 free(magicpath); 167 } 168 } 169 if (magicfp == NULL) { 170 magicpath = xstrdup("/etc/magic"); 171 magicfp = fopen(magicpath, "r"); 172 } 173 if (magicfp == NULL) 174 err(1, "%s", magicpath); 175 176 if (!cflag) { 177 inf = xcalloc(argc, sizeof *inf); 178 for (idx = 0; idx < argc; idx++) { 179 len = strlen(argv[idx]) + 1; 180 if (len > width) 181 width = len; 182 prepare_input(&inf[idx], argv[idx]); 183 } 184 } 185 186 tzset(); 187 188 if (pledge("stdio getpw id", NULL) == -1) 189 err(1, "pledge"); 190 191 if (geteuid() == 0) { 192 pw = getpwnam(FILE_USER); 193 if (pw == NULL) 194 errx(1, "unknown user %s", FILE_USER); 195 if (setgroups(1, &pw->pw_gid) != 0) 196 err(1, "setgroups"); 197 if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) 198 err(1, "setresgid"); 199 if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) 200 err(1, "setresuid"); 201 } 202 203 if (pledge("stdio", NULL) == -1) 204 err(1, "pledge"); 205 206 m = magic_load(magicfp, magicpath, cflag || Wflag); 207 if (cflag) { 208 magic_dump(m); 209 exit(0); 210 } 211 fclose(magicfp); 212 213 for (idx = 0; idx < argc; idx++) { 214 inf[idx].m = m; 215 test_file(&inf[idx], width); 216 } 217 exit(0); 218 } 219 220 static void 221 prepare_input(struct input_file *inf, const char *path) 222 { 223 int fd, mode, error; 224 225 if (strcmp(path, "-") == 0) { 226 if (fstat(STDIN_FILENO, &inf->sb) == -1) { 227 inf->error = errno; 228 inf->fd = -1; 229 } 230 inf->fd = STDIN_FILENO; 231 } 232 233 if (Lflag) 234 error = stat(path, &inf->sb); 235 else 236 error = lstat(path, &inf->sb); 237 if (error == -1) { 238 inf->error = errno; 239 inf->fd = -1; 240 } 241 242 /* We don't need them, so don't open directories or symlinks. */ 243 mode = inf->sb.st_mode; 244 if (!S_ISDIR(mode) && !S_ISLNK(mode)) { 245 fd = open(path, O_RDONLY|O_NONBLOCK); 246 if (fd == -1 && (errno == ENFILE || errno == EMFILE)) 247 err(1, "open"); 248 } else 249 fd = -1; 250 if (S_ISLNK(mode)) 251 read_link(inf, path); 252 inf->fd = fd; 253 inf->path = path; 254 } 255 256 static void 257 read_link(struct input_file *inf, const char *path) 258 { 259 struct stat sb; 260 char lpath[PATH_MAX]; 261 char *copy, *root; 262 int used; 263 ssize_t size; 264 265 size = readlink(path, lpath, sizeof lpath - 1); 266 if (size == -1) { 267 inf->link_error = errno; 268 return; 269 } 270 lpath[size] = '\0'; 271 272 if (*lpath == '/') 273 strlcpy(inf->link_path, lpath, sizeof inf->link_path); 274 else { 275 copy = xstrdup(path); 276 277 root = dirname(copy); 278 if (*root == '\0' || strcmp(root, ".") == 0 || 279 strcmp (root, "/") == 0) 280 strlcpy(inf->link_path, lpath, sizeof inf->link_path); 281 else { 282 used = snprintf(inf->link_path, sizeof inf->link_path, 283 "%s/%s", root, lpath); 284 if (used < 0 || (size_t)used >= sizeof inf->link_path) { 285 inf->link_error = ENAMETOOLONG; 286 free(copy); 287 return; 288 } 289 } 290 291 free(copy); 292 } 293 294 if (!Lflag && stat(path, &sb) == -1) 295 inf->link_target = errno; 296 } 297 298 static void * 299 fill_buffer(int fd, size_t size, size_t *used) 300 { 301 static void *buffer; 302 ssize_t got; 303 size_t left; 304 void *next; 305 306 if (buffer == NULL) 307 buffer = xmalloc(FILE_READ_SIZE); 308 309 next = buffer; 310 left = size; 311 while (left != 0) { 312 got = read(fd, next, left); 313 if (got == -1) { 314 if (errno == EINTR) 315 continue; 316 return (NULL); 317 } 318 if (got == 0) 319 break; 320 next = (char *)next + got; 321 left -= got; 322 } 323 *used = size - left; 324 return (buffer); 325 } 326 327 static int 328 load_file(struct input_file *inf) 329 { 330 size_t used; 331 332 if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) 333 return (0); /* empty file */ 334 if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE) 335 inf->size = FILE_READ_SIZE; 336 else 337 inf->size = inf->sb.st_size; 338 339 if (!S_ISREG(inf->sb.st_mode)) 340 goto try_read; 341 342 inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0); 343 if (inf->base == MAP_FAILED) 344 goto try_read; 345 inf->mapped = 1; 346 return (0); 347 348 try_read: 349 inf->base = fill_buffer(inf->fd, inf->size, &used); 350 if (inf->base == NULL) { 351 xasprintf(&inf->result, "cannot read '%s' (%s)", inf->path, 352 strerror(errno)); 353 return (1); 354 } 355 inf->size = used; 356 return (0); 357 } 358 359 static int 360 try_stat(struct input_file *inf) 361 { 362 if (inf->error != 0) { 363 xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path, 364 strerror(inf->error)); 365 return (1); 366 } 367 if (sflag || strcmp(inf->path, "-") == 0) { 368 switch (inf->sb.st_mode & S_IFMT) { 369 case S_IFIFO: 370 if (strcmp(inf->path, "-") != 0) 371 break; 372 case S_IFBLK: 373 case S_IFCHR: 374 case S_IFREG: 375 return (0); 376 } 377 } 378 379 if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) { 380 xasprintf(&inf->result, "application/x-not-regular-file"); 381 return (1); 382 } 383 384 switch (inf->sb.st_mode & S_IFMT) { 385 case S_IFDIR: 386 xasprintf(&inf->result, "directory"); 387 return (1); 388 case S_IFLNK: 389 if (inf->link_error != 0) { 390 xasprintf(&inf->result, "unreadable symlink '%s' (%s)", 391 inf->path, strerror(inf->link_error)); 392 return (1); 393 } 394 if (inf->link_target == ELOOP) 395 xasprintf(&inf->result, "symbolic link in a loop"); 396 else if (inf->link_target != 0) { 397 xasprintf(&inf->result, "broken symbolic link to '%s'", 398 inf->link_path); 399 } else { 400 xasprintf(&inf->result, "symbolic link to '%s'", 401 inf->link_path); 402 } 403 return (1); 404 case S_IFSOCK: 405 xasprintf(&inf->result, "socket"); 406 return (1); 407 case S_IFBLK: 408 xasprintf(&inf->result, "block special (%ld/%ld)", 409 (long)major(inf->sb.st_rdev), 410 (long)minor(inf->sb.st_rdev)); 411 return (1); 412 case S_IFCHR: 413 xasprintf(&inf->result, "character special (%ld/%ld)", 414 (long)major(inf->sb.st_rdev), 415 (long)minor(inf->sb.st_rdev)); 416 return (1); 417 case S_IFIFO: 418 xasprintf(&inf->result, "fifo (named pipe)"); 419 return (1); 420 } 421 return (0); 422 } 423 424 static int 425 try_empty(struct input_file *inf) 426 { 427 if (inf->size != 0) 428 return (0); 429 430 if (iflag) 431 xasprintf(&inf->result, "application/x-empty"); 432 else 433 xasprintf(&inf->result, "empty"); 434 return (1); 435 } 436 437 static int 438 try_access(struct input_file *inf) 439 { 440 char tmp[256] = ""; 441 442 if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) 443 return (0); /* empty file */ 444 if (inf->fd != -1) 445 return (0); 446 447 if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) 448 strlcat(tmp, "writable, ", sizeof tmp); 449 if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 450 strlcat(tmp, "executable, ", sizeof tmp); 451 if (S_ISREG(inf->sb.st_mode)) 452 strlcat(tmp, "regular file, ", sizeof tmp); 453 strlcat(tmp, "no read permission", sizeof tmp); 454 455 inf->result = xstrdup(tmp); 456 return (1); 457 } 458 459 static int 460 try_text(struct input_file *inf) 461 { 462 const char *type, *s; 463 int flags; 464 465 flags = MAGIC_TEST_TEXT; 466 if (iflag) 467 flags |= MAGIC_TEST_MIME; 468 469 type = text_get_type(inf->base, inf->size); 470 if (type == NULL) 471 return (0); 472 473 s = magic_test(inf->m, inf->base, inf->size, flags); 474 if (s != NULL) { 475 inf->result = xstrdup(s); 476 return (1); 477 } 478 479 s = text_try_words(inf->base, inf->size, flags); 480 if (s != NULL) { 481 if (iflag) 482 inf->result = xstrdup(s); 483 else 484 xasprintf(&inf->result, "%s %s text", type, s); 485 return (1); 486 } 487 488 if (iflag) 489 inf->result = xstrdup("text/plain"); 490 else 491 xasprintf(&inf->result, "%s text", type); 492 return (1); 493 } 494 495 static int 496 try_magic(struct input_file *inf) 497 { 498 const char *s; 499 int flags; 500 501 flags = 0; 502 if (iflag) 503 flags |= MAGIC_TEST_MIME; 504 505 s = magic_test(inf->m, inf->base, inf->size, flags); 506 if (s != NULL) { 507 inf->result = xstrdup(s); 508 return (1); 509 } 510 return (0); 511 } 512 513 static int 514 try_unknown(struct input_file *inf) 515 { 516 if (iflag) 517 xasprintf(&inf->result, "application/x-not-regular-file"); 518 else 519 xasprintf(&inf->result, "data"); 520 return (1); 521 } 522 523 static void 524 test_file(struct input_file *inf, size_t width) 525 { 526 char *label; 527 int stop; 528 529 stop = 0; 530 if (!stop) 531 stop = try_stat(inf); 532 if (!stop) 533 stop = try_access(inf); 534 if (!stop) 535 stop = load_file(inf); 536 if (!stop) 537 stop = try_empty(inf); 538 if (!stop) 539 stop = try_magic(inf); 540 if (!stop) 541 stop = try_text(inf); 542 if (!stop) 543 stop = try_unknown(inf); 544 545 if (bflag) 546 printf("%s\n", inf->result); 547 else { 548 if (strcmp(inf->path, "-") == 0) 549 xasprintf(&label, "/dev/stdin:"); 550 else 551 xasprintf(&label, "%s:", inf->path); 552 printf("%-*s %s\n", (int)width, label, inf->result); 553 free(label); 554 } 555 free(inf->result); 556 557 if (inf->mapped && inf->base != NULL) 558 munmap(inf->base, inf->size); 559 } 560