/* * Calls server_config_acl_permit with the given user identity and anonymous * set to false and returns the result. Wrapped in a function so that we can * cobble up a client struct. */ static bool acl_permit(const struct rule *rule, const char *user) { struct client client = { -1, -1, NULL, NULL, 0, NULL, (char *) user, false, 0, 0, false, false, NULL, NULL, NULL }; return server_config_acl_permit(rule, &client); }
/* * Calls server_config_acl_permit with the anonymous identity and anonymous * set to true. * * Heimdal's KRB5_WELLKNOWN_NAME and related constants expand to a literal in * parentheses, which means they cannot be concatenated by the preprocessor * and the string cannot be constructed at compile time. */ static bool acl_permit_anonymous(const struct rule *rule) { static char *pname = NULL; struct client client = { -1, -1, NULL, NULL, 0, NULL, NULL, true, 0, 0, false, false, NULL, NULL, NULL }; if (pname == NULL) basprintf(&pname, "%s/%s@%s", KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, KRB5_ANON_REALM); client.user = pname; return server_config_acl_permit(rule, &client); }
int main(void) { const char *acls[5]; const struct rule rule = { (char *) "TEST", 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, NULL, 0, 0, NULL, NULL, NULL }; plan(2); errors_capture(); acls[0] = "localgroup:foobargroup"; acls[1] = NULL; ok(!server_config_acl_permit(&rule, "*****@*****.**"), "localgroup ACL check fails"); is_string("TEST:0: ACL scheme 'localgroup' is not supported\n", errors, "...with not supported error"); free(errors); return 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); }
/* * 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); }