HACK: 2nd try to match RowsetProperties
[wireshark-wip.git] / epan / dissectors / packet-ftp.c
blob762b64b324f9cc25df98cd5acf5f24a47432e6db
1 /* packet-ftp.c
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)
6 * $Id$
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
29 #include "config.h"
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <ctype.h>
35 #include <string.h>
36 #include <glib.h>
37 #include <epan/packet.h>
38 #include <epan/strutil.h>
39 #include <epan/conversation.h>
40 #include <epan/expert.h>
41 #include <epan/wmem/wmem.h>
43 #ifdef HAVE_ARPA_INET_H
44 #include <arpa/inet.h>
45 #endif
46 #ifdef HAVE_SYS_SOCKET_H
47 #include <sys/socket.h> /* needed to define AF_ values on UNIX */
48 #endif
49 #ifdef HAVE_WINSOCK2_H
50 #include <winsock2.h> /* needed to define AF_ values on Windows */
51 #endif
52 #ifdef NEED_INET_V6DEFS_H
53 #include "wsutil/inet_v6defs.h" /* if not a *NIX system */
54 #endif
58 static int proto_ftp = -1;
59 static int proto_ftp_data = -1;
60 static int hf_ftp_response = -1;
61 static int hf_ftp_request = -1;
62 static int hf_ftp_request_command = -1;
63 static int hf_ftp_request_arg = -1;
64 static int hf_ftp_response_code = -1;
65 static int hf_ftp_response_arg = -1;
66 static int hf_ftp_pasv_ip = -1 ;
67 static int hf_ftp_pasv_port = -1;
68 static int hf_ftp_pasv_nat = -1;
69 static int hf_ftp_active_ip = -1;
70 static int hf_ftp_active_port = -1;
71 static int hf_ftp_active_nat = -1;
72 static int hf_ftp_eprt_af = -1;
73 static int hf_ftp_eprt_ip = -1;
74 static int hf_ftp_eprt_ipv6 = -1;
75 static int hf_ftp_eprt_port = -1;
76 static int hf_ftp_epsv_ip = -1;
77 static int hf_ftp_epsv_ipv6 = -1;
78 static int hf_ftp_epsv_port = -1;
80 static gint ett_ftp = -1;
81 static gint ett_ftp_reqresp = -1;
83 static expert_field ei_ftp_eprt_args_invalid = EI_INIT;
84 static expert_field ei_ftp_epsv_args_invalid = EI_INIT;
86 static dissector_handle_t ftpdata_handle;
88 #define TCP_PORT_FTPDATA 20
89 #define TCP_PORT_FTP 21
91 static const value_string response_table[] = {
92 { 110, "Restart marker reply" },
93 { 120, "Service ready in nnn minutes" },
94 { 125, "Data connection already open; transfer starting" },
95 { 150, "File status okay; about to open data connection" },
96 { 200, "Command okay" },
97 { 202, "Command not implemented, superfluous at this site" },
98 { 211, "System status, or system help reply" },
99 { 212, "Directory status" },
100 { 213, "File status" },
101 { 214, "Help message" },
102 { 215, "NAME system type" },
103 { 220, "Service ready for new user" },
104 { 221, "Service closing control connection" },
105 { 225, "Data connection open; no transfer in progress" },
106 { 226, "Closing data connection" },
107 { 227, "Entering Passive Mode" },
108 { 229, "Entering Extended Passive Mode" },
109 { 230, "User logged in, proceed" },
110 { 232, "User logged in, authorized by security data exchange" },
111 { 234, "Security data exchange complete" },
112 { 235, "Security data exchange completed successfully" },
113 { 250, "Requested file action okay, completed" },
114 { 257, "PATHNAME created" },
115 { 331, "User name okay, need password" },
116 { 332, "Need account for login" },
117 { 334, "Requested security mechanism is ok" },
118 { 335, "Security data is acceptable, more is required" },
119 { 336, "Username okay, need password. Challenge is ..." },
120 { 350, "Requested file action pending further information" },
121 { 421, "Service not available, closing control connection" },
122 { 425, "Can't open data connection" },
123 { 426, "Connection closed; transfer aborted" },
124 { 431, "Need some unavailable resource to process security" },
125 { 450, "Requested file action not taken" },
126 { 451, "Requested action aborted: local error in processing" },
127 { 452, "Requested action not taken. Insufficient storage space in system" },
128 { 500, "Syntax error, command unrecognized" },
129 { 501, "Syntax error in parameters or arguments" },
130 { 502, "Command not implemented" },
131 { 503, "Bad sequence of commands" },
132 { 504, "Command not implemented for that parameter" },
133 { 522, "Network protocol not supported" },
134 { 530, "Not logged in" },
135 { 532, "Need account for storing files" },
136 { 533, "Command protection level denied for policy reasons" },
137 { 534, "Request denied for policy reasons" },
138 { 535, "Failed security check (hash, sequence, etc)" },
139 { 536, "Requested PROT level not supported by mechanism" },
140 { 537, "Command protection level not supported by security mechanism" },
141 { 550, "Requested action not taken: File unavailable" },
142 { 551, "Requested action aborted: page type unknown" },
143 { 552, "Requested file action aborted: Exceeded storage allocation" },
144 { 553, "Requested action not taken: File name not allowed" },
145 { 631, "Integrity protected reply" },
146 { 632, "Confidentiality and integrity protected reply" },
147 { 633, "Confidentiality protected reply" },
148 { 0, NULL }
150 static value_string_ext response_table_ext = VALUE_STRING_EXT_INIT(response_table);
152 #define EPRT_AF_IPv4 1
153 #define EPRT_AF_IPv6 2
154 static const value_string eprt_af_vals[] = {
155 { EPRT_AF_IPv4, "IPv4" },
156 { EPRT_AF_IPv6, "IPv6" },
157 { 0, NULL }
162 * Parse the address and port information in a PORT command or in the
163 * response to a PASV command. Return TRUE if we found an address and
164 * port, and supply the address and port; return FALSE if we didn't find
165 * them.
167 * We ignore the IP address in the reply, and use the address from which
168 * the request came.
170 * XXX - are there cases where they differ? What if the FTP server is
171 * behind a NAT box, so that the address it puts into the reply isn't
172 * the address at which you should contact it? Do all NAT boxes detect
173 * FTP PASV replies and rewrite the address? (I suspect not.)
175 * RFC 959 doesn't say much about the syntax of the 227 reply.
177 * A proposal from Dan Bernstein at
179 * http://cr.yp.to/ftp/retr.html
181 * "recommend[s] that clients use the following strategy to parse the
182 * response line: look for the first digit after the initial space; look
183 * for the fourth comma after that digit; read two (possibly negative)
184 * integers, separated by a comma; the TCP port number is p1*256+p2, where
185 * p1 is the first integer modulo 256 and p2 is the second integer modulo
186 * 256."
188 * wget 1.5.3 looks for a digit, although it doesn't handle negative
189 * integers.
191 * The FTP code in the source of the cURL library, at
193 * http://curl.haxx.se/lxr/source/lib/ftp.c
195 * says that cURL "now scans for a sequence of six comma-separated numbers
196 * and will take them as IP+port indicators"; it loops, doing "sscanf"s
197 * looking for six numbers separated by commas, stepping the start pointer
198 * in the scanf one character at a time - i.e., it tries rather exhaustively.
200 * An optimization would be to scan for a digit, and start there, and if
201 * the scanf doesn't find six values, scan for the next digit and try
202 * again; this will probably succeed on the first try.
204 * The cURL code also says that "found reply-strings include":
206 * "227 Entering Passive Mode (127,0,0,1,4,51)"
207 * "227 Data transfer will passively listen to 127,0,0,1,4,51"
208 * "227 Entering passive mode. 127,0,0,1,4,51"
210 * so it appears that you can't assume there are parentheses around
211 * the address and port number.
213 static gboolean
214 parse_port_pasv(const guchar *line, int linelen, guint32 *ftp_ip, guint16 *ftp_port,
215 guint32 *pasv_offset, guint *ftp_ip_len, guint *ftp_port_len)
217 char *args;
218 char *p;
219 guchar c;
220 int i;
221 int ip_address[4], port[2];
222 gboolean ret = FALSE;
225 * Copy the rest of the line into a null-terminated buffer.
227 args = wmem_strndup(wmem_packet_scope(), line, linelen);
228 p = args;
230 for (;;) {
232 * Look for a digit.
234 while ((c = *p) != '\0' && !isdigit(c))
235 p++;
237 if (*p == '\0') {
239 * We ran out of text without finding anything.
241 break;
245 * See if we have six numbers.
247 i = sscanf(p, "%d,%d,%d,%d,%d,%d",
248 &ip_address[0], &ip_address[1], &ip_address[2], &ip_address[3],
249 &port[0], &port[1]);
250 if (i == 6) {
252 * We have a winner!
254 *ftp_port = ((port[0] & 0xFF)<<8) | (port[1] & 0xFF);
255 *ftp_ip = g_htonl((ip_address[0] << 24) | (ip_address[1] <<16) | (ip_address[2] <<8) | ip_address[3]);
256 *pasv_offset = (guint32)(p - args);
257 *ftp_port_len = (port[0] < 10 ? 1 : (port[0] < 100 ? 2 : 3 )) + 1 +
258 (port[1] < 10 ? 1 : (port[1] < 100 ? 2 : 3 ));
259 *ftp_ip_len = (ip_address[0] < 10 ? 1 : (ip_address[0] < 100 ? 2 : 3)) + 1 +
260 (ip_address[1] < 10 ? 1 : (ip_address[1] < 100 ? 2 : 3)) + 1 +
261 (ip_address[2] < 10 ? 1 : (ip_address[2] < 100 ? 2 : 3)) + 1 +
262 (ip_address[3] < 10 ? 1 : (ip_address[3] < 100 ? 2 : 3));
263 ret = TRUE;
264 break;
268 * Well, that didn't work. Skip the first number we found,
269 * and keep trying.
271 while ((c = *p) != '\0' && isdigit(c))
272 p++;
275 return ret;
278 static gboolean
279 isvalid_rfc2428_delimiter(const guchar c)
281 /* RFC2428 sect. 2 states rules for a valid delimiter */
282 static const gchar forbidden[] = {"0123456789abcdef.:"};
283 if (c < 33 || c > 126)
284 return FALSE;
285 else if (strchr(forbidden, tolower(c)))
286 return FALSE;
287 else
288 return TRUE;
293 * RFC2428 states...
295 * AF Number Protocol
296 * --------- --------
297 * 1 Internet Protocol, Version 4
298 * 2 Internet Protocol, Version 6
300 * AF Number Address Format Example
301 * --------- -------------- -------
302 * 1 dotted decimal 132.235.1.2
303 * 2 IPv6 string 1080::8:800:200C:417A
304 * representations
305 * defined in
307 * The following are sample EPRT commands:
308 * EPRT |1|132.235.1.2|6275|
309 * EPRT |2|1080::8:800:200C:417A|5282|
311 * The first command specifies that the server should use IPv4 to open a
312 * data connection to the host "132.235.1.2" on TCP port 6275. The
313 * second command specifies that the server should use the IPv6 network
314 * protocol and the network address "1080::8:800:200C:417A" to open a
315 * TCP data connection on port 5282.
317 * ... which means in fact that RFC2428 is capable to handle both,
318 * IPv4 and IPv6 so we have to care about the address family and properly
319 * act depending on it.
322 static gboolean
323 parse_eprt_request(const guchar* line, gint linelen, guint32 *eprt_af,
324 guint32 *eprt_ip, guint16 *eprt_ipv6, guint16 *ftp_port,
325 guint32 *eprt_ip_len, guint32 *ftp_port_len)
327 gint delimiters_seen = 0;
328 gchar delimiter;
329 gint fieldlen;
330 gchar *field;
331 gint n;
332 gint lastn;
333 char *args, *p;
334 gboolean ret = TRUE;
337 /* line contains the EPRT parameters, we need at least the 4 delimiters */
338 if (!line || linelen<4)
339 return FALSE;
341 /* Copy the rest of the line into a null-terminated buffer. */
342 args = wmem_strndup(wmem_packet_scope(), line, linelen);
343 p = args;
346 * RFC2428 sect. 2 states ...
348 * The EPRT command keyword MUST be followed by a single space (ASCII
349 * 32). Following the space, a delimiter character (<d>) MUST be
350 * specified.
352 * ... the preceding <space> is already stripped so we know that the first
353 * character must be the delimiter and has just to be checked to be valid.
355 if (!isvalid_rfc2428_delimiter(*p))
356 return FALSE; /* EPRT command does not follow a vaild delimiter;
357 * malformed EPRT command - immediate escape */
359 delimiter = *p;
360 /* Validate that the delimiter occurs 4 times in the string */
361 for (n = 0; n < linelen; n++) {
362 if (*(p+n) == delimiter)
363 delimiters_seen++;
365 if (delimiters_seen != 4)
366 return FALSE; /* delimiter doesn't occur 4 times
367 * probably no EPRT request - immediate escape */
369 /* we know that the first character is a delimiter... */
370 delimiters_seen = 1;
371 lastn = 0;
372 /* ... so we can start searching from the 2nd onwards */
373 for (n=1; n < linelen; n++) {
375 if (*(p+n) != delimiter)
376 continue;
378 /* we found a delimiter */
379 delimiters_seen++;
381 fieldlen = n - lastn - 1;
382 if (fieldlen<=0)
383 return FALSE; /* all fields must have data in them */
384 field = p + lastn + 1;
386 if (delimiters_seen == 2) { /* end of address family field */
387 gchar *af_str;
388 af_str = wmem_strndup(wmem_packet_scope(), field, fieldlen);
389 *eprt_af = atoi(af_str);
391 else if (delimiters_seen == 3) {/* end of IP address field */
392 gchar *ip_str;
393 ip_str = wmem_strndup(wmem_packet_scope(), field, fieldlen);
395 if (*eprt_af == EPRT_AF_IPv4) {
396 if (inet_pton(AF_INET, ip_str, eprt_ip) > 0)
397 ret = TRUE;
398 else
399 ret = FALSE;
401 else if (*eprt_af == EPRT_AF_IPv6) {
402 if (inet_pton(AF_INET6, ip_str, eprt_ipv6) > 0)
403 ret = TRUE;
404 else
405 ret = FALSE;
407 else
408 return FALSE; /* invalid/unknown address family */
410 *eprt_ip_len = fieldlen;
412 else if (delimiters_seen == 4) {/* end of port field */
413 gchar *pt_str;
414 pt_str = wmem_strndup(wmem_packet_scope(), field, fieldlen);
416 *ftp_port = atoi(pt_str);
417 *ftp_port_len = fieldlen;
420 lastn = n;
423 return ret;
427 * RFC2428 states ....
429 * The first two fields contained in the parenthesis MUST be blank. The
430 * third field MUST be the string representation of the TCP port number
431 * on which the server is listening for a data connection.
433 * The network protocol used by the data connection will be the same network
434 * protocol used by the control connection. In addition, the network
435 * address used to establish the data connection will be the same
436 * network address used for the control connection.
438 * An example response string follows:
440 * Entering Extended Passive Mode (|||6446|)
442 * ... which in fact means that again both address families IPv4 and IPv6
443 * are supported. But gladly it's not necessary to parse because it doesn't
444 * occur in EPSV responses. We can leverage ftp_ip_address which is
445 * protocol independent and already set.
448 static gboolean
449 parse_extended_pasv_response(const guchar *line, gint linelen, guint16 *ftp_port,
450 guint *pasv_offset, guint *ftp_port_len)
452 gint n;
453 gchar *args;
454 gchar *p;
455 gchar *e;
456 guchar c;
457 gboolean ret = FALSE;
458 gboolean delimiters_seen = FALSE;
461 * Copy the rest of the line into a null-terminated buffer.
463 args = wmem_strndup(wmem_packet_scope(), line, linelen);
464 p = args;
467 * Look for ( <d> <d> <d>
468 (Try to cope with '(' in description)
470 for (; !delimiters_seen;) {
471 guchar delimiter = '\0';
472 while ((c = *p) != '\0' && (c != '('))
473 p++;
475 if (*p == '\0') {
476 return FALSE;
479 /* Skip '(' */
480 p++;
482 /* Make sure same delimiter is used 3 times */
483 for (n=0; n<3; n++) {
484 if ((c = *p) != '\0') {
485 if (delimiter == '\0' && isvalid_rfc2428_delimiter(c)) {
486 delimiter = c;
488 if (c != delimiter) {
489 break;
491 p++;
493 else {
494 break;
497 delimiters_seen = TRUE;
501 * Should now be at digits.
503 if (*p != '\0') {
505 * We didn't run out of text without finding anything.
507 *ftp_port = atoi(p);
508 *pasv_offset = (guint32)(p - args);
510 ret = TRUE;
512 /* get port string length */
513 if ((e=strchr(p,')')) == NULL) {
514 ret = FALSE;
516 else {
517 *ftp_port_len = (guint)(--e - p);
521 return ret;
525 static void
526 dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
528 gboolean is_request;
529 proto_tree *ftp_tree = NULL;
530 proto_tree *reqresp_tree = NULL;
531 proto_item *ti, *hidden_item;
532 gint offset;
533 const guchar *line;
534 guint32 code;
535 gchar code_str[4];
536 gboolean is_port_request = FALSE;
537 gboolean is_eprt_request = FALSE;
538 gboolean is_pasv_response = FALSE;
539 gboolean is_epasv_response = FALSE;
540 gint next_offset;
541 int linelen;
542 int tokenlen = 0;
543 const guchar *next_token;
544 guint32 pasv_ip;
545 guint32 pasv_offset;
546 guint32 ftp_ip;
547 guint32 ftp_ip_len;
548 guint32 eprt_offset;
549 guint32 eprt_af = 0;
550 guint32 eprt_ip;
551 guint16 eprt_ipv6[8];
552 guint32 eprt_ip_len = 0;
553 guint16 ftp_port;
554 guint32 ftp_port_len;
555 address ftp_ip_address;
556 gboolean ftp_nat;
557 conversation_t *conversation;
559 ftp_ip_address = pinfo->src;
561 if (pinfo->match_uint == pinfo->destport)
562 is_request = TRUE;
563 else
564 is_request = FALSE;
566 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP");
569 * Find the end of the first line.
571 * Note that "tvb_find_line_end()" will return a value that is
572 * not longer than what's in the buffer, so the "tvb_get_ptr()"
573 * call won't throw an exception.
575 linelen = tvb_find_line_end(tvb, 0, -1, &next_offset, FALSE);
576 line = tvb_get_ptr(tvb, 0, linelen);
579 * Put the first line from the buffer into the summary
580 * (but leave out the line terminator).
582 col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s",
583 is_request ? "Request" : "Response",
584 format_text(line, linelen));
586 ti = proto_tree_add_item(tree, proto_ftp, tvb, 0, -1, ENC_NA);
587 ftp_tree = proto_item_add_subtree(ti, ett_ftp);
589 if (is_request) {
590 hidden_item = proto_tree_add_boolean(ftp_tree,
591 hf_ftp_request, tvb, 0, 0, TRUE);
592 PROTO_ITEM_SET_HIDDEN(hidden_item);
593 hidden_item = proto_tree_add_boolean(ftp_tree,
594 hf_ftp_response, tvb, 0, 0, FALSE);
595 PROTO_ITEM_SET_HIDDEN(hidden_item);
596 } else {
597 hidden_item = proto_tree_add_boolean(ftp_tree,
598 hf_ftp_request, tvb, 0, 0, FALSE);
599 PROTO_ITEM_SET_HIDDEN(hidden_item);
600 hidden_item = proto_tree_add_boolean(ftp_tree,
601 hf_ftp_response, tvb, 0, 0, TRUE);
602 PROTO_ITEM_SET_HIDDEN(hidden_item);
605 /* Put the line into the protocol tree. */
606 ti = proto_tree_add_text(ftp_tree, tvb, 0, next_offset, "%s",
607 tvb_format_text(tvb, 0, next_offset));
608 reqresp_tree = proto_item_add_subtree(ti, ett_ftp_reqresp);
610 if (is_request) {
612 * Extract the first token, and, if there is a first
613 * token, add it as the request.
615 tokenlen = get_token_len(line, line + linelen, &next_token);
616 if (tokenlen != 0) {
617 proto_tree_add_item(reqresp_tree, hf_ftp_request_command,
618 tvb, 0, tokenlen, ENC_ASCII|ENC_NA);
619 if (strncmp(line, "PORT", tokenlen) == 0)
620 is_port_request = TRUE;
622 * EPRT request command, as per RFC 2428
624 else if (strncmp(line, "EPRT", tokenlen) == 0)
625 is_eprt_request = TRUE;
627 } else {
629 * This is a response; the response code is 3 digits,
630 * followed by a space or hyphen, possibly followed by
631 * text.
633 * If the line doesn't start with 3 digits, it's part of
634 * a continuation.
636 * XXX - keep track of state in the first pass, and
637 * treat non-continuation lines not beginning with digits
638 * as errors?
640 if (linelen >= 3 && isdigit(line[0]) && isdigit(line[1])
641 && isdigit(line[2])) {
643 * One-line reply, or first or last line
644 * of a multi-line reply.
646 tvb_get_nstringz0(tvb, 0, sizeof(code_str), code_str);
647 code = (guint32)strtoul(code_str, NULL, 10);
649 proto_tree_add_uint(reqresp_tree,
650 hf_ftp_response_code, tvb, 0, 3, code);
653 * See if it's a passive-mode response.
655 * XXX - does anybody do FOOBAR, as per RFC
656 * 1639, or has that been supplanted by RFC 2428?
658 if (code == 227)
659 is_pasv_response = TRUE;
662 * Responses to EPSV command, as per RFC 2428
664 if (code == 229)
665 is_epasv_response = TRUE;
668 * Skip the 3 digits and, if present, the
669 * space or hyphen.
671 if (linelen >= 4)
672 next_token = line + 4;
673 else
674 next_token = line + linelen;
675 } else {
677 * Line doesn't start with 3 digits; assume it's
678 * a line in the middle of a multi-line reply.
680 next_token = line;
684 offset = (gint) (next_token - line);
685 linelen -= (int) (next_token - line);
686 line = next_token;
689 * Add the rest of the first line as request or
690 * reply data.
692 if (linelen != 0) {
693 if (is_request) {
694 proto_tree_add_item(reqresp_tree,
695 hf_ftp_request_arg, tvb, offset,
696 linelen, ENC_ASCII|ENC_NA);
697 } else {
698 proto_tree_add_item(reqresp_tree,
699 hf_ftp_response_arg, tvb, offset,
700 linelen, ENC_ASCII|ENC_NA);
703 offset = next_offset;
706 * If this is a PORT request or a PASV response, handle it.
708 if (is_port_request) {
709 if (parse_port_pasv(line, linelen, &ftp_ip, &ftp_port, &pasv_offset, &ftp_ip_len, &ftp_port_len)) {
710 proto_tree_add_ipv4(reqresp_tree, hf_ftp_active_ip,
711 tvb, pasv_offset + (tokenlen+1) , ftp_ip_len, ftp_ip);
712 proto_tree_add_uint(reqresp_tree, hf_ftp_active_port,
713 tvb, pasv_offset + 1 + (tokenlen+1) + ftp_ip_len, ftp_port_len, ftp_port);
714 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4, (const guint8 *)&ftp_ip);
715 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src, &ftp_ip_address);
716 if (ftp_nat) {
717 proto_tree_add_boolean(reqresp_tree, hf_ftp_active_nat,
718 tvb, 0, 0, ftp_nat);
723 if (is_pasv_response) {
724 if (linelen != 0) {
726 * This frame contains a PASV response; set up a
727 * conversation for the data.
729 if (parse_port_pasv(line, linelen, &pasv_ip, &ftp_port, &pasv_offset, &ftp_ip_len, &ftp_port_len)) {
730 proto_tree_add_ipv4(reqresp_tree, hf_ftp_pasv_ip,
731 tvb, pasv_offset + 4, ftp_ip_len, pasv_ip);
732 proto_tree_add_uint(reqresp_tree, hf_ftp_pasv_port,
733 tvb, pasv_offset + 4 + 1 + ftp_ip_len, ftp_port_len, ftp_port);
734 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4,
735 (const guint8 *)&pasv_ip);
736 ftp_nat = !ADDRESSES_EQUAL(&pinfo->src, &ftp_ip_address);
737 if (ftp_nat) {
738 proto_tree_add_boolean(reqresp_tree, hf_ftp_pasv_nat,
739 tvb, 0, 0, ftp_nat);
743 * We use "ftp_ip_address", so that if
744 * we're NAT'd we look for the un-NAT'd
745 * connection.
747 * XXX - should this call to
748 * "find_conversation()" just use
749 * "ftp_ip_address" and "server_port", and
750 * wildcard everything else?
752 conversation = find_conversation(pinfo->fd->num, &ftp_ip_address,
753 &pinfo->dst, PT_TCP, ftp_port, 0,
754 NO_PORT_B);
755 if (conversation == NULL) {
757 * XXX - should this call to "conversation_new()"
758 * just use "ftp_ip_address" and "server_port",
759 * and wildcard everything else?
761 * XXX - what if we did find a conversation? As
762 * we create it only on the first pass through the
763 * packets, if we find one, it's presumably an
764 * unrelated conversation. Should we remove the
765 * old one from the hash table and put this one in
766 * its place? Can the conversation code handle
767 * conversations not in the hash table? Or should
768 * we make conversations support start and end
769 * frames, as circuits do, and treat this as an
770 * indication that one conversation was closed and
771 * a new one was opened?
773 conversation = conversation_new(
774 pinfo->fd->num, &ftp_ip_address, &pinfo->dst,
775 PT_TCP, ftp_port, 0, NO_PORT2);
776 conversation_set_dissector(conversation, ftpdata_handle);
782 if (is_eprt_request) {
784 * RFC2428 - sect. 2
785 * This frame contains a EPRT request; let's dissect it and set up a
786 * conversation for the data connection.
788 if (parse_eprt_request(line, linelen,
789 &eprt_af, &eprt_ip, eprt_ipv6, &ftp_port,
790 &eprt_ip_len, &ftp_port_len)) {
792 /* since parse_eprt_request() returned TRUE,
793 we know that we have a valid address family */
794 eprt_offset = tokenlen + 1 + 1; /* token, space, 1st delimiter */
795 proto_tree_add_uint(reqresp_tree, hf_ftp_eprt_af, tvb,
796 eprt_offset, 1, eprt_af);
797 eprt_offset += 1 + 1; /* addr family, 2nd delimiter */
799 if (eprt_af == EPRT_AF_IPv4) {
800 proto_tree_add_ipv4(reqresp_tree, hf_ftp_eprt_ip,
801 tvb, eprt_offset, eprt_ip_len, eprt_ip);
802 SET_ADDRESS(&ftp_ip_address, AT_IPv4, 4,
803 (const guint8 *)&eprt_ip);
805 else if (eprt_af == EPRT_AF_IPv6) {
806 proto_tree_add_ipv6(reqresp_tree, hf_ftp_eprt_ipv6,
807 tvb, eprt_offset, eprt_ip_len, (const guint8 *)eprt_ipv6);
808 SET_ADDRESS(&ftp_ip_address, AT_IPv6, 16,
809 (const guint8 *)&eprt_ipv6);
811 eprt_offset += eprt_ip_len + 1; /* addr, 3rd delimiter */
813 proto_tree_add_uint(reqresp_tree, hf_ftp_eprt_port,
814 tvb, eprt_offset, ftp_port_len, ftp_port);
816 /* Find/create conversation for data */
817 conversation = find_conversation(pinfo->fd->num,
818 &pinfo->src, &ftp_ip_address,
819 PT_TCP, ftp_port, 0, NO_PORT_B);
820 if (conversation == NULL) {
821 conversation = conversation_new(
822 pinfo->fd->num, &pinfo->src, &ftp_ip_address,
823 PT_TCP, ftp_port, 0, NO_PORT2);
824 conversation_set_dissector(conversation,
825 ftpdata_handle);
828 else {
829 proto_item *item;
830 item = proto_tree_add_text(reqresp_tree,
831 tvb, offset - linelen - 1, linelen, "Invalid EPRT arguments");
832 expert_add_info(pinfo, item, &ei_ftp_eprt_args_invalid);
836 if (is_epasv_response) {
837 if (linelen != 0) {
838 proto_item *addr_it;
840 * RFC2428 - sect. 3
841 * This frame contains an EPSV response; set up a
842 * conversation for the data.
844 if (parse_extended_pasv_response(line, linelen,
845 &ftp_port, &pasv_offset, &ftp_port_len)) {
846 /* Add IP address and port number to tree */
848 if (ftp_ip_address.type == AT_IPv4) {
849 guint32 addr;
850 memcpy(&addr, ftp_ip_address.data, 4);
851 addr_it = proto_tree_add_ipv4(reqresp_tree,
852 hf_ftp_epsv_ip, tvb, 0, 0, addr);
853 PROTO_ITEM_SET_GENERATED(addr_it);
855 else if (ftp_ip_address.type == AT_IPv6) {
856 addr_it = proto_tree_add_ipv6(reqresp_tree,
857 hf_ftp_epsv_ipv6, tvb, 0, 0,
858 (guint8*)ftp_ip_address.data);
859 PROTO_ITEM_SET_GENERATED(addr_it);
862 proto_tree_add_uint(reqresp_tree,
863 hf_ftp_epsv_port, tvb, pasv_offset + 4,
864 ftp_port_len, ftp_port);
866 /* Find/create conversation for data */
867 conversation = find_conversation(pinfo->fd->num, &ftp_ip_address,
868 &pinfo->dst, PT_TCP, ftp_port, 0,
869 NO_PORT_B);
870 if (conversation == NULL) {
871 conversation = conversation_new(
872 pinfo->fd->num, &ftp_ip_address, &pinfo->dst,
873 PT_TCP, ftp_port, 0, NO_PORT2);
874 conversation_set_dissector(conversation,
875 ftpdata_handle);
878 else {
879 proto_item *item;
880 item = proto_tree_add_text(reqresp_tree,
881 tvb, offset - linelen - 1, linelen, "Invalid EPSV arguments");
882 expert_add_info(pinfo, item, &ei_ftp_epsv_args_invalid);
888 * Show the rest of the request or response as text,
889 * a line at a time.
890 * XXX - only if there's a continuation indicator?
892 while (tvb_offset_exists(tvb, offset)) {
894 * Find the end of the line.
896 tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
899 * Put this line.
901 proto_tree_add_text(ftp_tree, tvb, offset,
902 next_offset - offset, "%s",
903 tvb_format_text(tvb, offset, next_offset - offset));
904 offset = next_offset;
908 static void
909 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
911 proto_item *ti;
912 int data_length;
913 gboolean is_text = TRUE;
914 gint check_chars, i;
916 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP-DATA");
918 col_add_fstr(pinfo->cinfo, COL_INFO, "FTP Data: %u bytes",
919 tvb_reported_length(tvb));
921 data_length = tvb_length(tvb);
923 ti = proto_tree_add_item(tree, proto_ftp_data, tvb, 0, -1, ENC_NA);
925 /* Check the first few chars to see whether it looks like a text file or not */
926 check_chars = MIN(10, data_length);
927 for (i=0; i < check_chars; i++) {
928 if (!isprint(tvb_get_guint8(tvb, i))) {
929 is_text = FALSE;
930 break;
934 if (is_text) {
935 /* Show as string, but don't format more text than will be displayed */
936 proto_item_append_text(ti, " (%s)", tvb_format_text(tvb, 0, MIN(data_length, ITEM_LABEL_LENGTH)));
938 else {
939 /* Assume binary, just show the number of bytes */
940 proto_item_append_text(ti, " (%u bytes data)", data_length);
944 void
945 proto_register_ftp(void)
947 static hf_register_info hf[] = {
948 { &hf_ftp_response,
949 { "Response", "ftp.response",
950 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
951 "TRUE if FTP response", HFILL }},
953 { &hf_ftp_request,
954 { "Request", "ftp.request",
955 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
956 "TRUE if FTP request", HFILL }},
958 { &hf_ftp_request_command,
959 { "Request command", "ftp.request.command",
960 FT_STRING, BASE_NONE, NULL, 0x0,
961 NULL, HFILL }},
963 { &hf_ftp_request_arg,
964 { "Request arg", "ftp.request.arg",
965 FT_STRING, BASE_NONE, NULL, 0x0,
966 NULL, HFILL }},
968 { &hf_ftp_response_code,
969 { "Response code", "ftp.response.code",
970 FT_UINT32, BASE_DEC|BASE_EXT_STRING, &response_table_ext, 0x0,
971 NULL, HFILL }},
973 { &hf_ftp_response_arg,
974 { "Response arg", "ftp.response.arg",
975 FT_STRING, BASE_NONE, NULL, 0x0,
976 NULL, HFILL }},
978 { &hf_ftp_pasv_ip,
979 { "Passive IP address", "ftp.passive.ip",
980 FT_IPv4, BASE_NONE, NULL,0x0,
981 "Passive IP address (check NAT)", HFILL}},
983 { &hf_ftp_pasv_port,
984 { "Passive port", "ftp.passive.port",
985 FT_UINT16, BASE_DEC, NULL,0x0,
986 "Passive FTP server port", HFILL }},
988 { &hf_ftp_pasv_nat,
989 {"Passive IP NAT", "ftp.passive.nat",
990 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
991 "NAT is active SIP and passive IP different", HFILL }},
993 { &hf_ftp_active_ip,
994 { "Active IP address", "ftp.active.cip",
995 FT_IPv4, BASE_NONE, NULL, 0x0,
996 "Active FTP client IP address", HFILL }},
998 { &hf_ftp_active_port,
999 {"Active port", "ftp.active.port",
1000 FT_UINT16, BASE_DEC, NULL, 0x0,
1001 "Active FTP client port", HFILL }},
1003 { &hf_ftp_active_nat,
1004 { "Active IP NAT", "ftp.active.nat",
1005 FT_BOOLEAN, BASE_NONE, NULL, 0x0,
1006 "NAT is active", HFILL}},
1008 { &hf_ftp_eprt_af,
1009 { "Extended active address family", "ftp.eprt.af",
1010 FT_UINT8, BASE_DEC, VALS(eprt_af_vals), 0,
1011 NULL, HFILL }},
1013 { &hf_ftp_eprt_ip,
1014 { "Extended active IP address", "ftp.eprt.ip",
1015 FT_IPv4, BASE_NONE, NULL, 0,
1016 "Extended active FTP client IPv4 address", HFILL }},
1018 { &hf_ftp_eprt_ipv6,
1019 { "Extended active IPv6 address", "ftp.eprt.ipv6",
1020 FT_IPv6, BASE_NONE, NULL, 0,
1021 "Extended active FTP client IPv6 address", HFILL }},
1023 { &hf_ftp_eprt_port,
1024 { "Extended active port", "ftp.eprt.port",
1025 FT_UINT16, BASE_DEC, NULL, 0,
1026 "Extended active FTP client listener port", HFILL }},
1028 { &hf_ftp_epsv_ip,
1029 { "Extended passive IPv4 address", "ftp.epsv.ip",
1030 FT_IPv4, BASE_NONE, NULL, 0,
1031 "Extended passive FTP server IPv4 address", HFILL }},
1033 { &hf_ftp_epsv_ipv6,
1034 { "Extended passive IPv6 address", "ftp.epsv.ipv6",
1035 FT_IPv6, BASE_NONE, NULL, 0,
1036 "Extended passive FTP server IPv6 address", HFILL }},
1038 { &hf_ftp_epsv_port,
1039 { "Extended passive port", "ftp.epsv.port",
1040 FT_UINT16, BASE_DEC, NULL, 0,
1041 "Extended passive FTP server port", HFILL }}
1044 static gint *ett[] = {
1045 &ett_ftp,
1046 &ett_ftp_reqresp
1049 static ei_register_info ei[] = {
1050 { &ei_ftp_eprt_args_invalid, { "ftp.eprt.args_invalid", PI_MALFORMED, PI_WARN, "EPRT arguments must have the form: |<family>|<addr>|<port>|", EXPFILL }},
1051 { &ei_ftp_epsv_args_invalid, { "ftp.epsv.args_invalid", PI_MALFORMED, PI_WARN, "EPSV arguments must have the form (|||<port>|)", EXPFILL }},
1054 expert_module_t* expert_ftp;
1056 proto_ftp = proto_register_protocol("File Transfer Protocol (FTP)", "FTP", "ftp");
1058 register_dissector("ftp", dissect_ftp, proto_ftp);
1059 proto_ftp_data = proto_register_protocol("FTP Data", "FTP-DATA", "ftp-data");
1060 register_dissector("ftp-data", dissect_ftpdata, proto_ftp_data);
1061 proto_register_field_array(proto_ftp, hf, array_length(hf));
1062 proto_register_subtree_array(ett, array_length(ett));
1063 expert_ftp = expert_register_protocol(proto_ftp);
1064 expert_register_field_array(expert_ftp, ei, array_length(ei));
1067 void
1068 proto_reg_handoff_ftp(void)
1070 dissector_handle_t ftp_handle;
1072 ftpdata_handle = find_dissector("ftp-data");
1073 dissector_add_uint("tcp.port", TCP_PORT_FTPDATA, ftpdata_handle);
1074 ftp_handle = find_dissector("ftp");
1075 dissector_add_uint("tcp.port", TCP_PORT_FTP, ftp_handle);
1079 * Editor modelines - http://www.wireshark.org/tools/modelines.html
1081 * Local variables:
1082 * c-basic-offset: 4
1083 * tab-width: 4
1084 * indent-tabs-mode: nil
1085 * End:
1087 * vi: set shiftwidth=4 tabstop=4 expandtab:
1088 * :indentSize=4:tabSize=4:noTabs=true: