diff --git a/man/systemd.link.xml b/man/systemd.link.xml index c8d3c513745..487f314396c 100644 --- a/man/systemd.link.xml +++ b/man/systemd.link.xml @@ -537,8 +537,17 @@ <varlistentry> <term><option>secureon</option></term> <listitem> - <para>Enable secureon(tm) password for MagicPacket(tm). - </para> + <para>Enable SecureOn password for MagicPacket. Implied when + <varname>WakeOnLanPassword=</varname> is specified. If specified without + <varname>WakeOnLanPassword=</varname> option, then the password is read from the + credential <literal><replaceable>LINK</replaceable>.link.wol.password</literal> (e.g., + <literal>60-foo.link.wol.password</literal>), and if the credential not found, then + read from <literal>wol.password</literal>. See + <varname>LoadCredential=</varname>/<varname>SetCredential=</varname> in + <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> + for details. The password in the credential, must be 6 bytes in hex format with each + byte separated by a colon (<literal>:</literal>) like an Ethernet MAC address, e.g., + <literal>aa:bb:cc:dd:ee:ff</literal>.</para> </listitem> </varlistentry> </variablelist> @@ -548,6 +557,19 @@ cleared.</para> </listitem> </varlistentry> + <varlistentry> + <term><varname>WakeOnLanPassword=</varname></term> + <listitem> + <para>Specifies the SecureOn password for MagicPacket. Takes an absolute path to a regular + file or an <constant>AF_UNIX</constant> stream socket, or the plain password. When a path to + a regular file is specified, the password is read from it. When an + <constant>AF_UNIX</constant> 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 (<literal>:</literal>) like an Ethernet MAC address, e.g., + <literal>aa:bb:cc:dd:ee:ff</literal>. This implies <varname>WakeOnLan=secureon</varname>. + Defaults to unset, and the current value will not be changed.</para> + </listitem> + </varlistentry> <varlistentry> <term><varname>Port=</varname></term> <listitem> diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c index 00060abff40..ee7be4635fa 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 cc065589317..c57ff1a2b5a 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 04c255ce514..a9c94b48ed9 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 14ca2e032e6..1ce8e471e76 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 38a02a75b90..cf5eccf0939 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 8be2434665a..7586e35b493 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=