Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2137 lines
47 KiB

/**********************************************************************/
/** Microsoft Windows NT **/
/** Copyright(c) Microsoft Corp., 1993 **/
/**********************************************************************/
/*
sockutil.c
This module contains utility routines for managing & manipulating
sockets.
FILE HISTORY:
KeithMo 07-Mar-1993 Created.
*/
#include "ftpdp.h"
#pragma hdrstop
//
// Private constants.
//
#define DEFAULT_BUFFER_SIZE 4096 // bytes
#define CLEANUP_POLL_INTERVAL 2000 // milliseconds
#define CLEANUP_RETRY_COUNT 4 // iterations
#define FIRST_TELNET_COMMAND 240
//
// Private globals.
//
#if DBG
//
// Force socket API statistics in DEBUG builds.
//
#define KEEP_SOCK_STATS
#endif // DBG
#ifdef KEEP_SOCK_STATS
typedef struct _SOCK_STATS
{
LONG socket_Ok;
LONG socket_Fail;
LONG accept_Ok;
LONG accept_Fail;
LONG closesocket_Ok;
LONG closesocket_Fail;
LONG ActiveSockets;
} SOCK_STATS;
SOCK_STATS SockStats;
#define SOCKET_OK() InterlockedIncrement( &SockStats.socket_Ok ); \
InterlockedIncrement( &SockStats.ActiveSockets )
#define SOCKET_FAIL() InterlockedIncrement( &SockStats.socket_Fail )
#define ACCEPT_OK() InterlockedIncrement( &SockStats.accept_Ok ); \
InterlockedIncrement( &SockStats.ActiveSockets )
#define ACCEPT_FAIL() InterlockedIncrement( &SockStats.accept_Fail )
#define CLOSE_OK() InterlockedIncrement( &SockStats.closesocket_Ok ); \
InterlockedDecrement( &SockStats.ActiveSockets )
#define CLOSE_FAIL() InterlockedIncrement( &SockStats.closesocket_Fail )
#else // !KEEP_SOCK_STATS
#define SOCKET_OK()
#define SOCKET_FAIL()
#define ACCEPT_OK()
#define ACCEPT_FAIL()
#define CLOSE_OK()
#define CLOSE_FAIL()
#endif // KEEP_SOCK_STATS
//
// Private prototypes.
//
SOCKERR
vSockPrintf(
SOCKET sock,
CHAR * pszFormat,
va_list args
);
SOCKERR
vSockReply(
SOCKET sock,
UINT ReplyCode,
CHAR chSeparator,
CHAR * pszFormat,
va_list args
);
SOCKERR
WaitForSocketRead(
SOCKET sockRead,
SOCKET sockExcept,
BOOL * pfExcept
);
SOCKERR
WaitForSocketWrite(
SOCKET sockWrite,
SOCKET sockExcept,
BOOL * pfExcept
);
SOCKERR
WaitForSocketWorker(
SOCKET sockRead,
SOCKET sockWrite,
SOCKET sockExcept,
BOOL * pfRead,
BOOL * pfWrite,
BOOL * pfExcept
);
SOCKERR
DiscardOutOfBandData(
USER_DATA * pUserData,
SOCKET sock
);
//
// Public functions.
//
/*******************************************************************
NAME: InitializeSockets
SYNOPSIS: Initializes socket access. Among other things, this
routine is responsible for connecting to WinSock,
and creating the connection thread.
RETURNS: APIERR - NO_ERROR if successful, otherwise a Win32
error code.
NOTES: This routine may only be called by a single thread
of execution; it is not necessarily multi-thread safe.
HISTORY:
KeithMo 07-Mar-1993 Created.
KeithMo 07-Sep-1993 Get FTP data port via getservbyname().
********************************************************************/
APIERR
InitializeSockets(
VOID
)
{
WSADATA wsadata;
SOCKERR serr;
SERVENT * pserv;
INT cbOpt;
HANDLE hConnectThread;
DWORD idConnectThread;
CHAR szHost[MAXGETHOSTSTRUCT];
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "initializing sockets\n" ));
}
//
// Connect to WinSock.
//
serr = WSAStartup( MAKEWORD( 1, 1 ), &wsadata );
if( serr != 0 )
{
FtpdLogEvent( FTPD_EVENT_CANNOT_INITIALIZE_WINSOCK,
0,
NULL,
serr );
FTPD_PRINT(( "cannot initialize WinSock, socket error %d\n",
serr ));
svcStatus.dwServiceSpecificExitCode = (DWORD)serr;
return ERROR_SERVICE_SPECIFIC_ERROR;
}
//
// Determine the local host name.
//
pszHostName = NULL;
if( gethostname( szHost, sizeof(szHost) ) >= 0 )
{
pszHostName = (CHAR *)FTPD_ALLOC( strlen( szHost ) + 1 );
}
if( pszHostName == NULL )
{
APIERR err = GetLastError();
FtpdLogEvent( FTPD_EVENT_OUT_OF_MEMORY,
0,
NULL,
err );
FTPD_PRINT(( "cannot allocate memory for host name, error %lu\n",
err ));
return err;
}
strcpy( pszHostName, szHost );
//
// Determine the port number for FTP Connections.
//
pserv = getservbyname( "ftp", "tcp" );
if( pserv == NULL )
{
portFtpConnect = (PORT)htons( IPPORT_FTP );
FTPD_PRINT(( "cannot locate ftp connect port, assuming port %u\n",
ntohs( portFtpConnect ) ));
}
else
{
portFtpConnect = (PORT)pserv->s_port;
}
//
// Determine the port number for FTP Data.
//
pserv = getservbyname( "ftp-data", "tcp" );
if( pserv == NULL )
{
portFtpData = (PORT)htons( (u_short)( ntohs( portFtpConnect ) - 1 ) );
FTPD_PRINT(( "cannot locate ftp data port, assuming port %u\n",
ntohs( portFtpData ) ));
}
else
{
portFtpData = (PORT)pserv->s_port;
}
//
// Create connection socket. We do this now so we can
// abort this installation if we fail to create the socket.
//
serr = CreateFtpdSocket( &sConnect,
htonl( INADDR_ANY ),
portFtpConnect );
if( serr != 0 )
{
FtpdLogEvent( FTPD_EVENT_CANNOT_CREATE_CONNECTION_SOCKET,
0,
NULL,
serr );
FTPD_PRINT(( "cannot create connection socket, socket error %d\n",
serr ));
svcStatus.dwServiceSpecificExitCode = (DWORD)serr;
return ERROR_SERVICE_SPECIFIC_ERROR;
}
//
// Determine the sizes of the send & receive buffers.
//
// BUGBUG: This assumes that ALL sockets use the same
// size send & receive buffers. Verify this
// assumption with DavidTr.
//
cbOpt = sizeof(cbReceiveBuffer);
if( getsockopt( sConnect,
SOL_SOCKET,
SO_RCVBUF,
(CHAR *)&cbReceiveBuffer,
&cbOpt ) != 0 )
{
FTPD_PRINT(( "cannot get receive buffer size, using %u\n",
DEFAULT_BUFFER_SIZE ));
cbReceiveBuffer = DEFAULT_BUFFER_SIZE;
}
cbOpt = sizeof(cbSendBuffer);
if( getsockopt( sConnect,
SOL_SOCKET,
SO_SNDBUF,
(CHAR *)&cbSendBuffer,
&cbOpt ) != 0 )
{
FTPD_PRINT(( "cannot get send buffer size, using %u\n",
DEFAULT_BUFFER_SIZE ));
cbSendBuffer = DEFAULT_BUFFER_SIZE;
}
//
// Create the connection thread.
//
hConnectThread = CreateThread( NULL,
0,
&ConnectionThread,
NULL,
0,
&idConnectThread );
if( hConnectThread == NULL )
{
APIERR err = GetLastError();
FtpdLogEvent( FTPD_EVENT_CANNOT_CREATE_CONNECTION_THREAD,
0,
NULL,
err );
FTPD_PRINT(( "cannot create connection thread, error %d\n",
err ));
return err;
}
else
{
CloseHandle( hConnectThread );
}
//
// Success!
//
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "sockets initialized\n" ));
}
return NO_ERROR;
} // InitializeSockets
/*******************************************************************
NAME: TerminateSockets
SYNOPSIS: Terminate socket access. This routine is responsible
for closing the connection socket(s) and detaching
from WinSock.
NOTES: This routine may only be called by a single thread
of execution; it is not necessarily multi-thread safe.
HISTORY:
KeithMo 07-Mar-1993 Created.
********************************************************************/
VOID
TerminateSockets(
VOID
)
{
SOCKERR serr;
DWORD i;
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "terminating sockets\n" ));
}
//
// Close the connection socket.
//
if( sConnect != INVALID_SOCKET )
{
ResetSocket( sConnect );
sConnect = INVALID_SOCKET;
}
//
// Blow away any connected users.
//
DisconnectAllUsers();
//
// Wait for the users to die.
//
for( i = 0 ; ( i < CLEANUP_RETRY_COUNT ) && ( cConnectedUsers > 0 ) ; i++ )
{
Sleep( CLEANUP_POLL_INTERVAL );
}
//
// Free the local host name buffer.
//
if( pszHostName != NULL )
{
FTPD_FREE( pszHostName );
pszHostName = NULL;
}
//
// Disconnect from WinSock.
//
serr = WSACleanup();
if( serr != 0 )
{
FTPD_PRINT(( "cannot terminate WinSock, error %d\n",
serr ));
}
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "sockets terminated\n" ));
}
} // TerminateSockets
/*******************************************************************
NAME: CreateDataSocket
SYNOPSIS: Creates a data socket for the specified address & port.
ENTRY: psock - Will receive the new socket ID if successful.
addrLocal - The local Internet address for the socket
in network byte order.
portLocal - The local port for the socket in network
byte order.
addrRemote - The remote Internet address for the socket
in network byte order.
portRemote - The remote port for the socket in network
byte order.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 10-Mar-1993 Created.
KeithMo 07-Sep-1993 Enable SO_REUSEADDR.
********************************************************************/
SOCKERR
CreateDataSocket(
SOCKET * psock,
ULONG addrLocal,
PORT portLocal,
ULONG addrRemote,
PORT portRemote
)
{
SOCKET sNew = INVALID_SOCKET;
SOCKERR serr = 0;
SOCKADDR_IN sockAddr;
//
// Just to be paranoid...
//
FTPD_ASSERT( psock != NULL );
*psock = INVALID_SOCKET;
//
// Create the socket.
//
sNew = socket( PF_INET, SOCK_STREAM, 0 );
serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;
if( serr == 0 )
{
BOOL fReuseAddr = TRUE;
SOCKET_OK();
//
// Since we always bind to the same local port,
// allow the reuse of address/port pairs.
//
if( setsockopt( sNew,
SOL_SOCKET,
SO_REUSEADDR,
(CHAR *)&fReuseAddr,
sizeof(fReuseAddr) ) != 0 )
{
serr = WSAGetLastError();
}
}
else
{
SOCKET_FAIL();
}
if( serr == 0 )
{
//
// Bind the local internet address & port to the socket.
//
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = addrLocal;
sockAddr.sin_port = portLocal;
if( bind( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Connect to the remote internet address & port.
//
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = addrRemote;
sockAddr.sin_port = portRemote;
if( connect( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Success! Return the socket to the caller.
//
FTPD_ASSERT( sNew != INVALID_SOCKET );
*psock = sNew;
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "data socket %d connected from (%08lX,%04X) to (%08lX,%04X)\n",
sNew,
ntohl( addrLocal ),
ntohs( portLocal ),
ntohl( addrRemote ),
ntohs( portRemote ) ));
}
}
else
{
//
// Something fatal happened. Close the socket if
// managed to actually open it.
//
FTPD_PRINT(( "no data socket from (%08lX,%04X) to (%08lX, %04X), error %d\n",
ntohl( addrLocal ),
ntohs( portLocal ),
ntohl( addrRemote ),
ntohs( portRemote ),
serr ));
if( sNew != INVALID_SOCKET )
{
ResetSocket( sNew );
}
}
return serr;
} // CreateDataSocket
/*******************************************************************
NAME: CreateFtpdSocket
SYNOPSIS: Creates a new socket at the FTPD port.
ENTRY: psock - Will receive the new socket ID if successful.
addrLocal - The lcoal Internet address for the socket
in network byte order.
portLocal - The local port for the socket in network
byte order.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 08-Mar-1993 Created.
********************************************************************/
SOCKERR
CreateFtpdSocket(
SOCKET * psock,
ULONG addrLocal,
PORT portLocal
)
{
SOCKET sNew = INVALID_SOCKET;
SOCKERR serr = 0;
//
// Just to be paranoid...
//
FTPD_ASSERT( psock != NULL );
*psock = INVALID_SOCKET;
//
// Create the connection socket.
//
sNew = socket( PF_INET, SOCK_STREAM, 0 );
serr = ( sNew == INVALID_SOCKET ) ? WSAGetLastError() : 0;
if( serr == 0 )
{
BOOL fReuseAddr = FALSE;
SOCKET_OK();
//
// Muck around with the socket options a bit.
// Berkeley FTPD does this.
//
if( setsockopt( sNew,
SOL_SOCKET,
SO_REUSEADDR,
(CHAR *)&fReuseAddr,
sizeof(fReuseAddr) ) != 0 )
{
serr = WSAGetLastError();
}
}
else
{
SOCKET_FAIL();
}
if( serr == 0 )
{
SOCKADDR_IN sockAddr;
//
// Bind an address to the socket.
//
sockAddr.sin_family = AF_INET;
sockAddr.sin_addr.s_addr = addrLocal;
sockAddr.sin_port = portLocal;
if( bind( sNew, (SOCKADDR *)&sockAddr, sizeof(sockAddr) ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Put the socket into listen mode.
//
if( listen( sNew, nListenBacklog ) != 0 )
{
serr = WSAGetLastError();
}
}
if( serr == 0 )
{
//
// Success! Return the socket to the caller.
//
FTPD_ASSERT( sNew != INVALID_SOCKET );
*psock = sNew;
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "connection socket %d created at (%08lX,%04X)\n",
sNew,
ntohl( addrLocal ),
ntohs( portLocal ) ));
}
}
else
{
//
// Something fatal happened. Close the socket if
// managed to actually open it.
//
FTPD_PRINT(( "no connection socket at (%08lX, %04X), error %d\n",
ntohl( addrLocal ),
ntohs( portLocal ),
serr ));
if( sNew != INVALID_SOCKET )
{
ResetSocket( sNew );
}
}
return serr;
} // CreateFtpdSocket
/*******************************************************************
NAME: CloseSocket
SYNOPSIS: Closes the specified socket. This is just a thin
wrapper around the "real" closesocket() API.
ENTRY: sock - The socket to close.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 26-Apr-1993 Created.
********************************************************************/
SOCKERR
CloseSocket(
SOCKET sock
)
{
SOCKERR serr = 0;
//
// Close the socket.
//
if( closesocket( sock ) != 0 )
{
serr = WSAGetLastError();
}
if( serr == 0 )
{
CLOSE_OK();
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "closed socket %d\n",
sock ));
}
}
else
{
CLOSE_FAIL();
FTPD_PRINT(( "cannot close socket %d, error %d\n",
sock,
serr ));
}
return serr;
} // CloseSocket
/*******************************************************************
NAME: ResetSocket
SYNOPSIS: Performs a "hard" close on the given socket.
ENTRY: sock - The socket to close.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 08-Mar-1993 Created.
********************************************************************/
SOCKERR
ResetSocket(
SOCKET sock
)
{
SOCKERR serr = 0;
LINGER linger;
//
// Enable linger with a timeout of zero. This will
// force the hard close when we call closesocket().
//
// We ignore the error return from setsockopt. If it
// fails, we'll just try to close the socket anyway.
//
linger.l_onoff = TRUE;
linger.l_linger = 0;
setsockopt( sock,
SOL_SOCKET,
SO_LINGER,
(CHAR *)&linger,
sizeof(linger) );
//
// Close the socket.
//
if( closesocket( sock ) != 0 )
{
serr = WSAGetLastError();
}
if( serr == 0 )
{
CLOSE_OK();
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "reset socket %d\n",
sock ));
}
}
else
{
CLOSE_FAIL();
FTPD_PRINT(( "cannot reset socket %d, error %d\n",
sock,
serr ));
}
return serr;
} // ResetSocket
/*******************************************************************
NAME: AcceptSocket
SYNOPSIS: Waits for a connection to the specified socket.
The socket is assumed to be "listening".
ENTRY: sockListen - The socket to accept on.
psockNew - Will receive the newly "accepted" socket
if successful.
paddr - Will receive the client's network address.
fEnforceTimeout - If TRUE, this routine will enforce
the idle-client timeout. If FALSE, no timeouts
are enforced (and this routine may block
indefinitely).
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 27-Apr-1993 Created.
********************************************************************/
SOCKERR
AcceptSocket(
SOCKET sockListen,
SOCKET * psockNew,
LPSOCKADDR_IN paddr,
BOOL fEnforceTimeout
)
{
SOCKERR serr = 0;
SOCKET sockNew = INVALID_SOCKET;
FTPD_ASSERT( psockNew != NULL );
FTPD_ASSERT( paddr != NULL );
if( fEnforceTimeout )
{
//
// Timeouts are to be enforced, so wait for a connection
// to the socket.
//
serr = WaitForSocketRead( sockListen, INVALID_SOCKET, NULL );
}
if( serr == 0 )
{
INT cbAddr = sizeof(SOCKADDR_IN);
//
// Wait for the actual connection.
//
sockNew = accept( sockListen, (SOCKADDR *)paddr, &cbAddr );
if( sockNew == INVALID_SOCKET )
{
serr = WSAGetLastError();
ACCEPT_FAIL();
}
else
{
ACCEPT_OK();
}
}
//
// Return the (potentially invalid) socket to the caller.
//
*psockNew = sockNew;
return serr;
} // AcceptSocket
/*******************************************************************
NAME: SockSend
SYNOPSIS: Sends a block of bytes to a specified socket.
ENTRY: sock - The target socket.
pBuffer - Contains the data to send.
cbBuffer - The size (in bytes) of the buffer.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 13-Mar-1993 Created.
********************************************************************/
SOCKERR
SockSend(
SOCKET sock,
CHAR * pBuffer,
DWORD cbBuffer
)
{
SOCKERR serr = 0;
INT cbSent;
SOCKET sControl;
BOOL fExcept = FALSE;
DWORD dwBytesSent = 0;
USER_DATA * pUserData;
FTPD_ASSERT( pBuffer != NULL );
pUserData = UserDataPtr;
sControl = pUserData ? pUserData->sControl : INVALID_SOCKET;
//
// Loop until there's no more data to send.
//
while( cbBuffer > 0 )
{
//
// Wait for the socket to become writeable.
//
serr = WaitForSocketWrite( sock,
sControl,
&fExcept );
if( fExcept && ( serr == 0 ) )
{
//
// Out of band data has arrived. Discard it & abort.
//
serr = DiscardOutOfBandData( pUserData, sControl );
if( pUserData && TEST_UF( pUserData, TRANSFER ) )
{
SET_UF( pUserData, OOB_DATA );
break;
}
}
if( serr == 0 )
{
//
// Write a block to the socket.
//
cbSent = send( sock, pBuffer, (INT)cbBuffer, 0 );
if( cbSent < 0 )
{
//
// Socket error.
//
serr = WSAGetLastError();
}
else
{
dwBytesSent += (DWORD)cbSent;
IF_DEBUG( SEND )
{
if( pUserData && TEST_UF( pUserData, TRANSFER ) )
{
FTPD_PRINT(( "send %d bytes @%08lX to socket %d\n",
cbSent,
(ULONG)pBuffer,
sock ));
}
}
}
}
if( serr != 0 )
{
break;
}
pBuffer += cbSent;
cbBuffer -= (DWORD)cbSent;
}
if( serr != 0 )
{
IF_DEBUG( SEND )
{
FTPD_PRINT(( "socket error %d during send on socket %d\n",
serr,
sock ));
}
}
UPDATE_LARGE_COUNTER( TotalBytesSent, dwBytesSent );
return serr;
} // SockSend
/*******************************************************************
NAME: SockRecv
SYNOPSIS: Receives a block of bytes from a specified socket.
ENTRY: pUserData - The user initiating the request.
sock - The target socket.
pBuffer - Will receive the data.
cbBuffer - The size (in bytes) of the buffer.
pbReceived - Will receive the actual number of bytes
received. This value is undefined if this function
fails.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 13-Mar-1993 Created.
********************************************************************/
SOCKERR
SockRecv(
USER_DATA * pUserData,
SOCKET sock,
CHAR * pBuffer,
DWORD cbBuffer,
DWORD * pbReceived
)
{
SOCKERR serr = 0;
DWORD cbTotal = 0;
INT cbReceived;
SOCKET sControl;
BOOL fExcept = FALSE;
DWORD dwBytesRecv = 0;
FTPD_ASSERT( pBuffer != NULL );
FTPD_ASSERT( pbReceived != NULL );
//
// Loop until the buffer's full.
//
sControl = pUserData ? pUserData->sControl : INVALID_SOCKET;
while( cbBuffer > 0 )
{
//
// Wait for the socket to become readable.
//
serr = WaitForSocketRead( sock,
sControl,
&fExcept );
if( fExcept && ( serr == 0 ) )
{
//
// Out of band data has arrived. Discard it & abort.
//
serr = DiscardOutOfBandData( pUserData, sControl );
if( pUserData && TEST_UF( pUserData, TRANSFER ) )
{
SET_UF( pUserData, OOB_DATA );
break;
}
}
if( serr == 0 )
{
//
// Read a block from the socket.
//
cbReceived = recv( sock, pBuffer, (INT)cbBuffer, 0 );
if( cbReceived < 0 )
{
//
// Socket error.
//
serr = WSAGetLastError();
}
else
{
dwBytesRecv += (DWORD)cbReceived;
IF_DEBUG( RECV )
{
if( pUserData && TEST_UF( pUserData, TRANSFER ) )
{
FTPD_PRINT(( "received %d bytes @%08lX from socket %d\n",
cbReceived,
(ULONG)pBuffer,
sock ));
}
}
}
}
if( ( serr != 0 ) || ( cbReceived == 0 ) )
{
//
// End of file, socket closed, timeout, or socket error.
//
break;
}
pBuffer += cbReceived;
cbBuffer -= (DWORD)cbReceived;
cbTotal += (DWORD)cbReceived;
}
if( serr == 0 )
{
//
// Return total byte count to caller.
//
*pbReceived = cbTotal;
}
else
{
IF_DEBUG( RECV )
{
FTPD_PRINT(( "socket error %d during recv on socket %d\n",
serr,
sock ));
}
}
UPDATE_LARGE_COUNTER( TotalBytesReceived, dwBytesRecv );
return serr;
} // SockRecv
/*******************************************************************
NAME: SockPrintf2
SYNOPSIS: Send a formatted string to a specific socket.
ENTRY: sock - The target socket.
pszFormat - A printf-style format string.
... - Any other parameters needed by the format string.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 10-Mar-1993 Created.
********************************************************************/
SOCKERR
_CRTAPI2
SockPrintf2(
SOCKET sock,
CHAR * pszFormat,
...
)
{
va_list ArgPtr;
SOCKERR serr;
//
// Let the worker do the dirty work.
//
va_start( ArgPtr, pszFormat );
serr = vSockPrintf( sock,
pszFormat,
ArgPtr );
va_end( ArgPtr );
return serr;
} // SockPrintf2
/*******************************************************************
NAME: SockReply2
SYNOPSIS: Send an FTP reply to a specific socket.
ENTRY: sock - The target socket.
ReplyCode - One of the REPLY_* manifests.
pszFormat - A printf-style format string.
... - Any other parameters needed by the format string.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 09-Mar-1993 Created.
********************************************************************/
SOCKERR
_CRTAPI2
SockReply2(
SOCKET sock,
UINT ReplyCode,
CHAR * pszFormat,
...
)
{
va_list ArgPtr;
SOCKERR serr;
//
// Let the worker do the dirty work.
//
va_start( ArgPtr, pszFormat );
serr = vSockReply( sock,
ReplyCode,
' ',
pszFormat,
ArgPtr );
va_end( ArgPtr );
return serr;
} // SockReply2
/*******************************************************************
NAME: SockReplyFirst2
SYNOPSIS: Send the first reply of a multi-line FTP replay
to a specific socket.
ENTRY: sock - The target socket.
ReplyCode - One of the REPLY_* manifests.
pszFormat - A printf-style format string.
... - Any other parameters needed by the format string.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 03-Jun-1993 Created.
********************************************************************/
SOCKERR
_CRTAPI2
SockReplyFirst2(
SOCKET sock,
UINT ReplyCode,
CHAR * pszFormat,
...
)
{
va_list ArgPtr;
SOCKERR serr;
//
// Let the worker do the dirty work.
//
va_start( ArgPtr, pszFormat );
serr = vSockReply( sock,
ReplyCode,
'-',
pszFormat,
ArgPtr );
va_end( ArgPtr );
return serr;
} // SockReplyFirst2
/*******************************************************************
NAME: SockReadLine
SYNOPSIS: Reads a '\n' terminate line from the specified user's
control socket.
ENTRY: pUserData - The user initiating the request.
pszBuffer - Buffer to store line into.
cchBuffer - Maximum size of buffer.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 10-Mar-1993 Created.
********************************************************************/
SOCKERR
SockReadLine(
USER_DATA * pUserData,
CHAR * pszBuffer,
INT cchBuffer
)
{
SOCKERR serr = 0;
SOCKET sControl;
#if DBG
CHAR * pszTmp = pszBuffer;
#endif // DBG
FTPD_ASSERT( pUserData != NULL );
sControl = pUserData->sControl;
while( cchBuffer > 0 )
{
CHAR ch;
DWORD cbRead;
//
// Read a byte from the socket. This will return
// WSAETIMEDOUT if the connection is idle too long.
//
serr = SockRecv( pUserData, sControl, &ch, sizeof(ch), &cbRead );
if( ( cbRead == 0 ) && ( serr == 0 ) )
{
//
// End of file or socket closed.
//
serr = WSAENOTSOCK;
}
if( serr != 0 )
{
//
// Socket error.
//
break;
}
//
// Skip TELNET commands.
//
if( (UINT)ch >= (UINT)FIRST_TELNET_COMMAND )
{
continue;
}
//
// Filter out CR & LF.
//
if( ( ch != '\r' ) && ( ch != '\n' ) )
{
*pszBuffer++ = ch;
cchBuffer--;
}
//
// Terminate line at LF.
//
if( ch == '\n' )
{
break;
}
}
//
// Ensure line is properly terminated.
//
*pszBuffer = '\0';
#if DBG
IF_DEBUG( SOCKETS )
{
if( serr == 0 )
{
if( !_strnicmp( pszTmp, "pass", 4 ) )
{
pszTmp = "PASS {secret...}";
}
FTPD_PRINT(( "received '%s'\n",
pszTmp ));
}
}
#endif // DBG
return serr;
} // SockReadLine
/*******************************************************************
NAME: SendMultilineMessage2
SYNOPSIS: Send a multiline message to a specific socket.
ENTRY: sock - The target socket.
nReply - The reply code to use for the first line
of the multi-line message.
pszzMessage - The message to send. Each line should be
terminated by a NULL, and the entire message should
be terminated by a double NULL.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 03-Jun-1993 Created.
********************************************************************/
SOCKERR
SendMultilineMessage2(
SOCKET sock,
UINT nReply,
CHAR * pszzMessage
)
{
SOCKERR serr = 0;
if( pszzMessage != NULL )
{
CHAR * pszNext = pszzMessage;
BOOL fFirst = TRUE;
while( ( serr == 0 ) && ( *pszNext != '\0' ) )
{
if( fFirst )
{
serr = SockReplyFirst2( sock,
nReply,
"%s",
pszNext );
fFirst = FALSE;
}
else
{
serr = SockPrintf2( sock,
" %s",
pszNext );
}
pszNext += strlen(pszNext) + 1;
}
}
return serr;
} // SendMultilineMessage2
//
// Private functions.
//
/*******************************************************************
NAME: vSockPrintf
SYNOPSIS: Worker function for printf-to-socket functions.
ENTRY: sock - The target socket.
pszFormat - The format string.
args - Variable number of arguments.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 17-Mar-1993 Created.
********************************************************************/
SOCKERR
vSockPrintf(
SOCKET sock,
CHAR * pszFormat,
va_list args
)
{
INT cchBuffer;
INT bufLength;
SOCKERR serr = 0;
CHAR szBuffer[MAX_REPLY_LENGTH];
FTPD_ASSERT( pszFormat != NULL );
//
// Render the format into our local buffer.
//
bufLength = sizeof(szBuffer) - 3; // -3 for "\r\n\0"
cchBuffer = _vsnprintf( szBuffer,
bufLength,
pszFormat,
args );
if( cchBuffer == -1 )
{
cchBuffer = bufLength;
szBuffer[cchBuffer] = '\0';
}
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "sending '%s'\n",
szBuffer ));
}
strcat( szBuffer, "\r\n" );
cchBuffer += 2;
//
// Blast it out to the client.
//
serr = SockSend( sock, szBuffer, cchBuffer );
return serr;
} // vSockPrintf
/*******************************************************************
NAME: vSockReply
SYNOPSIS: Worker function for reply functions.
ENTRY: sock - The target socket.
ReplyCode - A three digit reply code from RFC 959.
chSeparator - Should be either ' ' (normal reply) or
'-' (first line of multi-line reply).
pszFormat - The format string.
args - Variable number of arguments.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 17-Mar-1993 Created.
********************************************************************/
SOCKERR
vSockReply(
SOCKET sock,
UINT ReplyCode,
CHAR chSeparator,
CHAR * pszFormat,
va_list args
)
{
INT cchBuffer;
INT cch2;
INT bufLength;
SOCKERR serr = 0;
CHAR szBuffer[MAX_REPLY_LENGTH];
FTPD_ASSERT( ( ReplyCode >= 100 ) && ( ReplyCode < 600 ) );
//
// Render the format into our local buffer.
//
cchBuffer = sprintf( szBuffer,
"%u%c",
ReplyCode,
chSeparator );
bufLength = sizeof(szBuffer) - cchBuffer - 3; // -3 for "\r\n\0"
cch2 = _vsnprintf( szBuffer + cchBuffer,
bufLength,
pszFormat,
args );
if( cch2 == -1 )
{
cchBuffer += bufLength;
szBuffer[cchBuffer] = '\0';
}
else
{
cchBuffer += cch2;
}
IF_DEBUG( SOCKETS )
{
FTPD_PRINT(( "sending '%s'\n",
szBuffer ));
}
strcat( szBuffer, "\r\n" );
cchBuffer += 2;
//
// Blast it out to the client.
//
serr = SockSend( sock, szBuffer, cchBuffer );
return serr;
} // vSockReply
/*******************************************************************
NAME: WaitForSocketRead
SYNOPSIS: Blocks until either a) there is data to read from
the specified socket, b) there is an error on
the specified socket, or c) timeout.
ENTRY: sockRead - The socket to check for readability.
sockExcept - The socket to check for exceptions.
May be INVALID_SOCKET if not interested in
exceptions.
pfExcept - Will receive TRUE if an exception occurred.
May be NULL if sockExcept == INVALID_SOCKET.
RETURNS: SOCKERR - 0 if successful, !0 if not. Will return
WSAETIMEDOUT if the timeout period expired.
HISTORY:
KeithMo 17-Mar-1993 Created.
********************************************************************/
SOCKERR
WaitForSocketRead(
SOCKET sockRead,
SOCKET sockExcept,
BOOL * pfExcept
)
{
SOCKERR serr;
BOOL fRead;
//
// Let the worker function do the dirty work.
//
serr = WaitForSocketWorker( sockRead,
INVALID_SOCKET,
sockExcept,
&fRead,
NULL,
pfExcept );
if( serr == 0 )
{
FTPD_ASSERT( fRead || *pfExcept );
}
return serr;
} // WaitForSocketRead
/*******************************************************************
NAME: WaitForSocketWrite
SYNOPSIS: Blocks until either a) data can be written to the
specified socket, b) there is an error on the
specified socket, or c) timeout.
ENTRY: sockWrite - The socket to check for writeability.
sockExcept - The socket to check for exceptions.
pfExcept - Will receive TRUE if an exception occurred.
RETURNS: SOCKERR - 0 if successful, !0 if not. Will return
WSAETIMEDOUT if the timeout period expired.
HISTORY:
KeithMo 17-Mar-1993 Created.
********************************************************************/
SOCKERR
WaitForSocketWrite(
SOCKET sockWrite,
SOCKET sockExcept,
BOOL * pfExcept
)
{
SOCKERR serr;
BOOL fWrite;
//
// Let the worker function do the dirty work.
//
serr = WaitForSocketWorker( INVALID_SOCKET,
sockWrite,
sockExcept,
NULL,
&fWrite,
pfExcept );
if( serr == 0 )
{
FTPD_ASSERT( fWrite || *pfExcept );
}
return serr;
} // WaitForSocketWrite
/*******************************************************************
NAME: WaitForSocketWorker
SYNOPSIS: Worker for WaitForSocketRead and WaitForSocketWrite
functions. Will block until any of the specified
sockets acheive the specified states, a timeout, or
an error occurs.
ENTRY: sockRead - The socket to check for readability.
sockWrite - The socket to check for writeability.
sockExcept - The socket to check for exceptions.
pfRead - Will receive TRUE if sockRead is readable.
pfWrite - Will receive TRUE if sockWrite is writeable.
pfExcept - Will receive TRUE if an exceptional condition
occurred on sockExcept.
RETURNS: SOCKERR - 0 if successful, !0 if not. Will return
WSAETIMEDOUT if the timeout period expired.
NOTES: Any (but not all) sockets may be INVALID_SOCKET. For
each socket that is INVALID_SOCKET, the corresponding
pf* parameter may be NULL.
HISTORY:
KeithMo 06-May-1993 Created.
********************************************************************/
SOCKERR
WaitForSocketWorker(
SOCKET sockRead,
SOCKET sockWrite,
SOCKET sockExcept,
BOOL * pfRead,
BOOL * pfWrite,
BOOL * pfExcept
)
{
SOCKERR serr = 0;
TIMEVAL timeout;
TIMEVAL * ptimeout;
fd_set fdsRead;
fd_set fdsWrite;
fd_set fdsExcept;
INT res;
//
// Ensure we got valid parameters.
//
if( ( sockRead == INVALID_SOCKET ) &&
( sockWrite == INVALID_SOCKET ) &&
( sockExcept == INVALID_SOCKET ) )
{
return WSAENOTSOCK;
}
if( nConnectionTimeout == 0 )
{
//
// If nConnectionTimeout == 0, then we have no timeout.
// So, we block and wait for the specified conditions.
//
ptimeout = NULL;
}
else
{
//
// nConnectionTimeout > 0, so setup the timeout structure.
//
timeout.tv_sec = (LONG)nConnectionTimeout;
timeout.tv_usec = 0;
ptimeout = &timeout;
}
for( ; ; )
{
//
// Setup our socket sets.
//
FD_ZERO( &fdsRead );
FD_ZERO( &fdsWrite );
FD_ZERO( &fdsExcept );
if( sockRead != INVALID_SOCKET )
{
FD_SET( sockRead, &fdsRead );
FTPD_ASSERT( pfRead != NULL );
*pfRead = FALSE;
}
if( sockWrite != INVALID_SOCKET )
{
FD_SET( sockWrite, &fdsWrite );
FTPD_ASSERT( pfWrite != NULL );
*pfWrite = FALSE;
}
if( sockExcept != INVALID_SOCKET )
{
FD_SET( sockExcept, &fdsExcept );
FTPD_ASSERT( pfExcept != NULL );
*pfExcept = FALSE;
}
//
// Wait for one of the conditions to be met.
//
res = select( 0, &fdsRead, &fdsWrite, &fdsExcept, ptimeout );
if( res == 0 )
{
//
// Timeout.
//
serr = WSAETIMEDOUT;
break;
}
else
if( res == SOCKET_ERROR )
{
//
// Bad news.
//
serr = WSAGetLastError();
break;
}
else
{
BOOL fSomethingWasSet = FALSE;
if( pfRead != NULL )
{
*pfRead = FD_ISSET( sockRead, &fdsRead );
fSomethingWasSet = TRUE;
}
if( pfWrite != NULL )
{
*pfWrite = FD_ISSET( sockWrite, &fdsWrite );
fSomethingWasSet = TRUE;
}
if( pfExcept != NULL )
{
*pfExcept = FD_ISSET( sockExcept, &fdsExcept );
fSomethingWasSet = TRUE;
}
if( fSomethingWasSet )
{
//
// Success.
//
serr = 0;
break;
}
else
{
//
// select() returned with neither a timeout, nor
// an error, nor any bits set. This feels bad...
//
FTPD_ASSERT( FALSE );
continue;
}
}
}
return serr;
} // WaitForSocketWorker
/*******************************************************************
NAME: DiscardOutOfBandData
SYNOPSIS: Reads & discards any out of band data pending on
the specified socket.
ENTRY: pUserData - The user initiating the request.
sock - The target socket.
RETURNS: SOCKERR - 0 if successful, !0 if not.
HISTORY:
KeithMo 06-May-1993 Created.
********************************************************************/
SOCKERR
DiscardOutOfBandData(
USER_DATA * pUserData,
SOCKET sock
)
{
SOCKERR serr = 0;
CHAR * pszAbort = "ABOR\r\n";
CHAR * pszNext = pszAbort;
#if DBG
DWORD cbDiscarded = 0;
#endif // DBG
for( ; ; )
{
TIMEVAL tv;
FD_SET fds;
INT res;
CHAR chTrash;
//
// Setup for select() call. Setting the timeout value
// to zero will "poll" the socket for any outstanding
// conditions.
//
FD_ZERO( &fds );
FD_SET( sock, &fds );
tv.tv_sec = 0;
tv.tv_usec = 0;
//
// Check for exceptional conditions.
//
res = select( 0, NULL, NULL, &fds, &tv );
if( res == 0 )
{
//
// No exceptions, ergo no OOB data pending.
//
serr = 0;
break;
}
if( res == SOCKET_ERROR )
{
//
// Bad news.
//
serr = WSAGetLastError();
break;
}
//
// Read the out of band data.
//
res = recv( sock, &chTrash, sizeof(chTrash), MSG_OOB );
if( res == 0 )
{
//
// Connection closed.
//
serr = 0;
break;
}
if( res == SOCKET_ERROR )
{
//
// Bad news.
//
serr = WSAGetLastError();
break;
}
if( pUserData && ( res > 0 ) )
{
if( chTrash != *pszNext )
{
pszNext = pszAbort;
}
if( chTrash == *pszNext )
{
pszNext++;
if( *pszNext == '\0' )
{
SET_UF( pUserData, OOB_ABORT) ;
pszNext = pszAbort;
}
}
}
#if DBG
cbDiscarded += (DWORD)res;
#endif // DBG
}
IF_DEBUG( SOCKETS )
{
if( serr == 0 )
{
FTPD_PRINT(( "discarded %lu bytes of out of band data\n",
cbDiscarded ));
}
else
{
FTPD_PRINT(( "cannot discard out of band data, socket error %d\n",
serr ));
}
}
return serr;
} // DiscardOutOfBandData