1 /* 2 * Copyright (c) 2015 The TCPDUMP project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 17 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 18 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 20 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 24 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 * POSSIBILITY OF SUCH DAMAGE. 26 * 27 * Initial contribution by Andrew Darqui (andrew.darqui@gmail.com). 28 */ 29 30 #include <sys/cdefs.h> 31 #ifndef lint 32 __RCSID("$NetBSD: print-resp.c,v 1.4 2017/09/08 14:01:13 christos Exp $"); 33 #endif 34 35 /* \summary: REdis Serialization Protocol (RESP) printer */ 36 37 #ifdef HAVE_CONFIG_H 38 #include "config.h" 39 #endif 40 41 #include <netdissect-stdinc.h> 42 #include "netdissect.h" 43 #include <limits.h> 44 #include <string.h> 45 #include <stdlib.h> 46 #include <errno.h> 47 48 #include "extract.h" 49 50 static const char tstr[] = " [|RESP]"; 51 52 /* 53 * For information regarding RESP, see: http://redis.io/topics/protocol 54 */ 55 56 #define RESP_SIMPLE_STRING '+' 57 #define RESP_ERROR '-' 58 #define RESP_INTEGER ':' 59 #define RESP_BULK_STRING '$' 60 #define RESP_ARRAY '*' 61 62 #define resp_print_empty(ndo) ND_PRINT((ndo, " empty")) 63 #define resp_print_null(ndo) ND_PRINT((ndo, " null")) 64 #define resp_print_length_too_large(ndo) ND_PRINT((ndo, " length too large")) 65 #define resp_print_length_negative(ndo) ND_PRINT((ndo, " length negative and not -1")) 66 #define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid")) 67 68 void resp_print(netdissect_options *, const u_char *, u_int); 69 static int resp_parse(netdissect_options *, register const u_char *, int); 70 static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int); 71 static int resp_print_simple_string(netdissect_options *, register const u_char *, int); 72 static int resp_print_integer(netdissect_options *, register const u_char *, int); 73 static int resp_print_error(netdissect_options *, register const u_char *, int); 74 static int resp_print_bulk_string(netdissect_options *, register const u_char *, int); 75 static int resp_print_bulk_array(netdissect_options *, register const u_char *, int); 76 static int resp_print_inline(netdissect_options *, register const u_char *, int); 77 static int resp_get_length(netdissect_options *, register const u_char *, int, const u_char **); 78 79 #define LCHECK2(_tot_len, _len) \ 80 { \ 81 if (_tot_len < _len) \ 82 goto trunc; \ 83 } 84 85 #define LCHECK(_tot_len) LCHECK2(_tot_len, 1) 86 87 /* 88 * FIND_CRLF: 89 * Attempts to move our 'ptr' forward until a \r\n is found, 90 * while also making sure we don't exceed the buffer '_len' 91 * or go past the end of the captured data. 92 * If we exceed or go past the end of the captured data, 93 * jump to trunc. 94 */ 95 #define FIND_CRLF(_ptr, _len) \ 96 for (;;) { \ 97 LCHECK2(_len, 2); \ 98 ND_TCHECK2(*_ptr, 2); \ 99 if (*_ptr == '\r' && *(_ptr+1) == '\n') \ 100 break; \ 101 _ptr++; \ 102 _len--; \ 103 } 104 105 /* 106 * CONSUME_CRLF 107 * Consume a CRLF that we've just found. 108 */ 109 #define CONSUME_CRLF(_ptr, _len) \ 110 _ptr += 2; \ 111 _len -= 2; 112 113 /* 114 * FIND_CR_OR_LF 115 * Attempts to move our '_ptr' forward until a \r or \n is found, 116 * while also making sure we don't exceed the buffer '_len' 117 * or go past the end of the captured data. 118 * If we exceed or go past the end of the captured data, 119 * jump to trunc. 120 */ 121 #define FIND_CR_OR_LF(_ptr, _len) \ 122 for (;;) { \ 123 LCHECK(_len); \ 124 ND_TCHECK(*_ptr); \ 125 if (*_ptr == '\r' || *_ptr == '\n') \ 126 break; \ 127 _ptr++; \ 128 _len--; \ 129 } 130 131 /* 132 * CONSUME_CR_OR_LF 133 * Consume all consecutive \r and \n bytes. 134 * If we exceed '_len' or go past the end of the captured data, 135 * jump to trunc. 136 */ 137 #define CONSUME_CR_OR_LF(_ptr, _len) \ 138 { \ 139 int _found_cr_or_lf = 0; \ 140 for (;;) { \ 141 /* \ 142 * Have we hit the end of data? \ 143 */ \ 144 if (_len == 0 || !ND_TTEST(*_ptr)) { \ 145 /* \ 146 * Yes. Have we seen a \r \ 147 * or \n? \ 148 */ \ 149 if (_found_cr_or_lf) { \ 150 /* \ 151 * Yes. Just stop. \ 152 */ \ 153 break; \ 154 } \ 155 /* \ 156 * No. We ran out of packet. \ 157 */ \ 158 goto trunc; \ 159 } \ 160 if (*_ptr != '\r' && *_ptr != '\n') \ 161 break; \ 162 _found_cr_or_lf = 1; \ 163 _ptr++; \ 164 _len--; \ 165 } \ 166 } 167 168 /* 169 * SKIP_OPCODE 170 * Skip over the opcode character. 171 * The opcode has already been fetched, so we know it's there, and don't 172 * need to do any checks. 173 */ 174 #define SKIP_OPCODE(_ptr, _tot_len) \ 175 _ptr++; \ 176 _tot_len--; 177 178 /* 179 * GET_LENGTH 180 * Get a bulk string or array length. 181 */ 182 #define GET_LENGTH(_ndo, _tot_len, _ptr, _len) \ 183 { \ 184 const u_char *_endp; \ 185 _len = resp_get_length(_ndo, _ptr, _tot_len, &_endp); \ 186 _tot_len -= (_endp - _ptr); \ 187 _ptr = _endp; \ 188 } 189 190 /* 191 * TEST_RET_LEN 192 * If ret_len is < 0, jump to the trunc tag which returns (-1) 193 * and 'bubbles up' to printing tstr. Otherwise, return ret_len. 194 */ 195 #define TEST_RET_LEN(rl) \ 196 if (rl < 0) { goto trunc; } else { return rl; } 197 198 /* 199 * TEST_RET_LEN_NORETURN 200 * If ret_len is < 0, jump to the trunc tag which returns (-1) 201 * and 'bubbles up' to printing tstr. Otherwise, continue onward. 202 */ 203 #define TEST_RET_LEN_NORETURN(rl) \ 204 if (rl < 0) { goto trunc; } 205 206 /* 207 * RESP_PRINT_SEGMENT 208 * Prints a segment in the form of: ' "<stuff>"\n" 209 * Assumes the data has already been verified as present. 210 */ 211 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \ 212 ND_PRINT((_ndo, " \"")); \ 213 if (fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend)) \ 214 goto trunc; \ 215 fn_print_char(_ndo, '"'); 216 217 void 218 resp_print(netdissect_options *ndo, const u_char *bp, u_int length) 219 { 220 int ret_len = 0, length_cur = length; 221 222 if(!bp || length <= 0) 223 return; 224 225 ND_PRINT((ndo, ": RESP")); 226 while (length_cur > 0) { 227 /* 228 * This block supports redis pipelining. 229 * For example, multiple operations can be pipelined within the same string: 230 * "*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n*2\r\n\$4\r\nINCR\r\n\$1\r\nz\r\n" 231 * or 232 * "PING\r\nPING\r\nPING\r\n" 233 * In order to handle this case, we must try and parse 'bp' until 234 * 'length' bytes have been processed or we reach a trunc condition. 235 */ 236 ret_len = resp_parse(ndo, bp, length_cur); 237 TEST_RET_LEN_NORETURN(ret_len); 238 bp += ret_len; 239 length_cur -= ret_len; 240 } 241 242 return; 243 244 trunc: 245 ND_PRINT((ndo, "%s", tstr)); 246 } 247 248 static int 249 resp_parse(netdissect_options *ndo, register const u_char *bp, int length) 250 { 251 u_char op; 252 int ret_len; 253 254 LCHECK2(length, 1); 255 ND_TCHECK(*bp); 256 op = *bp; 257 258 /* bp now points to the op, so these routines must skip it */ 259 switch(op) { 260 case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break; 261 case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break; 262 case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break; 263 case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break; 264 case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break; 265 default: ret_len = resp_print_inline(ndo, bp, length); break; 266 } 267 268 /* 269 * This gives up with a "truncated" indicator for all errors, 270 * including invalid packet errors; that's what we want, as 271 * we have to give up on further parsing in that case. 272 */ 273 TEST_RET_LEN(ret_len); 274 275 trunc: 276 return (-1); 277 } 278 279 static int 280 resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) { 281 return resp_print_string_error_integer(ndo, bp, length); 282 } 283 284 static int 285 resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) { 286 return resp_print_string_error_integer(ndo, bp, length); 287 } 288 289 static int 290 resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) { 291 return resp_print_string_error_integer(ndo, bp, length); 292 } 293 294 static int 295 resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) { 296 int length_cur = length, len, ret_len; 297 const u_char *bp_ptr; 298 299 /* bp points to the op; skip it */ 300 SKIP_OPCODE(bp, length_cur); 301 bp_ptr = bp; 302 303 /* 304 * bp now prints past the (+-;) opcode, so it's pointing to the first 305 * character of the string (which could be numeric). 306 * +OK\r\n 307 * -ERR ...\r\n 308 * :02912309\r\n 309 * 310 * Find the \r\n with FIND_CRLF(). 311 */ 312 FIND_CRLF(bp_ptr, length_cur); 313 314 /* 315 * bp_ptr points to the \r\n, so bp_ptr - bp is the length of text 316 * preceding the \r\n. That includes the opcode, so don't print 317 * that. 318 */ 319 len = (bp_ptr - bp); 320 RESP_PRINT_SEGMENT(ndo, bp, len); 321 ret_len = 1 /*<opcode>*/ + len /*<string>*/ + 2 /*<CRLF>*/; 322 323 TEST_RET_LEN(ret_len); 324 325 trunc: 326 return (-1); 327 } 328 329 static int 330 resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) { 331 int length_cur = length, string_len; 332 333 /* bp points to the op; skip it */ 334 SKIP_OPCODE(bp, length_cur); 335 336 /* <length>\r\n */ 337 GET_LENGTH(ndo, length_cur, bp, string_len); 338 339 if (string_len >= 0) { 340 /* Byte string of length string_len, starting at bp */ 341 if (string_len == 0) 342 resp_print_empty(ndo); 343 else { 344 LCHECK2(length_cur, string_len); 345 ND_TCHECK2(*bp, string_len); 346 RESP_PRINT_SEGMENT(ndo, bp, string_len); 347 bp += string_len; 348 length_cur -= string_len; 349 } 350 351 /* 352 * Find the \r\n at the end of the string and skip past it. 353 * XXX - report an error if the \r\n isn't immediately after 354 * the item? 355 */ 356 FIND_CRLF(bp, length_cur); 357 CONSUME_CRLF(bp, length_cur); 358 } else { 359 /* null, truncated, or invalid for some reason */ 360 switch(string_len) { 361 case (-1): resp_print_null(ndo); break; 362 case (-2): goto trunc; 363 case (-3): resp_print_length_too_large(ndo); break; 364 case (-4): resp_print_length_negative(ndo); break; 365 default: resp_print_invalid(ndo); break; 366 } 367 } 368 369 return (length - length_cur); 370 371 trunc: 372 return (-1); 373 } 374 375 static int 376 resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) { 377 u_int length_cur = length; 378 int array_len, i, ret_len; 379 380 /* bp points to the op; skip it */ 381 SKIP_OPCODE(bp, length_cur); 382 383 /* <array_length>\r\n */ 384 GET_LENGTH(ndo, length_cur, bp, array_len); 385 386 if (array_len > 0) { 387 /* non empty array */ 388 for (i = 0; i < array_len; i++) { 389 ret_len = resp_parse(ndo, bp, length_cur); 390 391 TEST_RET_LEN_NORETURN(ret_len); 392 393 bp += ret_len; 394 length_cur -= ret_len; 395 } 396 } else { 397 /* empty, null, truncated, or invalid */ 398 switch(array_len) { 399 case 0: resp_print_empty(ndo); break; 400 case (-1): resp_print_null(ndo); break; 401 case (-2): goto trunc; 402 case (-3): resp_print_length_too_large(ndo); break; 403 case (-4): resp_print_length_negative(ndo); break; 404 default: resp_print_invalid(ndo); break; 405 } 406 } 407 408 return (length - length_cur); 409 410 trunc: 411 return (-1); 412 } 413 414 static int 415 resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) { 416 int length_cur = length; 417 int len; 418 const u_char *bp_ptr; 419 420 /* 421 * Inline commands are simply 'strings' followed by \r or \n or both. 422 * Redis will do its best to split/parse these strings. 423 * This feature of redis is implemented to support the ability of 424 * command parsing from telnet/nc sessions etc. 425 * 426 * <string><\r||\n||\r\n...> 427 */ 428 429 /* 430 * Skip forward past any leading \r, \n, or \r\n. 431 */ 432 CONSUME_CR_OR_LF(bp, length_cur); 433 bp_ptr = bp; 434 435 /* 436 * Scan forward looking for \r or \n. 437 */ 438 FIND_CR_OR_LF(bp_ptr, length_cur); 439 440 /* 441 * Found it; bp_ptr points to the \r or \n, so bp_ptr - bp is the 442 * Length of the line text that preceeds it. Print it. 443 */ 444 len = (bp_ptr - bp); 445 RESP_PRINT_SEGMENT(ndo, bp, len); 446 447 /* 448 * Skip forward past the \r, \n, or \r\n. 449 */ 450 CONSUME_CR_OR_LF(bp_ptr, length_cur); 451 452 /* 453 * Return the number of bytes we processed. 454 */ 455 return (length - length_cur); 456 457 trunc: 458 return (-1); 459 } 460 461 static int 462 resp_get_length(netdissect_options *ndo, register const u_char *bp, int len, const u_char **endp) 463 { 464 int result; 465 u_char c; 466 int saw_digit; 467 int neg; 468 int too_large; 469 470 if (len == 0) 471 goto trunc; 472 ND_TCHECK(*bp); 473 too_large = 0; 474 neg = 0; 475 if (*bp == '-') { 476 neg = 1; 477 bp++; 478 len--; 479 } 480 result = 0; 481 saw_digit = 0; 482 483 for (;;) { 484 if (len == 0) 485 goto trunc; 486 ND_TCHECK(*bp); 487 c = *bp; 488 if (!(c >= '0' && c <= '9')) { 489 if (!saw_digit) { 490 bp++; 491 goto invalid; 492 } 493 break; 494 } 495 c -= '0'; 496 if (result > (INT_MAX / 10)) { 497 /* This will overflow an int when we multiply it by 10. */ 498 too_large = 1; 499 } else { 500 result *= 10; 501 if (result == ((INT_MAX / 10) * 10) && c > (INT_MAX % 10)) { 502 /* This will overflow an int when we add c */ 503 too_large = 1; 504 } else 505 result += c; 506 } 507 bp++; 508 len--; 509 saw_digit = 1; 510 } 511 512 /* 513 * OK, we found a non-digit character. It should be a \r, followed 514 * by a \n. 515 */ 516 if (*bp != '\r') { 517 bp++; 518 goto invalid; 519 } 520 bp++; 521 len--; 522 if (len == 0) 523 goto trunc; 524 ND_TCHECK(*bp); 525 if (*bp != '\n') { 526 bp++; 527 goto invalid; 528 } 529 bp++; 530 len--; 531 *endp = bp; 532 if (neg) { 533 /* -1 means "null", anything else is invalid */ 534 if (too_large || result != 1) 535 return (-4); 536 result = -1; 537 } 538 return (too_large ? -3 : result); 539 540 trunc: 541 *endp = bp; 542 return (-2); 543 544 invalid: 545 *endp = bp; 546 return (-5); 547 } 548