/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright 2011 Nexenta Systems, Inc.  All rights reserved.
 */

/*
 * References used throughout this code:
 *
 * [CIFS/1.0] : A Common Internet File System (CIFS/1.0) Protocol
 *		Internet Engineering Task Force (IETF) draft
 *		Paul J. Leach, Microsoft, Dec. 1997
 *
 * [X/Open-SMB] : X/Open CAE Specification;
 *		Protocols for X/Open PC Interworking: SMB, Version 2
 *		X/Open Document Number: C209
 */

#include <fcntl.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "snoop.h"

/*
 * SMB Format (header)
 * [X/Open-SMB, Sec. 5.1]
 */
struct smb {
	uchar_t idf[4]; /*  identifier, contains 0xff, 'SMB'  */
	uchar_t com;    /*  command code  */
	uchar_t err[4]; /*  NT Status, or error class+code */
	uchar_t flags;
	uchar_t flags2[2];
	uchar_t re[12];
	uchar_t tid[2];
	uchar_t pid[2];
	uchar_t uid[2];
	uchar_t mid[2];
	/*
	 * immediately after the above 32 byte header:
	 *   unsigned char  WordCount;
	 *   unsigned short ParameterWords[ WordCount ];
	 *   unsigned short ByteCount;
	 *   unsigned char  ParameterBytes[ ByteCount ];
	 */
};

/* smb flags */
#define	SERVER_RESPONSE		0x80

/* smb flags2 */
#define	FLAGS2_EXT_SEC		0x0800	/* Extended security */
#define	FLAGS2_NT_STATUS	0x4000	/* NT status codes */
#define	FLAGS2_UNICODE		0x8000	/* String are Unicode */

static void interpret_sesssetupX(int, uchar_t *, int, char *, int);
static void interpret_tconX(int, uchar_t *, int, char *, int);
static void interpret_trans(int, uchar_t *, int, char *, int);
static void interpret_trans2(int, uchar_t *, int, char *, int);
static void interpret_negprot(int, uchar_t *, int, char *, int);
static void interpret_default(int, uchar_t *, int, char *, int);

/*
 * Trans2 subcommand codes
 * [X/Open-SMB, Sec. 16.1.7]
 */
#define	TRANS2_OPEN 0x00
#define	TRANS2_FIND_FIRST 0x01
#define	TRANS2_FIND_NEXT2 0x02
#define	TRANS2_QUERY_FS_INFORMATION 0x03
#define	TRANS2_QUERY_PATH_INFORMATION 0x05
#define	TRANS2_SET_PATH_INFORMATION 0x06
#define	TRANS2_QUERY_FILE_INFORMATION 0x07
#define	TRANS2_SET_FILE_INFORMATION 0x08
#define	TRANS2_CREATE_DIRECTORY 0x0D


struct decode {
	char *name;
	void (*func)(int, uchar_t *, int, char *, int);
	char *callfmt;
	char *replyfmt;
};

/*
 * SMB command codes (function names)
 * [X/Open-SMB, Sec. 5.2]
 */
static struct decode SMBtable[256] = {
	/* 0x00 */
	{ "mkdir", 0, 0, 0 },
	{ "rmdir", 0, 0, 0 },
	{ "open", 0, 0, 0 },
	{ "create", 0, 0, 0 },

	{
		"close", 0,
		/* [X/Open-SMB, Sec. 7.10] */
		"WFileID\0"
		"lLastModTime\0"
		"dByteCount\0\0",
		"dByteCount\0\0"
	},

	{ "flush", 0, 0, 0 },
	{ "unlink", 0, 0, 0 },

	{
		"move", 0,
		/* [X/Open-SMB, Sec. 7.11] */
		"wFileAttributes\0"
		"dByteCount\0r\0"
		"UFileName\0r\0"
		"UNewPath\0\0",
		"dByteCount\0\0"
	},

	{
		"getatr", 0,
		/* [X/Open-SMB, Sec. 8.4] */
		"dBytecount\0r\0"
		"UFileName\0\0",
		"wFileAttributes\0"
		"lTime\0"
		"lSize\0"
		"R\0R\0R\0R\0R\0"
		"dByteCount\0\0"
	},

	{ "setatr", 0, 0, 0 },

	{
		"read", 0,
		/* [X/Open-SMB, Sec. 7.4] */
		"WFileID\0"
		"wI/0 Bytes\0"
		"LFileOffset\0"
		"WBytesLeft\0"
		"dByteCount\0\0",
		"WDataLength\0"
		"R\0R\0R\0R\0"
		"dByteCount\0\0"
	},

	{
		"write", 0,
		/* [X/Open-SMB, Sec. 7.5] */
		"WFileID\0"
		"wI/0 Bytes\0"
		"LFileOffset\0"
		"WBytesLeft\0"
		"dByteCount\0\0",
		"WDataLength\0"
		"dByteCount\0\0"
	},

	{ "lock", 0, 0, 0 },
	{ "unlock", 0, 0, 0 },
	{ "ctemp", 0, 0, 0 },
	{ "mknew", 0, 0, 0 },

	/* 0x10 */
	{
		"chkpth", 0,
		/* [X/Open-SMB, Sec. 8.7] */
		"dByteCount\0r\0"
		"UFile\0\0",
		"dByteCount\0\0"
	},

