/* jcifs smb client library in Java * Copyright (C) 2000 "Michael B. Allen" <jcifs at samba dot org> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package jcifs.smb; import jcifs.Config; import java.io.InputStream; import java.io.PushbackInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Calendar; import java.util.Date; import jcifs.util.Hexdump; import jcifs.util.LogStream; import jcifs.util.transport.*; abstract class ServerMessageBlock extends Response implements Request, SmbConstants { static LogStream log = LogStream.getInstance(); static final byte[] header = { (byte)0xFF, (byte)'S', (byte)'M', (byte)'B', (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 }; static void writeInt2( long val, byte[] dst, int dstIndex ) { dst[dstIndex] = (byte)(val); dst[++dstIndex] = (byte)(val >> 8); } static void writeInt4( long val, byte[] dst, int dstIndex ) { dst[dstIndex] = (byte)(val); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >> 8); } static int readInt2( byte[] src, int srcIndex ) { return ( src[srcIndex] & 0xFF ) + (( src[srcIndex + 1] & 0xFF ) << 8 ); } static int readInt4( byte[] src, int srcIndex ) { return ( src[srcIndex] & 0xFF ) + (( src[srcIndex + 1] & 0xFF ) << 8 ) + (( src[srcIndex + 2] & 0xFF ) << 16 ) + (( src[srcIndex + 3] & 0xFF ) << 24 ); } static long readInt8( byte[] src, int srcIndex ) { return (readInt4( src, srcIndex ) & 0xFFFFFFFFL) + ((long)(readInt4( src, srcIndex + 4 )) << 32); } static void writeInt8( long val, byte[] dst, int dstIndex ) { dst[dstIndex] = (byte)(val); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >>= 8); dst[++dstIndex] = (byte)(val >> 8); } static long readTime( byte[] src, int srcIndex ) { int low = readInt4( src, srcIndex ); int hi = readInt4( src, srcIndex + 4 ); long t = ((long)hi << 32L ) | (low & 0xFFFFFFFFL); t = ( t / 10000L - MILLISECONDS_BETWEEN_1970_AND_1601 ); return t; } static void writeTime( long t, byte[] dst, int dstIndex ) { if( t != 0L ) { t = (t + MILLISECONDS_BETWEEN_1970_AND_1601) * 10000L; } writeInt8( t, dst, dstIndex ); } static long readUTime( byte[] buffer, int bufferIndex ) { return readInt4( buffer, bufferIndex ) * 1000L; } static void writeUTime( long t, byte[] dst, int dstIndex ) { if( t == 0L || t == 0xFFFFFFFFFFFFFFFFL ) { writeInt4( 0xFFFFFFFF, dst, dstIndex ); return; } synchronized( TZ ) { if( TZ.inDaylightTime( new Date() )) { // in DST if( TZ.inDaylightTime( new Date( t ))) { // t also in DST so no correction } else { // t not in DST so subtract 1 hour t -= 3600000; } } else { // not in DST if( TZ.inDaylightTime( new Date( t ))) { // t is in DST so add 1 hour t += 3600000; } else { // t isn't in DST either } } } writeInt4( (int)(t / 1000L), dst, dstIndex ); } /* * These are all the smbs supported by this library. This includes requests * and well as their responses for each type however the actuall implementations * of the readXxxWireFormat and writeXxxWireFormat methods may not be in * place. For example at the time of this writing the readXxxWireFormat * for requests and the writeXxxWireFormat for responses are not implemented * and simply return 0. These would need to be completed for a server * implementation. */ static final byte SMB_COM_CREATE_DIRECTORY = (byte)0x00; static final byte SMB_COM_DELETE_DIRECTORY = (byte)0x01; static final byte SMB_COM_CLOSE = (byte)0x04; static final byte SMB_COM_DELETE = (byte)0x06; static final byte SMB_COM_RENAME = (byte)0x07; static final byte SMB_COM_QUERY_INFORMATION = (byte)0x08; static final byte SMB_COM_WRITE = (byte)0x0B; static final byte SMB_COM_CHECK_DIRECTORY = (byte)0x10; static final byte SMB_COM_TRANSACTION = (byte)0x25; static final byte SMB_COM_TRANSACTION_SECONDARY = (byte)0x26; static final byte SMB_COM_MOVE = (byte)0x2A; static final byte SMB_COM_ECHO = (byte)0x2B; static final byte SMB_COM_OPEN_ANDX = (byte)0x2D; static final byte SMB_COM_READ_ANDX = (byte)0x2E; static final byte SMB_COM_WRITE_ANDX = (byte)0x2F; static final byte SMB_COM_TRANSACTION2 = (byte)0x32; static final byte SMB_COM_FIND_CLOSE2 = (byte)0x34; static final byte SMB_COM_TREE_DISCONNECT = (byte)0x71; static final byte SMB_COM_NEGOTIATE = (byte)0x72; static final byte SMB_COM_SESSION_SETUP_ANDX = (byte)0x73; static final byte SMB_COM_LOGOFF_ANDX = (byte)0x74; static final byte SMB_COM_TREE_CONNECT_ANDX = (byte)0x75; static final byte SMB_COM_NT_TRANSACT = (byte)0xA0; static final byte SMB_COM_NT_TRANSACT_SECONDARY = (byte)0xA1; static final byte SMB_COM_NT_CREATE_ANDX = (byte)0xA2; /* * Some fields specify the offset from the beginning of the header. This * field should be used for calculating that. This would likely be zero * but an implemantation that encorporates the transport header(for * efficiency) might use a different initial bufferIndex. For example, * to eliminate copying data when writing NbtSession data one might * manage that 4 byte header specifically and therefore the initial * bufferIndex, and thus headerStart, would be 4).(NOTE: If one where * looking for a way to improve perfomance this is precisly what you * would want to do as the jcifs.netbios.SocketXxxputStream classes * arraycopy all data read or written into a new buffer shifted over 4!) */ byte command, flags; int headerStart, length, batchLevel, errorCode, flags2, tid, pid, uid, mid, wordCount, byteCount; boolean useUnicode, received, extendedSecurity; long responseTimeout = 1; int signSeq; boolean verifyFailed; NtlmPasswordAuthentication auth = null; String path; SigningDigest digest = null; ServerMessageBlock response; /** * Used for the request object to override the timeout. * null to follow the default */ public Long timeout; ServerMessageBlock() { flags = (byte)( FLAGS_PATH_NAMES_CASELESS | FLAGS_PATH_NAMES_CANONICALIZED ); pid = PID; batchLevel = 0; } void reset() { flags = (byte)( FLAGS_PATH_NAMES_CASELESS | FLAGS_PATH_NAMES_CANONICALIZED ); flags2 = 0; errorCode = 0; received = false; digest = null; } int writeString( String str, byte[] dst, int dstIndex ) { return writeString( str, dst, dstIndex, useUnicode ); } int writeString( String str, byte[] dst, int dstIndex, boolean useUnicode ) { int start = dstIndex; try { if( useUnicode ) { // Unicode requires word alignment if((( dstIndex - headerStart ) % 2 ) != 0 ) { dst[dstIndex++] = (byte)'\0'; } System.arraycopy( str.getBytes( UNI_ENCODING ), 0, dst, dstIndex, str.length() * 2 ); dstIndex += str.length() * 2; dst[dstIndex++] = (byte)'\0'; dst[dstIndex++] = (byte)'\0'; } else { byte[] b = str.getBytes( OEM_ENCODING ); System.arraycopy( b, 0, dst, dstIndex, b.length ); dstIndex += b.length; dst[dstIndex++] = (byte)'\0'; } } catch( UnsupportedEncodingException uee ) { if( log.level > 1 ) uee.printStackTrace( log ); } return dstIndex - start; } String readString( byte[] src, int srcIndex ) { return readString( src, srcIndex, 256, useUnicode ); } String readString( byte[] src, int srcIndex, int maxLen, boolean useUnicode ) { int len = 0; String str = null; try { if( useUnicode ) { // Unicode requires word alignment if((( srcIndex - headerStart ) % 2 ) != 0 ) { srcIndex++; } while( src[srcIndex + len] != (byte)0x00 || src[srcIndex + len + 1] != (byte)0x00 ) { len += 2; if( len > maxLen ) { if( log.level > 0 ) Hexdump.hexdump( System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128 ); throw new RuntimeException( "zero termination not found" ); } } str = new String( src, srcIndex, len, UNI_ENCODING ); } else { while( src[srcIndex + len] != (byte)0x00 ) { len++; if( len > maxLen ) { if( log.level > 0 ) Hexdump.hexdump( System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128 ); throw new RuntimeException( "zero termination not found" ); } } str = new String( src, srcIndex, len, OEM_ENCODING ); } } catch( UnsupportedEncodingException uee ) { if( log.level > 1 ) uee.printStackTrace( log ); } return str; } String readString(byte[] src, int srcIndex, int srcEnd, int maxLen, boolean useUnicode) { int len = 0; String str = null; try { if (useUnicode) { // Unicode requires word alignment if (((srcIndex - headerStart) % 2) != 0) { srcIndex++; } for (len = 0; (srcIndex + len + 1) < srcEnd; len += 2) { if (src[srcIndex + len] == (byte)0x00 && src[srcIndex + len + 1] == (byte)0x00) { break; } if (len > maxLen) { if (log.level > 0) Hexdump.hexdump(System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128); throw new RuntimeException("zero termination not found"); } } str = new String(src, srcIndex, len, UNI_ENCODING); } else { for (len = 0; srcIndex < srcEnd; len++) { if (src[srcIndex + len] == (byte)0x00) { break; } if (len > maxLen) { if (log.level > 0) Hexdump.hexdump(System.err, src, srcIndex, maxLen < 128 ? maxLen + 8 : 128); throw new RuntimeException("zero termination not found"); } } str = new String(src, srcIndex, len, OEM_ENCODING); } } catch( UnsupportedEncodingException uee ) { if( log.level > 1 ) uee.printStackTrace( log ); } return str; } int stringWireLength( String str, int offset ) { int len = str.length() + 1; if( useUnicode ) { len = str.length() * 2 + 2; len = ( offset % 2 ) != 0 ? len + 1 : len; } return len; } int readStringLength( byte[] src, int srcIndex, int max ) { int len = 0; while( src[srcIndex + len] != (byte)0x00 ) { if( len++ > max ) { throw new RuntimeException( "zero termination not found: " + this ); } } return len; } int encode( byte[] dst, int dstIndex ) { int start = headerStart = dstIndex; dstIndex += writeHeaderWireFormat( dst, dstIndex ); wordCount = writeParameterWordsWireFormat( dst, dstIndex + 1 ); dst[dstIndex++] = (byte)(( wordCount / 2 ) & 0xFF ); dstIndex += wordCount; wordCount /= 2; byteCount = writeBytesWireFormat( dst, dstIndex + 2 ); dst[dstIndex++] = (byte)( byteCount & 0xFF ); dst[dstIndex++] = (byte)(( byteCount >> 8 ) & 0xFF ); dstIndex += byteCount; length = dstIndex - start; if( digest != null ) { digest.sign( dst, headerStart, length, this, response ); } return length; } int decode( byte[] buffer, int bufferIndex ) { int start = headerStart = bufferIndex; bufferIndex += readHeaderWireFormat( buffer, bufferIndex ); wordCount = buffer[bufferIndex++]; if( wordCount != 0 ) { int n; if(( n = readParameterWordsWireFormat( buffer, bufferIndex )) != wordCount * 2 ) { if( log.level >= 5 ) { log.println( "wordCount * 2=" + ( wordCount * 2 ) + " but readParameterWordsWireFormat returned " + n ); } } bufferIndex += wordCount * 2; } byteCount = readInt2( buffer, bufferIndex ); bufferIndex += 2; if( byteCount != 0 ) { int n; if(( n = readBytesWireFormat( buffer, bufferIndex )) != byteCount ) { if( log.level >= 5 ) { log.println( "byteCount=" + byteCount + " but readBytesWireFormat returned " + n ); } } // Don't think we can rely on n being correct here. Must use byteCount. // Last paragraph of section 3.13.3 eludes to this. bufferIndex += byteCount; } length = bufferIndex - start; return length; } int writeHeaderWireFormat( byte[] dst, int dstIndex ) { System.arraycopy( header, 0, dst, dstIndex, header.length ); dst[dstIndex + CMD_OFFSET] = command; dst[dstIndex + FLAGS_OFFSET] = flags; writeInt2( flags2, dst, dstIndex + FLAGS_OFFSET + 1 ); dstIndex += TID_OFFSET; writeInt2( tid, dst, dstIndex ); writeInt2( pid, dst, dstIndex + 2 ); writeInt2( uid, dst, dstIndex + 4 ); writeInt2( mid, dst, dstIndex + 6 ); return HEADER_LENGTH; } int readHeaderWireFormat( byte[] buffer, int bufferIndex ) { command = buffer[bufferIndex + CMD_OFFSET]; errorCode = readInt4( buffer, bufferIndex + ERROR_CODE_OFFSET ); flags = buffer[bufferIndex + FLAGS_OFFSET]; flags2 = readInt2( buffer, bufferIndex + FLAGS_OFFSET + 1 ); tid = readInt2( buffer, bufferIndex + TID_OFFSET ); pid = readInt2( buffer, bufferIndex + TID_OFFSET + 2 ); uid = readInt2( buffer, bufferIndex + TID_OFFSET + 4 ); mid = readInt2( buffer, bufferIndex + TID_OFFSET + 6 ); return HEADER_LENGTH; } boolean isResponse() { return ( flags & FLAGS_RESPONSE ) == FLAGS_RESPONSE; } /* * For this packet deconstruction technique to work for * other networking protocols the InputStream may need * to be passed to the readXxxWireFormat methods. This is * actually purer. However, in the case of smb we know the * wordCount and byteCount. And since every subclass of * ServerMessageBlock would have to perform the same read * operation on the input stream, we might as will pull that * common functionality into the superclass and read wordCount * and byteCount worth of data. * * We will still use the readXxxWireFormat return values to * indicate how many bytes(note: readParameterWordsWireFormat * returns bytes read and not the number of words(but the * wordCount member DOES store the number of words)) we * actually read. Incedentally this is important to the * AndXServerMessageBlock class that needs to potentially * read in another smb's parameter words and bytes based on * information in it's andxCommand, andxOffset, ...etc. */ abstract int writeParameterWordsWireFormat( byte[] dst, int dstIndex ); abstract int writeBytesWireFormat( byte[] dst, int dstIndex ); abstract int readParameterWordsWireFormat( byte[] buffer, int bufferIndex ); abstract int readBytesWireFormat( byte[] buffer, int bufferIndex ); public int hashCode() { return mid; } public boolean equals( Object obj ) { return obj instanceof ServerMessageBlock && ((ServerMessageBlock)obj).mid == mid; } public String toString() { String c; switch( command ) { case SMB_COM_NEGOTIATE: c = "SMB_COM_NEGOTIATE"; break; case SMB_COM_SESSION_SETUP_ANDX: c = "SMB_COM_SESSION_SETUP_ANDX"; break; case SMB_COM_TREE_CONNECT_ANDX: c = "SMB_COM_TREE_CONNECT_ANDX"; break; case SMB_COM_QUERY_INFORMATION: c = "SMB_COM_QUERY_INFORMATION"; break; case SMB_COM_CHECK_DIRECTORY: c = "SMB_COM_CHECK_DIRECTORY"; break; case SMB_COM_TRANSACTION: c = "SMB_COM_TRANSACTION"; break; case SMB_COM_TRANSACTION2: c = "SMB_COM_TRANSACTION2"; break; case SMB_COM_TRANSACTION_SECONDARY: c = "SMB_COM_TRANSACTION_SECONDARY"; break; case SMB_COM_FIND_CLOSE2: c = "SMB_COM_FIND_CLOSE2"; break; case SMB_COM_TREE_DISCONNECT: c = "SMB_COM_TREE_DISCONNECT"; break; case SMB_COM_LOGOFF_ANDX: c = "SMB_COM_LOGOFF_ANDX"; break; case SMB_COM_ECHO: c = "SMB_COM_ECHO"; break; case SMB_COM_MOVE: c = "SMB_COM_MOVE"; break; case SMB_COM_RENAME: c = "SMB_COM_RENAME"; break; case SMB_COM_DELETE: c = "SMB_COM_DELETE"; break; case SMB_COM_DELETE_DIRECTORY: c = "SMB_COM_DELETE_DIRECTORY"; break; case SMB_COM_NT_CREATE_ANDX: c = "SMB_COM_NT_CREATE_ANDX"; break; case SMB_COM_OPEN_ANDX: c = "SMB_COM_OPEN_ANDX"; break; case SMB_COM_READ_ANDX: c = "SMB_COM_READ_ANDX"; break; case SMB_COM_CLOSE: c = "SMB_COM_CLOSE"; break; case SMB_COM_WRITE_ANDX: c = "SMB_COM_WRITE_ANDX"; break; case SMB_COM_CREATE_DIRECTORY: c = "SMB_COM_CREATE_DIRECTORY"; break; case SMB_COM_NT_TRANSACT: c = "SMB_COM_NT_TRANSACT"; break; case SMB_COM_NT_TRANSACT_SECONDARY: c = "SMB_COM_NT_TRANSACT_SECONDARY"; break; default: c = "UNKNOWN"; } String str = errorCode == 0 ? "0" : SmbException.getMessageByCode( errorCode ); return new String( "command=" + c + ",received=" + received + ",errorCode=" + str + ",flags=0x" + Hexdump.toHexString( flags & 0xFF, 4 ) + ",flags2=0x" + Hexdump.toHexString( flags2, 4 ) + ",signSeq=" + signSeq + ",tid=" + tid + ",pid=" + pid + ",uid=" + uid + ",mid=" + mid + ",wordCount=" + wordCount + ",byteCount=" + byteCount ); } }