/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (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 (c) 1999-2000 by Sun Microsystems, Inc. * All rights reserved. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * 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 #include #include #include #include "snoop.h" /* some macros just for compactness */ #define GETLINE get_line(0, 0) #define DECARGS int flags, uchar_t *data, int len, char *extrainfo /* * 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 rcls; /* error class */ uchar_t res; uchar_t err[2]; /* error 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 static void interpret_sesssetupX(DECARGS); static void interpret_tconX(DECARGS); static void interpret_trans(DECARGS); static void interpret_trans2(DECARGS); static void interpret_negprot(DECARGS); static void interpret_default(DECARGS); /* * 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)(DECARGS); 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\0lLastModTime\0wByteCount\0\0", "wByteCount\0\0" }, { "flush", 0, 0, 0 }, { "unlink", 0, 0, 0 }, { "mv", 0, /* [X/Open-SMB, Sec. 7.11] */ "wFileAttributes\0wByteCount\0" "r\0UFileName\0r\0UNewPath\0\0", "wByteCount\0\0" }, { "getatr", 0, /* [X/Open-SMB, Sec. 8.4] */ "dBytecount\0r\0UFileName\0\0", "wFileAttributes\0lTime\0lSize\0R\0R\0R\0" "R\0R\0wByteCount\0\0" }, { "setatr", 0, 0, 0 }, { "read", 0, /* [X/Open-SMB, Sec. 7.4] */ "WFileID\0wI/0 Bytes\0LFileOffset\0" "WBytesLeft\0wByteCount\0\0", "WDataLength\0R\0R\0R\0R\0wByteCount\0\0" }, { "write", 0, /* [X/Open-SMB, Sec. 7.5] */ "WFileID\0wI/0 Bytes\0LFileOffset\0WBytesLeft\0" "wByteCount\0\0", "WDataLength\0wByteCount\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] */ "wByteCount\0r\0UFile\0\0", "wByteCount\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\0LFileOffset\0wMaxCount\0" "wMinCount\0lTimeout\0R\0wByteCount\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\0wNextOffset\0WFileID\0" "wLockType\0lOpenTimeout\0" "W#Unlocks\0W#Locks\0wByteCount\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 }, { "openX", 0, /* [X/Open-SMB, Sec. 12.1] */ "wChainedCommand\0wNextOffset\0wFlags\0" "wMode\0wSearchAttributes\0wFileAttributes\0" "lTime\0wOpenFunction\0lFileSize\0lOpenTimeout\0" "R\0R\0wByteCount\0r\0UFileName\0\0", "wChainedCommand\0wNextOffset\0WFileID\0" "wAttributes\0lTime\0LSize\0wOpenMode\0" "wFileType\0wDeviceState\0wActionTaken\0" "lUniqueFileID\0R\0wBytecount\0\0" }, { "readX", 0, 0, 0 }, { "writeX", 0, 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\0wByteCount\0\0", "wByteCount\0\0" }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 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] */ "wByteCount\0\0", "wByteCount\0\0" }, { "negprot", interpret_negprot, 0, 0 }, { "sesssetupX", interpret_sesssetupX, 0, 0 }, { "uloggoffX", 0, /* [X/Open-SMB, Sec. 15.5] */ "wChainedCommand\0wNextOffset\0\0", "wChainedCommnad\0wNextOffset\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 }, { " NT_CreateX", 0, /* [CIFS/1.0, Sec. 4.2.1] */ "wChainedCommand\0wNextOffset\0r\0" "wNameLength\0lCreateFlags\0lRootDirFID\0" "lDesiredAccess\0R\0R\0R\0R\0" "lNTFileAttributes\0lFileShareAccess\0" "R\0R\0lCreateOption\0lImpersonationLevel\0" "bSecurityFlags\0wByteCount\0r\0" "UFileName\0\0", "wChainedCommand\0wNextOffset\0" "bOplockLevel\0WFileID\0lCreateAction\0\0" }, { 0, 0, 0, 0 }, { " NT_Cancel", 0, /* [CIFS/1.0, Sec. 4.1.8] */ "wByteCount\0", 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 0, 0, 0, 0 }, { 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 short and int values in Intel order. */ static ushort_t get2(uchar_t *p) { return (p[0] + (p[1]<<8)); } static uint_t get4(uchar_t *p) { return (p[0] + (p[1]<<8) + (p[2]<<16) + (p[3]<<24)); } /* * 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; char *call_reply_detail, *call_reply_sum; struct decode *decoder; char xtra[300]; char *line; smb = (struct smb *)data; decoder = &SMBtable[smb->com & 255]; if (smb->flags & SERVER_RESPONSE) { call_reply_detail = "SERVER RESPONSE"; call_reply_sum = "R"; } else { call_reply_detail = "CLIENT REQUEST"; call_reply_sum = "C"; } xtra[0] = '\0'; /* * SMB Header description * [X/Open-SMB, Sec. 5.1] */ if (flags & F_DTAIL) { show_header("SMB: ", "SMB Header", len); show_space(); sprintf(GETLINE, "%s", call_reply_detail); (void) sprintf(GETLINE, "Command code = 0x%x", smb->com); if (decoder->name) (void) sprintf(GETLINE, "Command name = SMB%s", decoder->name); show_space(); sprintf(GETLINE, "SMB Status:"); /* Error classes [X/Open-SMB, Sec. 5.6] */ switch (smb->rcls) { case 0x00: sprintf(GETLINE, " - Error class = No error"); break; case 0x01: sprintf(GETLINE, " - Error class = Operating System"); break; case 0x02: sprintf(GETLINE, " - Error class = LMX server"); break; case 0x03: sprintf(GETLINE, " - Error class = Hardware"); break; case 0xff: default: sprintf(GETLINE, " - Error class = Incorrect format."); break; } if (smb->err[0] != 0x00) { sprintf(GETLINE, " - Error code = %x", smb->err[0]); } else sprintf(GETLINE, " - Error code = No error"); show_space(); sprintf(GETLINE, "Header:"); sprintf(GETLINE, " - Tree ID (TID) = 0x%.4x", get2(smb->tid)); sprintf(GETLINE, " - Process ID (PID) = 0x%.4x", get2(smb->pid)); sprintf(GETLINE, " - User ID (UID) = 0x%.4x", get2(smb->uid)); sprintf(GETLINE, " - Multiplex ID (MID) = 0x%.4x", get2(smb->mid)); sprintf(GETLINE, " - Flags summary = 0x%.2x", smb->flags); sprintf(GETLINE, " - Flags2 summary = 0x%.4x", get2(smb->flags2)); show_space(); } if (decoder->func) (decoder->func)(flags, (uchar_t *)data, len, xtra); else interpret_default(flags, (uchar_t *)data, len, xtra); if (flags & F_SUM) { line = get_sum_line(); if (decoder->name) sprintf(line, "SMB %s Code=0x%x Name=SMB%s %sError=%x ", call_reply_sum, smb->com, decoder->name, xtra, smb->err[0]); else sprintf(line, "SMB %s Code=0x%x Error=%x ", call_reply_sum, smb->com, smb->err[0]); line += strlen(line); } if (flags & F_DTAIL) show_trailer(); } static void output_bytes(uchar_t *data, int bytecount) { int i; char buff[80]; char word[10]; buff[0] = word[0] = '\0'; sprintf(GETLINE, "Byte values (in hex):"); for (i = 0; i < bytecount; i++) { sprintf(word, "%.2x ", data[i]); strcat(buff, word); if ((i+1)%16 == 0 || i == (bytecount-1)) { sprintf(GETLINE, "%s", buff); strcpy(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); } /* * TRANS2 information levels * [X/Open-SMB, Sec. 16.1.6] */ static void get_info_level(char *outstr, int value) { switch (value) { case 1: sprintf(outstr, "Standard"); break; case 2: sprintf(outstr, "Query EA Size"); break; case 3: sprintf(outstr, "Query EAS from List"); break; case 0x101: sprintf(outstr, "Directory Info"); break; case 0x102: sprintf(outstr, "Full Directory Info"); break; case 0x103: sprintf(outstr, "Names Info"); break; case 0x104: sprintf(outstr, "Both Directory Info"); break; default: sprintf(outstr, "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 length; char filename[256]; if (flags & F_SUM) { length = sprintf(xtra, "QueryPathInfo "); xtra += length; data += 6; (void) unicode2ascii(filename, 256, data, 512); sprintf(xtra, "File=%s ", filename); } if (flags & F_DTAIL) { sprintf(GETLINE, "FunctionName = QueryPathInfo"); sprintf(GETLINE, "InfoLevel = 0x%.4x", get2(data)); data += 6; (void) unicode2ascii(filename, 256, data, 512); sprintf(GETLINE, "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 length; if (flags & F_SUM) { length = sprintf(xtra, "QueryFileInfo "); xtra += length; sprintf(xtra, "FileID=0x%x ", get2(data)); } if (flags & F_DTAIL) { sprintf(GETLINE, "FunctionName = QueryFileInfo"); sprintf(GETLINE, "FileID = 0x%.4x", get2(data)); data += 2; sprintf(GETLINE, "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 length; if (flags & F_SUM) { length = sprintf(xtra, "SetFileInfo "); xtra += length; sprintf(xtra, "FileID=0x%x ", get2(data)); } if (flags & F_DTAIL) { sprintf(GETLINE, "FunctionName = SetFileInfo"); sprintf(GETLINE, "FileID = 0x%.4x", get2(data)); data += 2; sprintf(GETLINE, "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 length; char filename[256]; char infolevel[100]; if (flags & F_SUM) { length = sprintf(xtra, "Findfirst "); xtra += length; data += 12; (void) unicode2ascii(filename, 256, data, 512); sprintf(xtra, "File=%s ", filename); } if (flags & F_DTAIL) { sprintf(GETLINE, "FunctionName = Findfirst"); sprintf(GETLINE, "SearchAttributes = 0x%.4x", get2(data)); data += 2; sprintf(GETLINE, "FindCount = 0x%.4x", get2(data)); data += 2; sprintf(GETLINE, "FindFlags = 0x%.4x", get2(data)); data += 2; get_info_level(infolevel, get2(data)); sprintf(GETLINE, "InfoLevel = %s", infolevel); data += 6; (void) unicode2ascii(filename, 256, data, 512); sprintf(GETLINE, "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 length; char filename[256]; char infolevel[100]; if (flags & F_SUM) { length = sprintf(xtra, "Findnext "); xtra += length; data += 12; (void) unicode2ascii(filename, 256, data, 512); sprintf(xtra, "File=%s ", filename); } if (flags & F_DTAIL) { sprintf(GETLINE, "FunctionName = Findnext"); sprintf(GETLINE, "FileID = 0x%.4x", get2(data)); data += 2; sprintf(GETLINE, "FindCount = 0x%.4x", get2(data)); data += 2; get_info_level(infolevel, get2(data)); sprintf(GETLINE, "InfoLevel = %s", infolevel); data += 2; sprintf(GETLINE, "FindKey = 0x%.8x", get4(data)); data += 4; sprintf(GETLINE, "FindFlags = 0x%.4x", get2(data)); data += 2; (void) unicode2ascii(filename, 256, data, 512); sprintf(GETLINE, "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 length; int bytecount; char dialect[256]; struct smb *smbdata; uchar_t *protodata; smbdata = (struct smb *)data; protodata = (uchar_t *)data + sizeof (struct smb); protodata++; /* skip wordcount */ if (smbdata->flags & SERVER_RESPONSE) { if (flags & F_SUM) { sprintf(xtra, "Dialect#=%d ", protodata[0]); } if (flags & F_DTAIL) { sprintf(GETLINE, "Protocol Index = %d", protodata[0]); } } else { /* * request packet: * short bytecount; * struct { char fmt; char name[]; } dialects */ bytecount = get2(protodata); protodata += 2; if (flags & F_SUM) { while (bytecount > 1) { length = sprintf(dialect, (char *)protodata+1); protodata += (length+2); bytecount -= (length+2); } sprintf(xtra, "LastDialect=%s ", dialect); } if (flags & F_DTAIL) { sprintf(GETLINE, "ByteCount = %d", bytecount); while (bytecount > 1) { length = sprintf(dialect, (char *)protodata+1); sprintf(GETLINE, "Dialect String = %s", dialect); protodata += (length+2); bytecount -= (length+2); } } } } /* * LAN Manager remote admin function names. * [X/Open-SMB, Appendix B.8] */ static const char *apinames[] = { "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 apimax = ( sizeof (apinames) / sizeof (apinames[0])); /* * 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) { 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]; 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) { sprintf(GETLINE, "WordCount = %d", wordcount); sprintf(GETLINE, "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 (parambytes > 0) apinum = params[0]; else apinum = -1; /* invalid */ /* Is the pathname in unicode? */ isunicode = smb->flags2[1] & 0x80; if (flags & F_DTAIL && !(smb->flags & SERVER_RESPONSE)) { /* This is a CALL. */ /* print the word parameters */ sprintf(GETLINE, "TotalParamBytes = %d", get2(vwv)); sprintf(GETLINE, "TotalDataBytes = %d", get2(vwv+2)); sprintf(GETLINE, "MaxParamBytes = %d", get2(vwv+4)); sprintf(GETLINE, "MaxDataBytes = %d", get2(vwv+6)); sprintf(GETLINE, "MaxSetupWords = %d", vwv[8]); sprintf(GETLINE, "TransFlags = 0x%.4x", get2(vwv+10)); sprintf(GETLINE, "Timeout = 0x%.8x", get4(vwv+12)); /* skip Reserved2 */ sprintf(GETLINE, "ParamBytes = 0x%.4x", parambytes); sprintf(GETLINE, "ParamOffset = 0x%.4x", paramoffset); sprintf(GETLINE, "DataBytes = 0x%.4x", get2(vwv+22)); sprintf(GETLINE, "DataOffset = 0x%.4x", get2(vwv+24)); sprintf(GETLINE, "SetupWords = %d", setupcount); /* That finishes the VWV, now the misc. stuff. */ if (subcode >= 0) sprintf(GETLINE, "Setup[0] = %d", subcode); if (apinum >= 0) sprintf(GETLINE, "APIcode = %d", apinum); if (0 <= apinum && apinum < apimax) sprintf(GETLINE, "APIname = %s", apinames[apinum]); /* Finally, print the byte parameters. */ if (isunicode) { byteparms += 1; /* alignment padding */ (void) unicode2ascii( filename, 256, byteparms, bytecount); } else { strcpy(filename, (char *)byteparms); } sprintf(GETLINE, "FileName = %s", filename); } if (flags & F_DTAIL && smb->flags & SERVER_RESPONSE) { /* This is a REPLY. */ /* print the word parameters */ sprintf(GETLINE, "TotalParamBytes = %d", get2(vwv)); sprintf(GETLINE, "TotalDataBytes = %d", get2(vwv+2)); /* skip Reserved */ sprintf(GETLINE, "ParamBytes = 0x%.4x", parambytes); sprintf(GETLINE, "ParamOffset = 0x%.4x", paramoffset); sprintf(GETLINE, "ParamDispl. = 0x%.4x", get2(vwv+10)); sprintf(GETLINE, "DataBytes = 0x%.4x", get2(vwv+12)); sprintf(GETLINE, "DataOffset = 0x%.4x", get2(vwv+14)); sprintf(GETLINE, "DataDispl. = 0x%.4x", get2(vwv+16)); sprintf(GETLINE, "SetupWords = %d", setupcount); output_bytes(byteparms, bytecount); } } /* * 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 length; int bytecount; int passwordlength; int wordcount; char tempstring[256]; struct smb *smbdata; uchar_t *tcondata; smbdata = (struct smb *)data; tcondata = (uchar_t *)data + sizeof (struct smb); wordcount = *tcondata++; if (flags & F_SUM && !(smbdata->flags & SERVER_RESPONSE)) { tcondata += 6; passwordlength = get2(tcondata); tcondata = tcondata + 4 + passwordlength; length = sprintf(tempstring, (char *)tcondata); sprintf(xtra, "Share=%s ", tempstring); } if (flags & F_SUM && smbdata->flags & SERVER_RESPONSE) { tcondata += 8; length = sprintf(tempstring, (char *)tcondata); sprintf(xtra, "Type=%s ", tempstring); } if (flags & F_DTAIL && !(smbdata->flags & SERVER_RESPONSE)) { sprintf(GETLINE, "WordCount = %d", wordcount); sprintf(GETLINE, "ChainedCommand = 0x%.2x", tcondata[0]); tcondata += 2; sprintf(GETLINE, "NextOffset = 0x%.4x", get2(tcondata)); tcondata += 2; sprintf(GETLINE, "DisconnectFlag = 0x%.4x", get2(tcondata)); tcondata += 2; passwordlength = get2(tcondata); sprintf(GETLINE, "PasswordLength = 0x%.4x", passwordlength); tcondata += 2; bytecount = get2(tcondata); sprintf(GETLINE, "ByteCount = %d", bytecount); tcondata = tcondata + 2 + passwordlength; length = sprintf(tempstring, (char *)tcondata); tcondata += (length+1); sprintf(GETLINE, "FileName = %s", tempstring); length = sprintf(tempstring, (char *)tcondata); tcondata += (length+1); sprintf(GETLINE, "ServiceName = %s", tempstring); } if (flags & F_DTAIL && smbdata->flags & SERVER_RESPONSE) { sprintf(GETLINE, "WordCount = %d", wordcount); sprintf(GETLINE, "ChainedCommand = 0x%.2x", tcondata[0]); tcondata += 2; sprintf(GETLINE, "NextOffset = 0x%.4x", get2(tcondata)); tcondata += 2; sprintf(GETLINE, "OptionalSupport = 0x%.4x", get2(tcondata)); tcondata += 2; bytecount = get2(tcondata); sprintf(GETLINE, "ByteCount = %d", bytecount); tcondata += 2; length = sprintf(tempstring, (char *)tcondata); tcondata += (length+1); sprintf(GETLINE, "ServiceName = %s", tempstring); length = sprintf(tempstring, (char *)tcondata); tcondata += (length+1); sprintf(GETLINE, "NativeFS = %s", tempstring); } } /* * 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 length; int bytecount; int passwordlength; int isunicode; int upasswordlength; int wordcount; int cap; char tempstring[256]; struct smb *smbdata; uchar_t *setupdata; smbdata = (struct smb *)data; setupdata = (uchar_t *)data + sizeof (struct smb); wordcount = *setupdata++; isunicode = smbdata->flags2[1] & 0x80; if (flags & F_SUM && !(smbdata->flags & SERVER_RESPONSE)) { if (wordcount != 13) return; setupdata += 14; passwordlength = get2(setupdata); setupdata += 2; upasswordlength = get2(setupdata); setupdata += 6; cap = get4(setupdata); setupdata = setupdata + 6 + passwordlength + upasswordlength; if (isunicode) { setupdata += 1; (void) unicode2ascii(tempstring, 256, setupdata, 256); sprintf(xtra, "Username=%s ", tempstring); } else { length = sprintf(tempstring, (char *)setupdata); sprintf(xtra, "Username=%s ", tempstring); } } if (flags & F_DTAIL && !(smbdata->flags & SERVER_RESPONSE)) { if (wordcount != 13) return; sprintf(GETLINE, "ChainedCommand = 0x%.2x", setupdata[0]); setupdata += 2; sprintf(GETLINE, "NextOffset = 0x%.4x", get2(setupdata)); setupdata += 2; sprintf(GETLINE, "MaxBufferSize = 0x%.4x", get2(setupdata)); setupdata += 2; sprintf(GETLINE, "MaxMPXRequests = %d", get2(setupdata)); setupdata += 2; sprintf(GETLINE, "VCNumber = %d", get2(setupdata)); setupdata += 2; sprintf(GETLINE, "SessionKey = %d", get4(setupdata)); setupdata += 4; passwordlength = get2(setupdata); sprintf(GETLINE, "PasswordLength = 0x%.4x", passwordlength); setupdata += 2; upasswordlength = get2(setupdata); sprintf(GETLINE, "UnicodePasswordLength = 0x%.4x", upasswordlength); setupdata += 6; cap = get4(setupdata); sprintf(GETLINE, "Capabilities = 0x%0.8x", cap); setupdata += 4; bytecount = get2(setupdata); sprintf(GETLINE, "ByteCount = %d", bytecount); setupdata = setupdata + 2 + passwordlength + upasswordlength; if (isunicode) { setupdata++; length = 2*unicode2ascii( tempstring, 256, setupdata, 256); if (length == 2) { sprintf(GETLINE, "AccountName = %s", tempstring); sprintf(GETLINE, "DomainName = %s", tempstring); setupdata += 3; } else { setupdata += length; sprintf(GETLINE, "AccountName = %s", tempstring); length = 2*unicode2ascii( tempstring, 256, setupdata, 256); setupdata += length; sprintf(GETLINE, "DomainName = %s", tempstring); } length = 2*unicode2ascii( tempstring, 256, setupdata, 256); setupdata += (length+2); sprintf(GETLINE, "NativeOS = %s", tempstring); length = 2*unicode2ascii( tempstring, 256, setupdata, 256); sprintf(GETLINE, "NativeLanman = %s", tempstring); } else { length = sprintf(tempstring, (char *)setupdata); setupdata += (length+1); sprintf(GETLINE, "AccountName = %s", tempstring); length = sprintf(tempstring, (char *)setupdata); setupdata += (length+1); sprintf(GETLINE, "DomainName = %s", tempstring); length = sprintf(tempstring, (char *)setupdata); setupdata += (length+1); sprintf(GETLINE, "NativeOS = %s", tempstring); sprintf(tempstring, (char *)setupdata); sprintf(GETLINE, "NativeLanman = %s", tempstring); } } if (flags & F_DTAIL && smbdata->flags & SERVER_RESPONSE) { if (wordcount != 3) return; sprintf(GETLINE, "ChainedCommand = 0x%.2x", setupdata[0]); setupdata += 2; sprintf(GETLINE, "NextOffset = 0x%.4x", get2(setupdata)); setupdata += 2; sprintf(GETLINE, "SetupAction = 0x%.4x", get2(setupdata)); setupdata += 2; bytecount = get2(setupdata); sprintf(GETLINE, "ByteCount = %d", bytecount); setupdata += 2; length = sprintf(tempstring, (char *)setupdata); setupdata += (length+1); sprintf(GETLINE, "NativeOS = %s", tempstring); length = sprintf(tempstring, (char *)setupdata); setupdata += (length+1); sprintf(GETLINE, "NativeLanman = %s", tempstring); length = sprintf(tempstring, (char *)setupdata); sprintf(GETLINE, "DomainName = %s", tempstring); } } /* * 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) { 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) { sprintf(GETLINE, "WordCount = %d", wordcount); sprintf(GETLINE, "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 */ sprintf(GETLINE, "TotalParamBytes = %d", get2(vwv)); sprintf(GETLINE, "TotalDataBytes = %d", get2(vwv+2)); sprintf(GETLINE, "MaxParamBytes = %d", get2(vwv+4)); sprintf(GETLINE, "MaxDataBytes = %d", get2(vwv+6)); sprintf(GETLINE, "MaxSetupWords = %d", vwv[8]); sprintf(GETLINE, "TransFlags = 0x%.4x", get2(vwv+10)); sprintf(GETLINE, "Timeout = 0x%.8x", get4(vwv+12)); /* skip Reserved2 */ sprintf(GETLINE, "ParamBytes = 0x%.4x", parambytes); sprintf(GETLINE, "ParamOffset = 0x%.4x", paramoffset); sprintf(GETLINE, "DataBytes = 0x%.4x", get2(vwv+22)); sprintf(GETLINE, "DataOffset = 0x%.4x", get2(vwv+24)); sprintf(GETLINE, "SetupWords = %d", setupcount); /* That finishes the VWV, now the misc. stuff. */ sprintf(GETLINE, "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); break; case TRANS2_FIND_NEXT2: output_trans2_findnext(flags, params, xtra); break; case TRANS2_QUERY_FS_INFORMATION: name = "QueryFSInfo"; goto name_only; case TRANS2_QUERY_PATH_INFORMATION: output_trans2_querypath(flags, params, xtra); break; case TRANS2_SET_PATH_INFORMATION: name = "SetPathInfo"; goto name_only; case TRANS2_QUERY_FILE_INFORMATION: output_trans2_queryfile(flags, params, xtra); break; case TRANS2_SET_FILE_INFORMATION: output_trans2_setfile(flags, params, xtra); break; case TRANS2_CREATE_DIRECTORY: name = "CreateDir"; goto name_only; default: name = "Unknown"; /* fall through */ name_only: if (flags & F_SUM) sprintf(xtra, "%s ", name); if (flags & F_DTAIL) sprintf(GETLINE, "FunctionName = %s", name); break; } } if (flags & F_DTAIL && smb->flags & SERVER_RESPONSE) { /* This is a REPLY. */ /* print the word parameters */ sprintf(GETLINE, "TotalParamBytes = %d", get2(vwv)); sprintf(GETLINE, "TotalDataBytes = %d", get2(vwv+2)); /* skip Reserved */ sprintf(GETLINE, "ParamBytes = 0x%.4x", parambytes); sprintf(GETLINE, "ParamOffset = 0x%.4x", paramoffset); sprintf(GETLINE, "ParamDispl. = 0x%.4x", get2(vwv+10)); sprintf(GETLINE, "DataBytes = 0x%.4x", get2(vwv+12)); sprintf(GETLINE, "DataOffset = 0x%.4x", get2(vwv+14)); sprintf(GETLINE, "DataDispl. = 0x%.4x", get2(vwv+16)); sprintf(GETLINE, "SetupWords = %d", setupcount); output_bytes(byteparms, bytecount); } } static void interpret_default(int flags, uchar_t *data, int len, char *xtra) { int slength; int i; int printit; int wordcount; char *outstr; char *prfmt; char *format; char valuetype; char word[10]; char *label; char tempstring[256]; uchar_t *comdata, *limit; char buff[80]; struct smb *smbdata; struct decode *decoder; smbdata = (struct smb *)data; comdata = (uchar_t *)data + sizeof (struct smb); wordcount = *comdata++; limit = data + len; decoder = &SMBtable[smbdata->com & 255]; if (smbdata->flags & SERVER_RESPONSE) format = decoder->replyfmt; else format = decoder->callfmt; if (!format || strlen(format) == 0) { if (wordcount == 0 || flags & F_SUM) return; sprintf(GETLINE, "WordCount = %d", wordcount); sprintf(GETLINE, "Word values (in hex):"); for (i = 0; i < wordcount; i++) { sprintf(word, "%.4x ", get2(comdata)); comdata += 2; if (comdata >= limit) wordcount = i+1; /* terminate */ strcat(buff, word); if (((i+1) & 7) == 0 || i == (wordcount-1)) { sprintf(GETLINE, "%s", buff); strcpy(buff, ""); } } return; } valuetype = format[0]; while (valuetype != '\0') { if (comdata >= limit) break; if ((flags & F_DTAIL) && valuetype != 'r' && valuetype != 'R') outstr = GETLINE; else outstr = xtra + strlen(xtra); label = format+1; printit = (flags & F_DTAIL) || (valuetype <= 'Z'); switch (valuetype) { case 'W': case 'w': prfmt = (flags & F_DTAIL) ? "%s = 0x%.4x" : "%s=0x%x "; if (printit) sprintf(outstr, prfmt, label, get2(comdata)); comdata += 2; break; case 'D': case 'd': prfmt = (flags & F_DTAIL) ? "%s = %d" : "%s=%d "; if (printit) sprintf(outstr, prfmt, label, get2(comdata)); comdata += 2; break; case 'L': case 'l': prfmt = (flags & F_DTAIL) ? "%s = 0x%.8x" : "%s=0x%x "; if (printit) sprintf(outstr, prfmt, label, get4(comdata)); comdata += 4; break; case 'B': case 'b': prfmt = (flags & F_DTAIL) ? "%s = 0x%.2x" : "%s=0x%x "; if (printit) sprintf(outstr, prfmt, label, comdata[0]); comdata += 1; break; case 'r': comdata++; break; case 'R': comdata += 2; break; case 'U': case 'u': prfmt = (flags & F_DTAIL) ? "%s = %s" : "%s=%s "; slength = unicode2ascii(tempstring, 256, comdata, 256); if (printit) sprintf(outstr, prfmt, label, tempstring); comdata += (slength*2 + 1); break; case 'S': case 's': prfmt = (flags & F_DTAIL) ? "%s = %s" : "%s=%s "; slength = sprintf(tempstring, (char *)comdata); if (printit) sprintf(outstr, prfmt, label, tempstring); comdata += (slength+1); break; } format += (strlen(format) + 1); valuetype = format[0]; } }