/* Copyright 2007-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) * * 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 /* D() */ #include /* IPSET_OPT_* */ #include /* prefixlen_netmask_map */ #include /* ipset_err */ #include /* ipset_type_get */ #include /* string utilities */ #include /* prototypes */ #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) static char * ipset_strchr(const char *str, const char *sep) { char *match; assert(str); assert(sep); for (; *sep != '\0'; sep++) if ((match = strchr(str, (int)sep[0])) != NULL && str[0] != sep[0] && str[strlen(str)-1] != sep[0]) return match; return NULL; } /* * 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) { 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("'%s' is out of range %llu-%llu", str, min, max); else if (errno == ERANGE) return syntax_err("'%s' is out of range %llu-%llu", str, min, ULLONG_MAX); else return syntax_err("'%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); *ret = (uint8_t) 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) { int err; unsigned long long num = 0; err = string_to_number_ll(session, str, 0, USHRT_MAX, &num); *ret = (uint16_t) 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); *ret = (uint32_t) 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) { unsigned int i = 0; unsigned char ether[ETH_ALEN]; assert(session); assert(opt == IPSET_OPT_ETHER); assert(str); if (strlen(str) != ETH_ALEN * 3 - 1) goto error; for (i = 0; i < ETH_ALEN; i++) { long number; char *end; number = strtol(str + i * 3, &end, 16); if (end == str + i * 3 + 2 && (*end == ':' || *end == '\0') && 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); } /* * Parse TCP service names or port numbers */ static int parse_portname(struct ipset_session *session, const char *str, uint16_t *port) { struct servent *service; if ((service = getservbyname(str, "tcp")) != NULL) { *port = ntohs((uint16_t) service->s_port); return 0; } return syntax_err("cannot parse '%s' as a (TCP) port", str); } /** * ipset_parse_single_port - parse a single (TCP) port number or name * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse string as a single (TCP) 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_single_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { uint16_t port; int err; assert(session); assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); assert(str); if ((err = string_to_u16(session, str, &port)) == 0 || (err = parse_portname(session, str, &port)) == 0) err = ipset_session_data_set(session, opt, &port); if (!err) /* No error, so reset session messages! */ ipset_session_report_reset(session); return err; } /** * ipset_parse_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_port(struct ipset_session *session, enum ipset_opt opt, const char *str) { char *a, *saved, *tmp; int err = 0; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); saved = tmp = strdup(str); if (tmp == NULL) return ipset_err(session, "Cannot allocate memory to duplicate %s.", str); a = range_separator(tmp); if (a != NULL) { /* port-port */ *a++ = '\0'; err = ipset_parse_single_port(session, IPSET_OPT_PORT_TO, a); if (err) goto error; } err = ipset_parse_single_port(session, opt, tmp); error: free(saved); return err; } /** * 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. "any" is supported * as a special protocol name for ipset itself. * The parsed protocol are 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) { uint8_t proto = 0; assert(session); assert(opt == IPSET_OPT_PROTO); assert(str); if (STREQ(str, "any")) proto = IPSET_IPPROTO_ANY; else { struct protoent *protoent = getprotobyname(str); if (protoent == NULL) return syntax_err("cannot parse '%s' as a protocol name", str); proto = protoent->p_proto; } if (!proto || proto == IPSET_IPPROTO_TCPUDP) return syntax_err("invalid protocol '%s'", str); return ipset_session_data_set(session, opt, &proto); } /** * 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) { char *a, *saved, *tmp; int err = 0; assert(session); assert(opt == IPSET_OPT_PORT); assert(str); saved = tmp = strdup(str); if (tmp == NULL) return ipset_err(session, "Cannot allocate memory to duplicate %s.", str); a = proto_separator(tmp); if (a != NULL) { /* proto:port */ *a++ = '\0'; err = ipset_parse_proto(session, IPSET_OPT_PROTO, tmp); if (err) goto error; tmp = a; } err = ipset_parse_single_port(session, opt, tmp); error: free(saved); 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))) syntax_err("protocol family may not be specified multiple times"); if (STREQ(str, "inet") || STREQ(str, "ipv4") || STREQ(str, "-4")) family = AF_INET; else if (STREQ(str, "inet6") || STREQ(str, "ipv6") || STREQ(str, "-6")) family = AF_INET6; else if (STREQ(str, "any") || STREQ(str, "unspec")) family = AF_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 struct addrinfo * get_addrinfo(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 == AF_INET6 ? "IPv6" : "IPv4", gai_strerror(err)); return NULL; } else return res; } #define GET_ADDRINFO(family, IP, f, n) \ static int \ get_addrinfo##f(struct ipset_session *session, \ const char *str, \ struct addrinfo **info, \ struct in##n##_addr **inaddr) \ { \ struct addrinfo *i; \ struct sockaddr_in##n saddr; \ int found; \ \ if ((*info = get_addrinfo(session, str, family)) == NULL) { \ syntax_err("cannot parse %s: " IP " resolving failed", \ str); \ return EINVAL; \ } \ \ for (i = *info, found = 0; i != NULL; i = i->ai_next) { \ if (i->ai_family != family \ || i->ai_addrlen != sizeof(saddr)) \ continue; \ if (found == 0) { \ /* Workaround: can't cast on Sparc */ \ memcpy(&saddr, i->ai_addr, sizeof(saddr)); \ *inaddr = &saddr.sin##n##_addr; \ } else if (found == 1) { \ ipset_warn(session, \ "%s resolves to multiple addresses: " \ "using only the first one returned by the resolver", \ str); \ } \ found++; \ } \ if (found == 0) \ return syntax_err("cannot parse %s: " \ IP "address could not be resolved", \ str); \ return 0; \ } #define PARSE_IP(mask, f, n) \ static int \ parse_ipv##f(struct ipset_session *session, \ enum ipset_opt opt, const char *str) \ { \ uint8_t m = mask; \ int aerr = EINVAL, err = 0, range = 0; \ char *saved = strdup(str); \ char *a, *tmp = saved; \ struct addrinfo *info; \ struct in##n##_addr *inaddr; \ struct ipset_data *data = ipset_session_data(session); \ enum ipset_opt copt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR \ : IPSET_OPT_CIDR2; \ \ if (tmp == NULL) \ return ipset_err(session, \ "Cannot allocate memory to duplicate %s.",\ str); \ if ((a = cidr_separator(tmp)) != NULL) { \ /* IP/mask */ \ *a++ = '\0'; \ \ if ((err = string_to_cidr(session, a, 0, m, &m)) != 0 \ || (err = ipset_data_set(data, copt, &m)) != 0) \ goto out; \ } else if ((a = range_separator(tmp)) != NULL) { \ /* IP-IP */ \ *a++ = '\0'; \ D("range %s", a); \ range++; \ } \ if ((aerr = get_addrinfo##f(session, tmp, &info, &inaddr)) != 0 \ || (err = ipset_data_set(data, opt, inaddr)) != 0 \ || !range) \ goto out; \ freeaddrinfo(info); \ if ((aerr = get_addrinfo##f(session, a, &info, &inaddr)) == 0) \ err = ipset_data_set(data, IPSET_OPT_IP_TO, inaddr); \ \ out: \ if (aerr != EINVAL) \ /* getaddrinfo not failed */ \ freeaddrinfo(info); \ else if (aerr) \ err = -1; \ free(saved); \ return err; \ } GET_ADDRINFO(AF_INET, "IPv4", 4, ) PARSE_IP(32, 4, ) GET_ADDRINFO(AF_INET6, "IPv6", 6, 6) PARSE_IP(128, 6, 6) enum ipaddr_type { IPADDR_ANY, IPADDR_PLAIN, IPADDR_NET, IPADDR_RANGE, }; static int parse_ip(struct ipset_session *session, enum ipset_opt opt, const char *str, enum ipaddr_type addrtype) { int err = 0; struct ipset_data *data = ipset_session_data(session); uint8_t family = ipset_data_family(data); if (family == AF_UNSPEC) { family = AF_INET; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } switch (addrtype) { case IPADDR_PLAIN: if (range_separator(str) || cidr_separator(str)) return syntax_err("plain IP address must be supplied: %s", str); break; case IPADDR_NET: if (!cidr_separator(str) || range_separator(str)) return syntax_err("IP/netblock must be supplied: %s", str); break; case IPADDR_RANGE: if (!range_separator(str) || cidr_separator(str)) return syntax_err("IP-IP range must supplied: %s", str); break; case IPADDR_ANY: default: break; } if (family == AF_INET) err = parse_ipv4(session, opt, str); else err = parse_ipv6(session, opt, str); return err; } /** * 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, 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 (!(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 (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_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 = strdup(str); if (saved == NULL) return ipset_err(session, "Cannot allocate memory to duplicate %s.", str); 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_uint32(session, IPSET_OPT_TIMEOUT, a); free(saved); return err; } #define check_setname(str, saved) \ do { \ if (strlen(str) > IPSET_MAXNAMELEN - 1) { \ if (saved != NULL) \ free(saved); \ return syntax_err("setname '%s' is longer than %u characters", \ str, IPSET_MAXNAMELEN - 1); \ } \ } 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 = strdup(str); if (saved == NULL) return ipset_err(session, "Cannot allocate memory to duplicate %s.", str); 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_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; } /** * 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 == AF_UNSPEC) { family = AF_INET; ipset_data_set(data, IPSET_OPT_FAMILY, &family); } err = string_to_cidr(session, str, family == AF_INET ? 1 : 4, family == AF_INET ? 31 : 124, &cidr); if (err) return syntax_err("netmask is out of the inclusive range " "of %u-%u", family == AF_INET ? 1 : 4, family == AF_INET ? 31 : 124); 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 UNUSED) { assert(session); return ipset_session_data_set(session, opt, NULL); } /** * 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, 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 unkown", 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_output - parse output format name * @session: session structure * @opt: option kind of the data * @str: string to parse * * Parse output format names and set session mode. * The value is stored in the session. * * Returns 0 on success or a negative error code. */ int ipset_parse_output(struct ipset_session *session, int opt UNUSED, const char *str) { assert(session); assert(str); if (STREQ(str, "plain")) return ipset_session_output(session, IPSET_LIST_PLAIN); else if (STREQ(str, "xml")) return ipset_session_output(session, IPSET_LIST_XML); else if (STREQ(str, "save")) return ipset_session_output(session, IPSET_LIST_SAVE); return syntax_err("unkown output mode '%s'", str); } /** * 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 is ignored. Please upgrade your syntax.", 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, ipset_parsefn parse, const char *optstr, enum ipset_opt opt, const char *str) { if (ipset_data_flags_test(ipset_session_data(session), IPSET_FLAG(opt))) syntax_err("%s already specified", optstr); return parse(session, opt, parse == ipset_parse_ignored ? optstr : str); } #define parse_elem(s, t, d, str) \ do { \ if (!(t)->elem[d].parse) \ goto internal; \ ret = (t)->elem[d].parse(s, (t)->elem[d].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, enum ipset_opt 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 = strdup(str); if (tmp == NULL) return ipset_err(session, "Cannot allocate memory to duplicate %s.", str); 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].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) 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; }