]>
Commit | Line | Data |
---|---|---|
03de315b | 1 | /* Copyright (C) 2007-2020 Open Information Security Foundation |
ce019275 WM |
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 | 21 | * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com> |
b0a69344 | 22 | * \author Eric Leblond <eric@regit.org> |
1930b1f5 | 23 | * \author Jeff Lucovsky <jeff@lucovsky.org> |
f2f9b832 PR |
24 | * |
25 | * App Layer Parser for FTP | |
26 | */ | |
27 | ||
28 | #include "suricata-common.h" | |
29 | #include "debug.h" | |
30 | #include "decode.h" | |
31 | #include "threads.h" | |
32 | ||
33 | #include "util-print.h" | |
34 | #include "util-pool.h" | |
35 | ||
cc76aa4b | 36 | #include "flow-util.h" |
b0a69344 | 37 | #include "flow-storage.h" |
cc76aa4b | 38 | |
0e4235cc WM |
39 | #include "detect-engine-state.h" |
40 | ||
f2f9b832 PR |
41 | #include "stream-tcp-private.h" |
42 | #include "stream-tcp-reassemble.h" | |
6a53ab9c | 43 | #include "stream-tcp.h" |
f2f9b832 PR |
44 | #include "stream.h" |
45 | ||
429c6388 | 46 | #include "app-layer.h" |
f2f9b832 PR |
47 | #include "app-layer-protos.h" |
48 | #include "app-layer-parser.h" | |
49 | #include "app-layer-ftp.h" | |
b0a69344 | 50 | #include "app-layer-expectation.h" |
f2f9b832 | 51 | |
705471e4 | 52 | #include "util-spm.h" |
09ab032a | 53 | #include "util-mpm.h" |
f2f9b832 PR |
54 | #include "util-unittest.h" |
55 | #include "util-debug.h" | |
1859ed54 | 56 | #include "util-memcmp.h" |
b0a69344 | 57 | #include "util-memrchr.h" |
711b6fb3 EL |
58 | #include "util-mem.h" |
59 | #include "util-misc.h" | |
b0a69344 | 60 | |
7e1235c9 | 61 | #include "output-json.h" |
b573c16d | 62 | #include "rust.h" |
7e1235c9 | 63 | |
09ab032a JL |
64 | typedef struct FTPThreadCtx_ { |
65 | MpmThreadCtx *ftp_mpm_thread_ctx; | |
66 | PrefilterRuleStore *pmq; | |
67 | } FTPThreadCtx; | |
68 | ||
69 | #define FTP_MPM mpm_default_matcher | |
70 | ||
71 | static MpmCtx *ftp_mpm_ctx = NULL; | |
72 | ||
1930b1f5 JL |
73 | const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = { |
74 | /* Parsed and handled */ | |
09ab032a JL |
75 | { FTP_COMMAND_PORT, "PORT", 4}, |
76 | { FTP_COMMAND_EPRT, "EPRT", 4}, | |
77 | { FTP_COMMAND_AUTH_TLS, "AUTH TLS", 8}, | |
78 | { FTP_COMMAND_PASV, "PASV", 4}, | |
79 | { FTP_COMMAND_RETR, "RETR", 4}, | |
80 | { FTP_COMMAND_EPSV, "EPSV", 4}, | |
81 | { FTP_COMMAND_STOR, "STOR", 4}, | |
1930b1f5 JL |
82 | |
83 | /* Parsed, but not handled */ | |
09ab032a JL |
84 | { FTP_COMMAND_ABOR, "ABOR", 4}, |
85 | { FTP_COMMAND_ACCT, "ACCT", 4}, | |
86 | { FTP_COMMAND_ALLO, "ALLO", 4}, | |
87 | { FTP_COMMAND_APPE, "APPE", 4}, | |
88 | { FTP_COMMAND_CDUP, "CDUP", 4}, | |
89 | { FTP_COMMAND_CHMOD, "CHMOD", 5}, | |
90 | { FTP_COMMAND_CWD, "CWD", 3}, | |
91 | { FTP_COMMAND_DELE, "DELE", 4}, | |
92 | { FTP_COMMAND_HELP, "HELP", 4}, | |
93 | { FTP_COMMAND_IDLE, "IDLE", 4}, | |
94 | { FTP_COMMAND_LIST, "LIST", 4}, | |
95 | { FTP_COMMAND_MAIL, "MAIL", 4}, | |
96 | { FTP_COMMAND_MDTM, "MDTM", 4}, | |
97 | { FTP_COMMAND_MKD, "MKD", 3}, | |
98 | { FTP_COMMAND_MLFL, "MLFL", 4}, | |
99 | { FTP_COMMAND_MODE, "MODE", 4}, | |
100 | { FTP_COMMAND_MRCP, "MRCP", 4}, | |
101 | { FTP_COMMAND_MRSQ, "MRSQ", 4}, | |
102 | { FTP_COMMAND_MSAM, "MSAM", 4}, | |
103 | { FTP_COMMAND_MSND, "MSND", 4}, | |
104 | { FTP_COMMAND_MSOM, "MSOM", 4}, | |
105 | { FTP_COMMAND_NLST, "NLST", 4}, | |
106 | { FTP_COMMAND_NOOP, "NOOP", 4}, | |
107 | { FTP_COMMAND_PASS, "PASS", 4}, | |
108 | { FTP_COMMAND_PWD, "PWD", 3}, | |
109 | { FTP_COMMAND_QUIT, "QUIT", 4}, | |
110 | { FTP_COMMAND_REIN, "REIN", 4}, | |
111 | { FTP_COMMAND_REST, "REST", 4}, | |
112 | { FTP_COMMAND_RMD, "RMD", 3}, | |
113 | { FTP_COMMAND_RNFR, "RNFR", 4}, | |
114 | { FTP_COMMAND_RNTO, "RNTO", 4}, | |
115 | { FTP_COMMAND_SITE, "SITE", 4}, | |
116 | { FTP_COMMAND_SIZE, "SIZE", 4}, | |
117 | { FTP_COMMAND_SMNT, "SMNT", 4}, | |
118 | { FTP_COMMAND_STAT, "STAT", 4}, | |
119 | { FTP_COMMAND_STOU, "STOU", 4}, | |
120 | { FTP_COMMAND_STRU, "STRU", 4}, | |
121 | { FTP_COMMAND_SYST, "SYST", 4}, | |
122 | { FTP_COMMAND_TYPE, "TYPE", 4}, | |
123 | { FTP_COMMAND_UMASK, "UMASK", 5}, | |
124 | { FTP_COMMAND_USER, "USER", 4}, | |
125 | { FTP_COMMAND_UNKNOWN, NULL, 0} | |
1930b1f5 | 126 | }; |
711b6fb3 EL |
127 | uint64_t ftp_config_memcap = 0; |
128 | ||
129 | SC_ATOMIC_DECLARE(uint64_t, ftp_memuse); | |
130 | SC_ATOMIC_DECLARE(uint64_t, ftp_memcap); | |
131 | ||
8ae69115 | 132 | static FTPTransaction *FTPGetOldestTx(FtpState *); |
2149807b | 133 | |
711b6fb3 EL |
134 | static void FTPParseMemcap(void) |
135 | { | |
136 | const char *conf_val; | |
137 | ||
138 | /** set config values for memcap, prealloc and hash_size */ | |
139 | if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1) | |
140 | { | |
141 | if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) { | |
142 | SCLogError(SC_ERR_SIZE_PARSE, "Error parsing ftp.memcap " | |
143 | "from conf file - %s. Killing engine", | |
144 | conf_val); | |
145 | exit(EXIT_FAILURE); | |
146 | } | |
147 | SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap); | |
148 | } else { | |
149 | /* default to unlimited */ | |
150 | ftp_config_memcap = 0; | |
151 | } | |
152 | ||
153 | SC_ATOMIC_INIT(ftp_memuse); | |
154 | SC_ATOMIC_INIT(ftp_memcap); | |
155 | } | |
156 | ||
157 | static void FTPIncrMemuse(uint64_t size) | |
158 | { | |
159 | (void) SC_ATOMIC_ADD(ftp_memuse, size); | |
160 | return; | |
161 | } | |
162 | ||
163 | static void FTPDecrMemuse(uint64_t size) | |
164 | { | |
165 | (void) SC_ATOMIC_SUB(ftp_memuse, size); | |
166 | return; | |
167 | } | |
168 | ||
169 | uint64_t FTPMemuseGlobalCounter(void) | |
170 | { | |
171 | uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse); | |
172 | return tmpval; | |
173 | } | |
174 | ||
175 | uint64_t FTPMemcapGlobalCounter(void) | |
176 | { | |
177 | uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap); | |
178 | return tmpval; | |
179 | } | |
180 | ||
181 | /** | |
182 | * \brief Check if alloc'ing "size" would mean we're over memcap | |
183 | * | |
184 | * \retval 1 if in bounds | |
185 | * \retval 0 if not in bounds | |
186 | */ | |
187 | static int FTPCheckMemcap(uint64_t size) | |
188 | { | |
189 | if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap) | |
190 | return 1; | |
191 | (void) SC_ATOMIC_ADD(ftp_memcap, 1); | |
192 | return 0; | |
193 | } | |
194 | ||
195 | static void *FTPMalloc(size_t size) | |
196 | { | |
197 | void *ptr = NULL; | |
198 | ||
199 | if (FTPCheckMemcap((uint32_t)size) == 0) | |
200 | return NULL; | |
201 | ||
202 | ptr = SCMalloc(size); | |
203 | ||
204 | if (unlikely(ptr == NULL)) | |
205 | return NULL; | |
206 | ||
207 | FTPIncrMemuse((uint64_t)size); | |
208 | ||
209 | return ptr; | |
210 | } | |
211 | ||
212 | static void *FTPCalloc(size_t n, size_t size) | |
213 | { | |
214 | void *ptr = NULL; | |
215 | ||
216 | if (FTPCheckMemcap((uint32_t)(n * size)) == 0) | |
217 | return NULL; | |
218 | ||
219 | ptr = SCCalloc(n, size); | |
220 | ||
221 | if (unlikely(ptr == NULL)) | |
222 | return NULL; | |
223 | ||
224 | FTPIncrMemuse((uint64_t)(n * size)); | |
225 | ||
226 | return ptr; | |
227 | } | |
228 | ||
229 | static void *FTPRealloc(void *ptr, size_t orig_size, size_t size) | |
230 | { | |
231 | void *rptr = NULL; | |
232 | ||
233 | if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) | |
234 | return NULL; | |
235 | ||
236 | rptr = SCRealloc(ptr, size); | |
237 | if (rptr == NULL) | |
238 | return NULL; | |
239 | ||
240 | if (size > orig_size) { | |
241 | FTPIncrMemuse(size - orig_size); | |
242 | } else { | |
243 | FTPDecrMemuse(orig_size - size); | |
244 | } | |
245 | ||
246 | return rptr; | |
247 | } | |
248 | ||
249 | static void FTPFree(void *ptr, size_t size) | |
250 | { | |
251 | SCFree(ptr); | |
252 | ||
253 | FTPDecrMemuse((uint64_t)size); | |
254 | } | |
255 | ||
1930b1f5 JL |
256 | static FTPString *FTPStringAlloc(void) |
257 | { | |
258 | return FTPCalloc(1, sizeof(FTPString)); | |
259 | } | |
260 | ||
261 | static void FTPStringFree(FTPString *str) | |
262 | { | |
263 | if (str->str) { | |
264 | FTPFree(str->str, str->len); | |
265 | } | |
266 | ||
267 | FTPFree(str, sizeof(FTPString)); | |
268 | } | |
269 | ||
09ab032a JL |
270 | static void *FTPLocalStorageAlloc(void) |
271 | { | |
272 | /* needed by the mpm */ | |
273 | FTPThreadCtx *td = SCCalloc(1, sizeof(*td)); | |
274 | if (td == NULL) { | |
275 | exit(EXIT_FAILURE); | |
276 | } | |
277 | ||
278 | td->pmq = SCCalloc(1, sizeof(*td->pmq)); | |
279 | if (td->pmq == NULL) { | |
280 | exit(EXIT_FAILURE); | |
281 | } | |
282 | PmqSetup(td->pmq); | |
283 | ||
284 | td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx)); | |
285 | if (unlikely(td->ftp_mpm_thread_ctx == NULL)) { | |
286 | exit(EXIT_FAILURE); | |
287 | } | |
288 | MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM); | |
289 | return td; | |
290 | } | |
291 | ||
292 | static void FTPLocalStorageFree(void *ptr) | |
293 | { | |
294 | FTPThreadCtx *td = ptr; | |
295 | if (td != NULL) { | |
296 | if (td->pmq != NULL) { | |
297 | PmqFree(td->pmq); | |
298 | SCFree(td->pmq); | |
299 | } | |
300 | ||
301 | if (td->ftp_mpm_thread_ctx != NULL) { | |
302 | mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx); | |
303 | SCFree(td->ftp_mpm_thread_ctx); | |
304 | } | |
305 | ||
306 | SCFree(td); | |
307 | } | |
308 | ||
309 | return; | |
310 | } | |
1930b1f5 JL |
311 | static FTPTransaction *FTPTransactionCreate(FtpState *state) |
312 | { | |
313 | SCEnter(); | |
314 | FTPTransaction *tx = FTPCalloc(1, sizeof(*tx)); | |
315 | if (tx == NULL) { | |
316 | return NULL; | |
317 | } | |
318 | ||
319 | TAILQ_INSERT_TAIL(&state->tx_list, tx, next); | |
320 | tx->tx_id = state->tx_cnt++; | |
321 | ||
322 | TAILQ_INIT(&tx->response_list); | |
323 | ||
324 | SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt); | |
325 | return tx; | |
326 | } | |
327 | ||
328 | static void FTPTransactionFree(FTPTransaction *tx) | |
329 | { | |
330 | SCEnter(); | |
331 | ||
332 | if (tx->de_state != NULL) { | |
333 | DetectEngineStateFree(tx->de_state); | |
334 | } | |
335 | ||
336 | if (tx->request) { | |
337 | FTPFree(tx->request, tx->request_length); | |
338 | } | |
1930b1f5 JL |
339 | |
340 | FTPString *str = NULL; | |
341 | while ((str = TAILQ_FIRST(&tx->response_list))) { | |
342 | TAILQ_REMOVE(&tx->response_list, str, next); | |
343 | FTPStringFree(str); | |
344 | } | |
345 | ||
aa3f784d | 346 | FTPFree(tx, sizeof(*tx)); |
1930b1f5 JL |
347 | } |
348 | ||
6ea8ac44 AS |
349 | static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state) |
350 | { | |
1f07d152 | 351 | void *ptmp; |
6ea8ac44 AS |
352 | if (line_state->current_line_lf_seen == 1) { |
353 | /* we have seen the lf for the previous line. Clear the parser | |
354 | * details to parse new line */ | |
355 | line_state->current_line_lf_seen = 0; | |
356 | if (line_state->current_line_db == 1) { | |
357 | line_state->current_line_db = 0; | |
711b6fb3 | 358 | FTPFree(line_state->db, line_state->db_len); |
6ea8ac44 AS |
359 | line_state->db = NULL; |
360 | line_state->db_len = 0; | |
361 | state->current_line = NULL; | |
362 | state->current_line_len = 0; | |
363 | } | |
364 | } | |
365 | ||
366 | uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len); | |
367 | ||
368 | if (lf_idx == NULL) { | |
369 | /* fragmented lines. Decoder event for special cases. Not all | |
370 | * fragmented lines should be treated as a possible evasion | |
371 | * attempt. With multi payload ftp chunks we can have valid | |
372 | * cases of fragmentation. But within the same segment chunk | |
373 | * if we see fragmentation then it's definitely something you | |
374 | * should alert about */ | |
375 | if (line_state->current_line_db == 0) { | |
711b6fb3 | 376 | line_state->db = FTPMalloc(state->input_len); |
6ea8ac44 AS |
377 | if (line_state->db == NULL) { |
378 | return -1; | |
379 | } | |
380 | line_state->current_line_db = 1; | |
381 | memcpy(line_state->db, state->input, state->input_len); | |
382 | line_state->db_len = state->input_len; | |
383 | } else { | |
711b6fb3 | 384 | ptmp = FTPRealloc(line_state->db, line_state->db_len, |
1f07d152 EL |
385 | (line_state->db_len + state->input_len)); |
386 | if (ptmp == NULL) { | |
711b6fb3 | 387 | FTPFree(line_state->db, line_state->db_len); |
1f07d152 EL |
388 | line_state->db = NULL; |
389 | line_state->db_len = 0; | |
6ea8ac44 AS |
390 | return -1; |
391 | } | |
1f07d152 EL |
392 | line_state->db = ptmp; |
393 | ||
6ea8ac44 AS |
394 | memcpy(line_state->db + line_state->db_len, |
395 | state->input, state->input_len); | |
396 | line_state->db_len += state->input_len; | |
397 | } | |
398 | state->input += state->input_len; | |
399 | state->input_len = 0; | |
400 | ||
401 | return -1; | |
402 | ||
403 | } else { | |
404 | line_state->current_line_lf_seen = 1; | |
405 | ||
406 | if (line_state->current_line_db == 1) { | |
711b6fb3 | 407 | ptmp = FTPRealloc(line_state->db, line_state->db_len, |
1f07d152 EL |
408 | (line_state->db_len + (lf_idx + 1 - state->input))); |
409 | if (ptmp == NULL) { | |
711b6fb3 | 410 | FTPFree(line_state->db, line_state->db_len); |
1f07d152 EL |
411 | line_state->db = NULL; |
412 | line_state->db_len = 0; | |
6ea8ac44 AS |
413 | return -1; |
414 | } | |
1f07d152 EL |
415 | line_state->db = ptmp; |
416 | ||
6ea8ac44 AS |
417 | memcpy(line_state->db + line_state->db_len, |
418 | state->input, (lf_idx + 1 - state->input)); | |
419 | line_state->db_len += (lf_idx + 1 - state->input); | |
420 | ||
421 | if (line_state->db_len > 1 && | |
422 | line_state->db[line_state->db_len - 2] == 0x0D) { | |
423 | line_state->db_len -= 2; | |
424 | state->current_line_delimiter_len = 2; | |
425 | } else { | |
426 | line_state->db_len -= 1; | |
427 | state->current_line_delimiter_len = 1; | |
428 | } | |
429 | ||
430 | state->current_line = line_state->db; | |
431 | state->current_line_len = line_state->db_len; | |
432 | ||
433 | } else { | |
434 | state->current_line = state->input; | |
435 | state->current_line_len = lf_idx - state->input; | |
436 | ||
437 | if (state->input != lf_idx && | |
438 | *(lf_idx - 1) == 0x0D) { | |
439 | state->current_line_len--; | |
440 | state->current_line_delimiter_len = 2; | |
441 | } else { | |
442 | state->current_line_delimiter_len = 1; | |
443 | } | |
444 | } | |
445 | ||
446 | state->input_len -= (lf_idx - state->input) + 1; | |
447 | state->input = (lf_idx + 1); | |
448 | ||
449 | return 0; | |
450 | } | |
451 | ||
452 | } | |
453 | ||
454 | static int FTPGetLine(FtpState *state) | |
455 | { | |
456 | SCEnter(); | |
457 | ||
458 | /* we have run out of input */ | |
459 | if (state->input_len <= 0) | |
460 | return -1; | |
461 | ||
462 | /* toserver */ | |
463 | if (state->direction == 0) | |
464 | return FTPGetLineForDirection(state, &state->line_state[0]); | |
465 | else | |
466 | return FTPGetLineForDirection(state, &state->line_state[1]); | |
467 | } | |
468 | ||
f2f9b832 PR |
469 | /** |
470 | * \brief This function is called to determine and set which command is being | |
1930b1f5 | 471 | * transferred to the ftp server |
86fabef0 | 472 | * \param thread context |
f2f9b832 PR |
473 | * \param input input line of the command |
474 | * \param len of the command | |
1930b1f5 | 475 | * \param cmd_descriptor when the command has been parsed |
f2f9b832 PR |
476 | * |
477 | * \retval 1 when the command is parsed, 0 otherwise | |
478 | */ | |
86fabef0 | 479 | static int FTPParseRequestCommand(FTPThreadCtx *td, |
579cc9f0 | 480 | const uint8_t *input, uint32_t input_len, |
09ab032a | 481 | const FtpCommand **cmd_descriptor) |
8f1d7503 | 482 | { |
f2f9b832 | 483 | SCEnter(); |
11b9e6fd | 484 | |
09ab032a JL |
485 | /* I don't like this pmq reset here. We'll devise a method later, that |
486 | * should make the use of the mpm very efficient */ | |
487 | PmqReset(td->pmq); | |
488 | int mpm_cnt = mpm_table[FTP_MPM].Search(ftp_mpm_ctx, td->ftp_mpm_thread_ctx, | |
489 | td->pmq, input, input_len); | |
490 | if (mpm_cnt) { | |
491 | *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; | |
86fabef0 | 492 | SCReturnInt(1); |
b0a69344 | 493 | } |
09ab032a JL |
494 | |
495 | *cmd_descriptor = NULL; | |
86fabef0 | 496 | SCReturnInt(0); |
f2f9b832 PR |
497 | } |
498 | ||
b0a69344 EL |
499 | struct FtpTransferCmd { |
500 | /** Need to look like a ExpectationData so DFree must | |
501 | * be first field . */ | |
502 | void (*DFree)(void *); | |
503 | uint64_t flow_id; | |
504 | uint8_t *file_name; | |
505 | uint16_t file_len; | |
506 | FtpRequestCommand cmd; | |
507 | }; | |
508 | ||
509 | static void FtpTransferCmdFree(void *data) | |
510 | { | |
511 | struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; | |
512 | if (cmd == NULL) | |
513 | return; | |
514 | if (cmd->file_name) { | |
aa3f784d | 515 | FTPFree(cmd->file_name, cmd->file_len + 1); |
b0a69344 | 516 | } |
711b6fb3 | 517 | FTPFree(cmd, sizeof(struct FtpTransferCmd)); |
b0a69344 EL |
518 | } |
519 | ||
579cc9f0 | 520 | static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length) |
1930b1f5 JL |
521 | { |
522 | if (likely(length)) { | |
1588cd87 | 523 | uint8_t *where = FTPCalloc(length + 1, sizeof(char)); |
1930b1f5 JL |
524 | if (unlikely(where == NULL)) { |
525 | return 0; | |
526 | } | |
527 | memcpy(where, src, length); | |
528 | ||
529 | /* Remove trailing newlines/carriage returns */ | |
1588cd87 ZK |
530 | while (length && isspace((unsigned char) where[length - 1])) { |
531 | length--; | |
1930b1f5 | 532 | } |
1588cd87 ZK |
533 | |
534 | where[length] = '\0'; | |
1930b1f5 JL |
535 | *dest = where; |
536 | } | |
537 | /* either 0 or actual */ | |
aa3f784d | 538 | return length ? length + 1 : 0; |
1930b1f5 JL |
539 | } |
540 | ||
4f33b8c1 | 541 | |
f2f9b832 PR |
542 | /** |
543 | * \brief This function is called to retrieve a ftp request | |
544 | * \param ftp_state the ftp state structure for the parser | |
545 | * \param input input line of the command | |
546 | * \param input_len length of the request | |
547 | * \param output the resulting output | |
548 | * | |
3bcf948a VJ |
549 | * \retval APP_LAYER_OK when input was process successfully |
550 | * \retval APP_LAYER_ERROR when a unrecoverable error was encountered | |
f2f9b832 | 551 | */ |
44d3f264 | 552 | static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, |
9634e60e | 553 | AppLayerParserState *pstate, |
579cc9f0 | 554 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 555 | void *local_data, const uint8_t flags) |
9a6aef45 | 556 | { |
09ab032a JL |
557 | FTPThreadCtx *thread_data = local_data; |
558 | ||
f2f9b832 | 559 | SCEnter(); |
f2f9b832 PR |
560 | /* PrintRawDataFp(stdout, input,input_len); */ |
561 | ||
6ea8ac44 | 562 | FtpState *state = (FtpState *)ftp_state; |
1f07d152 | 563 | void *ptmp; |
6ea8ac44 | 564 | |
4e7cb7b8 | 565 | if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { |
44d3f264 | 566 | SCReturnStruct(APP_LAYER_OK); |
f4f53924 | 567 | } else if (input == NULL || input_len == 0) { |
44d3f264 | 568 | SCReturnStruct(APP_LAYER_ERROR); |
4e7cb7b8 VJ |
569 | } |
570 | ||
6ea8ac44 AS |
571 | state->input = input; |
572 | state->input_len = input_len; | |
573 | /* toserver stream */ | |
574 | state->direction = 0; | |
575 | ||
b0a69344 | 576 | int direction = STREAM_TOSERVER; |
6ea8ac44 | 577 | while (FTPGetLine(state) >= 0) { |
1930b1f5 JL |
578 | const FtpCommand *cmd_descriptor; |
579 | ||
157d01e8 VJ |
580 | if (!FTPParseRequestCommand(thread_data, |
581 | state->current_line, state->current_line_len, | |
582 | &cmd_descriptor)) { | |
1930b1f5 JL |
583 | state->command = FTP_COMMAND_UNKNOWN; |
584 | continue; | |
585 | } | |
586 | ||
587 | state->command = cmd_descriptor->command; | |
588 | ||
2149807b JL |
589 | FTPTransaction *tx = FTPTransactionCreate(state); |
590 | if (unlikely(tx == NULL)) | |
44d3f264 | 591 | SCReturnStruct(APP_LAYER_ERROR); |
2149807b | 592 | state->curr_tx = tx; |
1930b1f5 JL |
593 | |
594 | tx->command_descriptor = cmd_descriptor; | |
157d01e8 VJ |
595 | tx->request_length = CopyCommandLine(&tx->request, |
596 | state->current_line, state->current_line_len); | |
1930b1f5 | 597 | |
b0a69344 | 598 | switch (state->command) { |
4f33b8c1 JL |
599 | case FTP_COMMAND_EPRT: |
600 | // fallthrough | |
b0a69344 | 601 | case FTP_COMMAND_PORT: |
3ae2edb2 | 602 | if (state->current_line_len + 1 > state->port_line_size) { |
4f33b8c1 | 603 | /* Allocate an extra byte for a NULL terminator */ |
711b6fb3 | 604 | ptmp = FTPRealloc(state->port_line, state->port_line_size, |
b4070b6d | 605 | state->current_line_len); |
b0a69344 | 606 | if (ptmp == NULL) { |
4f33b8c1 JL |
607 | if (state->port_line) { |
608 | FTPFree(state->port_line, state->port_line_size); | |
609 | state->port_line = NULL; | |
610 | state->port_line_size = 0; | |
611 | } | |
44d3f264 | 612 | SCReturnStruct(APP_LAYER_OK); |
b0a69344 EL |
613 | } |
614 | state->port_line = ptmp; | |
b4070b6d | 615 | state->port_line_size = state->current_line_len; |
6ea8ac44 | 616 | } |
b0a69344 EL |
617 | memcpy(state->port_line, state->current_line, |
618 | state->current_line_len); | |
619 | state->port_line_len = state->current_line_len; | |
620 | break; | |
621 | case FTP_COMMAND_RETR: | |
622 | /* change direction (default to server) so expectation will handle | |
623 | * the correct message when expectation will match. | |
624 | */ | |
625 | direction = STREAM_TOCLIENT; | |
626 | // fallthrough | |
3bcf948a | 627 | case FTP_COMMAND_STOR: { |
354074ba JL |
628 | /* Ensure that there is a negotiated dyn port and a file |
629 | * name -- need more than 5 chars: cmd [4], space, <filename> | |
630 | */ | |
631 | if (state->dyn_port == 0 || state->current_line_len < 6) { | |
44d3f264 | 632 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 633 | } |
711b6fb3 | 634 | struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); |
b0a69344 | 635 | if (data == NULL) |
44d3f264 | 636 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
637 | data->DFree = FtpTransferCmdFree; |
638 | /* Min size has been checked in FTPParseRequestCommand */ | |
711b6fb3 | 639 | data->file_name = FTPCalloc(state->current_line_len - 4, sizeof(char)); |
b0a69344 | 640 | if (data->file_name == NULL) { |
de983fb7 | 641 | FtpTransferCmdFree(data); |
44d3f264 | 642 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
643 | } |
644 | data->file_name[state->current_line_len - 5] = 0; | |
645 | data->file_len = state->current_line_len - 5; | |
646 | memcpy(data->file_name, state->current_line + 5, state->current_line_len - 5); | |
647 | data->cmd = state->command; | |
648 | data->flow_id = FlowGetId(f); | |
4f33b8c1 JL |
649 | int ret = AppLayerExpectationCreate(f, |
650 | state->active ? STREAM_TOSERVER : direction, | |
651 | 0, state->dyn_port, ALPROTO_FTPDATA, data); | |
b0a69344 | 652 | if (ret == -1) { |
de983fb7 | 653 | FtpTransferCmdFree(data); |
b0a69344 | 654 | SCLogDebug("No expectation created."); |
44d3f264 | 655 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 656 | } else { |
4f33b8c1 JL |
657 | SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].", |
658 | state->active ? "to server" : "to client", | |
659 | state->dyn_port); | |
b0a69344 | 660 | } |
4f33b8c1 | 661 | |
b0a69344 EL |
662 | /* reset the dyn port to avoid duplicate */ |
663 | state->dyn_port = 0; | |
4f33b8c1 JL |
664 | /* reset active/passive indicator */ |
665 | state->active = false; | |
b0a69344 EL |
666 | } |
667 | break; | |
668 | default: | |
669 | break; | |
6ea8ac44 | 670 | } |
f2f9b832 | 671 | } |
f2f9b832 | 672 | |
44d3f264 | 673 | SCReturnStruct(APP_LAYER_OK); |
f2f9b832 PR |
674 | } |
675 | ||
579cc9f0 | 676 | static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
b0a69344 | 677 | { |
f79316d7 | 678 | uint16_t dyn_port = rs_ftp_pasv_response(input, input_len); |
b0a69344 EL |
679 | if (dyn_port == 0) { |
680 | return -1; | |
681 | } | |
4f33b8c1 JL |
682 | SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port); |
683 | state->active = false; | |
b0a69344 | 684 | state->dyn_port = dyn_port; |
1930b1f5 JL |
685 | state->curr_tx->dyn_port = dyn_port; |
686 | state->curr_tx->active = false; | |
b0a69344 EL |
687 | |
688 | return 0; | |
689 | } | |
690 | ||
579cc9f0 | 691 | static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
b0a69344 | 692 | { |
f79316d7 | 693 | uint16_t dyn_port = rs_ftp_epsv_response(input, input_len); |
b0a69344 EL |
694 | if (dyn_port == 0) { |
695 | return -1; | |
696 | } | |
4f33b8c1 JL |
697 | SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port); |
698 | state->active = false; | |
b0a69344 | 699 | state->dyn_port = dyn_port; |
1930b1f5 JL |
700 | state->curr_tx->dyn_port = dyn_port; |
701 | state->curr_tx->active = false; | |
b0a69344 EL |
702 | return 0; |
703 | } | |
704 | ||
1930b1f5 JL |
705 | /** |
706 | * \brief Handle preliminary replies -- keep tx open | |
3bcf948a | 707 | * \retval bool True for a positive preliminary reply; false otherwise |
1930b1f5 JL |
708 | * |
709 | * 1yz Positive Preliminary reply | |
710 | * | |
711 | * The requested action is being initiated; expect another | |
712 | * reply before proceeding with a new command | |
713 | */ | |
579cc9f0 | 714 | static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len) |
1930b1f5 JL |
715 | { |
716 | return input_len >= 4 && isdigit(input[0]) && input[0] == '1' && | |
717 | isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]); | |
718 | } | |
719 | ||
f2f9b832 PR |
720 | /** |
721 | * \brief This function is called to retrieve a ftp response | |
722 | * \param ftp_state the ftp state structure for the parser | |
723 | * \param input input line of the command | |
724 | * \param input_len length of the request | |
725 | * \param output the resulting output | |
726 | * | |
727 | * \retval 1 when the command is parsed, 0 otherwise | |
728 | */ | |
44d3f264 | 729 | static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
579cc9f0 | 730 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 731 | void *local_data, const uint8_t flags) |
9a6aef45 | 732 | { |
11b9e6fd | 733 | FtpState *state = (FtpState *)ftp_state; |
1930b1f5 | 734 | |
b595da6c | 735 | if (unlikely(input_len == 0)) { |
44d3f264 | 736 | SCReturnStruct(APP_LAYER_OK); |
b595da6c | 737 | } |
a6294d6e PA |
738 | state->input = input; |
739 | state->input_len = input_len; | |
740 | /* toclient stream */ | |
741 | state->direction = 1; | |
a04b1c16 | 742 | |
a6294d6e | 743 | while (FTPGetLine(state) >= 0) { |
699d6682 PA |
744 | FTPTransaction *tx = FTPGetOldestTx(state); |
745 | if (tx == NULL) { | |
746 | tx = FTPTransactionCreate(state); | |
747 | } | |
748 | if (unlikely(tx == NULL)) { | |
749 | SCReturnStruct(APP_LAYER_ERROR); | |
750 | } | |
751 | if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { | |
752 | /* unknown */ | |
753 | tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1]; | |
11b9e6fd | 754 | } |
11b9e6fd | 755 | |
699d6682 | 756 | state->curr_tx = tx; |
fef124b9 PA |
757 | uint16_t dyn_port; |
758 | switch (state->command) { | |
759 | case FTP_COMMAND_AUTH_TLS: | |
760 | if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) { | |
761 | AppLayerRequestProtocolTLSUpgrade(f); | |
762 | } | |
763 | break; | |
4f33b8c1 | 764 | |
fef124b9 PA |
765 | case FTP_COMMAND_EPRT: |
766 | dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len); | |
767 | if (dyn_port == 0) { | |
768 | goto tx_complete; | |
769 | } | |
770 | state->dyn_port = dyn_port; | |
771 | state->active = true; | |
772 | tx->dyn_port = dyn_port; | |
773 | tx->active = true; | |
774 | SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port); | |
775 | break; | |
1930b1f5 | 776 | |
fef124b9 PA |
777 | case FTP_COMMAND_PORT: |
778 | dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len); | |
699d6682 PA |
779 | if (dyn_port == 0) { |
780 | goto tx_complete; | |
781 | } | |
782 | state->dyn_port = dyn_port; | |
783 | state->active = true; | |
784 | tx->dyn_port = state->dyn_port; | |
785 | tx->active = true; | |
786 | SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port); | |
fef124b9 | 787 | break; |
b0a69344 | 788 | |
fef124b9 PA |
789 | case FTP_COMMAND_PASV: |
790 | if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) { | |
791 | FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len); | |
792 | } | |
793 | break; | |
b0a69344 | 794 | |
fef124b9 PA |
795 | case FTP_COMMAND_EPSV: |
796 | if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) { | |
797 | FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len); | |
798 | } | |
799 | break; | |
800 | default: | |
801 | break; | |
1930b1f5 | 802 | } |
1930b1f5 | 803 | |
699d6682 PA |
804 | if (likely(state->current_line_len)) { |
805 | FTPString *response = FTPStringAlloc(); | |
806 | if (likely(response)) { | |
807 | response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len); | |
808 | TAILQ_INSERT_TAIL(&tx->response_list, response, next); | |
809 | } | |
810 | } | |
811 | ||
812 | /* Handle preliminary replies -- keep tx open */ | |
813 | if (FTPIsPPR(state->current_line, state->current_line_len)) { | |
814 | continue; | |
815 | } | |
816 | tx_complete: | |
817 | tx->done = true; | |
a6294d6e PA |
818 | } |
819 | ||
44d3f264 | 820 | SCReturnStruct(APP_LAYER_OK); |
f2f9b832 PR |
821 | } |
822 | ||
911d423a | 823 | |
f2f9b832 | 824 | #ifdef DEBUG |
5532af46 | 825 | static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER; |
f2f9b832 PR |
826 | static uint64_t ftp_state_memuse = 0; |
827 | static uint64_t ftp_state_memcnt = 0; | |
828 | #endif | |
829 | ||
8f1d7503 KS |
830 | static void *FTPStateAlloc(void) |
831 | { | |
1930b1f5 | 832 | void *s = FTPCalloc(1, sizeof(FtpState)); |
e176be6f | 833 | if (unlikely(s == NULL)) |
f2f9b832 PR |
834 | return NULL; |
835 | ||
1930b1f5 JL |
836 | FtpState *ftp_state = (FtpState *) s; |
837 | TAILQ_INIT(&ftp_state->tx_list); | |
f2f9b832 PR |
838 | |
839 | #ifdef DEBUG | |
840 | SCMutexLock(&ftp_state_mem_lock); | |
841 | ftp_state_memcnt++; | |
842 | ftp_state_memuse+=sizeof(FtpState); | |
843 | SCMutexUnlock(&ftp_state_mem_lock); | |
844 | #endif | |
845 | return s; | |
846 | } | |
847 | ||
8f1d7503 KS |
848 | static void FTPStateFree(void *s) |
849 | { | |
18954a2c PR |
850 | FtpState *fstate = (FtpState *) s; |
851 | if (fstate->port_line != NULL) | |
711b6fb3 | 852 | FTPFree(fstate->port_line, fstate->port_line_size); |
b4ab9a0a | 853 | if (fstate->line_state[0].db) |
711b6fb3 | 854 | FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len); |
b4ab9a0a | 855 | if (fstate->line_state[1].db) |
711b6fb3 | 856 | FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len); |
402eb645 VJ |
857 | |
858 | //AppLayerDecoderEventsFreeEvents(&s->decoder_events); | |
859 | ||
1930b1f5 JL |
860 | FTPTransaction *tx = NULL; |
861 | while ((tx = TAILQ_FIRST(&fstate->tx_list))) { | |
862 | TAILQ_REMOVE(&fstate->tx_list, tx, next); | |
2149807b | 863 | SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p", |
09ab032a | 864 | tx->command_descriptor->command_name, |
2149807b JL |
865 | s, tx->tx_id, |
866 | tx->request_length, tx->request); | |
1930b1f5 | 867 | FTPTransactionFree(tx); |
402eb645 VJ |
868 | } |
869 | ||
711b6fb3 | 870 | FTPFree(s, sizeof(FtpState)); |
f2f9b832 PR |
871 | #ifdef DEBUG |
872 | SCMutexLock(&ftp_state_mem_lock); | |
873 | ftp_state_memcnt--; | |
874 | ftp_state_memuse-=sizeof(FtpState); | |
875 | SCMutexUnlock(&ftp_state_mem_lock); | |
876 | #endif | |
877 | } | |
878 | ||
7548944b | 879 | static int FTPSetTxDetectState(void *vtx, DetectEngineState *de_state) |
402eb645 | 880 | { |
1930b1f5 JL |
881 | FTPTransaction *tx = (FTPTransaction *)vtx; |
882 | tx->de_state = de_state; | |
402eb645 VJ |
883 | return 0; |
884 | } | |
885 | ||
2149807b JL |
886 | /** |
887 | * \brief This function returns the oldest open transaction; if none | |
888 | * are open, then the oldest transaction is returned | |
889 | * \param ftp_state the ftp state structure for the parser | |
890 | * | |
891 | * \retval transaction pointer when a transaction was found; NULL otherwise. | |
892 | */ | |
8ae69115 | 893 | static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state) |
2149807b JL |
894 | { |
895 | if (unlikely(!ftp_state)) { | |
896 | SCLogDebug("NULL state object; no transactions available"); | |
897 | return NULL; | |
898 | } | |
899 | FTPTransaction *tx = NULL; | |
900 | FTPTransaction *lasttx = NULL; | |
901 | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { | |
902 | /* Return oldest open tx */ | |
903 | if (!tx->done) { | |
904 | SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id); | |
905 | return tx; | |
906 | } | |
907 | /* save for the end */ | |
908 | lasttx = tx; | |
909 | } | |
910 | /* All tx are closed; return last element */ | |
911 | if (lasttx) | |
912 | SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id); | |
913 | return lasttx; | |
914 | } | |
915 | ||
1930b1f5 JL |
916 | static void *FTPGetTx(void *state, uint64_t tx_id) |
917 | { | |
918 | FtpState *ftp_state = (FtpState *)state; | |
919 | if (ftp_state) { | |
920 | FTPTransaction *tx = NULL; | |
921 | ||
922 | if (ftp_state->curr_tx == NULL) | |
923 | return NULL; | |
924 | if (ftp_state->curr_tx->tx_id == tx_id) | |
925 | return ftp_state->curr_tx; | |
926 | ||
927 | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { | |
928 | if (tx->tx_id == tx_id) | |
929 | return tx; | |
930 | } | |
931 | } | |
932 | return NULL; | |
1930b1f5 JL |
933 | } |
934 | ||
402eb645 VJ |
935 | static DetectEngineState *FTPGetTxDetectState(void *vtx) |
936 | { | |
1930b1f5 JL |
937 | FTPTransaction *tx = (FTPTransaction *)vtx; |
938 | return tx->de_state; | |
402eb645 VJ |
939 | } |
940 | ||
1930b1f5 JL |
941 | |
942 | static uint64_t FTPGetTxDetectFlags(void *vtx, uint8_t dir) | |
402eb645 | 943 | { |
1930b1f5 JL |
944 | FTPTransaction *tx = (FTPTransaction *)vtx; |
945 | if (dir & STREAM_TOSERVER) { | |
946 | return tx->detect_flags_ts; | |
947 | } else { | |
948 | return tx->detect_flags_tc; | |
949 | } | |
402eb645 VJ |
950 | } |
951 | ||
1930b1f5 | 952 | static void FTPSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags) |
402eb645 | 953 | { |
1930b1f5 JL |
954 | FTPTransaction *tx = (FTPTransaction *)vtx; |
955 | if (dir & STREAM_TOSERVER) { | |
956 | tx->detect_flags_ts = flags; | |
957 | } else { | |
958 | tx->detect_flags_tc = flags; | |
959 | } | |
960 | } | |
961 | ||
962 | static void FTPStateTransactionFree(void *state, uint64_t tx_id) | |
963 | { | |
964 | FtpState *ftp_state = state; | |
965 | FTPTransaction *tx = NULL; | |
966 | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { | |
967 | if (tx_id < tx->tx_id) | |
968 | break; | |
969 | else if (tx_id > tx->tx_id) | |
970 | continue; | |
971 | ||
972 | if (tx == ftp_state->curr_tx) | |
973 | ftp_state->curr_tx = NULL; | |
974 | TAILQ_REMOVE(&ftp_state->tx_list, tx, next); | |
975 | FTPTransactionFree(tx); | |
976 | break; | |
977 | } | |
402eb645 VJ |
978 | } |
979 | ||
980 | static uint64_t FTPGetTxCnt(void *state) | |
981 | { | |
1930b1f5 JL |
982 | uint64_t cnt = 0; |
983 | FtpState *ftp_state = state; | |
984 | if (ftp_state) { | |
985 | cnt = ftp_state->tx_cnt; | |
986 | } | |
987 | SCLogDebug("returning state %p %"PRIu64, state, cnt); | |
988 | return cnt; | |
402eb645 VJ |
989 | } |
990 | ||
991 | static int FTPGetAlstateProgressCompletionStatus(uint8_t direction) | |
992 | { | |
993 | return FTP_STATE_FINISHED; | |
994 | } | |
995 | ||
1930b1f5 | 996 | static int FTPGetAlstateProgress(void *vtx, uint8_t direction) |
402eb645 | 997 | { |
1930b1f5 JL |
998 | SCLogDebug("tx %p", vtx); |
999 | FTPTransaction *tx = vtx; | |
402eb645 | 1000 | |
6f364032 PA |
1001 | if (!tx->done) { |
1002 | if (direction == STREAM_TOSERVER && | |
1003 | tx->command_descriptor->command == FTP_COMMAND_PORT) { | |
1004 | return FTP_STATE_PORT_DONE; | |
1005 | } | |
dc80d520 | 1006 | return FTP_STATE_IN_PROGRESS; |
6f364032 | 1007 | } |
402eb645 | 1008 | |
dc80d520 | 1009 | return FTP_STATE_FINISHED; |
402eb645 VJ |
1010 | } |
1011 | ||
1012 | ||
429c6388 AS |
1013 | static int FTPRegisterPatternsForProtocolDetection(void) |
1014 | { | |
8125f78f MK |
1015 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, |
1016 | "220 (", 5, 0, STREAM_TOCLIENT) < 0) | |
1017 | { | |
1018 | return -1; | |
1019 | } | |
1020 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
1021 | "FEAT", 4, 0, STREAM_TOSERVER) < 0) | |
1022 | { | |
1023 | return -1; | |
1024 | } | |
429c6388 AS |
1025 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, |
1026 | "USER ", 5, 0, STREAM_TOSERVER) < 0) | |
1027 | { | |
1028 | return -1; | |
1029 | } | |
1030 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
1031 | "PASS ", 5, 0, STREAM_TOSERVER) < 0) | |
1032 | { | |
1033 | return -1; | |
1034 | } | |
1035 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
1036 | "PORT ", 5, 0, STREAM_TOSERVER) < 0) | |
1037 | { | |
1038 | return -1; | |
1039 | } | |
1040 | ||
1041 | return 0; | |
1042 | } | |
1043 | ||
b0a69344 EL |
1044 | |
1045 | static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; | |
1046 | ||
1047 | /** | |
1048 | * \brief This function is called to retrieve a ftp request | |
1049 | * \param ftp_state the ftp state structure for the parser | |
1050 | * \param input input line of the command | |
1051 | * \param input_len length of the request | |
1052 | * \param output the resulting output | |
1053 | * | |
1054 | * \retval 1 when the command is parsed, 0 otherwise | |
1055 | */ | |
44d3f264 | 1056 | static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, |
b0a69344 | 1057 | AppLayerParserState *pstate, |
579cc9f0 | 1058 | const uint8_t *input, uint32_t input_len, |
b0a69344 EL |
1059 | void *local_data, int direction) |
1060 | { | |
1061 | uint16_t flags = FileFlowToFlags(f, direction); | |
1062 | int ret = 0; | |
1063 | /* we depend on detection engine for file pruning */ | |
1064 | flags |= FILE_USE_DETECT; | |
1065 | if (ftpdata_state->files == NULL) { | |
1066 | struct FtpTransferCmd *data = (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetDataId()); | |
1067 | if (data == NULL) { | |
44d3f264 | 1068 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
1069 | } |
1070 | ||
1071 | ftpdata_state->files = FileContainerAlloc(); | |
1072 | if (ftpdata_state->files == NULL) { | |
1073 | FlowFreeStorageById(f, AppLayerExpectationGetDataId()); | |
44d3f264 | 1074 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
1075 | } |
1076 | ||
1077 | ftpdata_state->file_name = data->file_name; | |
1078 | ftpdata_state->file_len = data->file_len; | |
1079 | data->file_name = NULL; | |
1080 | data->file_len = 0; | |
1081 | f->parent_id = data->flow_id; | |
1082 | ftpdata_state->command = data->cmd; | |
2515c892 EL |
1083 | switch (data->cmd) { |
1084 | case FTP_COMMAND_STOR: | |
1085 | ftpdata_state->direction = STREAM_TOSERVER; | |
1086 | break; | |
1087 | case FTP_COMMAND_RETR: | |
1088 | ftpdata_state->direction = STREAM_TOCLIENT; | |
1089 | break; | |
1090 | default: | |
1091 | break; | |
1092 | } | |
b0a69344 | 1093 | |
9132e403 VJ |
1094 | /* open with fixed track_id 0 as we can have just one |
1095 | * file per ftp-data flow. */ | |
1096 | if (FileOpenFileWithId(ftpdata_state->files, &sbcfg, | |
1097 | 0ULL, (uint8_t *) ftpdata_state->file_name, | |
b0a69344 | 1098 | ftpdata_state->file_len, |
9132e403 | 1099 | input, input_len, flags) != 0) { |
b0a69344 EL |
1100 | SCLogDebug("Can't open file"); |
1101 | ret = -1; | |
1102 | } | |
1103 | FlowFreeStorageById(f, AppLayerExpectationGetDataId()); | |
1104 | } else { | |
1105 | if (input_len != 0) { | |
1106 | ret = FileAppendData(ftpdata_state->files, input, input_len); | |
1107 | if (ret == -2) { | |
1108 | ret = 0; | |
1109 | SCLogDebug("FileAppendData() - file no longer being extracted"); | |
1110 | goto out; | |
1111 | } else if (ret < 0) { | |
1112 | SCLogDebug("FileAppendData() failed: %d", ret); | |
1113 | ret = -2; | |
1114 | goto out; | |
1115 | } | |
1116 | } else { | |
1117 | ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags); | |
1118 | ftpdata_state->state = FTPDATA_STATE_FINISHED; | |
1119 | if (ret < 0) | |
1120 | goto out; | |
1121 | } | |
1122 | } | |
1123 | ||
1124 | if (input_len && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF)) { | |
1125 | ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags); | |
1126 | ftpdata_state->state = FTPDATA_STATE_FINISHED; | |
1127 | } | |
1128 | ||
1129 | out: | |
44d3f264 VJ |
1130 | if (ret < 0) { |
1131 | SCReturnStruct(APP_LAYER_ERROR); | |
1132 | } | |
1133 | SCReturnStruct(APP_LAYER_OK); | |
b0a69344 EL |
1134 | } |
1135 | ||
1930b1f5 JL |
1136 | static void FTPStateSetTxLogged(void *state, void *vtx, LoggerId logged) |
1137 | { | |
1138 | FTPTransaction *tx = vtx; | |
1139 | tx->logged = logged; | |
1140 | } | |
1141 | ||
1142 | static LoggerId FTPStateGetTxLogged(void *state, void *vtx) | |
1143 | { | |
1144 | FTPTransaction *tx = vtx; | |
1145 | return tx->logged; | |
1146 | } | |
44d3f264 | 1147 | static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, |
b0a69344 | 1148 | AppLayerParserState *pstate, |
579cc9f0 | 1149 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 1150 | void *local_data, const uint8_t flags) |
b0a69344 EL |
1151 | { |
1152 | return FTPDataParse(f, ftp_state, pstate, input, input_len, | |
1153 | local_data, STREAM_TOSERVER); | |
1154 | } | |
1155 | ||
44d3f264 | 1156 | static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, |
b0a69344 | 1157 | AppLayerParserState *pstate, |
579cc9f0 | 1158 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 1159 | void *local_data, const uint8_t flags) |
b0a69344 EL |
1160 | { |
1161 | return FTPDataParse(f, ftp_state, pstate, input, input_len, | |
1162 | local_data, STREAM_TOCLIENT); | |
1163 | } | |
1164 | ||
1165 | #ifdef DEBUG | |
1166 | static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER; | |
1167 | static uint64_t ftpdata_state_memuse = 0; | |
1168 | static uint64_t ftpdata_state_memcnt = 0; | |
1169 | #endif | |
1170 | ||
1171 | static void *FTPDataStateAlloc(void) | |
1172 | { | |
1930b1f5 | 1173 | void *s = FTPCalloc(1, sizeof(FtpDataState)); |
b0a69344 EL |
1174 | if (unlikely(s == NULL)) |
1175 | return NULL; | |
1176 | ||
1930b1f5 JL |
1177 | FtpDataState *state = (FtpDataState *) s; |
1178 | state->state = FTPDATA_STATE_IN_PROGRESS; | |
b0a69344 EL |
1179 | |
1180 | #ifdef DEBUG | |
1181 | SCMutexLock(&ftpdata_state_mem_lock); | |
1182 | ftpdata_state_memcnt++; | |
1183 | ftpdata_state_memuse+=sizeof(FtpDataState); | |
1184 | SCMutexUnlock(&ftpdata_state_mem_lock); | |
1185 | #endif | |
1186 | return s; | |
1187 | } | |
1188 | ||
1189 | static void FTPDataStateFree(void *s) | |
1190 | { | |
1191 | FtpDataState *fstate = (FtpDataState *) s; | |
1192 | ||
1193 | if (fstate->de_state != NULL) { | |
1194 | DetectEngineStateFree(fstate->de_state); | |
1195 | } | |
1196 | if (fstate->file_name != NULL) { | |
aa3f784d | 1197 | FTPFree(fstate->file_name, fstate->file_len + 1); |
b0a69344 EL |
1198 | } |
1199 | ||
1200 | FileContainerFree(fstate->files); | |
1201 | ||
aa3f784d | 1202 | FTPFree(s, sizeof(FtpDataState)); |
b0a69344 EL |
1203 | #ifdef DEBUG |
1204 | SCMutexLock(&ftpdata_state_mem_lock); | |
1205 | ftpdata_state_memcnt--; | |
1206 | ftpdata_state_memuse-=sizeof(FtpDataState); | |
1207 | SCMutexUnlock(&ftpdata_state_mem_lock); | |
1208 | #endif | |
1209 | } | |
1210 | ||
7548944b | 1211 | static int FTPDataSetTxDetectState(void *vtx, DetectEngineState *de_state) |
b0a69344 | 1212 | { |
7548944b | 1213 | FtpDataState *ftp_state = (FtpDataState *)vtx; |
b0a69344 EL |
1214 | ftp_state->de_state = de_state; |
1215 | return 0; | |
1216 | } | |
1217 | ||
1218 | static DetectEngineState *FTPDataGetTxDetectState(void *vtx) | |
1219 | { | |
1220 | FtpDataState *ftp_state = (FtpDataState *)vtx; | |
1221 | return ftp_state->de_state; | |
1222 | } | |
1223 | ||
b1beb76f JI |
1224 | static void FTPDataSetTxDetectFlags(void *vtx, uint8_t dir, uint64_t flags) |
1225 | { | |
1226 | FtpDataState *ftp_state = (FtpDataState *)vtx; | |
1227 | if (dir & STREAM_TOSERVER) { | |
1228 | ftp_state->detect_flags_ts = flags; | |
1229 | } else { | |
1230 | ftp_state->detect_flags_tc = flags; | |
1231 | } | |
1232 | } | |
1233 | ||
1234 | static uint64_t FTPDataGetTxDetectFlags(void *vtx, uint8_t dir) | |
1235 | { | |
1236 | FtpDataState *ftp_state = (FtpDataState *)vtx; | |
1237 | if (dir & STREAM_TOSERVER) { | |
1238 | return ftp_state->detect_flags_ts; | |
1239 | } else { | |
1240 | return ftp_state->detect_flags_tc; | |
1241 | } | |
1242 | } | |
1243 | ||
b0a69344 EL |
1244 | static void FTPDataStateTransactionFree(void *state, uint64_t tx_id) |
1245 | { | |
1246 | /* do nothing */ | |
1247 | } | |
1248 | ||
1249 | static void *FTPDataGetTx(void *state, uint64_t tx_id) | |
1250 | { | |
1251 | FtpDataState *ftp_state = (FtpDataState *)state; | |
1252 | return ftp_state; | |
1253 | } | |
1254 | ||
1255 | static uint64_t FTPDataGetTxCnt(void *state) | |
1256 | { | |
1257 | /* ftp-data is single tx */ | |
1258 | return 1; | |
1259 | } | |
1260 | ||
1261 | static int FTPDataGetAlstateProgressCompletionStatus(uint8_t direction) | |
1262 | { | |
1263 | return FTPDATA_STATE_FINISHED; | |
1264 | } | |
1265 | ||
1266 | static int FTPDataGetAlstateProgress(void *tx, uint8_t direction) | |
1267 | { | |
1268 | FtpDataState *ftpdata_state = (FtpDataState *)tx; | |
1269 | return ftpdata_state->state; | |
1270 | } | |
1271 | ||
1272 | static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction) | |
1273 | { | |
1274 | FtpDataState *ftpdata_state = (FtpDataState *)state; | |
1275 | ||
2515c892 EL |
1276 | if (direction != ftpdata_state->direction) |
1277 | SCReturnPtr(NULL, "FileContainer"); | |
1278 | ||
b0a69344 EL |
1279 | SCReturnPtr(ftpdata_state->files, "FileContainer"); |
1280 | } | |
1281 | ||
09ab032a JL |
1282 | static void FTPSetMpmState(void) |
1283 | { | |
1284 | ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx)); | |
1285 | if (unlikely(ftp_mpm_ctx == NULL)) { | |
1286 | exit(EXIT_FAILURE); | |
1287 | } | |
1288 | memset(ftp_mpm_ctx, 0, sizeof(MpmCtx)); | |
1289 | MpmInitCtx(ftp_mpm_ctx, FTP_MPM); | |
1290 | ||
1291 | uint32_t i = 0; | |
1292 | for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { | |
1293 | const FtpCommand *cmd = &FtpCommands[i]; | |
86deaefe JL |
1294 | if (cmd->command_length == 0) |
1295 | continue; | |
1296 | ||
09ab032a JL |
1297 | MpmAddPatternCI(ftp_mpm_ctx, |
1298 | (uint8_t *)cmd->command_name, | |
1299 | cmd->command_length, | |
1300 | 0 /* defunct */, 0 /* defunct */, | |
1301 | i /* id */, i /* rule id */ , 0 /* no flags */); | |
1302 | } | |
1303 | ||
1304 | mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); | |
1305 | ||
1306 | } | |
1307 | ||
1308 | static void FTPFreeMpmState(void) | |
1309 | { | |
1310 | if (ftp_mpm_ctx != NULL) { | |
1311 | mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx); | |
1312 | SCFree(ftp_mpm_ctx); | |
1313 | ftp_mpm_ctx = NULL; | |
1314 | } | |
1315 | } | |
1316 | ||
8f1d7503 KS |
1317 | void RegisterFTPParsers(void) |
1318 | { | |
ab1200fb | 1319 | const char *proto_name = "ftp"; |
b0a69344 | 1320 | const char *proto_data_name = "ftp-data"; |
10966245 | 1321 | |
000ce98c | 1322 | /** FTP */ |
429c6388 AS |
1323 | if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { |
1324 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name); | |
1325 | if (FTPRegisterPatternsForProtocolDetection() < 0 ) | |
1326 | return; | |
b0a69344 | 1327 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name); |
ddde572f AS |
1328 | } |
1329 | ||
429c6388 AS |
1330 | if (AppLayerParserConfParserEnabled("tcp", proto_name)) { |
1331 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER, | |
1332 | FTPParseRequest); | |
1333 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT, | |
1334 | FTPParseResponse); | |
1335 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree); | |
1336 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT); | |
402eb645 VJ |
1337 | |
1338 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree); | |
1339 | ||
7548944b VJ |
1340 | AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTP, |
1341 | FTPGetTxDetectState, FTPSetTxDetectState); | |
402eb645 | 1342 | |
1930b1f5 JL |
1343 | AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_FTP, |
1344 | FTPGetTxDetectFlags, FTPSetTxDetectFlags); | |
1345 | ||
402eb645 | 1346 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx); |
1930b1f5 JL |
1347 | AppLayerParserRegisterLoggerFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateGetTxLogged, |
1348 | FTPStateSetTxLogged); | |
402eb645 | 1349 | |
09ab032a JL |
1350 | AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc, |
1351 | FTPLocalStorageFree); | |
402eb645 VJ |
1352 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt); |
1353 | ||
1354 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress); | |
1355 | ||
1356 | AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTP, | |
1357 | FTPGetAlstateProgressCompletionStatus); | |
b0a69344 EL |
1358 | |
1359 | ||
1360 | AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA); | |
1361 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER, | |
1362 | FTPDataParseRequest); | |
1363 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT, | |
1364 | FTPDataParseResponse); | |
1365 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree); | |
1366 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT); | |
1367 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree); | |
7548944b | 1368 | AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, |
b0a69344 | 1369 | FTPDataGetTxDetectState, FTPDataSetTxDetectState); |
b1beb76f JI |
1370 | AppLayerParserRegisterDetectFlagsFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, |
1371 | FTPDataGetTxDetectFlags, FTPDataSetTxDetectFlags); | |
b0a69344 EL |
1372 | |
1373 | AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles); | |
1374 | ||
1375 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx); | |
1376 | ||
1377 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt); | |
1378 | ||
1379 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress); | |
1380 | ||
1381 | AppLayerParserRegisterGetStateProgressCompletionStatus(ALPROTO_FTPDATA, | |
1382 | FTPDataGetAlstateProgressCompletionStatus); | |
1383 | ||
1384 | sbcfg.buf_size = 4096; | |
711b6fb3 EL |
1385 | sbcfg.Malloc = FTPMalloc; |
1386 | sbcfg.Calloc = FTPCalloc; | |
1387 | sbcfg.Realloc = FTPRealloc; | |
1388 | sbcfg.Free = FTPFree; | |
b0a69344 | 1389 | |
711b6fb3 | 1390 | FTPParseMemcap(); |
ddde572f AS |
1391 | } else { |
1392 | SCLogInfo("Parsed disabled for %s protocol. Protocol detection" | |
1393 | "still on.", proto_name); | |
1394 | } | |
09ab032a JL |
1395 | |
1396 | FTPSetMpmState(); | |
1397 | ||
9faa4b74 | 1398 | #ifdef UNITTESTS |
429c6388 | 1399 | AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests); |
9faa4b74 | 1400 | #endif |
f2f9b832 PR |
1401 | } |
1402 | ||
8f1d7503 KS |
1403 | void FTPAtExitPrintStats(void) |
1404 | { | |
f2f9b832 PR |
1405 | #ifdef DEBUG |
1406 | SCMutexLock(&ftp_state_mem_lock); | |
1407 | SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"", | |
1408 | ftp_state_memcnt, ftp_state_memuse); | |
1409 | SCMutexUnlock(&ftp_state_mem_lock); | |
1410 | #endif | |
1411 | } | |
1412 | ||
b0a69344 | 1413 | |
3df2b343 JL |
1414 | /* |
1415 | * \brief Returns the ending offset of the next line from a multi-line buffer. | |
1416 | * | |
1417 | * "Buffer" refers to a FTP response in a single buffer containing multiple lines. | |
1418 | * Here, "next line" is defined as terminating on | |
1419 | * - Newline character | |
1420 | * - Null character | |
1421 | * | |
1422 | * \param buffer Contains zero or more characters. | |
1423 | * \param len Size, in bytes, of buffer. | |
1424 | * | |
1425 | * \retval Offset from the start of buffer indicating the where the | |
1426 | * next "line ends". The characters between the input buffer and this | |
1427 | * value comprise the line. | |
1428 | * | |
86fabef0 | 1429 | * NULL is found first or a newline isn't found, then UINT16_MAX is returned. |
3df2b343 JL |
1430 | */ |
1431 | uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len) | |
1432 | { | |
86fabef0 JL |
1433 | if (!buffer || *buffer == '\0') { |
1434 | return UINT16_MAX; | |
1435 | } | |
3df2b343 | 1436 | |
86fabef0 JL |
1437 | char *c = strchr(buffer, '\n'); |
1438 | return c == NULL ? len : c - buffer + 1; | |
3df2b343 JL |
1439 | } |
1440 | ||
648bd5af | 1441 | void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb) |
b0a69344 EL |
1442 | { |
1443 | const FtpDataState *ftp_state = NULL; | |
1444 | if (f->alstate == NULL) | |
03de315b JL |
1445 | return; |
1446 | ||
b0a69344 | 1447 | ftp_state = (FtpDataState *)f->alstate; |
03de315b | 1448 | |
b0a69344 | 1449 | if (ftp_state->file_name) { |
03de315b | 1450 | jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len); |
b0a69344 EL |
1451 | } |
1452 | switch (ftp_state->command) { | |
1453 | case FTP_COMMAND_STOR: | |
03de315b | 1454 | JB_SET_STRING(jb, "command", "STOR"); |
b0a69344 EL |
1455 | break; |
1456 | case FTP_COMMAND_RETR: | |
03de315b | 1457 | JB_SET_STRING(jb, "command", "RETR"); |
b0a69344 EL |
1458 | break; |
1459 | default: | |
1460 | break; | |
1461 | } | |
b0a69344 | 1462 | } |
b0a69344 | 1463 | |
09ab032a | 1464 | /** |
03de315b | 1465 | * \brief Free memory allocated for global FTP parser state. |
09ab032a JL |
1466 | */ |
1467 | void FTPParserCleanup(void) | |
1468 | { | |
1469 | FTPFreeMpmState(); | |
1470 | } | |
1471 | ||
f2f9b832 PR |
1472 | /* UNITTESTS */ |
1473 | #ifdef UNITTESTS | |
1474 | ||
1475 | /** \test Send a get request in one chunk. */ | |
ab1200fb | 1476 | static int FTPParserTest01(void) |
8f1d7503 | 1477 | { |
f2f9b832 PR |
1478 | int result = 1; |
1479 | Flow f; | |
1480 | uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n"; | |
1481 | uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */ | |
1482 | TcpSession ssn; | |
8dbf7a0d | 1483 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1484 | |
1485 | memset(&f, 0, sizeof(f)); | |
1486 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b WM |
1487 | |
1488 | FLOW_INITIALIZE(&f); | |
f2f9b832 | 1489 | f.protoctx = (void *)&ssn; |
429c6388 | 1490 | f.proto = IPPROTO_TCP; |
5c01b409 | 1491 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1492 | |
6a53ab9c | 1493 | StreamTcpInitConfig(TRUE); |
6a53ab9c | 1494 | |
6530c3d0 | 1495 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1496 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1497 | STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen); | |
f2f9b832 | 1498 | if (r != 0) { |
f729d6f7 | 1499 | SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1500 | result = 0; |
6530c3d0 | 1501 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1502 | goto end; |
1503 | } | |
6530c3d0 | 1504 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1505 | |
06904c90 | 1506 | FtpState *ftp_state = f.alstate; |
f2f9b832 | 1507 | if (ftp_state == NULL) { |
f729d6f7 | 1508 | SCLogDebug("no ftp state: "); |
f2f9b832 PR |
1509 | result = 0; |
1510 | goto end; | |
1511 | } | |
1512 | ||
1513 | if (ftp_state->command != FTP_COMMAND_PORT) { | |
f729d6f7 | 1514 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); |
f2f9b832 PR |
1515 | result = 0; |
1516 | goto end; | |
1517 | } | |
1518 | ||
1519 | end: | |
429c6388 | 1520 | if (alp_tctx != NULL) |
fdefb65b | 1521 | AppLayerParserThreadCtxFree(alp_tctx); |
6a53ab9c | 1522 | StreamTcpFreeConfig(TRUE); |
0e4235cc | 1523 | FLOW_DESTROY(&f); |
f2f9b832 PR |
1524 | return result; |
1525 | } | |
1526 | ||
1930b1f5 | 1527 | /** \test Send a split get request. */ |
ab1200fb | 1528 | static int FTPParserTest03(void) |
8f1d7503 | 1529 | { |
f2f9b832 PR |
1530 | int result = 1; |
1531 | Flow f; | |
1532 | uint8_t ftpbuf1[] = "POR"; | |
1533 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1534 | uint8_t ftpbuf2[] = "T 192,168,1"; | |
1535 | uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ | |
1536 | uint8_t ftpbuf3[] = "1,1,10,20\r\n"; | |
1537 | uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */ | |
1538 | TcpSession ssn; | |
8dbf7a0d | 1539 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1540 | |
1541 | memset(&f, 0, sizeof(f)); | |
1542 | memset(&ssn, 0, sizeof(ssn)); | |
48b3cb04 VJ |
1543 | |
1544 | FLOW_INITIALIZE(&f); | |
f2f9b832 | 1545 | f.protoctx = (void *)&ssn; |
429c6388 | 1546 | f.proto = IPPROTO_TCP; |
5c01b409 | 1547 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1548 | |
6a53ab9c | 1549 | StreamTcpInitConfig(TRUE); |
6a53ab9c | 1550 | |
6530c3d0 | 1551 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1552 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1553 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1554 | ftplen1); | |
f2f9b832 | 1555 | if (r != 0) { |
f729d6f7 | 1556 | SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1557 | result = 0; |
6530c3d0 | 1558 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1559 | goto end; |
1560 | } | |
6530c3d0 | 1561 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1562 | |
6530c3d0 | 1563 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1564 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, |
1565 | ftpbuf2, ftplen2); | |
f2f9b832 | 1566 | if (r != 0) { |
f729d6f7 | 1567 | SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1568 | result = 0; |
6530c3d0 | 1569 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1570 | goto end; |
1571 | } | |
6530c3d0 | 1572 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1573 | |
6530c3d0 | 1574 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1575 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1576 | STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3); | |
f2f9b832 | 1577 | if (r != 0) { |
f729d6f7 | 1578 | SCLogDebug("toserver chunk 3 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1579 | result = 0; |
6530c3d0 | 1580 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1581 | goto end; |
1582 | } | |
6530c3d0 | 1583 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1584 | |
06904c90 | 1585 | FtpState *ftp_state = f.alstate; |
f2f9b832 | 1586 | if (ftp_state == NULL) { |
f729d6f7 | 1587 | SCLogDebug("no ftp state: "); |
f2f9b832 PR |
1588 | result = 0; |
1589 | goto end; | |
1590 | } | |
1591 | ||
1592 | if (ftp_state->command != FTP_COMMAND_PORT) { | |
f729d6f7 | 1593 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); |
f2f9b832 PR |
1594 | result = 0; |
1595 | goto end; | |
1596 | } | |
1597 | ||
1598 | end: | |
429c6388 | 1599 | if (alp_tctx != NULL) |
fdefb65b | 1600 | AppLayerParserThreadCtxFree(alp_tctx); |
6a53ab9c | 1601 | StreamTcpFreeConfig(TRUE); |
f2f9b832 PR |
1602 | return result; |
1603 | } | |
1604 | ||
1605 | /** \test See how it deals with an incomplete request. */ | |
ab1200fb | 1606 | static int FTPParserTest06(void) |
8f1d7503 | 1607 | { |
f2f9b832 PR |
1608 | int result = 1; |
1609 | Flow f; | |
1610 | uint8_t ftpbuf1[] = "PORT"; | |
1611 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1612 | TcpSession ssn; | |
8dbf7a0d | 1613 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1614 | |
1615 | memset(&f, 0, sizeof(f)); | |
1616 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b WM |
1617 | |
1618 | FLOW_INITIALIZE(&f); | |
f2f9b832 | 1619 | f.protoctx = (void *)&ssn; |
429c6388 | 1620 | f.proto = IPPROTO_TCP; |
5c01b409 | 1621 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1622 | |
6a53ab9c | 1623 | StreamTcpInitConfig(TRUE); |
6a53ab9c | 1624 | |
6530c3d0 | 1625 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1626 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1627 | STREAM_TOSERVER | STREAM_START | STREAM_EOF, | |
1628 | ftpbuf1, | |
1629 | ftplen1); | |
f2f9b832 | 1630 | if (r != 0) { |
f729d6f7 | 1631 | SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1632 | result = 0; |
6530c3d0 | 1633 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1634 | goto end; |
1635 | } | |
6530c3d0 | 1636 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1637 | |
06904c90 | 1638 | FtpState *ftp_state = f.alstate; |
f2f9b832 | 1639 | if (ftp_state == NULL) { |
f729d6f7 | 1640 | SCLogDebug("no ftp state: "); |
f2f9b832 PR |
1641 | result = 0; |
1642 | goto end; | |
1643 | } | |
1644 | ||
1645 | if (ftp_state->command != FTP_COMMAND_UNKNOWN) { | |
f729d6f7 | 1646 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_UNKNOWN, ftp_state->command); |
f2f9b832 PR |
1647 | result = 0; |
1648 | goto end; | |
1649 | } | |
1650 | ||
1651 | end: | |
429c6388 | 1652 | if (alp_tctx != NULL) |
fdefb65b | 1653 | AppLayerParserThreadCtxFree(alp_tctx); |
6a53ab9c | 1654 | StreamTcpFreeConfig(TRUE); |
0e4235cc | 1655 | FLOW_DESTROY(&f); |
f2f9b832 PR |
1656 | return result; |
1657 | } | |
1658 | ||
1659 | /** \test See how it deals with an incomplete request in multiple chunks. */ | |
ab1200fb | 1660 | static int FTPParserTest07(void) |
8f1d7503 | 1661 | { |
f2f9b832 PR |
1662 | int result = 1; |
1663 | Flow f; | |
1664 | uint8_t ftpbuf1[] = "PO"; | |
1665 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1666 | uint8_t ftpbuf2[] = "RT\r\n"; | |
1667 | uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ | |
1668 | TcpSession ssn; | |
8dbf7a0d | 1669 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1670 | |
1671 | memset(&f, 0, sizeof(f)); | |
1672 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b WM |
1673 | |
1674 | FLOW_INITIALIZE(&f); | |
f2f9b832 | 1675 | f.protoctx = (void *)&ssn; |
429c6388 | 1676 | f.proto = IPPROTO_TCP; |
5c01b409 | 1677 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1678 | |
6a53ab9c | 1679 | StreamTcpInitConfig(TRUE); |
6a53ab9c | 1680 | |
6530c3d0 | 1681 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1682 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1683 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1684 | ftplen1); | |
f2f9b832 | 1685 | if (r != 0) { |
f729d6f7 | 1686 | SCLogDebug("toserver chunk 1 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1687 | result = 0; |
6530c3d0 | 1688 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1689 | goto end; |
1690 | } | |
6530c3d0 | 1691 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1692 | |
6530c3d0 | 1693 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1694 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1695 | STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2); | |
f2f9b832 | 1696 | if (r != 0) { |
f729d6f7 | 1697 | SCLogDebug("toserver chunk 2 returned %" PRId32 ", expected 0: ", r); |
f2f9b832 | 1698 | result = 0; |
6530c3d0 | 1699 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1700 | goto end; |
1701 | } | |
6530c3d0 | 1702 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 | 1703 | |
06904c90 | 1704 | FtpState *ftp_state = f.alstate; |
f2f9b832 | 1705 | if (ftp_state == NULL) { |
f729d6f7 | 1706 | SCLogDebug("no ftp state: "); |
f2f9b832 PR |
1707 | result = 0; |
1708 | goto end; | |
1709 | } | |
1710 | ||
6ea8ac44 AS |
1711 | if (ftp_state->command != FTP_COMMAND_PORT) { |
1712 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", | |
1713 | FTP_COMMAND_PORT, ftp_state->command); | |
f2f9b832 PR |
1714 | result = 0; |
1715 | goto end; | |
1716 | } | |
1717 | ||
1718 | end: | |
429c6388 | 1719 | if (alp_tctx != NULL) |
fdefb65b | 1720 | AppLayerParserThreadCtxFree(alp_tctx); |
6a53ab9c | 1721 | StreamTcpFreeConfig(TRUE); |
0e4235cc | 1722 | FLOW_DESTROY(&f); |
f2f9b832 PR |
1723 | return result; |
1724 | } | |
1725 | ||
1726 | /** \test Test case where chunks are smaller than the delim length and the | |
1727 | * last chunk is supposed to match the delim. */ | |
ab1200fb | 1728 | static int FTPParserTest10(void) |
8f1d7503 | 1729 | { |
f2f9b832 PR |
1730 | int result = 1; |
1731 | Flow f; | |
1732 | uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n"; | |
1733 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1734 | TcpSession ssn; | |
8dbf7a0d | 1735 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1736 | int r = 0; |
1737 | memset(&f, 0, sizeof(f)); | |
1738 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b WM |
1739 | |
1740 | FLOW_INITIALIZE(&f); | |
f2f9b832 | 1741 | f.protoctx = (void *)&ssn; |
429c6388 | 1742 | f.proto = IPPROTO_TCP; |
5c01b409 | 1743 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1744 | |
6a53ab9c | 1745 | StreamTcpInitConfig(TRUE); |
6a53ab9c | 1746 | |
f2f9b832 PR |
1747 | uint32_t u; |
1748 | for (u = 0; u < ftplen1; u++) { | |
1749 | uint8_t flags = 0; | |
1750 | ||
1751 | if (u == 0) flags = STREAM_TOSERVER|STREAM_START; | |
1752 | else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF; | |
1753 | else flags = STREAM_TOSERVER; | |
1754 | ||
6530c3d0 | 1755 | FLOWLOCK_WRLOCK(&f); |
675fa564 GL |
1756 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags, |
1757 | &ftpbuf1[u], 1); | |
f2f9b832 | 1758 | if (r != 0) { |
f729d6f7 | 1759 | SCLogDebug("toserver chunk %" PRIu32 " returned %" PRId32 ", expected 0: ", u, r); |
f2f9b832 | 1760 | result = 0; |
6530c3d0 | 1761 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1762 | goto end; |
1763 | } | |
6530c3d0 | 1764 | FLOWLOCK_UNLOCK(&f); |
f2f9b832 PR |
1765 | } |
1766 | ||
06904c90 | 1767 | FtpState *ftp_state = f.alstate; |
f2f9b832 | 1768 | if (ftp_state == NULL) { |
f729d6f7 | 1769 | SCLogDebug("no ftp state: "); |
f2f9b832 PR |
1770 | result = 0; |
1771 | goto end; | |
1772 | } | |
1773 | ||
1774 | if (ftp_state->command != FTP_COMMAND_PORT) { | |
f729d6f7 | 1775 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", FTP_COMMAND_PORT, ftp_state->command); |
f2f9b832 PR |
1776 | result = 0; |
1777 | goto end; | |
1778 | } | |
1779 | ||
354074ba JL |
1780 | end: |
1781 | if (alp_tctx != NULL) | |
1782 | AppLayerParserThreadCtxFree(alp_tctx); | |
1783 | StreamTcpFreeConfig(TRUE); | |
1784 | FLOW_DESTROY(&f); | |
1785 | return result; | |
1786 | } | |
1787 | ||
1788 | /** \test Supply RETR without a filename */ | |
1789 | static int FTPParserTest11(void) | |
1790 | { | |
1791 | int result = 1; | |
1792 | Flow f; | |
1793 | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; | |
1794 | uint8_t ftpbuf2[] = "RETR\r\n"; | |
1795 | uint8_t ftpbuf3[] = "227 OK\r\n"; | |
1796 | TcpSession ssn; | |
1797 | ||
1798 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); | |
1799 | ||
1800 | memset(&f, 0, sizeof(f)); | |
1801 | memset(&ssn, 0, sizeof(ssn)); | |
1802 | ||
1803 | FLOW_INITIALIZE(&f); | |
1804 | f.protoctx = (void *)&ssn; | |
1805 | f.proto = IPPROTO_TCP; | |
1806 | f.alproto = ALPROTO_FTP; | |
1807 | ||
1808 | StreamTcpInitConfig(TRUE); | |
1809 | ||
1810 | FLOWLOCK_WRLOCK(&f); | |
1811 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1812 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1813 | sizeof(ftpbuf1) - 1); | |
1814 | if (r != 0) { | |
1815 | result = 0; | |
1816 | FLOWLOCK_UNLOCK(&f); | |
1817 | goto end; | |
1818 | } | |
1819 | FLOWLOCK_UNLOCK(&f); | |
1820 | ||
1821 | /* Response */ | |
1822 | FLOWLOCK_WRLOCK(&f); | |
1823 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1824 | STREAM_TOCLIENT, | |
1825 | ftpbuf3, | |
1826 | sizeof(ftpbuf3) - 1); | |
1827 | if (r != 0) { | |
1828 | result = 0; | |
1829 | FLOWLOCK_UNLOCK(&f); | |
1830 | goto end; | |
1831 | } | |
1832 | FLOWLOCK_UNLOCK(&f); | |
1833 | ||
1834 | FLOWLOCK_WRLOCK(&f); | |
1835 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1836 | STREAM_TOSERVER, ftpbuf2, | |
1837 | sizeof(ftpbuf2) - 1); | |
1838 | if (r == 0) { | |
1839 | SCLogDebug("parse should've failed"); | |
1840 | result = 0; | |
1841 | FLOWLOCK_UNLOCK(&f); | |
1842 | goto end; | |
1843 | } | |
1844 | FLOWLOCK_UNLOCK(&f); | |
1845 | ||
1846 | FtpState *ftp_state = f.alstate; | |
1847 | if (ftp_state == NULL) { | |
1848 | SCLogDebug("no ftp state: "); | |
1849 | result = 0; | |
1850 | goto end; | |
1851 | } | |
1852 | ||
1853 | if (ftp_state->command != FTP_COMMAND_RETR) { | |
1854 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", | |
1855 | FTP_COMMAND_RETR, ftp_state->command); | |
1856 | result = 0; | |
1857 | goto end; | |
1858 | } | |
1859 | ||
1860 | end: | |
1861 | if (alp_tctx != NULL) | |
1862 | AppLayerParserThreadCtxFree(alp_tctx); | |
1863 | StreamTcpFreeConfig(TRUE); | |
1864 | FLOW_DESTROY(&f); | |
1865 | return result; | |
1866 | } | |
1867 | ||
1868 | /** \test Supply STOR without a filename */ | |
1869 | static int FTPParserTest12(void) | |
1870 | { | |
1871 | int result = 1; | |
1872 | Flow f; | |
1873 | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; | |
1874 | uint8_t ftpbuf2[] = "STOR\r\n"; | |
1875 | uint8_t ftpbuf3[] = "227 OK\r\n"; | |
1876 | TcpSession ssn; | |
1877 | ||
1878 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); | |
1879 | ||
1880 | memset(&f, 0, sizeof(f)); | |
1881 | memset(&ssn, 0, sizeof(ssn)); | |
1882 | ||
1883 | FLOW_INITIALIZE(&f); | |
1884 | f.protoctx = (void *)&ssn; | |
1885 | f.proto = IPPROTO_TCP; | |
1886 | f.alproto = ALPROTO_FTP; | |
1887 | ||
1888 | StreamTcpInitConfig(TRUE); | |
1889 | ||
1890 | FLOWLOCK_WRLOCK(&f); | |
1891 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1892 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1893 | sizeof(ftpbuf1) - 1); | |
1894 | if (r != 0) { | |
1895 | result = 0; | |
1896 | FLOWLOCK_UNLOCK(&f); | |
1897 | goto end; | |
1898 | } | |
1899 | FLOWLOCK_UNLOCK(&f); | |
1900 | ||
1901 | /* Response */ | |
1902 | FLOWLOCK_WRLOCK(&f); | |
1903 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1904 | STREAM_TOCLIENT, | |
1905 | ftpbuf3, | |
1906 | sizeof(ftpbuf3) - 1); | |
1907 | if (r != 0) { | |
1908 | result = 0; | |
1909 | FLOWLOCK_UNLOCK(&f); | |
1910 | goto end; | |
1911 | } | |
1912 | FLOWLOCK_UNLOCK(&f); | |
1913 | ||
1914 | FLOWLOCK_WRLOCK(&f); | |
1915 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, | |
1916 | STREAM_TOSERVER, ftpbuf2, | |
1917 | sizeof(ftpbuf2) - 1); | |
1918 | if (r == 0) { | |
1919 | SCLogDebug("parse should've failed"); | |
1920 | result = 0; | |
1921 | FLOWLOCK_UNLOCK(&f); | |
1922 | goto end; | |
1923 | } | |
1924 | FLOWLOCK_UNLOCK(&f); | |
1925 | ||
1926 | FtpState *ftp_state = f.alstate; | |
1927 | if (ftp_state == NULL) { | |
1928 | SCLogDebug("no ftp state: "); | |
1929 | result = 0; | |
1930 | goto end; | |
1931 | } | |
1932 | ||
1933 | if (ftp_state->command != FTP_COMMAND_STOR) { | |
1934 | SCLogDebug("expected command %" PRIu32 ", got %" PRIu32 ": ", | |
1935 | FTP_COMMAND_STOR, ftp_state->command); | |
1936 | result = 0; | |
1937 | goto end; | |
1938 | } | |
1939 | ||
f2f9b832 | 1940 | end: |
429c6388 | 1941 | if (alp_tctx != NULL) |
fdefb65b | 1942 | AppLayerParserThreadCtxFree(alp_tctx); |
6a53ab9c | 1943 | StreamTcpFreeConfig(TRUE); |
0e4235cc | 1944 | FLOW_DESTROY(&f); |
f2f9b832 PR |
1945 | return result; |
1946 | } | |
1947 | #endif /* UNITTESTS */ | |
1948 | ||
8f1d7503 KS |
1949 | void FTPParserRegisterTests(void) |
1950 | { | |
f2f9b832 | 1951 | #ifdef UNITTESTS |
796dd522 JI |
1952 | UtRegisterTest("FTPParserTest01", FTPParserTest01); |
1953 | UtRegisterTest("FTPParserTest03", FTPParserTest03); | |
1954 | UtRegisterTest("FTPParserTest06", FTPParserTest06); | |
1955 | UtRegisterTest("FTPParserTest07", FTPParserTest07); | |
1956 | UtRegisterTest("FTPParserTest10", FTPParserTest10); | |
354074ba JL |
1957 | UtRegisterTest("FTPParserTest11", FTPParserTest11); |
1958 | UtRegisterTest("FTPParserTest12", FTPParserTest12); | |
f2f9b832 PR |
1959 | #endif /* UNITTESTS */ |
1960 | } | |
1961 |