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