+#include "ip.h"
+#ifdef INET6
+#include "ip6.h"
+#endif
+#include "rpc_auth.h"
+#include "rpc_msg.h"
+
+static const char tstr[] = " [|nfs]";
+
+static void nfs_printfh(netdissect_options *, const uint32_t *, const u_int);
+static int xid_map_enter(netdissect_options *, const struct sunrpc_msg *, const u_char *);
+static int xid_map_find(const struct sunrpc_msg *, const u_char *,
+ uint32_t *, uint32_t *);
+static void interp_reply(netdissect_options *, const struct sunrpc_msg *, uint32_t, uint32_t, int);
+static const uint32_t *parse_post_op_attr(netdissect_options *, const uint32_t *, int);
+
+/*
+ * Mapping of old NFS Version 2 RPC numbers to generic numbers.
+ */
+uint32_t nfsv3_procid[NFS_NPROCS] = {
+ NFSPROC_NULL,
+ NFSPROC_GETATTR,
+ NFSPROC_SETATTR,
+ NFSPROC_NOOP,
+ NFSPROC_LOOKUP,
+ NFSPROC_READLINK,
+ NFSPROC_READ,
+ NFSPROC_NOOP,
+ NFSPROC_WRITE,
+ NFSPROC_CREATE,
+ NFSPROC_REMOVE,
+ NFSPROC_RENAME,
+ NFSPROC_LINK,
+ NFSPROC_SYMLINK,
+ NFSPROC_MKDIR,
+ NFSPROC_RMDIR,
+ NFSPROC_READDIR,
+ NFSPROC_FSSTAT,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP,
+ NFSPROC_NOOP
+};
+
+static const struct tok nfsproc_str[] = {
+ { NFSPROC_NOOP, "nop" },
+ { NFSPROC_NULL, "null" },
+ { NFSPROC_GETATTR, "getattr" },
+ { NFSPROC_SETATTR, "setattr" },
+ { NFSPROC_LOOKUP, "lookup" },
+ { NFSPROC_ACCESS, "access" },
+ { NFSPROC_READLINK, "readlink" },
+ { NFSPROC_READ, "read" },
+ { NFSPROC_WRITE, "write" },
+ { NFSPROC_CREATE, "create" },
+ { NFSPROC_MKDIR, "mkdir" },
+ { NFSPROC_SYMLINK, "symlink" },
+ { NFSPROC_MKNOD, "mknod" },
+ { NFSPROC_REMOVE, "remove" },
+ { NFSPROC_RMDIR, "rmdir" },
+ { NFSPROC_RENAME, "rename" },
+ { NFSPROC_LINK, "link" },
+ { NFSPROC_READDIR, "readdir" },
+ { NFSPROC_READDIRPLUS, "readdirplus" },
+ { NFSPROC_FSSTAT, "fsstat" },
+ { NFSPROC_FSINFO, "fsinfo" },
+ { NFSPROC_PATHCONF, "pathconf" },
+ { NFSPROC_COMMIT, "commit" },
+ { 0, NULL }
+};
+
+/*
+ * NFS V2 and V3 status values.
+ *
+ * Some of these come from the RFCs for NFS V2 and V3, with the message
+ * strings taken from the FreeBSD C library "errlst.c".
+ *
+ * Others are errors that are not in the RFC but that I suspect some
+ * NFS servers could return; the values are FreeBSD errno values, as
+ * the first NFS server was the SunOS 2.0 one, and until 5.0 SunOS
+ * was primarily BSD-derived.
+ */
+static const struct tok status2str[] = {
+ { 1, "Operation not permitted" }, /* EPERM */
+ { 2, "No such file or directory" }, /* ENOENT */
+ { 5, "Input/output error" }, /* EIO */
+ { 6, "Device not configured" }, /* ENXIO */
+ { 11, "Resource deadlock avoided" }, /* EDEADLK */
+ { 12, "Cannot allocate memory" }, /* ENOMEM */
+ { 13, "Permission denied" }, /* EACCES */
+ { 17, "File exists" }, /* EEXIST */
+ { 18, "Cross-device link" }, /* EXDEV */
+ { 19, "Operation not supported by device" }, /* ENODEV */
+ { 20, "Not a directory" }, /* ENOTDIR */
+ { 21, "Is a directory" }, /* EISDIR */
+ { 22, "Invalid argument" }, /* EINVAL */
+ { 26, "Text file busy" }, /* ETXTBSY */
+ { 27, "File too large" }, /* EFBIG */
+ { 28, "No space left on device" }, /* ENOSPC */
+ { 30, "Read-only file system" }, /* EROFS */
+ { 31, "Too many links" }, /* EMLINK */
+ { 45, "Operation not supported" }, /* EOPNOTSUPP */
+ { 62, "Too many levels of symbolic links" }, /* ELOOP */
+ { 63, "File name too long" }, /* ENAMETOOLONG */
+ { 66, "Directory not empty" }, /* ENOTEMPTY */
+ { 69, "Disc quota exceeded" }, /* EDQUOT */
+ { 70, "Stale NFS file handle" }, /* ESTALE */
+ { 71, "Too many levels of remote in path" }, /* EREMOTE */
+ { 99, "Write cache flushed to disk" }, /* NFSERR_WFLUSH (not used) */
+ { 10001, "Illegal NFS file handle" }, /* NFS3ERR_BADHANDLE */
+ { 10002, "Update synchronization mismatch" }, /* NFS3ERR_NOT_SYNC */
+ { 10003, "READDIR/READDIRPLUS cookie is stale" }, /* NFS3ERR_BAD_COOKIE */
+ { 10004, "Operation not supported" }, /* NFS3ERR_NOTSUPP */
+ { 10005, "Buffer or request is too small" }, /* NFS3ERR_TOOSMALL */
+ { 10006, "Unspecified error on server" }, /* NFS3ERR_SERVERFAULT */
+ { 10007, "Object of that type not supported" }, /* NFS3ERR_BADTYPE */
+ { 10008, "Request couldn't be completed in time" }, /* NFS3ERR_JUKEBOX */
+ { 0, NULL }
+};
+
+static const struct tok nfsv3_writemodes[] = {
+ { 0, "unstable" },
+ { 1, "datasync" },
+ { 2, "filesync" },
+ { 0, NULL }
+};
+
+static const struct tok type2str[] = {
+ { NFNON, "NON" },
+ { NFREG, "REG" },
+ { NFDIR, "DIR" },
+ { NFBLK, "BLK" },
+ { NFCHR, "CHR" },
+ { NFLNK, "LNK" },
+ { NFFIFO, "FIFO" },
+ { 0, NULL }
+};
+
+static const struct tok sunrpc_auth_str[] = {
+ { SUNRPC_AUTH_OK, "OK" },
+ { SUNRPC_AUTH_BADCRED, "Bogus Credentials (seal broken)" },
+ { SUNRPC_AUTH_REJECTEDCRED, "Rejected Credentials (client should begin new session)" },
+ { SUNRPC_AUTH_BADVERF, "Bogus Verifier (seal broken)" },
+ { SUNRPC_AUTH_REJECTEDVERF, "Verifier expired or was replayed" },
+ { SUNRPC_AUTH_TOOWEAK, "Credentials are too weak" },
+ { SUNRPC_AUTH_INVALIDRESP, "Bogus response verifier" },
+ { SUNRPC_AUTH_FAILED, "Unknown failure" },
+ { 0, NULL }
+};
+
+static const struct tok sunrpc_str[] = {
+ { SUNRPC_PROG_UNAVAIL, "PROG_UNAVAIL" },
+ { SUNRPC_PROG_MISMATCH, "PROG_MISMATCH" },
+ { SUNRPC_PROC_UNAVAIL, "PROC_UNAVAIL" },
+ { SUNRPC_GARBAGE_ARGS, "GARBAGE_ARGS" },
+ { SUNRPC_SYSTEM_ERR, "SYSTEM_ERR" },
+ { 0, NULL }
+};
+
+static void
+print_nfsaddr(netdissect_options *ndo,
+ const u_char *bp, const char *s, const char *d)
+{
+ struct ip *ip;
+#ifdef INET6
+ struct ip6_hdr *ip6;
+ char srcaddr[INET6_ADDRSTRLEN], dstaddr[INET6_ADDRSTRLEN];
+#else
+#ifndef INET_ADDRSTRLEN
+#define INET_ADDRSTRLEN 16
+#endif
+ char srcaddr[INET_ADDRSTRLEN], dstaddr[INET_ADDRSTRLEN];
+#endif
+
+ srcaddr[0] = dstaddr[0] = '\0';
+ switch (IP_V((struct ip *)bp)) {
+ case 4:
+ ip = (struct ip *)bp;
+ strlcpy(srcaddr, ipaddr_string(ndo, &ip->ip_src), sizeof(srcaddr));
+ strlcpy(dstaddr, ipaddr_string(ndo, &ip->ip_dst), sizeof(dstaddr));
+ break;
+#ifdef INET6
+ case 6:
+ ip6 = (struct ip6_hdr *)bp;
+ strlcpy(srcaddr, ip6addr_string(ndo, &ip6->ip6_src),
+ sizeof(srcaddr));
+ strlcpy(dstaddr, ip6addr_string(ndo, &ip6->ip6_dst),
+ sizeof(dstaddr));
+ break;
+#endif
+ default:
+ strlcpy(srcaddr, "?", sizeof(srcaddr));
+ strlcpy(dstaddr, "?", sizeof(dstaddr));
+ break;
+ }
+
+ ND_PRINT((ndo, "%s.%s > %s.%s: ", srcaddr, s, dstaddr, d));
+}
+
+static const uint32_t *
+parse_sattr3(netdissect_options *ndo,
+ const uint32_t *dp, struct nfsv3_sattr *sa3)
+{
+ ND_TCHECK(dp[0]);
+ sa3->sa_modeset = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_modeset) {
+ ND_TCHECK(dp[0]);
+ sa3->sa_mode = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ ND_TCHECK(dp[0]);
+ sa3->sa_uidset = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_uidset) {
+ ND_TCHECK(dp[0]);
+ sa3->sa_uid = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ ND_TCHECK(dp[0]);
+ sa3->sa_gidset = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_gidset) {
+ ND_TCHECK(dp[0]);
+ sa3->sa_gid = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ ND_TCHECK(dp[0]);
+ sa3->sa_sizeset = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_sizeset) {
+ ND_TCHECK(dp[0]);
+ sa3->sa_size = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ ND_TCHECK(dp[0]);
+ sa3->sa_atimetype = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_atimetype == NFSV3SATTRTIME_TOCLIENT) {
+ ND_TCHECK(dp[1]);
+ sa3->sa_atime.nfsv3_sec = EXTRACT_32BITS(dp);
+ dp++;
+ sa3->sa_atime.nfsv3_nsec = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ ND_TCHECK(dp[0]);
+ sa3->sa_mtimetype = EXTRACT_32BITS(dp);
+ dp++;
+ if (sa3->sa_mtimetype == NFSV3SATTRTIME_TOCLIENT) {
+ ND_TCHECK(dp[1]);
+ sa3->sa_mtime.nfsv3_sec = EXTRACT_32BITS(dp);
+ dp++;
+ sa3->sa_mtime.nfsv3_nsec = EXTRACT_32BITS(dp);
+ dp++;
+ }
+
+ return dp;
+trunc:
+ return NULL;
+}