We are still dealing with the transport layer and haven't actually seen any SMBs yet. It is, however, finally time for some code. Listing 10.1 handles the basics of opening the connection with an SMB server. It is example code so, of course, it takes a few shortcuts. For instance, it completely sidesteps Server Identifier interpretation and transport discovery (that is, everything we just covered). Listing 10.1 Opening a session with an SMB server#include <stdio.h> #include <errno.h> #include <stdarg.h> #include <stdlib.h> #include <sys/poll.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* NBT Session Service Packet Type Codes */ #define SESS_MSG 0x00 #define SESS_REQ 0x81 #define SESS_POS_RESP 0x82 #define SESS_NEG_RESP 0x83 #define SESS_RETARGET 0x84 #define SESS_KEEPALIVE 0x85 /* NBT Session Service Error Codes */ #define ErrNLCalled 0x80 #define ErrNLCalling 0x81 #define ErrCalledNotPrsnt 0x82 #define ErrInsResources 0x83 #define ErrUnspecified 0x8F ushort nbt_GetShort( uchar *src, int offset ) /* ---------------------------------------------------- ** * Read two bytes from an NBT message and convert them * to an unsigned short int. * Note that we read the bytes in NBT byte order, which * is the opposite of SMB byte order. * ---------------------------------------------------- ** */ { ushort tmp; tmp = src[offset]; tmp = (tmp << 8) src[offset+1]; return( tmp ); } /* nbt_GetShort */ void Fail( char *fmt, ... ) /* ---------------------------------------------------- ** * This function formats and prints an error to stdout, * then exits the program. * A nice quick way to abandon ship. * ---------------------------------------------------- ** */ { va_list ap; va_start( ap, fmt ); (void)fprintf( stdout, "Error: " ); (void)vfprintf( stdout, fmt, ap ); exit( EXIT_FAILURE ); } /* Fail */ void NegResponse( uchar *bufr, int len ) /* ---------------------------------------------------- ** * Negative Session Response error reporting. * * The Negative Session Response message should always * be five bytes in length. The final byte (bufr[4]) * contains the error code. * ---------------------------------------------------- ** */ { if( len < 5 ) Fail( "Truncated Negative Session Response.\n" ); printf( "Negative Session Response: " ); switch( bufr[4] ) { case ErrNLCalled: printf( "Not listening on Called Name.\n" ); break; case ErrNLCalling: printf( "Not listening *for* Calling Name.\n" ); break; case ErrCalledNotPrsnt: printf( "Called Name not present.\n" ); break; case ErrInsResources: printf( "Insufficient resources on server.\n" ); break; case ErrUnspecified: printf( "Unspecified error.\n" ); break; default: printf( "Unknown error.\n" ); break; } } /* NegResponse */ void Retarget( uchar *bufr, int result ) /* ---------------------------------------------------- ** * This function is called if we receive a RETARGET * SESSION RESPONSE from the server. The correct thing * to do would be to retry the connection, using the * returned information. This function simply reports * the retarget response so that the user can manually * retry. * ---------------------------------------------------- ** */ { if( result < 10 ) Fail( "Truncated Retarget Session Response.\n" ); printf( "Retarget Session Response: " ); printf( "IP = %d.%d.%d.%d, ", bufr[4], bufr[5], bufr[6], bufr[7] ); printf( "Port = %d\n", nbt_GetShort( bufr, 8 ) ); } /* Retarget */ int MakeSessReq( uchar *bufr, uchar *Called, uchar *Calling ) /* ---------------------------------------------------- ** * Create an NBT SESSION REQUEST message. * ---------------------------------------------------- ** */ { /* Write the header. */ bufr[0] = SESS_REQ; bufr[1] = 0; bufr[2] = 0; bufr[3] = 68; /* 2x34 bytes in length. */ /* Copy the Called and Calling names into the buffer. */ (void)memcpy( &bufr[4], Called, 34 ); (void)memcpy( &bufr[38], Calling, 34 ); /* Return the total message length. */ return( 72 ); } /* MakeSessReq */ int RecvTimeout( int sock, uchar *bufr, int bsize, int timeout ) /* ---------------------------------------------------- ** * Attempt to receive a TCP packet within a specified * period of time. * ---------------------------------------------------- ** */ { int result; struct pollfd pollfd[1]; /* Wait timeout/1000 seconds for a message to arrive. */ pollfd->fd = sock; pollfd->events = POLLIN; pollfd->revents = 0; result = poll( pollfd, 1, timeout ); /* A result less than zero is an error. */ if( result < 0 ) Fail( "Poll() error: %s\n", strerror( errno ) ); /* A result of zero is a timeout. */ if( result == 0 ) return( 0 ); /* A result greater than zero means a message arrived, * so we attempt to read the message. */ result = recv( sock, bufr, bsize, 0 ); if( result < 0 ) Fail( "Recv() error: %s\n", strerror( errno ) ); /* Return the number of bytes received. * (Zero or more.) */ return( result ); } /* RecvTimeout */ void RequestNBTSession( int sock, uchar *Called, uchar *Calling ) /* ---------------------------------------------------- ** * Send an NBT SESSION REQUEST over the TCP connection, * then wait for a reply. * ---------------------------------------------------- ** */ { uchar bufr[128]; int result; /* Create the NBT Session Request message. */ result = MakeSessReq( bufr, Called, Calling ); /* Send the NBT Session Request message. */ result = send( sock, bufr, result, 0 ); if( result < 0 ) Fail( "Error sending Session Request message: %s\n", strerror( errno ) ); /* Now wait for and handle the reply (2 seconds). */ result = RecvTimeout( sock, bufr, 128, 2000 ); if( result == 0 ) { printf( "Timeout waiting for NBT Session Response.\n" ); return; } switch( *bufr ) { case SESS_POS_RESP: /* We got what we wanted. */ printf( "Positive Session Response.\n" ); return; case SESS_NEG_RESP: /* Report an error. */ NegResponse( bufr, result ); exit( EXIT_FAILURE ); case SESS_RETARGET: /* We've been retargeted. */ Retarget( bufr, result ); exit( EXIT_FAILURE ); default: /* Not a response we expected. */ Fail( "Unexpected response from server.\n" ); break; } } /* RequestNBTSession */ int OpenTCPSession( struct in_addr dst_IP, ushort dst_port ) /* ---------------------------------------------------- ** * Open a TCP session with the specified server. * Return the connected socket. * ---------------------------------------------------- ** */ { int sock; int result; struct sockaddr_in sock_addr; /* Create the socket. */ sock = socket( PF_INET, SOCK_STREAM, IPPROTO_TCP ); if( sock < 0 ) Fail( "Failed to create socket(); %s.\n", strerror( errno ) ); /* Connect the socket to the server at the other end. */ sock_addr.sin_addr = dst_IP; sock_addr.sin_family = AF_INET; sock_addr.sin_port = htons( dst_port ); result = connect( sock, (struct sockaddr *)&sock_addr, sizeof(struct sockaddr_in) ); if( result < 0 ) Fail( "Failed to create socket(); %s.\n", strerror( errno ) ); return( sock ); } /* OpenTCPSession */ int main( int argc, char *argv[] ) /* ---------------------------------------------------- ** * Program mainline. * Parse the command-line input and open the connection * to the server. * ---------------------------------------------------- ** */ { uchar Called[34]; uchar Calling[34]; struct in_addr dst_addr; int dst_port = 139; int sock; /* Check for the correct number of arguments. */ if( argc < 3 argc > 4 ) { printf( "Usage: %s <NAME> <IP> [<PORT>]\n", argv[0] ); exit( EXIT_FAILURE ); } /* Encode the destination name. */ if( '*' == *(argv[1]) ) (void)L2_Encode( Called, "*SMBSERVER", 0x20, 0x20, "" ); else (void)L2_Encode( Called, argv[1], 0x20, 0x20, "" ); /* Create a (bogus) Calling Name. */ (void)L2_Encode( Calling, "SMBCLIENT", 0x20, 0x00, "" ); /* Read the destination IP address. * We could do a little more work and resolve * the Called Name, but that would add a lot * of code to the example. */ if( 0 == inet_aton( argv[2], &dst_addr ) ) { printf( "Invalid IP.\n" ); printf( "Usage: %s <NAME> <IP> [<PORT>]\n", argv[0] ); exit( EXIT_FAILURE ); } /* Read the (optional) port number. */ if( argc == 4 ) { dst_port = atoi( argv[3] ); if( 0 == dst_port ) { printf( "Invalid Port number.\n" ); printf( "Usage: %s <NAME> <IP> [<PORT>]\n", argv[0] ); exit( EXIT_FAILURE ); } } /* Open the session. */ sock = OpenTCPSession( dst_addr, dst_port ); /* Comment out the next call for raw TCP. */ RequestNBTSession( sock, Called, Calling ); /* ** Do real work here. ** */ return( EXIT_SUCCESS ); } /* main */ The code in Listing 10.1 provides an outline for setting up the session via NBT or raw TCP. With that step behind us, we won't have to deal with the details of the transport layer any longer. Let's run through some code highlights quickly and put all that transport stuff behind us. Transport
The command line
The CALL ING NAME (NBT source address)
Transporting SMBs
Use the program above as a starting point for building your own SMB client utility. Add a parser capable of dissecting the UNC or SMB URL format, and then code up Server Identifier resolution and transport discovery, as described above. When you have all of that put together, you will have completed the foundation of your SMB client. |