]>
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 | ||
057c4b34 | 132 | static FTPTransaction *FTPGetOldestTx(FtpState *, FTPTransaction *); |
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 | { | |
711b6fb3 EL |
214 | if (FTPCheckMemcap((uint32_t)(n * size)) == 0) |
215 | return NULL; | |
216 | ||
7c364017 | 217 | void *ptr = SCCalloc(n, size); |
711b6fb3 EL |
218 | |
219 | if (unlikely(ptr == NULL)) | |
220 | return NULL; | |
221 | ||
222 | FTPIncrMemuse((uint64_t)(n * size)); | |
711b6fb3 EL |
223 | return ptr; |
224 | } | |
225 | ||
226 | static void *FTPRealloc(void *ptr, size_t orig_size, size_t size) | |
227 | { | |
228 | void *rptr = NULL; | |
229 | ||
230 | if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) | |
231 | return NULL; | |
232 | ||
233 | rptr = SCRealloc(ptr, size); | |
234 | if (rptr == NULL) | |
235 | return NULL; | |
236 | ||
237 | if (size > orig_size) { | |
238 | FTPIncrMemuse(size - orig_size); | |
239 | } else { | |
240 | FTPDecrMemuse(orig_size - size); | |
241 | } | |
242 | ||
243 | return rptr; | |
244 | } | |
245 | ||
246 | static void FTPFree(void *ptr, size_t size) | |
247 | { | |
248 | SCFree(ptr); | |
249 | ||
250 | FTPDecrMemuse((uint64_t)size); | |
251 | } | |
252 | ||
1930b1f5 JL |
253 | static FTPString *FTPStringAlloc(void) |
254 | { | |
255 | return FTPCalloc(1, sizeof(FTPString)); | |
256 | } | |
257 | ||
258 | static void FTPStringFree(FTPString *str) | |
259 | { | |
260 | if (str->str) { | |
261 | FTPFree(str->str, str->len); | |
262 | } | |
263 | ||
264 | FTPFree(str, sizeof(FTPString)); | |
265 | } | |
266 | ||
09ab032a JL |
267 | static void *FTPLocalStorageAlloc(void) |
268 | { | |
269 | /* needed by the mpm */ | |
270 | FTPThreadCtx *td = SCCalloc(1, sizeof(*td)); | |
271 | if (td == NULL) { | |
272 | exit(EXIT_FAILURE); | |
273 | } | |
274 | ||
275 | td->pmq = SCCalloc(1, sizeof(*td->pmq)); | |
276 | if (td->pmq == NULL) { | |
277 | exit(EXIT_FAILURE); | |
278 | } | |
279 | PmqSetup(td->pmq); | |
280 | ||
281 | td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx)); | |
282 | if (unlikely(td->ftp_mpm_thread_ctx == NULL)) { | |
283 | exit(EXIT_FAILURE); | |
284 | } | |
285 | MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM); | |
286 | return td; | |
287 | } | |
288 | ||
289 | static void FTPLocalStorageFree(void *ptr) | |
290 | { | |
291 | FTPThreadCtx *td = ptr; | |
292 | if (td != NULL) { | |
293 | if (td->pmq != NULL) { | |
294 | PmqFree(td->pmq); | |
295 | SCFree(td->pmq); | |
296 | } | |
297 | ||
298 | if (td->ftp_mpm_thread_ctx != NULL) { | |
299 | mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx); | |
300 | SCFree(td->ftp_mpm_thread_ctx); | |
301 | } | |
302 | ||
303 | SCFree(td); | |
304 | } | |
305 | ||
306 | return; | |
307 | } | |
1930b1f5 JL |
308 | static FTPTransaction *FTPTransactionCreate(FtpState *state) |
309 | { | |
310 | SCEnter(); | |
311 | FTPTransaction *tx = FTPCalloc(1, sizeof(*tx)); | |
312 | if (tx == NULL) { | |
313 | return NULL; | |
314 | } | |
315 | ||
316 | TAILQ_INSERT_TAIL(&state->tx_list, tx, next); | |
317 | tx->tx_id = state->tx_cnt++; | |
318 | ||
319 | TAILQ_INIT(&tx->response_list); | |
320 | ||
321 | SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt); | |
322 | return tx; | |
323 | } | |
324 | ||
325 | static void FTPTransactionFree(FTPTransaction *tx) | |
326 | { | |
327 | SCEnter(); | |
328 | ||
9c67c634 JI |
329 | if (tx->tx_data.de_state != NULL) { |
330 | DetectEngineStateFree(tx->tx_data.de_state); | |
1930b1f5 JL |
331 | } |
332 | ||
333 | if (tx->request) { | |
334 | FTPFree(tx->request, tx->request_length); | |
335 | } | |
1930b1f5 JL |
336 | |
337 | FTPString *str = NULL; | |
338 | while ((str = TAILQ_FIRST(&tx->response_list))) { | |
339 | TAILQ_REMOVE(&tx->response_list, str, next); | |
340 | FTPStringFree(str); | |
341 | } | |
342 | ||
aa3f784d | 343 | FTPFree(tx, sizeof(*tx)); |
1930b1f5 JL |
344 | } |
345 | ||
6ea8ac44 AS |
346 | static int FTPGetLineForDirection(FtpState *state, FtpLineState *line_state) |
347 | { | |
1f07d152 | 348 | void *ptmp; |
6ea8ac44 AS |
349 | if (line_state->current_line_lf_seen == 1) { |
350 | /* we have seen the lf for the previous line. Clear the parser | |
351 | * details to parse new line */ | |
352 | line_state->current_line_lf_seen = 0; | |
353 | if (line_state->current_line_db == 1) { | |
354 | line_state->current_line_db = 0; | |
711b6fb3 | 355 | FTPFree(line_state->db, line_state->db_len); |
6ea8ac44 AS |
356 | line_state->db = NULL; |
357 | line_state->db_len = 0; | |
358 | state->current_line = NULL; | |
359 | state->current_line_len = 0; | |
360 | } | |
361 | } | |
362 | ||
363 | uint8_t *lf_idx = memchr(state->input, 0x0a, state->input_len); | |
364 | ||
365 | if (lf_idx == NULL) { | |
366 | /* fragmented lines. Decoder event for special cases. Not all | |
367 | * fragmented lines should be treated as a possible evasion | |
368 | * attempt. With multi payload ftp chunks we can have valid | |
369 | * cases of fragmentation. But within the same segment chunk | |
370 | * if we see fragmentation then it's definitely something you | |
371 | * should alert about */ | |
372 | if (line_state->current_line_db == 0) { | |
711b6fb3 | 373 | line_state->db = FTPMalloc(state->input_len); |
6ea8ac44 AS |
374 | if (line_state->db == NULL) { |
375 | return -1; | |
376 | } | |
377 | line_state->current_line_db = 1; | |
378 | memcpy(line_state->db, state->input, state->input_len); | |
379 | line_state->db_len = state->input_len; | |
380 | } else { | |
711b6fb3 | 381 | ptmp = FTPRealloc(line_state->db, line_state->db_len, |
1f07d152 EL |
382 | (line_state->db_len + state->input_len)); |
383 | if (ptmp == NULL) { | |
711b6fb3 | 384 | FTPFree(line_state->db, line_state->db_len); |
1f07d152 EL |
385 | line_state->db = NULL; |
386 | line_state->db_len = 0; | |
6ea8ac44 AS |
387 | return -1; |
388 | } | |
1f07d152 EL |
389 | line_state->db = ptmp; |
390 | ||
6ea8ac44 AS |
391 | memcpy(line_state->db + line_state->db_len, |
392 | state->input, state->input_len); | |
393 | line_state->db_len += state->input_len; | |
394 | } | |
395 | state->input += state->input_len; | |
396 | state->input_len = 0; | |
397 | ||
398 | return -1; | |
399 | ||
400 | } else { | |
401 | line_state->current_line_lf_seen = 1; | |
402 | ||
403 | if (line_state->current_line_db == 1) { | |
711b6fb3 | 404 | ptmp = FTPRealloc(line_state->db, line_state->db_len, |
1f07d152 EL |
405 | (line_state->db_len + (lf_idx + 1 - state->input))); |
406 | if (ptmp == NULL) { | |
711b6fb3 | 407 | FTPFree(line_state->db, line_state->db_len); |
1f07d152 EL |
408 | line_state->db = NULL; |
409 | line_state->db_len = 0; | |
6ea8ac44 AS |
410 | return -1; |
411 | } | |
1f07d152 EL |
412 | line_state->db = ptmp; |
413 | ||
6ea8ac44 AS |
414 | memcpy(line_state->db + line_state->db_len, |
415 | state->input, (lf_idx + 1 - state->input)); | |
416 | line_state->db_len += (lf_idx + 1 - state->input); | |
417 | ||
418 | if (line_state->db_len > 1 && | |
419 | line_state->db[line_state->db_len - 2] == 0x0D) { | |
420 | line_state->db_len -= 2; | |
421 | state->current_line_delimiter_len = 2; | |
422 | } else { | |
423 | line_state->db_len -= 1; | |
424 | state->current_line_delimiter_len = 1; | |
425 | } | |
426 | ||
427 | state->current_line = line_state->db; | |
428 | state->current_line_len = line_state->db_len; | |
429 | ||
430 | } else { | |
431 | state->current_line = state->input; | |
432 | state->current_line_len = lf_idx - state->input; | |
433 | ||
434 | if (state->input != lf_idx && | |
435 | *(lf_idx - 1) == 0x0D) { | |
436 | state->current_line_len--; | |
437 | state->current_line_delimiter_len = 2; | |
438 | } else { | |
439 | state->current_line_delimiter_len = 1; | |
440 | } | |
441 | } | |
442 | ||
443 | state->input_len -= (lf_idx - state->input) + 1; | |
444 | state->input = (lf_idx + 1); | |
445 | ||
446 | return 0; | |
447 | } | |
448 | ||
449 | } | |
450 | ||
451 | static int FTPGetLine(FtpState *state) | |
452 | { | |
453 | SCEnter(); | |
454 | ||
455 | /* we have run out of input */ | |
456 | if (state->input_len <= 0) | |
457 | return -1; | |
458 | ||
459 | /* toserver */ | |
460 | if (state->direction == 0) | |
461 | return FTPGetLineForDirection(state, &state->line_state[0]); | |
462 | else | |
463 | return FTPGetLineForDirection(state, &state->line_state[1]); | |
464 | } | |
465 | ||
f2f9b832 PR |
466 | /** |
467 | * \brief This function is called to determine and set which command is being | |
1930b1f5 | 468 | * transferred to the ftp server |
86fabef0 | 469 | * \param thread context |
f2f9b832 PR |
470 | * \param input input line of the command |
471 | * \param len of the command | |
1930b1f5 | 472 | * \param cmd_descriptor when the command has been parsed |
f2f9b832 PR |
473 | * |
474 | * \retval 1 when the command is parsed, 0 otherwise | |
475 | */ | |
86fabef0 | 476 | static int FTPParseRequestCommand(FTPThreadCtx *td, |
579cc9f0 | 477 | const uint8_t *input, uint32_t input_len, |
09ab032a | 478 | const FtpCommand **cmd_descriptor) |
8f1d7503 | 479 | { |
f2f9b832 | 480 | SCEnter(); |
11b9e6fd | 481 | |
09ab032a JL |
482 | /* I don't like this pmq reset here. We'll devise a method later, that |
483 | * should make the use of the mpm very efficient */ | |
484 | PmqReset(td->pmq); | |
485 | int mpm_cnt = mpm_table[FTP_MPM].Search(ftp_mpm_ctx, td->ftp_mpm_thread_ctx, | |
486 | td->pmq, input, input_len); | |
487 | if (mpm_cnt) { | |
488 | *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; | |
86fabef0 | 489 | SCReturnInt(1); |
b0a69344 | 490 | } |
09ab032a JL |
491 | |
492 | *cmd_descriptor = NULL; | |
86fabef0 | 493 | SCReturnInt(0); |
f2f9b832 PR |
494 | } |
495 | ||
b0a69344 EL |
496 | struct FtpTransferCmd { |
497 | /** Need to look like a ExpectationData so DFree must | |
498 | * be first field . */ | |
499 | void (*DFree)(void *); | |
500 | uint64_t flow_id; | |
501 | uint8_t *file_name; | |
502 | uint16_t file_len; | |
503 | FtpRequestCommand cmd; | |
504 | }; | |
505 | ||
506 | static void FtpTransferCmdFree(void *data) | |
507 | { | |
508 | struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; | |
509 | if (cmd == NULL) | |
510 | return; | |
511 | if (cmd->file_name) { | |
aa3f784d | 512 | FTPFree(cmd->file_name, cmd->file_len + 1); |
b0a69344 | 513 | } |
711b6fb3 | 514 | FTPFree(cmd, sizeof(struct FtpTransferCmd)); |
b0a69344 EL |
515 | } |
516 | ||
579cc9f0 | 517 | static uint32_t CopyCommandLine(uint8_t **dest, const uint8_t *src, uint32_t length) |
1930b1f5 JL |
518 | { |
519 | if (likely(length)) { | |
1588cd87 | 520 | uint8_t *where = FTPCalloc(length + 1, sizeof(char)); |
1930b1f5 JL |
521 | if (unlikely(where == NULL)) { |
522 | return 0; | |
523 | } | |
524 | memcpy(where, src, length); | |
525 | ||
526 | /* Remove trailing newlines/carriage returns */ | |
1588cd87 ZK |
527 | while (length && isspace((unsigned char) where[length - 1])) { |
528 | length--; | |
1930b1f5 | 529 | } |
1588cd87 ZK |
530 | |
531 | where[length] = '\0'; | |
1930b1f5 JL |
532 | *dest = where; |
533 | } | |
534 | /* either 0 or actual */ | |
aa3f784d | 535 | return length ? length + 1 : 0; |
1930b1f5 JL |
536 | } |
537 | ||
4f33b8c1 | 538 | |
f2f9b832 PR |
539 | /** |
540 | * \brief This function is called to retrieve a ftp request | |
541 | * \param ftp_state the ftp state structure for the parser | |
542 | * \param input input line of the command | |
543 | * \param input_len length of the request | |
544 | * \param output the resulting output | |
545 | * | |
3bcf948a VJ |
546 | * \retval APP_LAYER_OK when input was process successfully |
547 | * \retval APP_LAYER_ERROR when a unrecoverable error was encountered | |
f2f9b832 | 548 | */ |
44d3f264 | 549 | static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, |
9634e60e | 550 | AppLayerParserState *pstate, |
579cc9f0 | 551 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 552 | void *local_data, const uint8_t flags) |
9a6aef45 | 553 | { |
09ab032a JL |
554 | FTPThreadCtx *thread_data = local_data; |
555 | ||
f2f9b832 | 556 | SCEnter(); |
f2f9b832 PR |
557 | /* PrintRawDataFp(stdout, input,input_len); */ |
558 | ||
6ea8ac44 | 559 | FtpState *state = (FtpState *)ftp_state; |
1f07d152 | 560 | void *ptmp; |
6ea8ac44 | 561 | |
4f73943d | 562 | if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) { |
44d3f264 | 563 | SCReturnStruct(APP_LAYER_OK); |
f4f53924 | 564 | } else if (input == NULL || input_len == 0) { |
44d3f264 | 565 | SCReturnStruct(APP_LAYER_ERROR); |
4e7cb7b8 VJ |
566 | } |
567 | ||
6ea8ac44 AS |
568 | state->input = input; |
569 | state->input_len = input_len; | |
570 | /* toserver stream */ | |
571 | state->direction = 0; | |
572 | ||
b0a69344 | 573 | int direction = STREAM_TOSERVER; |
6ea8ac44 | 574 | while (FTPGetLine(state) >= 0) { |
1930b1f5 JL |
575 | const FtpCommand *cmd_descriptor; |
576 | ||
157d01e8 VJ |
577 | if (!FTPParseRequestCommand(thread_data, |
578 | state->current_line, state->current_line_len, | |
579 | &cmd_descriptor)) { | |
1930b1f5 JL |
580 | state->command = FTP_COMMAND_UNKNOWN; |
581 | continue; | |
582 | } | |
583 | ||
584 | state->command = cmd_descriptor->command; | |
585 | ||
2149807b JL |
586 | FTPTransaction *tx = FTPTransactionCreate(state); |
587 | if (unlikely(tx == NULL)) | |
44d3f264 | 588 | SCReturnStruct(APP_LAYER_ERROR); |
2149807b | 589 | state->curr_tx = tx; |
1930b1f5 JL |
590 | |
591 | tx->command_descriptor = cmd_descriptor; | |
157d01e8 VJ |
592 | tx->request_length = CopyCommandLine(&tx->request, |
593 | state->current_line, state->current_line_len); | |
1930b1f5 | 594 | |
071f55dc XW |
595 | /* change direction (default to server) so expectation will handle |
596 | * the correct message when expectation will match. | |
597 | * For ftp active mode, data connection direction is opposite to | |
598 | * control direction. | |
599 | */ | |
600 | if ((state->active && state->command == FTP_COMMAND_STOR) || | |
601 | (!state->active && state->command == FTP_COMMAND_RETR)) { | |
602 | direction = STREAM_TOCLIENT; | |
603 | } | |
604 | ||
b0a69344 | 605 | switch (state->command) { |
4f33b8c1 JL |
606 | case FTP_COMMAND_EPRT: |
607 | // fallthrough | |
b0a69344 | 608 | case FTP_COMMAND_PORT: |
3ae2edb2 | 609 | if (state->current_line_len + 1 > state->port_line_size) { |
4f33b8c1 | 610 | /* Allocate an extra byte for a NULL terminator */ |
711b6fb3 | 611 | ptmp = FTPRealloc(state->port_line, state->port_line_size, |
b4070b6d | 612 | state->current_line_len); |
b0a69344 | 613 | if (ptmp == NULL) { |
4f33b8c1 JL |
614 | if (state->port_line) { |
615 | FTPFree(state->port_line, state->port_line_size); | |
616 | state->port_line = NULL; | |
617 | state->port_line_size = 0; | |
618 | } | |
44d3f264 | 619 | SCReturnStruct(APP_LAYER_OK); |
b0a69344 EL |
620 | } |
621 | state->port_line = ptmp; | |
b4070b6d | 622 | state->port_line_size = state->current_line_len; |
6ea8ac44 | 623 | } |
b0a69344 EL |
624 | memcpy(state->port_line, state->current_line, |
625 | state->current_line_len); | |
626 | state->port_line_len = state->current_line_len; | |
627 | break; | |
628 | case FTP_COMMAND_RETR: | |
b0a69344 | 629 | // fallthrough |
3bcf948a | 630 | case FTP_COMMAND_STOR: { |
354074ba JL |
631 | /* Ensure that there is a negotiated dyn port and a file |
632 | * name -- need more than 5 chars: cmd [4], space, <filename> | |
633 | */ | |
634 | if (state->dyn_port == 0 || state->current_line_len < 6) { | |
44d3f264 | 635 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 636 | } |
711b6fb3 | 637 | struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); |
b0a69344 | 638 | if (data == NULL) |
44d3f264 | 639 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 640 | data->DFree = FtpTransferCmdFree; |
72e2f36f JL |
641 | /* |
642 | * Min size has been checked in FTPParseRequestCommand | |
643 | * PATH_MAX includes the null | |
644 | */ | |
645 | int file_name_len = MIN(PATH_MAX - 1, state->current_line_len - 5); | |
646 | data->file_name = FTPCalloc(file_name_len + 1, sizeof(char)); | |
b0a69344 | 647 | if (data->file_name == NULL) { |
de983fb7 | 648 | FtpTransferCmdFree(data); |
44d3f264 | 649 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 650 | } |
72e2f36f JL |
651 | data->file_name[file_name_len] = 0; |
652 | data->file_len = file_name_len; | |
653 | memcpy(data->file_name, state->current_line + 5, file_name_len); | |
b0a69344 EL |
654 | data->cmd = state->command; |
655 | data->flow_id = FlowGetId(f); | |
071f55dc | 656 | int ret = AppLayerExpectationCreate(f, direction, |
4f33b8c1 | 657 | 0, state->dyn_port, ALPROTO_FTPDATA, data); |
b0a69344 | 658 | if (ret == -1) { |
de983fb7 | 659 | FtpTransferCmdFree(data); |
b0a69344 | 660 | SCLogDebug("No expectation created."); |
44d3f264 | 661 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 | 662 | } else { |
4f33b8c1 JL |
663 | SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].", |
664 | state->active ? "to server" : "to client", | |
665 | state->dyn_port); | |
b0a69344 | 666 | } |
4f33b8c1 | 667 | |
b0a69344 EL |
668 | /* reset the dyn port to avoid duplicate */ |
669 | state->dyn_port = 0; | |
4f33b8c1 JL |
670 | /* reset active/passive indicator */ |
671 | state->active = false; | |
b0a69344 EL |
672 | } |
673 | break; | |
674 | default: | |
675 | break; | |
6ea8ac44 | 676 | } |
f2f9b832 | 677 | } |
f2f9b832 | 678 | |
44d3f264 | 679 | SCReturnStruct(APP_LAYER_OK); |
f2f9b832 PR |
680 | } |
681 | ||
579cc9f0 | 682 | static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
b0a69344 | 683 | { |
f79316d7 | 684 | uint16_t dyn_port = rs_ftp_pasv_response(input, input_len); |
b0a69344 EL |
685 | if (dyn_port == 0) { |
686 | return -1; | |
687 | } | |
4f33b8c1 JL |
688 | SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port); |
689 | state->active = false; | |
b0a69344 | 690 | state->dyn_port = dyn_port; |
1930b1f5 JL |
691 | state->curr_tx->dyn_port = dyn_port; |
692 | state->curr_tx->active = false; | |
b0a69344 EL |
693 | |
694 | return 0; | |
695 | } | |
696 | ||
579cc9f0 | 697 | static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
b0a69344 | 698 | { |
f79316d7 | 699 | uint16_t dyn_port = rs_ftp_epsv_response(input, input_len); |
b0a69344 EL |
700 | if (dyn_port == 0) { |
701 | return -1; | |
702 | } | |
4f33b8c1 JL |
703 | SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port); |
704 | state->active = false; | |
b0a69344 | 705 | state->dyn_port = dyn_port; |
1930b1f5 JL |
706 | state->curr_tx->dyn_port = dyn_port; |
707 | state->curr_tx->active = false; | |
b0a69344 EL |
708 | return 0; |
709 | } | |
710 | ||
1930b1f5 JL |
711 | /** |
712 | * \brief Handle preliminary replies -- keep tx open | |
3bcf948a | 713 | * \retval bool True for a positive preliminary reply; false otherwise |
1930b1f5 JL |
714 | * |
715 | * 1yz Positive Preliminary reply | |
716 | * | |
717 | * The requested action is being initiated; expect another | |
718 | * reply before proceeding with a new command | |
719 | */ | |
579cc9f0 | 720 | static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len) |
1930b1f5 JL |
721 | { |
722 | return input_len >= 4 && isdigit(input[0]) && input[0] == '1' && | |
723 | isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]); | |
724 | } | |
725 | ||
f2f9b832 PR |
726 | /** |
727 | * \brief This function is called to retrieve a ftp response | |
728 | * \param ftp_state the ftp state structure for the parser | |
729 | * \param input input line of the command | |
730 | * \param input_len length of the request | |
731 | * \param output the resulting output | |
732 | * | |
733 | * \retval 1 when the command is parsed, 0 otherwise | |
734 | */ | |
44d3f264 | 735 | static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
579cc9f0 | 736 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 737 | void *local_data, const uint8_t flags) |
9a6aef45 | 738 | { |
11b9e6fd | 739 | FtpState *state = (FtpState *)ftp_state; |
1930b1f5 | 740 | |
b595da6c | 741 | if (unlikely(input_len == 0)) { |
44d3f264 | 742 | SCReturnStruct(APP_LAYER_OK); |
b595da6c | 743 | } |
a6294d6e PA |
744 | state->input = input; |
745 | state->input_len = input_len; | |
746 | /* toclient stream */ | |
747 | state->direction = 1; | |
a04b1c16 | 748 | |
057c4b34 | 749 | FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list); |
a6294d6e | 750 | while (FTPGetLine(state) >= 0) { |
057c4b34 | 751 | FTPTransaction *tx = FTPGetOldestTx(state, lasttx); |
699d6682 PA |
752 | if (tx == NULL) { |
753 | tx = FTPTransactionCreate(state); | |
754 | } | |
755 | if (unlikely(tx == NULL)) { | |
756 | SCReturnStruct(APP_LAYER_ERROR); | |
757 | } | |
057c4b34 | 758 | lasttx = tx; |
699d6682 PA |
759 | if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { |
760 | /* unknown */ | |
761 | tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX -1]; | |
11b9e6fd | 762 | } |
11b9e6fd | 763 | |
699d6682 | 764 | state->curr_tx = tx; |
fef124b9 PA |
765 | uint16_t dyn_port; |
766 | switch (state->command) { | |
767 | case FTP_COMMAND_AUTH_TLS: | |
768 | if (state->current_line_len >= 4 && SCMemcmp("234 ", state->current_line, 4) == 0) { | |
769 | AppLayerRequestProtocolTLSUpgrade(f); | |
770 | } | |
771 | break; | |
4f33b8c1 | 772 | |
fef124b9 PA |
773 | case FTP_COMMAND_EPRT: |
774 | dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len); | |
775 | if (dyn_port == 0) { | |
776 | goto tx_complete; | |
777 | } | |
778 | state->dyn_port = dyn_port; | |
779 | state->active = true; | |
780 | tx->dyn_port = dyn_port; | |
781 | tx->active = true; | |
782 | SCLogDebug("FTP active mode (v6): dynamic port %"PRIu16"", dyn_port); | |
783 | break; | |
1930b1f5 | 784 | |
fef124b9 PA |
785 | case FTP_COMMAND_PORT: |
786 | dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len); | |
699d6682 PA |
787 | if (dyn_port == 0) { |
788 | goto tx_complete; | |
789 | } | |
790 | state->dyn_port = dyn_port; | |
791 | state->active = true; | |
792 | tx->dyn_port = state->dyn_port; | |
793 | tx->active = true; | |
794 | SCLogDebug("FTP active mode (v4): dynamic port %"PRIu16"", dyn_port); | |
fef124b9 | 795 | break; |
b0a69344 | 796 | |
fef124b9 PA |
797 | case FTP_COMMAND_PASV: |
798 | if (state->current_line_len >= 4 && SCMemcmp("227 ", state->current_line, 4) == 0) { | |
799 | FTPParsePassiveResponse(f, ftp_state, state->current_line, state->current_line_len); | |
800 | } | |
801 | break; | |
b0a69344 | 802 | |
fef124b9 PA |
803 | case FTP_COMMAND_EPSV: |
804 | if (state->current_line_len >= 4 && SCMemcmp("229 ", state->current_line, 4) == 0) { | |
805 | FTPParsePassiveResponseV6(f, ftp_state, state->current_line, state->current_line_len); | |
806 | } | |
807 | break; | |
808 | default: | |
809 | break; | |
1930b1f5 | 810 | } |
1930b1f5 | 811 | |
699d6682 PA |
812 | if (likely(state->current_line_len)) { |
813 | FTPString *response = FTPStringAlloc(); | |
814 | if (likely(response)) { | |
815 | response->len = CopyCommandLine(&response->str, state->current_line, state->current_line_len); | |
816 | TAILQ_INSERT_TAIL(&tx->response_list, response, next); | |
817 | } | |
818 | } | |
819 | ||
820 | /* Handle preliminary replies -- keep tx open */ | |
821 | if (FTPIsPPR(state->current_line, state->current_line_len)) { | |
822 | continue; | |
823 | } | |
824 | tx_complete: | |
825 | tx->done = true; | |
a6294d6e PA |
826 | } |
827 | ||
44d3f264 | 828 | SCReturnStruct(APP_LAYER_OK); |
f2f9b832 PR |
829 | } |
830 | ||
911d423a | 831 | |
f2f9b832 | 832 | #ifdef DEBUG |
5532af46 | 833 | static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER; |
f2f9b832 PR |
834 | static uint64_t ftp_state_memuse = 0; |
835 | static uint64_t ftp_state_memcnt = 0; | |
836 | #endif | |
837 | ||
547d6c2d | 838 | static void *FTPStateAlloc(void *orig_state, AppProto proto_orig) |
8f1d7503 | 839 | { |
1930b1f5 | 840 | void *s = FTPCalloc(1, sizeof(FtpState)); |
e176be6f | 841 | if (unlikely(s == NULL)) |
f2f9b832 PR |
842 | return NULL; |
843 | ||
1930b1f5 JL |
844 | FtpState *ftp_state = (FtpState *) s; |
845 | TAILQ_INIT(&ftp_state->tx_list); | |
f2f9b832 PR |
846 | |
847 | #ifdef DEBUG | |
848 | SCMutexLock(&ftp_state_mem_lock); | |
849 | ftp_state_memcnt++; | |
850 | ftp_state_memuse+=sizeof(FtpState); | |
851 | SCMutexUnlock(&ftp_state_mem_lock); | |
852 | #endif | |
853 | return s; | |
854 | } | |
855 | ||
8f1d7503 KS |
856 | static void FTPStateFree(void *s) |
857 | { | |
18954a2c PR |
858 | FtpState *fstate = (FtpState *) s; |
859 | if (fstate->port_line != NULL) | |
711b6fb3 | 860 | FTPFree(fstate->port_line, fstate->port_line_size); |
b4ab9a0a | 861 | if (fstate->line_state[0].db) |
711b6fb3 | 862 | FTPFree(fstate->line_state[0].db, fstate->line_state[0].db_len); |
b4ab9a0a | 863 | if (fstate->line_state[1].db) |
711b6fb3 | 864 | FTPFree(fstate->line_state[1].db, fstate->line_state[1].db_len); |
402eb645 | 865 | |
1930b1f5 JL |
866 | FTPTransaction *tx = NULL; |
867 | while ((tx = TAILQ_FIRST(&fstate->tx_list))) { | |
868 | TAILQ_REMOVE(&fstate->tx_list, tx, next); | |
2149807b | 869 | SCLogDebug("[%s] state %p id %"PRIu64", Freeing %d bytes at %p", |
09ab032a | 870 | tx->command_descriptor->command_name, |
2149807b JL |
871 | s, tx->tx_id, |
872 | tx->request_length, tx->request); | |
1930b1f5 | 873 | FTPTransactionFree(tx); |
402eb645 VJ |
874 | } |
875 | ||
711b6fb3 | 876 | FTPFree(s, sizeof(FtpState)); |
f2f9b832 PR |
877 | #ifdef DEBUG |
878 | SCMutexLock(&ftp_state_mem_lock); | |
879 | ftp_state_memcnt--; | |
880 | ftp_state_memuse-=sizeof(FtpState); | |
881 | SCMutexUnlock(&ftp_state_mem_lock); | |
882 | #endif | |
883 | } | |
884 | ||
2149807b JL |
885 | /** |
886 | * \brief This function returns the oldest open transaction; if none | |
887 | * are open, then the oldest transaction is returned | |
888 | * \param ftp_state the ftp state structure for the parser | |
057c4b34 | 889 | * \param starttx the ftp transaction where to start looking |
2149807b JL |
890 | * |
891 | * \retval transaction pointer when a transaction was found; NULL otherwise. | |
892 | */ | |
057c4b34 | 893 | static FTPTransaction *FTPGetOldestTx(FtpState *ftp_state, FTPTransaction *starttx) |
2149807b JL |
894 | { |
895 | if (unlikely(!ftp_state)) { | |
896 | SCLogDebug("NULL state object; no transactions available"); | |
897 | return NULL; | |
898 | } | |
057c4b34 | 899 | FTPTransaction *tx = starttx; |
2149807b | 900 | FTPTransaction *lasttx = NULL; |
057c4b34 | 901 | while(tx != NULL) { |
2149807b JL |
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; | |
057c4b34 | 909 | tx = TAILQ_NEXT(tx, next); |
2149807b JL |
910 | } |
911 | /* All tx are closed; return last element */ | |
912 | if (lasttx) | |
913 | SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id); | |
914 | return lasttx; | |
915 | } | |
916 | ||
1930b1f5 JL |
917 | static void *FTPGetTx(void *state, uint64_t tx_id) |
918 | { | |
919 | FtpState *ftp_state = (FtpState *)state; | |
920 | if (ftp_state) { | |
921 | FTPTransaction *tx = NULL; | |
922 | ||
923 | if (ftp_state->curr_tx == NULL) | |
924 | return NULL; | |
925 | if (ftp_state->curr_tx->tx_id == tx_id) | |
926 | return ftp_state->curr_tx; | |
927 | ||
928 | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { | |
929 | if (tx->tx_id == tx_id) | |
930 | return tx; | |
931 | } | |
932 | } | |
933 | return NULL; | |
1930b1f5 JL |
934 | } |
935 | ||
c98f5978 | 936 | static AppLayerTxData *FTPGetTxData(void *vtx) |
402eb645 | 937 | { |
1930b1f5 | 938 | FTPTransaction *tx = (FTPTransaction *)vtx; |
c98f5978 | 939 | return &tx->tx_data; |
1930b1f5 JL |
940 | } |
941 | ||
942 | static void FTPStateTransactionFree(void *state, uint64_t tx_id) | |
943 | { | |
944 | FtpState *ftp_state = state; | |
945 | FTPTransaction *tx = NULL; | |
946 | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { | |
947 | if (tx_id < tx->tx_id) | |
948 | break; | |
949 | else if (tx_id > tx->tx_id) | |
950 | continue; | |
951 | ||
952 | if (tx == ftp_state->curr_tx) | |
953 | ftp_state->curr_tx = NULL; | |
954 | TAILQ_REMOVE(&ftp_state->tx_list, tx, next); | |
955 | FTPTransactionFree(tx); | |
956 | break; | |
957 | } | |
402eb645 VJ |
958 | } |
959 | ||
960 | static uint64_t FTPGetTxCnt(void *state) | |
961 | { | |
1930b1f5 JL |
962 | uint64_t cnt = 0; |
963 | FtpState *ftp_state = state; | |
964 | if (ftp_state) { | |
965 | cnt = ftp_state->tx_cnt; | |
966 | } | |
967 | SCLogDebug("returning state %p %"PRIu64, state, cnt); | |
968 | return cnt; | |
402eb645 VJ |
969 | } |
970 | ||
1930b1f5 | 971 | static int FTPGetAlstateProgress(void *vtx, uint8_t direction) |
402eb645 | 972 | { |
1930b1f5 JL |
973 | SCLogDebug("tx %p", vtx); |
974 | FTPTransaction *tx = vtx; | |
402eb645 | 975 | |
6f364032 PA |
976 | if (!tx->done) { |
977 | if (direction == STREAM_TOSERVER && | |
978 | tx->command_descriptor->command == FTP_COMMAND_PORT) { | |
979 | return FTP_STATE_PORT_DONE; | |
980 | } | |
dc80d520 | 981 | return FTP_STATE_IN_PROGRESS; |
6f364032 | 982 | } |
402eb645 | 983 | |
dc80d520 | 984 | return FTP_STATE_FINISHED; |
402eb645 VJ |
985 | } |
986 | ||
987 | ||
429c6388 AS |
988 | static int FTPRegisterPatternsForProtocolDetection(void) |
989 | { | |
8125f78f MK |
990 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, |
991 | "220 (", 5, 0, STREAM_TOCLIENT) < 0) | |
992 | { | |
993 | return -1; | |
994 | } | |
995 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
996 | "FEAT", 4, 0, STREAM_TOSERVER) < 0) | |
997 | { | |
998 | return -1; | |
999 | } | |
429c6388 AS |
1000 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, |
1001 | "USER ", 5, 0, STREAM_TOSERVER) < 0) | |
1002 | { | |
1003 | return -1; | |
1004 | } | |
1005 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
1006 | "PASS ", 5, 0, STREAM_TOSERVER) < 0) | |
1007 | { | |
1008 | return -1; | |
1009 | } | |
1010 | if (AppLayerProtoDetectPMRegisterPatternCI(IPPROTO_TCP, ALPROTO_FTP, | |
1011 | "PORT ", 5, 0, STREAM_TOSERVER) < 0) | |
1012 | { | |
1013 | return -1; | |
1014 | } | |
1015 | ||
1016 | return 0; | |
1017 | } | |
1018 | ||
b0a69344 EL |
1019 | |
1020 | static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; | |
1021 | ||
1022 | /** | |
1023 | * \brief This function is called to retrieve a ftp request | |
1024 | * \param ftp_state the ftp state structure for the parser | |
1025 | * \param input input line of the command | |
1026 | * \param input_len length of the request | |
1027 | * \param output the resulting output | |
1028 | * | |
1029 | * \retval 1 when the command is parsed, 0 otherwise | |
1030 | */ | |
44d3f264 | 1031 | static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, |
b0a69344 | 1032 | AppLayerParserState *pstate, |
579cc9f0 | 1033 | const uint8_t *input, uint32_t input_len, |
b0a69344 EL |
1034 | void *local_data, int direction) |
1035 | { | |
1036 | uint16_t flags = FileFlowToFlags(f, direction); | |
1037 | int ret = 0; | |
1038 | /* we depend on detection engine for file pruning */ | |
1039 | flags |= FILE_USE_DETECT; | |
1040 | if (ftpdata_state->files == NULL) { | |
4b3be245 VJ |
1041 | struct FtpTransferCmd *data = |
1042 | (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); | |
b0a69344 | 1043 | if (data == NULL) { |
44d3f264 | 1044 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
1045 | } |
1046 | ||
1047 | ftpdata_state->files = FileContainerAlloc(); | |
1048 | if (ftpdata_state->files == NULL) { | |
4b3be245 | 1049 | FlowFreeStorageById(f, AppLayerExpectationGetFlowId()); |
44d3f264 | 1050 | SCReturnStruct(APP_LAYER_ERROR); |
b0a69344 EL |
1051 | } |
1052 | ||
1053 | ftpdata_state->file_name = data->file_name; | |
1054 | ftpdata_state->file_len = data->file_len; | |
1055 | data->file_name = NULL; | |
1056 | data->file_len = 0; | |
1057 | f->parent_id = data->flow_id; | |
1058 | ftpdata_state->command = data->cmd; | |
2515c892 EL |
1059 | switch (data->cmd) { |
1060 | case FTP_COMMAND_STOR: | |
1061 | ftpdata_state->direction = STREAM_TOSERVER; | |
1062 | break; | |
1063 | case FTP_COMMAND_RETR: | |
1064 | ftpdata_state->direction = STREAM_TOCLIENT; | |
1065 | break; | |
1066 | default: | |
1067 | break; | |
1068 | } | |
b0a69344 | 1069 | |
9132e403 VJ |
1070 | /* open with fixed track_id 0 as we can have just one |
1071 | * file per ftp-data flow. */ | |
1072 | if (FileOpenFileWithId(ftpdata_state->files, &sbcfg, | |
1073 | 0ULL, (uint8_t *) ftpdata_state->file_name, | |
b0a69344 | 1074 | ftpdata_state->file_len, |
9132e403 | 1075 | input, input_len, flags) != 0) { |
b0a69344 EL |
1076 | SCLogDebug("Can't open file"); |
1077 | ret = -1; | |
1078 | } | |
4b3be245 | 1079 | FlowFreeStorageById(f, AppLayerExpectationGetFlowId()); |
04ba6dc1 | 1080 | ftpdata_state->tx_data.files_opened = 1; |
b0a69344 EL |
1081 | } else { |
1082 | if (input_len != 0) { | |
1083 | ret = FileAppendData(ftpdata_state->files, input, input_len); | |
1084 | if (ret == -2) { | |
1085 | ret = 0; | |
1086 | SCLogDebug("FileAppendData() - file no longer being extracted"); | |
1087 | goto out; | |
1088 | } else if (ret < 0) { | |
1089 | SCLogDebug("FileAppendData() failed: %d", ret); | |
1090 | ret = -2; | |
1091 | goto out; | |
1092 | } | |
1093 | } else { | |
1094 | ret = FileCloseFile(ftpdata_state->files, NULL, 0, flags); | |
1095 | ftpdata_state->state = FTPDATA_STATE_FINISHED; | |
1096 | if (ret < 0) | |
1097 | goto out; | |
1098 | } | |
1099 | } | |
1100 | ||
4f73943d VJ |
1101 | const bool eof_flag = flags & STREAM_TOSERVER ? |
1102 | AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) != 0 : | |
1103 | AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) != 0; | |
1104 | if (input_len && eof_flag) { | |
b0a69344 EL |
1105 | ret = FileCloseFile(ftpdata_state->files, (uint8_t *) NULL, 0, flags); |
1106 | ftpdata_state->state = FTPDATA_STATE_FINISHED; | |
1107 | } | |
1108 | ||
1109 | out: | |
44d3f264 VJ |
1110 | if (ret < 0) { |
1111 | SCReturnStruct(APP_LAYER_ERROR); | |
1112 | } | |
1113 | SCReturnStruct(APP_LAYER_OK); | |
b0a69344 EL |
1114 | } |
1115 | ||
44d3f264 | 1116 | static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, |
b0a69344 | 1117 | AppLayerParserState *pstate, |
579cc9f0 | 1118 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 1119 | void *local_data, const uint8_t flags) |
b0a69344 EL |
1120 | { |
1121 | return FTPDataParse(f, ftp_state, pstate, input, input_len, | |
1122 | local_data, STREAM_TOSERVER); | |
1123 | } | |
1124 | ||
44d3f264 | 1125 | static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, |
b0a69344 | 1126 | AppLayerParserState *pstate, |
579cc9f0 | 1127 | const uint8_t *input, uint32_t input_len, |
7bc3c3ac | 1128 | void *local_data, const uint8_t flags) |
b0a69344 EL |
1129 | { |
1130 | return FTPDataParse(f, ftp_state, pstate, input, input_len, | |
1131 | local_data, STREAM_TOCLIENT); | |
1132 | } | |
1133 | ||
1134 | #ifdef DEBUG | |
1135 | static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER; | |
1136 | static uint64_t ftpdata_state_memuse = 0; | |
1137 | static uint64_t ftpdata_state_memcnt = 0; | |
1138 | #endif | |
1139 | ||
547d6c2d | 1140 | static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig) |
b0a69344 | 1141 | { |
1930b1f5 | 1142 | void *s = FTPCalloc(1, sizeof(FtpDataState)); |
b0a69344 EL |
1143 | if (unlikely(s == NULL)) |
1144 | return NULL; | |
1145 | ||
1930b1f5 JL |
1146 | FtpDataState *state = (FtpDataState *) s; |
1147 | state->state = FTPDATA_STATE_IN_PROGRESS; | |
b0a69344 EL |
1148 | |
1149 | #ifdef DEBUG | |
1150 | SCMutexLock(&ftpdata_state_mem_lock); | |
1151 | ftpdata_state_memcnt++; | |
1152 | ftpdata_state_memuse+=sizeof(FtpDataState); | |
1153 | SCMutexUnlock(&ftpdata_state_mem_lock); | |
1154 | #endif | |
1155 | return s; | |
1156 | } | |
1157 | ||
1158 | static void FTPDataStateFree(void *s) | |
1159 | { | |
1160 | FtpDataState *fstate = (FtpDataState *) s; | |
1161 | ||
9c67c634 JI |
1162 | if (fstate->tx_data.de_state != NULL) { |
1163 | DetectEngineStateFree(fstate->tx_data.de_state); | |
b0a69344 EL |
1164 | } |
1165 | if (fstate->file_name != NULL) { | |
aa3f784d | 1166 | FTPFree(fstate->file_name, fstate->file_len + 1); |
b0a69344 EL |
1167 | } |
1168 | ||
1169 | FileContainerFree(fstate->files); | |
1170 | ||
aa3f784d | 1171 | FTPFree(s, sizeof(FtpDataState)); |
b0a69344 EL |
1172 | #ifdef DEBUG |
1173 | SCMutexLock(&ftpdata_state_mem_lock); | |
1174 | ftpdata_state_memcnt--; | |
1175 | ftpdata_state_memuse-=sizeof(FtpDataState); | |
1176 | SCMutexUnlock(&ftpdata_state_mem_lock); | |
1177 | #endif | |
1178 | } | |
1179 | ||
c98f5978 | 1180 | static AppLayerTxData *FTPDataGetTxData(void *vtx) |
b1beb76f JI |
1181 | { |
1182 | FtpDataState *ftp_state = (FtpDataState *)vtx; | |
c98f5978 | 1183 | return &ftp_state->tx_data; |
b1beb76f JI |
1184 | } |
1185 | ||
b0a69344 EL |
1186 | static void FTPDataStateTransactionFree(void *state, uint64_t tx_id) |
1187 | { | |
1188 | /* do nothing */ | |
1189 | } | |
1190 | ||
1191 | static void *FTPDataGetTx(void *state, uint64_t tx_id) | |
1192 | { | |
1193 | FtpDataState *ftp_state = (FtpDataState *)state; | |
1194 | return ftp_state; | |
1195 | } | |
1196 | ||
1197 | static uint64_t FTPDataGetTxCnt(void *state) | |
1198 | { | |
1199 | /* ftp-data is single tx */ | |
1200 | return 1; | |
1201 | } | |
1202 | ||
b0a69344 EL |
1203 | static int FTPDataGetAlstateProgress(void *tx, uint8_t direction) |
1204 | { | |
1205 | FtpDataState *ftpdata_state = (FtpDataState *)tx; | |
1206 | return ftpdata_state->state; | |
1207 | } | |
1208 | ||
1209 | static FileContainer *FTPDataStateGetFiles(void *state, uint8_t direction) | |
1210 | { | |
1211 | FtpDataState *ftpdata_state = (FtpDataState *)state; | |
1212 | ||
2515c892 EL |
1213 | if (direction != ftpdata_state->direction) |
1214 | SCReturnPtr(NULL, "FileContainer"); | |
1215 | ||
b0a69344 EL |
1216 | SCReturnPtr(ftpdata_state->files, "FileContainer"); |
1217 | } | |
1218 | ||
09ab032a JL |
1219 | static void FTPSetMpmState(void) |
1220 | { | |
1221 | ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx)); | |
1222 | if (unlikely(ftp_mpm_ctx == NULL)) { | |
1223 | exit(EXIT_FAILURE); | |
1224 | } | |
1225 | memset(ftp_mpm_ctx, 0, sizeof(MpmCtx)); | |
1226 | MpmInitCtx(ftp_mpm_ctx, FTP_MPM); | |
1227 | ||
1228 | uint32_t i = 0; | |
1229 | for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { | |
1230 | const FtpCommand *cmd = &FtpCommands[i]; | |
86deaefe JL |
1231 | if (cmd->command_length == 0) |
1232 | continue; | |
1233 | ||
09ab032a JL |
1234 | MpmAddPatternCI(ftp_mpm_ctx, |
1235 | (uint8_t *)cmd->command_name, | |
1236 | cmd->command_length, | |
1237 | 0 /* defunct */, 0 /* defunct */, | |
1238 | i /* id */, i /* rule id */ , 0 /* no flags */); | |
1239 | } | |
1240 | ||
1241 | mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); | |
1242 | ||
1243 | } | |
1244 | ||
1245 | static void FTPFreeMpmState(void) | |
1246 | { | |
1247 | if (ftp_mpm_ctx != NULL) { | |
1248 | mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx); | |
1249 | SCFree(ftp_mpm_ctx); | |
1250 | ftp_mpm_ctx = NULL; | |
1251 | } | |
1252 | } | |
1253 | ||
8f1d7503 KS |
1254 | void RegisterFTPParsers(void) |
1255 | { | |
ab1200fb | 1256 | const char *proto_name = "ftp"; |
b0a69344 | 1257 | const char *proto_data_name = "ftp-data"; |
10966245 | 1258 | |
000ce98c | 1259 | /** FTP */ |
429c6388 AS |
1260 | if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { |
1261 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name); | |
1262 | if (FTPRegisterPatternsForProtocolDetection() < 0 ) | |
1263 | return; | |
b0a69344 | 1264 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name); |
ddde572f AS |
1265 | } |
1266 | ||
429c6388 AS |
1267 | if (AppLayerParserConfParserEnabled("tcp", proto_name)) { |
1268 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER, | |
1269 | FTPParseRequest); | |
1270 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT, | |
1271 | FTPParseResponse); | |
1272 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree); | |
1273 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT); | |
402eb645 VJ |
1274 | |
1275 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree); | |
1276 | ||
402eb645 | 1277 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx); |
c98f5978 | 1278 | AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData); |
402eb645 | 1279 | |
09ab032a JL |
1280 | AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc, |
1281 | FTPLocalStorageFree); | |
402eb645 VJ |
1282 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt); |
1283 | ||
1284 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress); | |
1285 | ||
efc9a7a3 VJ |
1286 | AppLayerParserRegisterStateProgressCompletionStatus( |
1287 | ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED); | |
b0a69344 EL |
1288 | |
1289 | AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA); | |
1290 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER, | |
1291 | FTPDataParseRequest); | |
1292 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT, | |
1293 | FTPDataParseResponse); | |
1294 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree); | |
1295 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT); | |
1296 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree); | |
b0a69344 EL |
1297 | |
1298 | AppLayerParserRegisterGetFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetFiles); | |
1299 | ||
1300 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx); | |
c98f5978 | 1301 | AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData); |
b0a69344 EL |
1302 | |
1303 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt); | |
1304 | ||
1305 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress); | |
1306 | ||
efc9a7a3 VJ |
1307 | AppLayerParserRegisterStateProgressCompletionStatus( |
1308 | ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED); | |
b0a69344 EL |
1309 | |
1310 | sbcfg.buf_size = 4096; | |
711b6fb3 EL |
1311 | sbcfg.Malloc = FTPMalloc; |
1312 | sbcfg.Calloc = FTPCalloc; | |
1313 | sbcfg.Realloc = FTPRealloc; | |
1314 | sbcfg.Free = FTPFree; | |
b0a69344 | 1315 | |
711b6fb3 | 1316 | FTPParseMemcap(); |
ddde572f AS |
1317 | } else { |
1318 | SCLogInfo("Parsed disabled for %s protocol. Protocol detection" | |
1319 | "still on.", proto_name); | |
1320 | } | |
09ab032a JL |
1321 | |
1322 | FTPSetMpmState(); | |
1323 | ||
9faa4b74 | 1324 | #ifdef UNITTESTS |
429c6388 | 1325 | AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests); |
9faa4b74 | 1326 | #endif |
f2f9b832 PR |
1327 | } |
1328 | ||
8f1d7503 KS |
1329 | void FTPAtExitPrintStats(void) |
1330 | { | |
f2f9b832 PR |
1331 | #ifdef DEBUG |
1332 | SCMutexLock(&ftp_state_mem_lock); | |
1333 | SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"", | |
1334 | ftp_state_memcnt, ftp_state_memuse); | |
1335 | SCMutexUnlock(&ftp_state_mem_lock); | |
1336 | #endif | |
1337 | } | |
1338 | ||
b0a69344 | 1339 | |
3df2b343 JL |
1340 | /* |
1341 | * \brief Returns the ending offset of the next line from a multi-line buffer. | |
1342 | * | |
1343 | * "Buffer" refers to a FTP response in a single buffer containing multiple lines. | |
1344 | * Here, "next line" is defined as terminating on | |
1345 | * - Newline character | |
1346 | * - Null character | |
1347 | * | |
1348 | * \param buffer Contains zero or more characters. | |
1349 | * \param len Size, in bytes, of buffer. | |
1350 | * | |
1351 | * \retval Offset from the start of buffer indicating the where the | |
1352 | * next "line ends". The characters between the input buffer and this | |
1353 | * value comprise the line. | |
1354 | * | |
86fabef0 | 1355 | * NULL is found first or a newline isn't found, then UINT16_MAX is returned. |
3df2b343 JL |
1356 | */ |
1357 | uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len) | |
1358 | { | |
86fabef0 JL |
1359 | if (!buffer || *buffer == '\0') { |
1360 | return UINT16_MAX; | |
1361 | } | |
3df2b343 | 1362 | |
86fabef0 JL |
1363 | char *c = strchr(buffer, '\n'); |
1364 | return c == NULL ? len : c - buffer + 1; | |
3df2b343 JL |
1365 | } |
1366 | ||
648bd5af | 1367 | void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb) |
b0a69344 EL |
1368 | { |
1369 | const FtpDataState *ftp_state = NULL; | |
1370 | if (f->alstate == NULL) | |
03de315b JL |
1371 | return; |
1372 | ||
b0a69344 | 1373 | ftp_state = (FtpDataState *)f->alstate; |
03de315b | 1374 | |
b0a69344 | 1375 | if (ftp_state->file_name) { |
03de315b | 1376 | jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len); |
b0a69344 EL |
1377 | } |
1378 | switch (ftp_state->command) { | |
1379 | case FTP_COMMAND_STOR: | |
03de315b | 1380 | JB_SET_STRING(jb, "command", "STOR"); |
b0a69344 EL |
1381 | break; |
1382 | case FTP_COMMAND_RETR: | |
03de315b | 1383 | JB_SET_STRING(jb, "command", "RETR"); |
b0a69344 EL |
1384 | break; |
1385 | default: | |
1386 | break; | |
1387 | } | |
b0a69344 | 1388 | } |
b0a69344 | 1389 | |
09ab032a | 1390 | /** |
03de315b | 1391 | * \brief Free memory allocated for global FTP parser state. |
09ab032a JL |
1392 | */ |
1393 | void FTPParserCleanup(void) | |
1394 | { | |
1395 | FTPFreeMpmState(); | |
1396 | } | |
1397 | ||
f2f9b832 PR |
1398 | /* UNITTESTS */ |
1399 | #ifdef UNITTESTS | |
1400 | ||
1401 | /** \test Send a get request in one chunk. */ | |
ab1200fb | 1402 | static int FTPParserTest01(void) |
8f1d7503 | 1403 | { |
f2f9b832 PR |
1404 | Flow f; |
1405 | uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n"; | |
1406 | uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */ | |
1407 | TcpSession ssn; | |
8dbf7a0d | 1408 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1409 | |
1410 | memset(&f, 0, sizeof(f)); | |
1411 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b | 1412 | |
f2f9b832 | 1413 | f.protoctx = (void *)&ssn; |
429c6388 | 1414 | f.proto = IPPROTO_TCP; |
5c01b409 | 1415 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1416 | |
1eeb9669 | 1417 | StreamTcpInitConfig(true); |
6a53ab9c | 1418 | |
675fa564 GL |
1419 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1420 | STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen); | |
b195ffbe | 1421 | FAIL_IF(r != 0); |
f2f9b832 | 1422 | |
06904c90 | 1423 | FtpState *ftp_state = f.alstate; |
b195ffbe SB |
1424 | FAIL_IF_NULL(ftp_state); |
1425 | FAIL_IF(ftp_state->command != FTP_COMMAND_PORT); | |
f2f9b832 | 1426 | |
b195ffbe | 1427 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1428 | StreamTcpFreeConfig(true); |
b195ffbe | 1429 | PASS; |
f2f9b832 PR |
1430 | } |
1431 | ||
1930b1f5 | 1432 | /** \test Send a split get request. */ |
ab1200fb | 1433 | static int FTPParserTest03(void) |
8f1d7503 | 1434 | { |
f2f9b832 PR |
1435 | Flow f; |
1436 | uint8_t ftpbuf1[] = "POR"; | |
1437 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1438 | uint8_t ftpbuf2[] = "T 192,168,1"; | |
1439 | uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ | |
1440 | uint8_t ftpbuf3[] = "1,1,10,20\r\n"; | |
1441 | uint32_t ftplen3 = sizeof(ftpbuf3) - 1; /* minus the \0 */ | |
1442 | TcpSession ssn; | |
8dbf7a0d | 1443 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1444 | |
1445 | memset(&f, 0, sizeof(f)); | |
1446 | memset(&ssn, 0, sizeof(ssn)); | |
48b3cb04 | 1447 | |
f2f9b832 | 1448 | f.protoctx = (void *)&ssn; |
429c6388 | 1449 | f.proto = IPPROTO_TCP; |
5c01b409 | 1450 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1451 | |
1eeb9669 | 1452 | StreamTcpInitConfig(true); |
6a53ab9c | 1453 | |
675fa564 GL |
1454 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1455 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1456 | ftplen1); | |
b195ffbe | 1457 | FAIL_IF(r != 0); |
f2f9b832 | 1458 | |
675fa564 GL |
1459 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, STREAM_TOSERVER, |
1460 | ftpbuf2, ftplen2); | |
b195ffbe | 1461 | FAIL_IF(r != 0); |
f2f9b832 | 1462 | |
675fa564 GL |
1463 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1464 | STREAM_TOSERVER | STREAM_EOF, ftpbuf3, ftplen3); | |
b195ffbe | 1465 | FAIL_IF(r != 0); |
f2f9b832 | 1466 | |
06904c90 | 1467 | FtpState *ftp_state = f.alstate; |
b195ffbe | 1468 | FAIL_IF_NULL(ftp_state); |
f2f9b832 | 1469 | |
b195ffbe | 1470 | FAIL_IF(ftp_state->command != FTP_COMMAND_PORT); |
f2f9b832 | 1471 | |
b195ffbe | 1472 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1473 | StreamTcpFreeConfig(true); |
b195ffbe | 1474 | PASS; |
f2f9b832 PR |
1475 | } |
1476 | ||
1477 | /** \test See how it deals with an incomplete request. */ | |
ab1200fb | 1478 | static int FTPParserTest06(void) |
8f1d7503 | 1479 | { |
f2f9b832 PR |
1480 | Flow f; |
1481 | uint8_t ftpbuf1[] = "PORT"; | |
1482 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1483 | TcpSession ssn; | |
8dbf7a0d | 1484 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1485 | |
1486 | memset(&f, 0, sizeof(f)); | |
1487 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b | 1488 | |
f2f9b832 | 1489 | f.protoctx = (void *)&ssn; |
429c6388 | 1490 | f.proto = IPPROTO_TCP; |
5c01b409 | 1491 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1492 | |
1eeb9669 | 1493 | StreamTcpInitConfig(true); |
6a53ab9c | 1494 | |
675fa564 GL |
1495 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1496 | STREAM_TOSERVER | STREAM_START | STREAM_EOF, | |
1497 | ftpbuf1, | |
1498 | ftplen1); | |
b195ffbe | 1499 | FAIL_IF(r != 0); |
f2f9b832 | 1500 | |
06904c90 | 1501 | FtpState *ftp_state = f.alstate; |
b195ffbe | 1502 | FAIL_IF_NULL(ftp_state); |
f2f9b832 | 1503 | |
b195ffbe | 1504 | FAIL_IF(ftp_state->command != FTP_COMMAND_UNKNOWN); |
f2f9b832 | 1505 | |
b195ffbe | 1506 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1507 | StreamTcpFreeConfig(true); |
b195ffbe | 1508 | PASS; |
f2f9b832 PR |
1509 | } |
1510 | ||
1511 | /** \test See how it deals with an incomplete request in multiple chunks. */ | |
ab1200fb | 1512 | static int FTPParserTest07(void) |
8f1d7503 | 1513 | { |
f2f9b832 PR |
1514 | Flow f; |
1515 | uint8_t ftpbuf1[] = "PO"; | |
1516 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1517 | uint8_t ftpbuf2[] = "RT\r\n"; | |
1518 | uint32_t ftplen2 = sizeof(ftpbuf2) - 1; /* minus the \0 */ | |
1519 | TcpSession ssn; | |
8dbf7a0d | 1520 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1521 | |
1522 | memset(&f, 0, sizeof(f)); | |
1523 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b | 1524 | |
f2f9b832 | 1525 | f.protoctx = (void *)&ssn; |
429c6388 | 1526 | f.proto = IPPROTO_TCP; |
5c01b409 | 1527 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1528 | |
1eeb9669 | 1529 | StreamTcpInitConfig(true); |
6a53ab9c | 1530 | |
675fa564 GL |
1531 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1532 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1533 | ftplen1); | |
b195ffbe | 1534 | FAIL_IF(r != 0); |
f2f9b832 | 1535 | |
675fa564 GL |
1536 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1537 | STREAM_TOSERVER | STREAM_EOF, ftpbuf2, ftplen2); | |
b195ffbe | 1538 | FAIL_IF(r != 0); |
f2f9b832 | 1539 | |
06904c90 | 1540 | FtpState *ftp_state = f.alstate; |
b195ffbe | 1541 | FAIL_IF_NULL(ftp_state); |
f2f9b832 | 1542 | |
b195ffbe | 1543 | FAIL_IF(ftp_state->command != FTP_COMMAND_PORT); |
f2f9b832 | 1544 | |
b195ffbe | 1545 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1546 | StreamTcpFreeConfig(true); |
b195ffbe | 1547 | PASS; |
f2f9b832 PR |
1548 | } |
1549 | ||
1550 | /** \test Test case where chunks are smaller than the delim length and the | |
1551 | * last chunk is supposed to match the delim. */ | |
ab1200fb | 1552 | static int FTPParserTest10(void) |
8f1d7503 | 1553 | { |
f2f9b832 PR |
1554 | Flow f; |
1555 | uint8_t ftpbuf1[] = "PORT 1,2,3,4,5,6\r\n"; | |
1556 | uint32_t ftplen1 = sizeof(ftpbuf1) - 1; /* minus the \0 */ | |
1557 | TcpSession ssn; | |
8dbf7a0d | 1558 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
f2f9b832 PR |
1559 | int r = 0; |
1560 | memset(&f, 0, sizeof(f)); | |
1561 | memset(&ssn, 0, sizeof(ssn)); | |
cc76aa4b | 1562 | |
f2f9b832 | 1563 | f.protoctx = (void *)&ssn; |
429c6388 | 1564 | f.proto = IPPROTO_TCP; |
5c01b409 | 1565 | f.alproto = ALPROTO_FTP; |
f2f9b832 | 1566 | |
1eeb9669 | 1567 | StreamTcpInitConfig(true); |
6a53ab9c | 1568 | |
f2f9b832 PR |
1569 | uint32_t u; |
1570 | for (u = 0; u < ftplen1; u++) { | |
1571 | uint8_t flags = 0; | |
1572 | ||
1573 | if (u == 0) flags = STREAM_TOSERVER|STREAM_START; | |
1574 | else if (u == (ftplen1 - 1)) flags = STREAM_TOSERVER|STREAM_EOF; | |
1575 | else flags = STREAM_TOSERVER; | |
1576 | ||
675fa564 GL |
1577 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, flags, |
1578 | &ftpbuf1[u], 1); | |
b195ffbe | 1579 | FAIL_IF(r != 0); |
f2f9b832 PR |
1580 | } |
1581 | ||
06904c90 | 1582 | FtpState *ftp_state = f.alstate; |
b195ffbe | 1583 | FAIL_IF_NULL(ftp_state); |
f2f9b832 | 1584 | |
b195ffbe | 1585 | FAIL_IF(ftp_state->command != FTP_COMMAND_PORT); |
f2f9b832 | 1586 | |
b195ffbe | 1587 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1588 | StreamTcpFreeConfig(true); |
b195ffbe | 1589 | PASS; |
354074ba JL |
1590 | } |
1591 | ||
1592 | /** \test Supply RETR without a filename */ | |
1593 | static int FTPParserTest11(void) | |
1594 | { | |
354074ba JL |
1595 | Flow f; |
1596 | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; | |
1597 | uint8_t ftpbuf2[] = "RETR\r\n"; | |
1598 | uint8_t ftpbuf3[] = "227 OK\r\n"; | |
1599 | TcpSession ssn; | |
1600 | ||
1601 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); | |
1602 | ||
1603 | memset(&f, 0, sizeof(f)); | |
1604 | memset(&ssn, 0, sizeof(ssn)); | |
1605 | ||
354074ba JL |
1606 | f.protoctx = (void *)&ssn; |
1607 | f.proto = IPPROTO_TCP; | |
1608 | f.alproto = ALPROTO_FTP; | |
1609 | ||
1eeb9669 | 1610 | StreamTcpInitConfig(true); |
354074ba | 1611 | |
354074ba JL |
1612 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1613 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1614 | sizeof(ftpbuf1) - 1); | |
b195ffbe | 1615 | FAIL_IF(r != 0); |
354074ba JL |
1616 | |
1617 | /* Response */ | |
354074ba JL |
1618 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1619 | STREAM_TOCLIENT, | |
1620 | ftpbuf3, | |
1621 | sizeof(ftpbuf3) - 1); | |
b195ffbe | 1622 | FAIL_IF(r != 0); |
354074ba | 1623 | |
354074ba JL |
1624 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1625 | STREAM_TOSERVER, ftpbuf2, | |
1626 | sizeof(ftpbuf2) - 1); | |
b195ffbe | 1627 | FAIL_IF(r == 0); |
354074ba JL |
1628 | |
1629 | FtpState *ftp_state = f.alstate; | |
b195ffbe | 1630 | FAIL_IF_NULL(ftp_state); |
354074ba | 1631 | |
b195ffbe | 1632 | FAIL_IF(ftp_state->command != FTP_COMMAND_RETR); |
354074ba | 1633 | |
b195ffbe | 1634 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1635 | StreamTcpFreeConfig(true); |
b195ffbe | 1636 | PASS; |
354074ba JL |
1637 | } |
1638 | ||
1639 | /** \test Supply STOR without a filename */ | |
1640 | static int FTPParserTest12(void) | |
1641 | { | |
354074ba JL |
1642 | Flow f; |
1643 | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; | |
1644 | uint8_t ftpbuf2[] = "STOR\r\n"; | |
1645 | uint8_t ftpbuf3[] = "227 OK\r\n"; | |
1646 | TcpSession ssn; | |
1647 | ||
1648 | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); | |
1649 | ||
1650 | memset(&f, 0, sizeof(f)); | |
1651 | memset(&ssn, 0, sizeof(ssn)); | |
1652 | ||
354074ba JL |
1653 | f.protoctx = (void *)&ssn; |
1654 | f.proto = IPPROTO_TCP; | |
1655 | f.alproto = ALPROTO_FTP; | |
1656 | ||
1eeb9669 | 1657 | StreamTcpInitConfig(true); |
354074ba | 1658 | |
354074ba JL |
1659 | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1660 | STREAM_TOSERVER | STREAM_START, ftpbuf1, | |
1661 | sizeof(ftpbuf1) - 1); | |
b195ffbe | 1662 | FAIL_IF(r != 0); |
354074ba JL |
1663 | |
1664 | /* Response */ | |
354074ba JL |
1665 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1666 | STREAM_TOCLIENT, | |
1667 | ftpbuf3, | |
1668 | sizeof(ftpbuf3) - 1); | |
b195ffbe | 1669 | FAIL_IF(r != 0); |
354074ba | 1670 | |
354074ba JL |
1671 | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1672 | STREAM_TOSERVER, ftpbuf2, | |
1673 | sizeof(ftpbuf2) - 1); | |
b195ffbe | 1674 | FAIL_IF(r == 0); |
354074ba JL |
1675 | |
1676 | FtpState *ftp_state = f.alstate; | |
b195ffbe | 1677 | FAIL_IF_NULL(ftp_state); |
354074ba | 1678 | |
b195ffbe | 1679 | FAIL_IF(ftp_state->command != FTP_COMMAND_STOR); |
354074ba | 1680 | |
b195ffbe | 1681 | AppLayerParserThreadCtxFree(alp_tctx); |
1eeb9669 | 1682 | StreamTcpFreeConfig(true); |
b195ffbe | 1683 | PASS; |
f2f9b832 PR |
1684 | } |
1685 | #endif /* UNITTESTS */ | |
1686 | ||
8f1d7503 KS |
1687 | void FTPParserRegisterTests(void) |
1688 | { | |
f2f9b832 | 1689 | #ifdef UNITTESTS |
796dd522 JI |
1690 | UtRegisterTest("FTPParserTest01", FTPParserTest01); |
1691 | UtRegisterTest("FTPParserTest03", FTPParserTest03); | |
1692 | UtRegisterTest("FTPParserTest06", FTPParserTest06); | |
1693 | UtRegisterTest("FTPParserTest07", FTPParserTest07); | |
1694 | UtRegisterTest("FTPParserTest10", FTPParserTest10); | |
354074ba JL |
1695 | UtRegisterTest("FTPParserTest11", FTPParserTest11); |
1696 | UtRegisterTest("FTPParserTest12", FTPParserTest12); | |
f2f9b832 PR |
1697 | #endif /* UNITTESTS */ |
1698 | } | |
1699 |