]> The Tcpdump Group git mirrors - tcpdump/commitdiff
Add a routine to print "text protocols", and add FTP/HTTP/SMTP/RTSP support.
authorGuy Harris <[email protected]>
Sun, 19 Oct 2014 18:21:44 +0000 (11:21 -0700)
committerGuy Harris <[email protected]>
Sun, 19 Oct 2014 18:21:44 +0000 (11:21 -0700)
"Text protocols" are protocols that have the general feel of FTP, with
command lines with a command name and space-separated arguments and
response lines beginning with a 3-digit reply code.  They can also
include HTTP-style headers and an entity body.

We add support for the FTP control channel, HTTP, SMTP, and RTSP.  We
also change the SIP printer to use it.

Makefile.in
netdissect.h
print-ftp.c [new file with mode: 0644]
print-http.c [new file with mode: 0644]
print-rtsp.c [new file with mode: 0644]
print-sip.c
print-smtp.c [new file with mode: 0644]
print-tcp.c
tcp.h
util.c

index 5879b932c9459cc0ad23db378d2a08e2acc17a9d..d6836f698d1b2466badf5db208a5b07d4de99409 100644 (file)
@@ -124,9 +124,11 @@ LIBNETDISSECT_SRC=\
        print-fddi.c \
        print-forces.c \
        print-fr.c \
        print-fddi.c \
        print-forces.c \
        print-fr.c \
+       print-ftp.c \
        print-geonet.c \
        print-gre.c \
        print-hsrp.c \
        print-geonet.c \
        print-gre.c \
        print-hsrp.c \
+       print-http.c \
        print-icmp.c \
        print-igmp.c \
        print-igrp.c \
        print-icmp.c \
        print-igmp.c \
        print-igrp.c \
@@ -179,6 +181,7 @@ LIBNETDISSECT_SRC=\
        print-rpki-rtr.c \
        print-rrcp.c \
        print-rsvp.c \
        print-rpki-rtr.c \
        print-rrcp.c \
        print-rsvp.c \
+       print-rtsp.c \
        print-rx.c \
        print-sctp.c \
        print-sflow.c \
        print-rx.c \
        print-sctp.c \
        print-sflow.c \
@@ -186,6 +189,7 @@ LIBNETDISSECT_SRC=\
        print-sl.c \
        print-sll.c \
        print-slow.c \
        print-sl.c \
        print-sll.c \
        print-slow.c \
+       print-smtp.c \
        print-snmp.c \
        print-stp.c \
        print-sunatm.c \
        print-snmp.c \
        print-stp.c \
        print-sunatm.c \
