1
0
mirror of https://github.com/systemd/systemd.git synced 2024-11-01 17:51:22 +03:00

core: add support for StandardInputFile= and friends

These new settings permit specifiying arbitrary paths as
stdin/stdout/stderr locations. We try to open/create them as necessary.
Some special magic is applied:

1) if the same path is specified for both input and output/stderr, we'll
   open it only once O_RDWR, and duplicate them fd instead.

2) If we an AF_UNIX socket path is specified, we'll connect() to it,
   rather than open() it. This allows invoking systemd services with
   stdin/stdout/stderr connected to arbitrary foreign service sockets.

Fixes: #3991
This commit is contained in:
Lennart Poettering 2017-10-27 16:09:57 +02:00
parent 0664775c84
commit 2038c3f584
5 changed files with 263 additions and 62 deletions

View File

@ -1853,6 +1853,50 @@ int bus_exec_context_set_transient_property(
return 1;
} else if (STR_IN_SET(name, "StandardInputFile", "StandardOutputFile", "StandardErrorFile")) {
const char *s;
r = sd_bus_message_read(message, "s", &s);
if (r < 0)
return r;
if (!path_is_absolute(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute", s);
if (!path_is_safe(s))
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not normalized", s);
if (mode != UNIT_CHECK) {
if (streq(name, "StandardInputFile")) {
r = free_and_strdup(&c->stdio_file[STDIN_FILENO], s);
if (r < 0)
return r;
c->std_input = EXEC_INPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardInput=file:%s", s);
} else if (streq(name, "StandardOutputFile")) {
r = free_and_strdup(&c->stdio_file[STDOUT_FILENO], s);
if (r < 0)
return r;
c->std_output = EXEC_OUTPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardOutput=file:%s", s);
} else {
assert(streq(name, "StandardErrorFile"));
r = free_and_strdup(&c->stdio_file[STDERR_FILENO], s);
if (r < 0)
return r;
c->std_error = EXEC_OUTPUT_FILE;
unit_write_drop_in_private_format(u, mode, name, "StandardError=file:%s", s);
}
}
return 1;
} else if (streq(name, "StandardInputData")) {
const void *p;
size_t sz;

View File

@ -390,6 +390,53 @@ static int open_terminal_as(const char *path, int flags, int nfd) {
return move_fd(fd, nfd, false);
}
static int acquire_path(const char *path, int flags, mode_t mode) {
union sockaddr_union sa = {
.sa.sa_family = AF_UNIX,
};
int fd, r;
assert(path);
if (IN_SET(flags & O_ACCMODE, O_WRONLY, O_RDWR))
flags |= O_CREAT;
fd = open(path, flags|O_NOCTTY, mode);
if (fd >= 0)
return fd;
if (errno != ENXIO) /* ENXIO is returned when we try to open() an AF_UNIX file system socket on Linux */
return -errno;
if (strlen(path) > sizeof(sa.un.sun_path)) /* Too long, can't be a UNIX socket */
return -ENXIO;
/* So, it appears the specified path could be an AF_UNIX socket. Let's see if we can connect to it. */
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0)
return -errno;
strncpy(sa.un.sun_path, path, sizeof(sa.un.sun_path));
if (connect(fd, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0) {
safe_close(fd);
return errno == EINVAL ? -ENXIO : -errno; /* Propagate initial error if we get EINVAL, i.e. we have
* indication that his wasn't an AF_UNIX socket after all */
}
if ((flags & O_ACCMODE) == O_RDONLY)
r = shutdown(fd, SHUT_WR);
else if ((flags & O_ACCMODE) == O_WRONLY)
r = shutdown(fd, SHUT_RD);
else
return fd;
if (r < 0) {
safe_close(fd);
return -errno;
}
return fd;
}
static int fixup_input(
const ExecContext *context,
int socket_fd,
@ -489,6 +536,22 @@ static int setup_input(
return move_fd(fd, STDIN_FILENO, false);
}
case EXEC_INPUT_FILE: {
bool rw;
int fd;
assert(context->stdio_file[STDIN_FILENO]);
rw = (context->std_output == EXEC_OUTPUT_FILE && streq_ptr(context->stdio_file[STDIN_FILENO], context->stdio_file[STDOUT_FILENO])) ||
(context->std_error == EXEC_OUTPUT_FILE && streq_ptr(context->stdio_file[STDIN_FILENO], context->stdio_file[STDERR_FILENO]));
fd = acquire_path(context->stdio_file[STDIN_FILENO], rw ? O_RDWR : O_RDONLY, 0666 & ~context->umask);
if (fd < 0)
return fd;
return move_fd(fd, STDIN_FILENO, false);
}
default:
assert_not_reached("Unknown input type");
}
@ -625,6 +688,25 @@ static int setup_output(
(void) fd_nonblock(named_iofds[fileno], false);
return dup2(named_iofds[fileno], fileno) < 0 ? -errno : fileno;
case EXEC_OUTPUT_FILE: {
bool rw;
int fd;
assert(context->stdio_file[fileno]);
rw = context->std_input == EXEC_INPUT_FILE &&
streq_ptr(context->stdio_file[fileno], context->stdio_file[STDIN_FILENO]);
if (rw)
return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno;
fd = acquire_path(context->stdio_file[fileno], O_WRONLY, 0666 & ~context->umask);
if (fd < 0)
return fd;
return move_fd(fd, fileno, false);
}
default:
assert_not_reached("Unknown error type");
}
@ -3507,8 +3589,10 @@ void exec_context_done(ExecContext *c) {
for (l = 0; l < ELEMENTSOF(c->rlimit); l++)
c->rlimit[l] = mfree(c->rlimit[l]);
for (l = 0; l < 3; l++)
for (l = 0; l < 3; l++) {
c->stdio_fdname[l] = mfree(c->stdio_fdname[l]);
c->stdio_file[l] = mfree(c->stdio_file[l]);
}
c->working_directory = mfree(c->working_directory);
c->root_directory = mfree(c->root_directory);
@ -4648,6 +4732,7 @@ static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
[EXEC_INPUT_SOCKET] = "socket",
[EXEC_INPUT_NAMED_FD] = "fd",
[EXEC_INPUT_DATA] = "data",
[EXEC_INPUT_FILE] = "file",
};
DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput);
@ -4664,6 +4749,7 @@ static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = {
[EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console",
[EXEC_OUTPUT_SOCKET] = "socket",
[EXEC_OUTPUT_NAMED_FD] = "fd",
[EXEC_OUTPUT_FILE] = "file",
};
DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput);

View File

@ -55,6 +55,7 @@ typedef enum ExecInput {
EXEC_INPUT_SOCKET,
EXEC_INPUT_NAMED_FD,
EXEC_INPUT_DATA,
EXEC_INPUT_FILE,
_EXEC_INPUT_MAX,
_EXEC_INPUT_INVALID = -1
} ExecInput;
@ -71,6 +72,7 @@ typedef enum ExecOutput {
EXEC_OUTPUT_JOURNAL_AND_CONSOLE,
EXEC_OUTPUT_SOCKET,
EXEC_OUTPUT_NAMED_FD,
EXEC_OUTPUT_FILE,
_EXEC_OUTPUT_MAX,
_EXEC_OUTPUT_INVALID = -1
} ExecOutput;
@ -165,6 +167,7 @@ struct ExecContext {
ExecOutput std_output;
ExecOutput std_error;
char *stdio_fdname[3];
char *stdio_file[3];
void *stdin_data;
size_t stdin_data_size;

View File

@ -847,7 +847,9 @@ int config_parse_exec_input(
void *userdata) {
ExecContext *c = data;
const char *name;
Unit *u = userdata;
const char *n;
ExecInput ei;
int r;
assert(data);
@ -855,31 +857,55 @@ int config_parse_exec_input(
assert(line);
assert(rvalue);
name = startswith(rvalue, "fd:");
if (name) {
/* Strip prefix and validate fd name */
if (!fdname_is_valid(name)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name);
return 0;
n = startswith(rvalue, "fd:");
if (n) {
_cleanup_free_ char *resolved = NULL;
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (isempty(resolved))
resolved = mfree(resolved);
else if (!fdname_is_valid(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name: %s", resolved);
return -EINVAL;
}
r = free_and_strdup(&c->stdio_fdname[STDIN_FILENO], name);
free_and_replace(c->stdio_fdname[STDIN_FILENO], resolved);
ei = EXEC_INPUT_NAMED_FD;
} else if ((n = startswith(rvalue, "file:"))) {
_cleanup_free_ char *resolved = NULL;
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_oom();
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (!path_is_absolute(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires an absolute path name: %s", resolved);
return -EINVAL;
}
if (!path_is_safe(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires a normalized path name: %s", resolved);
return -EINVAL;
}
free_and_replace(c->stdio_file[STDIN_FILENO], resolved);
ei = EXEC_INPUT_FILE;
c->std_input = EXEC_INPUT_NAMED_FD;
} else {
ExecInput ei;
ei = exec_input_from_string(rvalue);
if (ei < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse input specifier, ignoring: %s", rvalue);
return 0;
}
c->std_input = ei;
}
c->std_input = ei;
return 0;
}
@ -915,22 +941,18 @@ int config_parse_exec_input_text(
}
r = cunescape(rvalue, 0, &unescaped);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode C escaped text, ignoring: %s", rvalue);
return 0;
}
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode C escaped text: %s", rvalue);
r = unit_full_printf(u, unescaped, &resolved);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", unescaped);
return 0;
}
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers: %s", unescaped);
sz = strlen(resolved);
if (c->stdin_data_size + sz + 1 < c->stdin_data_size || /* check for overflow */
c->stdin_data_size + sz + 1 > EXEC_STDIN_DATA_MAX) {
log_syntax(unit, LOG_ERR, filename, line, r, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return 0;
log_syntax(unit, LOG_ERR, filename, line, 0, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return -E2BIG;
}
p = realloc(c->stdin_data, c->stdin_data_size + sz + 1);
@ -983,17 +1005,15 @@ int config_parse_exec_input_data(
delete_chars(cleaned, WHITESPACE);
r = unbase64mem(cleaned, (size_t) -1, &p, &sz);
if (r < 0) {
log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode base64 data, ignoring: %s", cleaned);
return 0;
}
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to decode base64 data, ignoring: %s", cleaned);
assert(sz > 0);
if (c->stdin_data_size + sz < c->stdin_data_size || /* check for overflow */
c->stdin_data_size + sz > EXEC_STDIN_DATA_MAX) {
log_syntax(unit, LOG_ERR, filename, line, r, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return 0;
log_syntax(unit, LOG_ERR, filename, line, 0, "Standard input data too large (%zu), maximum of %zu permitted, ignoring.", c->stdin_data_size + sz, (size_t) EXEC_STDIN_DATA_MAX);
return -E2BIG;
}
q = realloc(c->stdin_data, c->stdin_data_size + sz);
@ -1008,19 +1028,23 @@ int config_parse_exec_input_data(
return 0;
}
int config_parse_exec_output(const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
int config_parse_exec_output(
const char *unit,
const char *filename,
unsigned line,
const char *section,
unsigned section_line,
const char *lvalue,
int ltype,
const char *rvalue,
void *data,
void *userdata) {
_cleanup_free_ char *resolved = NULL;
const char *n;
ExecContext *c = data;
Unit *u = userdata;
ExecOutput eo;
const char *name;
int r;
assert(data);
@ -1029,38 +1053,67 @@ int config_parse_exec_output(const char *unit,
assert(lvalue);
assert(rvalue);
name = startswith(rvalue, "fd:");
if (name) {
/* Strip prefix and validate fd name */
if (!fdname_is_valid(name)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", name);
return 0;
n = startswith(rvalue, "fd:");
if (n) {
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (isempty(resolved))
resolved = mfree(resolved);
else if (!fdname_is_valid(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name: %s", resolved);
return -EINVAL;
}
eo = EXEC_OUTPUT_NAMED_FD;
} else if ((n = startswith(rvalue, "file:"))) {
r = unit_full_printf(u, n, &resolved);
if (r < 0)
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s: %m", n);
if (!path_is_absolute(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires an absolute path name: %s", resolved);
return -EINVAL;
}
if (!path_is_safe(resolved)) {
log_syntax(unit, LOG_ERR, filename, line, 0, "file: requires a normalized path name, ignoring: %s", resolved);
return -EINVAL;
}
eo = EXEC_OUTPUT_FILE;
} else {
eo = exec_output_from_string(rvalue);
if (eo == _EXEC_OUTPUT_INVALID) {
if (eo < 0) {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output specifier, ignoring: %s", rvalue);
return 0;
}
}
if (streq(lvalue, "StandardOutput")) {
if (eo == EXEC_OUTPUT_NAMED_FD)
free_and_replace(c->stdio_fdname[STDOUT_FILENO], resolved);
else
free_and_replace(c->stdio_file[STDOUT_FILENO], resolved);
c->std_output = eo;
r = free_and_strdup(&c->stdio_fdname[STDOUT_FILENO], name);
if (r < 0)
log_oom();
return r;
} else if (streq(lvalue, "StandardError")) {
c->std_error = eo;
r = free_and_strdup(&c->stdio_fdname[STDERR_FILENO], name);
if (r < 0)
log_oom();
return r;
} else {
log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse output property, ignoring: %s", lvalue);
return 0;
assert(streq(lvalue, "StandardError"));
if (eo == EXEC_OUTPUT_NAMED_FD)
free_and_replace(c->stdio_fdname[STDERR_FILENO], resolved);
else
free_and_replace(c->stdio_file[STDERR_FILENO], resolved);
c->std_error = eo;
}
return 0;
}
int config_parse_exec_io_class(const char *unit,

View File

@ -275,6 +275,22 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
r = sd_bus_message_append(m, "sv", "TasksMax", "t", t);
goto finish;
} else if (STR_IN_SET(field, "StandardInput", "StandardOutput", "StandardError")) {
const char *n, *appended;
n = startswith(eq, "fd:");
if (n) {
appended = strjoina(field, "FileDescriptorName");
r = sd_bus_message_append(m, "sv", appended, "s", n);
} else if ((n = startswith(eq, "file:"))) {
appended = strjoina(field, "File");
r = sd_bus_message_append(m, "sv", appended, "s", n);
} else
r = sd_bus_message_append(m, "sv", field, "s", eq);
goto finish;
} else if (streq(field, "StandardInputText")) {
_cleanup_free_ char *unescaped = NULL;
@ -387,7 +403,6 @@ int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignmen
} else if (STR_IN_SET(field,
"User", "Group", "DevicePolicy", "KillMode",
"UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath",
"StandardInput", "StandardOutput", "StandardError",
"Description", "Slice", "Type", "WorkingDirectory",
"RootDirectory", "SyslogIdentifier", "ProtectSystem",
"ProtectHome", "SELinuxContext", "Restart", "RootImage",