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