/* 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.h>				/* assert */
#include <errno.h>				/* errno */
#include <limits.h>				/* ULLONG_MAX */
#include <netdb.h>				/* getservbyname, getaddrinfo */
#include <stdlib.h>				/* strtoull, etc. */
#include <sys/types.h>				/* getaddrinfo */
#include <sys/socket.h>				/* getaddrinfo, AF_ */
#include <net/ethernet.h>			/* ETH_ALEN */
#include <net/if.h>				/* IFNAMSIZ */
#include <netinet/in.h>				/* IPPROTO_ */

#include <libipset/debug.h>			/* D() */
#include <libipset/data.h>			/* IPSET_OPT_* */
#include <libipset/icmp.h>			/* name_to_icmp */
#include <libipset/icmpv6.h>			/* name_to_icmpv6 */
#include <libipset/pfxlen.h>			/* prefixlen_netmask_map */
#include <libipset/session.h>			/* ipset_err */
#include <libipset/types.h>			/* ipset_type_get */
#include <libipset/utils.h>			/* string utilities */
#include <libipset/parse.h>			/* 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,
	     enum ipset_err_type errtype)
{
	int err;
	unsigned long long num = 0;

	err = string_to_number_ll(session, str, 0, 255, &num, errtype);
	*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, IPSET_ERROR);

	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;
	const char *protoname;
	const struct protoent *protoent;
	struct servent *service;
	uint8_t protonum = 0;

	saved = tmp = ipset_strdup(session, str);
	if (tmp == NULL)
		return -1;
	tmp = strip_escape(session, tmp);
	if (tmp == NULL)
		goto error;

	protoname = proto;
	if (string_to_u8(session, proto, &protonum, IPSET_WARNING) == 0) {
		protoent = getprotobynumber(protonum);
		if (protoent == NULL)
			goto error;
		protoname = protoent->p_name;
	}

	service = getservbyname(tmp, protoname);
	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;
	uint8_t protonum = 0;

	assert(session);
	assert(opt == IPSET_OPT_PROTO);
	assert(str);

	if (string_to_u8(session, str, &protonum, IPSET_WARNING) == 0)
		return ipset_session_data_set(session, opt, &protonum);

	/* No error, so reset false error messages */
	ipset_session_report_reset(session);
	protoent = getprotobyname(strcasecmp(str, "icmpv6") == 0
				  ? "ipv6-icmp" : str);

	if (protoent == 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, IPSET_ERROR)) != 0 ||
	    (err = string_to_u8(session, a, &code, IPSET_ERROR)) != 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, IPSET_ERROR)) == 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);
	if (ipset_data_test(data, IPSET_OPT_BITMASK))
		return syntax_err("bitmask and netmask are mutually exclusive, provide only one");

	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_bitmask - parse string as a bitmask
 * @session: session structure
 * @opt: option kind of the data
 * @str: string to parse
 *
 * Parse string as a bitmask 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_bitmask(struct ipset_session *session,
		    enum ipset_opt opt, const char *str)
{
	uint8_t family;
	struct ipset_data *data;

	assert(session);
	assert(opt == IPSET_OPT_BITMASK);
	assert(str);

	data = ipset_session_data(session);
	if (ipset_data_test(data, IPSET_OPT_NETMASK))
		return syntax_err("bitmask and netmask are mutually exclusive, provide only one");

	family = ipset_data_family(data);
	if (family == NFPROTO_UNSPEC) {
		family = NFPROTO_IPV4;
		ipset_data_set(data, IPSET_OPT_FAMILY, &family);
	}

	if (parse_ipaddr(session, opt, str, family))
		return syntax_err("bitmask is not valid for family = %s",
				  family == NFPROTO_IPV4 ? "inet" : "inet6");

	return 0;
}

/**
 * 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;
}
