From: Michael Vogt Date: Wed, 11 Feb 2026 15:01:18 +0000 (+0100) Subject: varlinkctl: add pluggable protocol support to sd-varlink X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b5f87259891334fb0946a8641a8b7d6a3b1767fe;p=thirdparty%2Fsystemd.git varlinkctl: add pluggable protocol support to sd-varlink When sd_varlink_connect_url() gets an unknown URL we now check if there is a `$LIBEXECDIR/varlink-bridges/$scheme` binary and execute it (with the url as the first arguments). This makes varlink more flexible as it provides a way to dynamically add "bridges" in LIBEXECDIR/varlink-bridges/. This is conceptually similar to the libvarlink `varlink --bridge` command and allows to e.g. call varlink over http{,s} via e.g. the new varlink-http-bridge. With a running varlink-http-bridge [0] one can do: ```console $ varlinkctl call http://localhost:8080/ws/sockets/io.systemd.Hostname \ io.systemd.Hostname.Describe {} { "Hostname" : "top", ... ``` Closes: https://github.com/systemd/systemd/issues/40640 [0] https://github.com/mvo5/varlink-http-bridge/pull/1 --- diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index fb2984b0ecb..2de688eb5df 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -760,6 +760,9 @@ Tools using the Varlink protocol (such as `varlinkctl`) or sd-bus (such as would listen on. If set to "-" the tool will turn stdin/stdout into a Varlink connection. +* `$SYSTEMD_VARLINK_BRIDGES_DIR` – overrides the default `$LIBEXEC/varlink-bridges/` + path when looking up custom scheme bridge helper binaries. + `systemd-mountfsd`: * `$SYSTEMD_MOUNTFSD_TRUSTED_DIRECTORIES` – takes a boolean argument. If true diff --git a/man/sd-varlink.xml b/man/sd-varlink.xml index 282d6a330ef..576bdf1b7f8 100644 --- a/man/sd-varlink.xml +++ b/man/sd-varlink.xml @@ -57,6 +57,29 @@ + + Directories + + + + /usr/lib/systemd/varlink-bridges/ + + When sd_varlink_connect_url() encounters a URL with a scheme that is not + natively supported, it looks for a bridge helper binary named after the URL scheme in this + directory. The binary is invoked the same way as exec: binaries but with the full + URL passed as the first command line argument. + + For example, if + varlinkctl introspect https://example.com/ws/sockets/io.systemd.Hostname + is called, varlinkctl will look for an executable + /usr/lib/systemd/varlink-bridges/https and invoke it with + https://example.com/ws/sockets/io.systemd.Hostname as its only + argument. + + + + + See Also diff --git a/meson.build b/meson.build index c0319122f4c..f562754c19b 100644 --- a/meson.build +++ b/meson.build @@ -183,6 +183,7 @@ ntpservicelistdir = libexecdir / 'ntp-units.d' credstoredir = prefixdir / 'lib/credstore' pcrlockdir = prefixdir / 'lib/pcrlock.d' mimepackagesdir = prefixdir / 'share/mime/packages' +varlinkbridgesdir = libexecdir / 'varlink-bridges' configfiledir = get_option('configfiledir') if configfiledir == '' @@ -311,6 +312,7 @@ conf.set_quoted('USER_GENERATOR_DIR', usergeneratordir) conf.set_quoted('USER_KEYRING_PATH', pkgsysconfdir / 'import-pubring.pgp') conf.set_quoted('USER_KEYRING_PATH_LEGACY', pkgsysconfdir / 'import-pubring.gpg') conf.set_quoted('USER_PRESET_DIR', userpresetdir) +conf.set_quoted('VARLINK_BRIDGES_DIR', varlinkbridgesdir) conf.set_quoted('VENDOR_KEYRING_PATH', libexecdir / 'import-pubring.pgp') conf.set('ANSI_OK_COLOR', 'ANSI_' + get_option('ok-color').underscorify().to_upper()) diff --git a/src/libsystemd/sd-varlink/sd-varlink.c b/src/libsystemd/sd-varlink/sd-varlink.c index de1d172762a..32daf27d6a6 100644 --- a/src/libsystemd/sd-varlink/sd-varlink.c +++ b/src/libsystemd/sd-varlink/sd-varlink.c @@ -485,6 +485,14 @@ static int varlink_connect_ssh_exec(sd_varlink **ret, const char *where) { return 0; } +/* Do basic validation of the URL scheme (loosely following RFC 1738) */ +static bool is_valid_url_scheme(const char *s) { + return !isempty(s) && + strchr(LOWERCASE_LETTERS, s[0]) && + in_charset(s, LOWERCASE_LETTERS DIGITS "+.-") && + filename_is_valid(s); +} + _public_ int sd_varlink_connect_url(sd_varlink **ret, const char *url) { _cleanup_free_ char *c = NULL; const char *p; @@ -499,7 +507,7 @@ _public_ int sd_varlink_connect_url(sd_varlink **ret, const char *url) { assert_return(ret, -EINVAL); assert_return(url, -EINVAL); - // FIXME: Maybe add support for vsock: and ssh-exec: URL schemes here. + // FIXME: Maybe add support for vsock: URL schemes here. /* The Varlink URL scheme is a bit underdefined. We support only the spec-defined unix: transport for * now, plus exec:, ssh: transports we made up ourselves. Strictly speaking this shouldn't even be @@ -514,11 +522,39 @@ _public_ int sd_varlink_connect_url(sd_varlink **ret, const char *url) { scheme = SCHEME_SSH_UNIX; else if ((p = startswith(url, "ssh-exec:"))) scheme = SCHEME_SSH_EXEC; - else - return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme not supported."); + else { + /* scheme is not built-in: check if we have a bridge helper binary */ + const char *colon = strchr(url, ':'); + if (!colon) + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), + "Invalid URL '%s': does not contain a ':'", url); + + _cleanup_free_ char *scheme_name = strndup(url, colon - url); + if (!scheme_name) + return log_oom_debug(); + + if (!is_valid_url_scheme(scheme_name)) + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), + "URL scheme not valid as bridge name: %s", scheme_name); + + const char *bridges_dir = secure_getenv("SYSTEMD_VARLINK_BRIDGES_DIR") ?: VARLINK_BRIDGES_DIR; + _cleanup_free_ char *bridge = path_join(bridges_dir, scheme_name); + if (!bridge) + return log_oom_debug(); + + if (access(bridge, X_OK) < 0) { + if (errno == ENOENT) + return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL scheme '%s' not supported (and no auxiliary bridge binary is available).", scheme_name); + + return log_debug_errno(errno, "Failed to look up varlink bridge binary '%s': %m", bridge); + } + + return sd_varlink_connect_exec(ret, bridge, STRV_MAKE(bridge, url)); + } /* The varlink.org reference C library supports more than just file system paths. We might want to - * support that one day too. For now simply refuse that. */ + * support that one day too. For now simply refuse that for our built-in schemes. It is fine for + * external scheme handled via plugins (see above). */ if (p[strcspn(p, ";?#")] != '\0') return log_debug_errno(SYNTHETIC_ERRNO(EPROTONOSUPPORT), "URL parameterization with ';', '?', '#' not supported."); diff --git a/src/varlinkctl/meson.build b/src/varlinkctl/meson.build index c1074dc50a1..0ba12d46cdb 100644 --- a/src/varlinkctl/meson.build +++ b/src/varlinkctl/meson.build @@ -11,3 +11,5 @@ executables += [ 'sources' : varlinkctl_sources, }, ] + +install_emptydir(varlinkbridgesdir)