Welcome to little lamb

Code » anopa » jjk » tree

[jjk] / src / utils / aa-test.c

/*
 * anopa - Copyright (C) 2015-2017 Olivier Brunel
 *
 * aa-test.c
 * Copyright (C) 2015-2018 Olivier Brunel <jjk@jjacky.com>
 *
 * This file is part of anopa.
 *
 * anopa is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * anopa is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * anopa. If not, see http://www.gnu.org/licenses/
 */

#include <getopt.h>
#include <errno.h>
#include <unistd.h>
#include <sys/stat.h>
#include <skalibs/types.h>
#include <anopa/common.h>
#include <anopa/output.h>

enum
{
    RC_ST_EXIST     = 1 << 1,
    RC_ST_FAIL      = 2 << 1
};

static int
is_group_member (gid_t gid)
{
    int nb = getgroups (0, NULL);
    gid_t groups[nb];
    int i;

    if (gid == getgid () || gid == getegid())
        return 1;

    if (getgroups (nb, groups) < 0)
        return 0;

    for (i = 0; i < nb; ++i)
        if (gid == groups[i])
            return 1;

    return 0;
}

static void
dieusage (int rc)
{
    aa_die_usage (rc, "OPTION FILE",
            " -D, --double-output           Enable double-output mode\n"
            " -O, --log-file FILE|FD        Write log to FILE|FD\n"
            " -b, --block                   Test whether FILE is a block special\n"
            " -d, --directory               Test whether FILE is a directory\n"
            " -e, --exists                  Test whether FILE exists\n"
            " -f, --file                    Test whether FILE is a regular file\n"
            " -L, --symlink                 Test whether FILE is a symbolic link\n"
            " -p, --pipe                    Test whether FILE is a named pipe\n"
            " -S, --socket                  Test whether FILE is a socket\n"
            " -r, --read                    Test for read permission on FILE\n"
            " -w, --write                   Test for write permission on FILE\n"
            " -x, --execute                 Test for execute permission on FILE\n"
            " -R, --repeat[=TIMES]          Repeat test every second up to TIMES times\n"
            "\n"
            " -h, --help                    Show this help screen and exit\n"
            " -V, --version                 Show version information and exit\n"
            );
}

int
main (int argc, char * const argv[])
{
    PROG = "aa-test";
    struct stat st;
    uid_t euid;
    int mode = 0; /* silence warning */
    char test = 0;
    unsigned int repeat = 0;

    for (;;)
    {
        struct option longopts[] = {
            { "block",              no_argument,        NULL,   'b' },
            { "double-output",      no_argument,        NULL,   'D' },
            { "directory",          no_argument,        NULL,   'd' },
            { "exists",             no_argument,        NULL,   'e' },
            { "file",               no_argument,        NULL,   'f' },
            { "help",               no_argument,        NULL,   'h' },
            { "symlink",            no_argument,        NULL,   'L' },
            { "log-file",           required_argument,  NULL,   'O' },
            { "pipe",               no_argument,        NULL,   'p' },
            { "read",               no_argument,        NULL,   'r' },
            { "repeat",             optional_argument,  NULL,   'R' },
            { "socket",             no_argument,        NULL,   'S' },
            { "version",            no_argument,        NULL,   'V' },
            { "write",              no_argument,        NULL,   'w' },
            { "execute",            no_argument,        NULL,   'x' },
            { NULL, 0, 0, 0 }
        };
        int c;

        c = getopt_long (argc, argv, "bDdefhLO:prR::SVwx", longopts, NULL);
        if (c == -1)
            break;
        switch (c)
        {
            case 'b':
            case 'd':
            case 'e':
            case 'f':
            case 'L':
            case 'p':
            case 'r':
            case 'S':
            case 'w':
            case 'x':
                test = c;
                break;

            case 'D':
                aa_set_double_output (1);
                break;

            case 'h':
                dieusage (RC_OK);

            case 'O':
                aa_set_log_file_or_die (optarg);
                break;

            case 'R':
                if (optarg && !uint0_scan (optarg, &repeat))
                    aa_strerr_diefu2sys (RC_FATAL_USAGE, "set repeat counter to ", optarg);
                else if (!optarg)
                    repeat = 1;
                else
                    ++repeat;
                break;

            case 'V':
                aa_die_version ();

            default:
                dieusage (RC_FATAL_USAGE);
        }
    }
    argc -= optind;
    argv += optind;

    if (argc != 1 || test == 0)
        dieusage (RC_FATAL_USAGE);

again:
    if (lstat (argv[0], &st) < 0)
    {
        if (errno != ENOENT)
            aa_strerr_diefu2sys (RC_FATAL_IO, "stat ", argv[0]);
        else if (repeat >= 1)
        {
            if (repeat > 2)
                --repeat;
            else if (repeat == 2)
                return RC_ST_EXIST;
            sleep (1);
            goto again;
        }
        else
            return RC_ST_EXIST;
    }

    switch (test)
    {
        case 'b':
            return (S_ISBLK (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'd':
            return (S_ISDIR (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'e':
            return 0;

        case 'f':
            return (S_ISREG (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'L':
            return (S_ISLNK (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'p':
            return (S_ISFIFO (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'r':
            mode = R_OK;
            break;

        case 'S':
            return (S_ISSOCK (st.st_mode)) ? RC_OK : RC_ST_FAIL;

        case 'w':
            mode = W_OK;
            break;

        case 'x':
            mode = X_OK;
            break;
    }

    euid = geteuid ();
    if (euid == 0)
    {
        /* root can read/write any file */
        if (mode != X_OK)
            return RC_OK;
        /* and execute anything any execute permission set */
        else if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
            return RC_OK;
        else
            return RC_ST_FAIL;
    }

    if (st.st_uid == euid)
        mode <<= 6;
    else if (is_group_member (st.st_gid))
        mode <<= 3;

    return (st.st_mode & mode) ? RC_OK : RC_ST_FAIL;
}