From 3fd6b24ace319b139ec3c4e3031a5f05d21e304e Mon Sep 17 00:00:00 2001 From: Jozsef Kadlecsik Date: Tue, 15 Jun 2010 13:30:55 +0200 Subject: ipset 5 in an almost ready state - milestone Reworked protocol and internal interfaces, missing set types added, backward compatibility verified, lots of tests added (and thanks to the tests, bugs fixed), even the manpage is rewritten ;-). Countless changes everywhere... The missing bits before announcing ipset 5: - net namespace support - new iptables/ip6tables extension library - iptables/ip6tables match and target tests (backward/forward compatibility) - tests on catching syntax errors --- lib/Makefile.am | 4 +- lib/PROTOCOL | 6 +- lib/data.c | 67 ++++++++- lib/mnl.c | 2 + lib/parse.c | 449 ++++++++++++++++++++++++++++++++++++++++++++------------ lib/print.c | 27 ++-- lib/session.c | 98 ++++++++----- lib/types.c | 58 ++++---- lib/utils.c | 103 ------------- 9 files changed, 535 insertions(+), 279 deletions(-) delete mode 100644 lib/utils.c (limited to 'lib') diff --git a/lib/Makefile.am b/lib/Makefile.am index 74b6651..bf4e133 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -12,9 +12,7 @@ libipset_la_SOURCES = \ parse.c \ print.c \ session.c \ - types.c \ - utils.c - + types.c #%.o: %.c # ${AM_VERBOSE_CC} ${CC} ${AM_DEPFLAGS} ${AM_CFLAGS} ${CFLAGS} -o $@ -c $< diff --git a/lib/PROTOCOL b/lib/PROTOCOL index e1a139e..6f07445 100644 --- a/lib/PROTOCOL +++ b/lib/PROTOCOL @@ -33,7 +33,11 @@ req: msg: IPSET_CMD_LIST|SAVE attr: IPSET_ATTR_PROTOCOL IPSET_ATTR_SETNAME (optional) -resp: attr: IPSET_ATTR_DATA +resp: attr: IPSET_ATTR_SETNAME + IPSET_ATTR_TYPENAME + IPSET_ATTR_REVISION + IPSET_ATTR_FAMILY + IPSET_ATTR_DATA create-specific-data IPSET_ATTR_ADT IPSET_ATTR_DATA diff --git a/lib/data.c b/lib/data.c index 0de91a1..f8ff4a9 100644 --- a/lib/data.c +++ b/lib/data.c @@ -12,6 +12,7 @@ #include /* memset */ #include /* IPSET_MAXNAMELEN */ +#include /* D() */ #include /* struct ipset_type */ #include /* inXcpy */ #include /* prototypes */ @@ -26,13 +27,16 @@ struct ipset_data { /* Option bits: which fields are set */ uint64_t bits; + /* Option bits: which options are ignored */ + uint64_t ignored; /* Setname */ char setname[IPSET_MAXNAMELEN]; const struct ipset_type *type; /* Common CADT options */ uint8_t cidr; uint8_t family; - uint32_t flags; + uint32_t flags; /* command level flags */ + uint32_t cadt_flags; /* data level flags */ uint32_t timeout; union nf_inet_addr ip; union nf_inet_addr ip_to; @@ -78,6 +82,25 @@ copy_addr(uint8_t family, union nf_inet_addr *ip, const void *value) in6cpy(&ip->in6, (const struct in6_addr *)value); } +/** + * ipset_strncpy - copy the string from src to dst + * @dst: the target string buffer + * @src: the source string buffer + * @len: the length of bytes to copy, including the terminating null byte. + * + * Copy the string from src to destination, but at most len bytes are + * copied. The target is unconditionally terminated by the null byte. + */ +void +ipset_strncpy(char *dst, const char *src, size_t len) +{ + assert(dst); + assert(src); + + strncpy(dst, src, len); + dst[len - 1] = '\0'; +} + /** * ipset_data_flags_test - test option bits in the data blob * @data: data blob @@ -122,12 +145,37 @@ ipset_data_flags_unset(struct ipset_data *data, uint64_t flags) data->bits &= ~flags; } -#define flag_type_attr(data, opt, flag) \ -do { \ - data->flags |= (1 << flag); \ - opt = IPSET_OPT_FLAGS; \ +#define flag_type_attr(data, opt, flag) \ +do { \ + data->flags |= flag; \ + opt = IPSET_OPT_FLAGS; \ +} while (0) + +#define cadt_flag_type_attr(data, opt, flag) \ +do { \ + data->cadt_flags |= flag; \ + opt = IPSET_OPT_CADT_FLAGS; \ } while (0) +/** + * ipset_data_ignored - test and set ignored bits in the data blob + * @data: data blob + * @flags: the option flags which is ignored + * + * Returns true if the option was not already ignored. + */ +bool +ipset_data_ignored(struct ipset_data *data, enum ipset_opt opt) +{ + bool ignored; + assert(data); + + ignored = data->ignored & IPSET_FLAG(opt); + data->ignored |= IPSET_FLAG(opt); + + return ignored; +} + /** * ipset_data_set - put data into the data blob * @data: data blob @@ -249,11 +297,14 @@ ipset_data_set(struct ipset_data *data, enum ipset_opt opt, const void *value) flag_type_attr(data, opt, IPSET_FLAG_EXIST); break; case IPSET_OPT_BEFORE: - flag_type_attr(data, opt, IPSET_FLAG_BEFORE); + cadt_flag_type_attr(data, opt, IPSET_FLAG_BEFORE); break; case IPSET_OPT_FLAGS: data->flags = *(const uint32_t *)value; break; + case IPSET_OPT_CADT_FLAGS: + data->cadt_flags = *(const uint32_t *)value; + break; default: return -1; }; @@ -351,8 +402,10 @@ ipset_data_get(const struct ipset_data *data, enum ipset_opt opt) /* flags */ case IPSET_OPT_FLAGS: case IPSET_OPT_EXIST: - case IPSET_OPT_BEFORE: return &data->flags; + case IPSET_OPT_CADT_FLAGS: + case IPSET_OPT_BEFORE: + return &data->cadt_flags; default: return NULL; } diff --git a/lib/mnl.c b/lib/mnl.c index 5662a47..8056427 100644 --- a/lib/mnl.c +++ b/lib/mnl.c @@ -8,8 +8,10 @@ #include /* errno */ #include /* calloc, free */ #include /* time */ +#include /* hto* */ #include /* enum ipset_cmd */ +#include /* D() */ #include /* ipset_session_handle */ #include /* IPSET_ENV_EXIST */ #include /* UNUSED */ diff --git a/lib/parse.c b/lib/parse.c index 0e0e7f1..e347c69 100644 --- a/lib/parse.c +++ b/lib/parse.c @@ -13,6 +13,7 @@ #include /* getaddrinfo, AF_ */ #include /* ETH_ALEN */ +#include /* D() */ #include /* IPSET_OPT_* */ #include /* prefixlen_netmask_map */ #include /* ipset_err */ @@ -22,14 +23,31 @@ /* Parse input data */ -#define ipset_cidr_separator(str) ipset_strchr(str, IPSET_CIDR_SEPARATOR) -#define ipset_range_separator(str) ipset_strchr(str, IPSET_RANGE_SEPARATOR) -#define ipset_elem_separator(str) ipset_strchr(str, IPSET_ELEM_SEPARATOR) -#define ipset_name_separator(str) ipset_strchr(str, IPSET_NAME_SEPARATOR) +#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 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. @@ -70,33 +88,53 @@ string_to_number_ll(struct ipset_session *session, } static int -string_to_number_l(struct ipset_session *session, - const char *str, - unsigned long min, - unsigned long max, - unsigned long *ret) +string_to_u8(struct ipset_session *session, + const char *str, uint8_t *ret) { int err; - unsigned long long number = 0; + unsigned long long num = 0; - err = string_to_number_ll(session, str, min, max, &number); - *ret = (unsigned long) number; + err = string_to_number_ll(session, str, 0, 255, &num); + *ret = (uint8_t) num; return err; } static int -string_to_number(struct ipset_session *session, - const char *str, - unsigned int min, - unsigned int max, - unsigned int *ret) +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 number = 0; + unsigned long long num = 0; - err = string_to_number_l(session, str, min, max, &number); - *ret = (unsigned int) number; + err = string_to_number_ll(session, str, 0, UINT_MAX, &num); + *ret = (uint32_t) num; return err; } @@ -161,12 +199,6 @@ parse_portname(struct ipset_session *session, const char *str, uint16_t *port) return syntax_err("cannot parse '%s' as a (TCP) port", str); } -static int -parse_portnum(struct ipset_session *session, const char *str, uint16_t *port) -{ - return string_to_number(session, str, 0, 65535, (unsigned int *)port); -} - /** * ipset_parse_single_port - parse a single (TCP) port number or name * @session: session structure @@ -189,7 +221,7 @@ ipset_parse_single_port(struct ipset_session *session, assert(opt == IPSET_OPT_PORT || opt == IPSET_OPT_PORT_TO); assert(str); - if ((err = parse_portnum(session, str, &port)) == 0 + if ((err = string_to_u16(session, str, &port)) == 0 || (err = parse_portname(session, str, &port)) == 0) err = ipset_session_data_set(session, opt, &port); @@ -229,7 +261,7 @@ ipset_parse_port(struct ipset_session *session, "Cannot allocate memory to duplicate %s.", str); - a = ipset_range_separator(tmp); + a = range_separator(tmp); if (a != NULL) { /* port-port */ *a++ = '\0'; @@ -256,14 +288,20 @@ error: * Returns 0 on success or a negative error code. */ int -ipset_parse_family(struct ipset_session *session, int opt, const char *str) +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")) @@ -273,7 +311,7 @@ ipset_parse_family(struct ipset_session *session, int opt, const char *str) else return syntax_err("unknown INET family %s", str); - return ipset_session_data_set(session, opt, &family); + return ipset_data_set(data, opt, &family); } /* @@ -316,8 +354,8 @@ get_addrinfo##f(struct ipset_session *session, \ int found; \ \ if ((*info = get_addrinfo(session, str, family)) == NULL) { \ - syntax_err("cannot parse %s: resolving " \ - IP " failed", str); \ + syntax_err("cannot parse %s: " IP " resolving failed", \ + str); \ return EINVAL; \ } \ \ @@ -347,7 +385,7 @@ static int \ parse_ipv##f(struct ipset_session *session, \ enum ipset_opt opt, const char *str) \ { \ - unsigned int m = mask; \ + uint8_t m = mask; \ int aerr = EINVAL, err = 0, range = 0; \ char *saved = strdup(str); \ char *a, *tmp = saved; \ @@ -361,14 +399,14 @@ parse_ipv##f(struct ipset_session *session, \ return ipset_err(session, \ "Cannot allocate memory to duplicate %s.",\ str); \ - if ((a = ipset_cidr_separator(tmp)) != NULL) { \ + if ((a = cidr_separator(tmp)) != NULL) { \ /* IP/mask */ \ *a++ = '\0'; \ \ - if ((err = string_to_number(session, a, 0, m, &m)) != 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 = ipset_range_separator(tmp)) != NULL) { \ + } else if ((a = range_separator(tmp)) != NULL) { \ /* IP-IP */ \ *a++ = '\0'; \ D("range %s", a); \ @@ -420,17 +458,17 @@ parse_ip(struct ipset_session *session, switch (addrtype) { case IPADDR_PLAIN: - if (ipset_range_separator(str) || ipset_cidr_separator(str)) + if (range_separator(str) || cidr_separator(str)) return syntax_err("plain IP address must be supplied: %s", str); break; case IPADDR_NET: - if (!ipset_cidr_separator(str) || ipset_range_separator(str)) + if (!cidr_separator(str) || range_separator(str)) return syntax_err("IP/netblock must be supplied: %s", str); break; case IPADDR_RANGE: - if (!ipset_range_separator(str) || ipset_cidr_separator(str)) + if (!range_separator(str) || cidr_separator(str)) return syntax_err("IP-IP range must supplied: %s", str); break; @@ -539,7 +577,7 @@ ipset_parse_range(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); - assert(opt == IPSET_OPT_IP); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); return parse_ip(session, IPSET_OPT_IP, str, IPADDR_RANGE); @@ -563,15 +601,118 @@ ipset_parse_netrange(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); - assert(opt == IPSET_OPT_IP); + assert(opt == IPSET_OPT_IP || opt == IPSET_OPT_IP2); assert(str); - if (!(ipset_range_separator(str) || ipset_cidr_separator(str))) - return syntax_err("IP/net or IP-IP range must be specified: %s", + 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) { \ @@ -584,7 +725,7 @@ do { \ /** - * ipset_parse_name - parse setname as element + * ipset_parse_name_compat - parse setname as element * @session: session structure * @opt: option kind of the data * @str: string to parse @@ -597,8 +738,8 @@ do { \ * Returns 0 on success or a negative error code. */ int -ipset_parse_name(struct ipset_session *session, - enum ipset_opt opt, const char *str) +ipset_parse_name_compat(struct ipset_session *session, + enum ipset_opt opt, const char *str) { char *saved; char *a = NULL, *b = NULL, *tmp; @@ -607,25 +748,22 @@ ipset_parse_name(struct ipset_session *session, struct ipset_data *data; assert(session); - assert(opt == IPSET_OPT_NAME || opt == IPSET_OPT_SETNAME2); + assert(opt == IPSET_OPT_NAME); assert(str); data = ipset_session_data(session); - if (opt == IPSET_OPT_SETNAME2) { - check_setname(str, NULL); - - return ipset_data_set(data, opt, str); - } + 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 = ipset_elem_separator(tmp)) != NULL) { + if ((a = elem_separator(tmp)) != NULL) { /* setname,[before|after,setname */ *a++ = '\0'; - if ((b = ipset_elem_separator(a)) != NULL) + if ((b = elem_separator(a)) != NULL) *b++ = '\0'; if (b == NULL || !(STREQ(a, "before") || STREQ(a, "after"))) { @@ -644,7 +782,7 @@ ipset_parse_name(struct ipset_session *session, if ((err = ipset_data_set(data, IPSET_OPT_NAMEREF, b)) != 0) goto out; - + if (before) err = ipset_data_set(data, IPSET_OPT_BEFORE, &before); @@ -654,12 +792,12 @@ out: } /** - * ipset_parse_setname - parse name as the name of the (current) set + * ipset_parse_setname - parse string as a setname * @session: session structure * @opt: option kind of the data * @str: string to parse * - * Parse string as the name of the (current) set. + * 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. @@ -669,7 +807,9 @@ ipset_parse_setname(struct ipset_session *session, enum ipset_opt opt, const char *str) { assert(session); - assert(opt == IPSET_SETNAME); + assert(opt == IPSET_SETNAME + || opt == IPSET_OPT_NAME + || opt == IPSET_OPT_SETNAME2); assert(str); check_setname(str, NULL); @@ -677,6 +817,67 @@ ipset_parse_setname(struct ipset_session *session, 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 @@ -698,7 +899,7 @@ ipset_parse_uint32(struct ipset_session *session, assert(session); assert(str); - if ((err = string_to_number(session, str, 0, 0, &value)) == 0) + if ((err = string_to_u32(session, str, &value)) == 0) return ipset_session_data_set(session, opt, &value); return err; @@ -719,13 +920,13 @@ int ipset_parse_uint8(struct ipset_session *session, enum ipset_opt opt, const char *str) { - unsigned int value; + uint8_t value; int err; assert(session); assert(str); - if ((err = string_to_number(session, str, 0, 255, &value)) == 0) + if ((err = string_to_u8(session, str, &value)) == 0) return ipset_session_data_set(session, opt, &value); return err; @@ -747,7 +948,7 @@ int ipset_parse_netmask(struct ipset_session *session, enum ipset_opt opt, const char *str) { - unsigned int family, cidr; + uint8_t family, cidr; struct ipset_data *data; int err = 0; @@ -762,10 +963,10 @@ ipset_parse_netmask(struct ipset_session *session, ipset_data_set(data, IPSET_OPT_FAMILY, &family); } - err = string_to_number(session, str, - family == AF_INET ? 1 : 4, - family == AF_INET ? 31 : 124, - &cidr); + 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 " @@ -864,15 +1065,72 @@ ipset_parse_output(struct ipset_session *session, 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) \ + if (!(t)->elem[d].parse) \ goto internal; \ - err = t->elem[d].parse(s, t->elem[d].opt, str); \ - if (err) \ + 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 @@ -890,7 +1148,7 @@ ipset_parse_elem(struct ipset_session *session, { const struct ipset_type *type; char *a = NULL, *b = NULL, *tmp, *saved; - int err; + int ret; assert(session); assert(str); @@ -906,40 +1164,43 @@ ipset_parse_elem(struct ipset_session *session, "Cannot allocate memory to duplicate %s.", str); - a = ipset_elem_separator(tmp); + a = elem_separator(tmp); if (type->dimension > IPSET_DIM_ONE) { if (a != NULL) { /* elem,elem */ *a++ = '\0'; - } else if (type->dimension > IPSET_DIM_TWO && !optional) { - free(tmp); - return syntax_err("Second element is missing from %s.", - str); + } 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; } - } else if (a != NULL) - return syntax_err("Elem separator in %s, " - "but settype %s supports none.", - str, type->name); + elem_syntax_err("Elem separator in %s, " + "but settype %s supports none.", + str, type->name); + } if (a) - b = ipset_elem_separator(a); + b = elem_separator(a); if (type->dimension > IPSET_DIM_TWO) { if (b != NULL) { /* elem,elem,elem */ *b++ = '\0'; - } else if (!optional) { - free(tmp); - return syntax_err("Third element is missing from %s.", - str); - } + } else if (!optional) + elem_syntax_err("Third element is missing from %s.", + str); } else if (b != NULL) - return syntax_err("Two elem separators in %s, " - "but settype %s supports one.", - str, type->name); - if (b != NULL && ipset_elem_separator(b)) - return syntax_err("Three elem separators in %s, " - "but settype %s supports two.", - str, type->name); + 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); @@ -954,10 +1215,10 @@ ipset_parse_elem(struct ipset_session *session, goto out; internal: - err = ipset_err(session, + ret = ipset_err(session, "Internal error: missing parser function for %s", type->name); out: free(saved); - return err; + return ret; } diff --git a/lib/print.c b/lib/print.c index 4df0905..d96e643 100644 --- a/lib/print.c +++ b/lib/print.c @@ -13,6 +13,7 @@ #include /* inet_ntop */ #include /* ETH_ALEN */ +#include /* D() */ #include /* ipset_data_* */ #include /* IPSET_*_SEPARATOR */ #include /* ipset set types */ @@ -86,7 +87,7 @@ ipset_print_ether(char *buf, unsigned int len, */ int ipset_print_family(char *buf, unsigned int len, - const struct ipset_data *data, int opt, + const struct ipset_data *data, enum ipset_opt opt, uint8_t env UNUSED) { uint8_t family; @@ -172,10 +173,10 @@ snprintf_ipv##f(char *buf, unsigned int len, int flags, \ size = __getnameinfo##f(buf, len, flags, ip); \ SNPRINTF_FAILURE(size, len, offset); \ \ + D("cidr %u mask %u", cidr, mask); \ if (cidr == mask) \ return offset; \ - if ((unsigned int)(size + 5) < len) \ - return -1; \ + D("print cidr"); \ size = snprintf(buf + offset, len, \ "%s%u", IPSET_CIDR_SEPARATOR, cidr); \ SNPRINTF_FAILURE(size, len, offset); \ @@ -218,9 +219,10 @@ ipset_print_ip(char *buf, unsigned int len, D("len: %u", len); family = ipset_data_family(data); cidropt = opt == IPSET_OPT_IP ? IPSET_OPT_CIDR : IPSET_OPT_CIDR2; - if (ipset_data_test(data, cidropt)) + if (ipset_data_test(data, cidropt)) { cidr = *(uint8_t *) ipset_data_get(data, cidropt); - else + D("CIDR: %u", cidr); + } else cidr = family == AF_INET6 ? 128 : 32; flags = env & (1 << IPSET_ENV_RESOLVE) ? 0 : NI_NUMERICHOST; @@ -373,11 +375,14 @@ ipset_print_name(char *buf, unsigned int len, SNPRINTF_FAILURE(size, len, offset); if (ipset_data_test(data, IPSET_OPT_NAMEREF)) { - bool before = ipset_data_test(data, IPSET_OPT_BEFORE); + bool before = false; + if (ipset_data_flags_test(data, IPSET_FLAG(IPSET_OPT_FLAGS))) { + uint32_t *flags = + (uint32_t *)ipset_data_get(data, IPSET_OPT_FLAGS); + before = (*flags) & IPSET_FLAG_BEFORE; + } size = snprintf(buf + offset, len, - "%s%s%s%s", IPSET_ELEM_SEPARATOR, - before ? "before" : "after", - IPSET_ELEM_SEPARATOR, + " %s %s", before ? "before" : "after", (const char *) ipset_data_get(data, IPSET_OPT_NAMEREF)); SNPRINTF_FAILURE(size, len, offset); @@ -468,8 +473,8 @@ ipset_print_elem(char *buf, unsigned int len, size = type->elem[IPSET_DIM_ONE].print(buf, len, data, type->elem[IPSET_DIM_ONE].opt, env); SNPRINTF_FAILURE(size, len, offset); - if (ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt)) - D("print second elem"); + IF_D(ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt), + "print second elem"); if (type->dimension == IPSET_DIM_ONE || (type->last_elem_optional && !ipset_data_test(data, type->elem[IPSET_DIM_TWO].opt))) diff --git a/lib/session.c b/lib/session.c index 2c4e39a..2c85468 100644 --- a/lib/session.c +++ b/lib/session.c @@ -13,6 +13,7 @@ #include /* getpagesize */ #include /* ETH_ALEN */ +#include /* D() */ #include /* IPSET_OPT_* */ #include /* ipset_errcode */ #include /* ipset_print_* */ @@ -189,6 +190,8 @@ ipset_session_report(struct ipset_session *session, if (len >= IPSET_ERRORBUFLEN - 1 - offset) session->report[IPSET_ERRORBUFLEN - 1] = '\0'; + if (strlen(session->report) < IPSET_ERRORBUFLEN - 1) + strcat(session->report, "\n"); if (type == IPSET_ERROR) { session->errmsg = session->report; @@ -278,6 +281,10 @@ const struct ipset_attr_policy cmd_attrs[] = { .type = MNL_TYPE_U8, .opt = IPSET_OPT_FAMILY, }, + [IPSET_ATTR_FLAGS] = { + .type = MNL_TYPE_U32, + .opt = IPSET_OPT_FLAGS, + }, [IPSET_ATTR_DATA] = { .type = MNL_TYPE_NESTED, }, @@ -320,9 +327,9 @@ const struct ipset_attr_policy create_attrs[] = { .type = MNL_TYPE_U32, .opt = IPSET_OPT_TIMEOUT, }, - [IPSET_ATTR_FLAGS] = { + [IPSET_ATTR_CADT_FLAGS] = { .type = MNL_TYPE_U32, - .opt = IPSET_OPT_FLAGS, + .opt = IPSET_OPT_CADT_FLAGS, }, [IPSET_ATTR_GC] = { .type = MNL_TYPE_U32, @@ -391,9 +398,9 @@ const struct ipset_attr_policy adt_attrs[] = { .type = MNL_TYPE_U32, .opt = IPSET_OPT_TIMEOUT, }, - [IPSET_ATTR_FLAGS] = { + [IPSET_ATTR_CADT_FLAGS] = { .type = MNL_TYPE_U32, - .opt = IPSET_OPT_FLAGS, + .opt = IPSET_OPT_CADT_FLAGS, }, [IPSET_ATTR_LINENO] = { .type = MNL_TYPE_U32, @@ -434,6 +441,7 @@ attr2data(struct ipset_session *session, struct nlattr *nla[], struct ipset_data *data = session->data; const struct ipset_attr_policy *attr; const void *d; + int ret; attr = &attrs[type]; d = mnl_attr_get_payload(nla[type]); @@ -480,7 +488,14 @@ attr2data(struct ipset_session *session, struct nlattr *nla[], break; } } - return ipset_data_set(data, attr->opt, d); + if (type == IPSET_ATTR_TYPENAME) + D("nla typename %s", (char *) d); + ret = ipset_data_set(data, attr->opt, d); + if (type == IPSET_ATTR_TYPENAME) + D("nla typename %s", + (char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); + + return ret; } #define ATTR2DATA(session, nla, type, attrs) \ @@ -579,8 +594,9 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) const struct ipset_type *type; const struct ipset_arg *arg; uint8_t family; - int i; - + int i, found = 0; + + D("enter"); /* Check and load type, family */ if (!ipset_data_test(data, IPSET_OPT_TYPE)) type = ipset_type_get(session, IPSET_CMD_ADD); @@ -591,6 +607,15 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) return MNL_CB_ERROR; family = ipset_data_family(data); + for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) + if (nla[i]) { + found++; + ATTR2DATA(session, nla, i, adt_attrs); + } + D("attr found %u", found); + if (!found) + return MNL_CB_OK; + switch (session->mode) { case IPSET_LIST_SAVE: safe_snprintf(session, "add %s ", ipset_data_setname(data)); @@ -603,10 +628,6 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) break; } - for (i = IPSET_ATTR_UNSPEC + 1; i <= IPSET_ATTR_ADT_MAX; i++) - if (nla[i]) - ATTR2DATA(session, nla, i, adt_attrs); - safe_dprintf(session, ipset_print_elem, IPSET_OPT_ELEM); for (arg = type->args[IPSET_ADD]; arg != NULL && arg->print; arg++) { @@ -646,6 +667,10 @@ list_adt(struct ipset_session *session, struct nlattr *nla[]) return MNL_CB_OK; } +#define FAMILY_TO_STR(f) \ + ((f) == AF_INET ? "inet" : \ + (f) == AF_INET6 ? "inet6" : "any") + static int list_create(struct ipset_session *session, struct nlattr *nla[]) { @@ -671,33 +696,29 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) safe_snprintf(session, "create %s %s ", ipset_data_setname(data), type->name); - if (family == AF_INET6) - sprintf(session->outbuf, "family inet6 "); break; case IPSET_LIST_PLAIN: - safe_snprintf(session, "Name: %s\nType: %s\n", + safe_snprintf(session, "Name: %s\n" + "Type: %s\nHeader: ", ipset_data_setname(data), type->name); - if (family == AF_INET6) - safe_snprintf(session, "Family: INET6\n"); - safe_snprintf(session, "Header: "); break; case IPSET_LIST_XML: safe_snprintf(session, "\n" - " %s\n", + " %s\n" + "
\n", ipset_data_setname(data), type->name); - if (family == AF_INET6) - safe_snprintf(session, " INET6\n"); - safe_snprintf(session, "
\n"); break; default: break; } - for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->print; arg++) { - if (!ipset_data_test(data, arg->opt)) + for (arg = type->args[IPSET_CREATE]; arg != NULL && arg->opt; arg++) { + if (!arg->print + || !ipset_data_test(data, arg->opt) + || (arg->opt == IPSET_OPT_FAMILY && family == type->family)) continue; switch (session->mode) { case IPSET_LIST_SAVE: @@ -730,8 +751,6 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) safe_snprintf(session, "\n"); break; case IPSET_LIST_PLAIN: - safe_snprintf(session, "\nElements: "); - safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); safe_snprintf(session, "\nSize in memory: "); safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); safe_snprintf(session, "\nReferences: "); @@ -739,8 +758,6 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) safe_snprintf(session, "\nMembers:\n"); break; case IPSET_LIST_XML: - safe_snprintf(session, " "); - safe_dprintf(session, ipset_print_number, IPSET_OPT_ELEMENTS); safe_snprintf(session, "\n "); safe_dprintf(session, ipset_print_number, IPSET_OPT_MEMSIZE); safe_snprintf(session, "\n "); @@ -757,7 +774,8 @@ list_create(struct ipset_session *session, struct nlattr *nla[]) static int print_set_done(struct ipset_session *session) { - D("called"); + D("called for %s", session->saved_setname[0] == '\0' + ? "NONE" : session->saved_setname); switch (session->mode) { case IPSET_LIST_XML: if (session->saved_setname[0] == '\0') @@ -824,6 +842,7 @@ callback_list(struct ipset_session *session, struct nlattr *nla[], cmd2name[cmd]); ATTR2DATA(session, nla, IPSET_ATTR_SETNAME, cmd_attrs); + D("setname %s", ipset_data_setname(data)); if (STREQ(ipset_data_setname(data), session->saved_setname)) { /* Header part already seen */ if (ipset_data_test(data, IPSET_OPT_TYPE) @@ -851,12 +870,18 @@ callback_list(struct ipset_session *session, struct nlattr *nla[], !nla[IPSET_ATTR_TYPENAME] ? "typename" : !nla[IPSET_ATTR_FAMILY] ? "family" : "revision"); + /* Reset CREATE specific flags */ + ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); + D("nla typename %s", + (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); + D("nla typename %s", + (char *) mnl_attr_get_payload(nla[IPSET_ATTR_TYPENAME])); ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); - - /* Reset CREATE specific flags */ - ipset_data_flags_unset(data, IPSET_CREATE_FLAGS); + D("head: family %u, typename %s", + ipset_data_family(data), + (char *) ipset_data_get(data, IPSET_OPT_TYPENAME)); if (mnl_attr_parse_nested(nla[IPSET_ATTR_DATA], create_attr_cb, cattr) < 0) FAILURE("Broken %s kernel message: " @@ -869,8 +894,9 @@ callback_list(struct ipset_session *session, struct nlattr *nla[], if (nla[IPSET_ATTR_ADT] != NULL) { struct nlattr *tb, *adt[IPSET_ATTR_ADT_MAX+1]; - + mnl_attr_for_each_nested(tb, nla[IPSET_ATTR_ADT]) { + D("ADT attributes for %s", ipset_data_setname(data)); memset(adt, 0, sizeof(adt)); /* Reset ADT specific flags */ ipset_data_flags_unset(data, IPSET_ADT_FLAGS); @@ -950,6 +976,7 @@ callback_header(struct ipset_session *session, struct nlattr *nla[]) ATTR2DATA(session, nla, IPSET_ATTR_TYPENAME, cmd_attrs); ATTR2DATA(session, nla, IPSET_ATTR_REVISION, cmd_attrs); ATTR2DATA(session, nla, IPSET_ATTR_FAMILY, cmd_attrs); + D("got family: %u", ipset_data_family(session->data)); return MNL_CB_STOP; } @@ -1150,7 +1177,8 @@ callback_error(const struct nlmsghdr *nlh, void *cbdata) case IPSET_CMD_CREATE: /* Add successfully created set to the cache */ ipset_cache_add(ipset_data_setname(data), - ipset_data_get(data, IPSET_OPT_TYPE)); + ipset_data_get(data, IPSET_OPT_TYPE), + ipset_data_family(data)); break; case IPSET_CMD_DESTROY: /* Delete destroyed sets from the cache */ @@ -1750,7 +1778,7 @@ ipset_session_init(ipset_outfn outfn) if (session->data == NULL) goto free_session; - ipset_types_init(); + ipset_cache_init(); return session; free_session: @@ -1776,7 +1804,7 @@ ipset_session_fini(struct ipset_session *session) if (session->data) ipset_data_fini(session->data); - ipset_types_fini(); + ipset_cache_fini(); free(session); return 0; } diff --git a/lib/types.c b/lib/types.c index a6476ea..b39a04f 100644 --- a/lib/types.c +++ b/lib/types.c @@ -13,6 +13,7 @@ #include /* malloc, free */ #include /* FIXME: debug */ +#include /* D() */ #include /* ipset_data_* */ #include /* ipset_cmd */ #include /* STREQ */ @@ -23,6 +24,7 @@ struct ipset { char name[IPSET_MAXNAMELEN]; /* set name */ const struct ipset_type *type; /* set type */ + uint8_t family; /* family */ struct ipset *next; }; @@ -40,7 +42,8 @@ static struct ipset *setlist = NULL; /* cached sets */ * Returns 0 on success or a negative error code. */ int -ipset_cache_add(const char *name, const struct ipset_type *type) +ipset_cache_add(const char *name, const struct ipset_type *type, + uint8_t family) { struct ipset *s, *n; @@ -53,13 +56,14 @@ ipset_cache_add(const char *name, const struct ipset_type *type) ipset_strncpy(n->name, name, IPSET_MAXNAMELEN); n->type = type; + n->family = family; n->next = NULL; if (setlist == NULL) { setlist = n; return 0; } - for (s = setlist; s->next == NULL; s = s->next) { + for (s = setlist; s->next != NULL; s = s->next) { if (STREQ(name, s->name)) { free(n); return -EEXIST; @@ -171,6 +175,22 @@ ipset_cache_swap(const char *from, const char *to) #define MATCH_FAMILY(type, f) \ (f == AF_UNSPEC || type->family == f || type->family == AF_INET46) +bool +ipset_match_typename(const char *name, const struct ipset_type *type) +{ + const char * const * alias = type->alias; + + if (STREQ(name, type->name)) + return true; + + while (alias[0]) { + if (STREQ(name, alias[0])) + return true; + alias++; + } + return false; +} + static inline const struct ipset_type * create_type_get(struct ipset_session *session) { @@ -192,7 +212,7 @@ create_type_get(struct ipset_session *session) /* Skip revisions which are unsupported by the kernel */ if (t->kernel_check == IPSET_KERNEL_MISMATCH) continue; - if ((STREQ(typename, t->name) || STREQ(typename, t->alias)) + if (ipset_match_typename(typename, t) && MATCH_FAMILY(t, family)) { if (match == NULL) { match = t; @@ -390,8 +410,9 @@ ipset_type_check(struct ipset_session *session) for (t = typelist; t != NULL && match == NULL; t = t->next) { if (t->kernel_check == IPSET_KERNEL_MISMATCH) continue; - if ((STREQ(typename, t->name) || STREQ(typename, t->alias)) - && MATCH_FAMILY(t, family) && t->revision == revision) + if (ipset_match_typename(typename, t) + && MATCH_FAMILY(t, family) + && t->revision == revision) match = t; } if (!match) @@ -503,7 +524,7 @@ ipset_typename_resolve(const char *str) const struct ipset_type *t; for (t = typelist; t != NULL; t = t->next) - if (STREQ(str, t->name) || STREQ(str, t->alias)) + if (ipset_match_typename(str, t)) return t->name; return NULL; } @@ -523,38 +544,25 @@ ipset_types(void) } /** - * ipset_types_init - initialize known set types + * ipset_cache_init - initialize set cache * - * Initialize the type list with the known, supported set types. + * Initialize the set cache in userspace. * * Returns 0 on success or a negative error code. */ int -ipset_types_init(void) +ipset_cache_init(void) { - if (typelist != NULL) - return 0; - - ipset_type_add(&ipset_bitmap_ip0); - ipset_type_add(&ipset_bitmap_ipmac0); - ipset_type_add(&ipset_bitmap_port0); - ipset_type_add(&ipset_hash_ip0); - ipset_type_add(&ipset_hash_net0); - ipset_type_add(&ipset_hash_ipport0); - ipset_type_add(&ipset_hash_ipportip0); - ipset_type_add(&ipset_hash_ipportnet0); - ipset_type_add(&ipset_tree_ip0); - ipset_type_add(&ipset_list_set0); return 0; } /** - * ipset_types_fini - release initialized known set types + * ipset_cache_fini - release the set cache * - * Release initialized known set types and remove the set cache. + * Release the set cache. */ void -ipset_types_fini(void) +ipset_cache_fini(void) { struct ipset *set; diff --git a/lib/utils.c b/lib/utils.c deleted file mode 100644 index bddeb87..0000000 --- a/lib/utils.c +++ /dev/null @@ -1,103 +0,0 @@ -/* Copyright 2007-20010 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 /* bool */ -#include /* malloc, free */ -#include /* memset, str* */ - -#include /* ipset_err */ -#include /* prototypes */ - -/** - * ipset_strchr - locate character(s) in string - * @str: string to locate the character(s) in - * @sep: string of characters to locate - * - * Return a pointer to the first occurence of any of the - * characters to be located in the string. NULL is returned - * if no character is found. - */ -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) - return match; - - return NULL; -} - -/** - * ipset_name_match - match a string against an array of strings - * @arg: string - * @name: array of strings, last one is a NULL pointer - * - * Return true if arg matches any of the strings in the array. - */ -bool -ipset_name_match(const char *arg, const char * const name[]) -{ - int i = 0; - - assert(arg); - assert(name); - - while (name[i]) { - if (STREQ(arg, name[i])) - return true; - i++; - } - - return false; -} - -/** - * ipset_shift_argv - shift off an argument - * @arc: argument count - * @argv: array of argument strings - * @from: from where shift off an argument - * - * Shift off the argument at "from" from the array of - * arguments argv of size argc. - */ -void -ipset_shift_argv(int *argc, char *argv[], int from) -{ - int i; - - assert(*argc >= from + 1); - - for (i = from + 1; i <= *argc; i++) { - argv[i-1] = argv[i]; - } - (*argc)--; - return; -} - -/** - * ipset_strncpy - copy the string from src to dst - * @dst: the target string buffer - * @src: the source string buffer - * @len: the length of bytes to copy, including the terminating null byte. - * - * Copy the string from src to destination, but at most len bytes are - * copied. The target is unconditionally terminated by the null byte. - */ -void -ipset_strncpy(char *dst, const char *src, size_t len) -{ - assert(dst); - assert(src); - - strncpy(dst, src, len); - dst[len - 1] = '\0'; -} -- cgit v1.2.3