Annotation of src/usr.bin/file/file.c, Revision 1.64
1.64 ! brynet 1: /* $OpenBSD: file.c,v 1.63 2017/06/28 17:14:15 brynet 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.64 ! brynet 211: if (inf[idx].fd != -1 && inf[idx].fd != STDIN_FILENO)
! 212: close(inf[idx].fd);
1.30 nicm 213: }
1.60 brynet 214: exit(0);
1.55 nicm 215: }
216:
1.60 brynet 217: static void
218: prepare_input(struct input_file *inf, const char *path)
1.55 nicm 219: {
220: int fd, mode, error;
221:
1.63 brynet 222: inf->path = path;
223:
1.55 nicm 224: if (strcmp(path, "-") == 0) {
1.60 brynet 225: if (fstat(STDIN_FILENO, &inf->sb) == -1) {
226: inf->error = errno;
227: inf->fd = -1;
1.63 brynet 228: return;
1.55 nicm 229: }
1.60 brynet 230: inf->fd = STDIN_FILENO;
1.63 brynet 231: return;
1.55 nicm 232: }
233:
234: if (Lflag)
1.60 brynet 235: error = stat(path, &inf->sb);
1.55 nicm 236: else
1.60 brynet 237: error = lstat(path, &inf->sb);
1.55 nicm 238: if (error == -1) {
1.60 brynet 239: inf->error = errno;
240: inf->fd = -1;
1.63 brynet 241: return;
1.55 nicm 242: }
243:
1.60 brynet 244: /* We don't need them, so don't open directories or symlinks. */
245: mode = inf->sb.st_mode;
1.55 nicm 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))
1.60 brynet 253: read_link(inf, path);
254: inf->fd = fd;
1.27 nicm 255: }
256:
257: static void
1.60 brynet 258: read_link(struct input_file *inf, const char *path)
1.14 tedu 259: {
1.27 nicm 260: struct stat sb;
1.35 nicm 261: char lpath[PATH_MAX];
1.27 nicm 262: char *copy, *root;
263: int used;
264: ssize_t size;
265:
1.47 tobias 266: size = readlink(path, lpath, sizeof lpath - 1);
1.27 nicm 267: if (size == -1) {
1.60 brynet 268: inf->link_error = errno;
1.14 tedu 269: return;
1.27 nicm 270: }
1.35 nicm 271: lpath[size] = '\0';
1.27 nicm 272:
1.35 nicm 273: if (*lpath == '/')
1.60 brynet 274: strlcpy(inf->link_path, lpath, sizeof inf->link_path);
1.27 nicm 275: else {
1.35 nicm 276: copy = xstrdup(path);
1.27 nicm 277:
278: root = dirname(copy);
279: if (*root == '\0' || strcmp(root, ".") == 0 ||
280: strcmp (root, "/") == 0)
1.60 brynet 281: strlcpy(inf->link_path, lpath, sizeof inf->link_path);
1.27 nicm 282: else {
1.60 brynet 283: used = snprintf(inf->link_path, sizeof inf->link_path,
1.35 nicm 284: "%s/%s", root, lpath);
1.60 brynet 285: if (used < 0 || (size_t)used >= sizeof inf->link_path) {
286: inf->link_error = ENAMETOOLONG;
1.37 lteo 287: free(copy);
1.27 nicm 288: return;
289: }
290: }
291:
292: free(copy);
293: }
294:
1.54 nicm 295: if (!Lflag && stat(path, &sb) == -1)
1.60 brynet 296: inf->link_target = errno;
1.35 nicm 297: }
298:
1.27 nicm 299: static void *
1.42 nicm 300: fill_buffer(int fd, size_t size, size_t *used)
1.1 deraadt 301: {
1.27 nicm 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;
1.42 nicm 311: left = size;
1.27 nicm 312: while (left != 0) {
1.42 nicm 313: got = read(fd, next, left);
1.27 nicm 314: if (got == -1) {
315: if (errno == EINTR)
316: continue;
1.59 nicm 317: return (NULL);
1.5 millert 318: }
1.27 nicm 319: if (got == 0)
320: break;
1.30 nicm 321: next = (char *)next + got;
1.27 nicm 322: left -= got;
323: }
1.42 nicm 324: *used = size - left;
1.59 nicm 325: return (buffer);
1.27 nicm 326: }
327:
328: static int
329: load_file(struct input_file *inf)
330: {
1.42 nicm 331: size_t used;
332:
1.60 brynet 333: if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode))
1.43 nicm 334: return (0); /* empty file */
1.60 brynet 335: if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE)
1.27 nicm 336: inf->size = FILE_READ_SIZE;
1.46 tobias 337: else
1.60 brynet 338: inf->size = inf->sb.st_size;
1.43 nicm 339:
1.60 brynet 340: if (!S_ISREG(inf->sb.st_mode))
1.43 nicm 341: goto try_read;
1.27 nicm 342:
343: inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0);
1.43 nicm 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;
1.27 nicm 357: return (0);
358: }
1.5 millert 359:
1.27 nicm 360: static int
361: try_stat(struct input_file *inf)
362: {
1.60 brynet 363: if (inf->error != 0) {
1.27 nicm 364: xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path,
1.60 brynet 365: strerror(inf->error));
1.27 nicm 366: return (1);
367: }
1.45 nicm 368: if (sflag || strcmp(inf->path, "-") == 0) {
1.60 brynet 369: switch (inf->sb.st_mode & S_IFMT) {
1.45 nicm 370: case S_IFIFO:
371: if (strcmp(inf->path, "-") != 0)
372: break;
1.27 nicm 373: case S_IFBLK:
374: case S_IFCHR:
375: case S_IFREG:
376: return (0);
1.5 millert 377: }
1.27 nicm 378: }
1.1 deraadt 379:
1.60 brynet 380: if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) {
1.27 nicm 381: xasprintf(&inf->result, "application/x-not-regular-file");
382: return (1);
1.1 deraadt 383: }
384:
1.60 brynet 385: switch (inf->sb.st_mode & S_IFMT) {
1.27 nicm 386: case S_IFDIR:
387: xasprintf(&inf->result, "directory");
388: return (1);
389: case S_IFLNK:
1.60 brynet 390: if (inf->link_error != 0) {
1.27 nicm 391: xasprintf(&inf->result, "unreadable symlink '%s' (%s)",
1.60 brynet 392: inf->path, strerror(inf->link_error));
1.27 nicm 393: return (1);
394: }
1.60 brynet 395: if (inf->link_target == ELOOP)
1.27 nicm 396: xasprintf(&inf->result, "symbolic link in a loop");
1.60 brynet 397: else if (inf->link_target != 0) {
1.27 nicm 398: xasprintf(&inf->result, "broken symbolic link to '%s'",
1.60 brynet 399: inf->link_path);
1.27 nicm 400: } else {
401: xasprintf(&inf->result, "symbolic link to '%s'",
1.60 brynet 402: inf->link_path);
1.27 nicm 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)",
1.60 brynet 410: (long)major(inf->sb.st_rdev),
411: (long)minor(inf->sb.st_rdev));
1.27 nicm 412: return (1);
413: case S_IFCHR:
414: xasprintf(&inf->result, "character special (%ld/%ld)",
1.60 brynet 415: (long)major(inf->sb.st_rdev),
416: (long)minor(inf->sb.st_rdev));
1.27 nicm 417: return (1);
418: case S_IFIFO:
419: xasprintf(&inf->result, "fifo (named pipe)");
420: return (1);
1.1 deraadt 421: }
1.27 nicm 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:
1.60 brynet 443: if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode))
1.49 nicm 444: return (0); /* empty file */
1.27 nicm 445: if (inf->fd != -1)
446: return (0);
1.1 deraadt 447:
1.60 brynet 448: if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH))
1.27 nicm 449: strlcat(tmp, "writable, ", sizeof tmp);
1.60 brynet 450: if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH))
1.27 nicm 451: strlcat(tmp, "executable, ", sizeof tmp);
1.60 brynet 452: if (S_ISREG(inf->sb.st_mode))
1.27 nicm 453: strlcat(tmp, "regular file, ", sizeof tmp);
454: strlcat(tmp, "no read permission", sizeof tmp);
455:
456: inf->result = xstrdup(tmp);
457: return (1);
1.1 deraadt 458: }
459:
1.27 nicm 460: static int
461: try_text(struct input_file *inf)
1.14 tedu 462: {
1.27 nicm 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);
1.14 tedu 473:
1.27 nicm 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);
1.18 chl 484: else
1.27 nicm 485: xasprintf(&inf->result, "%s %s text", type, s);
486: return (1);
1.18 chl 487: }
1.14 tedu 488:
1.27 nicm 489: if (iflag)
490: inf->result = xstrdup("text/plain");
1.14 tedu 491: else
1.27 nicm 492: xasprintf(&inf->result, "%s text", type);
493: return (1);
1.14 tedu 494: }
495:
1.27 nicm 496: static int
497: try_magic(struct input_file *inf)
1.1 deraadt 498: {
1.27 nicm 499: const char *s;
500: int flags;
1.1 deraadt 501:
1.27 nicm 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);
1.1 deraadt 510: }
1.27 nicm 511: return (0);
1.14 tedu 512: }
1.1 deraadt 513:
1.27 nicm 514: static int
515: try_unknown(struct input_file *inf)
1.14 tedu 516: {
1.27 nicm 517: if (iflag)
518: xasprintf(&inf->result, "application/x-not-regular-file");
519: else
520: xasprintf(&inf->result, "data");
521: return (1);
1.1 deraadt 522: }
523:
1.27 nicm 524: static void
1.35 nicm 525: test_file(struct input_file *inf, size_t width)
1.1 deraadt 526: {
1.35 nicm 527: char *label;
528: int stop;
1.27 nicm 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);
1.35 nicm 548: else {
1.45 nicm 549: if (strcmp(inf->path, "-") == 0)
550: xasprintf(&label, "/dev/stdin:");
551: else
552: xasprintf(&label, "%s:", inf->path);
1.35 nicm 553: printf("%-*s %s\n", (int)width, label, inf->result);
554: free(label);
555: }
1.30 nicm 556: free(inf->result);
1.27 nicm 557:
558: if (inf->mapped && inf->base != NULL)
559: munmap(inf->base, inf->size);
1.1 deraadt 560: }