index d39e26b3fa04e1954132d3baa722bc0dfa3a794a..0fb895043db0c3a222a07f6cbf531657c4e01e8d 100644 (file)
@@ -281,6 +281,11 @@ extern int fn_printn(netdissect_options *, const u_char *, u_int, const u_char *
 extern int fn_printzp(netdissect_options *, const u_char *, u_int, const u_char *);
 extern const char *tok2str(const struct tok *, const char *, int);
 
 extern int fn_printzp(netdissect_options *, const u_char *, u_int, const u_char *);
 extern const char *tok2str(const struct tok *, const char *, int);
 
+#define RESP_CODE_SECOND_TOKEN 0x00000001
+
+extern void txtproto_print(netdissect_options *, const u_char *, u_int,
+    const char *, const char **, u_int);
+
 #if 0
 extern char *read_infile(netdissect_options *, char *);
 extern char *copy_argv(netdissect_options *, char **);
 #if 0
 extern char *read_infile(netdissect_options *, char *);
 extern char *copy_argv(netdissect_options *, char **);
@@ -544,6 +549,10 @@ extern void rsvp_print(netdissect_options *, const u_char *, u_int);
 extern void timed_print(netdissect_options *, const u_char *);
 extern void m3ua_print(netdissect_options *, const u_char *, const u_int);
 extern void aoe_print(netdissect_options *, const u_char *, const u_int);
 extern void timed_print(netdissect_options *, const u_char *);
 extern void m3ua_print(netdissect_options *, const u_char *, const u_int);
 extern void aoe_print(netdissect_options *, const u_char *, const u_int);
+extern void ftp_print(netdissect_options *, const u_char *, u_int);
+extern void http_print(netdissect_options *, const u_char *, u_int);
+extern void rtsp_print(netdissect_options *, const u_char *, u_int);
+extern void smtp_print(netdissect_options *, const u_char *, u_int);
 
 /* stuff that has not yet been rototiled */
 
 
 /* stuff that has not yet been rototiled */
 
diff --git a/print-ftp.c b/print-ftp.c
new file mode 100644 (file)
index 0000000..5479042
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code
+ * distributions retain the above copyright notice and this paragraph
+ * in its entirety, and (2) distributions including binary code include
+ * the above copyright notice and this paragraph in its entirety in
+ * the documentation or other materials provided with the distribution.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+ * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef lint
+static const char rcsid[] _U_ =
+    "@(#) $Header$";
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <tcpdump-stdinc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "netdissect.h"
+#include "extract.h"
+
+void
+ftp_print(netdissect_options *ndo, const u_char *pptr, u_int len)
+{
+       txtproto_print(ndo, pptr, len, "ftp", NULL, 0);
+}
diff --git a/print-http.c b/print-http.c
new file mode 100644 (file)
index 0000000..49df174
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code
+ * distributions retain the above copyright notice and this paragraph
+ * in its entirety, and (2) distributions including binary code include
+ * the above copyright notice and this paragraph in its entirety in
+ * the documentation or other materials provided with the distribution.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+ * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef lint
+static const char rcsid[] _U_ =
+    "@(#) $Header$";
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <tcpdump-stdinc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "interface.h"
+#include "extract.h"
+
+/*
+ * Includes WebDAV requests.
+ */
+static const char *httpcmds[] = {
+       "GET",
+       "PUT",
+       "COPY",
+       "HEAD",
+       "LOCK",
+       "MOVE",
+       "POLL",
+       "POST",
+       "BCOPY",
+       "BMOVE",
+       "MKCOL",
+       "TRACE",
+       "LABEL",
+       "MERGE",
+       "DELETE",
+       "SEARCH",
+       "UNLOCK",
+       "REPORT",
+       "UPDATE",
+       "NOTIFY",
+       "BDELETE",
+       "CONNECT",
+       "OPTIONS",
+       "CHECKIN",
+       "PROPFIND",
+       "CHECKOUT",
+       "CCM_POST",
+       "SUBSCRIBE",
+       "PROPPATCH",
+       "BPROPFIND",
+       "BPROPPATCH",
+       "UNCHECKOUT",
+       "MKACTIVITY",
+       "MKWORKSPACE",
+       "UNSUBSCRIBE",
+       "RPC_CONNECT",
+       "VERSION-CONTROL",
+       "BASELINE-CONTROL",
+       NULL
+};
+
+void
+http_print(netdissect_options *ndo, const u_char *pptr, u_int len)
+{
+       txtproto_print(ndo, pptr, len, "http", httpcmds, RESP_CODE_SECOND_TOKEN);
+}
diff --git a/print-rtsp.c b/print-rtsp.c
new file mode 100644 (file)
index 0000000..d721b8d
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code
+ * distributions retain the above copyright notice and this paragraph
+ * in its entirety, and (2) distributions including binary code include
+ * the above copyright notice and this paragraph in its entirety in
+ * the documentation or other materials provided with the distribution.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+ * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef lint
+static const char rcsid[] _U_ =
+    "@(#) $Header$";
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <tcpdump-stdinc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "interface.h"
+#include "extract.h"
+
+static const char *rtspcmds[] = {
+       "DESCRIBE",
+       "ANNOUNCE",
+       "GET_PARAMETER",
+       "OPTIONS",
+       "PAUSE",
+       "PLAY",
+       "RECORD",
+       "REDIRECT",
+       "SETUP",
+       "SET_PARAMETER",
+       "TEARDOWN",
+       NULL
+};
+
+void
+rtsp_print(netdissect_options *ndo, const u_char *pptr, u_int len)
+{
+       txtproto_print(ndo, pptr, len, "rtsp", rtspcmds, RESP_CODE_SECOND_TOKEN);
+}
index 540cbf424267b0d16ab57aeb6a60706ca165a2c6..d0fd349f25e7e0acffcbe310cd55d9bf69d5b8c6 100644 (file)
@@ -11,6 +11,8 @@
  * FOR A PARTICULAR PURPOSE.
  *
  * Original code by Hannes Gredler ([email protected])
  * FOR A PARTICULAR PURPOSE.
  *
  * Original code by Hannes Gredler ([email protected])
+ * Turned into common "text protocol" code, which this uses, by
+ * Guy Harris.
  */
 
 #define NETDISSECT_REWORKED
  */
 
 #define NETDISSECT_REWORKED
 #include "interface.h"
 #include "extract.h"
 
 #include "interface.h"
 #include "extract.h"
 
+static const char *sipcmds[] = {
+       "ACK",
+       "BYE",
+       "CANCEL",
+       "DO",
+       "INFO",
+       "INVITE",
+       "MESSAGE",
+       "NOTIFY",
+       "OPTIONS",
+       "PRACK",
+       "QAUTH",
+       "REFER",
+       "REGISTER",
+       "SPRACK",
+       "SUBSCRIBE",
+       "UPDATE",
+       "PUBLISH",
+       NULL
+};
+
 void
 void
-sip_print(netdissect_options *ndo,
-          register const u_char *pptr, register u_int len)
+sip_print(netdissect_options *ndo, const u_char *pptr, u_int len)
 {
 {
-    u_int idx;
-
-    ND_PRINT((ndo, "SIP, length: %u%s", len, ndo->ndo_vflag ? "\n\t" : ""));
-
-    /* in non-verbose mode just lets print the protocol and length */
-    if (ndo->ndo_vflag < 1)
-        return;
-
-    for (idx = 0; idx < len; idx++) {
-        ND_TCHECK2(*(pptr+idx), 2);
-        if (EXTRACT_16BITS(pptr+idx) != 0x0d0a) { /* linefeed ? */
-            safeputchar(ndo, *(pptr + idx));
-        } else {
-            ND_PRINT((ndo, "\n\t"));
-            idx+=1;
-        }
-    }
-
-    /* do we want to see an additionally hexdump ? */
-    if (ndo->ndo_vflag > 1)
-        print_unknown_data(ndo, pptr, "\n\t", len);
-
-    return;
-
-trunc:
-    ND_PRINT((ndo, "[|sip]"));
+       txtproto_print(ndo, pptr, len, "sip", sipcmds, RESP_CODE_SECOND_TOKEN);
 }
 }
diff --git a/print-smtp.c b/print-smtp.c
new file mode 100644 (file)
index 0000000..fe0bbc2
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code
+ * distributions retain the above copyright notice and this paragraph
+ * in its entirety, and (2) distributions including binary code include
+ * the above copyright notice and this paragraph in its entirety in
+ * the documentation or other materials provided with the distribution.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+ * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <tcpdump-stdinc.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "netdissect.h"
+#include "extract.h"
+
+void
+smtp_print(netdissect_options *ndo, const u_char *pptr, u_int len)
+{
+       txtproto_print(ndo, pptr, len, "smtp", NULL, 0);
+}
index 5b1133a6c30a451d0f944cae8f2b9a7aaea72d02..afd093950c4f6cd6effcb5e2864437358b7c89c3 100644 (file)
@@ -676,6 +676,9 @@ tcp_print(netdissect_options *ndo,
         if (sport == TELNET_PORT || dport == TELNET_PORT) {
                 if (!ndo->ndo_qflag && ndo->ndo_vflag)
                         telnet_print(ndo, bp, length);
         if (sport == TELNET_PORT || dport == TELNET_PORT) {
                 if (!ndo->ndo_qflag && ndo->ndo_vflag)
                         telnet_print(ndo, bp, length);
+        } else if (sport == SMTP_PORT || dport == SMTP_PORT) {
+                ND_PRINT((ndo, ": "));
+                smtp_print(ndo, bp, length);
         } else if (sport == BGP_PORT || dport == BGP_PORT)
                 bgp_print(ndo, bp, length);
         else if (sport == PPTP_PORT || dport == PPTP_PORT)
         } else if (sport == BGP_PORT || dport == BGP_PORT)
                 bgp_print(ndo, bp, length);
         else if (sport == PPTP_PORT || dport == PPTP_PORT)
@@ -691,7 +694,18 @@ tcp_print(netdissect_options *ndo,
         else if (sport == OPENFLOW_PORT_OLD || dport == OPENFLOW_PORT_OLD ||
                  sport == OPENFLOW_PORT_IANA || dport == OPENFLOW_PORT_IANA)
                 openflow_print(ndo, bp, length);
         else if (sport == OPENFLOW_PORT_OLD || dport == OPENFLOW_PORT_OLD ||
                  sport == OPENFLOW_PORT_IANA || dport == OPENFLOW_PORT_IANA)
                 openflow_print(ndo, bp, length);
-        else if (length > 2 &&
+        else if (sport == FTP_PORT || dport == FTP_PORT) {
+                ND_PRINT((ndo, ": "));
+                ftp_print(ndo, bp, length);
+        } else if (sport == HTTP_PORT || dport == HTTP_PORT ||
+            sport == HTTP_PORT_ALT || dport == HTTP_PORT_ALT) {
+                ND_PRINT((ndo, ": "));
+                http_print(ndo, bp, length);
+        } else if (sport == RTSP_PORT || dport == RTSP_PORT ||
+            sport == RTSP_PORT_ALT || dport == RTSP_PORT_ALT) {
+                ND_PRINT((ndo, ": "));
+                rtsp_print(ndo, bp, length);
+        } else if (length > 2 &&
                  (sport == NAMESERVER_PORT || dport == NAMESERVER_PORT ||
                   sport == MULTICASTDNS_PORT || dport == MULTICASTDNS_PORT)) {
                 /*
                  (sport == NAMESERVER_PORT || dport == NAMESERVER_PORT ||
                   sport == MULTICASTDNS_PORT || dport == MULTICASTDNS_PORT)) {
                 /*
diff --git a/tcp.h b/tcp.h
index 3645486099cc73eb2c0ea3467d3365d5c1585806..c18d1382511c53d0d1a662673807c0b659999bb0 100644 (file)
--- a/tcp.h
+++ b/tcp.h
@@ -93,6 +93,9 @@ struct tcphdr {
 #ifndef TELNET_PORT
 #define TELNET_PORT             23
 #endif
 #ifndef TELNET_PORT
 #define TELNET_PORT             23
 #endif
+#ifndef SMTP_PORT
+#define SMTP_PORT              25
+#endif
 #ifndef BGP_PORT
 #define BGP_PORT                179
 #endif
 #ifndef BGP_PORT
 #define BGP_PORT                179
 #endif
@@ -116,3 +119,18 @@ struct tcphdr {
 #ifndef SMB_PORT
 #define SMB_PORT                445
 #endif
 #ifndef SMB_PORT
 #define SMB_PORT                445
 #endif
+#ifndef HTTP_PORT
+#define HTTP_PORT              80
+#endif
+#ifndef HTTP_PORT_ALT
+#define HTTP_PORT_ALT          8080
+#endif
+#ifndef RTSP_PORT
+#define RTSP_PORT              554
+#endif
+#ifndef RTSP_PORT_ALT
+#define RTSP_PORT_ALT          8554
+#endif
+#ifndef FTP_PORT
+#define FTP_PORT               21
+#endif
diff --git a/util.c b/util.c
index 3836151c206b62490f86f60f7cfeeb492a7c759c..6a827dce71405a7618976e9a0dd9e509aa866ffd 100644 (file)
--- a/util.c
+++ b/util.c
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  */
 
+/*
+ * txtproto_print() derived from original code by Hannes Gredler
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that: (1) source code
+ * distributions retain the above copyright notice and this paragraph
+ * in its entirety, and (2) distributions including binary code include
+ * the above copyright notice and this paragraph in its entirety in
+ * the documentation or other materials provided with the distribution.
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT
+ * LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE.
+ */
+
 #define NETDISSECT_REWORKED
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #define NETDISSECT_REWORKED
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -475,6 +491,248 @@ mask62plen(const u_char *mask)
 }
 #endif /* INET6 */
 
 }
 #endif /* INET6 */
 
+/*
+ * Routine to print out information for text-based protocols such as FTP,
+ * HTTP, SMTP, RTSP, SIP, ....
+ */
+#define MAX_TOKEN      128
+
+/*
+ * Fetch a token from a packet, starting at the specified index,
+ * and return the length of the token.
+ *
+ * Returns 0 on error; yes, this is indistinguishable from an empty
+ * token, but an "empty token" isn't a valid token - it just means
+ * either a space character at the beginning of the line (this
+ * includes a blank line) or no more tokens remaining on the line.
+ */
+static int
+fetch_token(netdissect_options *ndo, const u_char *pptr, u_int idx, u_int len,
+    u_char *tbuf, size_t tbuflen)
+{
+       size_t toklen = 0;
+
+       for (; idx < len; idx++) {
+               if (!ND_TTEST(*(pptr + idx))) {
+                       /* ran past end of captured data */
+                       return (0);
+               }
+               if (!isascii(*(pptr + idx))) {
+                       /* not an ASCII character */
+                       return (0);
+               }
+               if (isspace(*(pptr + idx))) {
+                       /* end of token */
+                       break;
+               }
+               if (!isprint(*(pptr + idx))) {
+                       /* not part of a command token or response code */
+                       return (0);
+               }
+               if (toklen + 2 > tbuflen) {
+                       /* no room for this character and terminating '\0' */
+                       return (0);
+               }
+               tbuf[toklen] = *(pptr + idx);
+               toklen++;
+       }
+       if (toklen == 0) {
+               /* no token */
+               return (0);
+       }
+       tbuf[toklen] = '\0';
+
+       /*
+        * Skip past any white space after the token, until we see
+        * an end-of-line (CR or LF).
+        */
+       for (; idx < len; idx++) {
+               if (!ND_TTEST(*(pptr + idx))) {
+                       /* ran past end of captured data */
+                       break;
+               }
+               if (*(pptr + idx) == '\r' || *(pptr + idx) == '\n') {
+                       /* end of line */
+                       break;
+               }
+               if (!isascii(*(pptr + idx)) || !isprint(*(pptr + idx))) {
+                       /* not a printable ASCII character */
+                       break;
+               }
+               if (!isspace(*(pptr + idx))) {
+                       /* beginning of next token */
+                       break;
+               }
+       }
+       return (idx);
+}
+
+/*
+ * Scan a buffer looking for a line ending - LF or CR-LF.
+ * Return the index of the character after the line ending or 0 if
+ * we encounter a non-ASCII or non-printable character or don't find
+ * the line ending.
+ */
+static u_int
+print_txt_line(netdissect_options *ndo, const char *protoname,
+    const char *prefix, const u_char *pptr, u_int idx, u_int len)
+{
+       u_int startidx;
+       u_int linelen;
+
+       startidx = idx;
+       while (idx < len) {
+               ND_TCHECK(*(pptr+idx));
+               if (*(pptr+idx) == '\n') {
+                       /*
+                        * LF without CR; end of line.
+                        * Skip the LF and print the line, with the
+                        * exception of the LF.
+                        */
+                       linelen = idx - startidx;
+                       idx++;
+                       goto print;
+               } else if (*(pptr+idx) == '\r') {
+                       /* CR - any LF? */
+                       if ((idx+1) >= len) {
+                               /* not in this packet */
+                               return (0);
+                       }
+                       ND_TCHECK(*(pptr+idx+1));
+                       if (*(pptr+idx+1) == '\n') {
+                               /*
+                                * CR-LF; end of line.
+                                * Skip the CR-LF and print the line, with
+                                * the exception of the CR-LF.
+                                */
+                               linelen = idx - startidx;
+                               idx += 2;
+                               goto print;
+                       }
+
+                       /*
+                        * CR followed by something else; treat this
+                        * as if it were binary data, and don't print
+                        * it.
+                        */
+                       return (0);
+               } else if (!isascii(*(pptr+idx)) ||
+                   (!isprint(*(pptr+idx)) && *(pptr+idx) != '\t')) {
+                       /*
+                        * Not a printable ASCII character and not a tab;
+                        * treat this as if it were binary data, and
+                        * don't print it.
+                        */
+                       return (0);
+               }
+               idx++;
+       }
+
+       /*
+        * All printable ASCII, but no line ending after that point
+        * in the buffer; treat this as if it were truncated.
+        */
+trunc:
+       ND_PRINT((ndo, "%s%.*s[!%s]", prefix, (int)linelen, pptr + startidx,
+           protoname));
+       return (0);
+
+print:
+       ND_PRINT((ndo, "%s%.*s", prefix, (int)linelen, pptr + startidx));
+       return (idx);
+}
+
+void
+txtproto_print(netdissect_options *ndo, const u_char *pptr, u_int len,
+    const char *protoname, const char **cmds, u_int flags)
+{
+       u_int idx, eol;
+       u_char token[MAX_TOKEN+1];
+       const char *cmd;
+       int is_reqresp = 0;
+       const char *pnp;
+
+       if (cmds != NULL) {
+               /*
+                * This protocol has more than just request and
+                * response lines; see whether this looks like a
+                * request or response.
+                */
+               idx = fetch_token(ndo, pptr, 0, len, token, sizeof(token));
+               if (idx != 0) {
+                       /* Is this a valid request name? */
+                       while ((cmd = *cmds++) != NULL) {
+                               if (strcasecmp((const char *)token, cmd) == 0) {
+                                       /* Yes. */
+                                       is_reqresp = 1;
+                                       break;
+                               }
+                       }
+
+                       /*
+                        * No - is this a valid response code (3 digits)?
+                        *
+                        * Is this token the response code, or is the next
+                        * token the response code?
+                        */
+                       if (flags & RESP_CODE_SECOND_TOKEN) {
+                               /*
+                                * Next token - get it.
+                                */
+                               idx = fetch_token(ndo, pptr, idx, len, token,
+                                   sizeof(token));
+                       }
+                       if (idx != 0) {
+                               if (isdigit(token[0]) && isdigit(token[1]) &&
+                                   isdigit(token[2]) && token[3] == '\0') {
+                                       /* Yes. */
+                                       is_reqresp = 1;
+                               }
+                       }
+               }
+       } else {
+               /*
+                * This protocol has only request and response lines
+                * (e.g., FTP, where all the data goes over a
+                * different connection); assume the payload is
+                * a request or response.
+                */
+               is_reqresp = 1;
+       }
+
+       /* Capitalize the protocol name */
+       for (pnp = protoname; *pnp != '\0'; pnp++)
+               ND_PRINT((ndo, "%c", toupper(*pnp)));
+
+       if (is_reqresp) {
+               /*
+                * In non-verbose mode, just print the protocol, followed
+                * by the first line as the request or response info.
+                *
+                * In verbose mode, print lines as text until we run out
+                * of characters or see something that's not a
+                * printable-ASCII line.
+                */
+               if (ndo->ndo_vflag) {
+                       /*
+                        * We're going to print all the text lines in the
+                        * request or response; just print the length
+                        * on the first line of the output.
+                        */
+                       ND_PRINT((ndo, ", length: %u", len));
+                       for (idx = 0;
+                           idx < len && (eol = print_txt_line(ndo, protoname, "\n\t", pptr, idx, len)) != 0;
+                           idx = eol)
+                               ;
+               } else {
+                       /*
+                        * Just print the first text line.
+                        */
+                       print_txt_line(ndo, protoname, ": ", pptr, 0, len);
+               }
+       }
+}
+
 /* VARARGS */
 void
 error(const char *fmt, ...)
 /* VARARGS */
 void
 error(const char *fmt, ...)