diff --git a/Makefile b/Makefile
index fc3d0a0..0e40d6c 100644
--- a/Makefile
+++ b/Makefile
@@ -14,11 +14,19 @@
 # limitations under the License.
 #
 
-CURLINCL = `curl-config --cflags` 
-JANSINCL = -I/usr/local/include
+# Base directory for jansson header and libraries
+JANSBASE=/usr/local
+# For macOS on M1, use this instead of the above line:
+#JANSBASE=/opt/homebrew
 
+JANSINCL = -I$(JANSBASE)/include
+
+JANSLIBS = -L$(JANSBASE)/lib -ljansson
+# For almost static builds on macOS, use this instead of the above line:
+#JANSLIBS = $(JANSBASE)/lib/libjansson.a
+
+CURLINCL = `curl-config --cflags`
 CURLLIBS = `[ ! -z "$$(curl-config --libs)" ] && curl-config --libs || curl-config --static-libs`
-JANSLIBS = -L/usr/local/lib -ljansson
 
 CWARN =-W -Wall -Wextra -Wcast-qual -Wpointer-arith -Wwrite-strings \
 	-Wmissing-prototypes  -Wbad-function-cast -Wnested-externs \
@@ -33,17 +41,19 @@ CGPROF =
 CDEBUG = -g -O3
 CFLAGS += $(CGPROF) $(CDEBUG) $(CWARN) $(CDEFS)
 INCL= $(CURLINCL) $(JANSINCL)
-# freebsd requires that -lresolv _not_ be used here
 LIBS= $(CURLLIBS) $(JANSLIBS) -lresolv
+# For freebsd, it requires that -lresolv _not_ be used here, use this instead of the above line:
 #LIBS= $(CURLLIBS) $(JANSLIBS)
 
 TOOL = dnsdbq
 TOOL_OBJ = $(TOOL).o ns_ttl.o netio.o \
 	pdns.o pdns_circl.o pdns_dnsdb.o \
-	sort.o time.o asinfo.o deduper.o
+	sort.o time.o asinfo.o deduper.o \
+	tokstr.o
 TOOL_SRC = $(TOOL).c ns_ttl.c netio.c \
 	pdns.c pdns_circl.c pdns_dnsdb.c \
-	sort.c time.c asinfo.c deduper.c
+	sort.c time.c asinfo.c deduper.c \
+	tokstr.c
 
 all: $(TOOL)
 
@@ -77,7 +87,7 @@ asinfo.o: asinfo.c \
   asinfo.h globals.h defs.h sort.h pdns.h netio.h
 dnsdbq.o: dnsdbq.c \
   defs.h netio.h \
-  pdns.h \
+  pdns.h tokstr.h \
   pdns_dnsdb.h pdns_circl.h sort.h \
   time.h globals.h
 ns_ttl.o: ns_ttl.c \
@@ -91,7 +101,7 @@ pdns.o: pdns.c defs.h \
   netio.h \
   pdns.h \
   time.h \
-  globals.h sort.h
+  globals.h sort.h tokstr.h
 pdns_circl.o: pdns_circl.c \
   defs.h \
   pdns.h \
@@ -111,3 +121,5 @@ time.o: time.c \
   globals.h sort.h pdns.h \
   netio.h \
   ns_ttl.h
+tokstr.o: tokstr.c \
+  tokstr.h
diff --git a/README b/README
index 03f2ebd..445ca86 100644
--- a/README
+++ b/README
@@ -108,17 +108,21 @@ Building and installing:
     On FreeBSD, you may need to remove -lresolv in the LIBS line of
     the Makefile.
 
+    On macOS on Apple M1 processors, in the Makefile, uncomment the line
+    #JANSBASE=/opt/homebrew
+
     On macOS, if you want an almost static dnsdbq binary on macOS,
-    that is, one without any non-System library dependencies, you can
+    that is, one with minimal non-System library dependencies, you can
     rebuild dnsdbq with a static jansson library.  That binary could
     then be deployed on any identical macOS version and architecture.
 
-	1. Find the static jansson library, probably
-	   /usr/local/lib/libjansson.a as installed by brew.
+	1. Find the static jansson library, probably as installed by brew
+	   /usr/local/lib/libjansson.a or on M1 processors
+	   /opt/homebrew/lib/libjansson.a
 	2. Change the Makefile's line
-		JANSLIBS = -L/usr/local/lib -ljansson
+		JANSLIBS = -L$(JANSBASE)/lib -ljansson
 	    to instead specify the static library location, probably to:
-		JANSLIBS = /usr/local/lib/libjansson.a
+		JANSLIBS = $(JANSBASE)/lib/libjansson.a
 	3. Then run make
 
 
