1 /*
2  * Copyright (c) 2014-2020 by Farsight Security, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *    http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 /* asprintf() does not appear on linux without this */
18 #define _GNU_SOURCE
19 
20 #include <assert.h>
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <arpa/inet.h>
26 
27 #include "defs.h"
28 #include "sort.h"
29 #include "globals.h"
30 
31 /* in the POSIX sort(1) intermediate format, the fields are:
32  * #1 first
33  * #2 last
34  * #3 duration
35  * #4 count
36  * #5 rrname
37  * #7 rrtype
38  * #6 rdata
39  * #8 mode
40  * #9 json
41  */
42 
43 #define	MAX_KEYS 7
44 
45 extern char **environ;
46 
47 static struct sortkey keys[MAX_KEYS];
48 static int nkeys = 0;
49 
50 /* sort_ready -- finish initializing the sort related metadata.
51  *
52  * If sorting, all keys must be specified, to enable -u.
53  * This adds every possible sort key, ignoring any errors from adding
54  * a key in case the key was already added as specified by the user.
55  */
56 void
sort_ready(void)57 sort_ready(void) {
58 	(void) add_sort_key("first");
59 	(void) add_sort_key("last");
60 	(void) add_sort_key("duration");
61 	(void) add_sort_key("count");
62 	(void) add_sort_key("name");
63 	(void) add_sort_key("type");
64 	(void) add_sort_key("data");
65 }
66 
67 /* add_sort_key -- add a key for use by POSIX sort.
68  *
69  * Returns NULL if no error, otherwise a static error message.
70  */
71 const char *
add_sort_key(const char * key_name)72 add_sort_key(const char *key_name) {
73 	const char *key = NULL;
74 	char *computed;
75 	int x;
76 
77 	if (nkeys == MAX_KEYS)
78 		return "too many sort keys given.";
79 	if (strcasecmp(key_name, "first") == 0)
80 		key = "-k1n";
81 	else if (strcasecmp(key_name, "last") == 0)
82 		key = "-k2n";
83 	else if (strcasecmp(key_name, "duration") == 0)
84 		key = "-k3n";
85 	else if (strcasecmp(key_name, "count") == 0)
86 		key = "-k4n";
87 	else if (strcasecmp(key_name, "name") == 0)
88 		key = "-k5";
89 	else if (strcasecmp(key_name, "type") == 0)
90 		key = "-k6";
91 	else if (strcasecmp(key_name, "data") == 0)
92 		key = "-k7";
93 	else
94 		return "key must be in "
95 		        "first|last|duration|count|name|type|data";
96 	x = asprintf(&computed, "%s%s", key,
97 		     sorting == reverse_sort ? "r" : "");
98 	if (x < 0)
99 		my_panic(true, "asprintf");
100 	keys[nkeys++] = (struct sortkey){strdup(key_name), computed};
101 	return NULL;
102 }
103 
104 /* find_sort_key -- return pointer to a sort key, or NULL if it's not specified
105  */
106 sortkey_ct
find_sort_key(const char * key_name)107 find_sort_key(const char *key_name) {
108 	int n;
109 
110 	for (n = 0; n < nkeys; n++) {
111 		if (strcmp(keys[n].specified, key_name) == 0)
112 			return &keys[n];
113 	}
114 	return NULL;
115 }
116 
117 /* sort_destroy -- drop sort metadata from heap.
118  */
119 void
sort_destroy(void)120 sort_destroy(void) {
121 	int n;
122 
123 	for (n = 0; n < nkeys; n++) {
124 		DESTROY(keys[n].specified);
125 		DESTROY(keys[n].computed);
126 	}
127 }
128 
129 /* exec_sort -- replace this fork with a POSIX sort program
130  */
131 __attribute__((noreturn)) void
exec_sort(int p1[],int p2[])132 exec_sort(int p1[], int p2[]) {
133 	char *sort_argv[3+MAX_KEYS], **sap;
134 	int n;
135 
136 	if (dup2(p1[0], STDIN_FILENO) < 0 ||
137 	    dup2(p2[1], STDOUT_FILENO) < 0) {
138 		perror("dup2");
139 		_exit(1);
140 	}
141 	close(p1[0]); close(p1[1]);
142 	close(p2[0]); close(p2[1]);
143 	sap = sort_argv;
144 	*sap++ = strdup("sort");
145 	*sap++ = strdup("-u");
146 	for (n = 0; n < nkeys; n++)
147 		*sap++ = strdup(keys[n].computed);
148 	*sap++ = NULL;
149 	putenv(strdup("LC_ALL=C"));
150 	DEBUG(1, true, "\"%s\" args:", path_sort);
151 	for (sap = sort_argv; *sap != NULL; sap++)
152 		DEBUG(1, false, " [%s]", *sap);
153 	DEBUG(1, false, "\n");
154 	execve(path_sort, sort_argv, environ);
155 	perror("execve");
156 	for (sap = sort_argv; *sap != NULL; sap++)
157 		DESTROY(*sap);
158 	_exit(1);
159 }
160 
161 /* sortable_rrname -- return a POSIX-sort-collatable rendition of RR name+type.
162  */
163 char *
sortable_rrname(pdns_tuple_ct tup)164 sortable_rrname(pdns_tuple_ct tup) {
165 	struct sortbuf buf = {};
166 
167 	sortable_dnsname(&buf, json_string_value(tup->obj.rrname));
168 	buf.base = realloc(buf.base, buf.size+1);
169 	buf.base[buf.size++] = '\0';
170 	return buf.base;
171 }
172 
173 /* sortable_rdata -- return a POSIX-sort-collatable rendition of RR data set.
174  */
175 char *
sortable_rdata(pdns_tuple_ct tup)176 sortable_rdata(pdns_tuple_ct tup) {
177 	struct sortbuf buf = {};
178 
179 	if (json_is_array(tup->obj.rdata)) {
180 		size_t index;
181 		json_t *rr;
182 
183 		json_array_foreach(tup->obj.rdata, index, rr) {
184 			if (json_is_string(rr))
185 				sortable_rdatum(&buf, tup->rrtype,
186 						json_string_value(rr));
187 			else
188 				fprintf(stderr,
189 					"%s: warning: rdata slot "
190 					"is not a string\n",
191 					program_name);
192 		}
193 	} else {
194 		sortable_rdatum(&buf, tup->rrtype, tup->rdata);
195 	}
196 	buf.base = realloc(buf.base, buf.size+1);
197 	buf.base[buf.size++] = '\0';
198 	return buf.base;
199 }
200 
201 /* sortable_rdatum -- called only by sortable_rdata(), realloc and normalize.
202  *
203  * this converts (lossily) addresses into hex strings, and extracts the
204  * server-name component of a few other types like MX. all other rdata
205  * are left in their normal string form, because it's hard to know what
206  * to sort by with something like TXT, and extracting the serial number
207  * from an SOA using a language like C is a bit ugly.
208  */
209 void
sortable_rdatum(sortbuf_t buf,const char * rrtype,const char * rdatum)210 sortable_rdatum(sortbuf_t buf, const char *rrtype, const char *rdatum) {
211 	if (strcmp(rrtype, "A") == 0) {
212 		u_char a[4];
213 
214 		if (inet_pton(AF_INET, rdatum, a) != 1)
215 			memset(a, 0, sizeof a);
216 		sortable_hexify(buf, a, sizeof a);
217 	} else if (strcmp(rrtype, "AAAA") == 0) {
218 		u_char aaaa[16];
219 
220 		if (inet_pton(AF_INET6, rdatum, aaaa) != 1)
221 			memset(aaaa, 0, sizeof aaaa);
222 		sortable_hexify(buf, aaaa, sizeof aaaa);
223 	} else if (strcmp(rrtype, "NS") == 0 ||
224 		   strcmp(rrtype, "PTR") == 0 ||
225 		   strcmp(rrtype, "CNAME") == 0 ||
226 		   strcmp(rrtype, "DNAME") == 0)
227 	{
228 		sortable_dnsname(buf, rdatum);
229 	} else if (strcmp(rrtype, "MX") == 0 ||
230 		   strcmp(rrtype, "RP") == 0)
231 	{
232 		const char *space = strrchr(rdatum, ' ');
233 
234 		if (space != NULL)
235 			sortable_dnsname(buf, space+1);
236 		else
237 			sortable_hexify(buf, (const u_char *)rdatum,
238 					strlen(rdatum));
239 	} else {
240 		sortable_hexify(buf, (const u_char *)rdatum, strlen(rdatum));
241 	}
242 }
243 
244 /* sortable_hexify -- convert src into hex string in buffer
245  */
246 void
sortable_hexify(sortbuf_t buf,const u_char * src,size_t len)247 sortable_hexify(sortbuf_t buf, const u_char *src, size_t len) {
248 	buf->base = realloc(buf->base, buf->size + len*2);
249 	for (size_t i = 0; i < len; i++) {
250 		static const char hex[] = "0123456789abcdef";
251 		unsigned int ch = src[i];
252 		buf->base[buf->size++] = hex[ch >> 4];
253 		buf->base[buf->size++] = hex[ch & 0xf];
254 	}
255 }
256 
257 /* sortable_dnsname -- make a sortable dns name; destructive and lossy.
258  *
259  * to be lexicographically sortable, a dnsname has to be converted to
260  * TLD-first, all uppercase letters must be converted to lower case,
261  * and all characters except dots then converted to hexadecimal. this
262  * transformation is for POSIX sort's use, and is irreversibly lossy.
263  */
264 void
sortable_dnsname(sortbuf_t buf,const char * name)265 sortable_dnsname(sortbuf_t buf, const char *name) {
266 	struct counted *c = countoff(name);
267 
268 	// ensure our result buffer is large enough.
269 	size_t new_size = buf->size + c->nalnum;
270 	if (new_size == 0) {
271 		buf->base = realloc(buf->base, 1);
272 		buf->base[0] = '.';
273 		buf->size = 1;
274 		return;
275 	}
276 	if (new_size != buf->size)
277 		buf->base = realloc(buf->base, new_size);
278 	char *p = buf->base + buf->size;
279 
280 	// collatable names are TLD-first, alphanumeric only, lower case.
281 	size_t nchar = 0;
282 	for (ssize_t i = (ssize_t)(c->nlabel-1); i >= 0; i--) {
283 		size_t dot = (name[c->nchar - nchar - 1] == '.');
284 		ssize_t j = (ssize_t)(c->lens[i] - dot);
285 		ssize_t k = (ssize_t)(c->nchar - nchar - c->lens[i]);
286 		for (ssize_t l = k; l < j+k; l++) {
287 			int ch = name[l];
288 			if (isalnum(ch))
289 				*p++ = (char) tolower(ch);
290 		}
291 		nchar += c->lens[i];
292 	}
293 	DESTROY(c);
294 
295 	// update our counted-string output.
296 	buf->size = (size_t)(p - buf->base);
297 	assert(buf->size == new_size);
298 }
299