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