root/usr/src/cmd/acctadm/main.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/acctctl.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <libintl.h>
#include <libdllink.h>
#include <locale.h>
#include <priv.h>
#include <libscf.h>
#include <zone.h>

#include "utils.h"
#include "aconf.h"
#include "res.h"

#define ACCTADM_NET_LOG_INTERVAL        20

static const char USAGE[] = "\
Usage:\n\
    acctadm [ {process | task | flow | net} ]\n\
    acctadm -s\n\
    acctadm -r [ {process | task | flow | net} ]\n\
    acctadm -x|-E|-D {process | task | flow | net}\n\
    acctadm -f filename {process | task | flow | net}\n\
    acctadm -e resources -d resources {process | task | flow | net}\n";

static const char OPTS[] = "rsxf:e:d:ED";

dladm_handle_t dld_handle = NULL;

static void
usage()
{
        (void) fprintf(stderr, gettext(USAGE));
        exit(E_USAGE);
}

static void
setup_privs()
{
        priv_set_t *privset;

        if (seteuid(getuid()) == -1 || setegid(getgid()) == -1)
                die(gettext("seteuid()/setegid() failed"));

        /*
         * Add our privileges and remove unneeded 'basic' privileges from the
         * permitted set.
         */
        if ((privset = priv_str_to_set("basic", ",", NULL)) == NULL)
                die(gettext("cannot setup privileges"));

        (void) priv_addset(privset, PRIV_SYS_ACCT);
        (void) priv_addset(privset, PRIV_FILE_DAC_WRITE);
        (void) priv_addset(privset, PRIV_SYS_DL_CONFIG);
        (void) priv_delset(privset, PRIV_FILE_LINK_ANY);
        (void) priv_delset(privset, PRIV_PROC_EXEC);
        (void) priv_delset(privset, PRIV_PROC_FORK);
        (void) priv_delset(privset, PRIV_PROC_INFO);
        (void) priv_delset(privset, PRIV_PROC_SESSION);
        priv_inverse(privset);
        if (setppriv(PRIV_OFF, PRIV_PERMITTED, privset) == -1)
                die(gettext("cannot setup privileges"));
        priv_freeset(privset);

        /*
         * Clear the Inheritable and Limit sets.
         */
        if ((privset = priv_allocset()) == NULL)
                die(gettext("cannot setup privileges"));
        priv_emptyset(privset);
        if (setppriv(PRIV_SET, PRIV_INHERITABLE, privset) == -1 ||
            setppriv(PRIV_SET, PRIV_LIMIT, privset) == -1)
                die(gettext("cannot setup privileges"));

        /*
         * Turn off the sys_acct, file_dac_write and dl_config privileges
         * until needed.
         */
        (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_WRITE,
            PRIV_SYS_ACCT, PRIV_SYS_DL_CONFIG, NULL);
}

