/* * Copyright (c) Ian F. Darwin 1986-1995. * Software written by Ian F. Darwin and others; * maintained 1995-present by Christos Zoulas and others. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice immediately at the beginning of the file, without modification, * this list of conditions, and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * file - find type of a file or files - main program. */ #include "file.h" #ifndef lint FILE_RCSID("@(#)$File: file.c,v 1.217 2024/09/29 16:49:25 christos Exp $") #endif /* lint */ #include "magic.h" #include #include #include #ifdef RESTORE_TIME # if (__COHERENT__ >= 0x420) # include # else # ifdef USE_UTIMES # include # else # include # endif # endif #endif #ifdef HAVE_UNISTD_H #include /* for read() */ #endif #ifdef HAVE_WCHAR_H #include #endif #ifdef HAVE_WCTYPE_H #include #endif #if defined(HAVE_WCHAR_H) && defined(HAVE_MBRTOWC) && defined(HAVE_WCWIDTH) && \ defined(HAVE_WCTYPE_H) #define FILE_WIDE_SUPPORT #else #include #endif #if defined(HAVE_GETOPT_H) && defined(HAVE_STRUCT_OPTION) # include # ifndef HAVE_GETOPT_LONG int getopt_long(int, char * const *, const char *, const struct option *, int *); # endif # else # include "mygetopt.h" #endif #ifdef S_IFLNK # define IFLNK_h "h" # define IFLNK_L "L" #else # define IFLNK_h "" # define IFLNK_L "" #endif #define FILE_FLAGS "bcCdE" IFLNK_h "ik" IFLNK_L "lNnprsSvzZ0" #define OPTSTRING "bcCde:Ef:F:hiklLm:nNpP:rsSvzZ0" # define USAGE \ "Usage: %s [-" FILE_FLAGS "] [--apple] [--extension] [--mime-encoding]\n" \ " [--mime-type] [-e ] [-F ] " \ " [-f ]\n" \ " [-m ] [-P ] [--exclude-quiet]\n" \ " ...\n" \ " %s -C [-m ]\n" \ " %s [--help]\n" file_private int /* Global command-line options */ bflag = 0, /* brief output format */ nopad = 0, /* Don't pad output */ nobuffer = 0, /* Do not buffer stdout */ nulsep = 0; /* Append '\0' to the separator */ file_private const char *separator = ":"; /* Default field separator */ file_private const struct option long_options[] = { #define OPT_HELP 1 #define OPT_APPLE 2 #define OPT_EXTENSIONS 3 #define OPT_MIME_TYPE 4 #define OPT_MIME_ENCODING 5 #define OPT_EXCLUDE_QUIET 6 #define OPT(shortname, longname, opt, def, doc) \ {longname, opt, NULL, shortname}, #define OPT_LONGONLY(longname, opt, def, doc, id) \ {longname, opt, NULL, id}, #include "file_opts.h" #undef OPT #undef OPT_LONGONLY {0, 0, NULL, 0} }; file_private const struct { const char *name; int value; } nv[] = { { "apptype", MAGIC_NO_CHECK_APPTYPE }, { "ascii", MAGIC_NO_CHECK_ASCII }, { "cdf", MAGIC_NO_CHECK_CDF }, { "compress", MAGIC_NO_CHECK_COMPRESS }, { "csv", MAGIC_NO_CHECK_CSV }, { "elf", MAGIC_NO_CHECK_ELF }, { "encoding", MAGIC_NO_CHECK_ENCODING }, { "soft", MAGIC_NO_CHECK_SOFT }, { "tar", MAGIC_NO_CHECK_TAR }, { "json", MAGIC_NO_CHECK_JSON }, { "simh", MAGIC_NO_CHECK_SIMH }, { "text", MAGIC_NO_CHECK_TEXT }, /* synonym for ascii */ { "tokens", MAGIC_NO_CHECK_TOKENS }, /* OBSOLETE: ignored for backwards compatibility */ }; file_private struct { const char *name; size_t value; size_t def; const char *desc; int tag; int set; } pm[] = { { "bytes", 0, FILE_BYTES_MAX, "max bytes to look inside file", MAGIC_PARAM_BYTES_MAX, 0 }, { "elf_notes", 0, FILE_ELF_NOTES_MAX, "max ELF notes processed", MAGIC_PARAM_ELF_NOTES_MAX, 0 }, { "elf_phnum", 0, FILE_ELF_PHNUM_MAX, "max ELF prog sections processed", MAGIC_PARAM_ELF_PHNUM_MAX, 0 }, { "elf_shnum", 0, FILE_ELF_SHNUM_MAX, "max ELF sections processed", MAGIC_PARAM_ELF_SHNUM_MAX, 0 }, { "elf_shsize", 0, FILE_ELF_SHSIZE_MAX, "max ELF section size", MAGIC_PARAM_ELF_SHSIZE_MAX, 0 }, { "encoding", 0, FILE_ENCODING_MAX, "max bytes to scan for encoding", MAGIC_PARAM_ENCODING_MAX, 0 }, { "indir", 0, FILE_INDIR_MAX, "recursion limit for indirection", MAGIC_PARAM_INDIR_MAX, 0 }, { "name", 0, FILE_NAME_MAX, "use limit for name/use magic", MAGIC_PARAM_NAME_MAX, 0 }, { "regex", 0, FILE_REGEX_MAX, "length limit for REGEX searches", MAGIC_PARAM_REGEX_MAX, 0 }, { "magwarn", 0, FILE_MAGWARN_MAX, "maximum number of magic warnings", MAGIC_PARAM_MAGWARN_MAX, 0 }, }; file_private int posixly; #ifdef __dead __dead #endif file_private void usage(void); file_private void docprint(const char *, int); #ifdef __dead __dead #endif file_private void help(void); file_private int unwrap(struct magic_set *, const char *); file_private int process(struct magic_set *ms, const char *, int); file_private struct magic_set *load(const char *, int); file_private void setparam(const char *); file_private void applyparam(magic_t); /* * main - parse arguments and handle options */ int main(int argc, char *argv[]) { int c; size_t i, j, wid, nw; int action = 0, didsomefiles = 0, errflg = 0; int flags = 0, e = 0; #ifdef HAVE_LIBSECCOMP int sandbox = 1; #endif struct magic_set *magic = NULL; int longindex; const char *magicfile = NULL; /* where the magic is */ char *progname; /* makes islower etc work for other langs */ (void)setlocale(LC_CTYPE, ""); #ifdef __EMX__ /* sh-like wildcard expansion! Shouldn't hurt at least ... */ _wildcard(&argc, &argv); #endif if ((progname = strrchr(argv[0], '/')) != NULL) progname++; else progname = argv[0]; file_setprogname(progname); #ifdef S_IFLNK posixly = getenv("POSIXLY_CORRECT") != NULL; flags |= posixly ? MAGIC_SYMLINK : 0; #endif while ((c = getopt_long(argc, argv, OPTSTRING, long_options, &longindex)) != -1) switch (c) { case OPT_HELP: help(); break; case OPT_APPLE: flags |= MAGIC_APPLE; break; case OPT_EXTENSIONS: flags |= MAGIC_EXTENSION; break; case OPT_MIME_TYPE: flags |= MAGIC_MIME_TYPE; break; case OPT_MIME_ENCODING: flags |= MAGIC_MIME_ENCODING; break; case '0': nulsep++; break; case 'b': bflag++; break; case 'c': action = FILE_CHECK; break; case 'C': action = FILE_COMPILE; break; case 'd': flags |= MAGIC_DEBUG|MAGIC_CHECK; break; case 'E': flags |= MAGIC_ERROR; break; case 'e': case OPT_EXCLUDE_QUIET: for (i = 0; i < __arraycount(nv); i++) if (strcmp(nv[i].name, optarg) == 0) break; if (i == __arraycount(nv)) { if (c != OPT_EXCLUDE_QUIET) errflg++; } else flags |= nv[i].value; break; case 'f': if(action) usage(); if (magic == NULL) if ((magic = load(magicfile, flags)) == NULL) return 1; applyparam(magic); e |= unwrap(magic, optarg); ++didsomefiles; break; case 'F': separator = optarg; break; case 'i': flags |= MAGIC_MIME; break; case 'k': flags |= MAGIC_CONTINUE; break; case 'l': action = FILE_LIST; break; case 'm': magicfile = optarg; break; case 'n': ++nobuffer; break; case 'N': ++nopad; break; #if defined(HAVE_UTIME) || defined(HAVE_UTIMES) case 'p': flags |= MAGIC_PRESERVE_ATIME; break; #endif case 'P': setparam(optarg); break; case 'r': flags |= MAGIC_RAW; break; case 's': flags |= MAGIC_DEVICES; break; case 'S': #ifdef HAVE_LIBSECCOMP sandbox = 0; #endif break; case 'v': if (magicfile == NULL) magicfile = magic_getpath(magicfile, action); (void)fprintf(stdout, "%s-%s\n", file_getprogname(), VERSION); (void)fprintf(stdout, "magic file from %s\n", magicfile); #ifdef HAVE_LIBSECCOMP (void)fprintf(stdout, "seccomp support included\n"); #endif return 0; case 'z': flags |= MAGIC_COMPRESS; break; case 'Z': flags |= MAGIC_COMPRESS|MAGIC_COMPRESS_TRANSP; break; #ifdef S_IFLNK case 'L': flags |= MAGIC_SYMLINK; break; case 'h': flags &= ~MAGIC_SYMLINK; break; #endif case '?': default: errflg++; break; } if (errflg) { usage(); } if (e) return e; #ifdef HAVE_LIBSECCOMP if (sandbox && enable_sandbox() == -1) file_err(EXIT_FAILURE, "SECCOMP initialisation failed"); if (sandbox) flags |= MAGIC_NO_COMPRESS_FORK; #endif /* HAVE_LIBSECCOMP */ if (MAGIC_VERSION != magic_version()) file_warnx("Compiled magic version [%d] " "does not match with shared library magic version [%d]\n", MAGIC_VERSION, magic_version()); switch(action) { case FILE_CHECK: case FILE_COMPILE: case FILE_LIST: /* * Don't try to check/compile ~/.magic unless we explicitly * ask for it. */ magic = magic_open(flags|MAGIC_CHECK); if (magic == NULL) { file_warn("Can't create magic"); return 1; } switch(action) { case FILE_CHECK: c = magic_check(magic, magicfile); break; case FILE_COMPILE: c = magic_compile(magic, magicfile); break; case FILE_LIST: c = magic_list(magic, magicfile); break; default: abort(); } if (c == -1) { file_warnx("%s", magic_error(magic)); e = 1; goto out; } goto out; default: if (magic == NULL) if ((magic = load(magicfile, flags)) == NULL) return 1; applyparam(magic); } if (optind == argc) { if (!didsomefiles) usage(); goto out; } for (wid = 0, j = CAST(size_t, optind); j < CAST(size_t, argc); j++) { nw = file_mbswidth(magic, argv[j]); if (nw > wid) wid = nw; } /* * If bflag is only set twice, set it depending on * number of files [this is undocumented, and subject to change] */ if (bflag == 2) { bflag = optind >= argc - 1; } for (; optind < argc; optind++) e |= process(magic, argv[optind], wid); out: if (!nobuffer) e |= fflush(stdout) != 0; if (magic) magic_close(magic); return e; } file_private void applyparam(magic_t magic) { size_t i; for (i = 0; i < __arraycount(pm); i++) { if (!pm[i].set) continue; if (magic_setparam(magic, pm[i].tag, &pm[i].value) == -1) file_err(EXIT_FAILURE, "Can't set %s", pm[i].name); } } file_private void setparam(const char *p) { size_t i; char *s; if ((s = CCAST(char *, strchr(p, '='))) == NULL) goto badparm; for (i = 0; i < __arraycount(pm); i++) { if (strncmp(p, pm[i].name, s - p) != 0) continue; pm[i].value = atoi(s + 1); pm[i].set = 1; return; } badparm: file_errx(EXIT_FAILURE, "Unknown param %s", p); } file_private struct magic_set * /*ARGSUSED*/ load(const char *magicfile, int flags) { struct magic_set *magic = magic_open(flags); const char *e; if (magic == NULL) { file_warn("Can't create magic"); return NULL; } if (magic_load(magic, magicfile) == -1) { file_warn("%s", magic_error(magic)); magic_close(magic); return NULL; } if ((e = magic_error(magic)) != NULL) file_warn("%s", e); return magic; } /* * unwrap -- read a file of filenames, do each one. */ file_private int unwrap(struct magic_set *ms, const char *fn) { FILE *f; ssize_t len; char *line = NULL; size_t llen = 0; int wid = 0, cwid; int e = 0; size_t fi = 0, fimax = 0; char **flist = NULL; if (strcmp("-", fn) == 0) f = stdin; else { if ((f = fopen(fn, "r")) == NULL) { file_warn("Cannot open `%s'", fn); return 1; } } while ((len = getline(&line, &llen, f)) > 0) { if (line[len - 1] == '\n') line[len - 1] = '\0'; cwid = file_mbswidth(ms, line); if (nobuffer) { e |= process(ms, line, cwid); free(line); line = NULL; llen = 0; continue; } if (cwid > wid) wid = cwid; if (fi >= fimax) { fimax += 100; char **nf = CAST(char **, realloc(flist, fimax * sizeof(*flist))); if (nf == NULL) { file_err(EXIT_FAILURE, "Cannot allocate memory for file list"); } flist = nf; } flist[fi++] = line; line = NULL; llen = 0; } if (!nobuffer) { fimax = fi; for (fi = 0; fi < fimax; fi++) { e |= process(ms, flist[fi], wid); free(flist[fi]); } } free(flist); if (f != stdin) (void)fclose(f); return e; } file_private void file_octal(unsigned char c) { (void)putc('\\', stdout); (void)putc(((c >> 6) & 7) + '0', stdout); (void)putc(((c >> 3) & 7) + '0', stdout); (void)putc(((c >> 0) & 7) + '0', stdout); } file_private void fname_print(const char *inname) { size_t n = strlen(inname); #ifdef FILE_WIDE_SUPPORT mbstate_t state; wchar_t nextchar; size_t bytesconsumed; (void)memset(&state, 0, sizeof(state)); while (n > 0) { bytesconsumed = mbrtowc(&nextchar, inname, n, &state); if (bytesconsumed == CAST(size_t, -1) || bytesconsumed == CAST(size_t, -2)) { nextchar = *inname++; n--; (void)memset(&state, 0, sizeof(state)); file_octal(CAST(unsigned char, nextchar)); continue; } inname += bytesconsumed; n -= bytesconsumed; if (iswprint(nextchar)) { printf("%lc", (wint_t)nextchar); continue; } /* XXX: What if it is > 255? */ file_octal(CAST(unsigned char, nextchar)); } #else size_t i; for (i = 0; i < n; i++) { unsigned char c = CAST(unsigned char, inname[i]); if (isprint(c)) { (void)putc(c, stdout); continue; } file_octal(c); } #endif } /* * Called for each input file on the command line (or in a list of files) */ file_private int process(struct magic_set *ms, const char *inname, int wid) { const char *type, c = nulsep > 1 ? '\0' : '\n'; int std_in = strcmp(inname, "-") == 0; int haderror = 0; if (wid > 0 && !bflag) { const char *pname = std_in ? "/dev/stdin" : inname; if ((ms->flags & MAGIC_RAW) == 0) fname_print(pname); else (void)printf("%s", pname); if (nulsep) (void)putc('\0', stdout); if (nulsep < 2) { (void)printf("%s", separator); (void)printf("%*s ", CAST(int, nopad ? 0 : (wid - file_mbswidth(ms, inname))), ""); } } type = magic_file(ms, std_in ? NULL : inname); if (type == NULL) { haderror |= printf("ERROR: %s%c", magic_error(ms), c); } else { haderror |= printf("%s%c", type, c) < 0; } if (nobuffer) haderror |= fflush(stdout) != 0; return haderror || type == NULL; } file_protected size_t file_mbswidth(struct magic_set *ms, const char *s) { size_t width = 0; #ifdef FILE_WIDE_SUPPORT size_t bytesconsumed, n; mbstate_t state; wchar_t nextchar; (void)memset(&state, 0, sizeof(state)); n = strlen(s); while (n > 0) { bytesconsumed = mbrtowc(&nextchar, s, n, &state); if (bytesconsumed == CAST(size_t, -1) || bytesconsumed == CAST(size_t, -2)) { nextchar = *s; bytesconsumed = 1; (void)memset(&state, 0, sizeof(state)); width += 4; } else { int w = wcwidth(nextchar); width += ((ms->flags & MAGIC_RAW) != 0 || iswprint(nextchar)) ? (w > 0 ? w : 1) : 4; } s += bytesconsumed, n -= bytesconsumed; } #else for (; *s; s++) { width += (ms->flags & MAGIC_RAW) != 0 || isprint(CAST(unsigned char, *s)) ? 1 : 4; } #endif return width; } file_private void usage(void) { const char *pn = file_getprogname(); (void)fprintf(stderr, USAGE, pn, pn, pn); exit(EXIT_FAILURE); } file_private void defprint(int def) { if (!def) return; if (((def & 1) && posixly) || ((def & 2) && !posixly)) (void)fprintf(stdout, " (default)"); (void)putc('\n', stdout); } file_private void docprint(const char *opts, int def) { size_t i; int comma, pad; char *sp, *p; p = CCAST(char *, strchr(opts, '%')); if (p == NULL) { (void)fprintf(stdout, "%s", opts); defprint(def); return; } for (sp = p - 1; sp > opts && *sp == ' '; sp--) continue; (void)printf("%.*s", CAST(int, p - opts), opts); pad = (int)CAST(int, p - sp - 1); switch (*++p) { case 'e': comma = 0; for (i = 0; i < __arraycount(nv); i++) { (void)printf("%s%s", comma++ ? ", " : "", nv[i].name); if (i && i % 5 == 0 && i != __arraycount(nv) - 1) { (void)printf(",\n%*s", pad, ""); comma = 0; } } break; case 'P': for (i = 0; i < __arraycount(pm); i++) { (void)printf("%9s %7zu %s", pm[i].name, pm[i].def, pm[i].desc); if (i != __arraycount(pm) - 1) (void)printf("\n%*s", pad, ""); } break; default: file_errx(EXIT_FAILURE, "Unknown escape `%c' in long options", *p); break; } (void)printf("%s", opts + (p - opts) + 1); } file_private void help(void) { (void)fputs( "Usage: file [OPTION...] [FILE...]\n" "Determine type of FILEs.\n" "\n", stdout); #define OPT(shortname, longname, opt, def, doc) \ (void)printf(" -%c, --" longname, shortname), \ docprint(doc, def); #define OPT_LONGONLY(longname, opt, def, doc, id) \ (void)printf(" --" longname), \ docprint(doc, def); #include "file_opts.h" #undef OPT #undef OPT_LONGONLY (void)printf("\nReport bugs to https://bugs.astron.com/\n"); exit(EXIT_SUCCESS); } file_private const char *file_progname; file_protected void file_setprogname(const char *progname) { file_progname = progname; } file_protected const char * file_getprogname(void) { return file_progname; } file_protected void file_err(int e, const char *fmt, ...) { va_list ap; int se = errno; va_start(ap, fmt); (void)fprintf(stderr, "%s: ", file_progname); (void)vfprintf(stderr, fmt, ap); va_end(ap); if (se) (void)fprintf(stderr, " (%s)\n", strerror(se)); else fputc('\n', stderr); exit(e); } file_protected void file_errx(int e, const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void)fprintf(stderr, "%s: ", file_progname); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); exit(e); } file_protected void file_warn(const char *fmt, ...) { va_list ap; int se = errno; va_start(ap, fmt); (void)fprintf(stderr, "%s: ", file_progname); (void)vfprintf(stderr, fmt, ap); va_end(ap); if (se) (void)fprintf(stderr, " (%s)\n", strerror(se)); else fputc('\n', stderr); errno = se; } file_protected void file_warnx(const char *fmt, ...) { va_list ap; int se = errno; va_start(ap, fmt); (void)fprintf(stderr, "%s: ", file_progname); (void)vfprintf(stderr, fmt, ap); va_end(ap); (void)fprintf(stderr, "\n"); errno = se; }