]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
shared: add parser for SocketBind{Allow|Deny}=
authorJulia Kartseva <hex@fb.com>
Tue, 29 Jun 2021 23:04:32 +0000 (16:04 -0700)
committerJulia Kartseva <hex@fb.com>
Wed, 30 Jun 2021 07:36:26 +0000 (00:36 -0700)
Parse address family, ip protocol and ports, any of them can be
optional. If neither is specified, a special value 'any' is expected.
Helper is placed in shared to be reused in both fragment and dbus.
Add unit tests with valid and invalid examples.

src/shared/meson.build
src/shared/parse-socket-bind-item.c [new file with mode: 0644]
src/shared/parse-socket-bind-item.h [new file with mode: 0644]
src/test/meson.build
src/test/test-parse-socket-bind-item.c [new file with mode: 0644]

index 5008cda500a1619d0facb96acaf6c1af23323e50..7eb7050062c56d8aad067f1833018edaaea64a29 100644 (file)
@@ -225,6 +225,8 @@ shared_sources = files('''
         pager.h
         parse-argument.c
         parse-argument.h
+        parse-socket-bind-item.c
+        parse-socket-bind-item.h
         pe-header.h
         pkcs11-util.c
         pkcs11-util.h
diff --git a/src/shared/parse-socket-bind-item.c b/src/shared/parse-socket-bind-item.c
new file mode 100644 (file)
index 0000000..214abd8
--- /dev/null
@@ -0,0 +1,146 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include "af-list.h"
+#include "extract-word.h"
+#include "ip-protocol-list.h"
+#include "parse-socket-bind-item.h"
+#include "parse-util.h"
+
+static int parse_af_token(
+                const char *token,
+                int *family,
+                int *ip_protocol,
+                uint16_t *nr_ports,
+                uint16_t *port_min) {
+        int af;
+
+        assert(token);
+        assert(family);
+
+        af = af_from_ipv4_ipv6(token);
+        if (af == AF_UNSPEC)
+                return -EINVAL;
+
+        *family = af;
+        return 0;
+}
+
+static int parse_ip_protocol_token(
+                const char *token,
+                int *family,
+                int *ip_protocol,
+                uint16_t *nr_ports,
+                uint16_t *port_min) {
+        int proto;
+
+        assert(token);
+        assert(ip_protocol);
+
+        proto = ip_protocol_from_tcp_udp(token);
+        if (proto < 0)
+                return -EINVAL;
+
+        *ip_protocol = proto;
+        return 0;
+}
+
+static int parse_ip_ports_token(
+                const char *token,
+                int *family,
+                int *ip_protocol,
+                uint16_t *nr_ports,
+                uint16_t *port_min) {
+        assert(token);
+        assert(nr_ports);
+        assert(port_min);
+
+        if (streq(token, "any"))
+                *nr_ports = *port_min = 0;
+        else {
+                uint16_t mn = 0, mx = 0;
+                int r = parse_ip_port_range(token, &mn, &mx);
+                if (r < 0)
+                        return r;
+
+                *nr_ports = mx - mn + 1;
+                *port_min = mn;
+        }
+
+        return 0;
+}
+
+typedef int (*parse_token_f)(
+                const char *,
+                int *,
+                int *,
+                uint16_t *,
+                uint16_t *);
+
+int parse_socket_bind_item(
+                const char *str,
+                int *address_family,
+                int *ip_protocol,
+                uint16_t *nr_ports,
+                uint16_t *port_min) {
+        /* Order of token parsers is important. */
+        const parse_token_f parsers[] = {
+                &parse_af_token,
+                &parse_ip_protocol_token,
+                &parse_ip_ports_token,
+        };
+        parse_token_f const *parser_ptr = parsers;
+        int af = AF_UNSPEC, proto = 0, r;
+        uint16_t nr = 0, mn = 0;
+        const char *p = str;
+
+        assert(str);
+        assert(address_family);
+        assert(ip_protocol);
+        assert(nr_ports);
+        assert(port_min);
+
+        if (isempty(p))
+                return -EINVAL;
+
+        for (;;) {
+                _cleanup_free_ char *token = NULL;
+
+                r = extract_first_word(&p, &token, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
+                if (r == 0)
+                        break;
+                if (r < 0)
+                        return r;
+
+                if (isempty(token))
+                        return -EINVAL;
+
+                while (parser_ptr != parsers + ELEMENTSOF(parsers)) {
+                        r = (*parser_ptr)(token, &af, &proto, &nr, &mn);
+                        if (r == -ENOMEM)
+                                return r;
+
+                        ++parser_ptr;
+                        /* Continue to next token if parsing succeeded,
+                         * otherwise apply next parser to the same token.
+                         */
+                        if (r >= 0)
+                                break;
+                }
+                if (parser_ptr == parsers + ELEMENTSOF(parsers))
+                                break;
+        }
+
+        /* Failed to parse a token. */
+        if (r < 0)
+                return r;
+
+        /* Parsers applied succesfully, but end of the string not reached. */
+        if (p)
+                return -EINVAL;
+
+        *address_family = af;
+        *ip_protocol = proto;
+        *nr_ports = nr;
+        *port_min = mn;
+        return 0;
+}
diff --git a/src/shared/parse-socket-bind-item.h b/src/shared/parse-socket-bind-item.h
new file mode 100644 (file)
index 0000000..c8cff8d
--- /dev/null
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#pragma once
+
+#include <stdint.h>
+
+int parse_socket_bind_item(
+        const char *str,
+        int *address_family,
+        int *ip_protocol,
+        uint16_t *nr_ports,
+        uint16_t *port_min);
index 4999697804b9afc5d5da358bd6fafdac784c3557..e106059182c7b448dfb4588b7b54129fc2df2471 100644 (file)
@@ -242,6 +242,8 @@ tests += [
 
         [['src/test/test-parse-argument.c']],
 
+        [['src/test/test-parse-socket-bind-item.c']],
+
         [['src/test/test-parse-util.c']],
 
         [['src/test/test-sysctl-util.c']],
diff --git a/src/test/test-parse-socket-bind-item.c b/src/test/test-parse-socket-bind-item.c
new file mode 100644 (file)
index 0000000..6d52582
--- /dev/null
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <linux/in.h>
+#include <sys/socket.h>
+#include <stdio.h>
+
+#include "macro.h"
+#include "parse-socket-bind-item.h"
+
+static void test_valid_item(
+                const char *str,
+                int expected_af,
+                int expected_ip_protocol,
+                uint16_t expected_nr_ports,
+                uint16_t expected_port_min) {
+        uint16_t nr_ports, port_min;
+        int af, ip_protocol;
+
+        assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) >= 0);
+        assert_se(af == expected_af);
+        assert_se(ip_protocol == expected_ip_protocol);
+        assert_se(nr_ports == expected_nr_ports);
+        assert_se(port_min == expected_port_min);
+
+        log_info("%s: \"%s\" ok", __func__, str);
+}
+
+static void test_invalid_item(const char *str) {
+        uint16_t nr_ports, port_min;
+        int af, ip_protocol;
+
+        assert_se(parse_socket_bind_item(str, &af, &ip_protocol, &nr_ports, &port_min) == -EINVAL);
+
+        log_info("%s: \"%s\" ok", __func__, str);
+}
+
+int main(int argc, char *argv[]) {
+        test_valid_item("any", AF_UNSPEC, 0, 0, 0);
+        test_valid_item("ipv4", AF_INET, 0, 0, 0);
+        test_valid_item("ipv6", AF_INET6, 0, 0, 0);
+        test_valid_item("ipv4:any", AF_INET, 0, 0, 0);
+        test_valid_item("ipv6:any", AF_INET6, 0, 0, 0);
+        test_valid_item("tcp", AF_UNSPEC, IPPROTO_TCP, 0, 0);
+        test_valid_item("udp", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+        test_valid_item("tcp:any", AF_UNSPEC, IPPROTO_TCP, 0, 0);
+        test_valid_item("udp:any", AF_UNSPEC, IPPROTO_UDP, 0, 0);
+        test_valid_item("6666", AF_UNSPEC, 0, 1, 6666);
+        test_valid_item("6666-6667", AF_UNSPEC, 0, 2, 6666);
+        test_valid_item("65535", AF_UNSPEC, 0, 1, 65535);
+        test_valid_item("1-65535", AF_UNSPEC, 0, 65535, 1);
+        test_valid_item("ipv4:tcp", AF_INET, IPPROTO_TCP, 0, 0);
+        test_valid_item("ipv4:udp", AF_INET, IPPROTO_UDP, 0, 0);
+        test_valid_item("ipv6:tcp", AF_INET6, IPPROTO_TCP, 0, 0);
+        test_valid_item("ipv6:udp", AF_INET6, IPPROTO_UDP, 0, 0);
+        test_valid_item("ipv4:6666", AF_INET, 0, 1, 6666);
+        test_valid_item("ipv6:6666", AF_INET6, 0, 1, 6666);
+        test_valid_item("tcp:6666", AF_UNSPEC, IPPROTO_TCP, 1, 6666);
+        test_valid_item("udp:6666", AF_UNSPEC, IPPROTO_UDP, 1, 6666);
+        test_valid_item("ipv4:tcp:6666", AF_INET, IPPROTO_TCP, 1, 6666);
+        test_valid_item("ipv6:tcp:6666", AF_INET6, IPPROTO_TCP, 1, 6666);
+        test_valid_item("ipv6:udp:6666-6667", AF_INET6, IPPROTO_UDP, 2, 6666);
+        test_valid_item("ipv6:tcp:any", AF_INET6, IPPROTO_TCP, 0, 0);
+
+        test_invalid_item("");
+        test_invalid_item(":");
+        test_invalid_item("::");
+        test_invalid_item("any:");
+        test_invalid_item("meh");
+        test_invalid_item("zupa:meh");
+        test_invalid_item("zupa:meh:eh");
+        test_invalid_item("ip");
+        test_invalid_item("dccp");
+        test_invalid_item("ipv6meh");
+        test_invalid_item("ipv6::");
+        test_invalid_item("ipv6:ipv6");
+        test_invalid_item("ipv6:icmp");
+        test_invalid_item("ipv6:tcp:0");
+        test_invalid_item("65536");
+        test_invalid_item("0-65535");
+        test_invalid_item("ipv6:tcp:6666-6665");
+        test_invalid_item("ipv6:tcp:6666-100000");
+        test_invalid_item("ipv6::6666");
+        test_invalid_item("ipv6:tcp:any:");
+        test_invalid_item("ipv6:tcp:any:ipv6");
+        test_invalid_item("ipv6:tcp:6666:zupa");
+        test_invalid_item("ipv6:tcp:6666:any");
+        test_invalid_item("ipv6:tcp:6666 zupa");
+        test_invalid_item("ipv6:tcp:6666: zupa");
+        test_invalid_item("ipv6:tcp:6666\n zupa");
+        return 0;
+}