blob: 08bd4c0bce1f7cd75a2705b123fd34fc038567da [file] [log] [blame] [raw]
/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
Copyright 2016 Daniel Mack
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdio.h>
#include <stdlib.h>
#include "alloc-util.h"
#include "bpf-firewall.h"
#include "extract-word.h"
#include "hostname-util.h"
#include "ip-address-access.h"
#include "parse-util.h"
#include "string-util.h"
int config_parse_ip_address_access(
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) {
IPAddressAccessItem **list = data;
const char *p;
int r;
assert(list);
if (isempty(rvalue)) {
*list = ip_address_access_free_all(*list);
return 0;
}
p = rvalue;
for (;;) {
_cleanup_free_ IPAddressAccessItem *a = NULL;
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, 0);
if (r == 0)
break;
if (r == -ENOMEM)
return log_oom();
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
break;
}
a = new0(IPAddressAccessItem, 1);
if (!a)
return log_oom();
if (streq(word, "any")) {
/* "any" is a shortcut for 0.0.0.0/0 and ::/0 */
a->family = AF_INET;
LIST_APPEND(items, *list, a);
a = new0(IPAddressAccessItem, 1);
if (!a)
return log_oom();
a->family = AF_INET6;
} else if (is_localhost(word)) {
/* "localhost" is a shortcut for 127.0.0.0/8 and ::1/128 */
a->family = AF_INET;
a->address.in.s_addr = htobe32(0x7f000000);
a->prefixlen = 8;
LIST_APPEND(items, *list, a);
a = new0(IPAddressAccessItem, 1);
if (!a)
return log_oom();
a->family = AF_INET6;
a->address.in6 = (struct in6_addr) IN6ADDR_LOOPBACK_INIT;
a->prefixlen = 128;
} else if (streq(word, "link-local")) {
/* "link-local" is a shortcut for 169.254.0.0/16 and fe80::/64 */
a->family = AF_INET;
a->address.in.s_addr = htobe32((UINT32_C(169) << 24 | UINT32_C(254) << 16));
a->prefixlen = 16;
LIST_APPEND(items, *list, a);
a = new0(IPAddressAccessItem, 1);
if (!a)
return log_oom();
a->family = AF_INET6;
a->address.in6 = (struct in6_addr) {
.s6_addr32[0] = htobe32(0xfe800000)
};
a->prefixlen = 64;
} else if (streq(word, "multicast")) {
/* "multicast" is a shortcut for 224.0.0.0/4 and ff00::/8 */
a->family = AF_INET;
a->address.in.s_addr = htobe32((UINT32_C(224) << 24));
a->prefixlen = 4;
LIST_APPEND(items, *list, a);
a = new0(IPAddressAccessItem, 1);
if (!a)
return log_oom();
a->family = AF_INET6;
a->address.in6 = (struct in6_addr) {
.s6_addr32[0] = htobe32(0xff000000)
};
a->prefixlen = 8;
} else {
r = in_addr_prefix_from_string_auto(word, &a->family, &a->address, &a->prefixlen);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Address prefix is invalid, ignoring assignment: %s", word);
return 0;
}
}
LIST_APPEND(items, *list, a);
a = NULL;
}
*list = ip_address_access_reduce(*list);
if (*list) {
r = bpf_firewall_supported();
if (r < 0)
return r;
if (r == 0) {
static bool warned = false;
log_full(warned ? LOG_DEBUG : LOG_WARNING,
"File %s:%u configures an IP firewall (%s=%s), but the local system does not support BPF/cgroup based firewalling.\n"
"Proceeding WITHOUT firewalling in effect! (This warning is only shown for the first loaded unit using IP firewalling.)", filename, line, lvalue, rvalue);
warned = true;
}
}
return 0;
}
IPAddressAccessItem* ip_address_access_free_all(IPAddressAccessItem *first) {
IPAddressAccessItem *next, *p = first;
while (p) {
next = p->items_next;
free(p);
p = next;
}
return NULL;
}
IPAddressAccessItem* ip_address_access_reduce(IPAddressAccessItem *first) {
IPAddressAccessItem *a, *b, *tmp;
int r;
/* Drops all entries from the list that are covered by another entry in full, thus removing all redundant
* entries. */
LIST_FOREACH_SAFE(items, a, tmp, first) {
/* Drop irrelevant bits */
(void) in_addr_mask(a->family, &a->address, a->prefixlen);
LIST_FOREACH(items, b, first) {
if (a == b)
continue;
if (a->family != b->family)
continue;
if (b->prefixlen > a->prefixlen)
continue;
r = in_addr_prefix_covers(b->family,
&b->address,
b->prefixlen,
&a->address);
if (r > 0) {
/* b covers a fully, then let's drop a */
LIST_REMOVE(items, first, a);
free(a);
break;
}
}
}
return first;
}