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