]> git.ipfire.org Git - people/ms/suricata.git/blob - src/app-layer-ftp.c
Add new command line option --list-app-layer-protocols to list supported app layer...
[people/ms/suricata.git] / src / app-layer-ftp.c
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.
6 *
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 /**
19 * \file
20 *
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
34 #include "flow-util.h"
35
36 #include "detect-engine-state.h"
37
38 #include "stream-tcp-private.h"
39 #include "stream-tcp-reassemble.h"
40 #include "stream-tcp.h"
41 #include "stream.h"
42
43 #include "app-layer-protos.h"
44 #include "app-layer-parser.h"
45 #include "app-layer-ftp.h"
46
47 #include "util-spm.h"
48 #include "util-unittest.h"
49 #include "util-debug.h"
50 #include "util-memcmp.h"
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 */
61 static int FTPParseRequestCommand(void *ftp_state, uint8_t *input,
62 uint32_t input_len) {
63 SCEnter();
64 FtpState *fstate = (FtpState *)ftp_state;
65
66 if (input_len >= 4) {
67 if (SCMemcmpLowercase("port", input, 4) == 0) {
68 fstate->command = FTP_COMMAND_PORT;
69 }
70
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 */
88 static int FTPParseRequestCommandLine(Flow *f, void *ftp_state, AppLayerParserState
89 *pstate, uint8_t *input,uint32_t input_len,
90 void *local_data, AppLayerParserResult *output) {
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++) {
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);
111
112 if (r == 0) {
113 pstate->parse_field = 0;
114 return 0;
115 }
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)
128 SCFree(fstate->port_line);
129 fstate->port_line = SCMalloc(input_len);
130 if (fstate->port_line == NULL)
131 return 0;
132 fstate->port_line = memcpy(fstate->port_line, input,
133 input_len);
134 fstate->port_line_len = input_len;
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 */
158 static 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 {
163 SCEnter();
164 /* PrintRawDataFp(stdout, input,input_len); */
165
166 uint32_t offset = 0;
167
168 if (pstate == NULL)
169 return -1;
170
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 */
198 static int FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate,
199 uint8_t *input, uint32_t input_len,
200 void *local_data, AppLayerParserResult *output)
201 {
202 SCEnter();
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
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
231 static SCMutex ftp_state_mem_lock = PTHREAD_MUTEX_INITIALIZER;
232 static uint64_t ftp_state_memuse = 0;
233 static uint64_t ftp_state_memcnt = 0;
234 #endif
235
236 static void *FTPStateAlloc(void) {
237 void *s = SCMalloc(sizeof(FtpState));
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
252 static void FTPStateFree(void *s) {
253 FtpState *fstate = (FtpState *) s;
254 if (fstate->port_line != NULL)
255 SCFree(fstate->port_line);
256 SCFree(s);
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
266 void RegisterFTPParsers(void) {
267 char *proto_name = "ftp";
268
269 /** FTP */
270 AlpProtoAdd(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER);
271 AlpProtoAdd(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER);
272 AlpProtoAdd(&alp_proto_ctx, proto_name, IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER);
273
274 AppLayerRegisterProto(proto_name, ALPROTO_FTP, STREAM_TOSERVER,
275 FTPParseRequest);
276 AppLayerRegisterProto(proto_name, ALPROTO_FTP, STREAM_TOCLIENT,
277 FTPParseResponse);
278 AppLayerRegisterParser("ftp.request_command_line", ALPROTO_FTP,
279 FTP_FIELD_REQUEST_LINE, FTPParseRequestCommandLine,
280 "ftp");
281 AppLayerRegisterStateFuncs(ALPROTO_FTP, FTPStateAlloc, FTPStateFree);
282 }
283
284 void FTPAtExitPrintStats(void) {
285 #ifdef DEBUG
286 SCMutexLock(&ftp_state_mem_lock);
287 SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"",
288 ftp_state_memcnt, ftp_state_memuse);
289 SCMutexUnlock(&ftp_state_mem_lock);
290 #endif
291 }
292
293 /* UNITTESTS */
294 #ifdef UNITTESTS
295
296 /** \test Send a get request in one chunk. */
297 int FTPParserTest01(void) {
298 int result = 1;
299 Flow f;
300 uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n";
301 uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */
302 TcpSession ssn;
303
304 memset(&f, 0, sizeof(f));
305 memset(&ssn, 0, sizeof(ssn));
306
307 FLOW_INITIALIZE(&f);
308 f.protoctx = (void *)&ssn;
309
310 StreamTcpInitConfig(TRUE);
311
312 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf, ftplen);
313 if (r != 0) {
314 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
315 result = 0;
316 goto end;
317 }
318
319 FtpState *ftp_state = f.alstate;
320 if (ftp_state == NULL) {
321 SCLogDebug("no ftp state: ");
322 result = 0;
323 goto end;
324 }
325
326 if (ftp_state->command != FTP_COMMAND_PORT) {
327 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
328 result = 0;
329 goto end;
330 }
331
332 end:
333 StreamTcpFreeConfig(TRUE);
334 FLOW_DESTROY(&f);
335 return result;
336 }
337
338 /** \test Send a splitted get request. */
339 int FTPParserTest03(void) {
340 int result = 1;
341 Flow f;
342 uint8_t ftpbuf1[] = "POR";
343 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
344 uint8_t ftpbuf2[] = "T 192,168,1";
345 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
346 uint8_t ftpbuf3[] = "1,1,10,20\r\n";
347 uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */
348 TcpSession ssn;
349
350 memset(&f, 0, sizeof(f));
351 memset(&ssn, 0, sizeof(ssn));
352 f.protoctx = (void *)&ssn;
353
354 StreamTcpInitConfig(TRUE);
355
356 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
357 if (r != 0) {
358 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
359 result = 0;
360 goto end;
361 }
362
363 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER, ftpbuf2, ftplen2);
364 if (r != 0) {
365 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
366 result = 0;
367 goto end;
368 }
369
370 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf3, ftplen3);
371 if (r != 0) {
372 SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r);
373 result = 0;
374 goto end;
375 }
376
377 FtpState *ftp_state = f.alstate;
378 if (ftp_state == NULL) {
379 SCLogDebug("no ftp state: ");
380 result = 0;
381 goto end;
382 }
383
384 if (ftp_state->command != FTP_COMMAND_PORT) {
385 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
386 result = 0;
387 goto end;
388 }
389
390 end:
391 StreamTcpFreeConfig(TRUE);
392 return result;
393 }
394
395 /** \test See how it deals with an incomplete request. */
396 int FTPParserTest06(void) {
397 int result = 1;
398 Flow f;
399 uint8_t ftpbuf1[] = "PORT";
400 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
401 TcpSession ssn;
402
403 memset(&f, 0, sizeof(f));
404 memset(&ssn, 0, sizeof(ssn));
405
406 FLOW_INITIALIZE(&f);
407 f.protoctx = (void *)&ssn;
408
409 StreamTcpInitConfig(TRUE);
410
411 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START|STREAM_EOF, ftpbuf1, ftplen1);
412 if (r != 0) {
413 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
414 result = 0;
415 goto end;
416 }
417
418 FtpState *ftp_state = f.alstate;
419 if (ftp_state == NULL) {
420 SCLogDebug("no ftp state: ");
421 result = 0;
422 goto end;
423 }
424
425 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
426 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
427 result = 0;
428 goto end;
429 }
430
431 end:
432 StreamTcpFreeConfig(TRUE);
433 FLOW_DESTROY(&f);
434 return result;
435 }
436
437 /** \test See how it deals with an incomplete request in multiple chunks. */
438 int FTPParserTest07(void) {
439 int result = 1;
440 Flow f;
441 uint8_t ftpbuf1[] = "PO";
442 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
443 uint8_t ftpbuf2[] = "RT\r\n";
444 uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */
445 TcpSession ssn;
446
447 memset(&f, 0, sizeof(f));
448 memset(&ssn, 0, sizeof(ssn));
449
450 FLOW_INITIALIZE(&f);
451 f.protoctx = (void *)&ssn;
452
453 StreamTcpInitConfig(TRUE);
454
455 int r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_START, ftpbuf1, ftplen1);
456 if (r != 0) {
457 SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r);
458 result = 0;
459 goto end;
460 }
461
462 r = AppLayerParse(NULL, &f, ALPROTO_FTP, STREAM_TOSERVER|STREAM_EOF, ftpbuf2, ftplen2);
463 if (r != 0) {
464 SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r);
465 result = 0;
466 goto end;
467 }
468
469 FtpState *ftp_state = f.alstate;
470 if (ftp_state == NULL) {
471 SCLogDebug("no ftp state: ");
472 result = 0;
473 goto end;
474 }
475
476 if (ftp_state->command != FTP_COMMAND_UNKNOWN) {
477 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command);
478 result = 0;
479 goto end;
480 }
481
482 end:
483 StreamTcpFreeConfig(TRUE);
484 FLOW_DESTROY(&f);
485 return result;
486 }
487
488 /** \test Test case where chunks are smaller than the delim length and the
489 * last chunk is supposed to match the delim. */
490 int FTPParserTest10(void) {
491 int result = 1;
492 Flow f;
493 uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n";
494 uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */
495 TcpSession ssn;
496 int r = 0;
497 memset(&f, 0, sizeof(f));
498 memset(&ssn, 0, sizeof(ssn));
499
500 FLOW_INITIALIZE(&f);
501 f.protoctx = (void *)&ssn;
502
503 StreamTcpInitConfig(TRUE);
504
505 uint32_t u;
506 for (u = 0; u < ftplen1; u++) {
507 uint8_t flags = 0;
508
509 if (u == 0) flags = STREAM_TOSERVER|STREAM_START;
510 else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF;
511 else flags = STREAM_TOSERVER;
512
513 r = AppLayerParse(NULL, &f, ALPROTO_FTP, flags, &ftpbuf1[u], 1);
514 if (r != 0) {
515 SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r);
516 result = 0;
517 goto end;
518 }
519 }
520
521 FtpState *ftp_state = f.alstate;
522 if (ftp_state == NULL) {
523 SCLogDebug("no ftp state: ");
524 result = 0;
525 goto end;
526 }
527
528 if (ftp_state->command != FTP_COMMAND_PORT) {
529 SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command);
530 result = 0;
531 goto end;
532 }
533
534 end:
535 StreamTcpFreeConfig(TRUE);
536 FLOW_DESTROY(&f);
537 return result;
538 }
539 #endif /* UNITTESTS */
540
541 void FTPParserRegisterTests(void) {
542 #ifdef UNITTESTS
543 UtRegisterTest("FTPParserTest01", FTPParserTest01, 1);
544 UtRegisterTest("FTPParserTest03", FTPParserTest03, 1);
545 UtRegisterTest("FTPParserTest06", FTPParserTest06, 1);
546 UtRegisterTest("FTPParserTest07", FTPParserTest07, 1);
547 UtRegisterTest("FTPParserTest10", FTPParserTest10, 1);
548 #endif /* UNITTESTS */
549 }
550