/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@netfilter.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include /* assert */ #include /* errno */ #include /* ULLONG_MAX */ #include /* getservbyname, getaddrinfo */ #include /* strtoull, etc. */ #include /* getaddrinfo */ #include /* getaddrinfo, AF_ */ #include /* ETH_ALEN */ #include /* IFNAMSIZ */ #include /* IPPROTO_ */ #include /* D() */ #include /* IPSET_OPT_* */ #include /* name_to_icmp */ #include /* name_to_icmpv6 */ #include /* prefixlen_netmask_map */ #include /* ipset_err */ #include /* ipset_type_get */ #include /* string utilities */ #include /* prototypes */ #include "../config.h" #ifndef ULLONG_MAX #define ULLONG_MAX 18446744073709551615ULL #endif /* Parse input data */ #define cidr_separator(str) ipset_strchr(str, IPSET_CIDR_SEPARATOR) #define range_separator(str) ipset_strchr(str, IPSET_RANGE_SEPARATOR) #define elem_separator(str) ipset_strchr(str, IPSET_ELEM_SEPARATOR) #define name_separator(str) ipset_strchr(str, IPSET_NAME_SEPARATOR) #define proto_separator(str) ipset_strchr(str, IPSET_PROTO_SEPARATOR) #define syntax_err(fmt, args...) \ ipset_err(session, "Syntax error: " fmt , ## args) #define syntax_err_ll(errtype, fmt, args...) \ ipset_session_report(session, errtype, "Syntax error: " fmt , ## args) static char * ipset_strchr(const char *str, const char *sep) { char *match; assert(str); assert(sep); for (; *sep != '\0'; sep++) { match = strchr(str, sep[0]); if (match != NULL && str[0] != sep[0] && str[strlen(str)-1] != sep[0]) return match; } return NULL; } static char * escape_range_separator(const char *str) { const char *tmp = NULL; if (STRNEQ(str, IPSET_ESCAPE_START, 1)) { tmp = strstr(str, IPSET_ESCAPE_END); if (tmp == NULL) return NULL; } return range_separator(tmp == NULL ? str : tmp); } /* * Parser functions, shamelessly taken from iptables.c, ip6tables.c * and parser.c from libnetfilter_conntrack. */ /* * Parse numbers */ static int string_to_number_ll(struct ipset_session *session, const char *str, unsigned long long min, unsigned long long max, unsigned long long *ret, enum ipset_err_type errtype) { unsigned long long number = 0; char *end; /* Handle hex, octal, etc. */ errno = 0; number = strtoull(str, &end, 0); if (*end == '\0' && end != str && errno != ERANGE) { /* we parsed a number, let's see if we want this */ if (min <= number && (!max || number <= max)) { *ret = number; return 0; } else errno = ERANGE; } if (errno == ERANGE && max) return syntax_err_ll(errtype, "'%s' is out of range %llu-%llu", str, min, max); else if (errno == ERANGE) return syntax_err_ll(errtype, "'%s' is out of range %llu-%llu", str, min, ULLONG_MAX); else return syntax_err_ll(errtype, "'%s' is invalid as number", str); } static int string_to_u8(struct ipset_session *session, const char *str, uint8_t *ret) { int err; unsigned long long num = 0; err = string_to_number_ll(session, str, 0, 255, &num, IPSET_ERROR); *ret = num; return err; } static int string_to_cidr(struct ipset_session *session, const char *str, uint8_t min, uint8_t max, uint8_t *ret) { int err = string_to_u8(session, str, ret); if (!err && (*ret < min || *ret > max)) return syntax_err("'%s' is out of range %u-%u", str, min, max); return err; } static int string_to_u16(struct ipset_session *session, const char *str, uint16_t *ret, enum ipset_err_type errtype) { int err; unsigned long long num = 0; err = string_to_number_ll(session, str, 0, USHRT_MAX, &num, errtype); *ret = num; return err; } static int string_to_u32(struct ipset_session *session, const char *str, uint32_t *ret) { int err; unsigned long long num = 0; err = string_to_number_ll(session, str, 0, UINT_MAX, &num, IPSET_ERROR); *ret = num; return err; } /** * ipset_parse_ether - parse ethernet address * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an ethernet address. The parsed ethernet * address is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_ether(struct ipset_session *session, enum ipset_opt opt, const char *str) { size_t len, p = 0, i = 0; unsigned char ether[ETH_ALEN]; assert(session); assert(opt == IPSET_OPT_ETHER); assert(str); len = strlen(str); if (len > ETH_ALEN * 3 - 1) goto error; for (i = 0; i < ETH_ALEN; i++) { long number; char *end; number = strtol(str + p, &end, 16); p = end - str + 1; if (((*end == ':' && i < ETH_ALEN - 1) || (*end == '\0' && i == ETH_ALEN - 1)) && number >= 0 && number <= 255) ether[i] = number; else goto error; } return ipset_session_data_set(session, opt, ether); error: return syntax_err("cannot parse '%s' as ethernet address", str); } static char * ipset_strdup(struct ipset_session *session, const char *str) { char *tmp = strdup(str); if (tmp == NULL) ipset_err(session, "Cannot allocate memory to duplicate %s.", str); return tmp; } static char * find_range_separator(struct ipset_session *session, char *str) { char *esc; if (STRNEQ(str, IPSET_ESCAPE_START, 1)) { esc = strstr(str, IPSET_ESCAPE_END); if (esc == NULL) { syntax_err("cannot find closing escape character " "'%s' in %s", IPSET_ESCAPE_END, str); return str; } if (esc[1] == '\0') /* No range separator, just a single escaped elem */ return NULL; esc++; if (!STRNEQ(esc, IPSET_RANGE_SEPARATOR, 1)) { *esc = '\0'; syntax_err("range separator expected after " "'%s'", str); return str; } return esc; } return range_separator(str); } static char * strip_escape(struct ipset_session *session, char *str) { if (STRNEQ(str, IPSET_ESCAPE_START, 1)) { if (!STREQ(str + strlen(str) - 1, IPSET_ESCAPE_END)) { syntax_err("cannot find closing escape character " "'%s' in %s", IPSET_ESCAPE_END, str); return NULL; } str++; str[strlen(str) - 1] = '\0'; } return str; } /* * Parse TCP service names or port numbers */ static int parse_portname(struct ipset_session *session, const char *str, uint16_t *port, const char *proto) { char *saved, *tmp; struct servent *service; saved = tmp = ipset_strdup(session, str); if (tmp == NULL) return -1; tmp = strip_escape(session, tmp); if (tmp == NULL) goto error; service = getservbyname(tmp, proto); if (service != NULL) { *port = ntohs((uint16_t) service->s_port); free(saved); return 0; } error: free(saved); return ipset_warn(session, "cannot parse '%s' as a %s port", str, proto); } /** * ipset_parse_single_port - parse a single port number or name * @session: session structure * @opt: option kind of the data * @str: string to parse * @proto: protocol * * Parse string as a single port number or name. The parsed port * number is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_port(struct ipset_session *session, enum ipset_opt opt, const char *str, const char *proto) { uint16_t port; assert(session); assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); assert(str); if (string_to_u16(session, str, &port, IPSET_WARNING) == 0) { return ipset_session_data_set(session, opt, &port); } /* Error is stored as warning in session report */ if (parse_portname(session, str, &port, proto) == 0) { /* No error, so reset false error messages */ ipset_session_report_reset(session); return ipset_session_data_set(session, opt, &port); } /* Restore warning as error */ return ipset_session_warning_as_error(session); } /** * ipset_parse_mark - parse a mark * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a mark. The parsed mark number is * stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_mark(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint32_t mark; int err; assert(session); assert(str); if ((err = string_to_u32(session, str, &mark)) == 0) err = ipset_session_data_set(session, opt, &mark); if (!err) /* No error, so reset false error messages! */ ipset_session_report_reset(session); return err; } /** * ipset_parse_tcpudp_port - parse TCP/UDP port name, number, or range of them * @session: session structure * @opt: option kind of the data * @str: string to parse * @proto: TCP|UDP * * Parse string as a TCP/UDP port name or number or range of them * separated by a dash. The parsed port numbers are stored * in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_tcpudp_port(struct ipset_session *session, enum ipset_opt opt, const char *str, const char *proto) { char *a, *saved, *tmp; int err = 0; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); saved = tmp = ipset_strdup(session, str); if (tmp == NULL) return -1; a = find_range_separator(session, tmp); if (a == tmp) { err = -1; goto error; } if (a != NULL) { /* port-port */ *a++ = '\0'; err = ipset_parse_port(session, IPSET_OPT_PORT_TO, a, proto); if (err) goto error; } err = ipset_parse_port(session, opt, tmp, proto); error: free(saved); return err; } /** * ipset_parse_tcp_port - parse TCP port name, number, or range of them * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a TCP port name or number or range of them * separated by a dash. The parsed port numbers are stored * in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_tcp_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { return ipset_parse_tcpudp_port(session, opt, str, "tcp"); } /** * ipset_parse_single_tcp_port - parse TCP port name or number * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a single TCP port name or number. * The parsed port number is stored * in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_single_tcp_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); assert(str); return ipset_parse_port(session, opt, str, "tcp"); } /** * ipset_parse_proto - parse protocol name * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a protocol name. * The parsed protocol is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_proto(struct ipset_session *session, enum ipset_opt opt, const char *str) { const struct protoent *protoent; uint8_t proto = 0; assert(session); assert(opt == IPSET_OPT_PROTO); assert(str); protoent = getprotobyname(strcasecmp(str, "icmpv6") == 0 ? "ipv6-icmp" : str); if (protoent == NULL) { uint8_t protonum = 0; if (string_to_u8(session, str, &protonum) || (protoent = getprotobynumber(protonum)) == NULL) return syntax_err("cannot parse '%s' " "as a protocol", str); } proto = protoent->p_proto; if (!proto) return syntax_err("Unsupported protocol '%s'", str); return ipset_session_data_set(session, opt, &proto); } /* Parse ICMP and ICMPv6 type/code */ static int parse_icmp_typecode(struct ipset_session *session, enum ipset_opt opt, const char *str, const char *family) { uint16_t typecode; uint8_t type, code; char *a, *saved, *tmp; int err; saved = tmp = ipset_strdup(session, str); if (tmp == NULL) return -1; a = cidr_separator(tmp); if (a == NULL) { free(saved); return ipset_err(session, "Cannot parse %s as an %s type/code.", str, family); } *a++ = '\0'; if ((err = string_to_u8(session, tmp, &type)) != 0 || (err = string_to_u8(session, a, &code)) != 0) goto error; typecode = (type << 8) | code; err = ipset_session_data_set(session, opt, &typecode); error: free(saved); return err; } /** * ipset_parse_icmp - parse an ICMP name or type/code * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an ICMP name or type/code numbers. * The parsed ICMP type/code is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_icmp(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint16_t typecode; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); if (name_to_icmp(str, &typecode) < 0) return parse_icmp_typecode(session, opt, str, "ICMP"); return ipset_session_data_set(session, opt, &typecode); } /** * ipset_parse_icmpv6 - parse an ICMPv6 name or type/code * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an ICMPv6 name or type/code numbers. * The parsed ICMPv6 type/code is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_icmpv6(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint16_t typecode; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); if (name_to_icmpv6(str, &typecode) < 0) return parse_icmp_typecode(session, opt, str, "ICMPv6"); return ipset_session_data_set(session, opt, &typecode); } /** * ipset_parse_proto_port - parse (optional) protocol and a single port * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a protocol and port, separated by a colon. * The protocol part is optional. * The parsed protocol and port numbers are stored in the data * blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_proto_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; char *a, *saved, *tmp; const char *proto; uint8_t p = IPPROTO_TCP; int err = 0; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); data = ipset_session_data(session); saved = tmp = ipset_strdup(session, str); if (tmp == NULL) return -1; a = proto_separator(tmp); if (a != NULL) { uint8_t family = ipset_data_family(data); /* proto:port */ *a++ = '\0'; err = ipset_parse_proto(session, IPSET_OPT_PROTO, tmp); if (err) goto error; p = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); switch (p) { case IPPROTO_TCP: case IPPROTO_SCTP: case IPPROTO_UDP: case IPPROTO_UDPLITE: proto = tmp; tmp = a; goto parse_port; case IPPROTO_ICMP: if (family != NFPROTO_IPV4) { syntax_err("Protocol ICMP can be used " "with family inet only"); goto error; } err = ipset_parse_icmp(session, opt, a); break; case IPPROTO_ICMPV6: if (family != NFPROTO_IPV6) { syntax_err("Protocol ICMPv6 can be used " "with family inet6 only"); goto error; } err = ipset_parse_icmpv6(session, opt, a); break; default: if (!STREQ(a, "0")) { syntax_err("Protocol %s can be used " "with pseudo port value 0 only.", tmp); err = -1; goto error; } ipset_data_flags_set(data, IPSET_FLAG(opt)); } goto error; } else { proto = "tcp"; err = ipset_data_set(data, IPSET_OPT_PROTO, &p); if (err) goto error; } parse_port: err = ipset_parse_tcpudp_port(session, opt, tmp, proto); error: free(saved); return err; } /** * ipset_parse_tcp_udp_port - parse (optional) protocol and a single port * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a protocol and port, separated by a colon. * The protocol part is optional, but may only be "tcp" or "udp". * The parsed port numbers are stored in the data * blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_tcp_udp_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; int err = 0; uint8_t p = 0; err = ipset_parse_proto_port(session, opt, str); if (!err) { data = ipset_session_data(session); p = *(const uint8_t *) ipset_data_get(data, IPSET_OPT_PROTO); if (p != IPPROTO_TCP && p != IPPROTO_UDP) { syntax_err("Only protocols TCP and UDP are valid"); err = -1 ; } else { /* Reset the protocol to none */ ipset_data_flags_unset(data, IPSET_FLAG(IPSET_OPT_PROTO)); } } return err; } /** * ipset_parse_family - parse INET|INET6 family names * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an INET|INET6 family name. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_family(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; uint8_t family; assert(session); assert(opt == IPSET_OPT_FAMILY); assert(str); data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FAMILY)) && !ipset_data_test_ignored(data, IPSET_OPT_FAMILY)) syntax_err("protocol family may not be specified " "multiple times"); if (STREQ(str, "inet") || STREQ(str, "ipv4") || STREQ(str, "-4")) family = NFPROTO_IPV4; else if (STREQ(str, "inet6") || STREQ(str, "ipv6") || STREQ(str, "-6")) family = NFPROTO_IPV6; else if (STREQ(str, "any") || STREQ(str, "unspec")) family = NFPROTO_UNSPEC; else return syntax_err("unknown inet family %s", str); return ipset_data_set(data, opt, &family); } /* * Parse IPv4/IPv6 addresses, networks and ranges. * We resolve hostnames but just the first IP address is used. */ static void print_warn(struct ipset_session *session) { if (!ipset_envopt_test(session, IPSET_ENV_QUIET)) fprintf(stderr, "Warning: %s", ipset_session_report_msg(session)); ipset_session_report_reset(session); } #ifdef HAVE_GETHOSTBYNAME2 static int get_hostbyname2(struct ipset_session *session, enum ipset_opt opt, const char *str, int af) { struct hostent *h = gethostbyname2(str, af); if (h == NULL) { syntax_err("cannot parse %s: resolving to %s address failed", str, af == AF_INET ? "IPv4" : "IPv6"); return -1; } if (h->h_addr_list[1] != NULL) { ipset_warn(session, "%s resolves to multiple addresses: " "using only the first one returned " "by the resolver.", str); print_warn(session); } return ipset_session_data_set(session, opt, h->h_addr_list[0]); } static int parse_ipaddr(struct ipset_session *session, enum ipset_opt opt, const char *str, uint8_t family) { uint8_t m = family == NFPROTO_IPV4 ? 32 : 128; int af = family == NFPROTO_IPV4 ? AF_INET : AF_INET6; int err = 0, range = 0; char *saved = ipset_strdup(session, str); char *a, *tmp = saved; enum ipset_opt copt, opt2; if (opt == IPSET_OPT_IP) { copt = IPSET_OPT_CIDR; opt2 = IPSET_OPT_IP_TO; } else { copt = IPSET_OPT_CIDR2; opt2 = IPSET_OPT_IP2_TO; } if (tmp == NULL) return -1; if ((a = cidr_separator(tmp)) != NULL) { /* IP/mask */ *a++ = '\0'; if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 || (err = ipset_session_data_set(session, copt, &m)) != 0) goto out; } else { a = find_range_separator(session, tmp); if (a == tmp) { err = -1; goto out; } if (a != NULL) { /* IP-IP */ *a++ = '\0'; D("range %s", a); range++; } } tmp = strip_escape(session, tmp); if (tmp == NULL) { err = -1; goto out; } if ((err = get_hostbyname2(session, opt, tmp, af)) != 0 || !range) goto out; a = strip_escape(session, a); if (a == NULL) { err = -1; goto out; } err = get_hostbyname2(session, opt2, a, af); out: free(saved); return err; } #else static struct addrinfo * call_getaddrinfo(struct ipset_session *session, const char *str, uint8_t family) { struct addrinfo hints; struct addrinfo *res; int err; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_CANONNAME; hints.ai_family = family; hints.ai_socktype = SOCK_RAW; hints.ai_protocol = 0; hints.ai_next = NULL; if ((err = getaddrinfo(str, NULL, &hints, &res)) != 0) { syntax_err("cannot resolve '%s' to an %s address: %s", str, family == NFPROTO_IPV6 ? "IPv6" : "IPv4", gai_strerror(err)); return NULL; } else return res; } static int get_addrinfo(struct ipset_session *session, enum ipset_opt opt, const char *str, struct addrinfo **info, uint8_t family) { struct addrinfo *i; size_t addrlen = family == NFPROTO_IPV4 ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); int found, err = 0; if ((*info = call_getaddrinfo(session, str, family)) == NULL) { syntax_err("cannot parse %s: resolving to %s address failed", str, family == NFPROTO_IPV4 ? "IPv4" : "IPv6"); return EINVAL; } for (i = *info, found = 0; i != NULL; i = i->ai_next) { if (i->ai_family != family || i->ai_addrlen != addrlen) continue; if (found == 0) { if (family == NFPROTO_IPV4) { /* Workaround: direct cast increases * required alignment on Sparc */ const struct sockaddr_in *saddr = (void *)i->ai_addr; err = ipset_session_data_set(session, opt, &saddr->sin_addr); } else { /* Workaround: direct cast increases * required alignment on Sparc */ const struct sockaddr_in6 *saddr = (void *)i->ai_addr; err = ipset_session_data_set(session, opt, &saddr->sin6_addr); } } else if (found == 1) { ipset_warn(session, "%s resolves to multiple addresses: " "using only the first one returned " "by the resolver.", str); print_warn(session); } found++; } if (found == 0) return syntax_err("cannot parse %s: " "%s address could not be resolved", str, family == NFPROTO_IPV4 ? "IPv4" : "IPv6"); return err; } static int parse_ipaddr(struct ipset_session *session, enum ipset_opt opt, const char *str, uint8_t family) { uint8_t m = family == NFPROTO_IPV4 ? 32 : 128; int aerr = EINVAL, err = 0, range = 0; char *saved = ipset_strdup(session, str); char *a, *tmp = saved; struct addrinfo *info; enum ipset_opt copt, opt2; if (opt == IPSET_OPT_IP) { copt = IPSET_OPT_CIDR; opt2 = IPSET_OPT_IP_TO; } else { copt = IPSET_OPT_CIDR2; opt2 = IPSET_OPT_IP2_TO; } if (tmp == NULL) return -1; if ((a = cidr_separator(tmp)) != NULL) { /* IP/mask */ *a++ = '\0'; if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 || (err = ipset_session_data_set(session, copt, &m)) != 0) goto out; } else { a = find_range_separator(session, tmp); if (a == tmp) { err = -1; goto out; } if (a != NULL) { /* IP-IP */ *a++ = '\0'; D("range %s", a); range++; } } tmp = strip_escape(session, tmp); if (tmp == NULL) { err = -1; goto out; } if ((aerr = get_addrinfo(session, opt, tmp, &info, family)) != 0 || !range) goto out; freeaddrinfo(info); a = strip_escape(session, a); if (a == NULL) { err = -1; goto out; } aerr = get_addrinfo(session, opt2, a, &info, family); out: if (aerr != EINVAL) /* getaddrinfo not failed */ freeaddrinfo(info); else if (aerr) err = -1; free(saved); return err; } #endif enum ipaddr_type { IPADDR_ANY, IPADDR_PLAIN, IPADDR_NET, IPADDR_RANGE, }; static inline bool cidr_hostaddr(const char *str, uint8_t family) { char *a = cidr_separator(str); return family == NFPROTO_IPV4 ? STREQ(a, "/32") : STREQ(a, "/128"); } static int parse_ip(struct ipset_session *session, enum ipset_opt opt, const char *str, enum ipaddr_type addrtype) { struct ipset_data *data = ipset_session_data(session); uint8_t family = ipset_data_family(data); if (family == NFPROTO_UNSPEC) { family = NFPROTO_IPV4; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } switch (addrtype) { case IPADDR_PLAIN: if (escape_range_separator(str) || (cidr_separator(str) && !cidr_hostaddr(str, family))) return syntax_err("plain IP address must be supplied: " "%s", str); break; case IPADDR_NET: if (!cidr_separator(str) || escape_range_separator(str)) return syntax_err("IP/netblock must be supplied: %s", str); break; case IPADDR_RANGE: if (!escape_range_separator(str) || cidr_separator(str)) return syntax_err("IP-IP range must supplied: %s", str); break; case IPADDR_ANY: default: break; } return parse_ipaddr(session, opt, str, family); } /** * ipset_parse_ip - parse IPv4|IPv6 address, range or netblock * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address or address range * or netblock. Hostnames are resolved. If family is not set * yet in the data blob, INET is assumed. * The values are stored in the data blob of the session. * * FIXME: if the hostname resolves to multiple addresses, * the first one is used only. * * Returns 0 on success or a negative error code. */ int ipset_parse_ip(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); return parse_ip(session, opt, str, IPADDR_ANY); } /** * ipset_parse_single_ip - parse a single IPv4|IPv6 address * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address or hostname. If family * is not set yet in the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_single_ip(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP_TO || opt == IPSET_OPT_IP2); assert(str); return parse_ip(session, opt, str, IPADDR_PLAIN); } /** * ipset_parse_net - parse IPv4|IPv6 address/cidr * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address/cidr pattern. If family * is not set yet in the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_net(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); return parse_ip(session, opt, str, IPADDR_NET); } /** * ipset_parse_range - parse IPv4|IPv6 ranges * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 range separated by a dash. If family * is not set yet in the data blob, INET is assumed. * The values are stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_range(struct ipset_session *session, enum ipset_opt opt ASSERT_UNUSED, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); return parse_ip(session, IPSET_OPT_IP, str, IPADDR_RANGE); } /** * ipset_parse_netrange - parse IPv4|IPv6 address/cidr or range * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address/cidr pattern or a range * of addresses separated by a dash. If family is not set yet in * the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_netrange(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); if (!(escape_range_separator(str) || cidr_separator(str))) return syntax_err("IP/cidr or IP-IP range must be specified: " "%s", str); return parse_ip(session, opt, str, IPADDR_ANY); } /** * ipset_parse_iprange - parse IPv4|IPv6 address or range * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address pattern or a range * of addresses separated by a dash. If family is not set yet in * the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_iprange(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); if (cidr_separator(str)) return syntax_err("IP address or IP-IP range must be " "specified: %s", str); return parse_ip(session, opt, str, IPADDR_ANY); } /** * ipset_parse_ipnet - parse IPv4|IPv6 address or address/cidr pattern * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address or address/cidr pattern. * If family is not set yet in the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_ipnet(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); if (escape_range_separator(str)) return syntax_err("IP address or IP/cidr must be specified: %s", str); return parse_ip(session, opt, str, IPADDR_ANY); } /** * ipset_parse_ip4_single6 - parse IPv4 address, range or netblock or IPv6 address * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4 address or address range * or netblock or and IPv6 address. Hostnames are resolved. If family * is not set yet in the data blob, INET is assumed. * The values are stored in the data blob of the session. * * FIXME: if the hostname resolves to multiple addresses, * the first one is used only. * * Returns 0 on success or a negative error code. */ int ipset_parse_ip4_single6(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; uint8_t family; assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); data = ipset_session_data(session); family = ipset_data_family(data); if (family == NFPROTO_UNSPEC) { family = NFPROTO_IPV4; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } return family == NFPROTO_IPV4 ? ipset_parse_ip(session, opt, str) : ipset_parse_single_ip(session, opt, str); } /** * ipset_parse_ip4_net6 - parse IPv4|IPv6 address or address/cidr pattern * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address or address/cidr pattern. For IPv4, * address range is valid too. * If family is not set yet in the data blob, INET is assumed. * The values are stored in the data blob of the session. * * FIXME: if the hostname resolves to multiple addresses, * the first one is used only. * * Returns 0 on success or a negative error code. */ int ipset_parse_ip4_net6(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; uint8_t family; assert(session); assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); data = ipset_session_data(session); family = ipset_data_family(data); if (family == NFPROTO_UNSPEC) { family = NFPROTO_IPV4; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } return family == NFPROTO_IPV4 ? parse_ip(session, opt, str, IPADDR_ANY) : ipset_parse_ipnet(session, opt, str); } /** * ipset_parse_timeout - parse timeout parameter * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a timeout parameter. We have to take into account * the jiffies storage in kernel. * * Returns 0 on success or a negative error code. */ int ipset_parse_timeout(struct ipset_session *session, enum ipset_opt opt, const char *str) { int err; unsigned long long llnum = 0; uint32_t num = 0; assert(session); assert(opt == IPSET_OPT_TIMEOUT); assert(str); err = string_to_number_ll(session, str, 0, (UINT_MAX>>1)/1000, &llnum, IPSET_ERROR); if (err == 0) { /* Timeout is expected to be 32bits wide, so we have to convert it here */ num = llnum; return ipset_session_data_set(session, opt, &num); } return err; } /** * ipset_parse_iptimeout - parse IPv4|IPv6 address and timeout * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an IPv4|IPv6 address and timeout parameter. * If family is not set yet in the data blob, INET is assumed. * The value is stored in the data blob of the session. * * Compatibility parser. * * Returns 0 on success or a negative error code. */ int ipset_parse_iptimeout(struct ipset_session *session, enum ipset_opt opt, const char *str) { char *tmp, *saved, *a; int err; assert(session); assert(opt == IPSET_OPT_IP); assert(str); /* IP,timeout */ if (ipset_data_flags_test(ipset_session_data(session), IPSET_FLAG(IPSET_OPT_TIMEOUT))) return syntax_err("mixed syntax, timeout already specified"); tmp = saved = ipset_strdup(session, str); if (saved == NULL) return 1; a = elem_separator(tmp); if (a == NULL) { free(saved); return syntax_err("Missing separator from %s", str); } *a++ = '\0'; err = parse_ip(session, opt, tmp, IPADDR_ANY); if (!err) err = ipset_parse_timeout(session, IPSET_OPT_TIMEOUT, a); free(saved); return err; } #define check_setname(str, saved) \ do { \ if (strlen(str) > IPSET_MAXNAMELEN - 1) { \ int __err; \ __err = syntax_err("setname '%s' is longer than %u characters",\ str, IPSET_MAXNAMELEN - 1); \ free(saved); \ return __err; \ } \ } while (0) /** * ipset_parse_name_compat - parse setname as element * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a setname or a setname element to add to a set. * The pattern "setname,before|after,setname" is recognized and * parsed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_name_compat(struct ipset_session *session, enum ipset_opt opt, const char *str) { char *saved; char *a = NULL, *b = NULL, *tmp; int err, before = 0; const char *sep = IPSET_ELEM_SEPARATOR; struct ipset_data *data; assert(session); assert(opt == IPSET_OPT_NAME); assert(str); data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) syntax_err("mixed syntax, before|after option already used"); tmp = saved = ipset_strdup(session, str); if (saved == NULL) return -1; if ((a = elem_separator(tmp)) != NULL) { /* setname,[before|after,setname */ *a++ = '\0'; if ((b = elem_separator(a)) != NULL) *b++ = '\0'; if (b == NULL || !(STREQ(a, "before") || STREQ(a, "after"))) { err = ipset_err(session, "you must specify elements " "as setname%s[before|after]%ssetname", sep, sep); goto out; } before = STREQ(a, "before"); } check_setname(tmp, saved); if ((err = ipset_data_set(data, opt, tmp)) != 0 || b == NULL) goto out; check_setname(b, saved); if ((err = ipset_data_set(data, IPSET_OPT_NAMEREF, b)) != 0) goto out; if (before) err = ipset_data_set(data, IPSET_OPT_BEFORE, &before); out: free(saved); return err; } /** * ipset_parse_setname - parse string as a setname * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a setname. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_setname(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(opt == IPSET_SETNAME || opt == IPSET_OPT_NAME || opt == IPSET_OPT_SETNAME2); assert(str); check_setname(str, NULL); return ipset_session_data_set(session, opt, str); } /** * ipset_parse_before - parse string as "before" reference setname * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a "before" reference setname for list:set * type of sets. The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_before(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; assert(session); assert(opt == IPSET_OPT_NAMEREF); assert(str); data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) syntax_err("mixed syntax, before|after option already used"); check_setname(str, NULL); ipset_data_set(data, IPSET_OPT_BEFORE, str); return ipset_data_set(data, opt, str); } /** * ipset_parse_after - parse string as "after" reference setname * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a "after" reference setname for list:set * type of sets. The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_after(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; assert(session); assert(opt == IPSET_OPT_NAMEREF); assert(str); data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_NAMEREF))) syntax_err("mixed syntax, before|after option already used"); check_setname(str, NULL); return ipset_data_set(data, opt, str); } /** * ipset_parse_uint64 - parse string as an unsigned long integer * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an unsigned long integer number. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_uint64(struct ipset_session *session, enum ipset_opt opt, const char *str) { unsigned long long value = 0; int err; assert(session); assert(str); err = string_to_number_ll(session, str, 0, ULLONG_MAX - 1, &value, IPSET_ERROR); if (err) return err; return ipset_session_data_set(session, opt, &value); } /** * ipset_parse_uint32 - parse string as an unsigned integer * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an unsigned integer number. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_uint32(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint32_t value; int err; assert(session); assert(str); if ((err = string_to_u32(session, str, &value)) == 0) return ipset_session_data_set(session, opt, &value); return err; } int ipset_parse_uint16(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint16_t value; int err; assert(session); assert(str); err = string_to_u16(session, str, &value, IPSET_ERROR); if (err == 0) return ipset_session_data_set(session, opt, &value); return err; } /** * ipset_parse_uint8 - parse string as an unsigned short integer * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an unsigned short integer number. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_uint8(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint8_t value; int err; assert(session); assert(str); if ((err = string_to_u8(session, str, &value)) == 0) return ipset_session_data_set(session, opt, &value); return err; } /** * ipset_parse_netmask - parse string as a CIDR netmask value * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a CIDR netmask value, depending on family type. * If family is not set yet, INET is assumed. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_netmask(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint8_t family, cidr; struct ipset_data *data; int err = 0; assert(session); assert(opt == IPSET_OPT_NETMASK); assert(str); data = ipset_session_data(session); family = ipset_data_family(data); if (family == NFPROTO_UNSPEC) { family = NFPROTO_IPV4; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } err = string_to_cidr(session, str, 1, family == NFPROTO_IPV4 ? 32 : 128, &cidr); if (err) return syntax_err("netmask is out of the inclusive range " "of 1-%u", family == NFPROTO_IPV4 ? 32 : 128); return ipset_data_set(data, opt, &cidr); } /** * ipset_parse_flag - "parse" option flags * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse option flags :-) * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_flag(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); return ipset_session_data_set(session, opt, str); } /** * ipset_parse_type - parse ipset type name * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse ipset module type: supports both old and new formats. * The type name is looked up and the type found is stored * in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_typename(struct ipset_session *session, enum ipset_opt opt ASSERT_UNUSED, const char *str) { const struct ipset_type *type; const char *typename; assert(session); assert(opt == IPSET_OPT_TYPENAME); assert(str); if (strlen(str) > IPSET_MAXNAMELEN - 1) return syntax_err("typename '%s' is longer than %u characters", str, IPSET_MAXNAMELEN - 1); /* Find the corresponding type */ typename = ipset_typename_resolve(str); if (typename == NULL) return syntax_err("typename '%s' is unknown", str); ipset_session_data_set(session, IPSET_OPT_TYPENAME, typename); type = ipset_type_get(session, IPSET_CMD_CREATE); if (type == NULL) return -1; return ipset_session_data_set(session, IPSET_OPT_TYPE, type); } /** * ipset_parse_iface - parse string as an interface name * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as an interface name, optionally with 'physdev:' prefix. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_iface(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; int offset = 0, err = 0; static const char pdev_prefix[]="physdev:"; assert(session); assert(opt == IPSET_OPT_IFACE); assert(str); data = ipset_session_data(session); if (STRNEQ(str, pdev_prefix, strlen(pdev_prefix))) { offset = strlen(pdev_prefix); err = ipset_data_set(data, IPSET_OPT_PHYSDEV, str); if (err < 0) return err; } if (strlen(str + offset) > IFNAMSIZ - 1) return syntax_err("interface name '%s' is longer " "than %u characters", str + offset, IFNAMSIZ - 1); return ipset_data_set(data, opt, str + offset); } /** * ipset_parse_comment - parse string as a comment * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string for use as a comment on an ipset entry. * Gets stored in the data blob as usual. * * Returns 0 on success or a negative error code. */ int ipset_parse_comment(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; assert(session); assert(opt == IPSET_OPT_ADT_COMMENT); assert(str); data = ipset_session_data(session); if (strchr(str, '"')) return syntax_err("\" character is not permitted in comments"); if (strlen(str) > IPSET_MAX_COMMENT_SIZE) return syntax_err("Comment is longer than the maximum allowed " "%d characters", IPSET_MAX_COMMENT_SIZE); return ipset_data_set(data, opt, str); } int ipset_parse_skbmark(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; uint64_t result = 0; unsigned long mark, mask; int ret = 0; assert(session); assert(opt == IPSET_OPT_SKBMARK); assert(str); data = ipset_session_data(session); ret = sscanf(str, "0x%lx/0x%lx", &mark, &mask); if (ret != 2) { mask = 0xffffffff; ret = sscanf(str, "0x%lx", &mark); if (ret != 1) return syntax_err("Invalid skbmark format, " "it should be: " " MARK/MASK or MARK (see manpage)"); } result = ((uint64_t)(mark) << 32) | (mask & 0xffffffff); return ipset_data_set(data, opt, &result); } int ipset_parse_skbprio(struct ipset_session *session, enum ipset_opt opt, const char *str) { struct ipset_data *data; unsigned maj, min; uint32_t major; int err; assert(session); assert(opt == IPSET_OPT_SKBPRIO); assert(str); data = ipset_session_data(session); err = sscanf(str, "%x:%x", &maj, &min); if (err != 2) return syntax_err("Invalid skbprio format, it should be:"\ "MAJOR:MINOR (see manpage)"); major = ((uint32_t)maj << 16) | (min & 0xffff); return ipset_data_set(data, opt, &major); } /** * ipset_parse_ignored - "parse" ignored option * @session: session structure * @opt: option kind of the data * @str: string to parse * * Ignore deprecated options. A single warning is generated * for every ignored opton. * * Returns 0. */ int ipset_parse_ignored(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); assert(str); if (!ipset_data_ignored(ipset_session_data(session), opt)) ipset_warn(session, "Option '--%s %s' is ignored. " "Please upgrade your syntax.", ipset_ignored_optname(opt), str); return 0; } /** * ipset_call_parser - call a parser function * @session: session structure * @parsefn: parser function * @optstr: option name * @opt: option kind of the data * @str: string to parse * * Wrapper to call the parser functions so that ignored options * are handled properly. * * Returns 0 on success or a negative error code. */ int ipset_call_parser(struct ipset_session *session, const struct ipset_arg *arg, const char *str) { struct ipset_data *data = ipset_session_data(session); if (ipset_data_flags_test(data, IPSET_FLAG(arg->opt)) && !(arg->opt == IPSET_OPT_FAMILY && ipset_data_test_ignored(data, IPSET_OPT_FAMILY))) return syntax_err("%s already specified", arg->name[0]); return arg->parse(session, arg->opt, str); } #define parse_elem(s, t, d, str) \ do { \ if (!(t)->elem[d - 1].parse) \ goto internal; \ ret = (t)->elem[d - 1].parse(s, (t)->elem[d - 1].opt, str); \ if (ret) \ goto out; \ } while (0) #define elem_syntax_err(fmt, args...) \ do { \ free(saved); \ return syntax_err(fmt , ## args);\ } while (0) /** * ipset_parse_elem - parse ADT elem, depending on settype * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a (multipart) element according to the settype. * The value is stored in the data blob of the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_elem(struct ipset_session *session, bool optional, const char *str) { const struct ipset_type *type; char *a = NULL, *b = NULL, *tmp, *saved; int ret; assert(session); assert(str); type = ipset_session_data_get(session, IPSET_OPT_TYPE); if (!type) return ipset_err(session, "Internal error: set type is unknown!"); saved = tmp = ipset_strdup(session, str); if (tmp == NULL) return -1; a = elem_separator(tmp); if (type->dimension > IPSET_DIM_ONE) { if (a != NULL) { /* elem,elem */ *a++ = '\0'; } else if (!optional) elem_syntax_err("Second element is missing from %s.", str); } else if (a != NULL) { if (type->compat_parse_elem) { ret = type->compat_parse_elem(session, type->elem[IPSET_DIM_ONE - 1].opt, saved); goto out; } elem_syntax_err("Elem separator in %s, " "but settype %s supports none.", str, type->name); } if (a) b = elem_separator(a); if (type->dimension > IPSET_DIM_TWO) { if (b != NULL) { /* elem,elem,elem */ *b++ = '\0'; } else if (!optional) elem_syntax_err("Third element is missing from %s.", str); } else if (b != NULL) elem_syntax_err("Two elem separators in %s, " "but settype %s supports one.", str, type->name); if (b != NULL && elem_separator(b)) elem_syntax_err("Three elem separators in %s, " "but settype %s supports two.", str, type->name); D("parse elem part one: %s", tmp); parse_elem(session, type, IPSET_DIM_ONE, tmp); if (type->dimension > IPSET_DIM_ONE && a != NULL) { D("parse elem part two: %s", a); parse_elem(session, type, IPSET_DIM_TWO, a); } if (type->dimension > IPSET_DIM_TWO && b != NULL) { D("parse elem part three: %s", b); parse_elem(session, type, IPSET_DIM_THREE, b); } goto out; internal: ret = ipset_err(session, "Internal error: missing parser function for %s", type->name); out: free(saved); return ret; }