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