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