version 1.59, 2017/04/18 14:16:48 |
version 1.60, 2017/06/28 13:37:56 |
|
|
#include <errno.h> |
#include <errno.h> |
#include <fcntl.h> |
#include <fcntl.h> |
#include <getopt.h> |
#include <getopt.h> |
#include <imsg.h> |
|
#include <libgen.h> |
#include <libgen.h> |
#include <limits.h> |
#include <limits.h> |
#include <pwd.h> |
#include <pwd.h> |
#include <stdlib.h> |
#include <stdlib.h> |
#include <stdlib.h> |
|
#include <string.h> |
#include <string.h> |
#include <time.h> |
#include <time.h> |
#include <unistd.h> |
#include <unistd.h> |
|
|
#include "magic.h" |
#include "magic.h" |
#include "xmalloc.h" |
#include "xmalloc.h" |
|
|
struct input_msg { |
struct input_file { |
int idx; |
struct magic *m; |
|
|
struct stat sb; |
const char *path; |
int error; |
struct stat sb; |
|
int fd; |
|
int error; |
|
|
char link_path[PATH_MAX]; |
char link_path[PATH_MAX]; |
int link_error; |
int link_error; |
int link_target; |
int link_target; |
}; |
|
|
|
struct input_ack { |
void *base; |
int idx; |
size_t size; |
|
int mapped; |
|
char *result; |
}; |
}; |
|
|
struct input_file { |
|
struct magic *m; |
|
struct input_msg *msg; |
|
|
|
const char *path; |
|
int fd; |
|
|
|
void *base; |
|
size_t size; |
|
int mapped; |
|
char *result; |
|
}; |
|
|
|
extern char *__progname; |
extern char *__progname; |
|
|
__dead void usage(void); |
__dead void usage(void); |
|
|
static int prepare_message(struct input_msg *, int, const char *); |
static void prepare_input(struct input_file *, const char *); |
static void send_message(struct imsgbuf *, void *, size_t, int); |
|
static int read_message(struct imsgbuf *, struct imsg *, pid_t); |
|
|
|
static void read_link(struct input_msg *, const char *); |
static void read_link(struct input_file *, const char *); |
|
|
static __dead void child(int, pid_t, int, char **); |
|
|
|
static void test_file(struct input_file *, size_t); |
static void test_file(struct input_file *, size_t); |
|
|
static int try_stat(struct input_file *); |
static int try_stat(struct input_file *); |
|
|
static int sflag; |
static int sflag; |
static int Wflag; |
static int Wflag; |
|
|
static char *magicpath; |
|
static FILE *magicfp; |
|
|
|
static struct option longopts[] = { |
static struct option longopts[] = { |
{ "brief", no_argument, NULL, 'b' }, |
{ "brief", no_argument, NULL, 'b' }, |
{ "dereference", no_argument, NULL, 'L' }, |
{ "dereference", no_argument, NULL, 'L' }, |
|
|
int |
int |
main(int argc, char **argv) |
main(int argc, char **argv) |
{ |
{ |
int opt, pair[2], fd, idx; |
int opt, idx; |
char *home; |
char *home, *magicpath; |
struct passwd *pw; |
struct passwd *pw; |
struct imsgbuf ibuf; |
FILE *magicfp; |
struct imsg imsg; |
struct magic *m; |
struct input_msg msg; |
struct input_file *inf = NULL; |
struct input_ack *ack; |
size_t len, width = 0; |
pid_t pid, parent; |
|
|
|
tzset(); |
tzset(); |
|
|
|
|
if (magicfp == NULL) |
if (magicfp == NULL) |
err(1, "%s", magicpath); |
err(1, "%s", magicpath); |
|
|
parent = getpid(); |
if (!cflag) { |
if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pair) != 0) |
inf = xcalloc(argc, sizeof *inf); |
err(1, "socketpair"); |
for (idx = 0; idx < argc; idx++) { |
switch (pid = fork()) { |
len = strlen(argv[idx]) + 1; |
case -1: |
if (len > width) |
err(1, "fork"); |
width = len; |
case 0: |
prepare_input(&inf[idx], argv[idx]); |
close(pair[0]); |
} |
child(pair[1], parent, argc, argv); |
|
} |
} |
close(pair[1]); |
|
|
|
fclose(magicfp); |
if (pledge("stdio getpw id", NULL) == -1) |
magicfp = NULL; |
err(1, "pledge"); |
|
|
if (cflag) |
if (geteuid() == 0) { |
goto wait_for_child; |
pw = getpwnam(FILE_USER); |
|
if (pw == NULL) |
|
errx(1, "unknown user %s", FILE_USER); |
|
if (setgroups(1, &pw->pw_gid) != 0) |
|
err(1, "setgroups"); |
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) |
|
err(1, "setresgid"); |
|
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) |
|
err(1, "setresuid"); |
|
} |
|
|
imsg_init(&ibuf, pair[0]); |
if (pledge("stdio", NULL) == -1) |
for (idx = 0; idx < argc; idx++) { |
err(1, "pledge"); |
fd = prepare_message(&msg, idx, argv[idx]); |
|
send_message(&ibuf, &msg, sizeof msg, fd); |
|
|
|
if (read_message(&ibuf, &imsg, pid) == 0) |
m = magic_load(magicfp, magicpath, cflag || Wflag); |
break; |
if (cflag) { |
if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof *ack) |
magic_dump(m); |
errx(1, "message too small"); |
exit(0); |
ack = imsg.data; |
|
if (ack->idx != idx) |
|
errx(1, "index not expected"); |
|
imsg_free(&imsg); |
|
} |
} |
|
fclose(magicfp); |
|
|
wait_for_child: |
for (idx = 0; idx < argc; idx++) { |
close(pair[0]); |
inf[idx].m = m; |
while (wait(NULL) == -1 && errno != ECHILD) { |
test_file(&inf[idx], width); |
if (errno != EINTR) |
|
err(1, "wait"); |
|
} |
} |
_exit(0); /* let the child flush */ |
exit(0); |
} |
} |
|
|
static int |
static void |
prepare_message(struct input_msg *msg, int idx, const char *path) |
prepare_input(struct input_file *inf, const char *path) |
{ |
{ |
int fd, mode, error; |
int fd, mode, error; |
|
|
memset(msg, 0, sizeof *msg); |
|
msg->idx = idx; |
|
|
|
if (strcmp(path, "-") == 0) { |
if (strcmp(path, "-") == 0) { |
if (fstat(STDIN_FILENO, &msg->sb) == -1) { |
if (fstat(STDIN_FILENO, &inf->sb) == -1) { |
msg->error = errno; |
inf->error = errno; |
return (-1); |
inf->fd = -1; |
} |
} |
return (STDIN_FILENO); |
inf->fd = STDIN_FILENO; |
} |
} |
|
|
if (Lflag) |
if (Lflag) |
error = stat(path, &msg->sb); |
error = stat(path, &inf->sb); |
else |
else |
error = lstat(path, &msg->sb); |
error = lstat(path, &inf->sb); |
if (error == -1) { |
if (error == -1) { |
msg->error = errno; |
inf->error = errno; |
return (-1); |
inf->fd = -1; |
} |
} |
|
|
/* |
/* We don't need them, so don't open directories or symlinks. */ |
* pledge(2) doesn't let us pass directory file descriptors around - |
mode = inf->sb.st_mode; |
* but in fact we don't need them, so just don't open directories or |
|
* symlinks (which could be to directories). |
|
*/ |
|
mode = msg->sb.st_mode; |
|
if (!S_ISDIR(mode) && !S_ISLNK(mode)) { |
if (!S_ISDIR(mode) && !S_ISLNK(mode)) { |
fd = open(path, O_RDONLY|O_NONBLOCK); |
fd = open(path, O_RDONLY|O_NONBLOCK); |
if (fd == -1 && (errno == ENFILE || errno == EMFILE)) |
if (fd == -1 && (errno == ENFILE || errno == EMFILE)) |
|
|
} else |
} else |
fd = -1; |
fd = -1; |
if (S_ISLNK(mode)) |
if (S_ISLNK(mode)) |
read_link(msg, path); |
read_link(inf, path); |
return (fd); |
inf->fd = fd; |
|
inf->path = path; |
} |
} |
|
|
static void |
static void |
send_message(struct imsgbuf *ibuf, void *msg, size_t msglen, int fd) |
read_link(struct input_file *inf, const char *path) |
{ |
{ |
if (imsg_compose(ibuf, -1, -1, 0, fd, msg, msglen) != 1) |
|
err(1, "imsg_compose"); |
|
if (imsg_flush(ibuf) != 0) |
|
err(1, "imsg_flush"); |
|
} |
|
|
|
static int |
|
read_message(struct imsgbuf *ibuf, struct imsg *imsg, pid_t from) |
|
{ |
|
int n; |
|
|
|
while ((n = imsg_read(ibuf)) == -1 && errno == EAGAIN) |
|
/* nothing */ ; |
|
if (n == -1) |
|
err(1, "imsg_read"); |
|
if (n == 0) |
|
return (0); |
|
|
|
if ((n = imsg_get(ibuf, imsg)) == -1) |
|
err(1, "imsg_get"); |
|
if (n == 0) |
|
return (0); |
|
|
|
if ((pid_t)imsg->hdr.pid != from) |
|
errx(1, "PIDs don't match"); |
|
|
|
return (n); |
|
|
|
} |
|
|
|
static void |
|
read_link(struct input_msg *msg, const char *path) |
|
{ |
|
struct stat sb; |
struct stat sb; |
char lpath[PATH_MAX]; |
char lpath[PATH_MAX]; |
char *copy, *root; |
char *copy, *root; |
|
|
|
|
size = readlink(path, lpath, sizeof lpath - 1); |
size = readlink(path, lpath, sizeof lpath - 1); |
if (size == -1) { |
if (size == -1) { |
msg->link_error = errno; |
inf->link_error = errno; |
return; |
return; |
} |
} |
lpath[size] = '\0'; |
lpath[size] = '\0'; |
|
|
if (*lpath == '/') |
if (*lpath == '/') |
strlcpy(msg->link_path, lpath, sizeof msg->link_path); |
strlcpy(inf->link_path, lpath, sizeof inf->link_path); |
else { |
else { |
copy = xstrdup(path); |
copy = xstrdup(path); |
|
|
root = dirname(copy); |
root = dirname(copy); |
if (*root == '\0' || strcmp(root, ".") == 0 || |
if (*root == '\0' || strcmp(root, ".") == 0 || |
strcmp (root, "/") == 0) |
strcmp (root, "/") == 0) |
strlcpy(msg->link_path, lpath, sizeof msg->link_path); |
strlcpy(inf->link_path, lpath, sizeof inf->link_path); |
else { |
else { |
used = snprintf(msg->link_path, sizeof msg->link_path, |
used = snprintf(inf->link_path, sizeof inf->link_path, |
"%s/%s", root, lpath); |
"%s/%s", root, lpath); |
if (used < 0 || (size_t)used >= sizeof msg->link_path) { |
if (used < 0 || (size_t)used >= sizeof inf->link_path) { |
msg->link_error = ENAMETOOLONG; |
inf->link_error = ENAMETOOLONG; |
free(copy); |
free(copy); |
return; |
return; |
} |
} |
|
|
} |
} |
|
|
if (!Lflag && stat(path, &sb) == -1) |
if (!Lflag && stat(path, &sb) == -1) |
msg->link_target = errno; |
inf->link_target = errno; |
} |
} |
|
|
static __dead void |
|
child(int fd, pid_t parent, int argc, char **argv) |
|
{ |
|
struct passwd *pw; |
|
struct magic *m; |
|
struct imsgbuf ibuf; |
|
struct imsg imsg; |
|
struct input_msg *msg; |
|
struct input_ack ack; |
|
struct input_file inf; |
|
int i, idx; |
|
size_t len, width = 0; |
|
|
|
if (pledge("stdio getpw recvfd id", NULL) == -1) |
|
err(1, "pledge"); |
|
|
|
if (geteuid() == 0) { |
|
pw = getpwnam(FILE_USER); |
|
if (pw == NULL) |
|
errx(1, "unknown user %s", FILE_USER); |
|
if (setgroups(1, &pw->pw_gid) != 0) |
|
err(1, "setgroups"); |
|
if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) |
|
err(1, "setresgid"); |
|
if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) |
|
err(1, "setresuid"); |
|
} |
|
|
|
if (pledge("stdio recvfd", NULL) == -1) |
|
err(1, "pledge"); |
|
|
|
m = magic_load(magicfp, magicpath, cflag || Wflag); |
|
if (cflag) { |
|
magic_dump(m); |
|
exit(0); |
|
} |
|
|
|
for (i = 0; i < argc; i++) { |
|
len = strlen(argv[i]) + 1; |
|
if (len > width) |
|
width = len; |
|
} |
|
|
|
imsg_init(&ibuf, fd); |
|
for (;;) { |
|
if (read_message(&ibuf, &imsg, parent) == 0) |
|
break; |
|
if (imsg.hdr.len != IMSG_HEADER_SIZE + sizeof *msg) |
|
errx(1, "message too small"); |
|
msg = imsg.data; |
|
|
|
idx = msg->idx; |
|
if (idx < 0 || idx >= argc) |
|
errx(1, "index out of range"); |
|
|
|
memset(&inf, 0, sizeof inf); |
|
inf.m = m; |
|
inf.msg = msg; |
|
|
|
inf.path = argv[idx]; |
|
inf.fd = imsg.fd; |
|
|
|
test_file(&inf, width); |
|
|
|
if (imsg.fd != -1) |
|
close(imsg.fd); |
|
imsg_free(&imsg); |
|
|
|
ack.idx = idx; |
|
send_message(&ibuf, &ack, sizeof ack, -1); |
|
} |
|
exit(0); |
|
} |
|
|
|
static void * |
static void * |
fill_buffer(int fd, size_t size, size_t *used) |
fill_buffer(int fd, size_t size, size_t *used) |
{ |
{ |
|
|
{ |
{ |
size_t used; |
size_t used; |
|
|
if (inf->msg->sb.st_size == 0 && S_ISREG(inf->msg->sb.st_mode)) |
if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) |
return (0); /* empty file */ |
return (0); /* empty file */ |
if (inf->msg->sb.st_size == 0 || inf->msg->sb.st_size > FILE_READ_SIZE) |
if (inf->sb.st_size == 0 || inf->sb.st_size > FILE_READ_SIZE) |
inf->size = FILE_READ_SIZE; |
inf->size = FILE_READ_SIZE; |
else |
else |
inf->size = inf->msg->sb.st_size; |
inf->size = inf->sb.st_size; |
|
|
if (!S_ISREG(inf->msg->sb.st_mode)) |
if (!S_ISREG(inf->sb.st_mode)) |
goto try_read; |
goto try_read; |
|
|
inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0); |
inf->base = mmap(NULL, inf->size, PROT_READ, MAP_PRIVATE, inf->fd, 0); |
|
|
static int |
static int |
try_stat(struct input_file *inf) |
try_stat(struct input_file *inf) |
{ |
{ |
if (inf->msg->error != 0) { |
if (inf->error != 0) { |
xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path, |
xasprintf(&inf->result, "cannot stat '%s' (%s)", inf->path, |
strerror(inf->msg->error)); |
strerror(inf->error)); |
return (1); |
return (1); |
} |
} |
if (sflag || strcmp(inf->path, "-") == 0) { |
if (sflag || strcmp(inf->path, "-") == 0) { |
switch (inf->msg->sb.st_mode & S_IFMT) { |
switch (inf->sb.st_mode & S_IFMT) { |
case S_IFIFO: |
case S_IFIFO: |
if (strcmp(inf->path, "-") != 0) |
if (strcmp(inf->path, "-") != 0) |
break; |
break; |
|
|
} |
} |
} |
} |
|
|
if (iflag && (inf->msg->sb.st_mode & S_IFMT) != S_IFREG) { |
if (iflag && (inf->sb.st_mode & S_IFMT) != S_IFREG) { |
xasprintf(&inf->result, "application/x-not-regular-file"); |
xasprintf(&inf->result, "application/x-not-regular-file"); |
return (1); |
return (1); |
} |
} |
|
|
switch (inf->msg->sb.st_mode & S_IFMT) { |
switch (inf->sb.st_mode & S_IFMT) { |
case S_IFDIR: |
case S_IFDIR: |
xasprintf(&inf->result, "directory"); |
xasprintf(&inf->result, "directory"); |
return (1); |
return (1); |
case S_IFLNK: |
case S_IFLNK: |
if (inf->msg->link_error != 0) { |
if (inf->link_error != 0) { |
xasprintf(&inf->result, "unreadable symlink '%s' (%s)", |
xasprintf(&inf->result, "unreadable symlink '%s' (%s)", |
inf->path, strerror(inf->msg->link_error)); |
inf->path, strerror(inf->link_error)); |
return (1); |
return (1); |
} |
} |
if (inf->msg->link_target == ELOOP) |
if (inf->link_target == ELOOP) |
xasprintf(&inf->result, "symbolic link in a loop"); |
xasprintf(&inf->result, "symbolic link in a loop"); |
else if (inf->msg->link_target != 0) { |
else if (inf->link_target != 0) { |
xasprintf(&inf->result, "broken symbolic link to '%s'", |
xasprintf(&inf->result, "broken symbolic link to '%s'", |
inf->msg->link_path); |
inf->link_path); |
} else { |
} else { |
xasprintf(&inf->result, "symbolic link to '%s'", |
xasprintf(&inf->result, "symbolic link to '%s'", |
inf->msg->link_path); |
inf->link_path); |
} |
} |
return (1); |
return (1); |
case S_IFSOCK: |
case S_IFSOCK: |
|
|
return (1); |
return (1); |
case S_IFBLK: |
case S_IFBLK: |
xasprintf(&inf->result, "block special (%ld/%ld)", |
xasprintf(&inf->result, "block special (%ld/%ld)", |
(long)major(inf->msg->sb.st_rdev), |
(long)major(inf->sb.st_rdev), |
(long)minor(inf->msg->sb.st_rdev)); |
(long)minor(inf->sb.st_rdev)); |
return (1); |
return (1); |
case S_IFCHR: |
case S_IFCHR: |
xasprintf(&inf->result, "character special (%ld/%ld)", |
xasprintf(&inf->result, "character special (%ld/%ld)", |
(long)major(inf->msg->sb.st_rdev), |
(long)major(inf->sb.st_rdev), |
(long)minor(inf->msg->sb.st_rdev)); |
(long)minor(inf->sb.st_rdev)); |
return (1); |
return (1); |
case S_IFIFO: |
case S_IFIFO: |
xasprintf(&inf->result, "fifo (named pipe)"); |
xasprintf(&inf->result, "fifo (named pipe)"); |
|
|
{ |
{ |
char tmp[256] = ""; |
char tmp[256] = ""; |
|
|
if (inf->msg->sb.st_size == 0 && S_ISREG(inf->msg->sb.st_mode)) |
if (inf->sb.st_size == 0 && S_ISREG(inf->sb.st_mode)) |
return (0); /* empty file */ |
return (0); /* empty file */ |
if (inf->fd != -1) |
if (inf->fd != -1) |
return (0); |
return (0); |
|
|
if (inf->msg->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) |
if (inf->sb.st_mode & (S_IWUSR|S_IWGRP|S_IWOTH)) |
strlcat(tmp, "writable, ", sizeof tmp); |
strlcat(tmp, "writable, ", sizeof tmp); |
if (inf->msg->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) |
if (inf->sb.st_mode & (S_IXUSR|S_IXGRP|S_IXOTH)) |
strlcat(tmp, "executable, ", sizeof tmp); |
strlcat(tmp, "executable, ", sizeof tmp); |
if (S_ISREG(inf->msg->sb.st_mode)) |
if (S_ISREG(inf->sb.st_mode)) |
strlcat(tmp, "regular file, ", sizeof tmp); |
strlcat(tmp, "regular file, ", sizeof tmp); |
strlcat(tmp, "no read permission", sizeof tmp); |
strlcat(tmp, "no read permission", sizeof tmp); |
|
|