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