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 { 232, "User logged in, authorized by security data exchange" },
88 { 234, "Security data exchange complete" },
89 { 235, "Security data exchange completed successfully" },
90 { 250, "Requested file action okay, completed" },
91 { 257, "PATHNAME created" },
92 { 331, "User name okay, need password" },
93 { 332, "Need account for login" },
94 { 334, "Requested security mechanism is ok" },
95 { 335, "Security data is acceptable, more is required" },
96 { 336, "Username okay, need password. Challenge is ..." },
97 { 350, "Requested file action pending further information" },
98 { 421, "Service not available, closing control connection" },
99 { 425, "Can't open data connection" },
100 { 426, "Connection closed; transfer aborted" },
101 { 431, "Need some unavailable resource to process security" },
102 { 450, "Requested file action not taken" },
103 { 451, "Requested action aborted: local error in processing" },
104 { 452, "Requested action not taken. Insufficient storage space in system" },
105 { 500, "Syntax error, command unrecognized" },
106 { 501, "Syntax error in parameters or arguments" },
107 { 502, "Command not implemented" },
108 { 503, "Bad sequence of commands" },
109 { 504, "Command not implemented for that parameter" },
110 { 530, "Not logged in" },
111 { 532, "Need account for storing files" },
112 { 533, "Command protection level denied for policy reasons" },
113 { 534, "Request denied for policy reasons" },
114 { 535, "Failed security check (hash, sequence, etc)" },
115 { 536, "Requested PROT level not supported by mechanism" },
116 { 537, "Command protection level not supported by security mechanism" },
117 { 550, "Requested action not taken: File unavailable" },
118 { 551, "Requested action aborted: page type unknown" },
119 { 552, "Requested file action aborted: Exceeded storage allocation" },
120 { 553, "Requested action not taken: File name not allowed" },
121 { 631, "Integrity protected reply" },
122 { 632, "Confidentiality and integrity protected reply" },
123 { 633, "Confidentiality protected reply" },
128 * Parse the address and port information in a PORT command or in the
129 * response to a PASV command. Return TRUE if we found an address and
130 * port, and supply the address and port; return FALSE if we didn't find
133 * We ignore the IP address in the reply, and use the address from which
136 * XXX - are there cases where they differ? What if the FTP server is
137 * behind a NAT box, so that the address it puts into the reply isn't
138 * the address at which you should contact it? Do all NAT boxes detect
139 * FTP PASV replies and rewrite the address? (I suspect not.)
141 * RFC 959 doesn't say much about the syntax of the 227 reply.
143 * A proposal from Dan Bernstein at
145 * http://cr.yp.to/ftp/retr.html
147 * "recommend[s] that clients use the following strategy to parse the
148 * response line: look for the first digit after the initial space; look
149 * for the fourth comma after that digit; read two (possibly negative)
150 * integers, separated by a comma; the TCP port number is p1*256+p2, where
151 * p1 is the first integer modulo 256 and p2 is the second integer modulo
154 * wget 1.5.3 looks for a digit, although it doesn't handle negative
157 * The FTP code in the source of the cURL library, at
159 * http://curl.haxx.se/lxr/source/lib/ftp.c
161 * says that cURL "now scans for a sequence of six comma-separated numbers
162 * and will take them as IP+port indicators"; it loops, doing "sscanf"s
163 * looking for six numbers separated by commas, stepping the start pointer
164 * in the scanf one character at a time - i.e., it tries rather exhaustively.
166 * An optimization would be to scan for a digit, and start there, and if
167 * the scanf doesn't find six values, scan for the next digit and try
168 * again; this will probably succeed on the first try.
170 * The cURL code also says that "found reply-strings include":
172 * "227 Entering Passive Mode (127,0,0,1,4,51)"
173 * "227 Data transfer will passively listen to 127,0,0,1,4,51"
174 * "227 Entering passive mode. 127,0,0,1,4,51"
176 * so it appears that you can't assume there are parentheses around
177 * the address and port number.
180 parse_port_pasv(const guchar *line, int linelen, guint32 *ftp_ip,
187 int ip_address[4], port[2];
188 gboolean ret = FALSE;
191 * Copy the rest of the line into a null-terminated buffer.
193 args = ep_strndup(line, linelen);
200 while ((c = *p) != '\0' && !isdigit(c))
205 * We ran out of text without finding anything.
211 * See if we have six numbers.
213 i = sscanf(p, "%d,%d,%d,%d,%d,%d",
214 &ip_address[0], &ip_address[1], &ip_address[2], &ip_address[3],
220 *ftp_port = ((port[0] & 0xFF)<<8) | (port[1] & 0xFF);
221 *ftp_ip = g_htonl((ip_address[0] << 24) | (ip_address[1] <<16) | (ip_address[2] <<8) | ip_address[3]);
227 * Well, that didn't work. Skip the first number we found,
230 while ((c = *p) != '\0' && isdigit(c))
239 parse_extended_pasv_response(const guchar *line, int linelen, guint16 *ftp_port)
245 gboolean ret = FALSE;
246 gboolean delimiters_seen = FALSE;
249 * Copy the rest of the line into a null-terminated buffer.
251 args = ep_strndup(line, linelen);
255 * Look for ( <d> <d> <d>
256 (Try to cope with '(' in description)
258 for (; !delimiters_seen;) {
259 guchar delimiter = '\0';
260 while ((c = *p) != '\0' && (c != '('))
270 /* Make sure same delimiter is used 3 times */
271 for (n=0; n<3; n++) {
272 if ((c = *p) != '\0') {
273 if (delimiter == '\0') {
276 if (c != delimiter) {
285 delimiters_seen = TRUE;
289 * Should now be at digits.
293 * We didn't run out of text without finding anything.
304 dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
307 proto_tree *ftp_tree = NULL;
308 proto_tree *reqresp_tree = NULL;
309 proto_item *ti, *hidden_item;
314 gboolean is_port_request = FALSE;
315 gboolean is_pasv_response = FALSE;
316 gboolean is_epasv_response = FALSE;
320 const guchar *next_token;
324 address ftp_ip_address;
326 conversation_t *conversation;
328 ftp_ip_address = pinfo->src;
330 if (pinfo->match_uint == pinfo->destport)
335 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP");
338 * Find the end of the first line.
340 * Note that "tvb_find_line_end()" will return a value that is
341 * not longer than what's in the buffer, so the "tvb_get_ptr()"
342 * call won't throw an exception.
344 linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
345 line = tvb_get_ptr(tvb, offset, linelen);
348 * Put the first line from the buffer into the summary
349 * (but leave out the line terminator).
351 col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s",
352 is_request ? "Request" : "Response",
353 format_text(line, linelen));
356 ti = proto_tree_add_item(tree, proto_ftp, tvb, offset, -1,
358 ftp_tree = proto_item_add_subtree(ti, ett_ftp);
361 hidden_item = proto_tree_add_boolean(ftp_tree,
362 hf_ftp_request, tvb, 0, 0, TRUE);
363 PROTO_ITEM_SET_HIDDEN(hidden_item);
364 hidden_item = proto_tree_add_boolean(ftp_tree,
365 hf_ftp_response, tvb, 0, 0, FALSE);
366 PROTO_ITEM_SET_HIDDEN(hidden_item);
368 hidden_item = proto_tree_add_boolean(ftp_tree,
369 hf_ftp_request, tvb, 0, 0, FALSE);
370 PROTO_ITEM_SET_HIDDEN(hidden_item);
371 hidden_item = proto_tree_add_boolean(ftp_tree,
372 hf_ftp_response, tvb, 0, 0, TRUE);
373 PROTO_ITEM_SET_HIDDEN(hidden_item);
377 * Put the line into the protocol tree.
379 ti = proto_tree_add_text(ftp_tree, tvb, offset,
380 next_offset - offset, "%s",
381 tvb_format_text(tvb, offset, next_offset - offset));
382 reqresp_tree = proto_item_add_subtree(ti, ett_ftp_reqresp);
387 * Extract the first token, and, if there is a first
388 * token, add it as the request.
390 tokenlen = get_token_len(line, line + linelen, &next_token);
393 proto_tree_add_item(reqresp_tree,
394 hf_ftp_request_command, tvb, offset,
395 tokenlen, ENC_ASCII|ENC_NA);
397 if (strncmp(line, "PORT", tokenlen) == 0)
398 is_port_request = TRUE;
402 * This is a response; the response code is 3 digits,
403 * followed by a space or hyphen, possibly followed by
406 * If the line doesn't start with 3 digits, it's part of
409 * XXX - keep track of state in the first pass, and
410 * treat non-continuation lines not beginning with digits
413 if (linelen >= 3 && isdigit(line[0]) && isdigit(line[1])
414 && isdigit(line[2])) {
416 * One-line reply, or first or last line
417 * of a multi-line reply.
419 tvb_get_nstringz0(tvb, offset, sizeof(code_str), code_str);
420 code = strtoul(code_str, NULL, 10);
423 proto_tree_add_uint(reqresp_tree,
424 hf_ftp_response_code, tvb, offset, 3, code);
428 * See if it's a passive-mode response.
430 * XXX - does anybody do FOOBAR, as per RFC
431 * 1639, or has that been supplanted by RFC 2428?
434 is_pasv_response = TRUE;
437 * Responses to EPSV command, as per RFC 2428
441 is_epasv_response = TRUE;
444 * Skip the 3 digits and, if present, the
448 next_token = line + 4;
450 next_token = line + linelen;
453 * Line doesn't start with 3 digits; assume it's
454 * a line in the middle of a multi-line reply.
459 offset += (gint) (next_token - line);
460 linelen -= (int) (next_token - line);
465 * Add the rest of the first line as request or
470 proto_tree_add_item(reqresp_tree,
471 hf_ftp_request_arg, tvb, offset,
472 linelen, ENC_ASCII|ENC_NA);
474 proto_tree_add_item(reqresp_tree,
475 hf_ftp_response_arg, tvb, offset,
476 linelen, ENC_ASCII|ENC_NA);
479 offset = next_offset;
483 * If this is a PORT request or a PASV response, handle it.
485 if (is_port_request) {
486 if (parse_port_pasv(line, linelen, &ftp_ip, &ftp_port)) {
488 proto_tree_add_ipv4(reqresp_tree,
489 hf_ftp_active_ip, tvb, 0, 0,
491 proto_tree_add_uint(reqresp_tree,
492 hf_ftp_active_port, tvb, 0, 0,
495 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4, (const guint8 *)&ftp_ip);
496 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src, &ftp_ip_address);
499 proto_tree_add_boolean(
501 hf_ftp_active_nat, tvb,
508 if (is_pasv_response) {
511 * This frame contains a PASV response; set up a
512 * conversation for the data.
514 if (parse_port_pasv(line, linelen, &pasv_ip,
517 proto_tree_add_ipv4(reqresp_tree,
518 hf_ftp_pasv_ip, tvb, 0, 0, pasv_ip);
519 proto_tree_add_uint(reqresp_tree,
520 hf_ftp_pasv_port, tvb, 0, 0,
523 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4,
524 (const guint8 *)&pasv_ip);
525 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src,
529 proto_tree_add_boolean(reqresp_tree,
530 hf_ftp_pasv_nat, tvb, 0, 0,
536 * We use "ftp_ip_address", so that if
537 * we're NAT'd we look for the un-NAT'd
540 * XXX - should this call to
541 * "find_conversation()" just use
542 * "ftp_ip_address" and "server_port", and
543 * wildcard everything else?
545 conversation = find_conversation(pinfo->fd->num, &ftp_ip_address,
546 &pinfo->dst, PT_TCP, ftp_port, 0,
548 if (conversation == NULL) {
550 * XXX - should this call to
551 * "conversation_new()" just use
552 * "ftp_ip_address" and "server_port",
553 * and wildcard everything else?
555 * XXX - what if we did find a
556 * conversation? As we create it
557 * only on the first pass through
558 * the packets, if we find one, it's
559 * presumably an unrelated conversation.
560 * Should we remove the old one from
561 * the hash table and put this one in
562 * its place? Can the conversation
563 * code handle conversations not in
564 * the hash table? Or should we
565 * make conversations support
566 * start and end frames, as circuits
567 * do, and treat this as an indication
568 * that one conversation was closed
569 * and a new one was opened?
571 conversation = conversation_new(
572 pinfo->fd->num, &ftp_ip_address, &pinfo->dst,
573 PT_TCP, ftp_port, 0, NO_PORT2);
574 conversation_set_dissector(conversation,
582 if (is_epasv_response) {
585 * This frame contains an EPSV response; set up a
586 * conversation for the data.
588 if (parse_extended_pasv_response(line, linelen, &ftp_port)) {
589 /* Add port number to tree */
591 proto_tree_add_uint(reqresp_tree,
592 hf_ftp_pasv_port, tvb, 0, 0,
596 /* Find/create conversation for data */
597 conversation = find_conversation(pinfo->fd->num, &pinfo->src,
598 &pinfo->dst, PT_TCP, ftp_port, 0,
600 if (conversation == NULL) {
601 conversation = conversation_new(
602 pinfo->fd->num, &pinfo->src, &pinfo->dst,
603 PT_TCP, ftp_port, 0, NO_PORT2);
604 conversation_set_dissector(conversation,
613 * Show the rest of the request or response as text,
615 * XXX - only if there's a continuation indicator?
617 while (tvb_offset_exists(tvb, offset)) {
619 * Find the end of the line.
621 linelen = tvb_find_line_end(tvb, offset, -1,
622 &next_offset, FALSE);
627 proto_tree_add_text(ftp_tree, tvb, offset,
628 next_offset - offset, "%s",
629 tvb_format_text(tvb, offset, next_offset - offset));
630 offset = next_offset;
636 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
638 proto_tree *ti, *ftp_data_tree;
641 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP-DATA");
643 col_add_fstr(pinfo->cinfo, COL_INFO, "FTP Data: %u bytes",
644 tvb_reported_length(tvb));
647 data_length = tvb_length(tvb);
649 ti = proto_tree_add_item(tree, proto_ftp_data, tvb, 0, -1,
651 ftp_data_tree = proto_item_add_subtree(ti, ett_ftp_data);
654 * XXX - if this is binary data, it'll produce
655 * a *really* long line.
657 proto_tree_add_text(ftp_data_tree, tvb, 0, data_length,
658 "FTP Data: %s", tvb_format_text(tvb, 0, data_length));
663 proto_register_ftp(void)
665 static hf_register_info hf[] = {
667 { "Response", "ftp.response",
668 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
669 "TRUE if FTP response", HFILL }},
672 { "Request", "ftp.request",
673 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
674 "TRUE if FTP request", HFILL }},
676 { &hf_ftp_request_command,
677 { "Request command", "ftp.request.command",
678 FT_STRING, BASE_NONE, NULL, 0x0,
681 { &hf_ftp_request_arg,
682 { "Request arg", "ftp.request.arg",
683 FT_STRING, BASE_NONE, NULL, 0x0,
686 { &hf_ftp_response_code,
687 { "Response code", "ftp.response.code",
688 FT_UINT32, BASE_DEC, VALS(response_table), 0x0,
691 { &hf_ftp_response_arg,
692 { "Response arg", "ftp.response.arg",
693 FT_STRING, BASE_NONE, NULL, 0x0,
697 { "Passive IP address", "ftp.passive.ip",
698 FT_IPv4, BASE_NONE, NULL,0x0,
699 "Passive IP address (check NAT)", HFILL}},
702 { "Passive port", "ftp.passive.port",
703 FT_UINT16, BASE_DEC, NULL,0x0,
704 "Passive FTP server port", HFILL }},
707 {"Passive IP NAT", "ftp.passive.nat",
708 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
709 "NAT is active SIP and passive IP different", HFILL }},
712 { "Active IP address", "ftp.active.cip",
713 FT_IPv4, BASE_NONE, NULL, 0x0,
714 "Active FTP client IP address", HFILL }},
716 { &hf_ftp_active_port,
717 {"Active port", "ftp.active.port",
718 FT_UINT16, BASE_DEC, NULL, 0x0,
719 "Active FTP client port", HFILL }},
721 { &hf_ftp_active_nat,
722 { "Active IP NAT", "ftp.active.nat",
723 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
724 "NAT is active", HFILL}}
727 static gint *ett[] = {
733 proto_ftp = proto_register_protocol("File Transfer Protocol (FTP)", "FTP",
735 register_dissector("ftp", dissect_ftp, proto_ftp);
736 proto_ftp_data = proto_register_protocol("FTP Data", "FTP-DATA", "ftp-data");
737 register_dissector("ftp-data", dissect_ftpdata, proto_ftp_data);
738 proto_register_field_array(proto_ftp, hf, array_length(hf));
739 proto_register_subtree_array(ett, array_length(ett));
743 proto_reg_handoff_ftp(void)
745 dissector_handle_t ftp_handle;
747 ftpdata_handle = find_dissector("ftp-data");
748 dissector_add_uint("tcp.port", TCP_PORT_FTPDATA, ftpdata_handle);
749 ftp_handle = find_dissector("ftp");
750 dissector_add_uint("tcp.port", TCP_PORT_FTP, ftp_handle);