--- /dev/null
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1+
+#
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+import sys
+import subprocess
+
+EX_DATAERR = 65 # from sysexits.h
+
+exe = sys.argv[1]
+
+def test_invalid_line(line):
+ print('Running {} on {!r}'.format(exe, line))
+ c = subprocess.run([exe, '--create', '-'],
+ input=line, stdout=subprocess.PIPE, universal_newlines=True)
+ assert c.returncode == EX_DATAERR, c
+
+if __name__ == '__main__':
+ test_invalid_line('asdfa')
+ test_invalid_line('f "open quote')
+ test_invalid_line('f closed quote""')
+ test_invalid_line('Y /unknown/letter')
+ test_invalid_line('w non/absolute/path')
+ test_invalid_line('s') # s is for short
+ test_invalid_line('f!! /too/many/bangs')
+ test_invalid_line('f++ /too/many/plusses')
+ test_invalid_line('f+!+ /too/many/plusses')
+ test_invalid_line('f!+! /too/many/bangs')
+ #test_invalid_line('w /unresolved/argument - - - - "%Y"')
+ #test_invalid_line('w /unresolved/argument/sandwich - - - - "%v%Y%v"')
+ #test_invalid_line('w /unresolved/filename/%Y - - - - "whatever"')
+ #test_invalid_line('w /unresolved/filename/sandwich/%v%Y%v - - - - "whatever"')
+ test_invalid_line('w - - - - - "no file specfied"')
+ test_invalid_line('C - - - - - "no file specfied"')
+ test_invalid_line('C non/absolute/path - - - - -')
+ test_invalid_line('b - - - - - -')
+ test_invalid_line('b 1234 - - - - -')
+ test_invalid_line('c - - - - - -')
+ test_invalid_line('c 1234 - - - - -')
+ test_invalid_line('t - - -')
+ test_invalid_line('T - - -')
+ test_invalid_line('a - - -')
+ test_invalid_line('A - - -')
+ test_invalid_line('h - - -')
+ test_invalid_line('H - - -')
#include <string.h>
#include <sys/stat.h>
#include <sys/xattr.h>
+#include <sysexits.h>
#include <time.h>
#include <unistd.h>
return 0;
}
-static int parse_line(const char *fname, unsigned line, const char *buffer) {
+static int parse_line(const char *fname, unsigned line, const char *buffer, bool *invalid_config) {
_cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL;
_cleanup_(item_free_contents) Item i = {};
&group,
&age,
NULL);
- if (r < 0)
+ if (r < 0) {
+ if (r == -EINVAL) /* invalid quoting and such */
+ *invalid_config = true;
return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line);
+ }
+
else if (r < 2) {
+ *invalid_config = true;
log_error("[%s:%u] Syntax error.", fname, line);
return -EIO;
}
}
if (isempty(action)) {
+ *invalid_config = true;
log_error("[%s:%u] Command too short '%s'.", fname, line, action);
return -EINVAL;
}
else if (action[pos] == '+' && !force)
force = true;
else {
+ *invalid_config = true;
log_error("[%s:%u] Unknown modifiers in command '%s'",
fname, line, action);
return -EINVAL;
r = specifier_printf(path, specifier_table, NULL, &i.path);
if (r == -ENOKEY)
return log_unresolvable_specifier(fname, line);
- if (r < 0)
+ if (r < 0) {
+ /* ENOMEM is the only return value left after ENOKEY,
+ * so *invalid_config should not be set. */
return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path);
+ }
switch (i.type) {
case WRITE_FILE:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Write file requires argument.", fname, line);
return -EBADMSG;
}
if (!i.argument)
return log_oom();
} else if (!path_is_absolute(i.argument)) {
+ *invalid_config = true;
log_error("[%s:%u] Source path is not absolute.", fname, line);
return -EBADMSG;
}
unsigned major, minor;
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Device file requires argument.", fname, line);
return -EBADMSG;
}
if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) {
+ *invalid_config = true;
log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument);
return -EBADMSG;
}
case SET_XATTR:
case RECURSIVE_SET_XATTR:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set extended attribute requires argument.", fname, line);
return -EBADMSG;
}
case SET_ACL:
case RECURSIVE_SET_ACL:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set ACLs requires argument.", fname, line);
return -EBADMSG;
}
case SET_ATTRIBUTE:
case RECURSIVE_SET_ATTRIBUTE:
if (!i.argument) {
+ *invalid_config = true;
log_error("[%s:%u] Set file attribute requires argument.", fname, line);
return -EBADMSG;
}
r = parse_attribute_from_arg(&i);
+ if (r == -EINVAL)
+ *invalid_config = true;
if (r < 0)
return r;
break;
default:
log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type);
+ *invalid_config = true;
return -EBADMSG;
}
if (!path_is_absolute(i.path)) {
log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path);
+ *invalid_config = true;
return -EBADMSG;
}
r = get_user_creds(&u, &i.uid, NULL, NULL, NULL);
if (r < 0) {
- log_error("[%s:%u] Unknown user '%s'.", fname, line, user);
- return r;
+ *invalid_config = true;
+ return log_error_errno(r, "[%s:%u] Unknown user '%s'.", fname, line, user);
}
i.uid_set = true;
r = get_group_creds(&g, &i.gid);
if (r < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Unknown group '%s'.", fname, line, group);
return r;
}
}
if (parse_mode(mm, &m) < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode);
return -EBADMSG;
}
}
if (parse_sec(a, &i.age) < 0) {
+ *invalid_config = true;
log_error("[%s:%u] Invalid age '%s'.", fname, line, age);
return -EBADMSG;
}
" --boot Execute actions only safe at boot\n"
" --prefix=PATH Only apply rules with the specified prefix\n"
" --exclude-prefix=PATH Ignore rules with the specified prefix\n"
- " --root=PATH Operate on an alternate filesystem root\n",
- program_invocation_short_name);
+ " --root=PATH Operate on an alternate filesystem root\n"
+ , program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
return 1;
}
-static int read_config_file(const char *fn, bool ignore_enoent) {
+static int read_config_file(const char *fn, bool ignore_enoent, bool *invalid_config) {
_cleanup_fclose_ FILE *_f = NULL;
FILE *f;
char line[LINE_MAX];
FOREACH_LINE(line, f, break) {
char *l;
int k;
+ bool invalid_line = false;
v++;
if (IN_SET(*l, 0, '#'))
continue;
- k = parse_line(fn, v, l);
- if (k < 0 && r == 0)
- r = k;
+ k = parse_line(fn, v, l, &invalid_line);
+ if (k < 0) {
+ if (invalid_line)
+ /* Allow reporting with a special code if the caller requested this */
+ *invalid_config = true;
+ else if (r == 0)
+ /* The first error becomes our return value */
+ r = k;
+ }
}
/* we have to determine age parameter for each entry of type X */
int r, k;
ItemArray *a;
Iterator iterator;
+ bool invalid_config = false;
r = parse_argv(argc, argv);
if (r <= 0)
int j;
for (j = optind; j < argc; j++) {
- k = read_config_file(argv[j], false);
+ k = read_config_file(argv[j], false, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
}
STRV_FOREACH(f, files) {
- k = read_config_file(*f, true);
+ k = read_config_file(*f, true, &invalid_config);
if (k < 0 && r == 0)
r = k;
}
mac_selinux_finish();
- return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+ if (r < 0)
+ return EXIT_FAILURE;
+ else if (invalid_config)
+ return EX_DATAERR;
+ else
+ return EXIT_SUCCESS;
}