#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UID_YES 991
#define UID_NO 990
#define GID_YES 991
#define GID_NO 990
char dir[] = "testdir";
char exists[] = "testdir/testfile";
char r_r_exists[] = "testdir/testfile_r_r";
char r_w_exists[] = "testdir/testfile_r_w";
char w_r_exists[] = "testdir/testfile_w_r";
char w_w_exists[] = "testdir/testfile_w_w";
char x_x_exists[] = "testdir/testfile_x_x";
char noexists[] = "testdir/nosuchfile";
char temp[] = "/tmp/accessXXXXXXXXX";
struct tests {
int err, eaccess;
uid_t ruid, euid;
gid_t rgid, egid;
int amode;
const char *filename;
} tests[] = {
{ EACCES, 0, UID_NO, UID_NO, GID_NO, GID_NO, F_OK, exists },
{ EACCES, 0, UID_NO, UID_YES, GID_NO, GID_NO, F_OK, exists },
{ EACCES, 0, UID_NO, UID_NO, GID_NO, GID_YES, F_OK, exists },
{ EACCES, 0, UID_NO, UID_YES, GID_NO, GID_YES, F_OK, exists },
{ EACCES, 1, UID_NO, UID_NO, GID_NO, GID_NO, F_OK, exists },
{ EACCES, 1, UID_YES, UID_NO, GID_NO, GID_NO, F_OK, exists },
{ EACCES, 1, UID_NO, UID_NO, GID_YES, GID_NO, F_OK, exists },
{ EACCES, 1, UID_YES, UID_NO, GID_YES, GID_NO, F_OK, exists },
{ ENOENT, 0, UID_YES, UID_NO, GID_NO, GID_NO, F_OK, noexists },
{ ENOENT, 0, UID_NO, UID_NO, GID_YES, GID_NO, F_OK, noexists },
{ ENOENT, 0, UID_YES, UID_NO, GID_YES, GID_NO, F_OK, noexists },
{ ENOENT, 1, UID_NO, UID_YES, GID_NO, GID_NO, F_OK, noexists },
{ ENOENT, 1, UID_NO, UID_NO, GID_NO, GID_YES, F_OK, noexists },
{ ENOENT, 1, UID_NO, UID_YES, GID_NO, GID_YES, F_OK, noexists },
{ EACCES, 0, UID_YES, UID_NO, GID_NO, GID_NO, R_OK, w_w_exists },
{ EACCES, 0, UID_NO, UID_NO, GID_YES, GID_NO, R_OK, w_w_exists },
{ EACCES, 0, UID_YES, UID_NO, GID_YES, GID_NO, R_OK, w_w_exists },
{ EACCES, 1, UID_NO, UID_YES, GID_NO, GID_NO, R_OK, w_w_exists },
{ EACCES, 1, UID_NO, UID_NO, GID_NO, GID_YES, R_OK, w_w_exists },
{ EACCES, 0, UID_YES, UID_NO, GID_NO, GID_NO, R_OK, w_r_exists },
{ EACCES, 0, UID_NO, UID_NO, GID_YES, GID_NO, R_OK, r_w_exists },
{ EACCES, 1, UID_NO, UID_YES, GID_NO, GID_NO, R_OK, w_r_exists },
{ EACCES, 1, UID_NO, UID_NO, GID_NO, GID_YES, R_OK, r_w_exists },
{ EACCES, 0, UID_YES, UID_NO, GID_YES, GID_NO, R_OK, w_r_exists },
{ EACCES, 1, UID_NO, UID_YES, GID_NO, GID_YES, R_OK, w_r_exists },
{ EACCES, 0, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, w_r_exists },
{ EACCES, 1, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, w_r_exists },
{ 0, 0, UID_YES, UID_NO, GID_NO, GID_NO, R_OK, r_w_exists },
{ 0, 0, UID_NO, UID_NO, GID_YES, GID_NO, R_OK, w_r_exists },
{ 0, 0, UID_YES, UID_NO, GID_YES, GID_NO, R_OK, r_w_exists },
{ 0, 0, UID_YES, UID_NO, GID_YES, GID_NO, R_OK, r_r_exists },
{ 0, 1, UID_NO, UID_YES, GID_NO, GID_NO, R_OK, r_w_exists },
{ 0, 1, UID_NO, UID_NO, GID_NO, GID_YES, R_OK, w_r_exists },
{ 0, 1, UID_NO, UID_YES, GID_NO, GID_YES, R_OK, r_w_exists },
{ 0, 1, UID_NO, UID_YES, GID_NO, GID_YES, R_OK, r_r_exists },
{ 0, 0, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, r_w_exists },
{ 0, 0, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, r_r_exists },
{ 0, 1, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, r_w_exists },
{ 0, 1, UID_YES, UID_YES, GID_YES, GID_YES, R_OK, r_r_exists },
{ 0 }
};
static void
prepfile(const char *filename, mode_t mode)
{
int fd;
if ((fd = open(filename, O_WRONLY|O_CREAT, 600)) < 0)
err(1, "open %s", filename);
close(fd);
if (chown(filename, UID_YES, GID_YES))
err(1, "chown %s %d:%d", filename, UID_YES, GID_YES);
if (chmod(filename, mode))
err(1, "chmod %s %o", filename, mode);
}
static void
docleanup(void)
{
setresuid(0, 0, 0);
remove(exists);
remove(r_r_exists);
remove(r_w_exists);
remove(w_r_exists);
remove(w_w_exists);
remove(x_x_exists);
remove(dir);
chdir("/");
remove(temp);
}
int
main(int argc, char *argv[])
{
char buf[200];
struct tests *t;
int ret, result;
gid_t supp_group = GID_NO;
if (geteuid() != 0) {
if (getuid() != 0)
errx(0, "must be run as root");
else if (setuid(0))
err(1, "setuid");
}
if (setgroups(1, &supp_group))
err(1, "setgroups");
if (mkdtemp(temp) == NULL)
err(1, "mkdtemp");
if (chdir(temp)) {
ret = errno;
remove(temp);
errc(1, ret, "chdir");
}
if (chmod(temp, 0755))
err(1, "chmod %s %o", temp, 0755);
atexit(docleanup);
umask(0);
if (mkdir(dir, 0750))
err(1, "mkdir");
prepfile(exists, 0);
prepfile(r_r_exists, 0440);
prepfile(r_w_exists, 0420);
prepfile(w_r_exists, 0240);
prepfile(w_w_exists, 0220);
prepfile(x_x_exists, 0110);
if (chown(dir, UID_YES, GID_YES))
err(1, "chown %s %d:%d", dir, UID_YES, GID_YES);
result = 0;
for (t = tests; t->filename != NULL; t++) {
if (setresgid(t->rgid, t->egid, 0))
err(1, "setresgid");
if (setresuid(t->ruid, t->euid, 0))
err(1, "setresuid");
ret = faccessat(AT_FDCWD, t->filename, t->amode,
t->eaccess ? AT_EACCESS : 0);
if (ret) {
ret = errno;
strerror_r(ret, buf, sizeof buf);
}
if (ret != t->err) {
result = 1;
warnx("uid %d/%d gid %d/%d mode %d eaccess %d %s:"
" %s instead of %s",
t->ruid, t->euid, t->rgid, t->egid,
t->amode, t->eaccess, t->filename,
ret ? buf : "success",
t->err ? strerror(t->err) : "success");
}
if (setresuid(0, 0, 0))
err(1, "setresuid restore");
}
return (result);
}