	{ "exit", 0, 0, 0 },
	{ "lseek", 0, 0, 0 },
	{ "lockread", 0, 0, 0 },
	{ "writeunlock", 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	{
		"readbraw", 0,
		/* [X/Open-SMB, Sec. 10.1] */
		"WFileID\0"
		"LFileOffset\0"
		"wMaxCount\0"
		"wMinCount\0"
		"lTimeout\0R\0"
		"dByteCount\0\0", 0
	},

	{ "readbmpx", 0, 0, 0 },
	{ "readbs", 0, 0, 0 },
	{ "writebraw", 0, 0, 0 },
	{ "writebmpx", 0, 0, 0 },
	{ "writebs", 0, 0, 0 },

	/* 0x20 */
	{ "writec", 0, 0, 0 },
	{ "qrysrv", 0, 0, 0 },
	{ "setattrE", 0, 0, 0 },
	{ "getattrE", 0, 0, 0 },

	{
		"lockingX", 0,
		/* [X/Open-SMB, Sec. 12.2] */
		"wChainedCommand\0"
		"wNextOffset\0"
		"WFileID\0"
		"wLockType\0"
		"lOpenTimeout\0"
		"W#Unlocks\0"
		"W#Locks\0"
		"dByteCount\0\0", 0
	},

	{ "trans", interpret_trans, 0, 0 },
	{ "transs", 0, 0, 0 },
	{ "ioctl", 0, 0, 0 },
	{ "ioctls", 0, 0, 0 },
	{ "copy", 0, 0, 0 },
	{ "move", 0, 0, 0 },
	{ "echo", 0, 0, 0 },
	{ "writeclose", 0, 0, 0 },

	{
		/* [X/Open-SMB, Sec. 12.1] */
		"openX", 0,
		/* call */
		"wChainedCommand\0"
		"wNextOffset\0"
		"wFlags\0"
		"wMode\0"
		"wSearchAttributes\0"
		"wFileAttributes\0"
		"lTime\0"
		"wOpenFunction\0"
		"lFileSize\0"
		"lOpenTimeout\0R\0R\0"
		"dByteCount\0r\0"
		"UFileName\0\0",
		/* reply */
		"wChainedCommand\0"
		"wNextOffset\0"
		"WFileID\0"
		"wAttributes\0"
		"lTime\0"
		"LSize\0"
		"wOpenMode\0"
		"wFileType\0"
		"wDeviceState\0"
		"wActionTaken\0"
		"lUniqueFileID\0R\0"
		"wBytecount\0\0"
	},

	{
		/* [CIFS 4.2.4] */
		"readX", 0,
		/* call */
		"wChainedCommand\0"
		"wNextOffset\0"
		"WFileID\0"
		"LOffset\0"
		"DMaxCount\0"
		"dMinCount\0"
		"dMaxCountHigh\0"
		"R\0"
		"wRemaining\0"
		"lOffsetHigh\0"
		"dByteCount\0\0",
		/* reply */
		"wChainedCommand\0"
		"wNextOffset\0"
		"dRemaining\0R\0R\0"
		"DCount\0"
		"dDataOffset\0"
		"dCountHigh\0"
		"R\0R\0R\0R\0"
		"dByteCount\0\0"
	},

	{
		/* [CIFS 4.2.5] */
		"writeX", 0,
		/* call */
		"wChainedCommand\0"
		"wNextOffset\0"
		"WFileID\0"
		"LOffset\0R\0R\0"
		"wWriteMode\0"
		"wRemaining\0"
		"dDataLenHigh\0"
		"DDataLen\0"
		"dDataOffset\0"
		"lOffsetHigh\0\0",
		/* reply */
		"wChainedCommand\0"
		"wNextOffset\0"
		"DCount\0"
		"wRemaining\0"
		"wCountHigh\0\0"
	},

	/* 0x30 */
	{ 0, 0, 0, 0 },
	{ "closeTD", 0, 0, 0 },
	{ "trans2", interpret_trans2, 0, 0 },
	{ "trans2s", 0, 0, 0 },
	{
		"findclose", 0,
		/* [X/Open-SMB, Sec. 15.4 ] */
		"WFileID\0"
		"dByteCount\0\0",
		"dByteCount\0\0"
	},
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x40 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x50 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x60 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x70 */
	{ "tcon", 0, 0, 0 },
	{
		"tdis", 0,
		/* [X/Open-SMB, Sec. 6.3] */
		"dByteCount\0\0",
		"dByteCount\0\0"
	},
	{ "negprot", interpret_negprot, 0, 0 },
	{ "sesssetupX", interpret_sesssetupX, 0, 0 },
	{
		"uloggoffX", 0,
		/* [X/Open-SMB, Sec. 15.5] */
		"wChainedCommand\0"
		"wNextOffset\0\0",
		"wChainedCommnad\0"
		"wNextOffset\0\0" },
	{ "tconX", interpret_tconX, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x80 */
	{ "dskattr", 0, 0, 0 },
	{ "search", 0, 0, 0 },
	{ "ffirst", 0, 0, 0 },
	{ "funique", 0, 0, 0 },
	{ "fclose", 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0x90 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xa0 */
	/*
	 * Command codes 0xa0 to 0xa7 are from
	 * [CIFS/1.0, Sec. 5.1]
	 */
	{ "_NT_Trans", 0, 0, 0 },
	{ "_NT_Trans2", 0, 0, 0 },
	{
		/* [CIFS/1.0, Sec. 4.2.1] */
		"_NT_CreateX", 0,
		/* Call */
		"wChainedCommand\0"
		"wNextOffset\0r\0"
		"dNameLength\0"
		"lCreateFlags\0"
		"lRootDirFID\0"
		"lDesiredAccess\0"
		"lAllocSizeLow\0"
		"lAllocSizeHigh\0"
		"lNTFileAttributes\0"
		"lShareAccess\0"
		"lOpenDisposition\0"
		"lCreateOption\0"
		"lImpersonationLevel\0"
		"bSecurityFlags\0"
		"dByteCount\0r\0"
		"UFileName\0\0",
		/* Reply */
		"wChainedCommand\0"
		"wNextOffset\0"
		"bOplockLevel\0"
		"WFileID\0"
		"lCreateAction\0\0"
	},
	{ 0, 0, 0, 0 },
	{
		"_NT_Cancel", 0,
		/* [CIFS/1.0, Sec. 4.1.8] */
		"dByteCount\0", 0
	},
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xb0 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xc0 */
	{ "splopen", 0, 0, 0 },
	{ "splwr", 0, 0, 0 },
	{ "splclose", 0, 0, 0 },
	{ "splretq", 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xd0 */
	{ "sends", 0, 0, 0 },
	{ "sendb", 0, 0, 0 },
	{ "fwdname", 0, 0, 0 },
	{ "cancelf", 0, 0, 0 },
	{ "getmac", 0, 0, 0 },
	{ "sendstrt", 0, 0, 0 },
	{ "sendend", 0, 0, 0 },
	{ "sendtxt", 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xe0 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },

	/* 0xf0 */
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 },
	{ 0, 0, 0, 0 }
};

/* Helpers to get values in Intel order (often mis-aligned). */
static uint16_t
get2(uchar_t *p) {
	return (p[0] + (p[1]<<8));
}
static uint32_t
get4(uchar_t *p) {
	return (p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24));
}
static uint64_t
get8(uchar_t *p) {
	return (get4(p) | ((uint64_t)get4(p+4) << 32));
}

/*
 * Support displaying NT times.
 * Number of seconds between 1970 and 1601 year
 * (134774 days)
 */
static const uint64_t DIFF1970TO1601 = 11644473600ULL;
static const uint32_t TEN_MIL = 10000000UL;
static char *
format_nttime(uint64_t nt_time)
{
	uint64_t nt_sec;	/* seconds */
	uint64_t nt_tus;	/* tenths of uSec. */
	uint32_t ux_nsec;
	int64_t ux_sec;

	/* Optimize time zero. */
	if (nt_time == 0) {
		ux_sec = 0;
		ux_nsec = 0;
		goto out;
	}

	nt_sec = nt_time / TEN_MIL;
	nt_tus = nt_time % TEN_MIL;

	if (nt_sec <= DIFF1970TO1601) {
		ux_sec = 0;
		ux_nsec = 0;
		goto out;
	}
	ux_sec = nt_sec - DIFF1970TO1601;
	ux_nsec = nt_tus * 100;

out:
	return (format_time(ux_sec, ux_nsec));
}

/*
 * This is called by snoop_netbios.c.
 * This is the external entry point.
 */
void
interpret_smb(int flags, uchar_t *data, int len)
{
	struct smb *smb;
	struct decode *decoder;
	char xtra[MAXLINE];
	ushort_t smb_flags2;
	void (*func)(int, uchar_t *, int, char *, int);

	if (len < sizeof (struct smb))
		return;

	smb = (struct smb *)data;
	decoder = &SMBtable[smb->com & 255];
	smb_flags2 = get2(smb->flags2);
	xtra[0] = '\0';

	/*
	 * SMB Header description
	 * [X/Open-SMB, Sec. 5.1]
	 */
	if (flags & F_DTAIL) {
		show_header("SMB:  ", "SMB Header", len);
		show_space();

		if (smb->flags & SERVER_RESPONSE)
			show_line("SERVER RESPONSE");
		else
			show_line("CLIENT REQUEST");

		if (decoder->name)
			show_printf("Command code = 0x%x (SMB%s)",
			    smb->com, decoder->name);
		else
			show_printf("Command code = 0x%x", smb->com);

		/*
		 * NT status or error class/code
		 * [X/Open-SMB, Sec. 5.6]
		 */
		if (smb_flags2 & FLAGS2_NT_STATUS) {
			show_printf("NT Status = %x", get4(smb->err));
		} else {
			/* Error classes [X/Open-SMB, Sec. 5.6] */
			show_printf("Error class/code = %d/%d",
			    smb->err[0], get2(&smb->err[2]));
		}

		show_printf("Flags summary = 0x%.2x", smb->flags);
		show_printf("Flags2 summary = 0x%.4x", smb_flags2);
		show_printf("Tree ID  (TID) = 0x%.4x", get2(smb->tid));
		show_printf("Proc. ID (PID) = 0x%.4x", get2(smb->pid));
		show_printf("User ID  (UID) = 0x%.4x", get2(smb->uid));
		show_printf("Mux. ID  (MID) = 0x%.4x", get2(smb->mid));
		show_space();
	}

	if ((func = decoder->func) == NULL)
		func = interpret_default;
	(*func)(flags, (uchar_t *)data, len, xtra, sizeof (xtra));

	if (flags & F_SUM) {
		char *p;
		int sz, tl;

		/* Will advance p and decr. sz */
		p = get_sum_line();
		sz = MAXLINE;

		/* Call or Reply */
		if (smb->flags & SERVER_RESPONSE)
			tl = snprintf(p, sz, "SMB R");
		else
			tl = snprintf(p, sz, "SMB C");
		p += tl;
		sz -= tl;

		/* The name, if known, else the cmd code */
		if (decoder->name) {
			tl = snprintf(p, sz, " Cmd=SMB%s", decoder->name);
		} else {
			tl = snprintf(p, sz, " Cmd=0x%02X", smb->com);
		}
		p += tl;
		sz -= tl;

		/*
		 * The "extra" (cmd-specific summary).
		 * If non-null, has leading blank.
		 */
		if (xtra[0] != '\0') {
			tl = snprintf(p, sz, "%s", xtra);
			p += tl;
			sz -= tl;
		}

		/*
		 * NT status or error class/code
		 * [X/Open-SMB, Sec. 5.6]
		 *
		 * Only show for response, not call.
		 */
		if (smb->flags & SERVER_RESPONSE) {
			if (smb_flags2 & FLAGS2_NT_STATUS) {
				uint_t status = get4(smb->err);
				snprintf(p, sz, " Status=0x%x", status);
			} else {
				uchar_t errcl = smb->err[0];
				ushort_t code = get2(&smb->err[2]);
				snprintf(p, sz, " Error=%d/%d", errcl, code);
			}
		}
	}

	if (flags & F_DTAIL)
		show_trailer();
}

static void
output_bytes(uchar_t *data, int bytecount)
{
	int i;
	char buff[80];
	char word[10];

	(void) strlcpy(buff, "  ", sizeof (buff));
	for (i = 0; i < bytecount; i++) {
		snprintf(word, sizeof (word), "%.2x ", data[i]);
		(void) strlcat(buff, word, sizeof (buff));
		if ((i+1)%16 == 0 || i == (bytecount-1)) {
			show_line(buff);
			(void) strlcpy(buff, "  ", sizeof (buff));
		}
	}
}

/*
 * Based on the Unicode Standard,  http://www.unicode.org/
 * "The Unicode Standard: A Technical Introduction", June 1998
 */
static int
unicode2ascii(char *outstr, int outlen, uchar_t *instr, int inlen)
{
	int i = 0, j = 0;
	char c;

	while (i < inlen && j < (outlen-1)) {
		/* Show unicode chars >= 256 as '?' */
		if (instr[i+1])
			c = '?';
		else
			c = instr[i];
		if (c == '\0')
			break;
		outstr[j] = c;
		i += 2;
		j++;
	}
	outstr[j] = '\0';
	return (j);
}

/*
 * Convenience macro to copy a string from the data,
 * either in UCS-2 or ASCII as indicated by UCS.
 * OBUF must be an array type (see sizeof) and
 * DP must be an L-value (this increments it).
 */
#define	GET_STRING(OBUF, DP, UCS)				\
{								\
	int _len, _sz = sizeof (OBUF);				\
	if (UCS) {						\
		if (((uintptr_t)DP) & 1)			\
			DP++;					\
		_len = unicode2ascii(OBUF, _sz, DP, 2 * _sz);	\
		DP += 2 * (_len + 1);				\
	} else {						\
		_len = strlcpy(OBUF, (char *)DP, _sz);		\
		DP += (_len + 1);				\
	}							\
}

/*
 * TRANS2 information levels
 * [X/Open-SMB, Sec. 16.1.6]
 */
static void
get_info_level(char *outstr, int outsz, int value)
{

	switch (value) {
	case 1:
		snprintf(outstr, outsz, "Standard");
		break;
	case 2:
		snprintf(outstr, outsz, "Query EA Size");
		break;
	case 3:
		snprintf(outstr, outsz, "Query EAS from List");
		break;
	case 0x101:
		snprintf(outstr, outsz, "Directory Info");
		break;
	case 0x102:
		snprintf(outstr, outsz, "Full Directory Info");
		break;
	case 0x103:
		snprintf(outstr, outsz, "Names Info");
		break;
	case 0x104:
		snprintf(outstr, outsz, "Both Directory Info");
		break;
	default:
		snprintf(outstr, outsz, "Unknown");
		break;
	}
}

/*
 * Interpret TRANS2_QUERY_PATH subcommand
 * [X/Open-SMB, Sec. 16.7]
 */
/* ARGSUSED */
static void
output_trans2_querypath(int flags, uchar_t *data, char *xtra, int xsz)
{
	int length;
	char filename[256];

	if (flags & F_SUM) {
		length = snprintf(xtra, xsz, " QueryPathInfo");
		xtra += length;
		xsz -= length;
		data += 6;
		(void) unicode2ascii(filename, 256, data, 512);
		snprintf(xtra, xsz, " File=%s", filename);
	}

	if (flags & F_DTAIL) {
		show_line("FunctionName = QueryPathInfo");
		show_printf("InfoLevel = 0x%.4x", get2(data));
		data += 6;
		(void) unicode2ascii(filename, 256, data, 512);
		show_printf("FileName = %s", filename);
	}
}

/*
 * Interpret TRANS2_QUERY_FILE subcommand
 * [X/Open-SMB, Sec. 16.9]
 */
/* ARGSUSED */
static void
output_trans2_queryfile(int flags, uchar_t *data, char *xtra, int xsz)
{
	int length;

	if (flags & F_SUM) {
		length = snprintf(xtra, xsz, " QueryFileInfo");
		xtra += length;
		xsz -= length;
		snprintf(xtra, xsz, " FileID=0x%x", get2(data));
	}

	if (flags & F_DTAIL) {
		show_line("FunctionName = QueryFileInfo");
		show_printf("FileID = 0x%.4x", get2(data));
		data += 2;
		show_printf("InfoLevel = 0x%.4x", get2(data));
	}
}

/*
 * Interpret TRANS2_SET_FILE subcommand
 * [X/Open-SMB, Sec. 16.10]
 */
/* ARGSUSED */
static void
output_trans2_setfile(int flags, uchar_t *data, char *xtra, int xsz)
{
	int length;

	if (flags & F_SUM) {
		length = snprintf(xtra, xsz, " SetFileInfo");
		xtra += length;
		xsz -= length;
		snprintf(xtra, xsz, " FileID=0x%x", get2(data));
	}

	if (flags & F_DTAIL) {
		show_line("FunctionName = SetFileInfo");
		show_printf("FileID = 0x%.4x", get2(data));
		data += 2;
		show_printf("InfoLevel = 0x%.4x", get2(data));
	}
}

/*
 * Interpret TRANS2_FIND_FIRST subcommand
 * [X/Open-SMB, Sec. 16.3]
 */
/* ARGSUSED */
static void
output_trans2_findfirst(int flags, uchar_t *data, char *xtra, int xsz)
{
	int length;
	char filename[256];
	char infolevel[100];

	if (flags & F_SUM) {
		length = snprintf(xtra, xsz, " Findfirst");
		xtra += length;
		xsz -= length;
		data += 12;
		(void) unicode2ascii(filename, 256, data, 512);
		snprintf(xtra, xsz, " File=%s", filename);
	}

	if (flags & F_DTAIL) {
		show_line("FunctionName = Findfirst");
		show_printf("SearchAttributes = 0x%.4x", get2(data));
		data += 2;
		show_printf("FindCount = 0x%.4x", get2(data));
		data += 2;
		show_printf("FindFlags = 0x%.4x", get2(data));
		data += 2;
		get_info_level(infolevel, sizeof (infolevel), get2(data));
		show_printf("InfoLevel = %s", infolevel);
		data += 6;
		(void) unicode2ascii(filename, 256, data, 512);
		show_printf("FileName = %s", filename);
	}
}


/*
 * Interpret TRANS2_FIND_NEXT subcommand
 * [X/Open-SMB, Sec. 16.4]
 */
/* ARGSUSED */
static void
output_trans2_findnext(int flags, uchar_t *data, char *xtra, int xsz)
{
	int length;
	char filename[256];
	char infolevel[100];

	if (flags & F_SUM) {
		length = snprintf(xtra, xsz, " Findnext");
		xtra += length;
		xsz -= length;
		data += 12;
		(void) unicode2ascii(filename, 256, data, 512);
		snprintf(xtra, xsz, " File=%s", filename);
	}

	if (flags & F_DTAIL) {
		show_line("FunctionName = Findnext");
		show_printf("FileID = 0x%.4x", get2(data));
		data += 2;
		show_printf("FindCount = 0x%.4x", get2(data));
		data += 2;
		get_info_level(infolevel, sizeof (infolevel), get2(data));
		show_printf("InfoLevel = %s", infolevel);
		data += 2;
		show_printf("FindKey = 0x%.8x", get4(data));
		data += 4;
		show_printf("FindFlags = 0x%.4x", get2(data));
		data += 2;
		(void) unicode2ascii(filename, 256, data, 512);
		show_printf("FileName = %s", filename);
	}
}

/*
 * Interpret a "Negprot" SMB
 * [X/Open-SMB, Sec. 6.1]
 */
/* ARGSUSED */
static void
interpret_negprot(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	int i, last, length;
	int bytecount;
	int key_len;
	int wordcount;
	char tbuf[256];
	struct smb *smbdata;
	uchar_t *protodata;
	uchar_t *byte0;
	uint64_t nttime;
	uint32_t caps;
	ushort_t smb_flags2;

	smbdata  = (struct smb *)data;
	smb_flags2 = get2(smbdata->flags2);
	protodata = (uchar_t *)data + sizeof (struct smb);
	wordcount = *protodata++;

	if ((smbdata->flags & SERVER_RESPONSE) == 0) {
		/*
		 * request packet:
		 * short bytecount;
		 * struct { char fmt; char name[]; } dialects
		 */
		bytecount = get2(protodata);
		protodata += 2;
		byte0 = protodata;

		if (flags & F_DTAIL)
			show_printf("ByteCount = %d", bytecount);
		if (bytecount > len)
			bytecount = len;

		/* Walk the list of dialects. */
		i = last = 0;
		tbuf[0] = '\0';
		while (protodata < (byte0 + bytecount - 2)) {
			if (*protodata++ != 2)	/* format code */
				break;
			length = strlcpy(tbuf, (char *)protodata,
			    sizeof (tbuf));
			protodata += (length + 1);
			if (flags & F_DTAIL) {
				show_printf("Dialect[%d] = %s",
				    i, tbuf);
			}
			last = i++;
		}
		if (flags & F_SUM) {
			/*
			 * Just print the last dialect, which is
			 * normally the interesting one.
			 */
			snprintf(xtra, xsz, " Dialect[%d]=%s", last, tbuf);
		}
	} else {
		/* Parse reply */
		if (flags & F_SUM) {
			snprintf(xtra, xsz, " Dialect#=%d", protodata[0]);
		}
		if ((flags & F_DTAIL) == 0)
			return;
		if (wordcount < 13)
			return;
		show_printf("WordCount = %d", wordcount);
		show_printf("Dialect Index = %d", protodata[0]);
		protodata += 2;
		show_printf("Security Mode = 0x%x", protodata[0]);
		protodata++;
		show_printf("MaxMPXRequests = %d", get2(protodata));
		protodata += 2;
		show_printf("MaxVCs = %d", get2(protodata));
		protodata += 2;
		show_printf("MaxBufferSize = %d", get4(protodata));
		protodata += 4;
		show_printf("MaxRawBuffer = %d", get4(protodata));
		protodata += 4;
		show_printf("SessionKey = 0x%.8x", get4(protodata));
		protodata += 4;

		caps = get4(protodata);
		protodata += 4;
		show_printf("Capabilities = 0x%.8x", caps);

		/* Server Time */
		nttime = get8(protodata);
		protodata += 8;
		show_printf("Server Time = %s", format_nttime(nttime));

		show_printf("Server TZ = %d", get2(protodata));
		protodata += 2;

		key_len = *protodata++;
		show_printf("KeyLength = %d", key_len);
		bytecount = get2(protodata);
		protodata += 2;
		show_printf("ByteCount = %d", bytecount);

		if (smb_flags2 & FLAGS2_EXT_SEC) {
			show_printf("Server GUID (16)");
			output_bytes(protodata, 16);
			protodata += 16;
			show_printf("Security Blob (SPNEGO)");
			output_bytes(protodata, bytecount - 16);
		} else {
			show_printf("NTLM Challenge: (%d)", key_len);
			output_bytes(protodata, key_len);
			protodata += key_len;
			/*
			 * Get Unicode from capabilities here,
			 * as flags2 typically doesn't have it.
			 * Also, this one is NOT aligned!
			 */
			tbuf[0] = '\0';
			if (caps & 4) {
				(void) unicode2ascii(tbuf, sizeof (tbuf),
				    protodata, 2 * sizeof (tbuf));
			} else {
				(void) strlcpy(tbuf, (char *)protodata,
				    sizeof (tbuf));
			}
			show_printf("Server Domain = %s", tbuf);
		}
	}
}

/*
 * LAN Manager remote admin function names.
 * [X/Open-SMB, Appendix B.8]
 */
static const char *apiname_table[] = {
	"RNetShareEnum",
	"RNetShareGetInfo",
	"NetShareSetInfo",
	"NetShareAdd",
	"NetShareDel",
	"NetShareCheck",
	"NetSessionEnum",
	"NetSessionGetInfo",
	"NetSessionDel",
	"NetConnectionEnum",
	"NetFileEnum",
	"NetFileGetInfo",
	"NetFileClose",
	"RNetServerGetInfo",
	"NetServerSetInfo",
	"NetServerDiskEnum",
	"NetServerAdminCommand",
	"NetAuditOpen",
	"NetAuditClear",
	"NetErrorLogOpen",
	"NetErrorLogClear",
	"NetCharDevEnum",
	"NetCharDevGetInfo",
	"NetCharDevControl",
	"NetCharDevQEnum",
	"NetCharDevQGetInfo",
	"NetCharDevQSetInfo",
	"NetCharDevQPurge",
	"RNetCharDevQPurgeSelf",
	"NetMessageNameEnum",
	"NetMessageNameGetInfo",
	"NetMessageNameAdd",
	"NetMessageNameDel",
	"NetMessageNameFwd",
	"NetMessageNameUnFwd",
	"NetMessageBufferSend",
	"NetMessageFileSend",
	"NetMessageLogFileSet",
	"NetMessageLogFileGet",
	"NetServiceEnum",
	"RNetServiceInstall",
	"RNetServiceControl",
	"RNetAccessEnum",
	"RNetAccessGetInfo",
	"RNetAccessSetInfo",
	"RNetAccessAdd",
	"RNetAccessDel",
	"NetGroupEnum",
	"NetGroupAdd",
	"NetGroupDel",
	"NetGroupAddUser",
	"NetGroupDelUser",
	"NetGroupGetUsers",
	"NetUserEnum",
	"RNetUserAdd",
	"NetUserDel",
	"NetUserGetInfo",
	"RNetUserSetInfo",
	"RNetUserPasswordSet",
	"NetUserGetGroups",
	"NetWkstaLogon",
	"NetWkstaLogoff",
	"NetWkstaSetUID",
	"NetWkstaGetInfo",
	"NetWkstaSetInfo",
	"NetUseEnum",
	"NetUseAdd",
	"NetUseDel",
	"NetUseGetInfo",
	"DosPrintQEnum",
	"DosPrintQGetInfo",
	"DosPrintQSetInfo",
	"DosPrintQAdd",
	"DosPrintQDel",
	"DosPrintQPause",
	"DosPrintQContinue",
	"DosPrintJobEnum",
	"DosPrintJobGetInfo",
	"RDosPrintJobSetInfo",
	"DosPrintJobAdd",
	"DosPrintJobSchedule",
	"RDosPrintJobDel",
	"RDosPrintJobPause",
	"RDosPrintJobContinue",
	"DosPrintDestEnum",
	"DosPrintDestGetInfo",
	"DosPrintDestControl",
	"NetProfileSave",
	"NetProfileLoad",
	"NetStatisticsGet",
	"NetStatisticsClear",
	"NetRemoteTOD",
	"NetBiosEnum",
	"NetBiosGetInfo",
	"NetServerEnum",
	"I_NetServerEnum",
	"NetServiceGetInfo",
	"NetSplQmAbort",
	"NetSplQmClose",
	"NetSplQmEndDoc",
	"NetSplQmOpen",
	"NetSplQmStartDoc",
	"NetSplQmWrite",
	"DosPrintQPurge",
	"NetServerEnum2"
};
static const int apinum_max = (
	sizeof (apiname_table) /
	sizeof (apiname_table[0]));

static const char *
pipeapi_name(int code)
{
	char *name;

	switch (code) {
	case 0x01:
		name = "SetNmPipeState";
		break;
	case 0x11:
		name = "RawReadNmPipe";
		break;
	case 0x21:
		name = "QueryNmPipeState";
		break;
	case 0x22:
		name = "QueryNmPipeInfo";
		break;
	case 0x23:
		name = "PeekNmPipe";
		break;
	case 0x26:
		name = "XactNmPipe";
		break;
	case 0x31:
		name = "RawWriteNmPipe";
		break;
	case 0x36:
		name = "ReadNmPipe";
		break;
	case 0x37:
		name = "WriteNmPipe";
		break;
	case 0x53:
		name = "WaitNmPipe";
		break;
	case 0x54:
		name = "CallNmPipe";
		break;
	default:
		name = "?";
		break;
	}
	return (name);
}

/*
 * Interpret a "trans" SMB
 * [X/Open-SMB, Appendix B]
 *
 * This is very much like "trans2" below.
 */
/* ARGSUSED */
static void
interpret_trans(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	struct smb *smb;
	uchar_t *vwv; /* word parameters */
	int wordcount;
	uchar_t *byteparms;
	int bytecount;
	int parambytes;
	int paramoffset;
	int setupcount;
	int subcode;
	uchar_t *setupdata;
	uchar_t *params;
	int apinum;
	int isunicode;
	char filename[256];
	const char *apiname;
	const char *subcname;
	ushort_t smb_flags2;

	smb = (struct smb *)data;
	smb_flags2 = get2(smb->flags2);
	vwv = (uchar_t *)data + sizeof (struct smb);
	wordcount = *vwv++;

	/* Is the pathname in unicode? */
	isunicode = smb_flags2 & FLAGS2_UNICODE;

	byteparms = vwv + (2 * wordcount);
	bytecount = get2(byteparms);
	byteparms += 2;

	/*
	 * Print the lengths before we (potentially) bail out
	 * due to lack of data (so the user knows why we did).
	 */
	if (flags & F_DTAIL)
		show_printf("WordCount = %d", wordcount);

	/* Get length and location of params and setup data. */
	if (!(smb->flags & SERVER_RESPONSE)) {
		/* CALL */
		if (wordcount < 14)
			return;
		parambytes  = get2(vwv + (2 *  9));
		paramoffset = get2(vwv + (2 * 10));
		setupcount = *(vwv + (2 * 13));
		setupdata  =   vwv + (2 * 14);
	} else {
		/* REPLY */
		if (wordcount < 10)
			return;
		parambytes  = get2(vwv + (2 * 3));
		paramoffset = get2(vwv + (2 * 4));
		setupcount = *(vwv + (2 *  9));
		setupdata  =   vwv + (2 * 10);
	}

	/* The parameters are offset from the SMB header. */
	params = data + paramoffset;

	if ((smb->flags & SERVER_RESPONSE) == 0) {
		/* This is a CALL. */

		if (setupcount > 0)
			subcode = get2(setupdata);
		else
			subcode = -1; /* invalid */
		subcname = pipeapi_name(subcode);

		if (parambytes > 0)
			apinum = params[0];
		else
			apinum = -1; /* invalid */
		if (0 <= apinum && apinum < apinum_max)
			apiname = apiname_table[apinum];
		else
			apiname = "?";

		if (flags & F_SUM) {
			int tl;
			/* Only get one or the other */
			if (*subcname != '?') {
				tl = snprintf(xtra, xsz,
				    " Func=%s", subcname);
				xtra += tl;
				xsz -= tl;
			}
			if (*apiname != '?')
				snprintf(xtra, xsz,
				    " Func=%s", apiname);
			return;
		}
		if ((flags & F_DTAIL) == 0)
			return;

		/* print the word parameters */
		show_printf("TotalParamBytes = %d", get2(vwv));
		show_printf("TotalDataBytes = %d", get2(vwv+2));
		show_printf("MaxParamBytes = %d", get2(vwv+4));
		show_printf("MaxDataBytes = %d", get2(vwv+6));
		show_printf("MaxSetupWords = %d", vwv[8]);
		show_printf("TransFlags = 0x%.4x", get2(vwv+10));
		show_printf("Timeout = 0x%.8x", get4(vwv+12));
		/* skip Reserved2 */
		show_printf("ParamBytes = %d", parambytes);
		show_printf("ParamOffset = %d", paramoffset);
		show_printf("DataBytes = %d", get2(vwv+22));
		show_printf("DataOffset = %d", get2(vwv+24));
		show_printf("SetupWords = %d", setupcount);
		show_printf("ByteCount = %d", bytecount);

		/* That finishes the VWV, now the misc. stuff. */
		if (setupcount > 0)
			show_printf("NmPipeFunc = 0x%x (%s)",
			    subcode, subcname);
		if (parambytes > 0)
			show_printf("RAP_Func = %d (%s)",
			    apinum, apiname);

		/* Finally, print the byte parameters. */
		GET_STRING(filename, byteparms, isunicode);
		show_printf("FileName = %s", filename);
	} else {
		/* This is a REPLY. */
		if (flags & F_SUM)
			return;
		if ((flags & F_DTAIL) == 0)
			return;
		/* print the word parameters */
		show_printf("TotalParamBytes = %d", get2(vwv));
		show_printf("TotalDataBytes = %d", get2(vwv+2));
		/* skip Reserved */
		show_printf("ParamBytes = 0x%.4x", parambytes);
		show_printf("ParamOffset = 0x%.4x", paramoffset);
		show_printf("ParamDispl. = 0x%.4x", get2(vwv+10));
		show_printf("DataBytes = 0x%.4x", get2(vwv+12));
		show_printf("DataOffset = 0x%.4x", get2(vwv+14));
		show_printf("DataDispl. = 0x%.4x", get2(vwv+16));
		show_printf("SetupWords = %d", setupcount);
		show_printf("ByteCount = %d", bytecount);

		show_printf("ParamVec (%d)", parambytes);
		output_bytes(params, parambytes);
	}
}

/*
 * Interpret a "TconX" SMB
 * [X/Open-SMB, Sec. 11.4]
 */
/* ARGSUSED */
static void
interpret_tconX(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	int length;
	int isunicode;
	int bytecount;
	int wordcount;
	int andxcmd;
	int andxoffset;
	int tconflags;
	int pw_len;
	char path[256];
	char tbuf[256];
	char svc[8];
	struct smb *smbdata;
	uchar_t *tcondata;
	ushort_t smb_flags2;

	smbdata = (struct smb *)data;
	smb_flags2 = get2(smbdata->flags2);
	tcondata = (uchar_t *)data + sizeof (struct smb);
	wordcount = *tcondata++;

	isunicode = smb_flags2 & FLAGS2_UNICODE;

	if ((smbdata->flags & SERVER_RESPONSE) == 0) {
		/* Request */
		if (wordcount < 4)
			return;
		andxcmd = get2(tcondata);
		tcondata += 2;
		andxoffset = get2(tcondata);
		tcondata += 2;
		tconflags = get2(tcondata);
		tcondata += 2;
		pw_len = get2(tcondata);
		tcondata += 2;
		bytecount = get2(tcondata);
		tcondata += 2;

		/* skip password */
		if (pw_len > len)
			pw_len = len;
		tcondata += pw_len;

		GET_STRING(path, tcondata, isunicode);
		(void) strlcpy(svc, (char *)tcondata, sizeof (svc));

		if (flags & F_SUM) {
			snprintf(xtra, xsz, " Share=%s", path);
			return;
		}

		if ((flags & F_DTAIL) == 0)
			return;

		show_printf("WordCount = %d", wordcount);
		show_printf("ChainedCommand = 0x%.2x", andxcmd);
		show_printf("NextOffset = 0x%.4x", andxoffset);
		show_printf("TconFlags = 0x%.4x", tconflags);
		show_printf("PasswordLength = 0x%.4x", pw_len);
		show_printf("ByteCount = %d", bytecount);
		show_printf("SharePath = %s", path);
		show_printf("ServiceType = %s", svc);
	} else {
		/* response */
		if (wordcount < 3)
			return;
		andxcmd = get2(tcondata);
		tcondata += 2;
		andxoffset = get2(tcondata);
		tcondata += 2;
		tconflags = get2(tcondata);
		tcondata += 2;
		bytecount = get2(tcondata);
		tcondata += 2;

		length = strlcpy(svc, (char *)tcondata, sizeof (svc));
		tcondata += (length + 1);

		if (flags & F_SUM) {
			snprintf(xtra, xsz, " Type=%s", svc);
			return;
		}
		if ((flags & F_DTAIL) == 0)
			return;

		show_printf("WordCount = %d", wordcount);
		show_printf("ChainedCommand = 0x%.2x", andxcmd);
		show_printf("NextOffset = 0x%.4x", andxoffset);
		show_printf("OptionalSupport = 0x%.4x", tconflags);
		show_printf("ByteCount = %d", bytecount);
		show_printf("ServiceType = %s", svc);
		GET_STRING(tbuf, tcondata, isunicode);
		show_printf("NativeFS = %s", tbuf);
	}
}

/*
 * Interpret a "SesssetupX" SMB
 * [X/Open-SMB, Sec. 11.3]
 */
/* ARGSUSED */
static void
interpret_sesssetupX(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	int bytecount;
	int lm_pw_len;
	int ext_security;
	int sec_blob_len;
	int isunicode;
	int nt_pw_len;
	int wordcount;
	int cap;
	char tbuf[256];
	struct smb *smbdata;
	uchar_t *setupdata;
	ushort_t smb_flags2;

	smbdata  = (struct smb *)data;
	smb_flags2 = get2(smbdata->flags2);
	setupdata = (uchar_t *)data + sizeof (struct smb);
	wordcount = *setupdata++;

	isunicode = smb_flags2 & FLAGS2_UNICODE;
	ext_security = smb_flags2 & FLAGS2_EXT_SEC;

	if ((smbdata->flags & SERVER_RESPONSE) == 0) {
		/* request summary */
		if (flags & F_SUM) {
			if (ext_security) {
				/* No decoder for SPNEGO */
				snprintf(xtra, xsz, " (SPNEGO)");
				return;
			}
			if (wordcount != 13)
				return;
			setupdata += 14;
			lm_pw_len = get2(setupdata);
			setupdata += 2;
			nt_pw_len = get2(setupdata);
			setupdata += 6;
			cap = get4(setupdata);
			setupdata += 6 + lm_pw_len + nt_pw_len;

			GET_STRING(tbuf, setupdata, isunicode);
			snprintf(xtra, xsz, " Username=%s", tbuf);
		}

		if ((flags & F_DTAIL) == 0)
			return;

		/* request detail */
		show_printf("WordCount = %d", wordcount);
		if (wordcount < 7)
			return;
		/* words 0 - 6 */
		show_printf("ChainedCommand = 0x%.2x", setupdata[0]);
		setupdata += 2;
		show_printf("NextOffset = 0x%.4x", get2(setupdata));
		setupdata += 2;
		show_printf("MaxBufferSize = %d", get2(setupdata));
		setupdata += 2;
		show_printf("MaxMPXRequests = %d", get2(setupdata));
		setupdata += 2;
		show_printf("VCNumber = %d", get2(setupdata));
		setupdata += 2;
		show_printf("SessionKey = 0x%.8x", get4(setupdata));
		setupdata += 4;

		if (ext_security) {
			if (wordcount != 12)
				return;
			/* word 7 */
			sec_blob_len = get2(setupdata);
			setupdata += 2;
			show_printf("Sec. blob len = %d", sec_blob_len);
			/* words 8, 9 (reserved) */
			setupdata += 4;
		} else {
			if (wordcount != 13)
				return;
			/* word 7 */
			lm_pw_len = get2(setupdata);
			setupdata += 2;
			show_printf("LM_Hash_Len = %d", lm_pw_len);
			/* word 8 */
			nt_pw_len = get2(setupdata);
			setupdata += 2;
			show_printf("NT_Hash_Len = %d", nt_pw_len);
			/* words 9, 10 (reserved) */
			setupdata += 4;
		}

		cap = get4(setupdata);
		show_printf("Capabilities = 0x%.8x", cap);
		setupdata += 4;

		bytecount = get2(setupdata);
		setupdata += 2;
		show_printf("ByteCount = %d", bytecount);

		if (ext_security) {
			/* No decoder for SPNEGO.  Just dump hex. */
			show_printf("Security blob: (SPNEGO)");
			output_bytes(setupdata, sec_blob_len);
			setupdata += sec_blob_len;
		} else {
			/* Dump password hashes */
			if (lm_pw_len > 0) {
				show_printf("LM Hash (%d bytes)", lm_pw_len);
				output_bytes(setupdata, lm_pw_len);
				setupdata += lm_pw_len;
			}
			if (nt_pw_len > 0) {
				show_printf("NT Hash (%d bytes)", nt_pw_len);
				output_bytes(setupdata, nt_pw_len);
				setupdata += nt_pw_len;
			}

			/* User */
			GET_STRING(tbuf, setupdata, isunicode);
			show_printf("AccountName = %s", tbuf);

			/* Domain */
			GET_STRING(tbuf, setupdata, isunicode);
			show_printf("DomainName = %s", tbuf);
		}

		/*
		 * Remainder is the same for etc. sec. or not
		 * Native OS, Native LanMan
		 */
		GET_STRING(tbuf, setupdata, isunicode);
		show_printf("NativeOS = %s", tbuf);

		GET_STRING(tbuf, setupdata, isunicode);
		show_printf("NativeLanman = %s", tbuf);
	} else {
		/* response summary */
		if (flags & F_SUM) {
			if (ext_security) {
				/* No decoder for SPNEGO */
				snprintf(xtra, xsz, " (SPNEGO)");
			}
			return;
		}

		if ((flags & F_DTAIL) == 0)
			return;

		/* response detail */
		show_printf("WordCount = %d", wordcount);
		if (wordcount < 3)
			return;

		show_printf("ChainedCommand = 0x%.2x", setupdata[0]);
		setupdata += 2;
		show_printf("NextOffset = 0x%.4x", get2(setupdata));
		setupdata += 2;
		show_printf("SetupAction = 0x%.4x", get2(setupdata));
		setupdata += 2;

		if (ext_security) {
			if (wordcount != 4)
				return;
			sec_blob_len = get2(setupdata);
			setupdata += 2;
			show_printf("Sec. blob len = %d", sec_blob_len);
		} else {
			if (wordcount != 3)
				return;
		}

		bytecount = get2(setupdata);
		setupdata += 2;
		show_printf("ByteCount = %d", bytecount);

		if (ext_security) {
			/* No decoder for SPNEGO.  Just dump hex. */
			show_line("Security blob: (SPNEGO)");
			output_bytes(setupdata, sec_blob_len);
			setupdata += sec_blob_len;
		}

		/*
		 * Native OS, Native LanMan
		 */
		GET_STRING(tbuf, setupdata, isunicode);
		show_printf("NativeOS = %s", tbuf);

		GET_STRING(tbuf, setupdata, isunicode);
		show_printf("NativeLanman = %s", tbuf);

		if (ext_security == 0) {
			GET_STRING(tbuf, setupdata, isunicode);
			show_printf("DomainName = %s", tbuf);
		}
	}
}

/*
 * Interpret "Trans2" SMB
 * [X/Open-SMB, Sec. 16]
 *
 * This is very much like "trans" above.
 */
/* ARGSUSED */
static void
interpret_trans2(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	struct smb *smb;
	uchar_t *vwv; /* word parameters */
	int wordcount;
	uchar_t *byteparms;
	int bytecount;
	int parambytes;
	int paramoffset;
	int setupcount;
	int subcode;
	uchar_t *setupdata;
	uchar_t *params;
	char *name;

	smb  = (struct smb *)data;
	vwv = (uchar_t *)data + sizeof (struct smb);
	wordcount = *vwv++;

	byteparms = vwv + (2 * wordcount);
	bytecount = get2(byteparms);
	byteparms += 2;

	/*
	 * Print the lengths before we (potentially) bail out
	 * due to lack of data (so the user knows why we did).
	 */
	if (flags & F_DTAIL) {
		show_printf("WordCount = %d", wordcount);
		show_printf("ByteCount = %d", bytecount);
	}

	/* Get length and location of params and setup data. */
	if (!(smb->flags & SERVER_RESPONSE)) {
		/* CALL */
		if (wordcount < 14)
			return;
		parambytes  = get2(vwv + (2 *  9));
		paramoffset = get2(vwv + (2 * 10));
		setupcount = *(vwv + (2 * 13));
		setupdata  =   vwv + (2 * 14);
	} else {
		/* REPLY */
		if (wordcount < 10)
			return;
		parambytes  = get2(vwv + (2 * 3));
		paramoffset = get2(vwv + (2 * 4));
		setupcount = *(vwv + (2 *  9));
		setupdata  =   vwv + (2 * 10);
	}
	if (setupcount > 0)
		subcode = get2(setupdata);
	else
		subcode = -1; /* invalid */

	/* The parameters are offset from the SMB header. */
	params = data + paramoffset;

	if (flags & F_DTAIL && !(smb->flags & SERVER_RESPONSE)) {
		/* This is a CALL. */
		/* print the word parameters */
		show_printf("TotalParamBytes = %d", get2(vwv));
		show_printf("TotalDataBytes = %d", get2(vwv+2));
		show_printf("MaxParamBytes = %d", get2(vwv+4));
		show_printf("MaxDataBytes = %d", get2(vwv+6));
		show_printf("MaxSetupWords = %d", vwv[8]);
		show_printf("TransFlags = 0x%.4x", get2(vwv+10));
		show_printf("Timeout = 0x%.8x", get4(vwv+12));
		/* skip Reserved2 */
		show_printf("ParamBytes = 0x%.4x", parambytes);
		show_printf("ParamOffset = 0x%.4x", paramoffset);
		show_printf("DataBytes = 0x%.4x", get2(vwv+22));
		show_printf("DataOffset = 0x%.4x", get2(vwv+24));
		show_printf("SetupWords = %d", setupcount);

		/* That finishes the VWV, now the misc. stuff. */
		show_printf("FunctionCode = %d", subcode);
	}

	if (!(smb->flags & SERVER_RESPONSE)) {
		/* This is a CALL.  Do sub-function. */
		switch (subcode) {
		case TRANS2_OPEN:
			name = "Open";
			goto name_only;
		case TRANS2_FIND_FIRST:
			output_trans2_findfirst(flags, params, xtra, xsz);
			break;
		case TRANS2_FIND_NEXT2:
			output_trans2_findnext(flags, params, xtra, xsz);
			break;
		case TRANS2_QUERY_FS_INFORMATION:
			name = "QueryFSInfo";
			goto name_only;
		case TRANS2_QUERY_PATH_INFORMATION:
			output_trans2_querypath(flags, params, xtra, xsz);
			break;
		case TRANS2_SET_PATH_INFORMATION:
			name = "SetPathInfo";
			goto name_only;
		case TRANS2_QUERY_FILE_INFORMATION:
			output_trans2_queryfile(flags, params, xtra, xsz);
			break;
		case TRANS2_SET_FILE_INFORMATION:
			output_trans2_setfile(flags, params, xtra, xsz);
			break;
		case TRANS2_CREATE_DIRECTORY:
			name = "CreateDir";
			goto name_only;

		default:
			name = "Unknown";
			/* fall through */
		name_only:
			if (flags & F_SUM)
				snprintf(xtra, xsz, " %s", name);
			if (flags & F_DTAIL)
				show_printf("FunctionName = %s", name);
			break;
		}
	}

	if (flags & F_DTAIL && smb->flags & SERVER_RESPONSE) {
		/* This is a REPLY. */
		/* print the word parameters */
		show_printf("TotalParamBytes = %d", get2(vwv));
		show_printf("TotalDataBytes = %d",  get2(vwv+2));
		/* skip Reserved */
		show_printf("ParamBytes = 0x%.4x", parambytes);
		show_printf("ParamOffset = 0x%.4x", paramoffset);
		show_printf("ParamDispl. = 0x%.4x", get2(vwv+10));
		show_printf("DataBytes = 0x%.4x", get2(vwv+12));
		show_printf("DataOffset = 0x%.4x", get2(vwv+14));
		show_printf("DataDispl. = 0x%.4x", get2(vwv+16));
		show_printf("SetupWords = %d", setupcount);

		output_bytes(byteparms, bytecount);
	}
}


static void
interpret_default(int flags, uchar_t *data, int len, char *xtra, int xsz)
{
	int slength;
	int i, tl;
	int isunicode;
	int printit;
	int wordcount;
	int outsz;
	char *outstr;
	char *format;
	char valuetype;
	char word[10];
	char *label;
	char tempstr[256];
	uchar_t *comdata, *limit;
	char buff[80];
	struct smb *smbdata;
	struct decode *decoder;
	uchar_t bval;
	ushort_t wval;
	ushort_t smb_flags2;
	uint_t lval;

	smbdata  = (struct smb *)data;
	smb_flags2 = get2(smbdata->flags2);
	comdata = (uchar_t *)data + sizeof (struct smb);
	wordcount = *comdata++;
	limit = data + len;

	isunicode = smb_flags2 & FLAGS2_UNICODE;
	decoder = &SMBtable[smbdata->com & 255];

	if (smbdata->flags & SERVER_RESPONSE)
		format = decoder->replyfmt;
	else
		format = decoder->callfmt;

	if (!format || strlen(format) == 0) {
		if (flags & F_SUM)
			return;
		show_printf("WordCount = %d", wordcount);
		if (wordcount == 0)
			return;
		show_line("Word values (in hex):");
		buff[0] = '\0';
		for (i = 0; i < wordcount; i++) {
			snprintf(word, sizeof (word), "%.4x ", get2(comdata));
			comdata += 2;
			if (comdata >= limit)
				wordcount = i+1; /* terminate */
			(void) strlcat(buff, word, sizeof (buff));
			if (((i+1) & 7) == 0 || i == (wordcount-1)) {
				show_line(buff);
				strcpy(buff, "");
			}
		}
		return;
	}

	if (flags & F_DTAIL)
		show_printf("WordCount = %d", wordcount);

	outstr = xtra;
	outsz = xsz;

	valuetype = format[0];
	while (valuetype != '\0') {
		if (comdata >= limit)
			break;
		label = format+1;
		printit = (flags & F_DTAIL) || (valuetype <= 'Z');

		switch (valuetype) {
		case 'W':
		case 'w':
			wval = get2(comdata);
			comdata += 2;
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = 0x%.4x", label, wval);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=0x%x", label, wval);
				outstr += tl;
				outsz -= tl;
			}
			break;

		case 'D':
		case 'd':
			wval = get2(comdata);
			comdata += 2;
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = %d", label, wval);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=%d", label, wval);
				outstr += tl;
				outsz -= tl;
			}
			break;

