1 /* Copyright (C) 2007-2010 Open Information Security Foundation
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
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.
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
21 * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com>
23 * App Layer Parser for FTP
26 #include "suricata-common.h"
31 #include "util-print.h"
32 #include "util-pool.h"
34 #include "flow-util.h"
36 #include "detect-engine-state.h"
38 #include "stream-tcp-private.h"
39 #include "stream-tcp-reassemble.h"
40 #include "stream-tcp.h"
43 #include "app-layer-protos.h"
44 #include "app-layer-parser.h"
45 #include "app-layer-ftp.h"
48 #include "util-unittest.h"
49 #include "util-debug.h"
50 #include "util-memcmp.h"
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
59 * \retval 1 when the command is parsed, 0 otherwise
61 static int FTPParseRequestCommand(void *ftp_state
, uint8_t *input
,
64 FtpState
*fstate
= (FtpState
*)ftp_state
;
67 if (SCMemcmpLowercase("port", input
, 4) == 0) {
68 fstate
->command
= FTP_COMMAND_PORT
;
72 * Add the ftp commands you need here
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
86 * \retval 1 when the command is parsed, 0 otherwise
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
) {
92 //PrintRawDataFp(stdout, input,input_len);
94 FtpState
*fstate
= (FtpState
*)ftp_state
;
95 uint16_t max_fields
= 2;
102 for (u
= pstate
->parse_field
; u
< max_fields
; u
++) {
105 case 0: /* REQUEST COMMAND */
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
);
113 pstate
->parse_field
= 0;
116 fstate
->arg_offset
= offset
;
117 FTPParseRequestCommand(ftp_state
, input
, input_len
);
120 case 1: /* REQUEST COMMAND ARG */
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
127 if (fstate
->port_line
!= NULL
)
128 SCFree(fstate
->port_line
);
129 fstate
->port_line
= SCMalloc(input_len
);
130 if (fstate
->port_line
== NULL
)
132 fstate
->port_line
= memcpy(fstate
->port_line
, input
,
134 fstate
->port_line_len
= input_len
;
138 } /* end switch command specified args */
145 pstate
->parse_field
= 0;
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
156 * \retval 1 when the command is parsed, 0 otherwise
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
)
164 /* PrintRawDataFp(stdout, input,input_len); */
172 //PrintRawDataFp(stdout, pstate->store, pstate->store_len);
174 const uint8_t delim
[] = { 0x0D, 0x0A };
175 int r
= AlpParseFieldByDelimiter(output
, pstate
, FTP_FIELD_REQUEST_LINE
,
176 delim
, sizeof(delim
), input
, input_len
,
179 pstate
->parse_field
= 0;
182 if (pstate
->store_len
)
183 PrintRawDataFp(stdout
, pstate
->store
, pstate
->store_len
);
185 pstate
->parse_field
= 0;
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
196 * \retval 1 when the command is parsed, 0 otherwise
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
)
203 //PrintRawDataFp(stdout, input,input_len);
206 FtpState
*fstate
= (FtpState
*)ftp_state
;
212 const uint8_t delim
[] = { 0x0D, 0x0A };
213 int r
= AlpParseFieldByDelimiter(output
, pstate
, FTP_FIELD_RESPONSE_LINE
,
214 delim
, sizeof(delim
), input
, input_len
,
217 pstate
->parse_field
= 0;
221 memcpy(rcode
, input
, 4);
223 fstate
->response_code
= atoi(rcode
);
224 SCLogDebug("Response: %u\n", fstate
->response_code
);
226 pstate
->parse_field
= 0;
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;
236 static void *FTPStateAlloc(void) {
237 void *s
= SCMalloc(sizeof(FtpState
));
241 memset(s
, 0, sizeof(FtpState
));
244 SCMutexLock(&ftp_state_mem_lock
);
246 ftp_state_memuse
+=sizeof(FtpState
);
247 SCMutexUnlock(&ftp_state_mem_lock
);
252 static void FTPStateFree(void *s
) {
253 FtpState
*fstate
= (FtpState
*) s
;
254 if (fstate
->port_line
!= NULL
)
255 SCFree(fstate
->port_line
);
258 SCMutexLock(&ftp_state_mem_lock
);
260 ftp_state_memuse
-=sizeof(FtpState
);
261 SCMutexUnlock(&ftp_state_mem_lock
);
266 void RegisterFTPParsers(void) {
267 char *proto_name
= "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
);
274 AppLayerRegisterProto(proto_name
, ALPROTO_FTP
, STREAM_TOSERVER
,
276 AppLayerRegisterProto(proto_name
, ALPROTO_FTP
, STREAM_TOCLIENT
,
278 AppLayerRegisterParser("ftp.request_command_line", ALPROTO_FTP
,
279 FTP_FIELD_REQUEST_LINE
, FTPParseRequestCommandLine
,
281 AppLayerRegisterStateFuncs(ALPROTO_FTP
, FTPStateAlloc
, FTPStateFree
);
284 void FTPAtExitPrintStats(void) {
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
);
296 /** \test Send a get request in one chunk. */
297 int FTPParserTest01(void) {
300 uint8_t ftpbuf
[] = "PORT 192,168,1,1,0,80\r\n";
301 uint32_t ftplen
= sizeof(ftpbuf
) - 1; /* minus the \0 */
304 memset(&f
, 0, sizeof(f
));
305 memset(&ssn
, 0, sizeof(ssn
));
308 f
.protoctx
= (void *)&ssn
;
310 StreamTcpInitConfig(TRUE
);
312 int r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_EOF
, ftpbuf
, ftplen
);
314 SCLogDebug("toserver chunk 1 returned %" PRId32
", expected 0: ", r
);
319 FtpState
*ftp_state
= f
.alstate
;
320 if (ftp_state
== NULL
) {
321 SCLogDebug("no ftp state: ");
326 if (ftp_state
->command
!= FTP_COMMAND_PORT
) {
327 SCLogDebug("expected command %" PRIu32
", got %" PRIu32
": ", FTP_COMMAND_PORT
, ftp_state
->command
);
333 StreamTcpFreeConfig(TRUE
);
338 /** \test Send a splitted get request. */
339 int FTPParserTest03(void) {
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 */
350 memset(&f
, 0, sizeof(f
));
351 memset(&ssn
, 0, sizeof(ssn
));
352 f
.protoctx
= (void *)&ssn
;
354 StreamTcpInitConfig(TRUE
);
356 int r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_START
, ftpbuf1
, ftplen1
);
358 SCLogDebug("toserver chunk 1 returned %" PRId32
", expected 0: ", r
);
363 r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
, ftpbuf2
, ftplen2
);
365 SCLogDebug("toserver chunk 2 returned %" PRId32
", expected 0: ", r
);
370 r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_EOF
, ftpbuf3
, ftplen3
);
372 SCLogDebug("toserver chunk 3 returned %" PRId32
", expected 0: ", r
);
377 FtpState
*ftp_state
= f
.alstate
;
378 if (ftp_state
== NULL
) {
379 SCLogDebug("no ftp state: ");
384 if (ftp_state
->command
!= FTP_COMMAND_PORT
) {
385 SCLogDebug("expected command %" PRIu32
", got %" PRIu32
": ", FTP_COMMAND_PORT
, ftp_state
->command
);
391 StreamTcpFreeConfig(TRUE
);
395 /** \test See how it deals with an incomplete request. */
396 int FTPParserTest06(void) {
399 uint8_t ftpbuf1
[] = "PORT";
400 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
403 memset(&f
, 0, sizeof(f
));
404 memset(&ssn
, 0, sizeof(ssn
));
407 f
.protoctx
= (void *)&ssn
;
409 StreamTcpInitConfig(TRUE
);
411 int r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_START
|STREAM_EOF
, ftpbuf1
, ftplen1
);
413 SCLogDebug("toserver chunk 1 returned %" PRId32
", expected 0: ", r
);
418 FtpState
*ftp_state
= f
.alstate
;
419 if (ftp_state
== NULL
) {
420 SCLogDebug("no ftp state: ");
425 if (ftp_state
->command
!= FTP_COMMAND_UNKNOWN
) {
426 SCLogDebug("expected command %" PRIu32
", got %" PRIu32
": ", FTP_COMMAND_UNKNOWN
, ftp_state
->command
);
432 StreamTcpFreeConfig(TRUE
);
437 /** \test See how it deals with an incomplete request in multiple chunks. */
438 int FTPParserTest07(void) {
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 */
447 memset(&f
, 0, sizeof(f
));
448 memset(&ssn
, 0, sizeof(ssn
));
451 f
.protoctx
= (void *)&ssn
;
453 StreamTcpInitConfig(TRUE
);
455 int r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_START
, ftpbuf1
, ftplen1
);
457 SCLogDebug("toserver chunk 1 returned %" PRId32
", expected 0: ", r
);
462 r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, STREAM_TOSERVER
|STREAM_EOF
, ftpbuf2
, ftplen2
);
464 SCLogDebug("toserver chunk 2 returned %" PRId32
", expected 0: ", r
);
469 FtpState
*ftp_state
= f
.alstate
;
470 if (ftp_state
== NULL
) {
471 SCLogDebug("no ftp state: ");
476 if (ftp_state
->command
!= FTP_COMMAND_UNKNOWN
) {
477 SCLogDebug("expected command %" PRIu32
", got %" PRIu32
": ", FTP_COMMAND_UNKNOWN
, ftp_state
->command
);
483 StreamTcpFreeConfig(TRUE
);
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) {
493 uint8_t ftpbuf1
[] = "PORT 1,2,3,4,5,6\r\n";
494 uint32_t ftplen1
= sizeof(ftpbuf1
) - 1; /* minus the \0 */
497 memset(&f
, 0, sizeof(f
));
498 memset(&ssn
, 0, sizeof(ssn
));
501 f
.protoctx
= (void *)&ssn
;
503 StreamTcpInitConfig(TRUE
);
506 for (u
= 0; u
< ftplen1
; u
++) {
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
;
513 r
= AppLayerParse(NULL
, &f
, ALPROTO_FTP
, flags
, &ftpbuf1
[u
], 1);
515 SCLogDebug("toserver chunk %" PRIu32
" returned %" PRId32
", expected 0: ", u
, r
);
521 FtpState
*ftp_state
= f
.alstate
;
522 if (ftp_state
== NULL
) {
523 SCLogDebug("no ftp state: ");
528 if (ftp_state
->command
!= FTP_COMMAND_PORT
) {
529 SCLogDebug("expected command %" PRIu32
", got %" PRIu32
": ", FTP_COMMAND_PORT
, ftp_state
->command
);
535 StreamTcpFreeConfig(TRUE
);
539 #endif /* UNITTESTS */
541 void FTPParserRegisterTests(void) {
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 */