--- /dev/null
+From c2bcd1e183bcc5fdd63811c045355fc57e36ecfd Mon Sep 17 00:00:00 2001
+From: Simon Kelley <simon@thekelleys.org.uk>
+Date: Tue, 15 Dec 2015 17:25:21 +0000
+Subject: [PATCH] Generalise RR-filtering code, for use with EDNS0.
+
+---
+ Makefile | 3 +-
+ bld/Android.mk | 2 +-
+ src/dnsmasq.h | 5 +
+ src/dnssec.c | 307 +-------------------------------------------------
+ src/forward.c | 2 +-
+ src/rrfilter.c | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ 6 files changed, 349 insertions(+), 309 deletions(-)
+ create mode 100644 src/rrfilter.c
+
+diff --git a/Makefile b/Makefile
+index 4c87ea9..b664160 100644
+--- a/Makefile
++++ b/Makefile
+@@ -73,7 +73,8 @@ objs = cache.o rfc1035.o util.o option.o forward.o network.o \
+ dnsmasq.o dhcp.o lease.o rfc2131.o netlink.o dbus.o bpf.o \
+ helper.o tftp.o log.o conntrack.o dhcp6.o rfc3315.o \
+ dhcp-common.o outpacket.o radv.o slaac.o auth.o ipset.o \
+- domain.o dnssec.o blockdata.o tables.o loop.o inotify.o poll.o
++ domain.o dnssec.o blockdata.o tables.o loop.o inotify.o \
++ poll.o rrfilter.o
+
+ hdrs = dnsmasq.h config.h dhcp-protocol.h dhcp6-protocol.h \
+ dns-protocol.h radv-protocol.h ip6addr.h
+diff --git a/bld/Android.mk b/bld/Android.mk
+index 5364ee7..67b9c4b 100644
+--- a/bld/Android.mk
++++ b/bld/Android.mk
+@@ -10,7 +10,7 @@ LOCAL_SRC_FILES := bpf.c cache.c dbus.c dhcp.c dnsmasq.c \
+ dhcp6.c rfc3315.c dhcp-common.c outpacket.c \
+ radv.c slaac.c auth.c ipset.c domain.c \
+ dnssec.c dnssec-openssl.c blockdata.c tables.c \
+- loop.c inotify.c poll.c
++ loop.c inotify.c poll.c rrfilter.c
+
+ LOCAL_MODULE := dnsmasq
+
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index 4344cae..39a930c 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -1513,3 +1513,8 @@ int poll_check(int fd, short event);
+ void poll_listen(int fd, short event);
+ int do_poll(int timeout);
+
++/* rrfilter.c */
++size_t rrfilter(struct dns_header *header, size_t plen, int mode);
++u16 *rrfilter_desc(int type);
++int expand_workspace(unsigned char ***wkspc, int *szp, int new);
++
+diff --git a/src/dnssec.c b/src/dnssec.c
+index 359231f..fa3eb81 100644
+--- a/src/dnssec.c
++++ b/src/dnssec.c
+@@ -507,50 +507,6 @@ static int check_date_range(unsigned long date_start, unsigned long date_end)
+ && serial_compare_32(curtime, date_end) == SERIAL_LT;
+ }
+
+-static u16 *get_desc(int type)
+-{
+- /* List of RRtypes which include domains in the data.
+- 0 -> domain
+- integer -> no of plain bytes
+- -1 -> end
+-
+- zero is not a valid RRtype, so the final entry is returned for
+- anything which needs no mangling.
+- */
+-
+- static u16 rr_desc[] =
+- {
+- T_NS, 0, -1,
+- T_MD, 0, -1,
+- T_MF, 0, -1,
+- T_CNAME, 0, -1,
+- T_SOA, 0, 0, -1,
+- T_MB, 0, -1,
+- T_MG, 0, -1,
+- T_MR, 0, -1,
+- T_PTR, 0, -1,
+- T_MINFO, 0, 0, -1,
+- T_MX, 2, 0, -1,
+- T_RP, 0, 0, -1,
+- T_AFSDB, 2, 0, -1,
+- T_RT, 2, 0, -1,
+- T_SIG, 18, 0, -1,
+- T_PX, 2, 0, 0, -1,
+- T_NXT, 0, -1,
+- T_KX, 2, 0, -1,
+- T_SRV, 6, 0, -1,
+- T_DNAME, 0, -1,
+- 0, -1 /* wildcard/catchall */
+- };
+-
+- u16 *p = rr_desc;
+-
+- while (*p != type && *p != 0)
+- while (*p++ != (u16)-1);
+-
+- return p+1;
+-}
+-
+ /* Return bytes of canonicalised rdata, when the return value is zero, the remaining
+ data, pointed to by *p, should be used raw. */
+ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end, char *buff, int bufflen,
+@@ -594,34 +550,6 @@ static int get_rdata(struct dns_header *header, size_t plen, unsigned char *end,
+ }
+ }
+
+-static int expand_workspace(unsigned char ***wkspc, int *szp, int new)
+-{
+- unsigned char **p;
+- int old = *szp;
+-
+- if (old >= new+1)
+- return 1;
+-
+- if (new >= 100)
+- return 0;
+-
+- new += 5;
+-
+- if (!(p = whine_malloc(new * sizeof(unsigned char **))))
+- return 0;
+-
+- if (old != 0 && *wkspc)
+- {
+- memcpy(p, *wkspc, old * sizeof(unsigned char **));
+- free(*wkspc);
+- }
+-
+- *wkspc = p;
+- *szp = new;
+-
+- return 1;
+-}
+-
+ /* Bubble sort the RRset into the canonical order.
+ Note that the byte-streams from two RRs may get unsynced: consider
+ RRs which have two domain-names at the start and then other data.
+@@ -849,7 +777,7 @@ static int validate_rrset(time_t now, struct dns_header *header, size_t plen, in
+ int rdlen, j, name_labels;
+ struct crec *crecp = NULL;
+ int algo, labels, orig_ttl, key_tag;
+- u16 *rr_desc = get_desc(type);
++ u16 *rr_desc = rrfilter_desc(type);
+
+ if (wildcard_out)
+ *wildcard_out = NULL;
+@@ -2266,239 +2194,6 @@ size_t dnssec_generate_query(struct dns_header *header, char *end, char *name, i
+ return ret;
+ }
+
+-/* Go through a domain name, find "pointers" and fix them up based on how many bytes
+- we've chopped out of the packet, or check they don't point into an elided part. */
+-static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+-{
+- unsigned char *ansp = *namep;
+-
+- while(1)
+- {
+- unsigned int label_type;
+-
+- if (!CHECK_LEN(header, ansp, plen, 1))
+- return 0;
+-
+- label_type = (*ansp) & 0xc0;
+-
+- if (label_type == 0xc0)
+- {
+- /* pointer for compression. */
+- unsigned int offset;
+- int i;
+- unsigned char *p;
+-
+- if (!CHECK_LEN(header, ansp, plen, 2))
+- return 0;
+-
+- offset = ((*ansp++) & 0x3f) << 8;
+- offset |= *ansp++;
+-
+- p = offset + (unsigned char *)header;
+-
+- for (i = 0; i < rr_count; i++)
+- if (p < rrs[i])
+- break;
+- else
+- if (i & 1)
+- offset -= rrs[i] - rrs[i-1];
+-
+- /* does the pointer end up in an elided RR? */
+- if (i & 1)
+- return 0;
+-
+- /* No, scale the pointer */
+- if (fixup)
+- {
+- ansp -= 2;
+- *ansp++ = (offset >> 8) | 0xc0;
+- *ansp++ = offset & 0xff;
+- }
+- break;
+- }
+- else if (label_type == 0x80)
+- return 0; /* reserved */
+- else if (label_type == 0x40)
+- {
+- /* Extended label type */
+- unsigned int count;
+-
+- if (!CHECK_LEN(header, ansp, plen, 2))
+- return 0;
+-
+- if (((*ansp++) & 0x3f) != 1)
+- return 0; /* we only understand bitstrings */
+-
+- count = *(ansp++); /* Bits in bitstring */
+-
+- if (count == 0) /* count == 0 means 256 bits */
+- ansp += 32;
+- else
+- ansp += ((count-1)>>3)+1;
+- }
+- else
+- { /* label type == 0 Bottom six bits is length */
+- unsigned int len = (*ansp++) & 0x3f;
+-
+- if (!ADD_RDLEN(header, ansp, plen, len))
+- return 0;
+-
+- if (len == 0)
+- break; /* zero length label marks the end. */
+- }
+- }
+-
+- *namep = ansp;
+-
+- return 1;
+-}
+-
+-/* Go through RRs and check or fixup the domain names contained within */
+-static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
+-{
+- int i, type, class, rdlen;
+- unsigned char *pp;
+-
+- for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
+- {
+- pp = p;
+-
+- if (!(p = skip_name(p, header, plen, 10)))
+- return 0;
+-
+- GETSHORT(type, p);
+- GETSHORT(class, p);
+- p += 4; /* TTL */
+- GETSHORT(rdlen, p);
+-
+- if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
+- {
+- /* fixup name of RR */
+- if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+- return 0;
+-
+- if (class == C_IN)
+- {
+- u16 *d;
+-
+- for (pp = p, d = get_desc(type); *d != (u16)-1; d++)
+- {
+- if (*d != 0)
+- pp += *d;
+- else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
+- return 0;
+- }
+- }
+- }
+-
+- if (!ADD_RDLEN(header, p, plen, rdlen))
+- return 0;
+- }
+-
+- return 1;
+-}
+-
+-
+-size_t filter_rrsigs(struct dns_header *header, size_t plen)
+-{
+- static unsigned char **rrs;
+- static int rr_sz = 0;
+-
+- unsigned char *p = (unsigned char *)(header+1);
+- int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
+-
+- if (ntohs(header->qdcount) != 1 ||
+- !(p = skip_name(p, header, plen, 4)))
+- return plen;
+-
+- GETSHORT(qtype, p);
+- GETSHORT(qclass, p);
+-
+- /* First pass, find pointers to start and end of all the records we wish to elide:
+- records added for DNSSEC, unless explicity queried for */
+- for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0;
+- i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
+- i++)
+- {
+- unsigned char *pstart = p;
+- int type, class;
+-
+- if (!(p = skip_name(p, header, plen, 10)))
+- return plen;
+-
+- GETSHORT(type, p);
+- GETSHORT(class, p);
+- p += 4; /* TTL */
+- GETSHORT(rdlen, p);
+-
+- if ((type == T_NSEC || type == T_NSEC3 || type == T_RRSIG) &&
+- (type != qtype || class != qclass))
+- {
+- if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
+- return plen;
+-
+- rrs[rr_found++] = pstart;
+-
+- if (!ADD_RDLEN(header, p, plen, rdlen))
+- return plen;
+-
+- rrs[rr_found++] = p;
+-
+- if (i < ntohs(header->ancount))
+- chop_an++;
+- else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
+- chop_ns++;
+- else
+- chop_ar++;
+- }
+- else if (!ADD_RDLEN(header, p, plen, rdlen))
+- return plen;
+- }
+-
+- /* Nothing to do. */
+- if (rr_found == 0)
+- return plen;
+-
+- /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
+- point to records we're going to elide. This is theoretically possible, but unlikely. If
+- it happens, we give up and leave the answer unchanged. */
+- p = (unsigned char *)(header+1);
+-
+- /* question first */
+- if (!check_name(&p, header, plen, 0, rrs, rr_found))
+- return plen;
+- p += 4; /* qclass, qtype */
+-
+- /* Now answers and NS */
+- if (!check_rrs(p, header, plen, 0, rrs, rr_found))
+- return plen;
+-
+- /* Third pass, elide records */
+- for (p = rrs[0], i = 1; i < rr_found; i += 2)
+- {
+- unsigned char *start = rrs[i];
+- unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
+-
+- memmove(p, start, end-start);
+- p += end-start;
+- }
+-
+- plen = p - (unsigned char *)header;
+- header->ancount = htons(ntohs(header->ancount) - chop_an);
+- header->nscount = htons(ntohs(header->nscount) - chop_ns);
+- header->arcount = htons(ntohs(header->arcount) - chop_ar);
+-
+- /* Fourth pass, fix up pointers in the remaining records */
+- p = (unsigned char *)(header+1);
+-
+- check_name(&p, header, plen, 1, rrs, rr_found);
+- p += 4; /* qclass, qtype */
+-
+- check_rrs(p, header, plen, 1, rrs, rr_found);
+-
+- return plen;
+-}
+-
+ unsigned char* hash_questions(struct dns_header *header, size_t plen, char *name)
+ {
+ int q;
+diff --git a/src/forward.c b/src/forward.c
+index dd22a62..3e801c8 100644
+--- a/src/forward.c
++++ b/src/forward.c
+@@ -662,7 +662,7 @@ static size_t process_reply(struct dns_header *header, time_t now, struct server
+
+ /* If the requestor didn't set the DO bit, don't return DNSSEC info. */
+ if (!do_bit)
+- n = filter_rrsigs(header, n);
++ n = rrfilter(header, n, 1);
+ #endif
+
+ /* do this after extract_addresses. Ensure NODATA reply and remove
+diff --git a/src/rrfilter.c b/src/rrfilter.c
+new file mode 100644
+index 0000000..ae12261
+--- /dev/null
++++ b/src/rrfilter.c
+@@ -0,0 +1,339 @@
++/* dnsmasq is Copyright (c) 2000-2015 Simon Kelley
++
++ This program is free software; you can redistribute it and/or modify
++ it under the terms of the GNU General Public License as published by
++ the Free Software Foundation; version 2 dated June, 1991, or
++ (at your option) version 3 dated 29 June, 2007.
++
++ This program is distributed in the hope that it will be useful,
++ but WITHOUT ANY WARRANTY; without even the implied warranty of
++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
++ GNU General Public License for more details.
++
++ You should have received a copy of the GNU General Public License
++ along with this program. If not, see <http://www.gnu.org/licenses/>.
++*/
++
++/* Code to safely remove RRs from an DNS answer */
++
++#include "dnsmasq.h"
++
++/* Go through a domain name, find "pointers" and fix them up based on how many bytes
++ we've chopped out of the packet, or check they don't point into an elided part. */
++static int check_name(unsigned char **namep, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
++{
++ unsigned char *ansp = *namep;
++
++ while(1)
++ {
++ unsigned int label_type;
++
++ if (!CHECK_LEN(header, ansp, plen, 1))
++ return 0;
++
++ label_type = (*ansp) & 0xc0;
++
++ if (label_type == 0xc0)
++ {
++ /* pointer for compression. */
++ unsigned int offset;
++ int i;
++ unsigned char *p;
++
++ if (!CHECK_LEN(header, ansp, plen, 2))
++ return 0;
++
++ offset = ((*ansp++) & 0x3f) << 8;
++ offset |= *ansp++;
++
++ p = offset + (unsigned char *)header;
++
++ for (i = 0; i < rr_count; i++)
++ if (p < rrs[i])
++ break;
++ else
++ if (i & 1)
++ offset -= rrs[i] - rrs[i-1];
++
++ /* does the pointer end up in an elided RR? */
++ if (i & 1)
++ return 0;
++
++ /* No, scale the pointer */
++ if (fixup)
++ {
++ ansp -= 2;
++ *ansp++ = (offset >> 8) | 0xc0;
++ *ansp++ = offset & 0xff;
++ }
++ break;
++ }
++ else if (label_type == 0x80)
++ return 0; /* reserved */
++ else if (label_type == 0x40)
++ {
++ /* Extended label type */
++ unsigned int count;
++
++ if (!CHECK_LEN(header, ansp, plen, 2))
++ return 0;
++
++ if (((*ansp++) & 0x3f) != 1)
++ return 0; /* we only understand bitstrings */
++
++ count = *(ansp++); /* Bits in bitstring */
++
++ if (count == 0) /* count == 0 means 256 bits */
++ ansp += 32;
++ else
++ ansp += ((count-1)>>3)+1;
++ }
++ else
++ { /* label type == 0 Bottom six bits is length */
++ unsigned int len = (*ansp++) & 0x3f;
++
++ if (!ADD_RDLEN(header, ansp, plen, len))
++ return 0;
++
++ if (len == 0)
++ break; /* zero length label marks the end. */
++ }
++ }
++
++ *namep = ansp;
++
++ return 1;
++}
++
++/* Go through RRs and check or fixup the domain names contained within */
++static int check_rrs(unsigned char *p, struct dns_header *header, size_t plen, int fixup, unsigned char **rrs, int rr_count)
++{
++ int i, j, type, class, rdlen;
++ unsigned char *pp;
++
++ for (i = 0; i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount); i++)
++ {
++ pp = p;
++
++ if (!(p = skip_name(p, header, plen, 10)))
++ return 0;
++
++ GETSHORT(type, p);
++ GETSHORT(class, p);
++ p += 4; /* TTL */
++ GETSHORT(rdlen, p);
++
++ /* If this RR is to be elided, don't fix up its contents */
++ for (j = 0; j < rr_count; j += 2)
++ if (rrs[j] == pp)
++ break;
++
++ if (j >= rr_count)
++ {
++ /* fixup name of RR */
++ if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
++ return 0;
++
++ if (class == C_IN)
++ {
++ u16 *d;
++
++ for (pp = p, d = rrfilter_desc(type); *d != (u16)-1; d++)
++ {
++ if (*d != 0)
++ pp += *d;
++ else if (!check_name(&pp, header, plen, fixup, rrs, rr_count))
++ return 0;
++ }
++ }
++ }
++
++ if (!ADD_RDLEN(header, p, plen, rdlen))
++ return 0;
++ }
++
++ return 1;
++}
++
++
++/* mode is 0 to remove EDNS0, 1 to filter DNSSEC RRs */
++size_t rrfilter(struct dns_header *header, size_t plen, int mode)
++{
++ static unsigned char **rrs;
++ static int rr_sz = 0;
++
++ unsigned char *p = (unsigned char *)(header+1);
++ int i, rdlen, qtype, qclass, rr_found, chop_an, chop_ns, chop_ar;
++
++ if (ntohs(header->qdcount) != 1 ||
++ !(p = skip_name(p, header, plen, 4)))
++ return plen;
++
++ GETSHORT(qtype, p);
++ GETSHORT(qclass, p);
++
++ /* First pass, find pointers to start and end of all the records we wish to elide:
++ records added for DNSSEC, unless explicity queried for */
++ for (rr_found = 0, chop_ns = 0, chop_an = 0, chop_ar = 0, i = 0;
++ i < ntohs(header->ancount) + ntohs(header->nscount) + ntohs(header->arcount);
++ i++)
++ {
++ unsigned char *pstart = p;
++ int type, class;
++
++ if (!(p = skip_name(p, header, plen, 10)))
++ return plen;
++
++ GETSHORT(type, p);
++ GETSHORT(class, p);
++ p += 4; /* TTL */
++ GETSHORT(rdlen, p);
++
++ if (!ADD_RDLEN(header, p, plen, rdlen))
++ return plen;
++
++ /* Don't remove the answer. */
++ if (i < ntohs(header->ancount) && type == qtype && class == qclass)
++ continue;
++
++ if (mode == 0) /* EDNS */
++ {
++ /* EDNS mode, remove T_OPT from additional section only */
++ if (i < (ntohs(header->nscount) + ntohs(header->ancount)) || type != T_OPT)
++ continue;
++ }
++ else if (type != T_NSEC && type != T_NSEC3 && type != T_RRSIG)
++ /* DNSSEC mode, remove SIGs and NSECs from all three sections. */
++ continue;
++
++
++ if (!expand_workspace(&rrs, &rr_sz, rr_found + 1))
++ return plen;
++
++ rrs[rr_found++] = pstart;
++ rrs[rr_found++] = p;
++
++ if (i < ntohs(header->ancount))
++ chop_an++;
++ else if (i < (ntohs(header->nscount) + ntohs(header->ancount)))
++ chop_ns++;
++ else
++ chop_ar++;
++ }
++
++ /* Nothing to do. */
++ if (rr_found == 0)
++ return plen;
++
++ /* Second pass, look for pointers in names in the records we're keeping and make sure they don't
++ point to records we're going to elide. This is theoretically possible, but unlikely. If
++ it happens, we give up and leave the answer unchanged. */
++ p = (unsigned char *)(header+1);
++
++ /* question first */
++ if (!check_name(&p, header, plen, 0, rrs, rr_found))
++ return plen;
++ p += 4; /* qclass, qtype */
++
++ /* Now answers and NS */
++ if (!check_rrs(p, header, plen, 0, rrs, rr_found))
++ return plen;
++
++ /* Third pass, elide records */
++ for (p = rrs[0], i = 1; i < rr_found; i += 2)
++ {
++ unsigned char *start = rrs[i];
++ unsigned char *end = (i != rr_found - 1) ? rrs[i+1] : ((unsigned char *)(header+1)) + plen;
++
++ memmove(p, start, end-start);
++ p += end-start;
++ }
++
++ plen = p - (unsigned char *)header;
++ header->ancount = htons(ntohs(header->ancount) - chop_an);
++ header->nscount = htons(ntohs(header->nscount) - chop_ns);
++ header->arcount = htons(ntohs(header->arcount) - chop_ar);
++
++ /* Fourth pass, fix up pointers in the remaining records */
++ p = (unsigned char *)(header+1);
++
++ check_name(&p, header, plen, 1, rrs, rr_found);
++ p += 4; /* qclass, qtype */
++
++ check_rrs(p, header, plen, 1, rrs, rr_found);
++
++ return plen;
++}
++
++/* This is used in the DNSSEC code too, hence it's exported */
++u16 *rrfilter_desc(int type)
++{
++ /* List of RRtypes which include domains in the data.
++ 0 -> domain
++ integer -> no of plain bytes
++ -1 -> end
++
++ zero is not a valid RRtype, so the final entry is returned for
++ anything which needs no mangling.
++ */
++
++ static u16 rr_desc[] =
++ {
++ T_NS, 0, -1,
++ T_MD, 0, -1,
++ T_MF, 0, -1,
++ T_CNAME, 0, -1,
++ T_SOA, 0, 0, -1,
++ T_MB, 0, -1,
++ T_MG, 0, -1,
++ T_MR, 0, -1,
++ T_PTR, 0, -1,
++ T_MINFO, 0, 0, -1,
++ T_MX, 2, 0, -1,
++ T_RP, 0, 0, -1,
++ T_AFSDB, 2, 0, -1,
++ T_RT, 2, 0, -1,
++ T_SIG, 18, 0, -1,
++ T_PX, 2, 0, 0, -1,
++ T_NXT, 0, -1,
++ T_KX, 2, 0, -1,
++ T_SRV, 6, 0, -1,
++ T_DNAME, 0, -1,
++ 0, -1 /* wildcard/catchall */
++ };
++
++ u16 *p = rr_desc;
++
++ while (*p != type && *p != 0)
++ while (*p++ != (u16)-1);
++
++ return p+1;
++}
++
++int expand_workspace(unsigned char ***wkspc, int *szp, int new)
++{
++ unsigned char **p;
++ int old = *szp;
++
++ if (old >= new+1)
++ return 1;
++
++ if (new >= 100)
++ return 0;
++
++ new += 5;
++
++ if (!(p = whine_malloc(new * sizeof(unsigned char **))))
++ return 0;
++
++ if (old != 0 && *wkspc)
++ {
++ memcpy(p, *wkspc, old * sizeof(unsigned char **));
++ free(*wkspc);
++ }
++
++ *wkspc = p;
++ *szp = new;
++
++ return 1;
++}
+--
+1.7.10.4
+