diff --git a/man/systemd.link.xml b/man/systemd.link.xml
index c8d3c51374..487f314396 100644
--- a/man/systemd.link.xml
+++ b/man/systemd.link.xml
@@ -537,8 +537,17 @@
- Enable secureon(tm) password for MagicPacket(tm).
-
+ Enable SecureOn password for MagicPacket. Implied when
+ WakeOnLanPassword= is specified. If specified without
+ WakeOnLanPassword= option, then the password is read from the
+ credential LINK.link.wol.password (e.g.,
+ 60-foo.link.wol.password), and if the credential not found, then
+ read from wol.password. See
+ LoadCredential=/SetCredential= in
+ systemd.exec1
+ for details. The password in the credential, must be 6 bytes in hex format with each
+ byte separated by a colon (:) like an Ethernet MAC address, e.g.,
+ aa:bb:cc:dd:ee:ff.
@@ -548,6 +557,19 @@
cleared.
+
+ WakeOnLanPassword=
+
+ Specifies the SecureOn password for MagicPacket. Takes an absolute path to a regular
+ file or an AF_UNIX stream socket, or the plain password. When a path to
+ a regular file is specified, the password is read from it. When an
+ AF_UNIX stream socket is specified, a connection is made to it and the
+ password is read from it. The password must be 6 bytes in hex format with each byte separated
+ by a colon (:) like an Ethernet MAC address, e.g.,
+ aa:bb:cc:dd:ee:ff. This implies WakeOnLan=secureon.
+ Defaults to unset, and the current value will not be changed.
+
+
Port=
diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c
index 00060abff4..ee7be4635f 100644
--- a/src/shared/ethtool-util.c
+++ b/src/shared/ethtool-util.c
@@ -43,6 +43,11 @@ int wol_options_to_string_alloc(uint32_t opts, char **ret) {
assert(ret);
+ if (opts == UINT32_MAX) {
+ *ret = NULL;
+ return 0;
+ }
+
for (size_t i = 0; i < ELEMENTSOF(wol_option_map); i++)
if (opts & wol_option_map[i].opt &&
!strextend_with_separator(&str, ",", wol_option_map[i].name))
@@ -55,7 +60,7 @@ int wol_options_to_string_alloc(uint32_t opts, char **ret) {
}
*ret = TAKE_PTR(str);
- return 0;
+ return 1;
}
static const char* const port_table[] = {
@@ -395,7 +400,12 @@ int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct et
dest = _v; \
} while(false)
-int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts) {
+int ethtool_set_wol(
+ int *ethtool_fd,
+ const char *ifname,
+ uint32_t wolopts,
+ const uint8_t password[SOPASS_MAX]) {
+
struct ethtool_wolinfo ecmd = {
.cmd = ETHTOOL_GWOL,
};
@@ -408,7 +418,8 @@ int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts) {
assert(ethtool_fd);
assert(ifname);
- if (wolopts == UINT32_MAX)
+ if (wolopts == UINT32_MAX && !password)
+ /* Nothing requested. Return earlier. */
return 0;
r = ethtool_connect(ethtool_fd);
@@ -420,16 +431,47 @@ int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts) {
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
return -errno;
+ if (wolopts == UINT32_MAX) {
+ /* When password is specified without valid WoL options specified, then enable
+ * WAKE_MAGICSECURE flag if supported. */
+ wolopts = ecmd.wolopts;
+ if (password && FLAGS_SET(ecmd.supported, WAKE_MAGICSECURE))
+ wolopts |= WAKE_MAGICSECURE;
+ }
+
+ if ((wolopts & ~ecmd.supported) != 0) {
+ _cleanup_free_ char *str = NULL;
+
+ (void) wol_options_to_string_alloc(wolopts & ~ecmd.supported, &str);
+ log_debug("Network interface %s does not support requested Wake on LAN option(s) \"%s\", ignoring.",
+ ifname, strna(str));
+
+ wolopts &= ecmd.supported;
+ }
+
+ if (!FLAGS_SET(wolopts, WAKE_MAGICSECURE))
+ /* When WAKE_MAGICSECURE flag is not set, then ignore password. */
+ password = NULL;
+
UPDATE(ecmd.wolopts, wolopts, need_update);
+ if (password &&
+ memcmp(ecmd.sopass, password, sizeof(ecmd.sopass)) != 0) {
+ memcpy(ecmd.sopass, password, sizeof(ecmd.sopass));
+ need_update = true;
+ }
- if (!need_update)
+ if (!need_update) {
+ explicit_bzero_safe(&ecmd, sizeof(ecmd));
return 0;
+ }
+ r = 0;
ecmd.cmd = ETHTOOL_SWOL;
if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0)
- return -errno;
+ r = -errno;
- return 0;
+ explicit_bzero_safe(&ecmd, sizeof(ecmd));
+ return r;
}
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) {
diff --git a/src/shared/ethtool-util.h b/src/shared/ethtool-util.h
index cc06558931..c57ff1a2b5 100644
--- a/src/shared/ethtool-util.h
+++ b/src/shared/ethtool-util.h
@@ -164,7 +164,7 @@ int ethtool_get_link_info(int *ethtool_fd, const char *ifname,
int *ret_autonegotiation, uint64_t *ret_speed,
Duplex *ret_duplex, NetDevPort *ret_port);
int ethtool_get_permanent_macaddr(int *ethtool_fd, const char *ifname, struct ether_addr *ret);
-int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts);
+int ethtool_set_wol(int *ethtool_fd, const char *ifname, uint32_t wolopts, const uint8_t password[SOPASS_MAX]);
int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring);
int ethtool_set_features(int *ethtool_fd, const char *ifname, const int features[static _NET_DEV_FEAT_MAX]);
int ethtool_set_glinksettings(int *ethtool_fd, const char *ifname,
diff --git a/src/udev/net/link-config-gperf.gperf b/src/udev/net/link-config-gperf.gperf
index 04c255ce51..a9c94b48ed 100644
--- a/src/udev/net/link-config-gperf.gperf
+++ b/src/udev/net/link-config-gperf.gperf
@@ -49,6 +49,7 @@ Link.BitsPerSecond, config_parse_si_uint64,
Link.Duplex, config_parse_duplex, 0, offsetof(LinkConfig, duplex)
Link.AutoNegotiation, config_parse_tristate, 0, offsetof(LinkConfig, autonegotiation)
Link.WakeOnLan, config_parse_wol, 0, offsetof(LinkConfig, wol)
+Link.WakeOnLanPassword, config_parse_wol_password, 0, 0
Link.Port, config_parse_port, 0, offsetof(LinkConfig, port)
Link.ReceiveChecksumOffload, config_parse_tristate, 0, offsetof(LinkConfig, features[NET_DEV_FEAT_RXCSUM])
Link.TransmitChecksumOffload, config_parse_tristate, 0, offsetof(LinkConfig, features[NET_DEV_FEAT_TXCSUM])
diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c
index 14ca2e032e..1ce8e471e7 100644
--- a/src/udev/net/link-config.c
+++ b/src/udev/net/link-config.c
@@ -10,11 +10,13 @@
#include "alloc-util.h"
#include "conf-files.h"
#include "conf-parser.h"
+#include "creds-util.h"
#include "def.h"
#include "device-private.h"
#include "device-util.h"
#include "ethtool-util.h"
#include "fd-util.h"
+#include "fileio.h"
#include "link-config.h"
#include "log.h"
#include "memory-util.h"
@@ -56,6 +58,8 @@ static LinkConfig* link_config_free(LinkConfig *link) {
strv_free(link->alternative_names);
free(link->alternative_names_policy);
free(link->alias);
+ free(link->wol_password_file);
+ erase_and_free(link->wol_password);
return mfree(link);
}
@@ -101,6 +105,108 @@ int link_config_ctx_new(LinkConfigContext **ret) {
return 0;
}
+static int link_parse_wol_password(LinkConfig *link, const char *str) {
+ _cleanup_(erase_and_freep) uint8_t *p = NULL;
+ int r;
+
+ assert(link);
+ assert(str);
+
+ assert_cc(sizeof(struct ether_addr) == SOPASS_MAX);
+
+ p = new(uint8_t, SOPASS_MAX);
+ if (!p)
+ return -ENOMEM;
+
+ /* Reuse ether_addr_from_string(), as their formats are equivalent. */
+ r = ether_addr_from_string(str, (struct ether_addr*) p);
+ if (r < 0)
+ return r;
+
+ erase_and_free(link->wol_password);
+ link->wol_password = TAKE_PTR(p);
+ return 0;
+}
+
+static int link_read_wol_password_from_file(LinkConfig *link) {
+ _cleanup_(erase_and_freep) char *password = NULL;
+ int r;
+
+ assert(link);
+
+ if (!link->wol_password_file)
+ return 0;
+
+ r = read_full_file_full(
+ AT_FDCWD, link->wol_password_file, UINT64_MAX, SIZE_MAX,
+ READ_FULL_FILE_SECURE | READ_FULL_FILE_WARN_WORLD_READABLE | READ_FULL_FILE_CONNECT_SOCKET,
+ NULL, &password, NULL);
+ if (r < 0)
+ return r;
+
+ return link_parse_wol_password(link, password);
+}
+
+static int link_read_wol_password_from_cred(LinkConfig *link) {
+ _cleanup_free_ char *base = NULL, *cred_name = NULL;
+ _cleanup_(erase_and_freep) char *password = NULL;
+ int r;
+
+ assert(link);
+ assert(link->filename);
+
+ if (link->wol == UINT32_MAX)
+ return 0; /* WakeOnLan= is not specified. */
+ if (!FLAGS_SET(link->wol, WAKE_MAGICSECURE))
+ return 0; /* secureon is not specified in WakeOnLan=. */
+ if (link->wol_password)
+ return 0; /* WakeOnLanPassword= is specified. */
+ if (link->wol_password_file)
+ return 0; /* a file name is specified in WakeOnLanPassword=, but failed to read it. */
+
+ r = path_extract_filename(link->filename, &base);
+ if (r < 0)
+ return r;
+
+ cred_name = strjoin(base, ".wol.password");
+ if (!cred_name)
+ return -ENOMEM;
+
+ r = read_credential(cred_name, (void**) &password, NULL);
+ if (r == -ENOENT)
+ r = read_credential("wol.password", (void**) &password, NULL);
+ if (r < 0)
+ return r;
+
+ return link_parse_wol_password(link, password);
+}
+
+static int link_adjust_wol_options(LinkConfig *link) {
+ int r;
+
+ assert(link);
+
+ r = link_read_wol_password_from_file(link);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ log_warning_errno(r, "Failed to read WakeOnLan password from %s, ignoring: %m", link->wol_password_file);
+
+ r = link_read_wol_password_from_cred(link);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0)
+ log_warning_errno(r, "Failed to read WakeOnLan password from credential, ignoring: %m");
+
+ if (link->wol != UINT32_MAX && link->wol_password)
+ /* Enable WAKE_MAGICSECURE flag when WakeOnLanPassword=. Note that when
+ * WakeOnLanPassword= is set without WakeOnLan=, then ethtool_set_wol() enables
+ * WAKE_MAGICSECURE flag and other flags are not changed. */
+ link->wol |= WAKE_MAGICSECURE;
+
+ return 0;
+}
+
int link_load_one(LinkConfigContext *ctx, const char *filename) {
_cleanup_(link_config_freep) LinkConfig *link = NULL;
_cleanup_free_ char *name = NULL;
@@ -177,6 +283,10 @@ int link_load_one(LinkConfigContext *ctx, const char *filename) {
link->mac = mfree(link->mac);
}
+ r = link_adjust_wol_options(link);
+ if (r < 0)
+ return r;
+
log_debug("Parsed configuration file %s", filename);
LIST_PREPEND(links, ctx->links, TAKE_PTR(link));
@@ -329,13 +439,13 @@ static int link_config_apply_ethtool_settings(int *ethtool_fd, const LinkConfig
port_to_string(config->port));
}
- r = ethtool_set_wol(ethtool_fd, name, config->wol);
+ r = ethtool_set_wol(ethtool_fd, name, config->wol, config->wol_password);
if (r < 0) {
_cleanup_free_ char *str = NULL;
(void) wol_options_to_string_alloc(config->wol, &str);
- log_device_warning_errno(device, r, "Could not set WakeOnLan to %s, ignoring: %m",
- strna(str));
+ log_device_warning_errno(device, r, "Could not set WakeOnLan%s%s, ignoring: %m",
+ isempty(str) ? "" : " to ", strempty(str));
}
r = ethtool_set_features(ethtool_fd, name, config->features);
@@ -782,6 +892,52 @@ int config_parse_txqueuelen(
return 0;
}
+int config_parse_wol_password(
+ 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) {
+
+ LinkConfig *link = userdata;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(userdata);
+
+ if (isempty(rvalue)) {
+ link->wol_password = erase_and_free(link->wol_password);
+ link->wol_password_file = mfree(link->wol_password_file);
+ return 0;
+ }
+
+ if (path_is_absolute(rvalue) && path_is_safe(rvalue)) {
+ link->wol_password = erase_and_free(link->wol_password);
+ return free_and_strdup_warn(&link->wol_password_file, rvalue);
+ }
+
+ warn_file_is_world_accessible(filename, NULL, unit, line);
+
+ r = link_parse_wol_password(link, rvalue);
+ if (r == -ENOMEM)
+ return log_oom();
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to parse %s=, ignoring assignment: %s.", lvalue, rvalue);
+ return 0;
+ }
+
+ link->wol_password_file = mfree(link->wol_password_file);
+ return 0;
+}
+
static const char* const mac_address_policy_table[_MAC_ADDRESS_POLICY_MAX] = {
[MAC_ADDRESS_POLICY_PERSISTENT] = "persistent",
[MAC_ADDRESS_POLICY_RANDOM] = "random",
diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h
index 38a02a75b9..cf5eccf093 100644
--- a/src/udev/net/link-config.h
+++ b/src/udev/net/link-config.h
@@ -58,6 +58,8 @@ struct LinkConfig {
int autonegotiation;
uint32_t advertise[N_ADVERTISE];
uint32_t wol;
+ char *wol_password_file;
+ uint8_t *wol_password;
NetDevPort port;
int features[_NET_DEV_FEAT_MAX];
netdev_channels channels;
@@ -97,6 +99,7 @@ const struct ConfigPerfItem* link_config_gperf_lookup(const char *key, GPERF_LEN
CONFIG_PARSER_PROTOTYPE(config_parse_ifalias);
CONFIG_PARSER_PROTOTYPE(config_parse_rx_tx_queues);
CONFIG_PARSER_PROTOTYPE(config_parse_txqueuelen);
+CONFIG_PARSER_PROTOTYPE(config_parse_wol_password);
CONFIG_PARSER_PROTOTYPE(config_parse_mac_address_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_name_policy);
CONFIG_PARSER_PROTOTYPE(config_parse_alternative_names_policy);
diff --git a/test/fuzz/fuzz-link-parser/directives.link b/test/fuzz/fuzz-link-parser/directives.link
index 8be2434665..7586e35b49 100644
--- a/test/fuzz/fuzz-link-parser/directives.link
+++ b/test/fuzz/fuzz-link-parser/directives.link
@@ -28,6 +28,7 @@ BitsPerSecond=
Duplex=
AutoNegotiation=
WakeOnLan=
+WakeOnLanPassword=
Port=
ReceiveChecksumOffload=
TransmitChecksumOffload=