Exemplo n.º 1
0
/*
 * Send an error back to the client.  Takes the client struct, the error code,
 * and the message to send and dispatches to the appropriate protocol-specific
 * function.  Returns true on success, false on failure.
 */
bool
server_send_error(struct client *client, enum error_codes error,
                  const char *message)
{
    if (client->protocol > 1)
        return server_v2_send_error(client, error, message);
    else {
        if (client->output != NULL)
            free(client->output);
        client->output = xmalloc(strlen(message) + 1);
        memcpy(client->output, message, strlen(message));
        client->output[strlen(message)] = '\n';
        client->outlen = strlen(message) + 1;
        return server_v1_send_output(client, -1);
    }
}
Exemplo n.º 2
0
/*
 * Find the summary of all commands the user can run against this remctl
 * server.  We do so by checking all configuration lines for any that
 * provide a summary setup that the user can access, then running that
 * line's command with the given summary sub-command.
 *
 * Takes a client object, the user requesting access, and the list of all
 * valid configurations.
 */
static void
server_send_summary(struct client *client, struct config *config)
{
    char *path = NULL;
    char *program;
    struct rule *rule = NULL;
    size_t i;
    char **req_argv = NULL;
    bool ok_any = false;
    int status_all = 0;
    struct process process;
    struct evbuffer *output = NULL;

    /* Create a buffer to hold all the output for protocol version one. */
    if (client->protocol == 1) {
        output = evbuffer_new();
        if (output == NULL)
            die("internal error: cannot create output buffer");
    }

    /*
     * Check each line in the config to find any that are "<command> ALL"
     * lines, the user is authorized to run, and which have a summary field
     * given.
     */
    for (i = 0; i < config->count; i++) {
        memset(&process, 0, sizeof(process));
        process.client = client;
        rule = config->rules[i];
        if (strcmp(rule->subcommand, "ALL") != 0)
            continue;
        if (!server_config_acl_permit(rule, client->user))
            continue;
        if (rule->summary == NULL)
            continue;
        ok_any = true;

        /*
         * Get the real program name, and use it as the first argument in
         * argv passed to the command.  Then add the summary command to the
         * argv and pass off to be executed.
         */
        path = rule->program;
        req_argv = xmalloc(3 * sizeof(char *));
        program = strrchr(path, '/');
        if (program == NULL)
            program = path;
        else
            program++;
        req_argv[0] = program;
        req_argv[1] = rule->summary;
        req_argv[2] = NULL;
        process.command = rule->summary;
        process.argv = req_argv;
        process.rule = rule;
        if (server_process_run(&process)) {
            if (client->protocol == 1)
                if (evbuffer_add_buffer(output, process.output) < 0)
                    die("internal error: cannot copy data from output buffer");
            if (process.status != 0)
                status_all = process.status;
        }
        free(req_argv);
    }

    /*
     * Sets the last process status to 0 if all succeeded, or the last failed
     * exit status if any commands gave non-zero.  Return that we had output
     * successfully if any command gave it.
     */
    if (WIFEXITED(status_all))
        status_all = (int) WEXITSTATUS(process.status);
    else
        status_all = -1;
    if (ok_any) {
        if (client->protocol == 1)
            server_v1_send_output(client, output, status_all);
        else
            server_v2_send_status(client, status_all);
    } else {
        notice("summary request from user %s, but no defined summaries",
               client->user);
        server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
    }
    if (output != NULL)
        evbuffer_free(output);
}
Exemplo n.º 3
0
/*
 * Process an incoming command.  Check the configuration files and the ACL
 * file, and if appropriate, forks off the command.  Takes the argument vector
 * and the user principal, and a buffer into which to put the output from the
 * executable or any error message.  Returns 0 on success and a negative
 * integer on failure.
 *
 * Using the command and the subcommand, the following argument, a lookup in
 * the conf data structure is done to find the command executable and acl
 * file.  If the conf file, and subsequently the conf data structure contains
 * an entry for this command with subcommand equal to "ALL", that is a
 * wildcard match for any given subcommand.  The first argument is then
 * replaced with the actual program name to be executed.
 *
 * After checking the acl permissions, the process forks and the child execv's
 * the command with pipes arranged to gather output. The parent waits for the
 * return code and gathers stdout and stderr pipes.
 */
void
server_run_command(struct client *client, struct config *config,
                   struct iovec **argv)
{
    char *command = NULL;
    char *subcommand = NULL;
    char *helpsubcommand = NULL;
    struct rule *rule = NULL;
    char **req_argv = NULL;
    size_t i;
    bool ok = false;
    bool help = false;
    const char *user = client->user;
    struct process process;

    /* Start with an empty process. */
    memset(&process, 0, sizeof(process));
    process.client = client;