		case 'L':
		case 'l':
			lval = get4(comdata);
			comdata += 4;
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = 0x%.8x", label, lval);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=0x%x", label, lval);
				outstr += tl;
				outsz -= tl;
			}
			break;

		case 'B':
		case 'b':
			bval = comdata[0];
			comdata += 1;
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = 0x%.2x", label, bval);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=0x%x", label, bval);
				outstr += tl;
				outsz -= tl;
			}
			break;

		case 'r':
			comdata++;
			break;

		case 'R':
			comdata += 2;
			break;

		case 'U':
		case 'u':
			/* Unicode or ASCII string. */
			GET_STRING(tempstr, comdata, isunicode);
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = %s", label, tempstr);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=%s", label, tempstr);
				outstr += tl;
				outsz -= tl;
			}
			break;

		case 'S':
		case 's':
			slength = strlcpy(tempstr, (char *)comdata,
			    sizeof (tempstr));
			comdata += (slength+1);
			if (!printit)
				break;
			if (flags & F_DTAIL)
				show_printf(
				    "%s = %s", label, tempstr);
			else {
				tl = snprintf(outstr, outsz,
				    " %s=%s", label, tempstr);
				outstr += tl;
				outsz -= tl;
			}
			break;
		}
		format += (strlen(format) + 1);
		valuetype = format[0];
	}
}