10.4 Connecting to the Server

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 program does not attempt to discover which transport to use. As written, it assumes NBT transport. To try naked transport, simply comment out the call to RequestNBTSession() in main() .

The command line

Because we are shamelessly avoiding presenting code that interprets Server Identifiers, the example program makes the user do all of the work. The user must enter the NetBIOS name and IP address of the server. Entering a destination port number is optional.

The name entered on the command line will be used as the CALLED NAME . If the input string begins with an asterisk, the generic *SMBSERVER<20> name will be used instead.

The CALL ING NAME (NBT source address)

The program inserts SMBCLIENT<00> as the CALLING NAME .

In a correct implementation, the name should be the client's NetBIOS Machine Name (which is typically the same as the client's DNS hostname) with a suffix byte value if 0x00 .

The contents of the CALLING NAME field are not particularly significant. According to the expired Leach/Naik CIFS Internet Draft, the same name from the same IP address is supposed to represent the same client... but you knew that. Samba can make use of the CALLING NAME via a macro in the smb.conf configuration file. The macro is used for all sorts of things, including generating per-client log files.

Transporting SMBs

A key feature of this program is the line within main() which reads:

 /* ** Do real work here. ** */ 

That's where the SMB stuff is supposed to happen. At that point in the code, the session has been established on top of the transport layer and it is time to start moving those Server Message Blocks.

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.



Implementing CIFS. The Common Internet File System
Implementing CIFS: The Common Internet File System
ISBN: 013047116X
EAN: 2147483647
Year: 2002
Pages: 210

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net