/*
* Copyright (c) 1988, 1989, 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997
* The Regents of the University of California. All rights reserved.
*
* 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, (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, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* 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.
*/
/*
* NFS Version 3 sattr3 structure for the new node creation case.
* This does not have a fixed layout on the network, so this
* structure does not correspond to the layout of the data on
* the network; it's used to store the data when the sattr3
* is parsed for use when it's later printed.
*/
struct nfsv3_sattr {
uint32_t sa_modeset;
uint32_t sa_mode;
uint32_t sa_uidset;
uint32_t sa_uid;
uint32_t sa_gidset;
uint32_t sa_gid;
uint32_t sa_sizeset;
uint32_t sa_size;
uint32_t sa_atimetype;
struct {
uint32_t nfsv3_sec;
uint32_t nfsv3_nsec;
} sa_atime;
uint32_t sa_mtimetype;
struct {
uint32_t nfsv3_sec;
uint32_t nfsv3_nsec;
} sa_mtime;
};
/*
* Return a pointer to the first file handle in the packet.
* If the packet was truncated, return 0.
*/
UNALIGNED_OK
static const uint32_t *
parsereq(netdissect_options *ndo,
const struct sunrpc_msg *rp, u_int length)
{
const uint32_t *dp;
u_int len, rounded_len;
/*
* Find the start of the req data (if we captured it).
* First, get the length of the credentials, and make sure
* we have all of the opaque part of the credentials.
*/
dp = (const uint32_t *)&rp->rm_call.cb_cred;
if (length < 2 * sizeof(*dp))
goto trunc;
len = GET_BE_U_4(dp + 1);
if (len > length) {
ND_PRINT(" [credentials length %u > %u]", len, length);
nd_print_invalid(ndo);
return NULL;
}
rounded_len = roundup2(len, 4);
ND_TCHECK_LEN(dp + 2, rounded_len);
if (2 * sizeof(*dp) + rounded_len <= length) {
/*
* We have all of the credentials. Skip past them; they
* consist of 4 bytes of flavor, 4 bytes of length,
* and len-rounded-up-to-a-multiple-of-4 bytes of
* data.
*/
dp += (len + (2 * sizeof(*dp) + 3)) / sizeof(*dp);
length -= 2 * sizeof(*dp) + rounded_len;
/*
* Now get the length of the verifier, and make sure
* we have all of the opaque part of the verifier.
*/
if (length < 2 * sizeof(*dp))
goto trunc;
len = GET_BE_U_4(dp + 1);
if (len > length) {
ND_PRINT(" [verifier length %u > %u]", len, length);
nd_print_invalid(ndo);
return NULL;
}
rounded_len = roundup2(len, 4);
ND_TCHECK_LEN(dp + 2, rounded_len);
if (2 * sizeof(*dp) + rounded_len < length) {
/*
* We have all of the verifier. Skip past it;
* it consists of 4 bytes of flavor, 4 bytes of
* length, and len-rounded-up-to-a-multiple-of-4
* bytes of data.
*/
dp += (len + (2 * sizeof(*dp) + 3)) / sizeof(*dp);
return (dp);
}
}
trunc:
return (NULL);
}
/*
* Print out an NFS file handle and return a pointer to following word.
* If packet was truncated, return 0.
*/
static const uint32_t *
parsefh(netdissect_options *ndo,
const uint32_t *dp, int v3)
{
u_int len;
if (v3) {
len = GET_BE_U_4(dp) / 4;
dp++;
} else
len = NFSX_V2FH / 4;
if (ND_TTEST_LEN(dp, len * sizeof(*dp))) {
nfs_printfh(ndo, dp, len);
return (dp + len);
} else
return NULL;
}
/*
* Print out a file name and return pointer to 32-bit word past it.
* If packet was truncated, return 0.
*/
static const uint32_t *
parsefn(netdissect_options *ndo,
const uint32_t *dp)
{
uint32_t len, rounded_len;
const u_char *cp;
/* Fetch big-endian string length */
len = GET_BE_U_4(dp);
dp++;
if (UINT_MAX - len < 3) {
ND_PRINT("[cannot pad to 32-bit boundaries]");
nd_print_invalid(ndo);
return NULL;
}
/*
* Print out file handle and file name.
* Return pointer to 32-bit word past file name.
* If packet was truncated (or there was some other error), return 0.
*/
static const uint32_t *
parsefhn(netdissect_options *ndo,
const uint32_t *dp, int v3)
{
dp = parsefh(ndo, dp, v3);
if (dp == NULL)
return (NULL);
ND_PRINT(" ");
return (parsefn(ndo, dp));
}
case NFSPROC_GETATTR:
case NFSPROC_SETATTR:
case NFSPROC_READLINK:
case NFSPROC_FSSTAT:
case NFSPROC_FSINFO:
case NFSPROC_PATHCONF:
dp = parsereq(ndo, rp, length);
if (dp == NULL)
goto trunc;
if (parsefh(ndo, dp, v3) == NULL)
goto trunc;
break;
case NFSPROC_LOOKUP:
case NFSPROC_CREATE:
case NFSPROC_MKDIR:
case NFSPROC_REMOVE:
case NFSPROC_RMDIR:
dp = parsereq(ndo, rp, length);
if (dp == NULL)
goto trunc;
if (parsefhn(ndo, dp, v3) == NULL)
goto trunc;
break;
case NFSPROC_ACCESS:
dp = parsereq(ndo, rp, length);
if (dp == NULL)
goto trunc;
dp = parsefh(ndo, dp, v3);
if (dp == NULL)
goto trunc;
access_flags = GET_BE_U_4(dp);
if (access_flags & ~NFSV3ACCESS_FULL) {
/* NFSV3ACCESS definitions aren't up to date */
ND_PRINT(" %04x", access_flags);
} else if ((access_flags & NFSV3ACCESS_FULL) == NFSV3ACCESS_FULL) {
ND_PRINT(" NFS_ACCESS_FULL");
} else {
char separator = ' ';
if (access_flags & NFSV3ACCESS_READ) {
ND_PRINT(" NFS_ACCESS_READ");
separator = '|';
}
if (access_flags & NFSV3ACCESS_LOOKUP) {
ND_PRINT("%cNFS_ACCESS_LOOKUP", separator);
separator = '|';
}
if (access_flags & NFSV3ACCESS_MODIFY) {
ND_PRINT("%cNFS_ACCESS_MODIFY", separator);
separator = '|';
}
if (access_flags & NFSV3ACCESS_EXTEND) {
ND_PRINT("%cNFS_ACCESS_EXTEND", separator);
separator = '|';
}
if (access_flags & NFSV3ACCESS_DELETE) {
ND_PRINT("%cNFS_ACCESS_DELETE", separator);
separator = '|';
}
if (access_flags & NFSV3ACCESS_EXECUTE)
ND_PRINT("%cNFS_ACCESS_EXECUTE", separator);
}
break;
/*
* Print out an NFS file handle.
* We assume packet was not truncated before the end of the
* file handle pointed to by dp.
*
* Note: new version (using portable file-handle parser) doesn't produce
* generation number. It probably could be made to do that, with some
* additional hacking on the parser code.
*/
static void
nfs_printfh(netdissect_options *ndo,
const uint32_t *dp, const u_int len)
{
my_fsid fsid;
uint32_t ino;
const char *sfsname = NULL;
char *spacep;
if (ndo->ndo_uflag) {
u_int i;
char const *sep = "";
ND_PRINT(" fh[");
for (i=0; i<len; i++) {
/*
* This displays 4 bytes in big-endian byte
* order. That's as good a choice as little-
* endian, as there's no guarantee that the
* server is big-endian or little-endian or
* that the file handle contains 4-byte
* integral fields, and is better than "the
* byte order of the host running tcpdump", as
* the latter means that different hosts
* running tcpdump may show the same file
* handle in different ways.
*/
ND_PRINT("%s%x", sep, GET_BE_U_4(dp + i));
sep = ":";
}
ND_PRINT("]");
return;
}
/*
* Maintain a small cache of recent client.XID.server/proc pairs, to allow
* us to match up replies with requests and thus to know how to parse
* the reply.
*/
struct xid_map_entry {
uint32_t xid; /* transaction ID (net order) */
int ipver; /* IP version (4 or 6) */
nd_ipv6 client; /* client IP address (net order) */
nd_ipv6 server; /* server IP address (net order) */
uint32_t proc; /* call proc number (host order) */
uint32_t vers; /* program version (host order) */
};
/*
* Map entries are kept in an array that we manage as a ring;
* new entries are always added at the tail of the ring. Initially,
* all the entries are zero and hence don't match anything.
*/
#define XIDMAPSIZE 64
static struct xid_map_entry xid_map[XIDMAPSIZE];
static int xid_map_next = 0;
static int xid_map_hint = 0;
/*
* Returns 0 and puts NFSPROC_xxx in proc return and
* version in vers return, or returns -1 on failure
*/
UNALIGNED_OK
static int
xid_map_find(netdissect_options *ndo, const struct sunrpc_msg *rp,
const u_char *bp, uint32_t *proc, uint32_t *vers)
{
int i;
struct xid_map_entry *xmep;
uint32_t xid;
const struct ip *ip = (const struct ip *)bp;
const struct ip6_hdr *ip6 = (const struct ip6_hdr *)bp;
int cmp;
UNALIGNED_MEMCPY(&xid, &rp->rm_xid, sizeof(xmep->xid));
/* Start searching from where we last left off */
i = xid_map_hint;
do {
xmep = &xid_map[i];
cmp = 1;
if (xmep->ipver != IP_V(ip) || xmep->xid != xid)
goto nextitem;
switch (xmep->ipver) {
case 4:
if (UNALIGNED_MEMCMP(ip->ip_src, &xmep->server,
sizeof(ip->ip_src)) != 0 ||
UNALIGNED_MEMCMP(ip->ip_dst, &xmep->client,
sizeof(ip->ip_dst)) != 0) {
cmp = 0;
}
break;
case 6:
if (UNALIGNED_MEMCMP(ip6->ip6_src, &xmep->server,
sizeof(ip6->ip6_src)) != 0 ||
UNALIGNED_MEMCMP(ip6->ip6_dst, &xmep->client,
sizeof(ip6->ip6_dst)) != 0) {
cmp = 0;
}
break;
default:
cmp = 0;
break;
}
if (cmp) {
/* match */
xid_map_hint = i;
*proc = xmep->proc;
*vers = xmep->vers;
return 0;
}
nextitem:
if (++i >= XIDMAPSIZE)
i = 0;
} while (i != xid_map_hint);
/* search failed */
return (-1);
}
/*
* Routines for parsing reply packets
*/
/*
* Return a pointer to the beginning of the actual results.
* If the packet was truncated, return 0.
*/
UNALIGNED_OK
static const uint32_t *
parserep(netdissect_options *ndo,
const struct sunrpc_msg *rp, u_int length, int *nfserrp)
{
const uint32_t *dp;
u_int len;
enum sunrpc_accept_stat astat;
/*
* Portability note:
* Here we find the address of the ar_verf credentials.
* Originally, this calculation was
* dp = (uint32_t *)&rp->rm_reply.rp_acpt.ar_verf
* On the wire, the rp_acpt field starts immediately after
* the (32 bit) rp_stat field. However, rp_acpt (which is a
* "struct accepted_reply") contains a "struct opaque_auth",
* whose internal representation contains a pointer, so on a
* 64-bit machine the compiler inserts 32 bits of padding
* before rp->rm_reply.rp_acpt.ar_verf. So, we cannot use
* the internal representation to parse the on-the-wire
* representation. Instead, we skip past the rp_stat field,
* which is an "enum" and so occupies one 32-bit word.
*/
dp = ((const uint32_t *)&rp->rm_reply) + 1;
len = GET_BE_U_4(dp + 1);
if (len >= length)
return (NULL);
/*
* skip past the ar_verf credentials.
*/
dp += (len + (2*sizeof(uint32_t) + 3)) / sizeof(uint32_t);
/*
* now we can check the ar_stat field
*/
astat = (enum sunrpc_accept_stat) GET_BE_U_4(dp);
if (astat != SUNRPC_SUCCESS) {
ND_PRINT(" %s", tok2str(sunrpc_str, "ar_stat %u", astat));
*nfserrp = 1; /* suppress trunc string */
return (NULL);
}
/* successful return */
ND_TCHECK_LEN(dp, sizeof(astat));
return ((const uint32_t *) (sizeof(astat) + ((const char *)dp)));
trunc:
return (0);
}
static const uint32_t *
parsev3rddirres(netdissect_options *ndo,
const uint32_t *dp, int verbose, int *nfserrp)
{
u_int er;
dp = parsestatus(ndo, dp, &er, nfserrp);
if (dp == NULL)
return (0);
if (ndo->ndo_vflag)
ND_PRINT(" POST:");
dp = parse_post_op_attr(ndo, dp, verbose);
if (dp == NULL)
return (0);
if (er)
return dp;
if (ndo->ndo_vflag) {
/*
* This displays the 8 bytes of the verifier in order,
* from the low-order byte to the high-order byte.
*/
ND_PRINT(" verf %08x%08x",
GET_BE_U_4(dp), GET_BE_U_4(dp + 1));
dp += 2;
}
return dp;
}
static int
parsefsinfo(netdissect_options *ndo,
const uint32_t *dp, int *nfserrp)
{
const struct nfsv3_fsinfo *sfp;
u_int er;
dp = parsestatus(ndo, dp, &er, nfserrp);
if (dp == NULL)
return (0);
if (ndo->ndo_vflag)
ND_PRINT(" POST:");
dp = parse_post_op_attr(ndo, dp, ndo->ndo_vflag);
if (dp == NULL)
return (0);
if (er)
return (1);