1 /* $OpenBSD: file.c,v 1.62 2017/06/28 15:42:49 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/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 } 212 exit(0); 213 } 214 215 static void 216 prepare_input(struct input_file *inf, const char *path) 217 { 218 int fd, mode, error; 219 220 if (strcmp(path, "-") == 0) { 221 if (fstat(STDIN_FILENO, &inf->sb) == -1) { 222 inf->error = errno; 223 inf->fd = -1; 224 } 225 inf->fd = STDIN_FILENO; 226 } 227 228 if (Lflag) 229 error = stat(path, &inf->sb); 230 else 231 error = lstat(path, &inf->sb); 232 if (error == -1) { 233 inf->error = errno; 234 inf->fd = -1; 235 } 236 237 /* We don't need them, so don't open directories or symlinks. */ 238 mode = inf->sb.st_mode; 239 if (!S_ISDIR(mode) && !S_ISLNK(mode)) { 240 fd = open(path, O_RDONLY|O_NONBLOCK); 241 if (fd == -1 && (errno == ENFILE || errno == EMFILE)) 242 err(1, "open"); 243 } else 244 fd = -1; 245 if (S_ISLNK(mode)) 246 read_link(inf, path); 247 inf->fd = fd; 248 inf->path = path; 249 } 250 251 static void 252 read_link(struct input_file *inf, const char *path) 253 { 254 struct stat sb; 255 char lpath[PATH_MAX]; 256 char *copy, *root; 257 int used; 258 ssize_t size; 259 260 size = readlink(path, lpath, sizeof lpath - 1); 261 if (size == -1) { 262 inf->link_error = errno; 263 return; 264 } 265 lpath[size] = '\0'; 266 267 if (*lpath == '/') 268 strlcpy(inf->link_path, lpath, sizeof inf->link_path); 269 else { 270 copy = xstrdup(path); 271 272 root = dirname(copy); 273 if (*root == '\0' || strcmp(root, ".") == 0 || 274 strcmp (root, "/") == 0) 275 strlcpy(inf->link_path, lpath, sizeof inf->link_path); 276 else { 277 used = snprintf(inf->link_path, sizeof inf->link_path, 278 "%s/%s", root, lpath); 279 if (used < 0 || (size_t)used >= sizeof inf->link_path) { 280 inf->link_error = ENAMETOOLONG; 281 free(copy); 282 return; 283 } 284 } 285 286 free(copy); 287 } 288 289 if (!Lflag && stat(path, &sb) == -1) 290 inf->link_target = errno; 291 } 292 293 static void * 294 fill_buffer(int fd, size_t size, size_t *used) 295 { 296 static void *buffer; 297 ssize_t got; 298 size_t left; 299 void *next; 300 301 if (buffer == NULL) 302 buffer = xmalloc(FILE_READ_SIZE); 303 304 next = buffer; 305 left = size; 306 while (left != 0) { 307 got = read(fd, next, left); 308 if (got == -1) { 309 if (errno == EINTR) 310 continue; 311 return (NULL); 312 } 313 if (got == 0) 314 break; 315 next = (char *)next + got; 316 left -= got; 317 } 318 *used = size - left; 319 return (buffer); 320 } 321 322 static int 323 load_file(struct input_file *inf) 324 { 325 size_t used; 326 327 if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) 328 return (0); /* empty file */ 329 if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE) 330 inf->size = FILE_READ_SIZE; 331 else 332 inf->size = inf->sb.st_size; 333 334 if (!S_ISREG(inf->sb.st_mode)) 335 goto try_read; 336 337 inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0); 338 if (inf->base == MAP_FAILED) 339 goto try_read; 340 inf->mapped = 1; 341 return (0); 342 343 try_read: 344 inf->base = fill_buffer(inf->fd, inf->size, &used); 345 if (inf->base == NULL) { 346 xasprintf(&inf->result, "cannot read '%s' (%s)", inf->path, 347 strerror(errno)); 348 return (1); 349 } 350 inf->size = used; 351 return (0); 352 } 353 354 static int 355 try_stat(struct input_file *inf) 356 { 357 if (inf->error != 0) { 358 xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path, 359 strerror(inf->error)); 360 return (1); 361 } 362 if (sflag || strcmp(inf->path, "-") == 0) { 363 switch (inf->sb.st_mode & S_IFMT) { 364 case S_IFIFO: 365 if (strcmp(inf->path, "-") != 0) 366 break; 367 case S_IFBLK: 368 case S_IFCHR: 369 case S_IFREG: 370 return (0); 371 } 372 } 373 374 if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) { 375 xasprintf(&inf->result, "application/x-not-regular-file"); 376 return (1); 377 } 378 379 switch (inf->sb.st_mode & S_IFMT) { 380 case S_IFDIR: 381 xasprintf(&inf->result, "directory"); 382 return (1); 383 case S_IFLNK: 384 if (inf->link_error != 0) { 385 xasprintf(&inf->result, "unreadable symlink '%s' (%s)", 386 inf->path, strerror(inf->link_error)); 387 return (1); 388 } 389 if (inf->link_target == ELOOP) 390 xasprintf(&inf->result, "symbolic link in a loop"); 391 else if (inf->link_target != 0) { 392 xasprintf(&inf->result, "broken symbolic link to '%s'", 393 inf->link_path); 394 } else { 395 xasprintf(&inf->result, "symbolic link to '%s'", 396 inf->link_path); 397 } 398 return (1); 399 case S_IFSOCK: 400 xasprintf(&inf->result, "socket"); 401 return (1); 402 case S_IFBLK: 403 xasprintf(&inf->result, "block special (%ld/%ld)", 404 (long)major(inf->sb.st_rdev), 405 (long)minor(inf->sb.st_rdev)); 406 return (1); 407 case S_IFCHR: 408 xasprintf(&inf->result, "character special (%ld/%ld)", 409 (long)major(inf->sb.st_rdev), 410 (long)minor(inf->sb.st_rdev)); 411 return (1); 412 case S_IFIFO: 413 xasprintf(&inf->result, "fifo (named pipe)"); 414 return (1); 415 } 416 return (0); 417 } 418 419 static int 420 try_empty(struct input_file *inf) 421 { 422 if (inf->size != 0) 423 return (0); 424 425 if (iflag) 426 xasprintf(&inf->result, "application/x-empty"); 427 else 428 xasprintf(&inf->result, "empty"); 429 return (1); 430 } 431 432 static int 433 try_access(struct input_file *inf) 434 { 435 char tmp[256] = ""; 436 437 if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) 438 return (0); /* empty file */ 439 if (inf->fd != -1) 440 return (0); 441 442 if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) 443 strlcat(tmp, "writable, ", sizeof tmp); 444 if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) 445 strlcat(tmp, "executable, ", sizeof tmp); 446 if (S_ISREG(inf->sb.st_mode)) 447 strlcat(tmp, "regular file, ", sizeof tmp); 448 strlcat(tmp, "no read permission", sizeof tmp); 449 450 inf->result = xstrdup(tmp); 451 return (1); 452 } 453 454 static int 455 try_text(struct input_file *inf) 456 { 457 const char *type, *s; 458 int flags; 459 460 flags = MAGIC_TEST_TEXT; 461 if (iflag) 462 flags |= MAGIC_TEST_MIME; 463 464 type = text_get_type(inf->base, inf->size); 465 if (type == NULL) 466 return (0); 467 468 s = magic_test(inf->m, inf->base, inf->size, flags); 469 if (s != NULL) { 470 inf->result = xstrdup(s); 471 return (1); 472 } 473 474 s = text_try_words(inf->base, inf->size, flags); 475 if (s != NULL) { 476 if (iflag) 477 inf->result = xstrdup(s); 478 else 479 xasprintf(&inf->result, "%s %s text", type, s); 480 return (1); 481 } 482 483 if (iflag) 484 inf->result = xstrdup("text/plain"); 485 else 486 xasprintf(&inf->result, "%s text", type); 487 return (1); 488 } 489 490 static int 491 try_magic(struct input_file *inf) 492 { 493 const char *s; 494 int flags; 495 496 flags = 0; 497 if (iflag) 498 flags |= MAGIC_TEST_MIME; 499 500 s = magic_test(inf->m, inf->base, inf->size, flags); 501 if (s != NULL) { 502 inf->result = xstrdup(s); 503 return (1); 504 } 505 return (0); 506 } 507 508 static int 509 try_unknown(struct input_file *inf) 510 { 511 if (iflag) 512 xasprintf(&inf->result, "application/x-not-regular-file"); 513 else 514 xasprintf(&inf->result, "data"); 515 return (1); 516 } 517 518 static void 519 test_file(struct input_file *inf, size_t width) 520 { 521 char *label; 522 int stop; 523 524 stop = 0; 525 if (!stop) 526 stop = try_stat(inf); 527 if (!stop) 528 stop = try_access(inf); 529 if (!stop) 530 stop = load_file(inf); 531 if (!stop) 532 stop = try_empty(inf); 533 if (!stop) 534 stop = try_magic(inf); 535 if (!stop) 536 stop = try_text(inf); 537 if (!stop) 538 stop = try_unknown(inf); 539 540 if (bflag) 541 printf("%s\n", inf->result); 542 else { 543 if (strcmp(inf->path, "-") == 0) 544 xasprintf(&label, "/dev/stdin:"); 545 else 546 xasprintf(&label, "%s:", inf->path); 547 printf("%-*s %s\n", (int)width, label, inf->result); 548 free(label); 549 } 550 free(inf->result); 551 552 if (inf->mapped && inf->base != NULL) 553 munmap(inf->base, inf->size); 554 } 555