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