aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-ftp.c
diff options
context:
space:
mode:
authorMartin Mathieson <martin.r.mathieson@googlemail.com>2017-07-16 19:02:41 +0100
committerAnders Broman <a.broman58@gmail.com>2017-08-04 05:54:48 +0000
commit936a8f1cf8c314993f7fca6f193764cb53652924 (patch)
treeb2d65f0e3f21924343c45af4c3ad14a9e7877cb0 /epan/dissectors/packet-ftp.c
parente3d0bf0bb2aea5e6f18cce32ad0e75e14169c413 (diff)
FTP: track current working directory and show as generated field
Change-Id: Ib43f0a7c96f4a2dfd931224b536a8ff5d17b0a36 Reviewed-on: https://code.wireshark.org/review/22651 Petri-Dish: Martin Mathieson <martin.r.mathieson@googlemail.com> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'epan/dissectors/packet-ftp.c')
-rw-r--r--epan/dissectors/packet-ftp.c374
1 files changed, 348 insertions, 26 deletions
diff --git a/epan/dissectors/packet-ftp.c b/epan/dissectors/packet-ftp.c
index e620e489af..f90f6f41bf 100644
--- a/epan/dissectors/packet-ftp.c
+++ b/epan/dissectors/packet-ftp.c
@@ -34,12 +34,14 @@
#include <epan/conversation.h>
#include <epan/expert.h>
#include <epan/addr_resolv.h>
+#include <epan/proto_data.h>
void proto_register_ftp(void);
void proto_reg_handoff_ftp(void);
static int proto_ftp = -1;
static int proto_ftp_data = -1;
+static int hf_ftp_current_working_directory = -1;
static int hf_ftp_response = -1;
static int hf_ftp_request = -1;
static int hf_ftp_request_command = -1;
@@ -62,6 +64,8 @@ static int hf_ftp_epsv_port = -1;
static int hf_ftp_data_setup_frame = -1;
static int hf_ftp_data_setup_method = -1;
+static int hf_ftp_data_command = -1;
+static int hf_ftp_data_current_working_directory = -1;
static gint ett_ftp = -1;
static gint ett_ftp_reqresp = -1;
@@ -69,6 +73,7 @@ static gint ett_ftp_reqresp = -1;
static expert_field ei_ftp_eprt_args_invalid = EI_INIT;
static expert_field ei_ftp_epsv_args_invalid = EI_INIT;
static expert_field ei_ftp_response_code_invalid = EI_INIT;
+static expert_field ei_ftp_pwd_response_invalid = EI_INIT;
static dissector_handle_t ftpdata_handle;
static dissector_handle_t ftp_handle;
@@ -146,18 +151,48 @@ static const value_string eprt_af_vals[] = {
{ 0, NULL }
};
+/********************************************************************/
+/* Storing session state and linking between control (ftp) and data */
+/* data (ftp-data) conversations */
+
typedef struct ftp_data_conversation_t
{
- const gchar *description; /* Command that this data answers */
- const gchar *setup_method; /* Type of command used to set up data conversation */
+ const gchar *command; /* Command that this data answers */
+ const gchar *setup_method; /* Type of command used to set up data conversation */
+ wmem_strbuf_t *current_working_directory;
} ftp_data_conversation_t;
+typedef struct ftp_packet_data_t
+{
+ wmem_strbuf_t *current_working_directory;
+} ftp_packet_data_t;
+
typedef struct ftp_conversation_t
{
const gchar *last_command;
+ wmem_strbuf_t *current_working_directory;
ftp_data_conversation_t *current_data_conv;
} ftp_conversation_t;
+/* For a given packet, retrieve or initialise a new conversation, and return it */
+static ftp_conversation_t *find_or_create_ftp_conversation(packet_info *pinfo)
+{
+ /* Create control conversation if necessary */
+ conversation_t *conv = find_or_create_conversation(pinfo);
+ ftp_conversation_t *p_ftp_conv;
+
+ /* Control conversation data */
+ p_ftp_conv = (ftp_conversation_t *)conversation_get_proto_data(conv, proto_ftp);
+ if (!p_ftp_conv) {
+ p_ftp_conv = wmem_new0(wmem_file_scope(), ftp_conversation_t);
+ /* Start with an empty string - assume relative path unless/until find out differently. */
+ p_ftp_conv->current_working_directory = wmem_strbuf_new(wmem_file_scope(), "");
+ conversation_add_proto_data(conv, proto_ftp, p_ftp_conv);
+ }
+
+ return p_ftp_conv;
+}
+
/* When new data conversation is being created, should:
* - create data conversation
* - create control conversation, and have it point at control conversation
@@ -173,16 +208,8 @@ static void create_and_link_data_conversation(packet_info *pinfo,
if (pinfo->fd->flags.visited) {
return;
}
- /* Create control conversation if necessary */
- conversation_t *conv = find_or_create_conversation(pinfo);
- ftp_conversation_t *p_ftp_conv;
- /* Control conversation data */
- p_ftp_conv = (ftp_conversation_t *)conversation_get_proto_data(conv, proto_ftp);
- if (!p_ftp_conv) {
- p_ftp_conv = wmem_new0(wmem_file_scope(), ftp_conversation_t);
- conversation_add_proto_data(conv, proto_ftp, p_ftp_conv);
- }
+ ftp_conversation_t *p_ftp_conv = find_or_create_ftp_conversation(pinfo);
/* Create data conversation and set dissector */
ftp_data_conversation_t *p_ftp_data_conv;
@@ -196,13 +223,16 @@ static void create_and_link_data_conversation(packet_info *pinfo,
/* Allocate data for data conversation. Note that control conversation will update it with commands. */
p_ftp_data_conv = wmem_new0(wmem_file_scope(), ftp_data_conversation_t);
p_ftp_data_conv->setup_method = method;
+ /* Copy snapshot of what cwd is at this point */
+ p_ftp_data_conv->current_working_directory = p_ftp_conv->current_working_directory;
- /* Point control conversation at current data converstaion */
+ /* Point control conversation at current data conversation */
conversation_add_proto_data(data_conversation, proto_ftp_data,
p_ftp_data_conv);
p_ftp_conv->current_data_conv = p_ftp_data_conv;
}
+/********************************************************************/
/*
@@ -584,6 +614,212 @@ parse_extended_pasv_response(const guchar *line, gint linelen, guint16 *ftp_port
return ret;
}
+/* Get the last character out of a string */
+static gchar wmem_strbuf_get_last_char(wmem_strbuf_t *string)
+{
+ gsize len = wmem_strbuf_get_len(string);
+ if (len > 0) {
+ const gchar *buf = wmem_strbuf_get_str(string);
+ return buf[len-1];
+ }
+ else {
+ /* Error */
+ return '\0';
+ }
+}
+
+/* Get the nth character out of string */
+static gchar wmem_strbuf_get_char_n(wmem_strbuf_t *string, size_t n)
+{
+ if (n > wmem_strbuf_get_len(string)-1) {
+ return '\0';
+ }
+ else {
+ return wmem_strbuf_get_str(string)[n];
+ }
+}
+
+/* Does the path end with the separator character? */
+static gboolean ends_with_separator(wmem_strbuf_t *path)
+{
+ if (wmem_strbuf_get_len(path) == 0) {
+ return FALSE;
+ }
+
+ gchar last = wmem_strbuf_get_last_char(path);
+ return last == '/';
+}
+
+/* Does the path begin with the separator character? */
+static gboolean begins_with_separator(wmem_strbuf_t *path)
+{
+ if (wmem_strbuf_get_len(path) == 0) {
+ return FALSE;
+ }
+
+ gchar first = wmem_strbuf_get_char_n(path, 0);
+ return first == '/';
+}
+
+
+/* Add new_path to the current working directory of the conversation, then normalise. */
+/* N.B. could use e.g. g_build_path() here, but doesn't really buy us anything */
+static void add_directory_to_conv(ftp_conversation_t *conv, const char *new_path)
+{
+ wmem_strbuf_t *appended_path = wmem_strbuf_new(wmem_packet_scope(), NULL);
+
+ if (!wmem_strbuf_get_len(conv->current_working_directory)) {
+ /* Currently empty so just assign to new */
+ wmem_strbuf_append(conv->current_working_directory, new_path);
+ return;
+ }
+ if (ends_with_separator(conv->current_working_directory)) {
+ /* Ends in separator, so don't need to write one */
+ wmem_strbuf_append_printf(appended_path, "%s%s", wmem_strbuf_get_str(conv->current_working_directory), new_path);
+ }
+ else {
+ /* Separator needed */
+ wmem_strbuf_append_printf(appended_path, "%s/%s", wmem_strbuf_get_str(conv->current_working_directory), new_path);
+ }
+
+ /* Now normalise, by going through the string one directory at a time. If see "..",
+ remove it and the previous folder. If see ".", ignore it. */
+ guint offset;
+
+ /* Initialise with empty path */
+ wmem_strbuf_t *normalised_directory = wmem_strbuf_new(wmem_file_scope(), NULL);
+ wmem_strbuf_t *this_folder = wmem_strbuf_new(wmem_packet_scope(), NULL);
+
+ offset = 0;
+ /* If absolute, add root to this one too */
+ if (begins_with_separator(conv->current_working_directory)) {
+ wmem_strbuf_append_c(normalised_directory, '/');
+ offset++;
+ }
+
+ /* Now go through the appended path, one directory at a time, and
+ copy to normalised_directory */
+ for (; offset <= wmem_strbuf_get_len(appended_path); offset++) {
+ gchar ch = wmem_strbuf_get_char_n(appended_path, offset);
+ if ((offset == wmem_strbuf_get_len(appended_path)) || ch == '/' || ch == '\0') {
+ /* Folder name is complete */
+ if (offset>0 && wmem_strbuf_get_len(this_folder) > 0) {
+
+ /* Up a level. Rewind to before last directory - don't output this one */
+ if (strcmp(wmem_strbuf_get_str(this_folder), "..") == 0) {
+ while (wmem_strbuf_get_len(normalised_directory) && !ends_with_separator(normalised_directory)) {
+ wmem_strbuf_truncate(normalised_directory, wmem_strbuf_get_len(normalised_directory)-1);
+ }
+ /* Potentially skip left-over trailing '/' too */
+ if ((wmem_strbuf_get_len(normalised_directory) > 1) &&
+ (wmem_strbuf_get_last_char(normalised_directory) == '/')) {
+
+ wmem_strbuf_truncate(normalised_directory, wmem_strbuf_get_len(normalised_directory)-1);
+ }
+ }
+ /* Current directory - ignore */
+ else if (strcmp(wmem_strbuf_get_str(this_folder), ".") == 0) {
+ /* Don't copy to normalised_directory */
+ }
+ else {
+ /* Regular directory name - copy this one out */
+ if (wmem_strbuf_get_len(normalised_directory) > 0 && !ends_with_separator(normalised_directory)) {
+ wmem_strbuf_append_c(normalised_directory, '/');
+ }
+ wmem_strbuf_append(normalised_directory, wmem_strbuf_get_str(this_folder));
+ }
+
+ /* Reset folder name for next time */
+ this_folder = wmem_strbuf_new(wmem_packet_scope(), NULL);
+ }
+ }
+ else {
+ /* Keep copying this folder name */
+ wmem_strbuf_append_c(this_folder, ch);
+ }
+ if (ch == '\0') {
+ /* Reached end - get out of loop */
+ break;
+ }
+ }
+
+ /* Copy normalised path into conversation */
+ conv->current_working_directory = normalised_directory;
+}
+
+/* In response to the arg to a CWD command succeeding, update the conversation's current working directory */
+static void process_cwd_success(ftp_conversation_t *conv, const char *new_path)
+{
+ if (g_path_is_absolute(new_path)) {
+ /* Just adopt new_path */
+ conv->current_working_directory = wmem_strbuf_new(wmem_file_scope(), new_path);
+ }
+ else {
+ /* Add new_path to what we already have */
+ add_directory_to_conv(conv, new_path);
+ }
+}
+
+/* When get a PWD command response, extract directory and set it in conversation. */
+static void process_pwd_success(ftp_conversation_t *conv, const char *line,
+ packet_info *pinfo, proto_item *pi)
+{
+ wmem_strbuf_t *output = wmem_strbuf_new(wmem_file_scope(), NULL);
+ int offset;
+ gboolean outputStarted = FALSE;
+
+ /* Line must start with quotes */
+ if ((strlen(line) < 2) || (line[0] != '"')) {
+ expert_add_info(pinfo, pi, &ei_ftp_pwd_response_invalid);
+ return;
+ }
+
+ /* For each character */
+ for (offset=0;
+ (line[offset] != '\0') && (line[offset] != '\r') && (line[offset] != '\n');
+ offset++) {
+
+ if (line[offset] == '"') {
+ if (line[offset+1] == '"') {
+ /* Double, so output one */
+ wmem_strbuf_append_c(output, '"');
+ offset++;
+ }
+ else {
+ if (outputStarted) {
+ /* End of path */
+ break;
+ }
+ outputStarted = TRUE;
+ }
+ }
+ else {
+ /* Part of path - append */
+ wmem_strbuf_append_c(output, line[offset]);
+ }
+ }
+
+ /* Make sure output ends in " */
+ if (line[offset] != '"') {
+ expert_add_info(pinfo, pi, &ei_ftp_pwd_response_invalid);
+ return;
+ }
+
+ /* Save result */
+ conv->current_working_directory = output;
+}
+
+
+/* Associate the conversation's current working directory with the given packet */
+static void store_directory_in_packet(packet_info *pinfo, ftp_conversation_t *p_ftp_conv)
+{
+ ftp_packet_data_t *p_packet_data = wmem_new0(wmem_file_scope(), ftp_packet_data_t);
+ /* Take deep copy of current path, and associate with this packet */
+ p_packet_data->current_working_directory = wmem_strbuf_new(wmem_file_scope(),
+ wmem_strbuf_get_str(p_ftp_conv->current_working_directory));
+ p_add_proto_data(wmem_file_scope(), pinfo, proto_ftp, 0, p_packet_data);
+}
+
static int
dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
@@ -628,6 +864,14 @@ dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
col_set_str(pinfo->cinfo, COL_PROTOCOL, "FTP");
+ /* Get the conversation */
+ ftp_conversation_t *p_ftp_conv = find_or_create_ftp_conversation(pinfo);
+
+ /* Store the current working directory */
+ if (!pinfo->fd->flags.visited) {
+ store_directory_in_packet(pinfo, p_ftp_conv);
+ }
+
/*
* Find the end of the first line.
*
@@ -680,15 +924,12 @@ dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
/* If there is an ftp data conversation that doesn't have a
command yet, attempt to update here */
- if (!pinfo->fd->flags.visited) {
- conversation_t *conv = find_conversation(pinfo->fd->num, &pinfo->src, &pinfo->dst,
- PT_TCP, pinfo->srcport, pinfo->destport, 0);
- if (conv) {
- ftp_conversation_t *p_ftp_conv = (ftp_conversation_t *)conversation_get_proto_data(conv, proto_ftp);
- if (p_ftp_conv && p_ftp_conv->current_data_conv && !p_ftp_conv->current_data_conv->description) {
- p_ftp_conv->current_data_conv->description = wmem_strndup(wmem_file_scope(), line, linelen);
- }
- }
+ if (p_ftp_conv) {
+ p_ftp_conv->last_command = wmem_strndup(wmem_file_scope(), line, linelen);
+ }
+ /* And make sure set for FTP data conversation */
+ if (p_ftp_conv && p_ftp_conv->current_data_conv && !p_ftp_conv->current_data_conv->command) {
+ p_ftp_conv->current_data_conv->command = wmem_strndup(wmem_file_scope(), line, linelen);
}
} else {
/*
@@ -736,6 +977,50 @@ dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
is_epasv_response = TRUE;
/*
+ * Responses to CWD command.
+ */
+ if (code == 250) {
+ if (!pinfo->fd->flags.visited) {
+ if (p_ftp_conv) {
+ /* Explicit Change Working Directory command */
+ if (strncmp(p_ftp_conv->last_command, "CWD ", 4) == 0) {
+ process_cwd_success(p_ftp_conv, p_ftp_conv->last_command+4);
+ /* Update path in packet */
+ if (!pinfo->fd->flags.visited) {
+ store_directory_in_packet(pinfo, p_ftp_conv);
+ }
+ }
+ /* Change Directory Up command (i.e. "CWD ..") */
+ else if (strncmp(p_ftp_conv->last_command, "CDUP", 4) == 0) {
+ process_cwd_success(p_ftp_conv, "..");
+ /* Update path in packet */
+ if (!pinfo->fd->flags.visited) {
+ store_directory_in_packet(pinfo, p_ftp_conv);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Responses to PWD command. Overwrite whatever is stored - this is the truth!
+ */
+ if (code == 257) {
+ if (!pinfo->fd->flags.visited) {
+ if (p_ftp_conv) {
+ /* Want directory name, which will be between " " */
+ process_pwd_success(p_ftp_conv, line+4, pinfo, pi);
+
+ /* Update path in packet */
+ if (!pinfo->fd->flags.visited) {
+ store_directory_in_packet(pinfo, p_ftp_conv);
+ }
+ }
+ }
+ }
+
+
+ /*
* Skip the 3 digits and, if present, the
* space or hyphen.
*/
@@ -773,6 +1058,7 @@ dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
}
offset = next_offset;
+
/*
* If this is a PORT request or a PASV response, handle it.
*/
@@ -958,6 +1244,17 @@ dissect_ftp(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data _U_)
offset = next_offset;
}
+ /* Show current working directory */
+ ftp_packet_data_t *ftp_packet_data = (ftp_packet_data_t *)p_get_proto_data(wmem_file_scope(), pinfo, proto_ftp, 0);
+ if (ftp_packet_data != NULL) {
+ /* Should always be set.. */
+ if (ftp_packet_data->current_working_directory) {
+ proto_item *cwd_ti = proto_tree_add_string(tree, hf_ftp_current_working_directory,
+ tvb, 0, 0, wmem_strbuf_get_str(ftp_packet_data->current_working_directory));
+ PROTO_ITEM_SET_GENERATED(cwd_ti);
+ }
+ }
+
return tvb_captured_length(tvb);
}
@@ -1001,9 +1298,20 @@ dissect_ftpdata(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void* data
PROTO_ITEM_SET_GENERATED(setup_ti);
}
- /* Show description (command) in info column */
- if (p_ftp_data_conv->description) {
- col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", p_ftp_data_conv->description);
+ /* Show command in info column */
+ if (p_ftp_data_conv->command) {
+ proto_item *desc_ti;
+ desc_ti = proto_tree_add_string(tree, hf_ftp_data_command,
+ tvb, 0, 0, p_ftp_data_conv->command);
+ PROTO_ITEM_SET_GENERATED(desc_ti);
+ col_append_fstr(pinfo->cinfo, COL_INFO, " (%s)", p_ftp_data_conv->command);
+ }
+
+ /* Show current working directory */
+ if (p_ftp_data_conv->current_working_directory) {
+ proto_item *cwd_ti = proto_tree_add_string(tree, hf_ftp_data_current_working_directory,
+ tvb, 0, 0, wmem_strbuf_get_str(p_ftp_data_conv->current_working_directory));
+ PROTO_ITEM_SET_GENERATED(cwd_ti);
}
}
}
@@ -1033,6 +1341,11 @@ void
proto_register_ftp(void)
{
static hf_register_info hf[] = {
+ { &hf_ftp_current_working_directory,
+ { "Current working directory", "ftp.current-working-directory",
+ FT_STRING, BASE_NONE, NULL, 0,
+ NULL, HFILL }},
+
{ &hf_ftp_response,
{ "Response", "ftp.response",
FT_BOOLEAN, BASE_NONE, NULL, 0x0,
@@ -1142,13 +1455,22 @@ proto_register_ftp(void)
{ &hf_ftp_data_setup_method,
{ "Setup method", "ftp-data.setup-method",
FT_STRING, BASE_NONE, NULL, 0,
- "Method used to set up data conversation", HFILL }}
+ "Method used to set up data conversation", HFILL }},
+ { &hf_ftp_data_command,
+ { "Command", "ftp-data.command",
+ FT_STRING, BASE_NONE, NULL, 0,
+ "Command that this data stream answers", HFILL }},
+ { &hf_ftp_data_current_working_directory,
+ { "Current working directory", "ftp-data.current-working-directory",
+ FT_STRING, BASE_NONE, NULL, 0,
+ "Current working directory at time of command", HFILL }}
};
static ei_register_info ei[] = {
{ &ei_ftp_eprt_args_invalid, { "ftp.eprt.args_invalid", PI_MALFORMED, PI_WARN, "EPRT arguments must have the form: |<family>|<addr>|<port>|", EXPFILL }},
{ &ei_ftp_epsv_args_invalid, { "ftp.epsv.args_invalid", PI_MALFORMED, PI_WARN, "EPSV arguments must have the form (|||<port>|)", EXPFILL }},
- { &ei_ftp_response_code_invalid, { "ftp.response.code.invalid", PI_MALFORMED, PI_ERROR, "Invalid response code", EXPFILL }}
+ { &ei_ftp_response_code_invalid, { "ftp.response.code.invalid", PI_MALFORMED, PI_ERROR, "Invalid response code", EXPFILL }},
+ { &ei_ftp_pwd_response_invalid, { "ftp.response.pwd.invalid", PI_MALFORMED, PI_ERROR, "Invalid PWD response", EXPFILL }}
};
expert_module_t* expert_ftp;