root/usr/src/cmd/cmd-inet/usr.sbin/snoop/snoop_smb.c
/*
 * 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];
        }
}