xref: /openbsd-src/usr.sbin/bgplgd/qs.c (revision 5ffbcedbb1322a9a65053657189069e328d0b289)
1*5ffbcedbSclaudio /*	$OpenBSD: qs.c,v 1.7 2024/12/03 10:38:06 claudio Exp $ */
2e76e7180Sclaudio /*
3e76e7180Sclaudio  * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
4e76e7180Sclaudio  *
5e76e7180Sclaudio  * Permission to use, copy, modify, and distribute this software for any
6e76e7180Sclaudio  * purpose with or without fee is hereby granted, provided that the above
7e76e7180Sclaudio  * copyright notice and this permission notice appear in all copies.
8e76e7180Sclaudio  *
9e76e7180Sclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10e76e7180Sclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11e76e7180Sclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12e76e7180Sclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13e76e7180Sclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14e76e7180Sclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15e76e7180Sclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16e76e7180Sclaudio  */
17e76e7180Sclaudio 
18e76e7180Sclaudio #include <sys/types.h>
19e76e7180Sclaudio #include <sys/socket.h>
20e76e7180Sclaudio #include <ctype.h>
21e76e7180Sclaudio #include <netdb.h>
22e76e7180Sclaudio #include <stdlib.h>
23e76e7180Sclaudio #include <string.h>
24e76e7180Sclaudio 
25e76e7180Sclaudio #include "bgplgd.h"
26e76e7180Sclaudio #include "slowcgi.h"
27e76e7180Sclaudio 
28e76e7180Sclaudio enum qs_type {
29e76e7180Sclaudio 	ONE,
30e76e7180Sclaudio 	STRING,
31e76e7180Sclaudio 	PREFIX,
32e76e7180Sclaudio 	NUMBER,
33e76e7180Sclaudio 	FAMILY,
34600dedbfSjob 	OVS,
35600dedbfSjob 	AVS,
36e76e7180Sclaudio };
37e76e7180Sclaudio 
38e76e7180Sclaudio const struct qs {
39e76e7180Sclaudio 	unsigned int	qs;
40e76e7180Sclaudio 	const char	*key;
41e76e7180Sclaudio 	enum qs_type	type;
42e76e7180Sclaudio } qsargs[] = {
43e76e7180Sclaudio 	{ QS_NEIGHBOR, "neighbor", STRING, },
44e76e7180Sclaudio 	{ QS_GROUP, "group", STRING },
45e76e7180Sclaudio 	{ QS_AS, "as", NUMBER },
46e76e7180Sclaudio 	{ QS_PREFIX, "prefix", PREFIX },
47e76e7180Sclaudio 	{ QS_COMMUNITY, "community", STRING },
48e76e7180Sclaudio 	{ QS_EXTCOMMUNITY, "ext-community", STRING },
49e76e7180Sclaudio 	{ QS_LARGECOMMUNITY, "large-community", STRING },
50e76e7180Sclaudio 	{ QS_AF, "af", FAMILY },
51e76e7180Sclaudio 	{ QS_RIB, "rib", STRING },
52e76e7180Sclaudio 	{ QS_OVS, "ovs", OVS },
53e76e7180Sclaudio 	{ QS_BEST, "best", ONE },
54e76e7180Sclaudio 	{ QS_ALL, "all", ONE },
55e76e7180Sclaudio 	{ QS_SHORTER, "or-shorter", ONE },
56e76e7180Sclaudio 	{ QS_ERROR, "error", ONE },
57600dedbfSjob 	{ QS_AVS, "avs", AVS },
58cb563a9eSclaudio 	{ QS_INVALID, "invalid", ONE },
59cb563a9eSclaudio 	{ QS_LEAKED, "leaked", ONE },
60fac3be8eSclaudio 	{ QS_FILTERED, "filtered", ONE },
61e76e7180Sclaudio 	{ 0, NULL }
62e76e7180Sclaudio };
63e76e7180Sclaudio 
64e76e7180Sclaudio const char *qs2str(unsigned int qs);
65e76e7180Sclaudio 
66e76e7180Sclaudio static int
67e76e7180Sclaudio hex(char x)
68e76e7180Sclaudio {
69e76e7180Sclaudio 	if ('0' <= x && x <= '9')
70e76e7180Sclaudio 		return x - '0';
71e76e7180Sclaudio 	if ('a' <= x && x <= 'f')
72e76e7180Sclaudio 		return x - 'a' + 10;
73e76e7180Sclaudio 	else
74e76e7180Sclaudio 		return x - 'A' + 10;
75e76e7180Sclaudio }
76e76e7180Sclaudio 
77e76e7180Sclaudio static char *
78e76e7180Sclaudio urldecode(const char *s, size_t len)
79e76e7180Sclaudio {
80e76e7180Sclaudio 	static char buf[256];
81e76e7180Sclaudio 	size_t i, blen = 0;
82e76e7180Sclaudio 
83e76e7180Sclaudio 	for (i = 0; i < len; i++) {
84e76e7180Sclaudio 		if (blen >= sizeof(buf))
85e76e7180Sclaudio 			return NULL;
86e76e7180Sclaudio 		if (s[i] == '+') {
87e76e7180Sclaudio 			buf[blen++] = ' ';
88e76e7180Sclaudio 		} else if (s[i] == '%' && i + 2 < len) {
89e76e7180Sclaudio 			if (isxdigit((unsigned char)s[i + 1]) &&
90e76e7180Sclaudio 			    isxdigit((unsigned char)s[i + 2])) {
91e76e7180Sclaudio 				char c;
92e76e7180Sclaudio 				c = hex(s[i + 1]) << 4 | hex(s[i + 2]);
93e76e7180Sclaudio 				/* replace NUL chars with space */
94e76e7180Sclaudio 				if (c == 0)
95e76e7180Sclaudio 					c = ' ';
96e76e7180Sclaudio 				buf[blen++] = c;
97e76e7180Sclaudio 				i += 2;
98e76e7180Sclaudio 			} else
99e76e7180Sclaudio 				buf[blen++] = s[i];
100e76e7180Sclaudio 		} else {
101e76e7180Sclaudio 			buf[blen++] = s[i];
102e76e7180Sclaudio 		}
103e76e7180Sclaudio 	}
104e76e7180Sclaudio 	buf[blen] = '\0';
105e76e7180Sclaudio 
106e76e7180Sclaudio 	return buf;
107e76e7180Sclaudio }
108e76e7180Sclaudio 
109e76e7180Sclaudio static int
110e76e7180Sclaudio valid_string(const char *str)
111e76e7180Sclaudio {
112e76e7180Sclaudio 	unsigned char c;
113e76e7180Sclaudio 
114e76e7180Sclaudio 	while ((c = *str++) != '\0')
115e76e7180Sclaudio 		if (!isalnum(c) && !ispunct(c) && c != ' ')
116e76e7180Sclaudio 			return 0;
117e76e7180Sclaudio 	return 1;
118e76e7180Sclaudio }
119e76e7180Sclaudio 
120e76e7180Sclaudio /* validate that the input is pure decimal number */
121e76e7180Sclaudio static int
122e76e7180Sclaudio valid_number(const char *str)
123e76e7180Sclaudio {
124e76e7180Sclaudio 	unsigned char c;
125e76e7180Sclaudio 	int first = 1;
126e76e7180Sclaudio 
127e76e7180Sclaudio 	while ((c = *str++) != '\0') {
128e76e7180Sclaudio 		/* special handling of 0 */
129e76e7180Sclaudio 		if (first && c == '0') {
130e76e7180Sclaudio 			if (*str != '\0')
131e76e7180Sclaudio 				return 0;
132e76e7180Sclaudio 		}
133e76e7180Sclaudio 		first = 0;
134e76e7180Sclaudio 		if (!isdigit(c))
135e76e7180Sclaudio 			return 0;
136e76e7180Sclaudio 	}
137e76e7180Sclaudio 	return 1;
138e76e7180Sclaudio }
139e76e7180Sclaudio 
140e76e7180Sclaudio /* validate a prefix, does not support old 10/8 notation but that is ok */
141e76e7180Sclaudio static int
142e76e7180Sclaudio valid_prefix(char *str)
143e76e7180Sclaudio {
144e76e7180Sclaudio 	struct addrinfo hints, *res;
145e76e7180Sclaudio 	char *p;
146e76e7180Sclaudio 	int mask;
147e76e7180Sclaudio 
148e76e7180Sclaudio 	if ((p = strrchr(str, '/')) != NULL) {
149e76e7180Sclaudio 		const char *errstr;
150e76e7180Sclaudio 		mask = strtonum(p+1, 0, 128, &errstr);
151e76e7180Sclaudio 		if (errstr)
152e76e7180Sclaudio 			return 0;
153e76e7180Sclaudio 		p[0] = '\0';
154e76e7180Sclaudio 	}
155e76e7180Sclaudio 
156e479af8fSclaudio 	memset(&hints, 0, sizeof(hints));
157e76e7180Sclaudio 	hints.ai_family = AF_UNSPEC;
158e76e7180Sclaudio 	hints.ai_socktype = SOCK_DGRAM;
159e76e7180Sclaudio 	hints.ai_flags = AI_NUMERICHOST;
160e76e7180Sclaudio 	if (getaddrinfo(str, NULL, &hints, &res) != 0)
161e76e7180Sclaudio 		return 0;
162e76e7180Sclaudio 	if (p) {
163e76e7180Sclaudio 		if (res->ai_family == AF_INET && mask > 32)
164e76e7180Sclaudio 			return 0;
165e76e7180Sclaudio 		p[0] = '/';
166e76e7180Sclaudio 	}
167e76e7180Sclaudio 	freeaddrinfo(res);
168e76e7180Sclaudio 	return 1;
169e76e7180Sclaudio }
170e76e7180Sclaudio 
171e76e7180Sclaudio static int
172e76e7180Sclaudio parse_value(struct lg_ctx *ctx, unsigned int qs, enum qs_type type, char *val)
173e76e7180Sclaudio {
174e76e7180Sclaudio 	/* val can only be NULL if urldecode failed. */
175e76e7180Sclaudio 	if (val == NULL) {
176e76e7180Sclaudio 		lwarnx("urldecode of querystring failed");
177e76e7180Sclaudio 		return 400;
178e76e7180Sclaudio 	}
179e76e7180Sclaudio 
180e76e7180Sclaudio 	switch (type) {
181e76e7180Sclaudio 	case ONE:
182e76e7180Sclaudio 		if (strcmp("1", val) == 0) {
183e76e7180Sclaudio 			ctx->qs_args[qs].one = 1;
184e76e7180Sclaudio 		} else if (strcmp("0", val) == 0) {
185e76e7180Sclaudio 			/* silently ignored */
186e76e7180Sclaudio 		} else {
187e76e7180Sclaudio 			lwarnx("%s: bad value %s expected 1", qs2str(qs), val);
188e76e7180Sclaudio 			return 400;
189e76e7180Sclaudio 		}
190e76e7180Sclaudio 		break;
191e76e7180Sclaudio 	case STRING:
192e76e7180Sclaudio 		/* limit string to limited ascii chars */
193e76e7180Sclaudio 		if (!valid_string(val)) {
194e76e7180Sclaudio 			lwarnx("%s: bad string", qs2str(qs));
195e76e7180Sclaudio 			return 400;
196e76e7180Sclaudio 		}
197e76e7180Sclaudio 		ctx->qs_args[qs].string = strdup(val);
198e76e7180Sclaudio 		if (ctx->qs_args[qs].string == NULL) {
199e76e7180Sclaudio 			lwarn("parse_value");
200e76e7180Sclaudio 			return 500;
201e76e7180Sclaudio 		}
202e76e7180Sclaudio 		break;
203e76e7180Sclaudio 	case NUMBER:
204e76e7180Sclaudio 		if (!valid_number(val)) {
205e76e7180Sclaudio 			lwarnx("%s: bad number", qs2str(qs));
206e76e7180Sclaudio 			return 400;
207e76e7180Sclaudio 		}
208e76e7180Sclaudio 		ctx->qs_args[qs].string = strdup(val);
209e76e7180Sclaudio 		if (ctx->qs_args[qs].string == NULL) {
210e76e7180Sclaudio 			lwarn("parse_value");
211e76e7180Sclaudio 			return 500;
212e76e7180Sclaudio 		}
213e76e7180Sclaudio 		break;
214e76e7180Sclaudio 	case PREFIX:
215e76e7180Sclaudio 		if (!valid_prefix(val)) {
216e76e7180Sclaudio 			lwarnx("%s: bad prefix", qs2str(qs));
217e76e7180Sclaudio 			return 400;
218e76e7180Sclaudio 		}
219e76e7180Sclaudio 		ctx->qs_args[qs].string = strdup(val);
220e76e7180Sclaudio 		if (ctx->qs_args[qs].string == NULL) {
221e76e7180Sclaudio 			lwarn("parse_value");
222e76e7180Sclaudio 			return 500;
223e76e7180Sclaudio 		}
224e76e7180Sclaudio 		break;
225e76e7180Sclaudio 	case FAMILY:
226e76e7180Sclaudio 		if (strcasecmp("ipv4", val) == 0 ||
227e76e7180Sclaudio 		    strcasecmp("ipv6", val) == 0 ||
228e76e7180Sclaudio 		    strcasecmp("vpnv4", val) == 0 ||
229e76e7180Sclaudio 		    strcasecmp("vpnv6", val) == 0) {
230e76e7180Sclaudio 			ctx->qs_args[qs].string = strdup(val);
231e76e7180Sclaudio 			if (ctx->qs_args[qs].string == NULL) {
232e76e7180Sclaudio 				lwarn("parse_value");
233e76e7180Sclaudio 				return 500;
234e76e7180Sclaudio 			}
235e76e7180Sclaudio 		} else {
236e76e7180Sclaudio 			lwarnx("%s: bad value %s", qs2str(qs), val);
237e76e7180Sclaudio 			return 400;
238e76e7180Sclaudio 		}
239e76e7180Sclaudio 		break;
240e76e7180Sclaudio 	case OVS:
241e76e7180Sclaudio 		if (strcmp("not-found", val) == 0 ||
242e76e7180Sclaudio 		    strcmp("valid", val) == 0 ||
243e76e7180Sclaudio 		    strcmp("invalid", val) == 0) {
244e76e7180Sclaudio 			ctx->qs_args[qs].string = strdup(val);
245e76e7180Sclaudio 			if (ctx->qs_args[qs].string == NULL) {
246e76e7180Sclaudio 				lwarn("parse_value");
247e76e7180Sclaudio 				return 500;
248e76e7180Sclaudio 			}
249e76e7180Sclaudio 		} else {
250e76e7180Sclaudio 			lwarnx("%s: bad OVS value %s", qs2str(qs), val);
251e76e7180Sclaudio 			return 400;
252e76e7180Sclaudio 		}
253e76e7180Sclaudio 		break;
254600dedbfSjob 	case AVS:
255600dedbfSjob 		if (strcmp("unknown", val) == 0 ||
256600dedbfSjob 		    strcmp("valid", val) == 0 ||
257600dedbfSjob 		    strcmp("invalid", val) == 0) {
258600dedbfSjob 			ctx->qs_args[qs].string = strdup(val);
259600dedbfSjob 			if (ctx->qs_args[qs].string == NULL) {
260600dedbfSjob 				lwarn("parse_value");
261600dedbfSjob 				return 500;
262e76e7180Sclaudio 			}
263600dedbfSjob 		} else {
264600dedbfSjob 			lwarnx("%s: bad AVS value %s", qs2str(qs), val);
265600dedbfSjob 			return 400;
266600dedbfSjob 		}
267600dedbfSjob 		break;
268600dedbfSjob 	}
269600dedbfSjob 
270e76e7180Sclaudio 	return 0;
271e76e7180Sclaudio }
272e76e7180Sclaudio 
273e76e7180Sclaudio int
274e76e7180Sclaudio parse_querystring(const char *param, struct lg_ctx *ctx)
275e76e7180Sclaudio {
276e76e7180Sclaudio 	size_t len, i;
277e76e7180Sclaudio 	int rv;
278e76e7180Sclaudio 
279e76e7180Sclaudio 	while (param && *param) {
280e76e7180Sclaudio 		len = strcspn(param, "=");
281e76e7180Sclaudio 		for (i = 0; qsargs[i].key != NULL; i++)
282e76e7180Sclaudio 			if (strncmp(qsargs[i].key, param, len) == 0)
283e76e7180Sclaudio 				break;
284e76e7180Sclaudio 		if (qsargs[i].key == NULL) {
285e76e7180Sclaudio 			lwarnx("unknown querystring key %.*s", (int)len, param);
286e76e7180Sclaudio 			return 400;
287e76e7180Sclaudio 		}
288e76e7180Sclaudio 		if (((1 << qsargs[i].qs) & ctx->qs_mask) == 0) {
289e76e7180Sclaudio 			lwarnx("querystring param %s not allowed for command",
290e76e7180Sclaudio 			    qsargs[i].key);
291e76e7180Sclaudio 			return 400;
292e76e7180Sclaudio 		}
293e76e7180Sclaudio 		if (((1 << qsargs[i].qs) & ctx->qs_set) != 0) {
294e76e7180Sclaudio 			lwarnx("querystring param %s already set",
295e76e7180Sclaudio 			    qsargs[i].key);
296e76e7180Sclaudio 			return 400;
297e76e7180Sclaudio 		}
298e76e7180Sclaudio 		ctx->qs_set |= (1 << qsargs[i].qs);
299e76e7180Sclaudio 
300e76e7180Sclaudio 		if (param[len] != '=') {
301e76e7180Sclaudio 			lwarnx("querystring %s without value", qsargs[i].key);
302e76e7180Sclaudio 			return 400;
303e76e7180Sclaudio 		}
304e76e7180Sclaudio 
305e76e7180Sclaudio 		param += len + 1;
306e76e7180Sclaudio 		len = strcspn(param, "&");
307e76e7180Sclaudio 
308e76e7180Sclaudio 		if ((rv = parse_value(ctx, qsargs[i].qs, qsargs[i].type,
309e76e7180Sclaudio 		    urldecode(param, len))) != 0)
310e76e7180Sclaudio 			return rv;
311e76e7180Sclaudio 
312e76e7180Sclaudio 		param += len;
313e76e7180Sclaudio 		if (*param == '&')
314e76e7180Sclaudio 			param++;
315e76e7180Sclaudio 	}
316e76e7180Sclaudio 
317e76e7180Sclaudio 	return 0;
318e76e7180Sclaudio }
319e76e7180Sclaudio 
320e76e7180Sclaudio size_t
321e76e7180Sclaudio qs_argv(char **argv, size_t argc, size_t len, struct lg_ctx *ctx, int barenbr)
322e76e7180Sclaudio {
323e76e7180Sclaudio 	/* keep space for the final NULL in argv */
324e76e7180Sclaudio 	len -= 1;
325e76e7180Sclaudio 
326e76e7180Sclaudio 	/* NEIGHBOR and GROUP are exclusive */
327e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_NEIGHBOR)) {
328e76e7180Sclaudio 		if (!barenbr)
329e76e7180Sclaudio 			if (argc < len)
330e76e7180Sclaudio 				argv[argc++] = "neighbor";
331e76e7180Sclaudio 		if (argc < len)
332e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_NEIGHBOR].string;
333e76e7180Sclaudio 	} else if (ctx->qs_set & (1 << QS_GROUP)) {
334e76e7180Sclaudio 		if (argc < len)
335e76e7180Sclaudio 			argv[argc++] = "group";
336e76e7180Sclaudio 		if (argc < len)
337e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_GROUP].string;
338e76e7180Sclaudio 	}
339e76e7180Sclaudio 
340e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_AS)) {
341e76e7180Sclaudio 		if (argc < len)
342e76e7180Sclaudio 			argv[argc++] = "source-as";
343e76e7180Sclaudio 		if (argc < len)
344e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_AS].string;
345e76e7180Sclaudio 	}
346e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_COMMUNITY)) {
347e76e7180Sclaudio 		if (argc < len)
348e76e7180Sclaudio 			argv[argc++] = "community";
349e76e7180Sclaudio 		if (argc < len)
350e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_COMMUNITY].string;
351e76e7180Sclaudio 	}
352e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_EXTCOMMUNITY)) {
353e76e7180Sclaudio 		if (argc < len)
354e76e7180Sclaudio 			argv[argc++] = "ext-community";
355e76e7180Sclaudio 		if (argc < len)
356e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_EXTCOMMUNITY].string;
357e76e7180Sclaudio 	}
358e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_LARGECOMMUNITY)) {
359e76e7180Sclaudio 		if (argc < len)
360e76e7180Sclaudio 			argv[argc++] = "large-community";
361e76e7180Sclaudio 		if (argc < len)
362e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_LARGECOMMUNITY].string;
363e76e7180Sclaudio 	}
364e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_AF)) {
365e76e7180Sclaudio 		if (argc < len)
366e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_AF].string;
367e76e7180Sclaudio 	}
368e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_RIB)) {
369e76e7180Sclaudio 		if (argc < len)
370*5ffbcedbSclaudio 			argv[argc++] = "table";
371e76e7180Sclaudio 		if (argc < len)
372e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_RIB].string;
373e76e7180Sclaudio 	}
374e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_OVS)) {
375e76e7180Sclaudio 		if (argc < len)
376e76e7180Sclaudio 			argv[argc++] = "ovs";
377e76e7180Sclaudio 		if (argc < len)
378e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_OVS].string;
379e76e7180Sclaudio 	}
380600dedbfSjob 	if (ctx->qs_set & (1 << QS_AVS)) {
381600dedbfSjob 		if (argc < len)
382600dedbfSjob 			argv[argc++] = "avs";
383600dedbfSjob 		if (argc < len)
384600dedbfSjob 			argv[argc++] = ctx->qs_args[QS_AVS].string;
385600dedbfSjob 	}
386fac3be8eSclaudio 	/* BEST, ERROR, FILTERED, INVALID and LEAKED are exclusive */
387e76e7180Sclaudio 	if (ctx->qs_args[QS_BEST].one) {
388e76e7180Sclaudio 		if (argc < len)
389e76e7180Sclaudio 			argv[argc++] = "best";
390e76e7180Sclaudio 	} else if (ctx->qs_args[QS_ERROR].one) {
391e76e7180Sclaudio 		if (argc < len)
392e76e7180Sclaudio 			argv[argc++] = "error";
393fac3be8eSclaudio 	} else if (ctx->qs_args[QS_FILTERED].one) {
394fac3be8eSclaudio 		if (argc < len)
395fac3be8eSclaudio 			argv[argc++] = "filtered";
396cb563a9eSclaudio 	} else if (ctx->qs_args[QS_INVALID].one) {
397cb563a9eSclaudio 		if (argc < len)
398e8adb3e3Sclaudio 			argv[argc++] = "disqualified";
399cb563a9eSclaudio 	} else if (ctx->qs_args[QS_LEAKED].one) {
400cb563a9eSclaudio 		if (argc < len)
401cb563a9eSclaudio 			argv[argc++] = "leaked";
402e76e7180Sclaudio 	}
403e76e7180Sclaudio 
404e76e7180Sclaudio 	/* prefix must be last for show rib */
405e76e7180Sclaudio 	if (ctx->qs_set & (1 << QS_PREFIX)) {
406e76e7180Sclaudio 		if (argc < len)
407e76e7180Sclaudio 			argv[argc++] = ctx->qs_args[QS_PREFIX].string;
408e76e7180Sclaudio 
409e76e7180Sclaudio 		/* ALL and SHORTER are exclusive */
410e76e7180Sclaudio 		if (ctx->qs_args[QS_ALL].one) {
411e76e7180Sclaudio 			if (argc < len)
412e76e7180Sclaudio 				argv[argc++] = "all";
413e76e7180Sclaudio 		} else if (ctx->qs_args[QS_SHORTER].one) {
414e76e7180Sclaudio 			if (argc < len)
415e76e7180Sclaudio 				argv[argc++] = "or-shorter";
416e76e7180Sclaudio 		}
417e76e7180Sclaudio 	}
418e76e7180Sclaudio 
419e76e7180Sclaudio 	if (argc >= len)
420e76e7180Sclaudio 		lwarnx("hit limit of argv in qs_argv");
421e76e7180Sclaudio 
422e76e7180Sclaudio 	return argc;
423e76e7180Sclaudio }
424e76e7180Sclaudio 
425e76e7180Sclaudio const char *
426e76e7180Sclaudio qs2str(unsigned int qs)
427e76e7180Sclaudio {
428e76e7180Sclaudio 	size_t i;
429e76e7180Sclaudio 
430e76e7180Sclaudio 	for (i = 0; qsargs[i].key != NULL; i++)
431e76e7180Sclaudio 		if (qsargs[i].qs == qs)
432e76e7180Sclaudio 			return qsargs[i].key;
433e76e7180Sclaudio 
434e76e7180Sclaudio 	lerrx(1, "unknown querystring param %d", qs);
435e76e7180Sclaudio }
436