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