diff --git a/man/journal-upload.conf.xml b/man/journal-upload.conf.xml index 3de7044fd66..d4dfa11f2b9 100644 --- a/man/journal-upload.conf.xml +++ b/man/journal-upload.conf.xml @@ -129,6 +129,31 @@ Takes a boolean value, enforces using compression without content encoding negotiation. Defaults to false. + + + + Header= + + HTTP header which should be added to each request to URL. + + Header consists of name and value, which are separated by colon. + + Header name can contains alphanumeric values, "_" and "-" symbols additionally. + + This option may be specified more than once, in which case all listed headers will be set. If + the same header name is listed more than once, all its unique values will be concatenated with comma. + Setting Header to empty string clears all previous assignments. + + + Example: + Header=HeaderName: HeaderValue +Header=HeaderName: NewValue +Header=HeaderName: HeaderValue + + + adds HeaderName header with HeaderValue, NewValue to each HTTP request. + + diff --git a/src/journal-remote/journal-header-util.c b/src/journal-remote/journal-header-util.c new file mode 100644 index 00000000000..d25a9d09568 --- /dev/null +++ b/src/journal-remote/journal-header-util.c @@ -0,0 +1,98 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "escape.h" +#include "journal-header-util.h" +#include "string-util.h" +#include "strv.h" + +/* HTTP header name can contains: +- Alphanumeric characters: a-z, A-Z, and 0-9 +- The following special characters: - and _ */ +#define VALID_HEADER_NAME_CHARS \ + ALPHANUMERICAL "_-" + +#define VALID_HEADER_NAME_LENGTH 40 + +#define VALID_HEADER_VALUE_CHARS \ + ALPHANUMERICAL "_ :;.,\\/'\"?!(){}[]@<>=-+*#$&`|~^%" + +static bool header_name_is_valid(const char *e) { + if (!e) + return false; + + if (strlen(e) == 0 || strlen(e) > VALID_HEADER_NAME_LENGTH) + return false; + + return in_charset(e, VALID_HEADER_NAME_CHARS); +} + +static bool header_value_is_valid(const char *e) { + if (!e) + return false; + + return in_charset(e, VALID_HEADER_VALUE_CHARS); +} + +int header_put(OrderedHashmap **headers, const char *name, const char *value) { + assert(headers); + + if (!header_value_is_valid(value)) + return -EINVAL; + + if (!header_name_is_valid(name)) + return -EINVAL; + + return string_strv_ordered_hashmap_put(headers, name, value); +} + +int config_parse_header( + 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) { + + OrderedHashmap **headers = ASSERT_PTR(data); + char *unescaped, *t; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* an empty string clears the previous assignments. */ + *headers = ordered_hashmap_free(*headers); + return 1; + } + + r = cunescape(rvalue, 0, &unescaped); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to unescape headers: %s", rvalue); + return 0; + } + + t = strchr(unescaped, ':'); + if (!t) { + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Failed to parse header, name: value separator was not found, ignoring: %s", unescaped); + return 0; + } + + *t++ = '\0'; + + r = header_put(headers, strstrip(unescaped), skip_leading_chars(t, WHITESPACE)); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, + "Failed to update headers: %s", rvalue); + return 0; + } + + return 1; +} diff --git a/src/journal-remote/journal-header-util.h b/src/journal-remote/journal-header-util.h new file mode 100644 index 00000000000..b0d9bb49f57 --- /dev/null +++ b/src/journal-remote/journal-header-util.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "conf-parser.h" +#include "hashmap.h" + +int header_put(OrderedHashmap **headers, const char *name, const char *value); + +CONFIG_PARSER_PROTOTYPE(config_parse_header); diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index f0af903d8a5..a1d2be8b17e 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -15,11 +15,13 @@ #include "constants.h" #include "daemon-util.h" #include "env-file.h" +#include "escape.h" #include "fd-util.h" #include "fileio.h" #include "format-util.h" #include "fs-util.h" #include "glob-util.h" +#include "journal-header-util.h" #include "journal-upload.h" #include "journal-util.h" #include "log.h" @@ -59,6 +61,7 @@ static int arg_follow = -1; static char *arg_save_state = NULL; static usec_t arg_network_timeout_usec = USEC_INFINITY; static OrderedHashmap *arg_compression = NULL; +static OrderedHashmap *arg_headers = NULL; static bool arg_force_compression = false; STATIC_DESTRUCTOR_REGISTER(arg_url, freep); @@ -72,6 +75,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_machine, freep); STATIC_DESTRUCTOR_REGISTER(arg_namespace, freep); STATIC_DESTRUCTOR_REGISTER(arg_save_state, freep); STATIC_DESTRUCTOR_REGISTER(arg_compression, ordered_hashmap_freep); +STATIC_DESTRUCTOR_REGISTER(arg_headers, ordered_hashmap_freep); static void close_fd_input(Uploader *u); @@ -226,6 +230,23 @@ int start_upload(Uploader *u, h = l; } + char **values; + const char *name; + ORDERED_HASHMAP_FOREACH_KEY(values, name, arg_headers) { + _cleanup_free_ char *joined = strv_join(values, ", "); + if (!joined) + return log_oom(); + + _cleanup_free_ char *header = strjoin(name, ": ", joined); + if (!header) + return log_oom(); + + l = curl_slist_append(h, header); + if (!l) + return log_oom(); + h = l; + } + u->header = TAKE_PTR(h); } @@ -657,6 +678,7 @@ static int parse_config(void) { { "Upload", "ServerCertificateFile", config_parse_path_or_ignore, 0, &arg_cert }, { "Upload", "TrustedCertificateFile", config_parse_path_or_ignore, 0, &arg_trust }, { "Upload", "NetworkTimeoutSec", config_parse_sec, 0, &arg_network_timeout_usec }, + { "Upload", "Header", config_parse_header, 0, &arg_headers }, { "Upload", "Compression", config_parse_compression, /* with_level */ true, &arg_compression }, { "Upload", "ForceCompression", config_parse_bool, 0, &arg_force_compression }, {} diff --git a/src/journal-remote/meson.build b/src/journal-remote/meson.build index 0f3a91a621d..f133e6bbe43 100644 --- a/src/journal-remote/meson.build +++ b/src/journal-remote/meson.build @@ -4,6 +4,7 @@ systemd_journal_upload_sources = files( 'journal-compression-util.c', 'journal-upload-journal.c', 'journal-upload.c', + 'journal-header-util.c', ) libsystemd_journal_remote_sources = files( @@ -90,6 +91,12 @@ executables += [ }, ] +executables += [ + test_template + { + 'sources' : files('test-journal-header-util.c', 'journal-header-util.c'), + }, +] + in_files = [ ['journal-upload.conf', conf.get('ENABLE_REMOTE') == 1 and conf.get('HAVE_LIBCURL') == 1 and install_sysconfdir_samples], diff --git a/src/journal-remote/test-journal-header-util.c b/src/journal-remote/test-journal-header-util.c new file mode 100644 index 00000000000..e88bb164ef7 --- /dev/null +++ b/src/journal-remote/test-journal-header-util.c @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "hashmap.h" +#include "journal-header-util.h" +#include "tests.h" + +TEST(header_put) { + _cleanup_ordered_hashmap_free_ OrderedHashmap *headers = NULL; + + ASSERT_OK_POSITIVE(header_put(&headers, "NewName", "Val")); + ASSERT_OK_POSITIVE(header_put(&headers, "Name", "FirstName")); + ASSERT_OK_POSITIVE(header_put(&headers, "Name", "Override")); + ASSERT_OK_ZERO(header_put(&headers, "Name", "FirstName")); + ASSERT_ERROR(header_put(&headers, "InvalidN@me", "test"), EINVAL); + ASSERT_ERROR(header_put(&headers, "Name", NULL), EINVAL); + ASSERT_ERROR(header_put(&headers, NULL, "Value"), EINVAL); + ASSERT_OK_POSITIVE(header_put(&headers, "Name", "")); + ASSERT_ERROR(header_put(&headers, "", "Value"), EINVAL); +} + +DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/test/units/TEST-04-JOURNAL.journal-remote.sh b/test/units/TEST-04-JOURNAL.journal-remote.sh index df39a50b049..094fb3f4411 100755 --- a/test/units/TEST-04-JOURNAL.journal-remote.sh +++ b/test/units/TEST-04-JOURNAL.journal-remote.sh @@ -272,3 +272,41 @@ EOF rm /run/systemd/journal-upload.conf.d/99-test.conf rm /run/systemd/journal-remote.conf.d/99-test.conf done + +# Let's test sending data with custom headers +echo "$TEST_MESSAGE" | systemd-cat -t "$TEST_TAG" +journalctl --sync + +cat >/run/systemd/journal-remote.conf.d/99-test.conf </run/systemd/journal-upload.conf.d/99-test.conf <