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