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