Align the --with-pcap help message with other help messages.
[obnox/wireshark/wip.git] / packet-ftp.c
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)
5  *
6  * $Id: packet-ftp.c,v 1.52 2003/06/10 23:12:15 guy Exp $
7  *
8  * Ethereal - Network traffic analyzer
9  * By Gerald Combs <gerald@ethereal.com>
10  * Copyright 1998 Gerald Combs
11  *
12  * Copied from packet-pop.c
13  *
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.
18  *
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.
23  *
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.
27  */
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <ctype.h>
36
37 #include <string.h>
38 #include <glib.h>
39 #include <epan/packet.h>
40 #include <epan/strutil.h>
41 #include <epan/conversation.h>
42
43 static int proto_ftp = -1;
44 static int proto_ftp_data = -1;
45 static int hf_ftp_response = -1;
46 static int hf_ftp_request = -1;
47 static int hf_ftp_request_command = -1;
48 static int hf_ftp_request_arg = -1;
49 static int hf_ftp_response_code = -1;
50 static int hf_ftp_response_arg = -1;
51
52 static gint ett_ftp = -1;
53 static gint ett_ftp_reqresp = -1;
54 static gint ett_ftp_data = -1;
55
56 static dissector_handle_t ftpdata_handle;
57
58 #define TCP_PORT_FTPDATA                20
59 #define TCP_PORT_FTP                    21
60
61 static const value_string response_table[] = {
62         { 110, "Restart marker reply" },
63         { 120, "Service ready in nnn minutes" },
64         { 125, "Data connection already open; transfer starting" },
65         { 150, "File status okay; about to open data connection" },
66         { 200, "Command okay" },
67         { 202, "Command not implemented, superfluous at this site" },
68         { 211, "System status, or system help reply" },
69         { 212, "Directory status" },
70         { 213, "File status" },
71         { 214, "Help message" },
72         { 215, "NAME system type" },
73         { 220, "Service ready for new user" },
74         { 221, "Service closing control connection" },
75         { 225, "Data connection open; no transfer in progress" },
76         { 226, "Closing data connection" },
77         { 227, "Entering Passive Mode" },
78         { 230, "User logged in, proceed" },
79         { 250, "Requested file action okay, completed" },
80         { 257, "PATHNAME created" },
81         { 331, "User name okay, need password" },
82         { 332, "Need account for login" },
83         { 350, "Requested file action pending further information" },
84         { 421, "Service not available, closing control connection" },
85         { 425, "Can't open data connection" },
86         { 426, "Connection closed; transfer aborted" },
87         { 450, "Requested file action not taken" },
88         { 451, "Requested action aborted: local error in processing" },
89         { 452, "Requested action not taken. Insufficient storage space in system" },
90         { 500, "Syntax error, command unrecognized" },
91         { 501, "Syntax error in parameters or arguments" },
92         { 502, "Command not implemented" },
93         { 503, "Bad sequence of commands" },
94         { 504, "Command not implemented for that parameter" },
95         { 530, "Not logged in" },
96         { 532, "Need account for storing files" },
97         { 550, "Requested action not taken: File unavailable" },
98         { 551, "Requested action aborted: page type unknown" },
99         { 552, "Requested file action aborted: Exceeded storage allocation" },
100         { 553, "Requested action not taken: File name not allowed" },
101         { 0,   NULL }
102 };
103                 
104 static void
105 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree);
106
107 /*
108  * Handle a response to a PASV command.
109  *
110  * We ignore the IP address in the reply, and use the address from which
111  * the request came.
112  *
113  * XXX - are there cases where they differ?  What if the FTP server is
114  * behind a NAT box, so that the address it puts into the reply isn't
115  * the address at which you should contact it?  Do all NAT boxes detect
116  * FTP PASV replies and rewrite the address?  (I suspect not.)
117  *
118  * RFC 959 doesn't say much about the syntax of the 227 reply.
119  *
120  * A proposal from Dan Bernstein at
121  *
122  *      http://cr.yp.to/ftp/retr.html
123  *
124  * "recommend[s] that clients use the following strategy to parse the
125  * response line: look for the first digit after the initial space; look
126  * for the fourth comma after that digit; read two (possibly negative)
127  * integers, separated by a comma; the TCP port number is p1*256+p2, where
128  * p1 is the first integer modulo 256 and p2 is the second integer modulo
129  * 256."
130  *
131  * wget 1.5.3 looks for a digit, although it doesn't handle negative
132  * integers.
133  *
134  * The FTP code in the source of the cURL library, at
135  *
136  *      http://curl.haxx.se/lxr/source/lib/ftp.c
137  *
138  * says that cURL "now scans for a sequence of six comma-separated numbers
139  * and will take them as IP+port indicators"; it loops, doing "sscanf"s
140  * looking for six numbers separated by commas, stepping the start pointer
141  * in the scanf one character at a time - i.e., it tries rather exhaustively.
142  *
143  * An optimization would be to scan for a digit, and start there, and if
144  * the scanf doesn't find six values, scan for the next digit and try
145  * again; this will probably succeed on the first try.
146  *
147  * The cURL code also says that "found reply-strings include":
148  *
149  *      "227 Entering Passive Mode (127,0,0,1,4,51)"
150  *      "227 Data transfer will passively listen to 127,0,0,1,4,51"
151  *      "227 Entering passive mode. 127,0,0,1,4,51"
152  *
153  * so it appears that you can't assume there are parentheses around
154  * the address and port number.
155  */
156 static void
157 handle_pasv_response(const guchar *line, int linelen, packet_info *pinfo)
158 {
159         char *args;
160         char *p;
161         guchar c;
162         int i;
163         int address[4], port[2];
164         guint16 server_port;
165         conversation_t  *conversation;
166
167         /*
168          * Copy the rest of the line into a null-terminated buffer.
169          */
170         args = g_malloc(linelen + 1);
171         memcpy(args, line, linelen);
172         args[linelen] = '\0';
173         p = args;
174
175         for (;;) {
176                 /*
177                  * Look for a digit.
178                  */
179                 while ((c = *p) != '\0' && !isdigit(c))
180                         p++;
181
182                 if (*p == '\0') {
183                         /*
184                          * We ran out of text without finding anything.
185                          */
186                         break;
187                 }
188
189                 /*
190                  * See if we have six numbers.
191                  */
192                 i = sscanf(p, "%d,%d,%d,%d,%d,%d",
193                     &address[0], &address[1], &address[2], &address[3],
194                     &port[0], &port[1]);
195                 if (i == 6) {
196                         /*
197                          * We have a winner!
198                          * Set up a conversation, to be dissected as FTP data.
199                          */
200                         server_port = ((port[0] & 0xFF)<<8) | (port[1] & 0xFF);
201
202                         /*
203                          * XXX - should this call to "find_conversation()"
204                          * just use "pinfo->src" and "server_port", and
205                          * wildcard everything else?
206                          */
207                         conversation = find_conversation(&pinfo->src,
208                             &pinfo->dst, PT_TCP, server_port, 0, NO_PORT_B);
209                         if (conversation == NULL) {
210                                 /*
211                                  * XXX - should this call to
212                                  * "conversation_new()" just use "pinfo->src"
213                                  * and "server_port", and wildcard everything
214                                  * else?
215                                  *
216                                  * XXX - what if we did find a conversation?
217                                  * As we create it only on the first pass
218                                  * through the packets, if we find one, it's
219                                  * presumably an unrelated conversation.
220                                  * Should we remove the old one from the hash
221                                  * table and put this one in its place?
222                                  * Can the conversaton code handle
223                                  * conversations not in the hash table?
224                                  */
225                                 conversation = conversation_new(&pinfo->src,
226                                     &pinfo->dst, PT_TCP, server_port, 0,
227                                     NO_PORT2);
228                                 conversation_set_dissector(conversation,
229                                     ftpdata_handle);
230                         }
231                         break;
232                 }
233
234                 /*
235                  * Well, that didn't work.  Skip the first number we found,
236                  * and keep trying.
237                  */
238                 while ((c = *p) != '\0' && isdigit(c))
239                         p++;
240         }
241
242         g_free(args);
243 }
244
245 static void
246 dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
247 {
248         gboolean        is_request;
249         proto_tree      *ftp_tree = NULL;
250         proto_tree      *reqresp_tree = NULL;
251         proto_item      *ti;
252         gint            offset = 0;
253         const guchar    *line;
254         guint32         code;
255         gchar           code_str[4];
256         gboolean        is_pasv_response = FALSE;
257         gint            next_offset;
258         int             linelen;
259         int             tokenlen;
260         const guchar    *next_token;
261
262         if (pinfo->match_port == pinfo->destport)
263                 is_request = TRUE;
264         else
265                 is_request = FALSE;
266
267         if (check_col(pinfo->cinfo, COL_PROTOCOL))
268                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP");
269
270         /*
271          * Find the end of the first line.
272          *
273          * Note that "tvb_find_line_end()" will return a value that is
274          * not longer than what's in the buffer, so the "tvb_get_ptr()"
275          * call won't throw an exception.
276          */
277         linelen = tvb_find_line_end(tvb, offset, -1, &next_offset, FALSE);
278         line = tvb_get_ptr(tvb, offset, linelen);
279
280         if (check_col(pinfo->cinfo, COL_INFO)) {
281                 /*
282                  * Put the first line from the buffer into the summary
283                  * (but leave out the line terminator).
284                  */
285                 col_add_fstr(pinfo->cinfo, COL_INFO, "%s: %s",
286                     is_request ? "Request" : "Response",
287                     format_text(line, linelen));
288         }
289
290         if (tree) {
291                 ti = proto_tree_add_item(tree, proto_ftp, tvb, offset, -1,
292                     FALSE);
293                 ftp_tree = proto_item_add_subtree(ti, ett_ftp);
294
295                 if (is_request) {
296                         proto_tree_add_boolean_hidden(ftp_tree,
297                             hf_ftp_request, tvb, 0, 0, TRUE);
298                         proto_tree_add_boolean_hidden(ftp_tree,
299                             hf_ftp_response, tvb, 0, 0, FALSE);
300                 } else {
301                         proto_tree_add_boolean_hidden(ftp_tree,
302                             hf_ftp_request, tvb, 0, 0, FALSE);
303                         proto_tree_add_boolean_hidden(ftp_tree,
304                             hf_ftp_response, tvb, 0, 0, TRUE);
305                 }
306
307                 /*
308                  * Put the line into the protocol tree.
309                  */
310                 ti = proto_tree_add_text(ftp_tree, tvb, offset,
311                     next_offset - offset, "%s",
312                     tvb_format_text(tvb, offset, next_offset - offset));
313                 reqresp_tree = proto_item_add_subtree(ti, ett_ftp_reqresp);
314         }
315
316         if (is_request) {
317                 /*
318                  * Extract the first token, and, if there is a first
319                  * token, add it as the request.
320                  */
321                 tokenlen = get_token_len(line, line + linelen, &next_token);
322                 if (tokenlen != 0) {
323                         if (tree) {
324                                 proto_tree_add_item(reqresp_tree,
325                                     hf_ftp_request_command, tvb, offset,
326                                     tokenlen, FALSE);
327                         }
328                 }
329         } else {
330                 /*
331                  * This is a response; the response code is 3 digits,
332                  * followed by a space or hyphen, possibly followed by
333                  * text.
334                  *
335                  * If the line doesn't start with 3 digits, it's part of
336                  * a continuation.
337                  *
338                  * XXX - keep track of state in the first pass, and
339                  * treat non-continuation lines not beginning with digits
340                  * as errors?
341                  */
342                 if (linelen >= 3 && isdigit(line[0]) && isdigit(line[1])
343                     && isdigit(line[2])) {
344                         /*
345                          * One-line reply, or first or last line
346                          * of a multi-line reply.
347                          */
348                         tvb_get_nstringz0(tvb, offset, sizeof(code_str), code_str);
349                         code = strtoul(code_str, NULL, 10);
350                                 
351                         if (tree) {
352                                 proto_tree_add_uint(reqresp_tree,
353                                     hf_ftp_response_code, tvb, offset, 3, code);
354                         }
355
356                         /*
357                          * See if it's a passive-mode response.
358                          *
359                          * XXX - check for "229" responses to EPSV
360                          * commands, to handle IPv6, as per RFC 2428?
361                          *
362                          * XXX - does anybody do FOOBAR, as per RFC
363                          * 1639, or has that been supplanted by RFC 2428?
364                          */
365                         if (code == 227)
366                                 is_pasv_response = TRUE;
367
368                         /*
369                          * Skip the 3 digits and, if present, the
370                          * space or hyphen.
371                          */
372                         if (linelen >= 4)
373                                 next_token = line + 4;
374                         else
375                                 next_token = line + linelen;
376                 } else {
377                         /*
378                          * Line doesn't start with 3 digits; assume it's
379                          * a line in the middle of a multi-line reply.
380                          */
381                         next_token = line;
382                 }
383         }
384         offset += next_token - line;
385         linelen -= next_token - line;
386         line = next_token;
387
388         /*
389          * If this is a PASV response, handle it if we haven't
390          * already processed this frame.
391          */
392         if (!pinfo->fd->flags.visited && is_pasv_response) {
393                 if (linelen != 0) {
394                         /*
395                          * We haven't processed this frame, and it contains
396                          * a PASV response; set up a conversation for the
397                          * data.
398                          */
399                         handle_pasv_response(line, linelen, pinfo);
400                 }
401         }
402
403         if (tree) {
404                 /*
405                  * Add the rest of the first line as request or
406                  * reply data.
407                  */
408                 if (linelen != 0) {
409                         if (is_request) {
410                                 proto_tree_add_item(reqresp_tree,
411                                     hf_ftp_request_arg, tvb, offset,
412                                     linelen, FALSE);
413                         } else {
414                                 proto_tree_add_item(reqresp_tree,
415                                     hf_ftp_response_arg, tvb, offset,
416                                     linelen, FALSE);
417                         }
418                 }
419                 offset = next_offset;
420
421                 /*
422                  * Show the rest of the request or response as text,
423                  * a line at a time.
424                  * XXX - only if there's a continuation indicator?
425                  */
426                 while (tvb_offset_exists(tvb, offset)) {
427                         /*
428                          * Find the end of the line.
429                          */
430                         linelen = tvb_find_line_end(tvb, offset, -1,
431                             &next_offset, FALSE);
432
433                         /*
434                          * Put this line.
435                          */
436                         proto_tree_add_text(ftp_tree, tvb, offset,
437                             next_offset - offset, "%s",
438                             tvb_format_text(tvb, offset, next_offset - offset));
439                         offset = next_offset;
440                 }
441         }
442 }
443
444 static void
445 dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
446 {
447         proto_tree      *ti, *ftp_data_tree;
448         int             data_length;
449
450         if (check_col(pinfo->cinfo, COL_PROTOCOL))
451                 col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP-DATA");
452
453         if (check_col(pinfo->cinfo, COL_INFO)) {
454                 col_add_fstr(pinfo->cinfo, COL_INFO, "FTP Data: %u bytes",
455                     tvb_reported_length(tvb));
456         }
457
458         if (tree) {
459                 data_length = tvb_length(tvb);
460
461                 ti = proto_tree_add_item(tree, proto_ftp_data, tvb, 0, -1,
462                     FALSE);
463                 ftp_data_tree = proto_item_add_subtree(ti, ett_ftp_data);
464
465                 /*
466                  * XXX - if this is binary data, it'll produce
467                  * a *really* long line.
468                  */
469                 proto_tree_add_text(ftp_data_tree, tvb, 0, data_length,
470                     "FTP Data: %s", tvb_format_text(tvb, 0, data_length));
471         }
472 }
473
474 void
475 proto_register_ftp(void)
476 {
477     static hf_register_info hf[] = {
478     { &hf_ftp_response,
479       { "Response",           "ftp.response",
480         FT_BOOLEAN, BASE_NONE, NULL, 0x0,
481         "TRUE if FTP response", HFILL }},
482
483     { &hf_ftp_request,
484       { "Request",            "ftp.request",
485         FT_BOOLEAN, BASE_NONE, NULL, 0x0,
486         "TRUE if FTP request", HFILL }},
487
488     { &hf_ftp_request_command,
489       { "Request command",    "ftp.request.command",
490         FT_STRING,  BASE_NONE, NULL, 0x0,
491         "", HFILL }},
492
493     { &hf_ftp_request_arg,
494       { "Request arg",        "ftp.request.arg",
495         FT_STRING,  BASE_NONE, NULL, 0x0,
496         "", HFILL }},
497
498     { &hf_ftp_response_code,
499       { "Response code",      "ftp.response.code",
500         FT_UINT32,   BASE_DEC, VALS(response_table), 0x0,
501         "", HFILL }},
502
503     { &hf_ftp_response_arg,
504       { "Response arg",      "ftp.response.arg",
505         FT_STRING,  BASE_NONE, NULL, 0x0,
506         "", HFILL }}
507   };
508   static gint *ett[] = {
509     &ett_ftp,
510     &ett_ftp_reqresp,
511     &ett_ftp_data,
512   };
513
514   proto_ftp = proto_register_protocol("File Transfer Protocol (FTP)", "FTP",
515                                       "ftp");
516   proto_ftp_data = proto_register_protocol("FTP Data", "FTP-DATA", "ftp-data");
517   proto_register_field_array(proto_ftp, hf, array_length(hf));
518   proto_register_subtree_array(ett, array_length(ett));
519
520   ftpdata_handle = create_dissector_handle(dissect_ftpdata, proto_ftp_data);
521 }
522
523 void
524 proto_reg_handoff_ftp(void)
525 {
526   dissector_handle_t ftpdata_handle, ftp_handle;
527
528   ftpdata_handle = create_dissector_handle(dissect_ftpdata, proto_ftp_data);
529   dissector_add("tcp.port", TCP_PORT_FTPDATA, ftpdata_handle);
530   ftp_handle = create_dissector_handle(dissect_ftp, proto_ftp);
531   dissector_add("tcp.port", TCP_PORT_FTP, ftp_handle);
532 }