]> git.ipfire.org Git - people/ms/suricata.git/blame - src/app-layer-ftp.c
Add BUG_ON to avoid overruning AppLayerDetectDirection map array
[people/ms/suricata.git] / src / app-layer-ftp.c
CommitLineData
ce019275
WM
1/* Copyright (C) 2007-2010 Open Information Security Foundation
2 *
3 * You can copy, redistribute or modify this Program under the terms of
4 * the GNU General Public License version 2 as published by the Free
5 * Software Foundation.
f2f9b832 6 *
ce019275
WM
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * version 2 along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15 * 02110-1301, USA.
16 */
17
18/**
f2f9b832 19 * \file
ce019275 20 *
f2f9b832
PR
21 * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
22 *
23 * App Layer Parser for FTP
24 */
25
26#include "suricata-common.h"
27#include "debug.h"
28#include "decode.h"
29#include "threads.h"
30
31#include "util-print.h"
32#include "util-pool.h"
33
cc76aa4b
WM
34#include "flow-util.h"
35
0e4235cc
WM
36#include "detect-engine-state.h"
37
f2f9b832
PR
38#include "stream-tcp-private.h"
39#include "stream-tcp-reassemble.h"
6a53ab9c 40#include "stream-tcp.h"
f2f9b832
PR
41#include "stream.h"
42
43#include "app-layer-protos.h"
44#include "app-layer-parser.h"
45#include "app-layer-ftp.h"
46
705471e4 47#include "util-spm.h"
f2f9b832
PR
48#include "util-unittest.h"
49#include "util-debug.h"
1859ed54 50#include "util-memcmp.h"
f2f9b832
PR
51
52/**
53 * \brief This function is called to determine and set which command is being
54 * transfered to the ftp server
55 * \param ftp_state the ftp state structure for the parser
56 * \param input input line of the command
57 * \param len of the command
58 *
59 * \retval 1 when the command is parsed, 0 otherwise
60 */
61static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
62 uint32_t input_len) {
63 SCEnter();
64 FtpState *fstate = (FtpState *)ftp_state;
65
f2f9b832 66 if (input_len >= 4) {
1859ed54 67 if (SCMemcmpLowercase("port", input, 4) == 0) {
f2f9b832
PR
68 fstate->command = FTP_COMMAND_PORT;
69 }
9d7baa7a 70
f2f9b832
PR
71 /* else {
72 * Add the ftp commands you need here
73 * }
74 */
75 }
76 return 1;
77}
78
79/**
80 * \brief This function is called to retrieve the request line and parse it
81 * \param ftp_state the ftp state structure for the parser
82 * \param input input line of the command
83 * \param input_len length of the request
84 * \param output the resulting output
85 *
86 * \retval 1 when the command is parsed, 0 otherwise
87 */
88static int FTPParseRequestCommandLine(Flow *f, void *ftp_state, AppLayerParserState
9a6aef45
AS
89 *pstate, uint8_t *input,uint32_t input_len,
90 void *local_data, AppLayerParserResult *output) {
f2f9b832
PR
91 SCEnter();
92 //PrintRawDataFp(stdout, input,input_len);
93
94 FtpState *fstate = (FtpState *)ftp_state;
95 uint16_t max_fields = 2;
96 uint16_t u = 0;
97 uint32_t offset = 0;
98
99 if (pstate == NULL)
100 return -1;
101
102 for (u = pstate->parse_field; u < max_fields; u++) {
f2f9b832
PR
103
104 switch(u) {
105 case 0: /* REQUEST COMMAND */
106 {
107 const uint8_t delim[] = { 0x20, };
108 int r = AlpParseFieldByDelimiter(output, pstate,
109 FTP_FIELD_REQUEST_COMMAND, delim, sizeof(delim),
110 input, input_len, &offset);
f2f9b832
PR
111
112 if (r == 0) {
113 pstate->parse_field = 0;
114 return 0;
115 }
f2f9b832
PR
116 fstate->arg_offset = offset;
117 FTPParseRequestCommand(ftp_state, input, input_len);
118 break;
119 }
120 case 1: /* REQUEST COMMAND ARG */
121 {
122 switch (fstate->command) {
123 case FTP_COMMAND_PORT:
124 /* We don't need to parse args, we are going to check
125 * the ftpbounce condition directly from detect-ftpbounce
126 */
127 if (fstate->port_line != NULL)
25a3a5c6
PR
128 SCFree(fstate->port_line);
129 fstate->port_line = SCMalloc(input_len);
9f4fae5b
GIG
130 if (fstate->port_line == NULL)
131 return 0;
f2f9b832
PR
132 fstate->port_line = memcpy(fstate->port_line, input,
133 input_len);
134 fstate->port_line_len = input_len;
f2f9b832
PR
135 break;
136 default:
137 break;
138 } /* end switch command specified args */
139
140 break;
141 }
142 }
143 }
144
145 pstate->parse_field = 0;
146 return 1;
147}
148
149/**
150 * \brief This function is called to retrieve a ftp request
151 * \param ftp_state the ftp state structure for the parser
152 * \param input input line of the command
153 * \param input_len length of the request
154 * \param output the resulting output
155 *
156 * \retval 1 when the command is parsed, 0 otherwise
157 */
9a6aef45
AS
158static int FTPParseRequest(Flow *f, void *ftp_state,
159 AppLayerParserState *pstate,
160 uint8_t *input, uint32_t input_len,
161 void *local_data, AppLayerParserResult *output)
162{
f2f9b832 163 SCEnter();
f2f9b832
PR
164 /* PrintRawDataFp(stdout, input,input_len); */
165
166 uint32_t offset = 0;
167
168 if (pstate == NULL)
169 return -1;
170
f2f9b832
PR
171
172 //PrintRawDataFp(stdout, pstate->store, pstate->store_len);
173
174 const uint8_t delim[] = { 0x0D, 0x0A };
175 int r = AlpParseFieldByDelimiter(output, pstate, FTP_FIELD_REQUEST_LINE,
176 delim, sizeof(delim), input, input_len,
177 &offset);
178 if (r == 0) {
179 pstate->parse_field = 0;
180 return 0;
181 }
182 if (pstate->store_len)
183 PrintRawDataFp(stdout, pstate->store, pstate->store_len);
184
185 pstate->parse_field = 0;
186 return 1;
187}
188
189/**
190 * \brief This function is called to retrieve a ftp response
191 * \param ftp_state the ftp state structure for the parser
192 * \param input input line of the command
193 * \param input_len length of the request
194 * \param output the resulting output
195 *
196 * \retval 1 when the command is parsed, 0 otherwise
197 */
198static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
199 uint8_t *input, uint32_t input_len,
9a6aef45
AS
200 void *local_data, AppLayerParserResult *output)
201{
f2f9b832 202 SCEnter();
f2f9b832
PR
203 //PrintRawDataFp(stdout, input,input_len);
204
205 uint32_t offset = 0;
206 FtpState *fstate = (FtpState *)ftp_state;
207
208 if (pstate == NULL)
209 return -1;
210
f2f9b832
PR
211
212 const uint8_t delim[] = { 0x0D, 0x0A };
213 int r = AlpParseFieldByDelimiter(output, pstate, FTP_FIELD_RESPONSE_LINE,
214 delim, sizeof(delim), input, input_len,
215 &offset);
216 if (r == 0) {
217 pstate->parse_field = 0;
218 return 0;
219 }
220 char rcode[5];
221 memcpy(rcode, input, 4);
222 rcode[4] = '\0';
223 fstate->response_code = atoi(rcode);
224 SCLogDebug("Response: %u\n", fstate->response_code);
225
226 pstate->parse_field = 0;
227 return 1;
228}
229
230#ifdef DEBUG
231static SCMutex ftp_state_mem_lock = PTHREAD_MUTEX_INITIALIZER;
232static uint64_t ftp_state_memuse = 0;
233static uint64_t ftp_state_memcnt = 0;
234#endif
235
236static void *FTPStateAlloc(void) {
25a3a5c6 237 void *s = SCMalloc(sizeof(FtpState));
f2f9b832
PR
238 if (s == NULL)
239 return NULL;
240
241 memset(s, 0, sizeof(FtpState));
242
243#ifdef DEBUG
244 SCMutexLock(&ftp_state_mem_lock);
245 ftp_state_memcnt++;
246 ftp_state_memuse+=sizeof(FtpState);
247 SCMutexUnlock(&ftp_state_mem_lock);
248#endif
249 return s;
250}
251
252static void FTPStateFree(void *s) {
18954a2c
PR
253 FtpState *fstate = (FtpState *) s;
254 if (fstate->port_line != NULL)
255 SCFree(fstate->port_line);
25a3a5c6 256 SCFree(s);
f2f9b832
PR
257#ifdef DEBUG
258 SCMutexLock(&ftp_state_mem_lock);
259 ftp_state_memcnt--;
260 ftp_state_memuse-=sizeof(FtpState);
261 SCMutexUnlock(&ftp_state_mem_lock);
262#endif
263}
264
265
266void RegisterFTPParsers(void) {
000ce98c
AS
267 /** FTP */
268 AlpProtoAdd(&alp_proto_ctx, IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER);
269 AlpProtoAdd(&alp_proto_ctx, IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER);
270 AlpProtoAdd(&alp_proto_ctx, IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER);
000ce98c 271
c6e090f7 272 AppLayerRegisterProto("ftp", ALPROTO_FTP, STREAM_TOSERVER,
f2f9b832 273 FTPParseRequest);
c6e090f7 274 AppLayerRegisterProto("ftp", ALPROTO_FTP, STREAM_TOCLIENT,
f2f9b832
PR
275 FTPParseResponse);
276 AppLayerRegisterParser("ftp.request_command_line", ALPROTO_FTP,
277 FTP_FIELD_REQUEST_LINE, FTPParseRequestCommandLine,
278 "ftp");
279 AppLayerRegisterStateFuncs(ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
280}
281
282void FTPAtExitPrintStats(void) {
283#ifdef DEBUG
284 SCMutexLock(&ftp_state_mem_lock);
285 SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
286 ftp_state_memcnt, ftp_state_memuse);
287 SCMutexUnlock(&ftp_state_mem_lock);
288#endif
289}
290
291/* UNITTESTS */
292#ifdef UNITTESTS
293
294/** \test Send a get request in one chunk. */
295int FTPParserTest01(void) {
296 int result = 1;
297 Flow f;
298 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
299 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
300 TcpSession ssn;
301
302 memset(&f, 0, sizeof(f));
303 memset(&ssn, 0, sizeof(ssn));
cc76aa4b
WM
304
305 FLOW_INITIALIZE(&f);
f2f9b832
PR
306 f.protoctx = (void *)&ssn;
307
6a53ab9c 308 StreamTcpInitConfig(TRUE);
6a53ab9c 309
9a6aef45 310 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf, ftplen);
f2f9b832 311 if (r != 0) {
f729d6f7 312 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
313 result = 0;
314 goto end;
315 }
316
06904c90 317 FtpState *ftp_state = f.alstate;
f2f9b832 318 if (ftp_state == NULL) {
f729d6f7 319 SCLogDebug("no ftp state: ");
f2f9b832
PR
320 result = 0;
321 goto end;
322 }
323
324 if (ftp_state->command != FTP_COMMAND_PORT) {
f729d6f7 325 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
f2f9b832
PR
326 result = 0;
327 goto end;
328 }
329
330end:
6a53ab9c 331 StreamTcpFreeConfig(TRUE);
0e4235cc 332 FLOW_DESTROY(&f);
f2f9b832
PR
333 return result;
334}
335
336/** \test Send a splitted get request. */
337int FTPParserTest03(void) {
338 int result = 1;
339 Flow f;
340 uint8_t ftpbuf1[] = "POR";
341 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
342 uint8_t ftpbuf2[] = "T 192,168,1";
343 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
344 uint8_t ftpbuf3[] = "1,1,10,20\r\n";
345 uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
346 TcpSession ssn;
347
348 memset(&f, 0, sizeof(f));
349 memset(&ssn, 0, sizeof(ssn));
f2f9b832
PR
350 f.protoctx = (void *)&ssn;
351
6a53ab9c 352 StreamTcpInitConfig(TRUE);
6a53ab9c 353
9a6aef45 354 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
f2f9b832 355 if (r != 0) {
f729d6f7 356 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
357 result = 0;
358 goto end;
359 }
360
9a6aef45 361 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, ftplen2);
f2f9b832 362 if (r != 0) {
f729d6f7 363 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
364 result = 0;
365 goto end;
366 }
367
9a6aef45 368 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf3, ftplen3);
f2f9b832 369 if (r != 0) {
f729d6f7 370 SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
371 result = 0;
372 goto end;
373 }
374
06904c90 375 FtpState *ftp_state = f.alstate;
f2f9b832 376 if (ftp_state == NULL) {
f729d6f7 377 SCLogDebug("no ftp state: ");
f2f9b832
PR
378 result = 0;
379 goto end;
380 }
381
382 if (ftp_state->command != FTP_COMMAND_PORT) {
f729d6f7 383 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
f2f9b832
PR
384 result = 0;
385 goto end;
386 }
387
388end:
6a53ab9c 389 StreamTcpFreeConfig(TRUE);
f2f9b832
PR
390 return result;
391}
392
393/** \test See how it deals with an incomplete request. */
394int FTPParserTest06(void) {
395 int result = 1;
396 Flow f;
397 uint8_t ftpbuf1[] = "PORT";
398 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
399 TcpSession ssn;
400
401 memset(&f, 0, sizeof(f));
402 memset(&ssn, 0, sizeof(ssn));
cc76aa4b
WM
403
404 FLOW_INITIALIZE(&f);
f2f9b832
PR
405 f.protoctx = (void *)&ssn;
406
6a53ab9c 407 StreamTcpInitConfig(TRUE);
6a53ab9c 408
9a6aef45 409 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START|STREAM_EOF, ftpbuf1, ftplen1);
f2f9b832 410 if (r != 0) {
f729d6f7 411 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
412 result = 0;
413 goto end;
414 }
415
06904c90 416 FtpState *ftp_state = f.alstate;
f2f9b832 417 if (ftp_state == NULL) {
f729d6f7 418 SCLogDebug("no ftp state: ");
f2f9b832
PR
419 result = 0;
420 goto end;
421 }
422
423 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
f729d6f7 424 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
f2f9b832
PR
425 result = 0;
426 goto end;
427 }
428
429end:
6a53ab9c 430 StreamTcpFreeConfig(TRUE);
0e4235cc 431 FLOW_DESTROY(&f);
f2f9b832
PR
432 return result;
433}
434
435/** \test See how it deals with an incomplete request in multiple chunks. */
436int FTPParserTest07(void) {
437 int result = 1;
438 Flow f;
439 uint8_t ftpbuf1[] = "PO";
440 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
441 uint8_t ftpbuf2[] = "RT\r\n";
442 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
443 TcpSession ssn;
444
445 memset(&f, 0, sizeof(f));
446 memset(&ssn, 0, sizeof(ssn));
cc76aa4b
WM
447
448 FLOW_INITIALIZE(&f);
f2f9b832
PR
449 f.protoctx = (void *)&ssn;
450
6a53ab9c 451 StreamTcpInitConfig(TRUE);
6a53ab9c 452
9a6aef45 453 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
f2f9b832 454 if (r != 0) {
f729d6f7 455 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
456 result = 0;
457 goto end;
458 }
459
9a6aef45 460 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf2, ftplen2);
f2f9b832 461 if (r != 0) {
f729d6f7 462 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
f2f9b832
PR
463 result = 0;
464 goto end;
465 }
466
06904c90 467 FtpState *ftp_state = f.alstate;
f2f9b832 468 if (ftp_state == NULL) {
f729d6f7 469 SCLogDebug("no ftp state: ");
f2f9b832
PR
470 result = 0;
471 goto end;
472 }
473
474 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
f729d6f7 475 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
f2f9b832
PR
476 result = 0;
477 goto end;
478 }
479
480end:
6a53ab9c 481 StreamTcpFreeConfig(TRUE);
0e4235cc 482 FLOW_DESTROY(&f);
f2f9b832
PR
483 return result;
484}
485
486/** \test Test case where chunks are smaller than the delim length and the
487 * last chunk is supposed to match the delim. */
488int FTPParserTest10(void) {
489 int result = 1;
490 Flow f;
491 uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
492 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
493 TcpSession ssn;
494 int r = 0;
495 memset(&f, 0, sizeof(f));
496 memset(&ssn, 0, sizeof(ssn));
cc76aa4b
WM
497
498 FLOW_INITIALIZE(&f);
f2f9b832
PR
499 f.protoctx = (void *)&ssn;
500
6a53ab9c 501 StreamTcpInitConfig(TRUE);
6a53ab9c 502
f2f9b832
PR
503 uint32_t u;
504 for (u = 0; u < ftplen1; u++) {
505 uint8_t flags = 0;
506
507 if (u == 0) flags = STREAM_TOSERVER|STREAM_START;
508 else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
509 else flags = STREAM_TOSERVER;
510
9a6aef45 511 r = AppLayerParse(NULL, &f, ALPROTO_FTP, flags, &ftpbuf1[u], 1);
f2f9b832 512 if (r != 0) {
f729d6f7 513 SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r);
f2f9b832
PR
514 result = 0;
515 goto end;
516 }
517 }
518
06904c90 519 FtpState *ftp_state = f.alstate;
f2f9b832 520 if (ftp_state == NULL) {
f729d6f7 521 SCLogDebug("no ftp state: ");
f2f9b832
PR
522 result = 0;
523 goto end;
524 }
525
526 if (ftp_state->command != FTP_COMMAND_PORT) {
f729d6f7 527 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
f2f9b832
PR
528 result = 0;
529 goto end;
530 }
531
532end:
6a53ab9c 533 StreamTcpFreeConfig(TRUE);
0e4235cc 534 FLOW_DESTROY(&f);
f2f9b832
PR
535 return result;
536}
537#endif /* UNITTESTS */
538
539void FTPParserRegisterTests(void) {
540#ifdef UNITTESTS
541 UtRegisterTest("FTPParserTest01", FTPParserTest01, 1);
542 UtRegisterTest("FTPParserTest03", FTPParserTest03, 1);
543 UtRegisterTest("FTPParserTest06", FTPParserTest06, 1);
544 UtRegisterTest("FTPParserTest07", FTPParserTest07, 1);
545 UtRegisterTest("FTPParserTest10", FTPParserTest10, 1);
546#endif /* UNITTESTS */
547}
548