/src/suricata7/src/app-layer-ftp.c
Line | Count | Source |
1 | | /* Copyright (C) 2007-2022 Open Information Security Foundation |
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. |
6 | | * |
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 | | /** |
19 | | * \file |
20 | | * |
21 | | * \author Pablo Rincon Crespo <pablo.rincon.crespo@gmail.com> |
22 | | * \author Eric Leblond <eric@regit.org> |
23 | | * \author Jeff Lucovsky <jeff@lucovsky.org> |
24 | | * |
25 | | * App Layer Parser for FTP |
26 | | */ |
27 | | |
28 | | #include "suricata-common.h" |
29 | | #include "app-layer-ftp.h" |
30 | | #include "app-layer.h" |
31 | | #include "app-layer-parser.h" |
32 | | #include "app-layer-expectation.h" |
33 | | #include "app-layer-detect-proto.h" |
34 | | |
35 | | #include "rust.h" |
36 | | |
37 | | #include "util-misc.h" |
38 | | #include "util-mpm.h" |
39 | | #include "util-validate.h" |
40 | | |
41 | | typedef struct FTPThreadCtx_ { |
42 | | MpmThreadCtx *ftp_mpm_thread_ctx; |
43 | | PrefilterRuleStore *pmq; |
44 | | } FTPThreadCtx; |
45 | | |
46 | 1.57M | #define FTP_MPM mpm_default_matcher |
47 | | |
48 | | static MpmCtx *ftp_mpm_ctx = NULL; |
49 | | |
50 | | // clang-format off |
51 | | const FtpCommand FtpCommands[FTP_COMMAND_MAX + 1] = { |
52 | | /* Parsed and handled */ |
53 | | { "PORT", FTP_COMMAND_PORT, 4 }, |
54 | | { "EPRT", FTP_COMMAND_EPRT, 4 }, |
55 | | { "AUTH TLS", FTP_COMMAND_AUTH_TLS, 8 }, |
56 | | { "PASV", FTP_COMMAND_PASV, 4 }, |
57 | | { "RETR", FTP_COMMAND_RETR, 4 }, |
58 | | { "EPSV", FTP_COMMAND_EPSV, 4 }, |
59 | | { "STOR", FTP_COMMAND_STOR, 4 }, |
60 | | |
61 | | /* Parsed, but not handled */ |
62 | | { "ABOR", FTP_COMMAND_ABOR, 4 }, |
63 | | { "ACCT", FTP_COMMAND_ACCT, 4 }, |
64 | | { "ALLO", FTP_COMMAND_ALLO, 4 }, |
65 | | { "APPE", FTP_COMMAND_APPE, 4 }, |
66 | | { "CDUP", FTP_COMMAND_CDUP, 4 }, |
67 | | { "CHMOD", FTP_COMMAND_CHMOD, 5 }, |
68 | | { "CWD", FTP_COMMAND_CWD, 3 }, |
69 | | { "DELE", FTP_COMMAND_DELE, 4 }, |
70 | | { "HELP", FTP_COMMAND_HELP, 4 }, |
71 | | { "IDLE", FTP_COMMAND_IDLE, 4 }, |
72 | | { "LIST", FTP_COMMAND_LIST, 4 }, |
73 | | { "MAIL", FTP_COMMAND_MAIL, 4 }, |
74 | | { "MDTM", FTP_COMMAND_MDTM, 4 }, |
75 | | { "MKD", FTP_COMMAND_MKD, 3 }, |
76 | | { "MLFL", FTP_COMMAND_MLFL, 4 }, |
77 | | { "MODE", FTP_COMMAND_MODE, 4 }, |
78 | | { "MRCP", FTP_COMMAND_MRCP, 4 }, |
79 | | { "MRSQ", FTP_COMMAND_MRSQ, 4 }, |
80 | | { "MSAM", FTP_COMMAND_MSAM, 4 }, |
81 | | { "MSND", FTP_COMMAND_MSND, 4 }, |
82 | | { "MSOM", FTP_COMMAND_MSOM, 4 }, |
83 | | { "NLST", FTP_COMMAND_NLST, 4 }, |
84 | | { "NOOP", FTP_COMMAND_NOOP, 4 }, |
85 | | { "PASS", FTP_COMMAND_PASS, 4 }, |
86 | | { "PWD", FTP_COMMAND_PWD, 3 }, |
87 | | { "QUIT", FTP_COMMAND_QUIT, 4 }, |
88 | | { "REIN", FTP_COMMAND_REIN, 4 }, |
89 | | { "REST", FTP_COMMAND_REST, 4 }, |
90 | | { "RMD", FTP_COMMAND_RMD, 3 }, |
91 | | { "RNFR", FTP_COMMAND_RNFR, 4 }, |
92 | | { "RNTO", FTP_COMMAND_RNTO, 4 }, |
93 | | { "SITE", FTP_COMMAND_SITE, 4 }, |
94 | | { "SIZE", FTP_COMMAND_SIZE, 4 }, |
95 | | { "SMNT", FTP_COMMAND_SMNT, 4 }, |
96 | | { "STAT", FTP_COMMAND_STAT, 4 }, |
97 | | { "STOU", FTP_COMMAND_STOU, 4 }, |
98 | | { "STRU", FTP_COMMAND_STRU, 4 }, |
99 | | { "SYST", FTP_COMMAND_SYST, 4 }, |
100 | | { "TYPE", FTP_COMMAND_TYPE, 4 }, |
101 | | { "UMASK", FTP_COMMAND_UMASK, 5 }, |
102 | | { "USER", FTP_COMMAND_USER, 4 }, |
103 | | { NULL, FTP_COMMAND_UNKNOWN, 0 } |
104 | | }; |
105 | | // clang-format on |
106 | | |
107 | | uint64_t ftp_config_memcap = 0; |
108 | | uint32_t ftp_config_maxtx = 1024; |
109 | | uint32_t ftp_max_line_len = 4096; |
110 | | |
111 | | SC_ATOMIC_DECLARE(uint64_t, ftp_memuse); |
112 | | SC_ATOMIC_DECLARE(uint64_t, ftp_memcap); |
113 | | |
114 | | static FTPTransaction *FTPGetOldestTx(const FtpState *, FTPTransaction *); |
115 | | |
116 | | static void FTPParseMemcap(void) |
117 | 34 | { |
118 | 34 | const char *conf_val; |
119 | | |
120 | | /** set config values for memcap, prealloc and hash_size */ |
121 | 34 | if ((ConfGet("app-layer.protocols.ftp.memcap", &conf_val)) == 1) |
122 | 0 | { |
123 | 0 | if (ParseSizeStringU64(conf_val, &ftp_config_memcap) < 0) { |
124 | 0 | SCLogError("Error parsing ftp.memcap " |
125 | 0 | "from conf file - %s. Killing engine", |
126 | 0 | conf_val); |
127 | 0 | exit(EXIT_FAILURE); |
128 | 0 | } |
129 | 0 | SCLogInfo("FTP memcap: %"PRIu64, ftp_config_memcap); |
130 | 34 | } else { |
131 | | /* default to unlimited */ |
132 | 34 | ftp_config_memcap = 0; |
133 | 34 | } |
134 | | |
135 | 34 | SC_ATOMIC_INIT(ftp_memuse); |
136 | 34 | SC_ATOMIC_INIT(ftp_memcap); |
137 | | |
138 | 34 | if ((ConfGet("app-layer.protocols.ftp.max-tx", &conf_val)) == 1) { |
139 | 0 | if (ParseSizeStringU32(conf_val, &ftp_config_maxtx) < 0) { |
140 | 0 | SCLogError("Error parsing ftp.max-tx " |
141 | 0 | "from conf file - %s.", |
142 | 0 | conf_val); |
143 | 0 | } |
144 | 0 | SCLogInfo("FTP max tx: %" PRIu32, ftp_config_maxtx); |
145 | 0 | } |
146 | | |
147 | 34 | if ((ConfGet("app-layer.protocols.ftp.max-line-length", &conf_val)) == 1) { |
148 | 0 | if (ParseSizeStringU32(conf_val, &ftp_max_line_len) < 0) { |
149 | 0 | SCLogError("Error parsing ftp.max-line-length from conf file - %s.", conf_val); |
150 | 0 | } |
151 | 0 | SCLogConfig("FTP max line length: %" PRIu32, ftp_max_line_len); |
152 | 0 | } |
153 | 34 | } |
154 | | |
155 | | static void FTPIncrMemuse(uint64_t size) |
156 | 1.31M | { |
157 | 1.31M | (void) SC_ATOMIC_ADD(ftp_memuse, size); |
158 | 1.31M | return; |
159 | 1.31M | } |
160 | | |
161 | | static void FTPDecrMemuse(uint64_t size) |
162 | 1.31M | { |
163 | 1.31M | (void) SC_ATOMIC_SUB(ftp_memuse, size); |
164 | 1.31M | return; |
165 | 1.31M | } |
166 | | |
167 | | uint64_t FTPMemuseGlobalCounter(void) |
168 | 0 | { |
169 | 0 | uint64_t tmpval = SC_ATOMIC_GET(ftp_memuse); |
170 | 0 | return tmpval; |
171 | 0 | } |
172 | | |
173 | | uint64_t FTPMemcapGlobalCounter(void) |
174 | 0 | { |
175 | 0 | uint64_t tmpval = SC_ATOMIC_GET(ftp_memcap); |
176 | 0 | return tmpval; |
177 | 0 | } |
178 | | |
179 | | /** |
180 | | * \brief Check if alloc'ing "size" would mean we're over memcap |
181 | | * |
182 | | * \retval 1 if in bounds |
183 | | * \retval 0 if not in bounds |
184 | | */ |
185 | | static int FTPCheckMemcap(uint64_t size) |
186 | 3.19M | { |
187 | 3.19M | if (ftp_config_memcap == 0 || size + SC_ATOMIC_GET(ftp_memuse) <= ftp_config_memcap) |
188 | 3.19M | return 1; |
189 | 0 | (void) SC_ATOMIC_ADD(ftp_memcap, 1); |
190 | 0 | return 0; |
191 | 3.19M | } |
192 | | |
193 | | static void *FTPCalloc(size_t n, size_t size) |
194 | 3.17M | { |
195 | 3.17M | if (FTPCheckMemcap((uint32_t)(n * size)) == 0) { |
196 | 0 | sc_errno = SC_ELIMIT; |
197 | 0 | return NULL; |
198 | 0 | } |
199 | | |
200 | 3.17M | void *ptr = SCCalloc(n, size); |
201 | | |
202 | 3.17M | if (unlikely(ptr == NULL)) { |
203 | 0 | sc_errno = SC_ENOMEM; |
204 | 0 | return NULL; |
205 | 0 | } |
206 | | |
207 | 3.17M | FTPIncrMemuse((uint64_t)(n * size)); |
208 | 3.17M | return ptr; |
209 | 3.17M | } |
210 | | |
211 | | static void *FTPRealloc(void *ptr, size_t orig_size, size_t size) |
212 | 12.5k | { |
213 | 12.5k | void *rptr = NULL; |
214 | | |
215 | 12.5k | if (FTPCheckMemcap((uint32_t)(size - orig_size)) == 0) { |
216 | 0 | sc_errno = SC_ELIMIT; |
217 | 0 | return NULL; |
218 | 0 | } |
219 | | |
220 | 12.5k | rptr = SCRealloc(ptr, size); |
221 | 12.5k | if (rptr == NULL) { |
222 | 0 | sc_errno = SC_ENOMEM; |
223 | 0 | return NULL; |
224 | 0 | } |
225 | | |
226 | 12.5k | if (size > orig_size) { |
227 | 8.44k | FTPIncrMemuse(size - orig_size); |
228 | 8.44k | } else { |
229 | 4.08k | FTPDecrMemuse(orig_size - size); |
230 | 4.08k | } |
231 | | |
232 | 12.5k | return rptr; |
233 | 12.5k | } |
234 | | |
235 | | static void FTPFree(void *ptr, size_t size) |
236 | 3.18M | { |
237 | 3.18M | SCFree(ptr); |
238 | | |
239 | 3.18M | FTPDecrMemuse((uint64_t)size); |
240 | 3.18M | } |
241 | | |
242 | | static FTPString *FTPStringAlloc(void) |
243 | 297k | { |
244 | 297k | return FTPCalloc(1, sizeof(FTPString)); |
245 | 297k | } |
246 | | |
247 | | static void FTPStringFree(FTPString *str) |
248 | 297k | { |
249 | 297k | if (str->str) { |
250 | 297k | FTPFree(str->str, str->len); |
251 | 297k | } |
252 | | |
253 | 297k | FTPFree(str, sizeof(FTPString)); |
254 | 297k | } |
255 | | |
256 | | static void *FTPLocalStorageAlloc(void) |
257 | 80 | { |
258 | | /* needed by the mpm */ |
259 | 80 | FTPThreadCtx *td = SCCalloc(1, sizeof(*td)); |
260 | 80 | if (td == NULL) { |
261 | 0 | exit(EXIT_FAILURE); |
262 | 0 | } |
263 | | |
264 | 80 | td->pmq = SCCalloc(1, sizeof(*td->pmq)); |
265 | 80 | if (td->pmq == NULL) { |
266 | 0 | exit(EXIT_FAILURE); |
267 | 0 | } |
268 | 80 | PmqSetup(td->pmq); |
269 | | |
270 | 80 | td->ftp_mpm_thread_ctx = SCCalloc(1, sizeof(MpmThreadCtx)); |
271 | 80 | if (unlikely(td->ftp_mpm_thread_ctx == NULL)) { |
272 | 0 | exit(EXIT_FAILURE); |
273 | 0 | } |
274 | 80 | MpmInitThreadCtx(td->ftp_mpm_thread_ctx, FTP_MPM); |
275 | 80 | return td; |
276 | 80 | } |
277 | | |
278 | | static void FTPLocalStorageFree(void *ptr) |
279 | 0 | { |
280 | 0 | FTPThreadCtx *td = ptr; |
281 | 0 | if (td != NULL) { |
282 | 0 | if (td->pmq != NULL) { |
283 | 0 | PmqFree(td->pmq); |
284 | 0 | SCFree(td->pmq); |
285 | 0 | } |
286 | |
|
287 | 0 | if (td->ftp_mpm_thread_ctx != NULL) { |
288 | 0 | mpm_table[FTP_MPM].DestroyThreadCtx(ftp_mpm_ctx, td->ftp_mpm_thread_ctx); |
289 | 0 | SCFree(td->ftp_mpm_thread_ctx); |
290 | 0 | } |
291 | |
|
292 | 0 | SCFree(td); |
293 | 0 | } |
294 | |
|
295 | 0 | return; |
296 | 0 | } |
297 | | static FTPTransaction *FTPTransactionCreate(FtpState *state) |
298 | 367k | { |
299 | 367k | SCEnter(); |
300 | 367k | FTPTransaction *firsttx = TAILQ_FIRST(&state->tx_list); |
301 | 367k | if (firsttx && state->tx_cnt - firsttx->tx_id > ftp_config_maxtx) { |
302 | | // FTP does not set events yet... |
303 | 4 | return NULL; |
304 | 4 | } |
305 | 367k | FTPTransaction *tx = FTPCalloc(1, sizeof(*tx)); |
306 | 367k | if (tx == NULL) { |
307 | 0 | return NULL; |
308 | 0 | } |
309 | | |
310 | 367k | TAILQ_INSERT_TAIL(&state->tx_list, tx, next); |
311 | 367k | tx->tx_id = state->tx_cnt++; |
312 | | |
313 | 367k | TAILQ_INIT(&tx->response_list); |
314 | | |
315 | 367k | SCLogDebug("new transaction %p (state tx cnt %"PRIu64")", tx, state->tx_cnt); |
316 | 367k | return tx; |
317 | 367k | } |
318 | | |
319 | | static void FTPTransactionFree(FTPTransaction *tx) |
320 | 367k | { |
321 | 367k | SCEnter(); |
322 | | |
323 | 367k | if (tx->tx_data.de_state != NULL) { |
324 | 760 | DetectEngineStateFree(tx->tx_data.de_state); |
325 | 760 | } |
326 | | |
327 | 367k | if (tx->request) { |
328 | 340k | FTPFree(tx->request, tx->request_length); |
329 | 340k | } |
330 | | |
331 | 367k | FTPString *str = NULL; |
332 | 664k | while ((str = TAILQ_FIRST(&tx->response_list))) { |
333 | 297k | TAILQ_REMOVE(&tx->response_list, str, next); |
334 | 297k | FTPStringFree(str); |
335 | 297k | } |
336 | | |
337 | 367k | if (tx->tx_data.events) { |
338 | 2.37k | AppLayerDecoderEventsFreeEvents(&tx->tx_data.events); |
339 | 2.37k | } |
340 | | |
341 | 367k | FTPFree(tx, sizeof(*tx)); |
342 | 367k | } |
343 | | |
344 | | typedef struct FtpInput_ { |
345 | | const uint8_t *buf; |
346 | | int32_t consumed; |
347 | | int32_t len; |
348 | | int32_t orig_len; |
349 | | } FtpInput; |
350 | | |
351 | | static AppLayerResult FTPGetLineForDirection( |
352 | | FtpState *state, FtpLineState *line, FtpInput *input, bool *current_line_truncated) |
353 | 5.11M | { |
354 | 5.11M | SCEnter(); |
355 | | |
356 | | /* we have run out of input */ |
357 | 5.11M | if (input->len <= 0) |
358 | 202k | return APP_LAYER_ERROR; |
359 | | |
360 | 4.90M | const uint8_t *lf_idx = memchr(input->buf + input->consumed, 0x0a, input->len); |
361 | | |
362 | 4.90M | if (lf_idx == NULL) { |
363 | 161k | if (!(*current_line_truncated) && (uint32_t)input->len >= ftp_max_line_len) { |
364 | 3.07k | *current_line_truncated = true; |
365 | 3.07k | line->buf = input->buf; |
366 | 3.07k | line->len = ftp_max_line_len; |
367 | 3.07k | line->delim_len = 0; |
368 | 3.07k | input->len = 0; |
369 | 3.07k | SCReturnStruct(APP_LAYER_OK); |
370 | 3.07k | } |
371 | 161k | SCReturnStruct(APP_LAYER_INCOMPLETE(input->consumed, input->len + 1)); |
372 | 4.74M | } else if (*current_line_truncated) { |
373 | | // Whatever came in with first LF should also get discarded |
374 | 2.17k | *current_line_truncated = false; |
375 | 2.17k | line->len = 0; |
376 | 2.17k | line->delim_len = 0; |
377 | 2.17k | input->len = 0; |
378 | 2.17k | SCReturnStruct(APP_LAYER_ERROR); |
379 | 4.74M | } else { |
380 | | // There could be one chunk of command data that has LF but post the line limit |
381 | | // e.g. input_len = 5077 |
382 | | // lf_idx = 5010 |
383 | | // max_line_len = 4096 |
384 | 4.74M | uint32_t o_consumed = input->consumed; |
385 | 4.74M | input->consumed = lf_idx - input->buf + 1; |
386 | 4.74M | line->len = input->consumed - o_consumed; |
387 | 4.74M | input->len -= line->len; |
388 | 4.74M | line->lf_found = true; |
389 | 4.74M | DEBUG_VALIDATE_BUG_ON((input->consumed + input->len) != input->orig_len); |
390 | 4.74M | line->buf = input->buf + o_consumed; |
391 | 4.74M | if (line->len >= ftp_max_line_len) { |
392 | 8.25k | *current_line_truncated = true; |
393 | 8.25k | line->len = ftp_max_line_len; |
394 | 8.25k | SCReturnStruct(APP_LAYER_OK); |
395 | 8.25k | } |
396 | 4.73M | if (input->consumed >= 2 && input->buf[input->consumed - 2] == 0x0D) { |
397 | 207k | line->delim_len = 2; |
398 | 207k | line->len -= 2; |
399 | 4.52M | } else { |
400 | 4.52M | line->delim_len = 1; |
401 | 4.52M | line->len -= 1; |
402 | 4.52M | } |
403 | 4.73M | SCReturnStruct(APP_LAYER_OK); |
404 | 4.74M | } |
405 | 4.90M | } |
406 | | |
407 | | /** |
408 | | * \brief This function is called to determine and set which command is being |
409 | | * transferred to the ftp server |
410 | | * \param thread context |
411 | | * \param input input line of the command |
412 | | * \param len of the command |
413 | | * \param cmd_descriptor when the command has been parsed |
414 | | * |
415 | | * \retval 1 when the command is parsed, 0 otherwise |
416 | | */ |
417 | | static int FTPParseRequestCommand( |
418 | | FTPThreadCtx *td, FtpLineState *line, const FtpCommand **cmd_descriptor) |
419 | 1.57M | { |
420 | 1.57M | SCEnter(); |
421 | | |
422 | | /* I don't like this pmq reset here. We'll devise a method later, that |
423 | | * should make the use of the mpm very efficient */ |
424 | 1.57M | PmqReset(td->pmq); |
425 | 1.57M | int mpm_cnt = mpm_table[FTP_MPM].Search( |
426 | 1.57M | ftp_mpm_ctx, td->ftp_mpm_thread_ctx, td->pmq, line->buf, line->len); |
427 | 1.57M | if (mpm_cnt) { |
428 | 340k | *cmd_descriptor = &FtpCommands[td->pmq->rule_id_array[0]]; |
429 | 340k | SCReturnInt(1); |
430 | 340k | } |
431 | | |
432 | 1.23M | *cmd_descriptor = NULL; |
433 | 1.23M | SCReturnInt(0); |
434 | 1.57M | } |
435 | | |
436 | | struct FtpTransferCmd { |
437 | | /** Need to look like a ExpectationData so DFree must |
438 | | * be first field . */ |
439 | | void (*DFree)(void *); |
440 | | uint64_t flow_id; |
441 | | uint8_t *file_name; |
442 | | uint16_t file_len; |
443 | | uint8_t direction; /**< direction in which the data will flow */ |
444 | | FtpRequestCommand cmd; |
445 | | }; |
446 | | |
447 | | static void FtpTransferCmdFree(void *data) |
448 | 8.24k | { |
449 | 8.24k | struct FtpTransferCmd *cmd = (struct FtpTransferCmd *) data; |
450 | 8.24k | if (cmd == NULL) |
451 | 0 | return; |
452 | 8.24k | if (cmd->file_name) { |
453 | 7.98k | FTPFree(cmd->file_name, cmd->file_len + 1); |
454 | 7.98k | } |
455 | 8.24k | FTPFree(cmd, sizeof(struct FtpTransferCmd)); |
456 | 8.24k | } |
457 | | |
458 | | static uint32_t CopyCommandLine(uint8_t **dest, FtpLineState *line) |
459 | 1.16M | { |
460 | 1.16M | if (likely(line->len)) { |
461 | 1.16M | uint8_t *where = FTPCalloc(line->len + 1, sizeof(char)); |
462 | 1.16M | if (unlikely(where == NULL)) { |
463 | 0 | return 0; |
464 | 0 | } |
465 | 1.16M | memcpy(where, line->buf, line->len); |
466 | | |
467 | | /* Remove trailing newlines/carriage returns */ |
468 | 1.83M | while (line->len && isspace((unsigned char)where[line->len - 1])) { |
469 | 669k | line->len--; |
470 | 669k | } |
471 | | |
472 | 1.16M | where[line->len] = '\0'; |
473 | 1.16M | *dest = where; |
474 | 1.16M | } |
475 | | /* either 0 or actual */ |
476 | 1.16M | return line->len ? line->len + 1 : 0; |
477 | 1.16M | } |
478 | | |
479 | | #include "util-print.h" |
480 | | |
481 | | /** |
482 | | * \brief This function is called to retrieve a ftp request |
483 | | * \param ftp_state the ftp state structure for the parser |
484 | | * |
485 | | * \retval APP_LAYER_OK when input was process successfully |
486 | | * \retval APP_LAYER_ERROR when a unrecoverable error was encountered |
487 | | */ |
488 | | static AppLayerResult FTPParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
489 | | StreamSlice stream_slice, void *local_data) |
490 | 172k | { |
491 | 172k | FTPThreadCtx *thread_data = local_data; |
492 | | |
493 | 172k | SCEnter(); |
494 | | /* PrintRawDataFp(stdout, input,input_len); */ |
495 | | |
496 | 172k | FtpState *state = (FtpState *)ftp_state; |
497 | 172k | void *ptmp; |
498 | | |
499 | 172k | const uint8_t *input = StreamSliceGetData(&stream_slice); |
500 | 172k | uint32_t input_len = StreamSliceGetDataLen(&stream_slice); |
501 | | |
502 | 172k | if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) { |
503 | 438 | SCReturnStruct(APP_LAYER_OK); |
504 | 171k | } else if (input == NULL || input_len == 0) { |
505 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
506 | 0 | } |
507 | | |
508 | 171k | FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 }; |
509 | 171k | FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false }; |
510 | | |
511 | 171k | uint8_t direction = STREAM_TOSERVER; |
512 | 171k | AppLayerResult res; |
513 | 3.03M | while (1) { |
514 | 3.03M | res = FTPGetLineForDirection(state, &line, &ftpi, &state->current_line_truncated_ts); |
515 | 3.03M | if (res.status == 1) { |
516 | 85.7k | return res; |
517 | 2.94M | } else if (res.status == -1) { |
518 | 84.3k | break; |
519 | 84.3k | } |
520 | 2.86M | const FtpCommand *cmd_descriptor; |
521 | | |
522 | 2.86M | if (!FTPParseRequestCommand(thread_data, &line, &cmd_descriptor)) { |
523 | 1.99M | state->command = FTP_COMMAND_UNKNOWN; |
524 | 1.99M | continue; |
525 | 1.99M | } |
526 | | |
527 | 872k | state->command = cmd_descriptor->command; |
528 | 872k | FTPTransaction *tx = FTPTransactionCreate(state); |
529 | 872k | if (unlikely(tx == NULL)) |
530 | 4 | SCReturnStruct(APP_LAYER_ERROR); |
531 | 872k | tx->tx_data.updated_ts = true; |
532 | 872k | state->curr_tx = tx; |
533 | | |
534 | 872k | tx->command_descriptor = cmd_descriptor; |
535 | 872k | tx->request_length = CopyCommandLine(&tx->request, &line); |
536 | 872k | tx->request_truncated = state->current_line_truncated_ts; |
537 | | |
538 | 872k | if (line.lf_found) { |
539 | 871k | state->current_line_truncated_ts = false; |
540 | 871k | } |
541 | 872k | if (tx->request_truncated) { |
542 | 2.36k | AppLayerDecoderEventsSetEventRaw(&tx->tx_data.events, FtpEventRequestCommandTooLong); |
543 | 2.36k | } |
544 | | |
545 | | /* change direction (default to server) so expectation will handle |
546 | | * the correct message when expectation will match. |
547 | | * For ftp active mode, data connection direction is opposite to |
548 | | * control direction. |
549 | | */ |
550 | 872k | if ((state->active && state->command == FTP_COMMAND_STOR) || |
551 | 871k | (!state->active && state->command == FTP_COMMAND_RETR)) { |
552 | 5.80k | direction = STREAM_TOCLIENT; |
553 | 5.80k | } |
554 | | |
555 | 872k | switch (state->command) { |
556 | 11.3k | case FTP_COMMAND_EPRT: |
557 | | // fallthrough |
558 | 34.6k | case FTP_COMMAND_PORT: |
559 | 34.6k | if (line.len + 1 > state->port_line_size) { |
560 | | /* Allocate an extra byte for a NULL terminator */ |
561 | 10.8k | ptmp = FTPRealloc(state->port_line, state->port_line_size, line.len); |
562 | 10.8k | if (ptmp == NULL) { |
563 | 0 | if (state->port_line) { |
564 | 0 | FTPFree(state->port_line, state->port_line_size); |
565 | 0 | state->port_line = NULL; |
566 | 0 | state->port_line_size = 0; |
567 | 0 | state->port_line_len = 0; |
568 | 0 | } |
569 | 0 | SCReturnStruct(APP_LAYER_OK); |
570 | 0 | } |
571 | 10.8k | state->port_line = ptmp; |
572 | 10.8k | state->port_line_size = line.len; |
573 | 10.8k | } |
574 | 34.6k | memcpy(state->port_line, line.buf, line.len); |
575 | 34.6k | state->port_line_len = line.len; |
576 | 34.6k | break; |
577 | 6.52k | case FTP_COMMAND_RETR: |
578 | | // fallthrough |
579 | 8.56k | case FTP_COMMAND_STOR: { |
580 | | /* Ensure that there is a negotiated dyn port and a file |
581 | | * name -- need more than 5 chars: cmd [4], space, <filename> |
582 | | */ |
583 | 8.56k | if (state->dyn_port == 0 || line.len < 6) { |
584 | 273 | SCReturnStruct(APP_LAYER_ERROR); |
585 | 273 | } |
586 | 8.28k | struct FtpTransferCmd *data = FTPCalloc(1, sizeof(struct FtpTransferCmd)); |
587 | 8.28k | if (data == NULL) |
588 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
589 | 8.28k | data->DFree = FtpTransferCmdFree; |
590 | | /* |
591 | | * Min size has been checked in FTPParseRequestCommand |
592 | | * SC_FILENAME_MAX includes the null |
593 | | */ |
594 | 8.28k | uint32_t file_name_len = MIN(SC_FILENAME_MAX - 1, line.len - 5); |
595 | | #if SC_FILENAME_MAX > UINT16_MAX |
596 | | #error SC_FILENAME_MAX is greater than UINT16_MAX |
597 | | #endif |
598 | 8.28k | data->file_name = FTPCalloc(file_name_len + 1, sizeof(char)); |
599 | 8.28k | if (data->file_name == NULL) { |
600 | 0 | FtpTransferCmdFree(data); |
601 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
602 | 0 | } |
603 | 8.28k | data->file_name[file_name_len] = 0; |
604 | 8.28k | data->file_len = (uint16_t)file_name_len; |
605 | 8.28k | memcpy(data->file_name, line.buf + 5, file_name_len); |
606 | 8.28k | data->cmd = state->command; |
607 | 8.28k | data->flow_id = FlowGetId(f); |
608 | 8.28k | data->direction = direction; |
609 | 8.28k | int ret = AppLayerExpectationCreate(f, direction, |
610 | 8.28k | 0, state->dyn_port, ALPROTO_FTPDATA, data); |
611 | 8.28k | if (ret == -1) { |
612 | 0 | FtpTransferCmdFree(data); |
613 | 0 | SCLogDebug("No expectation created."); |
614 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
615 | 8.28k | } else { |
616 | 8.28k | SCLogDebug("Expectation created [direction: %s, dynamic port %"PRIu16"].", |
617 | 8.28k | state->active ? "to server" : "to client", |
618 | 8.28k | state->dyn_port); |
619 | 8.28k | } |
620 | | |
621 | | /* reset the dyn port to avoid duplicate */ |
622 | 8.28k | state->dyn_port = 0; |
623 | | /* reset active/passive indicator */ |
624 | 8.28k | state->active = false; |
625 | 8.28k | } break; |
626 | 829k | default: |
627 | 829k | break; |
628 | 872k | } |
629 | 872k | if (line.len >= ftp_max_line_len) { |
630 | 1.26k | ftpi.consumed = ftpi.len + 1; |
631 | 1.26k | break; |
632 | 1.26k | } |
633 | 872k | } |
634 | | |
635 | 171k | SCReturnStruct(APP_LAYER_OK); |
636 | 171k | } |
637 | | |
638 | | static int FTPParsePassiveResponse(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
639 | 236k | { |
640 | 236k | uint16_t dyn_port = rs_ftp_pasv_response(input, input_len); |
641 | 236k | if (dyn_port == 0) { |
642 | 195k | return -1; |
643 | 195k | } |
644 | 40.2k | SCLogDebug("FTP passive mode (v4): dynamic port %"PRIu16"", dyn_port); |
645 | 40.2k | state->active = false; |
646 | 40.2k | state->dyn_port = dyn_port; |
647 | 40.2k | state->curr_tx->dyn_port = dyn_port; |
648 | 40.2k | state->curr_tx->active = false; |
649 | | |
650 | 40.2k | return 0; |
651 | 236k | } |
652 | | |
653 | | static int FTPParsePassiveResponseV6(Flow *f, FtpState *state, const uint8_t *input, uint32_t input_len) |
654 | 110k | { |
655 | 110k | uint16_t dyn_port = rs_ftp_epsv_response(input, input_len); |
656 | 110k | if (dyn_port == 0) { |
657 | 35.6k | return -1; |
658 | 35.6k | } |
659 | 75.0k | SCLogDebug("FTP passive mode (v6): dynamic port %"PRIu16"", dyn_port); |
660 | 75.0k | state->active = false; |
661 | 75.0k | state->dyn_port = dyn_port; |
662 | 75.0k | state->curr_tx->dyn_port = dyn_port; |
663 | 75.0k | state->curr_tx->active = false; |
664 | 75.0k | return 0; |
665 | 110k | } |
666 | | |
667 | | /** |
668 | | * \brief Handle preliminary replies -- keep tx open |
669 | | * \retval bool True for a positive preliminary reply; false otherwise |
670 | | * |
671 | | * 1yz Positive Preliminary reply |
672 | | * |
673 | | * The requested action is being initiated; expect another |
674 | | * reply before proceeding with a new command |
675 | | */ |
676 | | static inline bool FTPIsPPR(const uint8_t *input, uint32_t input_len) |
677 | 1.38M | { |
678 | 1.38M | return input_len >= 4 && isdigit(input[0]) && input[0] == '1' && |
679 | 1.38M | isdigit(input[1]) && isdigit(input[2]) && isspace(input[3]); |
680 | 1.38M | } |
681 | | |
682 | | /** |
683 | | * \brief This function is called to retrieve a ftp response |
684 | | * \param ftp_state the ftp state structure for the parser |
685 | | * \param input input line of the command |
686 | | * \param input_len length of the request |
687 | | * \param output the resulting output |
688 | | * |
689 | | * \retval 1 when the command is parsed, 0 otherwise |
690 | | */ |
691 | | static AppLayerResult FTPParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
692 | | StreamSlice stream_slice, void *local_data) |
693 | 78.2k | { |
694 | 78.2k | FtpState *state = (FtpState *)ftp_state; |
695 | | |
696 | 78.2k | const uint8_t *input = StreamSliceGetData(&stream_slice); |
697 | 78.2k | uint32_t input_len = StreamSliceGetDataLen(&stream_slice); |
698 | | |
699 | 78.2k | if (unlikely(input_len == 0)) { |
700 | 151 | SCReturnStruct(APP_LAYER_OK); |
701 | 151 | } |
702 | 78.1k | FtpInput ftpi = { .buf = input, .len = input_len, .orig_len = input_len, .consumed = 0 }; |
703 | 78.1k | FtpLineState line = { .buf = NULL, .len = 0, .delim_len = 0, .lf_found = false }; |
704 | | |
705 | 78.1k | FTPTransaction *lasttx = TAILQ_FIRST(&state->tx_list); |
706 | 78.1k | AppLayerResult res; |
707 | 814k | while (1) { |
708 | 814k | res = FTPGetLineForDirection(state, &line, &ftpi, &state->current_line_truncated_tc); |
709 | 814k | if (res.status == 1) { |
710 | 36.7k | return res; |
711 | 777k | } else if (res.status == -1) { |
712 | 40.4k | break; |
713 | 40.4k | } |
714 | 737k | FTPTransaction *tx = FTPGetOldestTx(state, lasttx); |
715 | 737k | if (tx == NULL) { |
716 | 27.2k | tx = FTPTransactionCreate(state); |
717 | 27.2k | } |
718 | 737k | if (unlikely(tx == NULL)) { |
719 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
720 | 0 | } |
721 | 737k | lasttx = tx; |
722 | 737k | tx->tx_data.updated_tc = true; |
723 | 737k | if (state->command == FTP_COMMAND_UNKNOWN || tx->command_descriptor == NULL) { |
724 | | /* unknown */ |
725 | 218k | tx->command_descriptor = &FtpCommands[FTP_COMMAND_MAX - 1]; |
726 | 218k | } |
727 | | |
728 | 737k | state->curr_tx = tx; |
729 | 737k | uint16_t dyn_port; |
730 | 737k | switch (state->command) { |
731 | 0 | case FTP_COMMAND_AUTH_TLS: |
732 | 0 | if (line.len >= 4 && SCMemcmp("234 ", line.buf, 4) == 0) { |
733 | 0 | AppLayerRequestProtocolTLSUpgrade(f); |
734 | 0 | } |
735 | 0 | break; |
736 | | |
737 | 199k | case FTP_COMMAND_EPRT: |
738 | 199k | dyn_port = rs_ftp_active_eprt(state->port_line, state->port_line_len); |
739 | 199k | if (dyn_port == 0) { |
740 | 197k | goto tx_complete; |
741 | 197k | } |
742 | 1.65k | state->dyn_port = dyn_port; |
743 | 1.65k | state->active = true; |
744 | 1.65k | tx->dyn_port = dyn_port; |
745 | 1.65k | tx->active = true; |
746 | 1.65k | SCLogDebug("FTP active mode (v6): dynamic port %" PRIu16 "", dyn_port); |
747 | 1.65k | break; |
748 | | |
749 | 144k | case FTP_COMMAND_PORT: |
750 | 144k | dyn_port = rs_ftp_active_port(state->port_line, state->port_line_len); |
751 | 144k | if (dyn_port == 0) { |
752 | 138k | goto tx_complete; |
753 | 138k | } |
754 | 6.17k | state->dyn_port = dyn_port; |
755 | 6.17k | state->active = true; |
756 | 6.17k | tx->dyn_port = state->dyn_port; |
757 | 6.17k | tx->active = true; |
758 | 6.17k | SCLogDebug("FTP active mode (v4): dynamic port %" PRIu16 "", dyn_port); |
759 | 6.17k | break; |
760 | | |
761 | 145k | case FTP_COMMAND_PASV: |
762 | 145k | if (line.len >= 4 && SCMemcmp("227 ", line.buf, 4) == 0) { |
763 | 103k | FTPParsePassiveResponse(f, ftp_state, line.buf, line.len); |
764 | 103k | } |
765 | 145k | break; |
766 | | |
767 | 21.1k | case FTP_COMMAND_EPSV: |
768 | 21.1k | if (line.len >= 4 && SCMemcmp("229 ", line.buf, 4) == 0) { |
769 | 11.2k | FTPParsePassiveResponseV6(f, ftp_state, line.buf, line.len); |
770 | 11.2k | } |
771 | 21.1k | break; |
772 | 226k | default: |
773 | 226k | break; |
774 | 737k | } |
775 | | |
776 | 401k | if (likely(line.len)) { |
777 | 297k | FTPString *response = FTPStringAlloc(); |
778 | 297k | if (likely(response)) { |
779 | 297k | response->len = CopyCommandLine(&response->str, &line); |
780 | 297k | response->truncated = state->current_line_truncated_tc; |
781 | 297k | if (response->truncated) { |
782 | 5.07k | AppLayerDecoderEventsSetEventRaw( |
783 | 5.07k | &tx->tx_data.events, FtpEventResponseCommandTooLong); |
784 | 5.07k | } |
785 | 297k | if (line.lf_found) { |
786 | 296k | state->current_line_truncated_tc = false; |
787 | 296k | } |
788 | 297k | TAILQ_INSERT_TAIL(&tx->response_list, response, next); |
789 | 297k | } |
790 | 297k | } |
791 | | |
792 | | /* Handle preliminary replies -- keep tx open */ |
793 | 401k | if (FTPIsPPR(line.buf, line.len)) { |
794 | 11.7k | continue; |
795 | 11.7k | } |
796 | 725k | tx_complete: |
797 | 725k | tx->done = true; |
798 | | |
799 | 725k | if (line.len >= ftp_max_line_len) { |
800 | 925 | ftpi.consumed = ftpi.len + 1; |
801 | 925 | break; |
802 | 925 | } |
803 | 725k | } |
804 | | |
805 | 78.1k | SCReturnStruct(APP_LAYER_OK); |
806 | 78.1k | } |
807 | | |
808 | | |
809 | | #ifdef DEBUG |
810 | | static SCMutex ftp_state_mem_lock = SCMUTEX_INITIALIZER; |
811 | | static uint64_t ftp_state_memuse = 0; |
812 | | static uint64_t ftp_state_memcnt = 0; |
813 | | #endif |
814 | | |
815 | | static void *FTPStateAlloc(void *orig_state, AppProto proto_orig) |
816 | 11.6k | { |
817 | 11.6k | void *s = FTPCalloc(1, sizeof(FtpState)); |
818 | 11.6k | if (unlikely(s == NULL)) |
819 | 0 | return NULL; |
820 | | |
821 | 11.6k | FtpState *ftp_state = (FtpState *) s; |
822 | 11.6k | TAILQ_INIT(&ftp_state->tx_list); |
823 | | |
824 | | #ifdef DEBUG |
825 | | SCMutexLock(&ftp_state_mem_lock); |
826 | | ftp_state_memcnt++; |
827 | | ftp_state_memuse+=sizeof(FtpState); |
828 | | SCMutexUnlock(&ftp_state_mem_lock); |
829 | | #endif |
830 | 11.6k | return s; |
831 | 11.6k | } |
832 | | |
833 | | static void FTPStateFree(void *s) |
834 | 5.44k | { |
835 | 5.44k | FtpState *fstate = (FtpState *) s; |
836 | 5.44k | if (fstate->port_line != NULL) |
837 | 3.09k | FTPFree(fstate->port_line, fstate->port_line_size); |
838 | | |
839 | 5.44k | FTPTransaction *tx = NULL; |
840 | 36.3k | while ((tx = TAILQ_FIRST(&fstate->tx_list))) { |
841 | 30.9k | TAILQ_REMOVE(&fstate->tx_list, tx, next); |
842 | 30.9k | SCLogDebug("[%s] state %p id %" PRIu64 ", Freeing %d bytes at %p", |
843 | 30.9k | tx->command_descriptor->command_name, s, tx->tx_id, tx->request_length, |
844 | 30.9k | tx->request); |
845 | 30.9k | FTPTransactionFree(tx); |
846 | 30.9k | } |
847 | | |
848 | 5.44k | FTPFree(s, sizeof(FtpState)); |
849 | | #ifdef DEBUG |
850 | | SCMutexLock(&ftp_state_mem_lock); |
851 | | ftp_state_memcnt--; |
852 | | ftp_state_memuse-=sizeof(FtpState); |
853 | | SCMutexUnlock(&ftp_state_mem_lock); |
854 | | #endif |
855 | 5.44k | } |
856 | | |
857 | | /** |
858 | | * \brief This function returns the oldest open transaction; if none |
859 | | * are open, then the oldest transaction is returned |
860 | | * \param ftp_state the ftp state structure for the parser |
861 | | * \param starttx the ftp transaction where to start looking |
862 | | * |
863 | | * \retval transaction pointer when a transaction was found; NULL otherwise. |
864 | | */ |
865 | | static FTPTransaction *FTPGetOldestTx(const FtpState *ftp_state, FTPTransaction *starttx) |
866 | 1.88M | { |
867 | 1.88M | if (unlikely(!ftp_state)) { |
868 | 0 | SCLogDebug("NULL state object; no transactions available"); |
869 | 0 | return NULL; |
870 | 0 | } |
871 | 1.88M | FTPTransaction *tx = starttx; |
872 | 1.88M | FTPTransaction *lasttx = NULL; |
873 | 3.61M | while(tx != NULL) { |
874 | | /* Return oldest open tx */ |
875 | 2.25M | if (!tx->done) { |
876 | 523k | SCLogDebug("Returning tx %p id %"PRIu64, tx, tx->tx_id); |
877 | 523k | return tx; |
878 | 523k | } |
879 | | /* save for the end */ |
880 | 1.73M | lasttx = tx; |
881 | 1.73M | tx = TAILQ_NEXT(tx, next); |
882 | 1.73M | } |
883 | | /* All tx are closed; return last element */ |
884 | 1.36M | if (lasttx) |
885 | 1.28M | SCLogDebug("Returning OLDEST tx %p id %"PRIu64, lasttx, lasttx->tx_id); |
886 | 1.36M | return lasttx; |
887 | 1.88M | } |
888 | | |
889 | | static void *FTPGetTx(void *state, uint64_t tx_id) |
890 | 2.34k | { |
891 | 2.34k | FtpState *ftp_state = (FtpState *)state; |
892 | 2.34k | if (ftp_state) { |
893 | 2.34k | FTPTransaction *tx = NULL; |
894 | | |
895 | 2.34k | if (ftp_state->curr_tx == NULL) |
896 | 257 | return NULL; |
897 | 2.08k | if (ftp_state->curr_tx->tx_id == tx_id) |
898 | 1.10k | return ftp_state->curr_tx; |
899 | | |
900 | 2.08k | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { |
901 | 1.26k | if (tx->tx_id == tx_id) |
902 | 802 | return tx; |
903 | 1.26k | } |
904 | 981 | } |
905 | 179 | return NULL; |
906 | 2.34k | } |
907 | | |
908 | | static AppLayerTxData *FTPGetTxData(void *vtx) |
909 | 4.30M | { |
910 | 4.30M | FTPTransaction *tx = (FTPTransaction *)vtx; |
911 | 4.30M | return &tx->tx_data; |
912 | 4.30M | } |
913 | | |
914 | | static AppLayerStateData *FTPGetStateData(void *vstate) |
915 | 4.43k | { |
916 | 4.43k | FtpState *s = (FtpState *)vstate; |
917 | 4.43k | return &s->state_data; |
918 | 4.43k | } |
919 | | |
920 | | static void FTPStateTransactionFree(void *state, uint64_t tx_id) |
921 | 750k | { |
922 | 750k | FtpState *ftp_state = state; |
923 | 750k | FTPTransaction *tx = NULL; |
924 | 750k | TAILQ_FOREACH(tx, &ftp_state->tx_list, next) { |
925 | 750k | if (tx_id < tx->tx_id) |
926 | 0 | break; |
927 | 750k | else if (tx_id > tx->tx_id) |
928 | 753 | continue; |
929 | | |
930 | 750k | if (tx == ftp_state->curr_tx) |
931 | 122k | ftp_state->curr_tx = NULL; |
932 | 750k | TAILQ_REMOVE(&ftp_state->tx_list, tx, next); |
933 | 750k | FTPTransactionFree(tx); |
934 | 750k | break; |
935 | 750k | } |
936 | 750k | } |
937 | | |
938 | | static uint64_t FTPGetTxCnt(void *state) |
939 | 1.46M | { |
940 | 1.46M | uint64_t cnt = 0; |
941 | 1.46M | FtpState *ftp_state = state; |
942 | 1.46M | if (ftp_state) { |
943 | 1.46M | cnt = ftp_state->tx_cnt; |
944 | 1.46M | } |
945 | 1.46M | SCLogDebug("returning state %p %"PRIu64, state, cnt); |
946 | 1.46M | return cnt; |
947 | 1.46M | } |
948 | | |
949 | | static int FTPGetAlstateProgress(void *vtx, uint8_t direction) |
950 | 2.13M | { |
951 | 2.13M | SCLogDebug("tx %p", vtx); |
952 | 2.13M | FTPTransaction *tx = vtx; |
953 | | |
954 | 2.13M | if (!tx->done) { |
955 | 1.33M | if (direction == STREAM_TOSERVER && tx->command_descriptor->command == FTP_COMMAND_PORT) { |
956 | 20.4k | return FTP_STATE_PORT_DONE; |
957 | 20.4k | } |
958 | 1.30M | return FTP_STATE_IN_PROGRESS; |
959 | 1.33M | } |
960 | | |
961 | 807k | return FTP_STATE_FINISHED; |
962 | 2.13M | } |
963 | | |
964 | | |
965 | | static int FTPRegisterPatternsForProtocolDetection(void) |
966 | 34 | { |
967 | 34 | if (AppLayerProtoDetectPMRegisterPatternCI( |
968 | 34 | IPPROTO_TCP, ALPROTO_FTP, "220 (", 5, 0, STREAM_TOCLIENT) < 0) { |
969 | 0 | return -1; |
970 | 0 | } |
971 | 34 | if (AppLayerProtoDetectPMRegisterPatternCI( |
972 | 34 | IPPROTO_TCP, ALPROTO_FTP, "FEAT", 4, 0, STREAM_TOSERVER) < 0) { |
973 | 0 | return -1; |
974 | 0 | } |
975 | 34 | if (AppLayerProtoDetectPMRegisterPatternCI( |
976 | 34 | IPPROTO_TCP, ALPROTO_FTP, "USER ", 5, 0, STREAM_TOSERVER) < 0) { |
977 | 0 | return -1; |
978 | 0 | } |
979 | 34 | if (AppLayerProtoDetectPMRegisterPatternCI( |
980 | 34 | IPPROTO_TCP, ALPROTO_FTP, "PASS ", 5, 0, STREAM_TOSERVER) < 0) { |
981 | 0 | return -1; |
982 | 0 | } |
983 | 34 | if (AppLayerProtoDetectPMRegisterPatternCI( |
984 | 34 | IPPROTO_TCP, ALPROTO_FTP, "PORT ", 5, 0, STREAM_TOSERVER) < 0) { |
985 | 0 | return -1; |
986 | 0 | } |
987 | | |
988 | 34 | return 0; |
989 | 34 | } |
990 | | |
991 | | |
992 | | static StreamingBufferConfig sbcfg = STREAMING_BUFFER_CONFIG_INITIALIZER; |
993 | | |
994 | | /** |
995 | | * \brief This function is called to retrieve a ftp request |
996 | | * \param ftp_state the ftp state structure for the parser |
997 | | * \param output the resulting output |
998 | | * |
999 | | * \retval 1 when the command is parsed, 0 otherwise |
1000 | | */ |
1001 | | static AppLayerResult FTPDataParse(Flow *f, FtpDataState *ftpdata_state, |
1002 | | AppLayerParserState *pstate, StreamSlice stream_slice, void *local_data, uint8_t direction) |
1003 | 980 | { |
1004 | 980 | const uint8_t *input = StreamSliceGetData(&stream_slice); |
1005 | 980 | uint32_t input_len = StreamSliceGetDataLen(&stream_slice); |
1006 | 980 | const bool eof = (direction & STREAM_TOSERVER) |
1007 | 980 | ? AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) != 0 |
1008 | 980 | : AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) != 0; |
1009 | | |
1010 | 980 | SCTxDataUpdateFileFlags(&ftpdata_state->tx_data, ftpdata_state->state_data.file_flags); |
1011 | 980 | if (ftpdata_state->tx_data.file_tx == 0) |
1012 | 103 | ftpdata_state->tx_data.file_tx = direction & (STREAM_TOSERVER | STREAM_TOCLIENT); |
1013 | 980 | if (direction & STREAM_TOSERVER) { |
1014 | 813 | ftpdata_state->tx_data.updated_ts = true; |
1015 | 813 | } else { |
1016 | 167 | ftpdata_state->tx_data.updated_tc = true; |
1017 | 167 | } |
1018 | | /* we depend on detection engine for file pruning */ |
1019 | 980 | const uint16_t flags = FileFlowFlagsToFlags(ftpdata_state->tx_data.file_flags, direction); |
1020 | 980 | int ret = 0; |
1021 | | |
1022 | 980 | SCLogDebug("FTP-DATA input_len %u flags %04x dir %d/%s EOF %s", input_len, flags, direction, |
1023 | 980 | (direction & STREAM_TOSERVER) ? "toserver" : "toclient", eof ? "true" : "false"); |
1024 | | |
1025 | 980 | SCLogDebug("FTP-DATA flags %04x dir %d", flags, direction); |
1026 | 980 | if (input_len && ftpdata_state->files == NULL) { |
1027 | 103 | struct FtpTransferCmd *data = |
1028 | 103 | (struct FtpTransferCmd *)FlowGetStorageById(f, AppLayerExpectationGetFlowId()); |
1029 | 103 | if (data == NULL) { |
1030 | 32 | SCReturnStruct(APP_LAYER_ERROR); |
1031 | 32 | } |
1032 | | |
1033 | | /* we shouldn't get data in the wrong dir. Don't set things up for this dir */ |
1034 | 71 | if ((direction & data->direction) == 0) { |
1035 | | // TODO set event for data in wrong direction |
1036 | 0 | SCLogDebug("input %u not for our direction (%s): %s/%s", input_len, |
1037 | 0 | (direction & STREAM_TOSERVER) ? "toserver" : "toclient", |
1038 | 0 | data->cmd == FTP_COMMAND_STOR ? "STOR" : "RETR", |
1039 | 0 | (data->direction & STREAM_TOSERVER) ? "toserver" : "toclient"); |
1040 | 0 | SCReturnStruct(APP_LAYER_OK); |
1041 | 0 | } |
1042 | | |
1043 | 71 | ftpdata_state->files = FileContainerAlloc(); |
1044 | 71 | if (ftpdata_state->files == NULL) { |
1045 | 0 | FlowFreeStorageById(f, AppLayerExpectationGetFlowId()); |
1046 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
1047 | 0 | } |
1048 | | |
1049 | 71 | ftpdata_state->file_name = data->file_name; |
1050 | 71 | ftpdata_state->file_len = data->file_len; |
1051 | 71 | data->file_name = NULL; |
1052 | 71 | data->file_len = 0; |
1053 | 71 | f->parent_id = data->flow_id; |
1054 | 71 | ftpdata_state->command = data->cmd; |
1055 | 71 | switch (data->cmd) { |
1056 | 23 | case FTP_COMMAND_STOR: |
1057 | 23 | ftpdata_state->direction = data->direction; |
1058 | 23 | SCLogDebug("STOR data to %s", |
1059 | 23 | (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient"); |
1060 | 23 | break; |
1061 | 48 | case FTP_COMMAND_RETR: |
1062 | 48 | ftpdata_state->direction = data->direction; |
1063 | 48 | SCLogDebug("RETR data to %s", |
1064 | 48 | (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient"); |
1065 | 48 | break; |
1066 | 0 | default: |
1067 | 0 | break; |
1068 | 71 | } |
1069 | | |
1070 | | /* open with fixed track_id 0 as we can have just one |
1071 | | * file per ftp-data flow. */ |
1072 | 71 | if (FileOpenFileWithId(ftpdata_state->files, &sbcfg, |
1073 | 71 | 0ULL, (uint8_t *) ftpdata_state->file_name, |
1074 | 71 | ftpdata_state->file_len, |
1075 | 71 | input, input_len, flags) != 0) { |
1076 | 0 | SCLogDebug("Can't open file"); |
1077 | 0 | ret = -1; |
1078 | 0 | } |
1079 | 71 | FlowFreeStorageById(f, AppLayerExpectationGetFlowId()); |
1080 | 71 | ftpdata_state->tx_data.files_opened = 1; |
1081 | 877 | } else { |
1082 | 877 | if (ftpdata_state->state == FTPDATA_STATE_FINISHED) { |
1083 | 4 | SCLogDebug("state is already finished"); |
1084 | 4 | DEBUG_VALIDATE_BUG_ON(input_len); // data after state finished is a bug. |
1085 | 4 | SCReturnStruct(APP_LAYER_OK); |
1086 | 4 | } |
1087 | 873 | if ((direction & ftpdata_state->direction) == 0) { |
1088 | 9 | if (input_len) { |
1089 | | // TODO set event for data in wrong direction |
1090 | 0 | } |
1091 | 9 | SCLogDebug("input %u not for us (%s): %s/%s", input_len, |
1092 | 9 | (direction & STREAM_TOSERVER) ? "toserver" : "toclient", |
1093 | 9 | ftpdata_state->command == FTP_COMMAND_STOR ? "STOR" : "RETR", |
1094 | 9 | (ftpdata_state->direction & STREAM_TOSERVER) ? "toserver" : "toclient"); |
1095 | 9 | SCReturnStruct(APP_LAYER_OK); |
1096 | 9 | } |
1097 | 864 | if (input_len != 0) { |
1098 | 854 | ret = FileAppendData(ftpdata_state->files, &sbcfg, input, input_len); |
1099 | 854 | if (ret == -2) { |
1100 | 0 | ret = 0; |
1101 | 0 | SCLogDebug("FileAppendData() - file no longer being extracted"); |
1102 | 0 | goto out; |
1103 | 854 | } else if (ret < 0) { |
1104 | 0 | SCLogDebug("FileAppendData() failed: %d", ret); |
1105 | 0 | ret = -2; |
1106 | 0 | goto out; |
1107 | 0 | } |
1108 | 854 | } |
1109 | 864 | } |
1110 | | |
1111 | 935 | BUG_ON((direction & ftpdata_state->direction) == 0); // should be unreachable |
1112 | 935 | if (eof) { |
1113 | 14 | ret = FileCloseFile(ftpdata_state->files, &sbcfg, NULL, 0, flags); |
1114 | 14 | ftpdata_state->state = FTPDATA_STATE_FINISHED; |
1115 | 14 | SCLogDebug("closed because of eof: state now FTPDATA_STATE_FINISHED"); |
1116 | 14 | } |
1117 | 935 | out: |
1118 | 935 | if (ret < 0) { |
1119 | 0 | SCReturnStruct(APP_LAYER_ERROR); |
1120 | 0 | } |
1121 | 935 | SCReturnStruct(APP_LAYER_OK); |
1122 | 935 | } |
1123 | | |
1124 | | static AppLayerResult FTPDataParseRequest(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
1125 | | StreamSlice stream_slice, void *local_data) |
1126 | 2.75k | { |
1127 | 2.75k | return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOSERVER); |
1128 | 2.75k | } |
1129 | | |
1130 | | static AppLayerResult FTPDataParseResponse(Flow *f, void *ftp_state, AppLayerParserState *pstate, |
1131 | | StreamSlice stream_slice, void *local_data) |
1132 | 1.65k | { |
1133 | 1.65k | return FTPDataParse(f, ftp_state, pstate, stream_slice, local_data, STREAM_TOCLIENT); |
1134 | 1.65k | } |
1135 | | |
1136 | | #ifdef DEBUG |
1137 | | static SCMutex ftpdata_state_mem_lock = SCMUTEX_INITIALIZER; |
1138 | | static uint64_t ftpdata_state_memuse = 0; |
1139 | | static uint64_t ftpdata_state_memcnt = 0; |
1140 | | #endif |
1141 | | |
1142 | | static void *FTPDataStateAlloc(void *orig_state, AppProto proto_orig) |
1143 | 321 | { |
1144 | 321 | void *s = FTPCalloc(1, sizeof(FtpDataState)); |
1145 | 321 | if (unlikely(s == NULL)) |
1146 | 0 | return NULL; |
1147 | | |
1148 | 321 | FtpDataState *state = (FtpDataState *) s; |
1149 | 321 | state->state = FTPDATA_STATE_IN_PROGRESS; |
1150 | | |
1151 | | #ifdef DEBUG |
1152 | | SCMutexLock(&ftpdata_state_mem_lock); |
1153 | | ftpdata_state_memcnt++; |
1154 | | ftpdata_state_memuse+=sizeof(FtpDataState); |
1155 | | SCMutexUnlock(&ftpdata_state_mem_lock); |
1156 | | #endif |
1157 | 321 | return s; |
1158 | 321 | } |
1159 | | |
1160 | | static void FTPDataStateFree(void *s) |
1161 | 103 | { |
1162 | 103 | FtpDataState *fstate = (FtpDataState *) s; |
1163 | | |
1164 | 103 | if (fstate->tx_data.de_state != NULL) { |
1165 | 22 | DetectEngineStateFree(fstate->tx_data.de_state); |
1166 | 22 | } |
1167 | 103 | if (fstate->file_name != NULL) { |
1168 | 71 | FTPFree(fstate->file_name, fstate->file_len + 1); |
1169 | 71 | } |
1170 | | |
1171 | 103 | FileContainerFree(fstate->files, &sbcfg); |
1172 | | |
1173 | 103 | FTPFree(s, sizeof(FtpDataState)); |
1174 | | #ifdef DEBUG |
1175 | | SCMutexLock(&ftpdata_state_mem_lock); |
1176 | | ftpdata_state_memcnt--; |
1177 | | ftpdata_state_memuse-=sizeof(FtpDataState); |
1178 | | SCMutexUnlock(&ftpdata_state_mem_lock); |
1179 | | #endif |
1180 | 103 | } |
1181 | | |
1182 | | static AppLayerTxData *FTPDataGetTxData(void *vtx) |
1183 | 21.3k | { |
1184 | 21.3k | FtpDataState *ftp_state = (FtpDataState *)vtx; |
1185 | 21.3k | return &ftp_state->tx_data; |
1186 | 21.3k | } |
1187 | | |
1188 | | static AppLayerStateData *FTPDataGetStateData(void *vstate) |
1189 | 263 | { |
1190 | 263 | FtpDataState *ftp_state = (FtpDataState *)vstate; |
1191 | 263 | return &ftp_state->state_data; |
1192 | 263 | } |
1193 | | |
1194 | | static void FTPDataStateTransactionFree(void *state, uint64_t tx_id) |
1195 | 98 | { |
1196 | | /* do nothing */ |
1197 | 98 | } |
1198 | | |
1199 | | static void *FTPDataGetTx(void *state, uint64_t tx_id) |
1200 | 16.3k | { |
1201 | 16.3k | FtpDataState *ftp_state = (FtpDataState *)state; |
1202 | 16.3k | return ftp_state; |
1203 | 16.3k | } |
1204 | | |
1205 | | static uint64_t FTPDataGetTxCnt(void *state) |
1206 | 30.1k | { |
1207 | | /* ftp-data is single tx */ |
1208 | 30.1k | return 1; |
1209 | 30.1k | } |
1210 | | |
1211 | | static int FTPDataGetAlstateProgress(void *tx, uint8_t direction) |
1212 | 26.5k | { |
1213 | 26.5k | FtpDataState *ftpdata_state = (FtpDataState *)tx; |
1214 | 26.5k | if (direction == ftpdata_state->direction) |
1215 | 15.6k | return ftpdata_state->state; |
1216 | 10.8k | else |
1217 | 10.8k | return FTPDATA_STATE_FINISHED; |
1218 | 26.5k | } |
1219 | | |
1220 | | static AppLayerGetFileState FTPDataStateGetTxFiles(void *_state, void *tx, uint8_t direction) |
1221 | 16.1k | { |
1222 | 16.1k | FtpDataState *ftpdata_state = (FtpDataState *)tx; |
1223 | 16.1k | AppLayerGetFileState files = { .fc = NULL, .cfg = &sbcfg }; |
1224 | | |
1225 | 16.1k | if (direction == ftpdata_state->direction) |
1226 | 9.45k | files.fc = ftpdata_state->files; |
1227 | | |
1228 | 16.1k | return files; |
1229 | 16.1k | } |
1230 | | |
1231 | | static void FTPSetMpmState(void) |
1232 | 34 | { |
1233 | 34 | ftp_mpm_ctx = SCMalloc(sizeof(MpmCtx)); |
1234 | 34 | if (unlikely(ftp_mpm_ctx == NULL)) { |
1235 | 0 | exit(EXIT_FAILURE); |
1236 | 0 | } |
1237 | 34 | memset(ftp_mpm_ctx, 0, sizeof(MpmCtx)); |
1238 | 34 | MpmInitCtx(ftp_mpm_ctx, FTP_MPM); |
1239 | | |
1240 | 34 | uint32_t i = 0; |
1241 | 1.70k | for (i = 0; i < sizeof(FtpCommands)/sizeof(FtpCommand) - 1; i++) { |
1242 | 1.66k | const FtpCommand *cmd = &FtpCommands[i]; |
1243 | 1.66k | if (cmd->command_length == 0) |
1244 | 34 | continue; |
1245 | | |
1246 | 1.63k | MpmAddPatternCI(ftp_mpm_ctx, |
1247 | 1.63k | (uint8_t *)cmd->command_name, |
1248 | 1.63k | cmd->command_length, |
1249 | 1.63k | 0 /* defunct */, 0 /* defunct */, |
1250 | 1.63k | i /* id */, i /* rule id */ , 0 /* no flags */); |
1251 | 1.63k | } |
1252 | | |
1253 | 34 | mpm_table[FTP_MPM].Prepare(ftp_mpm_ctx); |
1254 | | |
1255 | 34 | } |
1256 | | |
1257 | | static void FTPFreeMpmState(void) |
1258 | 0 | { |
1259 | 0 | if (ftp_mpm_ctx != NULL) { |
1260 | 0 | mpm_table[FTP_MPM].DestroyCtx(ftp_mpm_ctx); |
1261 | 0 | SCFree(ftp_mpm_ctx); |
1262 | 0 | ftp_mpm_ctx = NULL; |
1263 | 0 | } |
1264 | 0 | } |
1265 | | |
1266 | | /** \brief FTP tx iterator, specialized for its linked list |
1267 | | * |
1268 | | * \retval txptr or NULL if no more txs in list |
1269 | | */ |
1270 | | static AppLayerGetTxIterTuple FTPGetTxIterator(const uint8_t ipproto, const AppProto alproto, |
1271 | | void *alstate, uint64_t min_tx_id, uint64_t max_tx_id, AppLayerGetTxIterState *state) |
1272 | 4.28M | { |
1273 | 4.28M | FtpState *ftp_state = (FtpState *)alstate; |
1274 | 4.28M | AppLayerGetTxIterTuple no_tuple = { NULL, 0, false }; |
1275 | 4.28M | if (ftp_state) { |
1276 | 4.28M | FTPTransaction *tx_ptr; |
1277 | 4.28M | if (state->un.ptr == NULL) { |
1278 | 729k | tx_ptr = TAILQ_FIRST(&ftp_state->tx_list); |
1279 | 3.56M | } else { |
1280 | 3.56M | tx_ptr = (FTPTransaction *)state->un.ptr; |
1281 | 3.56M | } |
1282 | 4.28M | if (tx_ptr) { |
1283 | 4.18M | while (tx_ptr->tx_id < min_tx_id) { |
1284 | 93.4k | tx_ptr = TAILQ_NEXT(tx_ptr, next); |
1285 | 93.4k | if (!tx_ptr) { |
1286 | 18.3k | return no_tuple; |
1287 | 18.3k | } |
1288 | 93.4k | } |
1289 | 4.08M | if (tx_ptr->tx_id >= max_tx_id) { |
1290 | 0 | return no_tuple; |
1291 | 0 | } |
1292 | 4.08M | state->un.ptr = TAILQ_NEXT(tx_ptr, next); |
1293 | 4.08M | AppLayerGetTxIterTuple tuple = { |
1294 | 4.08M | .tx_ptr = tx_ptr, |
1295 | 4.08M | .tx_id = tx_ptr->tx_id, |
1296 | 4.08M | .has_next = (state->un.ptr != NULL), |
1297 | 4.08M | }; |
1298 | 4.08M | return tuple; |
1299 | 4.08M | } |
1300 | 4.28M | } |
1301 | 182k | return no_tuple; |
1302 | 4.28M | } |
1303 | | |
1304 | | void RegisterFTPParsers(void) |
1305 | 76 | { |
1306 | 76 | const char *proto_name = "ftp"; |
1307 | 76 | const char *proto_data_name = "ftp-data"; |
1308 | | |
1309 | | /** FTP */ |
1310 | 76 | if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) { |
1311 | 76 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTP, proto_name); |
1312 | 76 | if (FTPRegisterPatternsForProtocolDetection() < 0 ) |
1313 | 0 | return; |
1314 | 76 | AppLayerProtoDetectRegisterProtocol(ALPROTO_FTPDATA, proto_data_name); |
1315 | 76 | } |
1316 | | |
1317 | 76 | if (AppLayerParserConfParserEnabled("tcp", proto_name)) { |
1318 | 76 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER, |
1319 | 76 | FTPParseRequest); |
1320 | 76 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOCLIENT, |
1321 | 76 | FTPParseResponse); |
1322 | 76 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTP, FTPStateAlloc, FTPStateFree); |
1323 | 76 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTP, STREAM_TOSERVER | STREAM_TOCLIENT); |
1324 | | |
1325 | 76 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTP, FTPStateTransactionFree); |
1326 | | |
1327 | 76 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTP, FTPGetTx); |
1328 | 76 | AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxData); |
1329 | 76 | AppLayerParserRegisterGetTxIterator(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxIterator); |
1330 | 76 | AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetStateData); |
1331 | | |
1332 | 76 | AppLayerParserRegisterLocalStorageFunc(IPPROTO_TCP, ALPROTO_FTP, FTPLocalStorageAlloc, |
1333 | 76 | FTPLocalStorageFree); |
1334 | 76 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTP, FTPGetTxCnt); |
1335 | | |
1336 | 76 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTP, FTPGetAlstateProgress); |
1337 | | |
1338 | 76 | AppLayerParserRegisterStateProgressCompletionStatus( |
1339 | 76 | ALPROTO_FTP, FTP_STATE_FINISHED, FTP_STATE_FINISHED); |
1340 | | |
1341 | 76 | AppLayerRegisterExpectationProto(IPPROTO_TCP, ALPROTO_FTPDATA); |
1342 | 76 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER, |
1343 | 76 | FTPDataParseRequest); |
1344 | 76 | AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOCLIENT, |
1345 | 76 | FTPDataParseResponse); |
1346 | 76 | AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateAlloc, FTPDataStateFree); |
1347 | 76 | AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_FTPDATA, STREAM_TOSERVER | STREAM_TOCLIENT); |
1348 | 76 | AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateTransactionFree); |
1349 | | |
1350 | 76 | AppLayerParserRegisterGetTxFilesFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataStateGetTxFiles); |
1351 | | |
1352 | 76 | AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTx); |
1353 | 76 | AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxData); |
1354 | 76 | AppLayerParserRegisterStateDataFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetStateData); |
1355 | | |
1356 | 76 | AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetTxCnt); |
1357 | | |
1358 | 76 | AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_FTPDATA, FTPDataGetAlstateProgress); |
1359 | | |
1360 | 76 | AppLayerParserRegisterStateProgressCompletionStatus( |
1361 | 76 | ALPROTO_FTPDATA, FTPDATA_STATE_FINISHED, FTPDATA_STATE_FINISHED); |
1362 | | |
1363 | 76 | AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info); |
1364 | 76 | AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_FTP, ftp_get_event_info_by_id); |
1365 | | |
1366 | 76 | sbcfg.buf_size = 4096; |
1367 | 76 | sbcfg.Calloc = FTPCalloc; |
1368 | 76 | sbcfg.Realloc = FTPRealloc; |
1369 | 76 | sbcfg.Free = FTPFree; |
1370 | | |
1371 | 76 | FTPParseMemcap(); |
1372 | 76 | } else { |
1373 | 0 | SCLogInfo("Parsed disabled for %s protocol. Protocol detection" |
1374 | 0 | "still on.", proto_name); |
1375 | 0 | } |
1376 | | |
1377 | 76 | FTPSetMpmState(); |
1378 | | |
1379 | | #ifdef UNITTESTS |
1380 | | AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_FTP, FTPParserRegisterTests); |
1381 | | #endif |
1382 | 76 | } |
1383 | | |
1384 | | void FTPAtExitPrintStats(void) |
1385 | 0 | { |
1386 | | #ifdef DEBUG |
1387 | | SCMutexLock(&ftp_state_mem_lock); |
1388 | | SCLogDebug("ftp_state_memcnt %"PRIu64", ftp_state_memuse %"PRIu64"", |
1389 | | ftp_state_memcnt, ftp_state_memuse); |
1390 | | SCMutexUnlock(&ftp_state_mem_lock); |
1391 | | #endif |
1392 | 0 | } |
1393 | | |
1394 | | |
1395 | | /* |
1396 | | * \brief Returns the ending offset of the next line from a multi-line buffer. |
1397 | | * |
1398 | | * "Buffer" refers to a FTP response in a single buffer containing multiple lines. |
1399 | | * Here, "next line" is defined as terminating on |
1400 | | * - Newline character |
1401 | | * - Null character |
1402 | | * |
1403 | | * \param buffer Contains zero or more characters. |
1404 | | * \param len Size, in bytes, of buffer. |
1405 | | * |
1406 | | * \retval Offset from the start of buffer indicating the where the |
1407 | | * next "line ends". The characters between the input buffer and this |
1408 | | * value comprise the line. |
1409 | | * |
1410 | | * NULL is found first or a newline isn't found, then UINT16_MAX is returned. |
1411 | | */ |
1412 | | uint16_t JsonGetNextLineFromBuffer(const char *buffer, const uint16_t len) |
1413 | 161k | { |
1414 | 161k | if (!buffer || *buffer == '\0') { |
1415 | 87.6k | return UINT16_MAX; |
1416 | 87.6k | } |
1417 | | |
1418 | 74.3k | const char *c = strchr(buffer, '\n'); |
1419 | 74.3k | return c == NULL ? len : (uint16_t)(c - buffer + 1); |
1420 | 161k | } |
1421 | | |
1422 | | void EveFTPDataAddMetadata(const Flow *f, JsonBuilder *jb) |
1423 | 46 | { |
1424 | 46 | const FtpDataState *ftp_state = NULL; |
1425 | 46 | if (f->alstate == NULL) |
1426 | 0 | return; |
1427 | | |
1428 | 46 | ftp_state = (FtpDataState *)f->alstate; |
1429 | | |
1430 | 46 | if (ftp_state->file_name) { |
1431 | 46 | jb_set_string_from_bytes(jb, "filename", ftp_state->file_name, ftp_state->file_len); |
1432 | 46 | } |
1433 | 46 | switch (ftp_state->command) { |
1434 | 9 | case FTP_COMMAND_STOR: |
1435 | 9 | JB_SET_STRING(jb, "command", "STOR"); |
1436 | 9 | break; |
1437 | 37 | case FTP_COMMAND_RETR: |
1438 | 37 | JB_SET_STRING(jb, "command", "RETR"); |
1439 | 37 | break; |
1440 | 0 | default: |
1441 | 0 | break; |
1442 | 46 | } |
1443 | 46 | } |
1444 | | |
1445 | | /** |
1446 | | * \brief Free memory allocated for global FTP parser state. |
1447 | | */ |
1448 | | void FTPParserCleanup(void) |
1449 | 0 | { |
1450 | 0 | FTPFreeMpmState(); |
1451 | 0 | } |
1452 | | |
1453 | | /* UNITTESTS */ |
1454 | | #ifdef UNITTESTS |
1455 | | #include "stream-tcp.h" |
1456 | | |
1457 | | /** \test Send a get request in one chunk. */ |
1458 | | static int FTPParserTest01(void) |
1459 | | { |
1460 | | Flow f; |
1461 | | uint8_t ftpbuf[] = "PORT 192,168,1,1,0,80\r\n"; |
1462 | | uint32_t ftplen = sizeof(ftpbuf) - 1; /* minus the \0 */ |
1463 | | TcpSession ssn; |
1464 | | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
1465 | | |
1466 | | memset(&f, 0, sizeof(f)); |
1467 | | memset(&ssn, 0, sizeof(ssn)); |
1468 | | |
1469 | | f.protoctx = (void *)&ssn; |
1470 | | f.proto = IPPROTO_TCP; |
1471 | | f.alproto = ALPROTO_FTP; |
1472 | | |
1473 | | StreamTcpInitConfig(true); |
1474 | | |
1475 | | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1476 | | STREAM_TOSERVER | STREAM_EOF, ftpbuf, ftplen); |
1477 | | FAIL_IF(r != 0); |
1478 | | |
1479 | | FtpState *ftp_state = f.alstate; |
1480 | | FAIL_IF_NULL(ftp_state); |
1481 | | FAIL_IF(ftp_state->command != FTP_COMMAND_PORT); |
1482 | | |
1483 | | AppLayerParserThreadCtxFree(alp_tctx); |
1484 | | StreamTcpFreeConfig(true); |
1485 | | PASS; |
1486 | | } |
1487 | | |
1488 | | /** \test Supply RETR without a filename */ |
1489 | | static int FTPParserTest11(void) |
1490 | | { |
1491 | | Flow f; |
1492 | | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; |
1493 | | uint8_t ftpbuf2[] = "RETR\r\n"; |
1494 | | uint8_t ftpbuf3[] = "227 OK\r\n"; |
1495 | | TcpSession ssn; |
1496 | | |
1497 | | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
1498 | | |
1499 | | memset(&f, 0, sizeof(f)); |
1500 | | memset(&ssn, 0, sizeof(ssn)); |
1501 | | |
1502 | | f.protoctx = (void *)&ssn; |
1503 | | f.proto = IPPROTO_TCP; |
1504 | | f.alproto = ALPROTO_FTP; |
1505 | | |
1506 | | StreamTcpInitConfig(true); |
1507 | | |
1508 | | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1509 | | STREAM_TOSERVER | STREAM_START, ftpbuf1, |
1510 | | sizeof(ftpbuf1) - 1); |
1511 | | FAIL_IF(r != 0); |
1512 | | |
1513 | | /* Response */ |
1514 | | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1515 | | STREAM_TOCLIENT, |
1516 | | ftpbuf3, |
1517 | | sizeof(ftpbuf3) - 1); |
1518 | | FAIL_IF(r != 0); |
1519 | | |
1520 | | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1521 | | STREAM_TOSERVER, ftpbuf2, |
1522 | | sizeof(ftpbuf2) - 1); |
1523 | | FAIL_IF(r == 0); |
1524 | | |
1525 | | FtpState *ftp_state = f.alstate; |
1526 | | FAIL_IF_NULL(ftp_state); |
1527 | | |
1528 | | FAIL_IF(ftp_state->command != FTP_COMMAND_RETR); |
1529 | | |
1530 | | AppLayerParserThreadCtxFree(alp_tctx); |
1531 | | StreamTcpFreeConfig(true); |
1532 | | PASS; |
1533 | | } |
1534 | | |
1535 | | /** \test Supply STOR without a filename */ |
1536 | | static int FTPParserTest12(void) |
1537 | | { |
1538 | | Flow f; |
1539 | | uint8_t ftpbuf1[] = "PORT 192,168,1,1,0,80\r\n"; |
1540 | | uint8_t ftpbuf2[] = "STOR\r\n"; |
1541 | | uint8_t ftpbuf3[] = "227 OK\r\n"; |
1542 | | TcpSession ssn; |
1543 | | |
1544 | | AppLayerParserThreadCtx *alp_tctx = AppLayerParserThreadCtxAlloc(); |
1545 | | |
1546 | | memset(&f, 0, sizeof(f)); |
1547 | | memset(&ssn, 0, sizeof(ssn)); |
1548 | | |
1549 | | f.protoctx = (void *)&ssn; |
1550 | | f.proto = IPPROTO_TCP; |
1551 | | f.alproto = ALPROTO_FTP; |
1552 | | |
1553 | | StreamTcpInitConfig(true); |
1554 | | |
1555 | | int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1556 | | STREAM_TOSERVER | STREAM_START, ftpbuf1, |
1557 | | sizeof(ftpbuf1) - 1); |
1558 | | FAIL_IF(r != 0); |
1559 | | |
1560 | | /* Response */ |
1561 | | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1562 | | STREAM_TOCLIENT, |
1563 | | ftpbuf3, |
1564 | | sizeof(ftpbuf3) - 1); |
1565 | | FAIL_IF(r != 0); |
1566 | | |
1567 | | r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_FTP, |
1568 | | STREAM_TOSERVER, ftpbuf2, |
1569 | | sizeof(ftpbuf2) - 1); |
1570 | | FAIL_IF(r == 0); |
1571 | | |
1572 | | FtpState *ftp_state = f.alstate; |
1573 | | FAIL_IF_NULL(ftp_state); |
1574 | | |
1575 | | FAIL_IF(ftp_state->command != FTP_COMMAND_STOR); |
1576 | | |
1577 | | AppLayerParserThreadCtxFree(alp_tctx); |
1578 | | StreamTcpFreeConfig(true); |
1579 | | PASS; |
1580 | | } |
1581 | | #endif /* UNITTESTS */ |
1582 | | |
1583 | | void FTPParserRegisterTests(void) |
1584 | 0 | { |
1585 | | #ifdef UNITTESTS |
1586 | | UtRegisterTest("FTPParserTest01", FTPParserTest01); |
1587 | | UtRegisterTest("FTPParserTest11", FTPParserTest11); |
1588 | | UtRegisterTest("FTPParserTest12", FTPParserTest12); |
1589 | | #endif /* UNITTESTS */ |
1590 | 0 | } |
1591 | | |