    /*
     * We need at least one argument.  This is also rejected earlier when
     * parsing the command and checking argc, but may as well be sure.
     */
    if (argv[0] == NULL) {
        notice("empty command from user %s", user);
        server_send_error(client, ERROR_BAD_COMMAND, "Invalid command token");
        goto done;
    }

    /* Neither the command nor the subcommand may ever contain nuls. */
    for (i = 0; argv[i] != NULL && i < 2; i++) {
        if (memchr(argv[i]->iov_base, '\0', argv[i]->iov_len)) {
            notice("%s from user %s contains nul octet",
                   (i == 0) ? "command" : "subcommand", user);
            server_send_error(client, ERROR_BAD_COMMAND,
                              "Invalid command token");
            goto done;
        }
    }

    /* We need the command and subcommand as nul-terminated strings. */
    command = xstrndup(argv[0]->iov_base, argv[0]->iov_len);
    if (argv[1] != NULL)
        subcommand = xstrndup(argv[1]->iov_base, argv[1]->iov_len);

    /*
     * Find the program path we need to run.  If we find no matching command
     * at first and the command is a help command, then we either dispatch
     * to the summary command if no specific help was requested, or if a
     * specific help command was listed, check for that in the configuration
     * instead.
     */
    rule = find_config_line(config, command, subcommand);
    if (rule == NULL && strcmp(command, "help") == 0) {

        /* Error if we have more than a command and possible subcommand. */
        if (argv[1] != NULL && argv[2] != NULL && argv[3] != NULL) {
            notice("help command from user %s has more than three arguments",
                   user);
            server_send_error(client, ERROR_TOOMANY_ARGS,
                              "Too many arguments for help command");
        }

        if (subcommand == NULL) {
            server_send_summary(client, config);
            goto done;
        } else {
            help = true;
            if (argv[2] != NULL)
                helpsubcommand = xstrndup(argv[2]->iov_base,
                                          argv[2]->iov_len);
            rule = find_config_line(config, subcommand, helpsubcommand);
        }
    }

    /*
     * Arguments may only contain nuls if they're the argument being passed on
     * standard input.
     */
    for (i = 1; argv[i] != NULL; i++) {
        if (rule != NULL) {
            if (help == false && (long) i == rule->stdin_arg)
                continue;
            if (argv[i + 1] == NULL && rule->stdin_arg == -1)
                continue;
        }
        if (memchr(argv[i]->iov_base, '\0', argv[i]->iov_len)) {
            notice("argument %lu from user %s contains nul octet",
                   (unsigned long) i, user);
            server_send_error(client, ERROR_BAD_COMMAND,
                              "Invalid command token");
            goto done;
        }
    }

    /* Log after we look for command so we can get potentially get logmask. */
    server_log_command(argv, rule, user);

    /*
     * Check the command, aclfile, and the authorization of this client to
     * run this command.
     */
    if (rule == NULL) {
        notice("unknown command %s%s%s from user %s", command,
               (subcommand == NULL) ? "" : " ",
               (subcommand == NULL) ? "" : subcommand, user);
        server_send_error(client, ERROR_UNKNOWN_COMMAND, "Unknown command");
        goto done;
    }
    if (!server_config_acl_permit(rule, user)) {
        notice("access denied: user %s, command %s%s%s", user, command,
               (subcommand == NULL) ? "" : " ",
               (subcommand == NULL) ? "" : subcommand);
        server_send_error(client, ERROR_ACCESS, "Access denied");
        goto done;
    }

    /*
     * Check for a specific command help request with the rule and do error
     * checking and arg massaging.
     */
    if (help) {
        if (rule->help == NULL) {
            notice("command %s from user %s has no defined help",
                   command, user);
            server_send_error(client, ERROR_NO_HELP,
                              "No help defined for command");
            goto done;
        } else {
            free(subcommand);
            subcommand = xstrdup(rule->help);
        }
    }

    /* Assemble the argv for the command we're about to run. */
    if (help)
        req_argv = create_argv_help(rule->program, subcommand, helpsubcommand);
    else
        req_argv = create_argv_command(rule, &process, argv);

    /* Now actually execute the program. */
    process.command = command;
    process.argv = req_argv;
    process.rule = rule;
    ok = server_process_run(&process);
    if (ok) {
        if (WIFEXITED(process.status))
            process.status = (signed int) WEXITSTATUS(process.status);
        else
            process.status = -1;
        if (client->protocol == 1)
            server_v1_send_output(client, process.output, process.status);
        else
            server_v2_send_status(client, process.status);
    }

 done:
    free(command);
    free(subcommand);
    free(helpsubcommand);
    if (req_argv != NULL) {
        for (i = 0; req_argv[i] != NULL; i++)
            free(req_argv[i]);
        free(req_argv);
    }
    if (process.input != NULL)
        evbuffer_free(process.input);
    if (process.output != NULL)
        evbuffer_free(process.output);
}