diff --git a/asinfo.c b/asinfo.c
index 36c0c45..21fca4b 100644
--- a/asinfo.c
+++ b/asinfo.c
@@ -45,22 +45,22 @@ static struct __res_state res;
 
 /* forward. */
 
-static const char *asinfo_from_ipv4(const char *, char **, char **);
+static char *asinfo_from_ipv4(const char *, char **, char **);
 #ifdef asinfo_ipv6
 static const char *asinfo_from_ipv6(const char *, char **, char **);
 #endif
-static const char *asinfo_from_dns(const char *, char **, char **);
+static char *asinfo_from_dns(const char *, char **, char **);
 static const char *keep_best(char **, char **, char *, char *);
 
 /* public. */
 
 /* asinfo_from_rr(rrtype, rdata, asnum, cidr) -- find ASINFO for A/AAAA string
  *
- * return NULL on success, or else, reason (string) for failure.
+ * return NULL on success, or else, reason (malloc'd string) for failure.
  *
  * side effect: on success, *asnum and *cidr will be heap-allocated strings.
  */
-const char *
+char *
 asinfo_from_rr(const char *rrtype, const char *rdata,
 	       char **asnum, char **cidr)
 {
@@ -99,22 +99,22 @@ asinfo_shutdown(void) {
 
 /* asinfo_from_ipv4(addr, asnum, cidr) -- prepare and use ASINFO IPv4 name
  *
- * return NULL on success, or else, reason (string) for failure.
+ * return NULL on success, or else, reason (malloc'd string) for failure.
  *
  * side effect: on success, *asnum and *cidr will be heap-allocated strings.
  */
-static const char *
+static char *
 asinfo_from_ipv4(const char *addr, char **asnum, char **cidr) {
 	u_char a4[32/8];
 	char *dname;
 
 	if (inet_pton(AF_INET, addr, a4) < 0)
-		return strerror(errno);
+		return strdup(strerror(errno));
 	int n = asprintf(&dname, "%d.%d.%d.%d.%s",
 			 a4[3], a4[2], a4[1], a4[0], asinfo_domain);
 	if (n < 0)
-		return strerror(errno);
-	const char *result = asinfo_from_dns(dname, asnum, cidr);
+		return strdup(strerror(errno));
+	char *result = asinfo_from_dns(dname, asnum, cidr);
 	free(dname);
 	return result;
 }
@@ -122,30 +122,29 @@ asinfo_from_ipv4(const char *addr, char **asnum, char **cidr) {
 #ifdef asinfo_ipv6
 /* asinfo_from_ipv6(addr, asnum, cidr) -- prepare and use ASINFO IPv6 name
  *
- * return NULL on success, or else, reason (string) for failure.
+ * return NULL on success, or else, reason (malloc'd string) for failure.
  *
  * side effect: on success, *asnum and *cidr will be heap-allocated strings.
  *
  * NOTE WELL: this is a placeholder, since no ASINFO source has working IPv6.
  */
-static const char *
+static char *
 asinfo_from_ipv6(const char *addr, char **asnum, char **cidr) {
+	char *result, *dname, *p;
 	u_char a6[128/8];
-	const char *result;
-	char *dname, *p;
 	int i;
 
 	if (inet_pton(AF_INET6, addr, &a6) < 0)
-		return strerror(errno);
+		return strdup(strerror(errno));
 	dname = malloc(strlen(asinfo_domain) + (128/4)*2);
 	if (dname == NULL)
-		return strerror(errno);
+		return strdup(strerror(errno));
 	result = NULL;
 	p = dname;
 	for (i = (128/8) - 1; i >= 0; i--) {
 		int n = sprintf(p, "%x.%x.", a6[i] & 0xf, a6[i] >> 4);
 		if (n < 0) {
-			result = strerror(errno);
+			result = strdup(strerror(errno));
 			break;
 		}
 		p += n;
@@ -162,15 +161,15 @@ asinfo_from_ipv6(const char *addr, char **asnum, char **cidr) {
 
 /* asinfo_from_dns(dname, asnum, cidr) -- retrieve and parse a ASINFO DNS TXT
  *
- * return NULL on success, or else, reason (string) for failure.
+ * return NULL on success, or else, reason (malloc'd string) for failure.
  *
  * side effect: on success, *asnum and *cidr will be heap-allocated strings.
  */
-static const char *
+static char *
 asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 	u_char buf[NS_PACKETSZ];
 	int n, an, rrn, rcode;
-	const char *result;
+	char *result;
 	ns_msg msg;
 	ns_rr rr;
 
@@ -185,16 +184,19 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 		if (res.res_h_errno == HOST_NOT_FOUND)
 			return NULL;
 		else
-			return hstrerror(res.res_h_errno);
+			return strdup(hstrerror(res.res_h_errno));
 	}
 	if (ns_initparse(buf, n, &msg) < 0)
-		return strerror(errno);
+		return strdup(strerror(errno));
 	rcode = ns_msg_getflag(msg, ns_f_rcode);
-	if (rcode != ns_r_noerror)
-		return p_rcode(rcode);
+	if (rcode != ns_r_noerror) {
+		if (asprintf(&result, "DNS RCODE 0x%x", rcode) < 0)
+			return strdup(strerror(errno));
+		return result;
+	}
 	an = ns_msg_count(msg, ns_s_an);
 	if (an == 0)
-		return "ANCOUNT == 0";
+		return strdup("ANCOUNT == 0");
 	/* some ASINFO data sources return multiple TXT RR's, each having
 	 * a prefix length measured in bits. we will select the best
 	 * (longest match) prefix offered.
@@ -205,7 +207,7 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 		char *txt[3];
 
 		if (ns_parserr(&msg, ns_s_an, rrn, &rr) < 0) {
-			result = strerror(errno);
+			result = strdup(strerror(errno));
 			break;
 		}
 		rdata = ns_rr_rdata(rr);
@@ -216,18 +218,18 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 			 * more than three TXT segments (<character-strings>).
 			 */
 			if (ntxt == 3) {
-				result = "len(TXT[]) > 3";
+				result = strdup("len(TXT[]) > 3");
 				break;
 			}
 			n = *rdata++;
 			rdlen--;
 			if (n > rdlen) {
-				result = "TXT overrun";
+				result = strdup("TXT overrun");
 				break;
 			}
 			txt[ntxt] = strndup((const char *)rdata, (size_t)n);
 			if (txt[ntxt] == NULL) {
-				result = "strndup FAIL";
+				result = strdup("strndup FAIL");
 				break;
 			}
 			DEBUG(2, true, "TXT[%d] \"%s\"\n", ntxt, txt[ntxt]);
@@ -258,8 +260,11 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 				new_cidr = strndup(t1 + seplen, (size_t)
 						   (t2 - (t1 + seplen)));
 				t1 = t2 = NULL;
-				result = keep_best(asnum, cidr,
-						   new_asnum, new_cidr);
+				const char *t = keep_best(asnum, cidr,
+							  new_asnum,
+							  new_cidr);
+				if (t != NULL)
+					result = strdup(t);
 			} else if (ntxt == 3) {
 				/* routeviews.org format:
 				 *
@@ -274,14 +279,16 @@ asinfo_from_dns(const char *dname, char **asnum, char **cidr) {
 					     txt[1], txt[2]) >= 0)
 				{
 					new_asnum = strdup(txt[0]);
-					result = keep_best(asnum, cidr,
-							   new_asnum,
-							   new_cidr);
+					const char *t = keep_best(asnum, cidr,
+								  new_asnum,
+								  new_cidr);
+					if (t != NULL)
+						result = strdup(t);
 				} else {
-					result = strerror(errno);
+					result = strdup(strerror(errno));
 				}
 			} else {
-				result = "unrecognized asinfo TXT format";
+				result = strdup("unrecognized TXT format");
 			}
 		}
 		for (n = 0; n < ntxt; n++) {
diff --git a/asinfo.h b/asinfo.h
index 6d2131d..e2c6b47 100644
--- a/asinfo.h
+++ b/asinfo.h
@@ -20,7 +20,7 @@
 #include <stdbool.h>
 
 #ifndef CRIPPLED_LIBC
-const char *
+char *
 asinfo_from_rr(const char *rrtype, const char *rdata, char **asn, char **cidr);
 #endif
 
diff --git a/deduper.c b/deduper.c
index 5300aea..a06b13e 100644
--- a/deduper.c
+++ b/deduper.c
@@ -87,7 +87,7 @@ void
 deduper_dump(deduper_t me, FILE *out) {
 	for (size_t bucket = 0; bucket < me->buckets; bucket++)
 		if (me->chains[bucket] != NULL) {
-			fprintf(out, "[%lu]", bucket);
+			fprintf(out, "[%zu]", bucket);
 			for (chainlink_t chainlink = me->chains[bucket];
 			     chainlink != NULL;
 			     chainlink = chainlink->next)
diff --git a/dnsdbq.c b/dnsdbq.c
index 28056f9..71bf237 100644
--- a/dnsdbq.c
+++ b/dnsdbq.c
@@ -52,6 +52,7 @@
 #include "pdns.h"
 #include "sort.h"
 #include "time.h"
+#include "tokstr.h"
 #include "globals.h"
 #undef MAIN_PROGRAM
 
@@ -356,11 +357,10 @@ main(int argc, char *argv[]) {
 			if (sorting == no_sort)
 				usage("-k must be preceded by -s or -S");
 
-			char *saveptr = NULL;
-			const char *tok;
-			for (tok = strtok_r(optarg, ",", &saveptr);
-			     tok != NULL;
-			     tok = strtok_r(NULL, ",", &saveptr))
+			struct tokstr *ts = tokstr_string(optarg);
+			for (char *tok;
+			     (tok = tokstr_next(ts, ",")) != NULL;
+			     free(tok))
 			{
 				if (find_sort_key(tok) != NULL)
 					usage("Each sort key may only be "
@@ -369,6 +369,7 @@ main(int argc, char *argv[]) {
 				if ((msg = add_sort_key(tok)) != NULL)
 					usage(msg);
 			}
+			tokstr_last(&ts);
 			break;
 		    }
 		case 'J':
@@ -402,9 +403,11 @@ main(int argc, char *argv[]) {
 			}
 			break;
 		case 'T': {
-			char *copy, *walker, *token;
-			copy = walker = strdup(optarg);
-			while ((token = strsep(&walker, ",")) != NULL)
+			struct tokstr *ts = tokstr_string(optarg);
+			for (char *token;
+			     (token = tokstr_next(ts, ",")) != NULL;
+			     free(token))
+			{
 				if (strcasecmp(token, "reverse") == 0)
 					transforms |= TRANS_REVERSE;
 				else if (strcasecmp(token, "datefix") == 0)
@@ -412,10 +415,10 @@ main(int argc, char *argv[]) {
 				else if (strcasecmp(token, "chomp") == 0)
 					transforms |= TRANS_CHOMP;
 				else {
-					DESTROY(copy);
 					usage("unrecognized transform in -T");
 				}
-			DESTROY(copy);
+			}
+			tokstr_last(&ts);
 			break;
 		    }
 		case 'm':
@@ -1117,20 +1120,19 @@ batch_options(const char *optstr, qparam_t options, qparam_ct dflt) {
 	char **opt = optv;
 	const char *msg;
 	int optc, ch;
-	char *tok;
 
-	char *temp = strdup(optstr);
-	char *saveptr = NULL;
 	/* crack the option string based on space or tab delimiters. */
-	for (tok = strtok_r(temp, "\040\t", &saveptr);
-	     tok != NULL;
-	     tok = strtok_r(NULL, "\040\t", &saveptr))
+	struct tokstr *ts = tokstr_string(optstr);
+	for (char *tok;
+	     (tok = tokstr_next(ts, "\040\011")) != NULL;
+	     free(tok))
 	{
 		/* dispense with extra spaces and tabs (empty fields). */
 		if (*tok == '\0')
 			continue;
 		*opt++ = tok;
 	}
+	tokstr_last(&ts);
 
 	/* if no options were specified (e.g., $options\n), restore defaults.
 	 */
@@ -1175,7 +1177,6 @@ batch_options(const char *optstr, qparam_t options, qparam_ct dflt) {
 	}
 	/* done. */
 	DESTROY(optv);
-	DESTROY(temp);
 	return msg;
 }
 
@@ -1363,28 +1364,32 @@ query_launcher(qdesc_ct qdp, qparam_ct qpp, writer_t writer) {
 
 	/* ready player one. */
 	CREATE(query, sizeof(struct query));
+	query->descrip = makepath(qdp);
+	query->mode = qdp->mode;
+	query->params = *qpp;
+	qpp = NULL;
 
 	/* define the fence. */
-	if (qpp->after != 0) {
-		if (qpp->complete) {
+	if (query->params.after != 0) {
+		if (query->params.complete) {
 			/* each db tuple must begin after the fence-start. */
-			fence.first_after = qpp->after;
+			fence.first_after = query->params.after;
 		} else {
 			/* each db tuple must end after the fence-start. */
-			fence.last_after = qpp->after;
+			fence.last_after = query->params.after;
 		}
 	}
-	if (qpp->before != 0) {
-		if (qpp->complete) {
+	if (query->params.before != 0) {
+		if (query->params.complete) {
 			/* each db tuple must end before the fence-end. */
-			fence.last_before = qpp->before;
+			fence.last_before = query->params.before;
 		} else {
 			/* each db tuple must begin before the fence-end. */
-			fence.first_before = qpp->before;
+			fence.first_before = query->params.before;
 		}
 	}
 
-	/* branch on rrtype. */
+	/* branch on rrtype; launch (or queue) nec'y fetches. */
 	if (qdp->rrtype == NULL) {
 		/* no rrtype string given, let makepath set it to "any". */
 		char *path = makepath(qdp);
@@ -1397,12 +1402,11 @@ query_launcher(qdesc_ct qdp, qparam_ct qpp, writer_t writer) {
 		return NULL;
 	} else {
 		/* rrtype string was given, parse comma separated list. */
-		char *rrtypes = strdup(qdp->rrtype);
-		char *saveptr = NULL;
 		int nfetches = 0;
-		for (char *rrtype = strtok_r(rrtypes, ",", &saveptr);
-		     rrtype != NULL;
-		     rrtype = strtok_r(NULL, ",", &saveptr))
+		struct tokstr *ts = tokstr_string(qdp->rrtype);
+		for (char *rrtype;
+		     (rrtype = tokstr_next(ts, ",")) != NULL;
+		     free(rrtype))
 		{
 			struct qdesc qd = {
 				.mode = qdp->mode,
@@ -1416,19 +1420,16 @@ query_launcher(qdesc_ct qdp, qparam_ct qpp, writer_t writer) {
 			nfetches++;
 			DESTROY(path);
 		}
+		tokstr_last(&ts);
 		if (nfetches > 1)
 			query->multitype = true;
-		DESTROY(rrtypes);
 	}
 
 	/* finish query initialization, link it up, and return it. */
 	query->writer = writer;
 	writer = NULL;
-	query->params = *qpp;
 	query->next = query->writer->queries;
 	query->writer->queries = query;
-	query->descrip = makepath(qdp);
-	query->mode = qdp->mode;
 	return query;
 }
 
@@ -1437,19 +1438,18 @@ query_launcher(qdesc_ct qdp, qparam_ct qpp, writer_t writer) {
 static const char *
 rrtype_correctness(const char *input) {
 	char **rrtypeset = calloc(MAX_FETCHES, sizeof(char *));
-	char *rrtypes = strdup(input);
-	char *saveptr = NULL;
 	const char *ret = NULL;
 	int nrrtypeset = 0;
 	bool some = false, any = false,
 		some_dnssec = false, any_dnssec = false;
-	for (char *rrtype = strtok_r(rrtypes, ",", &saveptr);
-	     rrtype != NULL;
-	     rrtype = strtok_r(NULL, ",", &saveptr))
+	struct tokstr *ts = tokstr_string(input);
+	for (char *rrtype;
+	     (rrtype = tokstr_next(ts, ",")) != NULL;
+	     free(rrtype))
 	{
 		for (char *p = rrtype; *p != '\0'; p++)
-			if (isupper(*p))
-				*p = (char) tolower(*p);
+			if (isupper((int)*p))
+				*p = (char) tolower((int)*p);
 		if (nrrtypeset == MAX_FETCHES) {
 			ret = "too many rrtypes specified";
 			goto done;
@@ -1459,7 +1459,7 @@ rrtype_correctness(const char *input) {
 				ret = "duplicate rrtype encountered";
 				goto done;
 			}
-		rrtypeset[nrrtypeset++] = rrtype;
+		rrtypeset[nrrtypeset++] = strdup(rrtype);
 		if (strcmp(rrtype, "any") == 0)
 			any = true;
 		else if (strcmp(rrtype, "any-dnssec") == 0)
@@ -1474,9 +1474,11 @@ rrtype_correctness(const char *input) {
 			 strcmp(rrtype, "nsec3") == 0 ||
 			 strcmp(rrtype, "nsec3param") == 0 ||
 			 strcmp(rrtype, "dlv") == 0)
+		{
 			some_dnssec = true;
-		else
+		} else {
 			some = true;
+		}
 		if (any && some) {
 			ret = "ANY is redundant when mixed like this";
 			goto done;
@@ -1486,8 +1488,10 @@ rrtype_correctness(const char *input) {
 			goto done;
 		}
 	}
+	tokstr_last(&ts);
  done:
-	DESTROY(rrtypes);
+	for (int i = 0; i < nrrtypeset; i++)
+		DESTROY(rrtypeset[i]);
 	DESTROY(rrtypeset);
 	return ret;
 }
@@ -1496,10 +1500,7 @@ rrtype_correctness(const char *input) {
  */
 static void
 launch_fetch(query_t query, const char *path, pdns_fence_ct fp) {
-	qparam_ct qpp = &query->params;
-	char *url;
-
-	url = psys->url(path, NULL, qpp, fp, false);
+	char *url = psys->url(path, NULL, &query->params, fp, false);
 	if (url == NULL)
 		my_exit(1);
 
diff --git a/globals.h b/globals.h
index 50d04a4..c799fe1 100644
--- a/globals.h
+++ b/globals.h
@@ -34,7 +34,7 @@ extern const struct verb verbs[];
 #endif
 
 EXTERN	const char id_swclient[]	INIT("dnsdbq");
-EXTERN	const char id_version[]		INIT("2.6.0");
+EXTERN	const char id_version[]		INIT("2.6.3");
 EXTERN	const char *program_name	INIT(NULL);
 EXTERN	const char path_sort[]		INIT("/usr/bin/sort");
 EXTERN	const char json_header[]	INIT("Accept: application/json");
diff --git a/netio.c b/netio.c
index 15b7265..e7b0be1 100644
--- a/netio.c
+++ b/netio.c
@@ -81,7 +81,7 @@ unmake_curl(void) {
 	}
 }
 
-/* fetch -- given a url, tell libcurl to go fetch it.
+/* fetch -- given a url, tell libcurl to go fetch it, attach fetch to query.
  */
 fetch_t
 create_fetch(query_t query, char *url) {
@@ -159,6 +159,7 @@ fetch_reap(fetch_t fetch) {
 		curl_slist_free_all(fetch->hdrs);
 		fetch->hdrs = NULL;
 	}
+	DESTROY(fetch->saf_msg);
 	DESTROY(fetch->url);
 	DESTROY(fetch->buf);
 	DESTROY(fetch);
diff --git a/pdns.c b/pdns.c
index 237858a..0088f0b 100644
--- a/pdns.c
+++ b/pdns.c
@@ -29,6 +29,7 @@
 #include "ns_ttl.h"
 #include "pdns.h"
 #include "time.h"
+#include "tokstr.h"
 #include "globals.h"
 
 static void present_text_line(const char *, const char *, const char *);
@@ -128,14 +129,14 @@ present_text_lookup(pdns_tuple_ct tup,
  */
 static void
 present_text_line(const char *rrname, const char *rrtype, const char *rdata) {
-	char *asnum = NULL, *cidr = NULL, *comment = NULL;
-	const char *result = NULL;
+	char *asnum = NULL, *cidr = NULL, *comment = NULL, *result = NULL;
 
 #ifndef CRIPPLED_LIBC
 	result = asinfo_from_rr(rrtype, rdata, &asnum, &cidr);
 #endif
 	if (result != NULL) {
-		comment = strdup(result);
+		comment = result;
+		result = NULL;
 	} else if (asnum != NULL && cidr != NULL) {
 		const char *src = asnum;
 		bool wordbreak = true;
@@ -369,28 +370,26 @@ annotate_one(json_t *anno, const char *rdata, const char *name, json_t *obj) {
 #ifndef CRIPPLED_LIBC
 static json_t *
 annotate_asinfo(const char *rrtype, const char *rdata) {
-	char *asnum = NULL, *cidr = NULL;
+	char *asnum = NULL, *cidr = NULL, *result = NULL;
 	json_t *asinfo = NULL;
-	const char *result;
 
 	if ((result = asinfo_from_rr(rrtype, rdata, &asnum, &cidr)) != NULL) {
 		asinfo = json_object();
 		json_object_set_new_nocheck(asinfo, "comment",
 					    json_string(result));
+		free(result);
 	} else if (asnum != NULL && cidr != NULL) {
 		json_t *array = json_array();
-		char *copy, *walker, *token;
-
-		copy = walker = strdup(asnum);
-		while ((token = strsep(&walker, " ")) != NULL)
-			json_array_append(array, json_integer(atol(token)));
-		free(copy);
+		struct tokstr *ts = tokstr_string(asnum);
+		for (char *t; (t = tokstr_next(ts, "\040")) != NULL; free(t))
+			json_array_append_new(array, json_integer(atol(t)));
+		tokstr_last(&ts);
 		asinfo = json_object();
 		json_object_set_new_nocheck(asinfo, "as", array);
 		json_object_set_new_nocheck(asinfo, "cidr", json_string(cidr));
-		free(asnum);
-		free(cidr);
 	}
+	DESTROY(asnum);
+	DESTROY(cidr);
 	return asinfo;
 }
 #endif
@@ -467,15 +466,15 @@ present_csv_line(pdns_tuple_ct tup, const char *rdata) {
 		printf("\"%s\"", rdata);
 	if (asinfo_lookup && tup->obj.rrtype != NULL &&
 	    tup->obj.rdata != NULL) {
-		char *asnum = NULL, *cidr = NULL;
-		const char *result = NULL;
+		char *asnum = NULL, *cidr = NULL, *result = NULL;
 
 #ifndef CRIPPLED_LIBC
 		result = asinfo_from_rr(tup->rrtype, rdata, &asnum, &cidr);
 #endif
 		if (result != NULL) {
 			asnum = strdup(result);
-			cidr = strdup(result);
+			cidr = result;
+			result = NULL;
 		}
 		putchar(',');
 		if (asnum != NULL) {
diff --git a/sort.c b/sort.c
index 0c76696..262a5ed 100644
--- a/sort.c
+++ b/sort.c
@@ -268,6 +268,7 @@ sortable_dnsname(sortbuf_t buf, const char *name) {
 	// ensure our result buffer is large enough.
 	size_t new_size = buf->size + c->nalnum;
 	if (new_size == 0) {
+		DESTROY(c);
 		buf->base = realloc(buf->base, 1);
 		buf->base[0] = '.';
 		buf->size = 1;
diff --git a/tokstr.c b/tokstr.c
new file mode 100644
index 0000000..11fc2f2
--- /dev/null
+++ b/tokstr.c
@@ -0,0 +1,167 @@
+// tokstr -- textual token iterator with some input independence
+// 2022-01-29 [revised during code review, add regions]
+// 2022-01-25 [initially released inside dnsdbq]
+
+/* externals. */
+
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "tokstr.h"
+
+/* private data types. */
+
+enum tokstr_type { ts_buffer, ts_string };
+
+struct tokstr_class {
+	enum tokstr_type	type;
+};
+
+struct tokstr_region {
+	struct tokstr_class	class;
+	struct tokstr_reg	source;
+};
+
+struct tokstr_string {
+	struct tokstr_class	class;
+	const char		*source;
+};
+
+struct tokstr_pvt {
+	union {
+		struct tokstr_class	class;
+		struct tokstr_region	region;
+		struct tokstr_string	string;
+	} data;
+};
+
+/* forward. */
+
+static struct tokstr_reg next_region(struct tokstr_region *, const char *);
+static struct tokstr_reg next_string(struct tokstr_string *, const char *);
+
+/* public. */
+
+// tokstr_region -- create an iterator for a counted string
+struct tokstr *
+tokstr_region(struct tokstr_reg source) {
+	struct tokstr_region *ts = malloc(sizeof(struct tokstr_region));
+	if (ts != NULL) {
+		*ts = (struct tokstr_region) {
+			.class = (struct tokstr_class) {
+				.type = ts_buffer,
+				},
+			.source = source,
+		};
+	}
+	return (struct tokstr *) ts;
+}
+
+// tokstr_string -- create an iterator for a nul-terminated string
+struct tokstr *
+tokstr_string(const char *source) {
+	struct tokstr_string *ts = malloc(sizeof(struct tokstr_string));
+	if (ts != NULL) {
+		*ts = (struct tokstr_string) {
+			.class = (struct tokstr_class) {
+				.type = ts_string,
+				},
+			.source = source,
+		};
+	}
+	return (struct tokstr *) ts;
+}
+
+// tokstr_next -- return next token from an iterator (which must be free()'d)
+// (NULL means no more tokens are available.)
+char *
+tokstr_next(struct tokstr *ts_pub, const char *delims) {
+	struct tokstr_reg reg = tokstr_next_region(ts_pub, delims);
+	if (reg.base == NULL)
+		return NULL;
+	return strndup(reg.base, reg.size);
+}
+
+// tokstr_next_copy -- copy next token from an iterator; return size, 0, or -1
+// (0 means no more tokens are available.)
+ssize_t
+tokstr_next_copy(struct tokstr *ts, const char *delims,
+		 char *buffer, size_t size)
+{
+	struct tokstr_reg reg = tokstr_next_region(ts, delims);
+	if (reg.base == NULL)
+		return 0;
+	if (reg.size >= size)
+		return -1;
+	memcpy(buffer, reg.base, reg.size);
+	buffer[reg.size] = '\0';
+	return (ssize_t) reg.size;
+}
+
+// tokstr_next_region -- return next token from an iterator (zero-copy)
+// (.base == NULL means no more tokens are available.)
+struct tokstr_reg
+tokstr_next_region(struct tokstr *ts_pub, const char *delims) {
+	struct tokstr_pvt *ts = (struct tokstr_pvt *) ts_pub;
+	struct tokstr_reg reg = {};
+	switch (ts->data.class.type) {
+	case ts_buffer:
+		reg = next_region(&ts->data.region, delims);
+		break;
+	case ts_string:
+		reg = next_string(&ts->data.string, delims);
+		break;
+	default:
+		abort();
+	}
+	assert((reg.base == NULL) == (reg.size == 0));
+	return reg;
+}
+
+// tokstr_last -- destroy an iterator and release all of its internal resources
+void
+tokstr_last(struct tokstr **pts) {
+	free(*pts);
+	*pts = NULL;
+}
+
+/* private functions. */
+
+// next_buffer -- implement tokstr_next for counted string iterators
+static struct tokstr_reg
+next_region(struct tokstr_region *reg, const char *delims) {
+	if (reg->source.size != 0) {
+		while (reg->source.size != 0 &&
+		       strchr(delims, *reg->source.base) != 0)
+			reg->source.size--, reg->source.base++;
+		const char *prev = reg->source.base;
+		while (reg->source.size != 0 &&
+		       strchr(delims, *reg->source.base) == 0)
+			reg->source.size--, reg->source.base++;
+		size_t size = (size_t) (reg->source.base - prev);
+		if (size != 0)
+			return (struct tokstr_reg) {prev, size};
+	}
+	return (struct tokstr_reg) {};
+}
+
+// next_string -- implement tokstr_next for nul-terminated string iterators
+static struct tokstr_reg
+next_string(struct tokstr_string *str, const char *delims) {
+	int ch = *str->source;
+	if (ch != '\0') {
+		while (ch != '\0' && strchr(delims, ch) != NULL)
+			ch = *++str->source;
+		const char *prev = str->source;
+		while (ch != '\0' && strchr(delims, ch) == NULL)
+			ch = *++str->source;
+		size_t size = (size_t) (str->source - prev);
+		if (size != 0)
+			return (struct tokstr_reg) {prev, size};
+	}
+	return (struct tokstr_reg) {};
+}
diff --git a/tokstr.h b/tokstr.h
new file mode 100644
index 0000000..23eb652
--- /dev/null
+++ b/tokstr.h
@@ -0,0 +1,68 @@
+#ifndef __TOKSTR_H
+#define __TOKSTR_H
+
+// tokstr -- textual token iterator with some input independence
+// 2022-01-29 [revised during code review, add regions]
+// 2022-01-25 [initially released inside dnsdbq]
+
+/* example using heap-allocated strings:
+
+	struct tokstr *ts = tokstr_string("this:is+-test");
+	for (char *t; (t = tokstr_next(ts, "-:+")) != NULL; free(t))
+		printf("\t\"%s\"\n", t);
+	tokstr_last(&ts);
+
+ * will output "this", "is", and "test". so will this:
+
+	struct tokstr *ts = tokstr_string("this:is+-test");
+	for (char t[100]; tokstr_next_copy(ts, "-:+", t, sizeof t) > 0;)
+		printf("\t\"%s\"\n", t);
+	tokstr_last(&ts);
+
+ * as will this:
+
+	struct tokstr *ts = tokstr_string("this:is+-test");
+	for (;;) {
+		struct tokstr_reg t = tokstr_next_region(ts, "-:+");
+		if (t.base == NULL)
+			break;
+		printf("\t\"%*s\"\n", t.size, t.base);
+	}
+	tokstr_last(&ts);
+
+ */
+
+// opaque type for iterator state -- never used
+struct tokstr;
+
+struct tokstr_reg {
+	const char		*base;
+	size_t			size;
+};
+
+// tokstr_region -- create an iterator for a counted string
+struct tokstr *tokstr_region(struct tokstr_reg);
+
+// tokstr_string -- create an iterator for a nul-terminated string
+struct tokstr *tokstr_string(const char *);
+
+// tokstr_string -- create an iterator for a nul-terminated string
+struct tokstr *tokstr_string(const char *);
+
+// tokstr_next -- return next token from an iterator (which must be free()'d)
+// (NULL means no more tokens are available.)
+char *tokstr_next(struct tokstr *, const char *);
+
+// tokstr_next_copy -- copy next token from an iterator; return size, 0, or -1
+// (0 means no more tokens are available.)
+ssize_t tokstr_next_copy(struct tokstr *, const char *, char *, size_t);
+
+// tokstr_next_region -- return next token from iterator (zero-copy)
+// (.base == NULL means no more tokens are available.)
+// NOTE WELL: if program state becomes undefined here, can assert() or abort()
+struct tokstr_reg tokstr_next_region(struct tokstr *, const char *);
+
+// tokstr_last -- destroy an iterator and release all of its internal resources
+void tokstr_last(struct tokstr **);
+
+#endif /*__TOKSTR_H*/