Annotation of src/usr.bin/file/file.c, Revision 1.63
1.63 ! brynet 1: /* $OpenBSD: file.c,v 1.62 2017/06/28 15:42:49 deraadt Exp $ */
1.27 nicm 2:
1.14 tedu 3: /*
1.27 nicm 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.
1.14 tedu 17: */
1.11 ian 18:
1.20 deraadt 19: #include <sys/types.h>
1.27 nicm 20: #include <sys/mman.h>
1.58 nicm 21: #include <sys/stat.h>
1.20 deraadt 22:
1.58 nicm 23: #include <err.h>
1.27 nicm 24: #include <errno.h>
1.58 nicm 25: #include <fcntl.h>
26: #include <getopt.h>
1.27 nicm 27: #include <libgen.h>
1.58 nicm 28: #include <limits.h>
1.27 nicm 29: #include <pwd.h>
1.1 deraadt 30: #include <stdlib.h>
1.58 nicm 31: #include <string.h>
1.44 nicm 32: #include <time.h>
1.14 tedu 33: #include <unistd.h>
34:
1.27 nicm 35: #include "file.h"
36: #include "magic.h"
37: #include "xmalloc.h"
1.1 deraadt 38:
1.59 nicm 39: struct input_file {
1.60 brynet 40: struct magic *m;
1.1 deraadt 41:
1.60 brynet 42: const char *path;
43: struct stat sb;
44: int fd;
45: int error;
1.14 tedu 46:
1.60 brynet 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;
1.27 nicm 55: };
1.1 deraadt 56:
1.27 nicm 57: extern char *__progname;
1.1 deraadt 58:
1.27 nicm 59: __dead void usage(void);
1.1 deraadt 60:
1.60 brynet 61: static void prepare_input(struct input_file *, const char *);
1.35 nicm 62:
1.60 brynet 63: static void read_link(struct input_file *, const char *);
1.35 nicm 64:
65: static void test_file(struct input_file *, size_t);
1.27 nicm 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[] = {
1.57 jca 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 }
1.27 nicm 87: };
1.14 tedu 88:
1.27 nicm 89: __dead void
90: usage(void)
91: {
1.39 jmc 92: fprintf(stderr, "usage: %s [-bchiLsW] file ...\n", __progname);
1.27 nicm 93: exit(1);
94: }
1.14 tedu 95:
1.1 deraadt 96: int
1.27 nicm 97: main(int argc, char **argv)
1.1 deraadt 98: {
1.60 brynet 99: int opt, idx;
100: char *home, *magicpath;
1.27 nicm 101: struct passwd *pw;
1.61 deraadt 102: FILE *magicfp = NULL;
1.60 brynet 103: struct magic *m;
104: struct input_file *inf = NULL;
105: size_t len, width = 0;
1.44 nicm 106:
1.61 deraadt 107: if (pledge("stdio rpath getpw id", NULL) == -1)
108: err(1, "pledge");
1.27 nicm 109:
110: for (;;) {
111: opt = getopt_long(argc, argv, "bchiLsW", longopts, NULL);
112: if (opt == -1)
1.18 chl 113: break;
1.27 nicm 114: switch (opt) {
1.9 millert 115: case 'b':
1.27 nicm 116: bflag = 1;
1.9 millert 117: break;
1.1 deraadt 118: case 'c':
1.27 nicm 119: cflag = 1;
1.14 tedu 120: break;
1.27 nicm 121: case 'h':
122: Lflag = 0;
1.14 tedu 123: break;
1.19 chl 124: case 'i':
1.27 nicm 125: iflag = 1;
1.19 chl 126: break;
1.27 nicm 127: case 'L':
128: Lflag = 1;
1.14 tedu 129: break;
130: case 's':
1.27 nicm 131: sflag = 1;
1.14 tedu 132: break;
1.27 nicm 133: case 'W':
134: Wflag = 1;
1.18 chl 135: break;
1.1 deraadt 136: default:
1.27 nicm 137: usage();
1.1 deraadt 138: }
1.27 nicm 139: }
140: argc -= optind;
141: argv += optind;
142: if (cflag) {
143: if (argc != 0)
144: usage();
145: } else if (argc == 0)
146: usage();
1.1 deraadt 147:
1.32 nicm 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) {
1.35 nicm 158: xasprintf(&magicpath, "%s/.magic", home);
159: magicfp = fopen(magicpath, "r");
160: if (magicfp == NULL)
161: free(magicpath);
1.32 nicm 162: }
1.27 nicm 163: }
1.35 nicm 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:
1.60 brynet 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: }
1.35 nicm 179: }
1.61 deraadt 180:
181: tzset();
1.35 nicm 182:
1.60 brynet 183: if (pledge("stdio getpw id", NULL) == -1)
184: err(1, "pledge");
1.35 nicm 185:
1.60 brynet 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: }
1.35 nicm 197:
1.60 brynet 198: if (pledge("stdio", NULL) == -1)
199: err(1, "pledge");
1.1 deraadt 200:
1.60 brynet 201: m = magic_load(magicfp, magicpath, cflag || Wflag);
202: if (cflag) {
203: magic_dump(m);
204: exit(0);
1.1 deraadt 205: }
1.60 brynet 206: fclose(magicfp);
1.1 deraadt 207:
1.60 brynet 208: for (idx = 0; idx < argc; idx++) {
209: inf[idx].m = m;
210: test_file(&inf[idx], width);
1.30 nicm 211: }
1.60 brynet 212: exit(0);
1.55 nicm 213: }
214:
1.60 brynet 215: static void
216: prepare_input(struct input_file *inf, const char *path)
1.55 nicm 217: {
218: int fd, mode, error;
219:
1.63 ! brynet 220: inf->path = path;
! 221:
1.55 nicm 222: if (strcmp(path, "-") == 0) {
1.60 brynet 223: if (fstat(STDIN_FILENO, &inf->sb) == -1) {
224: inf->error = errno;
225: inf->fd = -1;
1.63 ! brynet 226: return;
1.55 nicm 227: }
1.60 brynet 228: inf->fd = STDIN_FILENO;
1.63 ! brynet 229: return;
1.55 nicm 230: }
231:
232: if (Lflag)
1.60 brynet 233: error = stat(path, &inf->sb);
1.55 nicm 234: else
1.60 brynet 235: error = lstat(path, &inf->sb);
1.55 nicm 236: if (error == -1) {
1.60 brynet 237: inf->error = errno;
238: inf->fd = -1;
1.63 ! brynet 239: return;
1.55 nicm 240: }
241:
1.60 brynet 242: /* We don't need them, so don't open directories or symlinks. */
243: mode = inf->sb.st_mode;
1.55 nicm 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))
1.60 brynet 251: read_link(inf, path);
252: inf->fd = fd;
1.27 nicm 253: }
254:
255: static void
1.60 brynet 256: read_link(struct input_file *inf, const char *path)
1.14 tedu 257: {
1.27 nicm 258: struct stat sb;
1.35 nicm 259: char lpath[PATH_MAX];
1.27 nicm 260: char *copy, *root;
261: int used;
262: ssize_t size;
263:
1.47 tobias 264: size = readlink(path, lpath, sizeof lpath - 1);
1.27 nicm 265: if (size == -1) {
1.60 brynet 266: inf->link_error = errno;
1.14 tedu 267: return;
1.27 nicm 268: }
1.35 nicm 269: lpath[size] = '\0';
1.27 nicm 270:
1.35 nicm 271: if (*lpath == '/')
1.60 brynet 272: strlcpy(inf->link_path, lpath, sizeof inf->link_path);
1.27 nicm 273: else {
1.35 nicm 274: copy = xstrdup(path);
1.27 nicm 275:
276: root = dirname(copy);
277: if (*root == '\0' || strcmp(root, ".") == 0 ||
278: strcmp (root, "/") == 0)
1.60 brynet 279: strlcpy(inf->link_path, lpath, sizeof inf->link_path);
1.27 nicm 280: else {
1.60 brynet 281: used = snprintf(inf->link_path, sizeof inf->link_path,
1.35 nicm 282: "%s/%s", root, lpath);
1.60 brynet 283: if (used < 0 || (size_t)used >= sizeof inf->link_path) {
284: inf->link_error = ENAMETOOLONG;
1.37 lteo 285: free(copy);
1.27 nicm 286: return;
287: }
288: }
289:
290: free(copy);
291: }
292:
1.54 nicm 293: if (!Lflag && stat(path, &sb) == -1)
1.60 brynet 294: inf->link_target = errno;
1.35 nicm 295: }
296:
1.27 nicm 297: static void *
1.42 nicm 298: fill_buffer(int fd, size_t size, size_t *used)
1.1 deraadt 299: {
1.27 nicm 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;
1.42 nicm 309: left = size;
1.27 nicm 310: while (left != 0) {
1.42 nicm 311: got = read(fd, next, left);
1.27 nicm 312: if (got == -1) {
313: if (errno == EINTR)
314: continue;
1.59 nicm 315: return (NULL);
1.5 millert 316: }
1.27 nicm 317: if (got == 0)
318: break;
1.30 nicm 319: next = (char *)next + got;
1.27 nicm 320: left -= got;
321: }
1.42 nicm 322: *used = size - left;
1.59 nicm 323: return (buffer);
1.27 nicm 324: }
325:
326: static int
327: load_file(struct input_file *inf)
328: {
1.42 nicm 329: size_t used;
330:
1.60 brynet 331: if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode))
1.43 nicm 332: return (0); /* empty file */
1.60 brynet 333: if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE)
1.27 nicm 334: inf->size = FILE_READ_SIZE;
1.46 tobias 335: else
1.60 brynet 336: inf->size = inf->sb.st_size;
1.43 nicm 337:
1.60 brynet 338: if (!S_ISREG(inf->sb.st_mode))
1.43 nicm 339: goto try_read;
1.27 nicm 340:
341: inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0);
1.43 nicm 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;
1.27 nicm 355: return (0);
356: }
1.5 millert 357:
1.27 nicm 358: static int
359: try_stat(struct input_file *inf)
360: {
1.60 brynet 361: if (inf->error != 0) {
1.27 nicm 362: xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path,
1.60 brynet 363: strerror(inf->error));
1.27 nicm 364: return (1);
365: }
1.45 nicm 366: if (sflag || strcmp(inf->path, "-") == 0) {
1.60 brynet 367: switch (inf->sb.st_mode & S_IFMT) {
1.45 nicm 368: case S_IFIFO:
369: if (strcmp(inf->path, "-") != 0)
370: break;
1.27 nicm 371: case S_IFBLK:
372: case S_IFCHR:
373: case S_IFREG:
374: return (0);
1.5 millert 375: }
1.27 nicm 376: }
1.1 deraadt 377:
1.60 brynet 378: if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) {
1.27 nicm 379: xasprintf(&inf->result, "application/x-not-regular-file");
380: return (1);
1.1 deraadt 381: }
382:
1.60 brynet 383: switch (inf->sb.st_mode & S_IFMT) {
1.27 nicm 384: case S_IFDIR:
385: xasprintf(&inf->result, "directory");
386: return (1);
387: case S_IFLNK:
1.60 brynet 388: if (inf->link_error != 0) {
1.27 nicm 389: xasprintf(&inf->result, "unreadable symlink '%s' (%s)",
1.60 brynet 390: inf->path, strerror(inf->link_error));
1.27 nicm 391: return (1);
392: }
1.60 brynet 393: if (inf->link_target == ELOOP)
1.27 nicm 394: xasprintf(&inf->result, "symbolic link in a loop");
1.60 brynet 395: else if (inf->link_target != 0) {
1.27 nicm 396: xasprintf(&inf->result, "broken symbolic link to '%s'",
1.60 brynet 397: inf->link_path);
1.27 nicm 398: } else {
399: xasprintf(&inf->result, "symbolic link to '%s'",
1.60 brynet 400: inf->link_path);
1.27 nicm 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)",
1.60 brynet 408: (long)major(inf->sb.st_rdev),
409: (long)minor(inf->sb.st_rdev));
1.27 nicm 410: return (1);
411: case S_IFCHR:
412: xasprintf(&inf->result, "character special (%ld/%ld)",
1.60 brynet 413: (long)major(inf->sb.st_rdev),
414: (long)minor(inf->sb.st_rdev));
1.27 nicm 415: return (1);
416: case S_IFIFO:
417: xasprintf(&inf->result, "fifo (named pipe)");
418: return (1);
1.1 deraadt 419: }
1.27 nicm 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:
1.60 brynet 441: if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode))
1.49 nicm 442: return (0); /* empty file */
1.27 nicm 443: if (inf->fd != -1)
444: return (0);
1.1 deraadt 445:
1.60 brynet 446: if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
1.27 nicm 447: strlcat(tmp, "writable, ", sizeof tmp);
1.60 brynet 448: if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
1.27 nicm 449: strlcat(tmp, "executable, ", sizeof tmp);
1.60 brynet 450: if (S_ISREG(inf->sb.st_mode))
1.27 nicm 451: strlcat(tmp, "regular file, ", sizeof tmp);
452: strlcat(tmp, "no read permission", sizeof tmp);
453:
454: inf->result = xstrdup(tmp);
455: return (1);
1.1 deraadt 456: }
457:
1.27 nicm 458: static int
459: try_text(struct input_file *inf)
1.14 tedu 460: {
1.27 nicm 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);
1.14 tedu 471:
1.27 nicm 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);
1.18 chl 482: else
1.27 nicm 483: xasprintf(&inf->result, "%s %s text", type, s);
484: return (1);
1.18 chl 485: }
1.14 tedu 486:
1.27 nicm 487: if (iflag)
488: inf->result = xstrdup("text/plain");
1.14 tedu 489: else
1.27 nicm 490: xasprintf(&inf->result, "%s text", type);
491: return (1);
1.14 tedu 492: }
493:
1.27 nicm 494: static int
495: try_magic(struct input_file *inf)
1.1 deraadt 496: {
1.27 nicm 497: const char *s;
498: int flags;
1.1 deraadt 499:
1.27 nicm 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);
1.1 deraadt 508: }
1.27 nicm 509: return (0);
1.14 tedu 510: }
1.1 deraadt 511:
1.27 nicm 512: static int
513: try_unknown(struct input_file *inf)
1.14 tedu 514: {
1.27 nicm 515: if (iflag)
516: xasprintf(&inf->result, "application/x-not-regular-file");
517: else
518: xasprintf(&inf->result, "data");
519: return (1);
1.1 deraadt 520: }
521:
1.27 nicm 522: static void
1.35 nicm 523: test_file(struct input_file *inf, size_t width)
1.1 deraadt 524: {
1.35 nicm 525: char *label;
526: int stop;
1.27 nicm 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);
1.35 nicm 546: else {
1.45 nicm 547: if (strcmp(inf->path, "-") == 0)
548: xasprintf(&label, "/dev/stdin:");
549: else
550: xasprintf(&label, "%s:", inf->path);
1.35 nicm 551: printf("%-*s %s\n", (int)width, label, inf->result);
552: free(label);
553: }
1.30 nicm 554: free(inf->result);
1.27 nicm 555:
556: if (inf->mapped && inf->base != NULL)
557: munmap(inf->base, inf->size);
1.1 deraadt 558: }