aboutsummaryrefslogtreecommitdiffstats
path: root/epan/dissectors/packet-pgsql.c
diff options
context:
space:
mode:
authorJörg Mayer <jmayer@loplof.de>2004-12-20 23:24:13 +0000
committerJörg Mayer <jmayer@loplof.de>2004-12-20 23:24:13 +0000
commitf774652d9bc2389c5e12dc90d1dbd8f1aa4db191 (patch)
tree7108aa9b1763ce5af029e355fb151d84e6b719ee /epan/dissectors/packet-pgsql.c
parentb40e2e4dfbbcffb8004d47f02ba00f6be9cf926b (diff)
Abhijit Menon-Sen: Postgres v3 support
svn path=/trunk/; revision=12795
Diffstat (limited to 'epan/dissectors/packet-pgsql.c')
-rw-r--r--epan/dissectors/packet-pgsql.c886
1 files changed, 886 insertions, 0 deletions
diff --git a/epan/dissectors/packet-pgsql.c b/epan/dissectors/packet-pgsql.c
new file mode 100644
index 0000000000..e7c46ac672
--- /dev/null
+++ b/epan/dissectors/packet-pgsql.c
@@ -0,0 +1,886 @@
+/* packet-pgsql.c
+ * Routines for PostgreSQL v3 protocol dissection.
+ * <http://www.postgresql.org/docs/current/static/protocol.html>
+ * Copyright 2004 Abhijit Menon-Sen <ams@oryx.com>
+ *
+ * $Id $
+ *
+ * Ethereal - Network traffic analyzer
+ * By Gerald Combs <gerald@ethereal.com>
+ * Copyright 1998 Gerald Combs
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <glib.h>
+#include <epan/packet.h>
+#include <epan/conversation.h>
+
+#include "packet-tcp.h"
+#include "reassemble.h"
+
+#define TCP_PORT_PGSQL 5432
+
+
+static int proto_pgsql = -1;
+static int hf_frontend = -1;
+static int hf_type = -1;
+static int hf_length = -1;
+static int hf_parameter_name = -1;
+static int hf_parameter_value = -1;
+static int hf_query = -1;
+static int hf_authtype = -1;
+static int hf_passwd = -1;
+static int hf_salt = -1;
+static int hf_statement = -1;
+static int hf_portal = -1;
+static int hf_tag = -1;
+static int hf_status = -1;
+static int hf_copydata = -1;
+static int hf_error = -1;
+static int hf_pid = -1;
+static int hf_key = -1;
+static int hf_condition = -1;
+static int hf_text = -1;
+static int hf_tableoid = -1;
+static int hf_typeoid = -1;
+static int hf_oid = -1;
+static int hf_format = -1;
+static int hf_val_name = -1;
+static int hf_val_idx = -1;
+static int hf_val_length = -1;
+static int hf_val_data = -1;
+static int hf_val_mod = -1;
+static int hf_severity = -1;
+static int hf_code = -1;
+static int hf_message = -1;
+static int hf_detail = -1;
+static int hf_hint = -1;
+static int hf_position = -1;
+static int hf_where = -1;
+static int hf_file = -1;
+static int hf_line = -1;
+static int hf_routine = -1;
+
+static gint ett_pgsql = -1;
+static gint ett_values = -1;
+
+static gboolean pgsql_desegment = TRUE;
+static gboolean first_message = TRUE;
+
+static void dissect_pgsql_fe_msg(guchar, guint32, guint32, tvbuff_t *, proto_tree *);
+static void dissect_pgsql_be_msg(guchar, guint32, guint32, tvbuff_t *, proto_tree *);
+static void dissect_pgsql_msg(tvbuff_t *, packet_info *, proto_tree *);
+static void dissect_pgsql(tvbuff_t *, packet_info *, proto_tree *);
+static char *identify(gboolean, guchar);
+static guint pgsql_length(tvbuff_t *, int);
+
+static const value_string auth_types[] = {
+ { 0, "Success" },
+ { 1, "Kerberos V4" },
+ { 2, "Kerberos V5" },
+ { 3, "Plaintext password" },
+ { 4, "crypt()ed password" },
+ { 5, "MD5 password" },
+ { 6, "SCM credentials" },
+ { 0, NULL }
+};
+
+static const value_string status_vals[] = {
+ { 'I', "Idle" },
+ { 'T', "In a transaction" },
+ { 'E', "In a failed transaction" },
+ { 0, NULL }
+};
+
+static const value_string format_vals[] = {
+ { 0, "Text" },
+ { 1, "Binary" },
+ { 0, NULL }
+};
+
+
+void
+proto_reg_handoff_pgsql(void)
+{
+ dissector_handle_t pgsql_handle;
+
+ pgsql_handle = create_dissector_handle(dissect_pgsql, proto_pgsql);
+ dissector_add("tcp.port", TCP_PORT_PGSQL, pgsql_handle);
+}
+
+
+void
+proto_register_pgsql(void)
+{
+ static hf_register_info hf[] = {
+ { &hf_frontend,
+ { "Frontend", "pgsql.frontend", FT_BOOLEAN, BASE_NONE, NULL, 0,
+ "True for messages from the frontend, false otherwise.",
+ HFILL }
+ },
+ { &hf_type,
+ { "Type", "pgsql.type", FT_STRING, BASE_NONE, NULL, 0,
+ "A one-byte message type identifier.", HFILL }
+ },
+ { &hf_length,
+ { "Length", "pgsql.length", FT_UINT32, BASE_DEC, NULL, 0,
+ "The length of the message (not including the type).",
+ HFILL }
+ },
+ { &hf_parameter_name,
+ { "Parameter name", "pgsql.parameter_name", FT_STRINGZ,
+ BASE_NONE, NULL, 0, "The name of a database parameter.",
+ HFILL }
+ },
+ { &hf_parameter_value,
+ { "Parameter value", "pgsql.parameter_value", FT_STRINGZ,
+ BASE_NONE, NULL, 0, "The value of a database parameter.",
+ HFILL }
+ },
+ { &hf_query,
+ { "Query", "pgsql.query", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "A query string.", HFILL }
+ },
+ { &hf_passwd,
+ { "Password", "pgsql.password", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "A password.", HFILL }
+ },
+ { &hf_authtype,
+ { "Authentication type", "pgsql.authtype", FT_INT32, BASE_DEC,
+ VALS(auth_types), 0,
+ "The type of authentication requested by the backend.", HFILL }
+ },
+ { &hf_salt,
+ { "Salt value", "pgsql.salt", FT_BYTES, BASE_HEX, NULL, 0,
+ "The salt to use while encrypting a password.", HFILL }
+ },
+ { &hf_statement,
+ { "Statement", "pgsql.statement", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The name of a prepared statement.", HFILL }
+ },
+ { &hf_portal,
+ { "Portal", "pgsql.portal", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The name of a portal.", HFILL }
+ },
+ { &hf_tag,
+ { "Tag", "pgsql.tag", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "A completion tag.", HFILL }
+ },
+ { &hf_status,
+ { "Status", "pgsql.status", FT_UINT8, BASE_DEC, VALS(status_vals),
+ 0, "The transaction status of the backend.", HFILL }
+ },
+ { &hf_copydata,
+ { "Copy data", "pgsql.copydata", FT_BYTES, BASE_NONE, NULL, 0,
+ "Data sent following a Copy-in or Copy-out response.", HFILL }
+ },
+ { &hf_error,
+ { "Error", "pgsql.error", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "An error message.", HFILL }
+ },
+ { &hf_pid,
+ { "PID", "pgsql.pid", FT_UINT32, BASE_DEC, NULL, 0,
+ "The process ID of a backend.", HFILL }
+ },
+ { &hf_key,
+ { "Key", "pgsql.key", FT_UINT32, BASE_DEC, NULL, 0,
+ "The secret key used by a particular backend.", HFILL }
+ },
+ { &hf_condition,
+ { "Condition", "pgsql.condition", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The name of a NOTIFY condition.", HFILL }
+ },
+ { &hf_text,
+ { "Text", "pgsql.text", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "Text from the backend.", HFILL }
+ },
+ { &hf_tableoid,
+ { "Table OID", "pgsql.oid.table", FT_UINT32, BASE_DEC, NULL, 0,
+ "The object identifier of a table.", HFILL }
+ },
+ { &hf_typeoid,
+ { "Type OID", "pgsql.oid.type", FT_UINT32, BASE_DEC, NULL, 0,
+ "The object identifier of a type.", HFILL }
+ },
+ { &hf_oid,
+ { "OID", "pgsql.oid", FT_UINT32, BASE_DEC, NULL, 0,
+ "An object identifier.", HFILL }
+ },
+ { &hf_format,
+ { "Format", "pgsql.format", FT_UINT16, BASE_DEC, VALS(format_vals),
+ 0, "A format specifier.", HFILL }
+ },
+ { &hf_val_name,
+ { "Column name", "pgsql.col.name", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The name of a column.", HFILL }
+ },
+ { &hf_val_idx,
+ { "Column index", "pgsql.col.index", FT_UINT32, BASE_DEC, NULL, 0,
+ "The position of a column within a row.", HFILL }
+ },
+ { &hf_val_length,
+ { "Column length", "pgsql.val.length", FT_INT32, BASE_DEC, NULL, 0,
+ "The length of a parameter value, in bytes. -1 means NULL.",
+ HFILL }
+ },
+ { &hf_val_data,
+ { "Data", "pgsql.val.data", FT_BYTES, BASE_NONE, NULL, 0,
+ "Parameter data.", HFILL }
+ },
+ { &hf_val_mod,
+ { "Type modifier", "pgsql.col.typemod", FT_INT32, BASE_DEC, NULL, 0,
+ "The type modifier for a column.", HFILL }
+ },
+ { &hf_severity,
+ { "Severity", "pgsql.severity", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "Message severity.", HFILL }
+ },
+ { &hf_code,
+ { "Code", "pgsql.code", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "SQLState code.", HFILL }
+ },
+ { &hf_message,
+ { "Message", "pgsql.message", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "Error message.", HFILL }
+ },
+ { &hf_detail,
+ { "Detail", "pgsql.detail", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "Detailed error message.", HFILL }
+ },
+ { &hf_hint,
+ { "Hint", "pgsql.hint", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "A suggestion to resolve an error.", HFILL }
+ },
+ { &hf_position,
+ { "Position", "pgsql.position", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The index of the error within the query string.", HFILL }
+ },
+ { &hf_where,
+ { "Context", "pgsql.where", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The context in which an error occurred.", HFILL }
+ },
+ { &hf_file,
+ { "File", "pgsql.file", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The source-code file where an error was reported.", HFILL }
+ },
+ { &hf_line,
+ { "Line", "pgsql.line", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The line number on which an error was reported.", HFILL }
+ },
+ { &hf_routine,
+ { "Routine", "pgsql.routine", FT_STRINGZ, BASE_NONE, NULL, 0,
+ "The routine that reported an error.", HFILL }
+ }
+ };
+
+ static gint *ett[] = {
+ &ett_pgsql,
+ &ett_values
+ };
+
+ proto_pgsql = proto_register_protocol("PostgreSQL", "PGSQL", "pgsql");
+ proto_register_field_array(proto_pgsql, hf, array_length(hf));
+ proto_register_subtree_array(ett, array_length(ett));
+}
+
+
+/* This function is called once per TCP packet. It sets COL_PROTOCOL and
+ * identifies FE/BE messages by adding a ">" or "<" to COL_INFO. Then it
+ * arranges for each message to be dissected individually. */
+
+static void
+dissect_pgsql(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ conversation_t *cv;
+
+ first_message = TRUE;
+
+ /* We don't use conversation data yet, but... */
+ cv = find_conversation(&pinfo->src, &pinfo->dst, pinfo->ptype,
+ pinfo->srcport, pinfo->destport, 0);
+ if (!cv) {
+ cv = conversation_new(&pinfo->src, &pinfo->dst, pinfo->ptype,
+ pinfo->srcport, pinfo->destport, 0);
+ }
+
+ if (check_col(pinfo->cinfo, COL_PROTOCOL))
+ col_set_str(pinfo->cinfo, COL_PROTOCOL, "PGSQL");
+ if (check_col(pinfo->cinfo, COL_INFO))
+ col_add_str(pinfo->cinfo, COL_INFO,
+ (pinfo->match_port == pinfo->destport) ?
+ ">" : "<");
+
+ tcp_dissect_pdus(tvb, pinfo, tree, pgsql_desegment, 5,
+ pgsql_length, dissect_pgsql_msg);
+}
+
+
+/* This function is called by tcp_dissect_pdus() to find the size of the
+ message starting at tvb[offset]. */
+
+static guint
+pgsql_length(tvbuff_t *tvb, int offset)
+{
+ gint n = 0;
+ guchar type;
+ guint32 length;
+
+ /* The length is either the four bytes after the type, or, if the
+ type is 0, the first four bytes. */
+ type = tvb_get_guint8(tvb, offset);
+ if (type != '\0')
+ n = 1;
+ length = tvb_get_ntohl(tvb, offset+n);
+ return length+n;
+}
+
+
+/* This function is responsible for dissecting a single message. */
+
+static void
+dissect_pgsql_msg(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree)
+{
+ proto_item *ti;
+ proto_tree *ptree;
+
+ gint n;
+ guchar type;
+ char *typestr;
+ guint32 length;
+ gboolean info = check_col(pinfo->cinfo, COL_INFO);
+ gboolean fe = (pinfo->match_port == pinfo->destport);
+
+ n = 0;
+ type = tvb_get_guint8(tvb, 0);
+ if (type != '\0')
+ n += 1;
+ length = tvb_get_ntohl(tvb, n);
+
+ /* There are a few frontend messages that have no leading type byte.
+ We identify them by the fact that the first byte of their length
+ must be zero, and that the next four bytes are a unique tag. */
+ if (fe && type == '\0') {
+ guint32 tag = tvb_get_ntohl(tvb, 4);
+
+ if (length == 16 && tag == 80877102)
+ typestr = "Cancel request";
+ else if (length == 8 && tag == 80877103)
+ typestr = "SSL request";
+ else if (tag == 196608)
+ typestr = "Startup message";
+ else
+ typestr = "Unknown";
+ }
+ else {
+ typestr = identify(fe, type);
+ }
+
+ if (info) {
+ /* This is a terrible hack. It makes the "Info" column reflect
+ the contents of every message in a TCP packet. Could it be
+ done any better? */
+ col_append_fstr(pinfo->cinfo, COL_INFO, "%s%c",
+ ( first_message ? "" : "/" ), type);
+ first_message = FALSE;
+ }
+
+ if (tree) {
+ ti = proto_tree_add_item(tree, proto_pgsql, tvb, 0, -1, FALSE);
+ ptree = proto_item_add_subtree(ti, ett_pgsql);
+
+ n = 1;
+ if (type == '\0')
+ n = 0;
+ proto_tree_add_text(ptree, tvb, 0, n, "Type: %s", typestr);
+ proto_tree_add_item_hidden(ptree, hf_type, tvb, 0, n, FALSE);
+ proto_tree_add_item(ptree, hf_length, tvb, n, 4, FALSE);
+ proto_tree_add_boolean_hidden(ptree, hf_frontend, tvb, 0, 0, fe);
+ n += 4;
+
+ if (fe)
+ dissect_pgsql_fe_msg(type, n, length, tvb, ptree);
+ else
+ dissect_pgsql_be_msg(type, n, length, tvb, ptree);
+ }
+}
+
+
+static const value_string fe_messages[] = {
+ { 'p', "Password message" },
+ { 'Q', "Simple query" },
+ { 'P', "Parse" },
+ { 'B', "Bind" },
+ { 'E', "Execute" },
+ { 'D', "Describe" },
+ { 'C', "Close" },
+ { 'H', "Flush" },
+ { 'S', "Sync" },
+ { 'F', "Function call" },
+ { 'd', "Copy data" },
+ { 'c', "Copy completion" },
+ { 'f', "Copy failure" },
+ { 'X', "Termination" },
+ { 0, NULL }
+};
+
+
+static void dissect_pgsql_fe_msg(guchar type, guint32 n, guint32 length,
+ tvbuff_t *tvb, proto_tree *tree)
+{
+ guchar c;
+ char *s, *t;
+ guint32 i, l;
+ proto_item *ti;
+ proto_tree *shrub;
+
+ switch (type) {
+ /* Password */
+ case 'p':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_passwd, tvb, n, l, s);
+ g_free(s);
+ break;
+
+ /* Simple query */
+ case 'Q':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_query, tvb, n, l, s);
+ g_free(s);
+ break;
+
+ /* Parse */
+ case 'P':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_statement, tvb, n, l, s);
+ g_free(s);
+ n += l;
+
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_query, tvb, n, l, s);
+ g_free(s);
+ n += l;
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Parameters: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree_add_item(shrub, hf_typeoid, tvb, n, 4, FALSE);
+ n += 4;
+ }
+ break;
+
+ /* Bind */
+ case 'B':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_portal, tvb, n, l, s);
+ g_free(s);
+ n += l;
+
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_statement, tvb, n, l, s);
+ g_free(s);
+ n += l;
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Parameter formats: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree_add_item(shrub, hf_format, tvb, n, 2, FALSE);
+ n += 2;
+ }
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Parameter values: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ l = tvb_get_ntohl(tvb, n);
+ proto_tree_add_int(shrub, hf_val_length, tvb, n, 4, l);
+ n += 4;
+ if (l > 0)
+ proto_tree_add_item(shrub, hf_val_data, tvb, n, l, FALSE);
+ n += l;
+ }
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Result formats: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree_add_item(shrub, hf_format, tvb, n, 2, FALSE);
+ n += 2;
+ }
+ break;
+
+ /* Execute */
+ case 'E':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_portal, tvb, n, l, s);
+ g_free(s);
+ n += l;
+
+ ti = proto_tree_add_text(tree, tvb, n, 4, "Returns: ");
+ i = tvb_get_ntohl(tvb, n);
+ if (i == 0)
+ proto_item_append_text(ti, "all");
+ else
+ proto_item_append_text(ti, "%d", i);
+ proto_item_append_text(ti, " rows");
+ break;
+
+ /* Describe, Close */
+ case 'D':
+ case 'C':
+ i = 0;
+ c = tvb_get_guint8(tvb, n);
+ if (c == 'P')
+ i = hf_portal;
+ else
+ i = hf_statement;
+
+ if (i != 0) {
+ n += 1;
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string_hidden(tree, i, tvb, n, l, s);
+ proto_tree_add_text(
+ tree, tvb, n-1, l, "%s: %s",
+ (c == 'P' ? "Portal" : "Statement"), s
+ );
+ g_free(s);
+ }
+ break;
+
+ /* Messages without a type identifier */
+ case '\0':
+ i = tvb_get_ntohl(tvb, n);
+ n += 4;
+ length -= n;
+ switch (i) {
+ /* Startup message */
+ case 196608:
+ while (length > 0) {
+ s = tvb_get_stringz(tvb, n, &l);
+ n += l;
+ length -= l;
+ if (length <= 0) {
+ g_free(s);
+ break;
+ }
+ t = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_text(tree, tvb, n, l, "%s: %s", s, t);
+ g_free(s);
+ g_free(t);
+ n += l;
+ length -= l;
+ if (length == 1 && tvb_get_guint8(tvb, n) == 0)
+ break;
+ }
+ break;
+
+ /* SSL request */
+ case 80877103:
+ /* There's nothing to parse here, but what do we do if the
+ SSL negotiation succeeds? */
+ break;
+
+ /* Cancellation request */
+ case 80877102:
+ proto_tree_add_item(tree, hf_pid, tvb, n, 4, FALSE);
+ proto_tree_add_item(tree, hf_key, tvb, n+4, 4, FALSE);
+ break;
+ }
+ break;
+
+ /* Copy data */
+ case 'd':
+ proto_tree_add_item(tree, hf_copydata, tvb, n, length-n+1, FALSE);
+ break;
+
+ /* Copy failure */
+ case 'f':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_error, tvb, n, l, s);
+ g_free(s);
+ break;
+
+ /* Function call */
+ case 'F':
+ proto_tree_add_item(tree, hf_oid, tvb, n, 4, FALSE);
+ n += 4;
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Parameter formats: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree_add_item(shrub, hf_format, tvb, n, 2, FALSE);
+ n += 2;
+ }
+
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Parameter values: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ l = tvb_get_ntohl(tvb, n);
+ proto_tree_add_item(shrub, hf_val_length, tvb, n, 4, FALSE);
+ n += 4;
+ if (l > 0)
+ proto_tree_add_item(shrub, hf_val_data, tvb, n, l, FALSE);
+ n += l;
+ }
+
+ proto_tree_add_item(tree, hf_format, tvb, n, 2, FALSE);
+ break;
+ }
+}
+
+
+static const value_string be_messages[] = {
+ { 'R', "Authentication request" },
+ { 'K', "Backend key data" },
+ { 'S', "Parameter status" },
+ { '1', "Parse completion" },
+ { '2', "Bind completion" },
+ { '3', "Close completion" },
+ { 'C', "Command completion" },
+ { 't', "Parameter description" },
+ { 'T', "Row description" },
+ { 'D', "Data row" },
+ { 'I', "Empty query" },
+ { 'n', "No data" },
+ { 'E', "Error" },
+ { 'N', "Notice" },
+ { 's', "Portal suspended" },
+ { 'Z', "Ready for query" },
+ { 'A', "Notification" },
+ { 'V', "Function call response" },
+ { 'G', "CopyIn response" },
+ { 'H', "CopyOut response" },
+ { 'd', "Copy data" },
+ { 'c', "Copy completion" },
+ { 0, NULL }
+};
+
+
+static void dissect_pgsql_be_msg(guchar type, guint32 n, guint32 length,
+ tvbuff_t *tvb, proto_tree *tree)
+{
+ guchar c;
+ char *s, *t;
+ guint32 i, l;
+ proto_item *ti;
+ proto_tree *shrub;
+
+ switch (type) {
+ /* Authentication request */
+ case 'R':
+ proto_tree_add_item(tree, hf_authtype, tvb, n, 4, FALSE);
+ i = tvb_get_ntohl(tvb, n);
+ if (i == 4 || i == 5) {
+ /* i -= (6-i); :-) */
+ n += 4;
+ l = (i == 4 ? 2 : 4);
+ proto_tree_add_item(tree, hf_salt, tvb, n, l, FALSE);
+ }
+ break;
+
+ /* Key data */
+ case 'K':
+ proto_tree_add_item(tree, hf_pid, tvb, n, 4, FALSE);
+ proto_tree_add_item(tree, hf_key, tvb, n+4, 4, FALSE);
+ break;
+
+ /* Parameter status */
+ case 'S':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string_hidden(tree, hf_parameter_name, tvb, n, l, s);
+ n += l;
+ t = tvb_get_stringz(tvb, n, &i);
+ proto_tree_add_string_hidden(tree, hf_parameter_value, tvb, n, i, t);
+ proto_tree_add_text(tree, tvb, n-l, l+i, "%s: %s", s, t);
+ g_free(s);
+ g_free(t);
+ break;
+
+ /* Parameter description */
+ case 't':
+ i = tvb_get_ntohs(tvb, n);
+ proto_tree_add_text(tree, tvb, n, 2, "Parameters: %d", i);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree_add_item(tree, hf_typeoid, tvb, n, 4, FALSE);
+ n += 4;
+ }
+ break;
+
+ /* Row description */
+ case 'T':
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Columns: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ proto_tree *twig;
+ s = tvb_get_stringz(tvb, n, &l);
+ ti = proto_tree_add_string(shrub, hf_val_name, tvb, n, l, s);
+ twig = proto_item_add_subtree(ti, ett_values);
+ g_free(s);
+ n += l;
+ proto_tree_add_item(twig, hf_tableoid, tvb, n, 4, FALSE);
+ n += 4;
+ proto_tree_add_item(twig, hf_val_idx, tvb, n, 2, FALSE);
+ n += 2;
+ proto_tree_add_item(twig, hf_typeoid, tvb, n, 4, FALSE);
+ n += 4;
+ proto_tree_add_item(twig, hf_val_length, tvb, n, 2, FALSE);
+ n += 2;
+ proto_tree_add_item(twig, hf_val_mod, tvb, n, 4, FALSE);
+ n += 4;
+ proto_tree_add_item(twig, hf_format, tvb, n, 2, FALSE);
+ n += 2;
+ }
+ break;
+
+ /* Data row */
+ case 'D':
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Columns: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 0) {
+ l = tvb_get_ntohl(tvb, n);
+ proto_tree_add_int(shrub, hf_val_length, tvb, n, 4, l);
+ n += 4;
+ if (l > 0)
+ proto_tree_add_item(shrub, hf_val_data, tvb, n, l, FALSE);
+ n += l;
+ }
+ break;
+
+ /* Command completion */
+ case 'C':
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_tag, tvb, n, l, s);
+ g_free(s);
+ break;
+
+ /* Ready */
+ case 'Z':
+ proto_tree_add_item(tree, hf_status, tvb, n, 1, FALSE);
+ break;
+
+ /* Error, Notice */
+ case 'E':
+ case 'N':
+ length -= 4;
+ while (length > 0) {
+ c = tvb_get_guint8(tvb, n);
+ if (c == '\0')
+ break;
+ s = tvb_get_stringz(tvb, n+1, &l);
+ i = hf_text;
+ switch (c) {
+ case 'S': i = hf_severity; break;
+ case 'C': i = hf_code; break;
+ case 'M': i = hf_message; break;
+ case 'D': i = hf_detail; break;
+ case 'H': i = hf_hint; break;
+ case 'P': i = hf_position; break;
+ case 'W': i = hf_where; break;
+ case 'F': i = hf_file; break;
+ case 'L': i = hf_line; break;
+ case 'R': i = hf_routine; break;
+ }
+ proto_tree_add_string(tree, i, tvb, n, l+1, s);
+ g_free(s);
+ n += l+1;
+ }
+ break;
+
+ /* NOTICE response */
+ case 'A':
+ proto_tree_add_item(tree, hf_pid, tvb, n, 4, FALSE);
+ n += 4;
+ s = tvb_get_stringz(tvb, n, &l);
+ proto_tree_add_string(tree, hf_condition, tvb, n, l, s);
+ g_free(s);
+ n += l;
+ s = tvb_get_stringz(tvb, n, &l);
+ if (l > 1)
+ proto_tree_add_string(tree, hf_text, tvb, n, l, s);
+ g_free(s);
+ break;
+
+ /* Copy in/out */
+ case 'G':
+ case 'H':
+ proto_tree_add_item(tree, hf_format, tvb, n, 1, FALSE);
+ n += 1;
+ i = tvb_get_ntohs(tvb, n);
+ ti = proto_tree_add_text(tree, tvb, n, 2, "Columns: %d", i);
+ shrub = proto_item_add_subtree(ti, ett_values);
+ n += 2;
+ while (i-- > 2) {
+ proto_tree_add_item(shrub, hf_format, tvb, n, 2, FALSE);
+ n += 2;
+ }
+ break;
+
+ /* Copy data */
+ case 'd':
+ proto_tree_add_item(tree, hf_copydata, tvb, n, length-n+1, FALSE);
+ break;
+
+ /* Function call response */
+ case 'V':
+ l = tvb_get_ntohl(tvb, n);
+ proto_tree_add_int(tree, hf_val_length, tvb, n, 4, l);
+ if (l > 0)
+ proto_tree_add_item(tree, hf_val_data, tvb, n+4, l, FALSE);
+ break;
+ }
+}
+
+
+/* This is like specifying VALS(messages) for hf_type, which we can't do
+ directly because of messages without type bytes, and because the type
+ interpretation depends on fe. */
+
+static char *identify(gboolean fe, guchar type)
+{
+ int i = 0;
+ const value_string *messages;
+
+ if (fe)
+ messages = fe_messages;
+ else
+ messages = be_messages;
+
+ while (messages[i].strptr) {
+ if (messages[i].value == type)
+ return messages[i].strptr;
+ i++;
+ }
+
+ return "Unknown";
+}