int
main(int argc, char *argv[])
{
        int c;                  /* options character */
        int type = 0;           /* type of accounting */
        int modified = 0;       /* have we modified any properties? */
        acctconf_t ac;          /* current configuration */
        char *typestr = NULL;   /* type of accounting argument string */
        char *enabled = NULL;   /* enabled resources string */
        char *disabled = NULL;  /* disabled resources string */
        char *file = NULL;
        int Eflg = 0;
        int Dflg = 0;
        int rflg = 0;
        int sflg = 0;
        int xflg = 0;
        int optcnt = 0;
        int state;
        const char *fmri;       /* FMRI for this instance */
        int err = 0;

        setup_privs();

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);
        (void) setpname(argv[0]);

        for (; optind < argc; optind++) {
                while ((c = getopt(argc, argv, OPTS)) != (int)EOF) {
                        switch (c) {
                        case 'd':
                                disabled = optarg;
                                break;
                        case 'e':
                                enabled = optarg;
                                break;
                        case 'D':
                                Dflg = 1;
                                optcnt++;
                                break;
                        case 'E':
                                Eflg = 1;
                                optcnt++;
                                break;
                        case 'f':
                                file = optarg;
                                optcnt++;
                                break;
                        case 'r':
                                rflg = 1;
                                optcnt++;
                                break;
                        case 's':
                                sflg = 1;
                                optcnt++;
                                break;
                        case 'x':
                                xflg = 1;
                                optcnt++;
                                break;
                        case '?':
                        default:
                                usage();
                        }
                }

                /*
                 * Permanently give up euid 0, egid 0 and privileges we
                 * don't need for the specified options.
                 */
                if (!(file || sflg)) {
                        if (setreuid(getuid(), getuid()) == -1 ||
                            setregid(getgid(), getgid()) == -1)
                                die(gettext("setreuid()/setregid() failed"));
                        (void) priv_set(PRIV_OFF, PRIV_PERMITTED,
                            PRIV_FILE_DAC_WRITE, NULL);
                }
                if (!(disabled || enabled || Dflg || Eflg || file || sflg ||
                    xflg))
                        (void) priv_set(PRIV_OFF, PRIV_PERMITTED,
                            PRIV_SYS_ACCT, PRIV_SYS_DL_CONFIG, NULL);

                if (optind < argc) {
                        if (typestr != NULL) {
                                warn(gettext("illegal argument -- %s\n"),
                                    argv[optind]);
                                usage();
                        } else {
                                typestr = argv[optind];
                        }
                }
        }
        if (typestr != NULL) {
                if (strcmp(typestr, "process") == 0 ||
                    strcmp(typestr, "proc") == 0)
                        type |= AC_PROC;
                else if (strcmp(typestr, "task") == 0)
                        type |= AC_TASK;
                else if (strcmp(typestr, "flow") == 0)
                        type |= AC_FLOW;
                else if (strcmp(typestr, "net") == 0)
                        type |= AC_NET;
                else {
                        warn(gettext("unknown accounting type -- %s\n"),
                            typestr);
                        usage();
                }
        } else
                type = AC_PROC | AC_TASK | AC_FLOW | AC_NET;

        /*
         * Drop the DL config privilege if we are not working with
         * net.
         */
        if ((type & AC_NET) == 0) {
                (void) priv_set(PRIV_OFF, PRIV_PERMITTED,
                    PRIV_SYS_DL_CONFIG, NULL);
        }
        /*
         * check for invalid options
         */
        if (optcnt > 1)
                usage();

        /*
         * XXX For AC_NET, enabled/disabled should only be "basic" or
         * "extended" - need to check it here.
         */
        if ((enabled || disabled) && (rflg || Dflg || sflg || xflg || Eflg))
                usage();

        if ((file || xflg || Dflg || Eflg || enabled || disabled) &&
            !typestr) {
                warn(gettext("accounting type must be specified\n"));
                usage();
        }

        if (rflg) {
                printgroups(type);
                return (E_SUCCESS);
        }

        /*
         * If no arguments have been passed then just print out the current
         * state and exit.
         */
        if (!enabled && !disabled && !file &&
            !Eflg && !rflg && !Dflg && !sflg && !xflg) {
                aconf_print(stdout, type);
                return (E_SUCCESS);
        }

        /* Open the libdladm handle */
        if (dladm_open(&dld_handle) != DLADM_STATUS_OK)
                die(gettext("failed to open dladm handle\n"));

        /*
         * smf(7) start method.  The FMRI to operate on is retrieved from the
         * SMF_FMRI environment variable that the restarter provides.
         */
        if (sflg) {
                if ((fmri = getenv("SMF_FMRI")) != NULL) {
                        int ret = aconf_setup(fmri);
                        dladm_close(dld_handle);
                        return (ret);
                }

                die(gettext("-s option should only be invoked by smf(7)\n"));
        }

        assert(type == AC_PROC || type == AC_TASK || type == AC_FLOW ||
            type == AC_NET);

        if ((type == AC_FLOW || type == AC_NET) && getzoneid() != GLOBAL_ZONEID)
                die(gettext("%s accounting cannot be configured in "
                    "non-global zones\n"), ac_type_name(type));

        fmri = aconf_type2fmri(type);
        if (aconf_scf_init(fmri) == -1)
                die(gettext("cannot connect to repository for %s\n"), fmri);

        /*
         * Since the sys_acct the privilege allows use of acctctl() regardless
         * of the accounting type, we check the smf(7) authorizations granted
         * to the user to determine whether the user is allowed to change the
         * configuration for this particular accounting type.
         */
        if (!aconf_have_smf_auths())
                die(gettext("insufficient authorization to change %s extended "
                    "accounting configuration\n"), ac_type_name(type));

        if (xflg) {
                /*
                 * Turn off the specified accounting and close its file
                 */

                /*
                 * Stop net logging before turning it off so that the last
                 * set of logs can be written.
                 */
                if (type & AC_NET) {
                        (void) priv_set(PRIV_ON, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        err = dladm_stop_usagelog(dld_handle,
                            DLADM_LOGTYPE_FLOW);
                        (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        if (err != DLADM_STATUS_OK) {
                                die(gettext("failed to stop logging network "
                                    "information, error %d\n"), errno);
                        }
                }
                state = AC_OFF;

                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                if (acctctl(type | AC_STATE_SET, &state, sizeof (int)) == -1)
                        die(gettext("cannot disable %s accounting"),
                            ac_type_name(type));
                if (acctctl(type | AC_FILE_SET, NULL, 0) == -1)
                        die(gettext("cannot close %s accounting file\n"),
                            ac_type_name(type));
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);

                if (aconf_set_bool(AC_PROP_STATE, B_FALSE) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_STATE);
                if (aconf_set_string(AC_PROP_FILE, AC_STR_NONE) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_FILE);
                modified++;
        }

        if (enabled || disabled) {
                char *tracked, *untracked;
                ac_res_t *buf;

                /*
                 * Enable/disable resources
                 */
                if ((buf = malloc(AC_BUFSIZE)) == NULL)
                        die(gettext("not enough memory\n"));
                (void) memset(buf, 0, AC_BUFSIZE);
                if (acctctl(type | AC_RES_GET, buf, AC_BUFSIZE) == -1) {
                        free(buf);
                        die(gettext("cannot obtain list of resources\n"));
                }
                if (disabled) {
                        /*
                         * Stop net logging before turning it off so that the
                         * last set of logs can be written.
                         */
                        if (type & AC_NET) {
                                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE,
                                    PRIV_SYS_DL_CONFIG, NULL);
                                err = dladm_stop_usagelog(dld_handle,
                                    strcmp(disabled, "basic") == 0 ?
                                    DLADM_LOGTYPE_LINK : DLADM_LOGTYPE_FLOW);
                                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE,
                                    PRIV_SYS_DL_CONFIG, NULL);
                                if (err != DLADM_STATUS_OK) {
                                        die(gettext("failed to stop logging "
                                            "network information, error %d\n"),
                                            errno);
                                }
                        }
                        str2buf(buf, disabled, AC_OFF, type);
                } else if (enabled) {
                        str2buf(buf, enabled, AC_ON, type);
                }
                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                if (acctctl(type | AC_RES_SET, buf, AC_BUFSIZE) == -1) {
                        free(buf);
                        die(gettext("cannot enable/disable %s accounting "
                            "resources\n"), ac_type_name(type));
                }
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                tracked = buf2str(buf, AC_BUFSIZE, AC_ON, type);
                untracked = buf2str(buf, AC_BUFSIZE, AC_OFF, type);
                if (aconf_set_string(AC_PROP_TRACKED, tracked) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_TRACKED);
                if (aconf_set_string(AC_PROP_UNTRACKED, untracked) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_UNTRACKED);
                free(tracked);
                free(untracked);
                free(buf);
                modified++;
        }

        if (file) {
                /*
                 * Open new accounting file
                 */
                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                if (open_exacct_file(file, type) == -1) {
                        dladm_close(dld_handle);
                        exit(E_ERROR);
                }
                if (aconf_set_string(AC_PROP_FILE, file) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_FILE);
                state = AC_ON;

                if (acctctl(type | AC_STATE_SET, &state, sizeof (int)) == -1)
                        die(gettext("cannot enable %s accounting"),
                            ac_type_name(type));
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);

                if (aconf_set_bool(AC_PROP_STATE, B_TRUE) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_STATE);
                modified++;
        }

        /*
         * Let's get network logging started. We do this after turning on
         * accounting and opening the file so that we can start writing
         * immediately.
         */
        if (enabled && (type & AC_NET)) {
                /*
                 * Default logging interval for AC_NET is
                 * ACCTADM_NET_LOG_INTERVAL.
                 */
                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE,
                    PRIV_SYS_DL_CONFIG, NULL);
                err = dladm_start_usagelog(dld_handle,
                    strcmp(enabled, "basic") == 0 ?
                    DLADM_LOGTYPE_LINK : DLADM_LOGTYPE_FLOW,
                    ACCTADM_NET_LOG_INTERVAL);
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE,
                    PRIV_SYS_DL_CONFIG, NULL);
                if (err != DLADM_STATUS_OK) {
                        die(gettext("failed to start logging "
                            "network information, error %d\n"),
                            errno);
                }
        }

        if (Dflg) {
                /*
                 * Disable accounting
                 */

                /*
                 * Stop net logging before turning it off so that the last
                 * set of logs can be written.
                 */
                if (type & AC_NET) {
                        (void) priv_set(PRIV_ON, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        err = dladm_stop_usagelog(dld_handle,
                            DLADM_LOGTYPE_FLOW);
                        (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        if (err != DLADM_STATUS_OK) {
                                die(gettext("failed to stop logging "
                                    "network information, error %d\n"), errno);
                        }
                }
                state = AC_OFF;

                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                if (acctctl(type | AC_STATE_SET, &state, sizeof (int)) == -1)
                        die(gettext("cannot disable %s accounting"),
                            ac_type_name(type));
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);

                if (aconf_set_bool(AC_PROP_STATE, B_FALSE) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_STATE);
                modified++;
        }

        if (Eflg) {
                /*
                 * Enable accounting
                 */

                /*
                 * Let's get network logging started.
                 */
                if (type & AC_NET) {
                        /*
                         * Default logging interval for AC_NET is
                         * ACCTADM_NET_LOG_INTERVAL.
                         */
                        (void) priv_set(PRIV_ON, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        err = dladm_start_usagelog(dld_handle,
                            DLADM_LOGTYPE_FLOW, ACCTADM_NET_LOG_INTERVAL);
                        (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE,
                            PRIV_SYS_DL_CONFIG, NULL);
                        if (err != DLADM_STATUS_OK) {
                                die(gettext("failed to start logging "
                                    "network information, error %d\n"), errno);
                        }
                }
                state = AC_ON;

                (void) priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);
                if (acctctl(type | AC_STATE_SET, &state, sizeof (int)) == -1)
                        die(gettext("cannot enable %s accounting"),
                            ac_type_name(type));
                (void) priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_SYS_ACCT, NULL);

                if (aconf_set_bool(AC_PROP_STATE, B_TRUE) == -1)
                        die(gettext("cannot update %s property\n"),
                            AC_PROP_STATE);
                modified++;
        }
        (void) priv_set(PRIV_OFF, PRIV_PERMITTED, PRIV_SYS_ACCT, NULL);

        if (modified) {
                char *smf_state;

                if (aconf_save() == -1)
                        die(gettext("cannot save %s accounting "
                            "configuration\n"), ac_type_name(type));

                /*
                 * Enable or disable the instance depending on the effective
                 * configuration.  If the effective configuration results in
                 * extended accounting being 'on', the instance is enabled so
                 * the configuration is applied at the next boot.
                 */
                smf_state = smf_get_state(fmri);
                aconf_init(&ac, type);

                if (ac.state == AC_ON ||
                    strcmp(ac.file, AC_STR_NONE) != 0 ||
                    strcmp(ac.tracked, AC_STR_NONE) != 0) {
                        if (strcmp(smf_state, SCF_STATE_STRING_ONLINE) != 0)
                                if (smf_enable_instance(fmri, 0) == -1)
                                        die(gettext("cannot enable %s\n"),
                                            fmri);
                } else {
                        if (strcmp(smf_state, SCF_STATE_STRING_ONLINE) == 0)
                                if (smf_disable_instance(fmri, 0) == -1)
                                        die(gettext("cannot disable %s\n"),
                                            fmri);
                }
                free(smf_state);
        }
        aconf_scf_fini();
        dladm_close(dld_handle);
        return (E_SUCCESS);
}