2 * Routines for ftp packet dissection
3 * Copyright 1999, Richard Sharpe <rsharpe@ns.aus.com>
4 * Copyright 2001, Juan Toledo <toledo@users.sourceforge.net> (Passive FTP)
8 * Wireshark - Network traffic analyzer
9 * By Gerald Combs <gerald@wireshark.org>
10 * Copyright 1998 Gerald Combs
12 * Copied from packet-pop.c
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
39 #include <epan/packet.h>
40 #include <epan/strutil.h>
41 #include <epan/conversation.h>
42 #include <epan/emem.h>
44 static int proto_ftp = -1;
45 static int proto_ftp_data = -1;
46 static int hf_ftp_response = -1;
47 static int hf_ftp_request = -1;
48 static int hf_ftp_request_command = -1;
49 static int hf_ftp_request_arg = -1;
50 static int hf_ftp_response_code = -1;
51 static int hf_ftp_response_arg = -1;
52 static int hf_ftp_pasv_ip = -1 ;
53 static int hf_ftp_pasv_port = -1;
54 static int hf_ftp_pasv_nat = -1;
55 static int hf_ftp_active_ip = -1;
56 static int hf_ftp_active_port = -1;
57 static int hf_ftp_active_nat = -1;
59 static gint ett_ftp = -1;
60 static gint ett_ftp_reqresp = -1;
61 static gint ett_ftp_data = -1;
63 static dissector_handle_t ftpdata_handle;
65 #define TCP_PORT_FTPDATA 20
66 #define TCP_PORT_FTP 21
68 static const value_string response_table[] = {
69 { 110, "Restart marker reply" },
70 { 120, "Service ready in nnn minutes" },
71 { 125, "Data connection already open; transfer starting" },
72 { 150, "File status okay; about to open data connection" },
73 { 200, "Command okay" },
74 { 202, "Command not implemented, superfluous at this site" },
75 { 211, "System status, or system help reply" },
76 { 212, "Directory status" },
77 { 213, "File status" },
78 { 214, "Help message" },
79 { 215, "NAME system type" },
80 { 220, "Service ready for new user" },
81 { 221, "Service closing control connection" },
82 { 225, "Data connection open; no transfer in progress" },
83 { 226, "Closing data connection" },
84 { 227, "Entering Passive Mode" },
85 { 229, "Entering Extended Passive Mode" },
86 { 230, "User logged in, proceed" },
87 { 250, "Requested file action okay, completed" },
88 { 257, "PATHNAME created" },
89 { 331, "User name okay, need password" },
90 { 332, "Need account for login" },
91 { 350, "Requested file action pending further information" },
92 { 421, "Service not available, closing control connection" },
93 { 425, "Can't open data connection" },
94 { 426, "Connection closed; transfer aborted" },
95 { 450, "Requested file action not taken" },
96 { 451, "Requested action aborted: local error in processing" },
97 { 452, "Requested action not taken. Insufficient storage space in system" },
98 { 500, "Syntax error, command unrecognized" },
99 { 501, "Syntax error in parameters or arguments" },
100 { 502, "Command not implemented" },
101 { 503, "Bad sequence of commands" },
102 { 504, "Command not implemented for that parameter" },
103 { 530, "Not logged in" },
104 { 532, "Need account for storing files" },
105 { 550, "Requested action not taken: File unavailable" },
106 { 551, "Requested action aborted: page type unknown" },
107 { 552, "Requested file action aborted: Exceeded storage allocation" },
108 { 553, "Requested action not taken: File name not allowed" },
113 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
116 * Parse the address and port information in a PORT command or in the
117 * response to a PASV command. Return TRUE if we found an address and
118 * port, and supply the address and port; return FALSE if we didn't find
121 * We ignore the IP address in the reply, and use the address from which
124 * XXX - are there cases where they differ? What if the FTP server is
125 * behind a NAT box, so that the address it puts into the reply isn't
126 * the address at which you should contact it? Do all NAT boxes detect
127 * FTP PASV replies and rewrite the address? (I suspect not.)
129 * RFC 959 doesn't say much about the syntax of the 227 reply.
131 * A proposal from Dan Bernstein at
133 * http://cr.yp.to/ftp/retr.html
135 * "recommend[s] that clients use the following strategy to parse the
136 * response line: look for the first digit after the initial space; look
137 * for the fourth comma after that digit; read two (possibly negative)
138 * integers, separated by a comma; the TCP port number is p1*256+p2, where
139 * p1 is the first integer modulo 256 and p2 is the second integer modulo
142 * wget 1.5.3 looks for a digit, although it doesn't handle negative
145 * The FTP code in the source of the cURL library, at
147 * http://curl.haxx.se/lxr/source/lib/ftp.c
149 * says that cURL "now scans for a sequence of six comma-separated numbers
150 * and will take them as IP+port indicators"; it loops, doing "sscanf"s
151 * looking for six numbers separated by commas, stepping the start pointer
152 * in the scanf one character at a time - i.e., it tries rather exhaustively.
154 * An optimization would be to scan for a digit, and start there, and if
155 * the scanf doesn't find six values, scan for the next digit and try
156 * again; this will probably succeed on the first try.
158 * The cURL code also says that "found reply-strings include":
160 * "227 Entering Passive Mode (127,0,0,1,4,51)"
161 * "227 Data transfer will passively listen to 127,0,0,1,4,51"
162 * "227 Entering passive mode. 127,0,0,1,4,51"
164 * so it appears that you can't assume there are parentheses around
165 * the address and port number.
168 parse_port_pasv(const guchar *line, int linelen, guint32 *ftp_ip,
175 int address[4], port[2];
176 gboolean ret = FALSE;
179 * Copy the rest of the line into a null-terminated buffer.
181 args = ep_alloc(linelen + 1);
182 memcpy(args, line, linelen);
183 args[linelen] = '\0';
190 while ((c = *p) != '\0' && !isdigit(c))
195 * We ran out of text without finding anything.
201 * See if we have six numbers.
203 i = sscanf(p, "%d,%d,%d,%d,%d,%d",
204 &address[0], &address[1], &address[2], &address[3],
210 *ftp_port = ((port[0] & 0xFF)<<8) | (port[1] & 0xFF);
211 *ftp_ip = g_htonl((address[0] << 24) | (address[1] <<16) | (address[2] <<8) | address[3]);
217 * Well, that didn't work. Skip the first number we found,
220 while ((c = *p) != '\0' && isdigit(c))
229 parse_extended_pasv_response(const guchar *line, int linelen, guint16 *ftp_port)
235 gboolean ret = FALSE;
236 gboolean delimiters_seen = FALSE;
239 * Copy the rest of the line into a null-terminated buffer.
241 args = ep_alloc(linelen + 1);
242 memcpy(args, line, linelen);
243 args[linelen] = '\0';
247 * Look for ( <d> <d> <d>
248 (Try to cope with '(' in description)
250 for (; !delimiters_seen;) {
251 char delimiter = '\0';
252 while ((c = *p) != '\0' && (c != '('))
262 /* Make sure same delimiter is used 3 times */
263 for (n=0; n<3; n++) {
264 if ((c = *p) != '\0') {
265 if (delimiter == '\0') {
268 if (c != delimiter) {
277 delimiters_seen = TRUE;
281 * Should now be at digits.
285 * We didn't run out of text without finding anything.
296 dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
299 proto_tree *ftp_tree = NULL;
300 proto_tree *reqresp_tree = NULL;
301 proto_item *ti, *hidden_item;
306 gboolean is_port_request = FALSE;
307 gboolean is_pasv_response = FALSE;
308 gboolean is_epasv_response = FALSE;
312 const guchar *next_token;
316 address ftp_ip_address;
318 conversation_t *conversation;
320 ftp_ip_address = pinfo->src;
322 if (pinfo->match_port == pinfo->destport)
327 if (check_col(pinfo->cinfo, COL_PROTOCOL))
328 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP");
331 * Find the end of the first line.
333 * Note that "tvb_find_line_end()" will return a value that is
334 * not longer than what's in the buffer, so the "tvb_get_ptr()"
335 * call won't throw an exception.
337 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
338 line = tvb_get_ptr(tvb, offset, linelen);
340 if (check_col(pinfo->cinfo, COL_INFO)) {
342 * Put the first line from the buffer into the summary
343 * (but leave out the line terminator).
345 col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s",
346 is_request ? "Request" : "Response",
347 format_text(line, linelen));
351 ti = proto_tree_add_item(tree, proto_ftp, tvb, offset, -1,
353 ftp_tree = proto_item_add_subtree(ti, ett_ftp);
356 hidden_item = proto_tree_add_boolean(ftp_tree,
357 hf_ftp_request, tvb, 0, 0, TRUE);
358 PROTO_ITEM_SET_HIDDEN(hidden_item);
359 hidden_item = proto_tree_add_boolean(ftp_tree,
360 hf_ftp_response, tvb, 0, 0, FALSE);
361 PROTO_ITEM_SET_HIDDEN(hidden_item);
363 hidden_item = proto_tree_add_boolean(ftp_tree,
364 hf_ftp_request, tvb, 0, 0, FALSE);
365 PROTO_ITEM_SET_HIDDEN(hidden_item);
366 hidden_item = proto_tree_add_boolean(ftp_tree,
367 hf_ftp_response, tvb, 0, 0, TRUE);
368 PROTO_ITEM_SET_HIDDEN(hidden_item);
372 * Put the line into the protocol tree.
374 ti = proto_tree_add_text(ftp_tree, tvb, offset,
375 next_offset - offset, "%s",
376 tvb_format_text(tvb, offset, next_offset - offset));
377 reqresp_tree = proto_item_add_subtree(ti, ett_ftp_reqresp);
382 * Extract the first token, and, if there is a first
383 * token, add it as the request.
385 tokenlen = get_token_len(line, line + linelen, &next_token);
388 proto_tree_add_item(reqresp_tree,
389 hf_ftp_request_command, tvb, offset,
392 if (strncmp(line, "PORT", tokenlen) == 0)
393 is_port_request = TRUE;
397 * This is a response; the response code is 3 digits,
398 * followed by a space or hyphen, possibly followed by
401 * If the line doesn't start with 3 digits, it's part of
404 * XXX - keep track of state in the first pass, and
405 * treat non-continuation lines not beginning with digits
408 if (linelen >= 3 && isdigit(line[0]) && isdigit(line[1])
409 && isdigit(line[2])) {
411 * One-line reply, or first or last line
412 * of a multi-line reply.
414 tvb_get_nstringz0(tvb, offset, sizeof(code_str), code_str);
415 code = strtoul(code_str, NULL, 10);
418 proto_tree_add_uint(reqresp_tree,
419 hf_ftp_response_code, tvb, offset, 3, code);
423 * See if it's a passive-mode response.
425 * XXX - does anybody do FOOBAR, as per RFC
426 * 1639, or has that been supplanted by RFC 2428?
429 is_pasv_response = TRUE;
432 * Responses to EPSV command, as per RFC 2428
436 is_epasv_response = TRUE;
439 * Skip the 3 digits and, if present, the
443 next_token = line + 4;
445 next_token = line + linelen;
448 * Line doesn't start with 3 digits; assume it's
449 * a line in the middle of a multi-line reply.
454 offset += (gint) (next_token - line);
455 linelen -= (int) (next_token - line);
460 * Add the rest of the first line as request or
465 proto_tree_add_item(reqresp_tree,
466 hf_ftp_request_arg, tvb, offset,
469 proto_tree_add_item(reqresp_tree,
470 hf_ftp_response_arg, tvb, offset,
474 offset = next_offset;
478 * If this is a PORT request or a PASV response, handle it.
480 if (is_port_request) {
481 if (parse_port_pasv(line, linelen, &ftp_ip, &ftp_port)) {
483 proto_tree_add_ipv4(reqresp_tree,
484 hf_ftp_active_ip, tvb, 0, 0,
486 proto_tree_add_uint(reqresp_tree,
487 hf_ftp_active_port, tvb, 0, 0,
490 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4, (const guint8 *)&ftp_ip);
491 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src, &ftp_ip_address);
494 proto_tree_add_boolean(
496 hf_ftp_active_nat, tvb,
503 if (is_pasv_response) {
506 * This frame contains a PASV response; set up a
507 * conversation for the data.
509 if (parse_port_pasv(line, linelen, &pasv_ip,
512 proto_tree_add_ipv4(reqresp_tree,
513 hf_ftp_pasv_ip, tvb, 0, 0, pasv_ip);
514 proto_tree_add_uint(reqresp_tree,
515 hf_ftp_pasv_port, tvb, 0, 0,
518 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4,
519 (const guint8 *)&pasv_ip);
520 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src,
524 proto_tree_add_boolean(reqresp_tree,
525 hf_ftp_pasv_nat, tvb, 0, 0,
531 * We use "ftp_ip_address", so that if
532 * we're NAT'd we look for the un-NAT'd
535 * XXX - should this call to
536 * "find_conversation()" just use
537 * "ftp_ip_address" and "server_port", and
538 * wildcard everything else?
540 conversation = find_conversation(pinfo->fd->num, &ftp_ip_address,
541 &pinfo->dst, PT_TCP, ftp_port, 0,
543 if (conversation == NULL) {
545 * XXX - should this call to
546 * "conversation_new()" just use
547 * "ftp_ip_address" and "server_port",
548 * and wildcard everything else?
550 * XXX - what if we did find a
551 * conversation? As we create it
552 * only on the first pass through
553 * the packets, if we find one, it's
554 * presumably an unrelated conversation.
555 * Should we remove the old one from
556 * the hash table and put this one in
557 * its place? Can the conversation
558 * code handle conversations not in
559 * the hash table? Or should we
560 * make conversations support
561 * start and end frames, as circuits
562 * do, and treat this as an indication
563 * that one conversation was closed
564 * and a new one was opened?
566 conversation = conversation_new(
567 pinfo->fd->num, &ftp_ip_address, &pinfo->dst,
568 PT_TCP, ftp_port, 0, NO_PORT2);
569 conversation_set_dissector(conversation,
577 if (is_epasv_response) {
580 * This frame contains an EPSV response; set up a
581 * conversation for the data.
583 if (parse_extended_pasv_response(line, linelen, &ftp_port)) {
584 /* Add port number to tree */
586 proto_tree_add_uint(reqresp_tree,
587 hf_ftp_pasv_port, tvb, 0, 0,
591 /* Find/create conversation for data */
592 conversation = find_conversation(pinfo->fd->num, &pinfo->src,
593 &pinfo->dst, PT_TCP, ftp_port, 0,
595 if (conversation == NULL) {
596 conversation = conversation_new(
597 pinfo->fd->num, &pinfo->src, &pinfo->dst,
598 PT_TCP, ftp_port, 0, NO_PORT2);
599 conversation_set_dissector(conversation,
608 * Show the rest of the request or response as text,
610 * XXX - only if there's a continuation indicator?
612 while (tvb_offset_exists(tvb, offset)) {
614 * Find the end of the line.
616 linelen = tvb_find_line_end(tvb, offset, -1,
617 &next_offset, FALSE);
622 proto_tree_add_text(ftp_tree, tvb, offset,
623 next_offset - offset, "%s",
624 tvb_format_text(tvb, offset, next_offset - offset));
625 offset = next_offset;
631 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
633 proto_tree *ti, *ftp_data_tree;
636 if (check_col(pinfo->cinfo, COL_PROTOCOL))
637 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP-DATA");
639 if (check_col(pinfo->cinfo, COL_INFO)) {
640 col_add_fstr(pinfo->cinfo, COL_INFO, "FTP Data: %u bytes",
641 tvb_reported_length(tvb));
645 data_length = tvb_length(tvb);
647 ti = proto_tree_add_item(tree, proto_ftp_data, tvb, 0, -1,
649 ftp_data_tree = proto_item_add_subtree(ti, ett_ftp_data);
652 * XXX - if this is binary data, it'll produce
653 * a *really* long line.
655 proto_tree_add_text(ftp_data_tree, tvb, 0, data_length,
656 "FTP Data: %s", tvb_format_text(tvb, 0, data_length));
661 proto_register_ftp(void)
663 static hf_register_info hf[] = {
665 { "Response", "ftp.response",
666 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
667 "TRUE if FTP response", HFILL }},
670 { "Request", "ftp.request",
671 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
672 "TRUE if FTP request", HFILL }},
674 { &hf_ftp_request_command,
675 { "Request command", "ftp.request.command",
676 FT_STRING, BASE_NONE, NULL, 0x0,
679 { &hf_ftp_request_arg,
680 { "Request arg", "ftp.request.arg",
681 FT_STRING, BASE_NONE, NULL, 0x0,
684 { &hf_ftp_response_code,
685 { "Response code", "ftp.response.code",
686 FT_UINT32, BASE_DEC, VALS(response_table), 0x0,
689 { &hf_ftp_response_arg,
690 { "Response arg", "ftp.response.arg",
691 FT_STRING, BASE_NONE, NULL, 0x0,
695 { "Passive IP address", "ftp.passive.ip",
696 FT_IPv4, BASE_NONE, NULL,0x0,
697 "Passive IP address (check NAT)", HFILL}},
700 { "Passive port", "ftp.passive.port",
701 FT_UINT16, BASE_DEC, NULL,0x0,
702 "Passive FTP server port", HFILL }},
705 {"Passive IP NAT", "ftp.passive.nat",
706 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
707 "NAT is active SIP and passive IP different", HFILL }},
710 { "Active IP address", "ftp.active.cip",
711 FT_IPv4, BASE_NONE, NULL, 0x0,
712 "Active FTP client IP address", HFILL }},
714 { &hf_ftp_active_port,
715 {"Active port", "ftp.active.port",
716 FT_UINT16, BASE_DEC, NULL, 0x0,
717 "Active FTP client port", HFILL }},
719 { &hf_ftp_active_nat,
720 { "Active IP NAT", "ftp.active.nat",
721 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
722 "NAT is active", HFILL}}
725 static gint *ett[] = {
731 proto_ftp = proto_register_protocol("File Transfer Protocol (FTP)", "FTP",
733 register_dissector("ftp", dissect_ftp, proto_ftp);
734 proto_ftp_data = proto_register_protocol("FTP Data", "FTP-DATA", "ftp-data");
735 register_dissector("ftp-data", dissect_ftpdata, proto_ftp_data);
736 proto_register_field_array(proto_ftp, hf, array_length(hf));
737 proto_register_subtree_array(ett, array_length(ett));
741 proto_reg_handoff_ftp(void)
743 dissector_handle_t ftp_handle;
745 ftpdata_handle = find_dissector("ftp-data");
746 dissector_add("tcp.port", TCP_PORT_FTPDATA, ftpdata_handle);
747 ftp_handle = find_dissector("ftp");
748 dissector_add("tcp.port", TCP_PORT_FTP, ftp_handle);