]> The Tcpdump Group git mirrors - tcpdump/blob - print-resp.c
Use strtol(), not atoi(), to parse integral values.
[tcpdump] / print-resp.c
1 /*
2 * This file implements decoding of the REdis Serialization Protocol.
3 *
4 *
5 * Copyright (c) 2015 The TCPDUMP project
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
20 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
21 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
23 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 *
30 * Initial contribution by Andrew Darqui (andrew.darqui@gmail.com).
31 */
32
33 #define NETDISSECT_REWORKED
34 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
37
38 #include <netdissect-stdinc.h>
39 #include "netdissect.h"
40 #include <limits.h>
41 #include <string.h>
42 #include <stdlib.h>
43 #include <errno.h>
44
45 #include "extract.h"
46
47 static const char tstr[] = " [|RESP]";
48
49 /*
50 * For information regarding RESP, see: http://redis.io/topics/protocol
51 */
52
53 #define RESP_SIMPLE_STRING '+'
54 #define RESP_ERROR '-'
55 #define RESP_INTEGER ':'
56 #define RESP_BULK_STRING '$'
57 #define RESP_ARRAY '*'
58
59 #define resp_print_empty(ndo) ND_PRINT((ndo, " empty"))
60 #define resp_print_null(ndo) ND_PRINT((ndo, " null"))
61 #define resp_print_invalid(ndo) ND_PRINT((ndo, " invalid"))
62
63 void resp_print(netdissect_options *, const u_char *, u_int);
64 static int resp_parse(netdissect_options *, register const u_char *, int);
65 static int resp_print_string_error_integer(netdissect_options *, register const u_char *, int);
66 static int resp_print_simple_string(netdissect_options *, register const u_char *, int);
67 static int resp_print_integer(netdissect_options *, register const u_char *, int);
68 static int resp_print_error(netdissect_options *, register const u_char *, int);
69 static int resp_print_bulk_string(netdissect_options *, register const u_char *, int);
70 static int resp_print_bulk_array(netdissect_options *, register const u_char *, int);
71 static int resp_print_inline(netdissect_options *, register const u_char *, int);
72
73 /*
74 * MOVE_FORWARD:
75 * Attempts to move our 'ptr' forward until a \r\n is found,
76 * while also making sure we don't exceed the buffer 'len'.
77 * If we exceed, jump to trunc.
78 */
79 #define MOVE_FORWARD(ptr, len) \
80 while(*ptr != '\r' && *(ptr+1) != '\n') { ND_TCHECK2(*ptr, 2); ptr++; len--; }
81
82 /*
83 * MOVE_FORWARD_CR_OR_LF
84 * Attempts to move our 'ptr' forward until a \r or \n is found,
85 * while also making sure we don't exceed the buffer 'len'.
86 * If we exceed, jump to trunc.
87 */
88 #define MOVE_FORWARD_CR_OR_LF(ptr, len) \
89 while(*ptr != '\r' && *ptr != '\n') { ND_TCHECK(*ptr); ptr++; len--; }
90
91 /*
92 * CONSUME_CR_OR_LF
93 * Consume all consecutive \r and \n bytes.
94 * If we exceed 'len', jump to trunc.
95 */
96 #define CONSUME_CR_OR_LF(ptr, len) \
97 while (*ptr == '\r' || *ptr == '\n') { ND_TCHECK(*ptr); ptr++; len--; }
98
99 /*
100 * INCBY
101 * Attempts to increment our 'ptr' by 'increment' bytes.
102 * If our increment exceeds the buffer length (len - increment),
103 * bail out by jumping to the trunc goto tag.
104 */
105 #define INCBY(ptr, increment, len) \
106 { ND_TCHECK2(*ptr, increment); ptr+=increment; len-=increment; }
107
108 /*
109 * INC1
110 * Increment our ptr by 1 byte.
111 * Most often used to skip an opcode (+-:*$ etc)
112 */
113 #define INC1(ptr, len) INCBY(ptr, 1, len)
114
115 /*
116 * INC2
117 * Increment our ptr by 2 bytes.
118 * Most often used to skip CRLF (\r\n).
119 */
120 #define INC2(ptr, len) INCBY(ptr, 2, len)
121
122 /*
123 * TEST_RET_LEN
124 * If ret_len is < 0, jump to the trunc tag which returns (-1)
125 * and 'bubbles up' to printing tstr. Otherwise, return ret_len.
126 */
127 #define TEST_RET_LEN(rl) \
128 if (rl < 0) { goto trunc; } else { return rl; }
129
130 /*
131 * TEST_RET_LEN_NORETURN
132 * If ret_len is < 0, jump to the trunc tag which returns (-1)
133 * and 'bubbles up' to printing tstr. Otherwise, continue onward.
134 */
135 #define TEST_RET_LEN_NORETURN(rl) \
136 if (rl < 0) { goto trunc; }
137
138 /*
139 * RESP_PRINT_SEGMENT
140 * Prints a segment in the form of: ' "<stuff>"\n"
141 */
142 #define RESP_PRINT_SEGMENT(_ndo, _bp, _len) \
143 ND_PRINT((_ndo, " \"")); \
144 fn_printn(_ndo, _bp, _len, _ndo->ndo_snapend); \
145 fn_print_char(_ndo, '"');
146
147 void
148 resp_print(netdissect_options *ndo, const u_char *bp, u_int length)
149 {
150 int ret_len = 0, length_cur = length;
151
152 if(!bp || length <= 0)
153 return;
154
155 ND_PRINT((ndo, ": RESP"));
156 while (length_cur > 0) {
157 /*
158 * This block supports redis pipelining.
159 * For example, multiple operations can be pipelined within the same string:
160 * "*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"
161 * or
162 * "PING\r\nPING\r\nPING\r\n"
163 * In order to handle this case, we must try and parse 'bp' until
164 * 'length' bytes have been processed or we reach a trunc condition.
165 */
166 ret_len = resp_parse(ndo, bp, length_cur);
167 TEST_RET_LEN_NORETURN(ret_len);
168 bp += ret_len;
169 length_cur -= ret_len;
170 }
171
172 return;
173
174 trunc:
175 ND_PRINT((ndo, "%s", tstr));
176 }
177
178 static int
179 resp_parse(netdissect_options *ndo, register const u_char *bp, int length)
180 {
181 int ret_len = 0;
182 u_char op = *bp;
183
184 ND_TCHECK(*bp);
185
186 switch(op) {
187 case RESP_SIMPLE_STRING: ret_len = resp_print_simple_string(ndo, bp, length); break;
188 case RESP_INTEGER: ret_len = resp_print_integer(ndo, bp, length); break;
189 case RESP_ERROR: ret_len = resp_print_error(ndo, bp, length); break;
190 case RESP_BULK_STRING: ret_len = resp_print_bulk_string(ndo, bp, length); break;
191 case RESP_ARRAY: ret_len = resp_print_bulk_array(ndo, bp, length); break;
192 default: ret_len = resp_print_inline(ndo, bp, length); break;
193 }
194
195 TEST_RET_LEN(ret_len);
196
197 trunc:
198 return (-1);
199 }
200
201 static int
202 resp_print_simple_string(netdissect_options *ndo, register const u_char *bp, int length) {
203 return resp_print_string_error_integer(ndo, bp, length);
204 }
205
206 static int
207 resp_print_integer(netdissect_options *ndo, register const u_char *bp, int length) {
208 return resp_print_string_error_integer(ndo, bp, length);
209 }
210
211 static int
212 resp_print_error(netdissect_options *ndo, register const u_char *bp, int length) {
213 return resp_print_string_error_integer(ndo, bp, length);
214 }
215
216 static int
217 resp_print_string_error_integer(netdissect_options *ndo, register const u_char *bp, int length) {
218 int length_cur = length, len, ret_len = 0;
219 const u_char *bp_ptr = bp;
220
221 /*
222 * MOVE_FORWARD moves past the string that follows the (+-;) opcodes
223 * +OK\r\n
224 * -ERR ...\r\n
225 * :02912309\r\n
226 */
227 MOVE_FORWARD(bp_ptr, length_cur);
228 len = (bp_ptr - bp);
229 ND_TCHECK2(*bp, len);
230 RESP_PRINT_SEGMENT(ndo, bp+1, len-1);
231 ret_len = len /*<1byte>+<string>*/ + 2 /*<CRLF>*/;
232
233 TEST_RET_LEN(ret_len);
234
235 trunc:
236 return (-1);
237 }
238
239 static int
240 resp_print_bulk_string(netdissect_options *ndo, register const u_char *bp, int length) {
241 int length_cur = length, string_len;
242 long strtol_ret;
243 char *p;
244
245 ND_TCHECK(*bp);
246
247 /* opcode: '$' */
248 INC1(bp, length_cur);
249 ND_TCHECK(*bp);
250
251 /* <length> */
252 errno = 0;
253 strtol_ret = strtol((const char *)bp, &p, 10);
254 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
255 strtol_ret > INT_MAX)
256 string_len = -2; /* invalid */
257 else
258 string_len = (int)strtol_ret;
259
260 /* move to \r\n */
261 MOVE_FORWARD(bp, length_cur);
262
263 /* \r\n */
264 INC2(bp, length_cur);
265
266 if (string_len > 0) {
267 /* Byte string of length string_len */
268 ND_TCHECK2(*bp, string_len);
269 RESP_PRINT_SEGMENT(ndo, bp, string_len);
270 } else {
271 switch(string_len) {
272 case 0: resp_print_empty(ndo); break;
273 case (-1): {
274 /* This is the NULL response. It follows a different pattern: $-1\r\n */
275 resp_print_null(ndo);
276 TEST_RET_LEN(length - length_cur);
277 /* returned ret_len or jumped to trunc */
278 }
279 default: resp_print_invalid(ndo); break;
280 }
281 }
282
283 /* <string> */
284 INCBY(bp, string_len, length_cur);
285
286 /* \r\n */
287 INC2(bp, length_cur);
288
289 TEST_RET_LEN(length - length_cur);
290
291 trunc:
292 return (-1);
293 }
294
295 static int
296 resp_print_bulk_array(netdissect_options *ndo, register const u_char *bp, int length) {
297 int length_cur = length, array_len, i, ret_len = 0;
298 long strtol_ret;
299 char *p;
300
301 ND_TCHECK(*bp);
302
303 /* opcode: '*' */
304 INC1(bp, length_cur);
305 ND_TCHECK(*bp);
306
307 /* <array_length> */
308 errno = 0;
309 strtol_ret = strtol((const char *)bp, &p, 10);
310 if (errno != 0 || p == (const char *)bp || strtol_ret < -1 ||
311 strtol_ret > INT_MAX)
312 array_len = -2; /* invalid */
313 else
314 array_len = (int)strtol_ret;
315
316 /* move to \r\n */
317 MOVE_FORWARD(bp, length_cur);
318
319 /* \r\n */
320 INC2(bp, length_cur);
321
322 if (array_len > 0) {
323 /* non empty array */
324 for (i = 0; i < array_len; i++) {
325 ret_len = resp_parse(ndo, bp, length_cur);
326
327 TEST_RET_LEN_NORETURN(ret_len);
328
329 bp += ret_len;
330 length_cur -= ret_len;
331
332 TEST_RET_LEN_NORETURN(length - length_cur);
333 }
334 } else {
335 /* empty, null, or invalid */
336 switch(array_len) {
337 case 0: resp_print_empty(ndo); break;
338 case (-1): resp_print_null(ndo); break;
339 default: resp_print_invalid(ndo); break;
340 }
341 }
342
343 TEST_RET_LEN(length - length_cur);
344
345 trunc:
346 return (-1);
347 }
348
349 static int
350 resp_print_inline(netdissect_options *ndo, register const u_char *bp, int length) {
351 int length_cur = length, len;
352 const u_char *bp_ptr;
353
354 /*
355 * Inline commands are simply 'strings' followed by \r or \n or both.
356 * Redis will do it's best to split/parse these strings.
357 * This feature of redis is implemented to support the ability of
358 * command parsing from telnet/nc sessions etc.
359 *
360 * <string><\r||\n||\r\n...>
361 */
362 CONSUME_CR_OR_LF(bp, length_cur);
363 bp_ptr = bp;
364 MOVE_FORWARD_CR_OR_LF(bp_ptr, length_cur);
365 len = (bp_ptr - bp);
366 ND_TCHECK2(*bp, len);
367 RESP_PRINT_SEGMENT(ndo, bp, len);
368 CONSUME_CR_OR_LF(bp_ptr, length_cur);
369
370 TEST_RET_LEN(length - length_cur);
371
372 trunc:
373 return (-1);
374 }