2025-04-27 07:49:33 -04:00

931 lines
22 KiB
C++

//
// Command line HTTP client application
//
extern "C" {
#include <windows.h>
#include <winsock2.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <nspapi.h>
#include <svcguid.h>
#include <inetcom.h>
#include <svcloc.h>
} // extern "C"
//
// Converts a value between zero and fifteen to the appropriate hex digit
//
#define HEXDIGIT( nDigit ) \
(TCHAR)((nDigit) > 9 ? \
(nDigit) - 10 + 'A' \
: (nDigit) + '0')
//
// Converts a single hex digit to its decimal equivalent
//
#define TOHEX( ch ) \
((ch) > '9' ? \
(ch) >= 'a' ? \
(ch) - 'a' + 10 : \
(ch) - 'A' + 10 \
: (ch) - '0')
#define W3SVC_GUID { 0x585908c0, 0x6305, 0x11ce, { 0xae,0x00,0,0xaa,0,0x4a,0x38,0xb9 } }
CHAR * SkipWhite( CHAR * pch );
#define ISWHITE( ch ) ((ch) == ' ' || (ch) == '\t' || (ch) == '\r')
DWORD PrintHeaders( BOOL * pfAllHeaders );
BOOL ReadRequest( VOID );
BOOL SendRequest( VOID );
BOOL ReadReply( VOID );
void PrintUsage( void );
BOOL FillAddr( PSOCKADDR psin,
BOOL bConnect);
CHAR * SkipComments( CHAR * pch );
CHAR * BuildNextRequest( VOID );
BOOL AddAuthorizeHeaders( CHAR * pch );
VOID DumpAuthBuffer( VOID * pbuff, char * pchSrc );
#define HTTPCMD_VERSION "1.1"
#define HTTP_REQ_TERM "$END"
char buff[65535];
int cbRequest;
char * pchCurrentReq; // Current location of request
SOCKET sock = INVALID_SOCKET;
int portno = 80; // HTTP port number
char * pchServer; // Server to make the request to
char * pchInputFile; // Input file to read from
char * pchUserName = NULL; // Username to user for authentication
char * pchPassword = NULL; // Password to use for authentication
BOOL fKeepConnection = FALSE; // Pass "Pragma: Keep-connection" ?
BOOL fEchoSends = FALSE; // Print send requests to stdout?
BOOL fDumpAuth = FALSE; // Dump raw authorization data
BOOL fDontEchoHeaders = FALSE;// Don't print received headers
BOOL fHexTranslation = FALSE; // Simulate for encryption filters
BOOL fReadHeaders = FALSE; // On translated reply, have we read HTTP headers?
BOOL fProxyRequest = FALSE; // Is this a proxy request?
DWORD cbContentLength = 0xffffffff; // Number of bytes in server reply
char achAuth[256]; // Authentication schemes to support
char * pchAuth = achAuth; // Current position in achAuth
char bufftmp[65535]; // Temporary buffer
#if TCP_AUTHENT
TCP_AUTHENT * pTcpAuth = NULL; // NT Authentication class
#endif
char achAuthData[512]; // Server response authentication blob
BOOL fLoggedOn = FALSE; // Has the server accepted our request?
BOOL fPipelined = FALSE;
//
// Default field names for authorization
//
char * pchAuthSendHeader = "Authorization: ";
char * pchAuthRecvHeader = "WWW-Authenticate: ";
int __cdecl
main( int argc, char * argv[], char * envp[] )
{
CHAR * pch;
if ( argc < 3 )
{
PrintUsage();
return 0;
}
pchServer = argv[1];
pchInputFile = argv[2];
memset( achAuth, 0, sizeof( achAuth ));
for ( int i = 4; i < (argc + 1); i++ )
{
switch ( *(pch = (argv[i-1] + 1) ))
{
case 'k':
case 'K':
fKeepConnection = TRUE;
break;
case 'x':
case 'X':
fPipelined = TRUE;
break;
case 'e':
case 'E':
fEchoSends = TRUE;
break;
case 'd':
case 'D':
fDumpAuth = TRUE;
break;
//
// -u:username:password
//
case 'u':
case 'U':
if ( *(++pch) != ':' )
{
PrintUsage();
return 0;
}
pch++;
pchUserName = pch;
if ( pch = strchr( pch, ':' ) )
{
*pch = '\0';
pchPassword = pch+1;
}
else
{
pchPassword = "";
}
break;
//
// Authentication schemes
//
// -a:NTLM -a:Basic
//
case 'a':
case 'A':
if ( *(++pch) != ':' )
{
PrintUsage();
return 0;
}
pch++;
strcpy( pchAuth, pch );
pchAuth += strlen( pch ) + 1;
break;
//
// translation schemes
//
case 't':
case 'T':
//
// Assume hex conversion for time being
//
fHexTranslation = TRUE;
break;
case 'H':
case 'h':
fDontEchoHeaders = TRUE;
break;
case 'P':
case 'p':
pchAuthSendHeader = "Proxy-Authorization: ";
pchAuthRecvHeader = "Proxy-Authenticate: ";
break;
}
}
if ( !ReadRequest() )
return -1;
while ( TRUE )
{
if ( !SendRequest() ||
!ReadReply() )
{
break;
}
}
return 0;
}
//
// ReadRequest - Reads stdin for an HTTP request
//
BOOL ReadRequest( void )
{
int i;
char * pch;
FILE *stream;
cbRequest = 0;
if( (stream = fopen( pchInputFile, "rb" )) != NULL )
cbRequest = fread( buff, 1, sizeof(buff), stream );
if ( !cbRequest )
{
fprintf( stderr, "failed to read anything from %s, quitting\n", pchInputFile);
return FALSE;
}
buff[cbRequest] = '\0';
pchCurrentReq = SkipComments( buff );
return TRUE;
}
//
// SendRequest - Sends the request to the specified server
//
BOOL SendRequest( VOID )
{
WSADATA WSAData;
int status;
SOCKADDR dest_sin;
if ( sock == INVALID_SOCKET )
{
if ((status = WSAStartup(MAKEWORD(1,1), &WSAData)) != 0)
{
fprintf( stderr, "WSAStartup failed, status = %d\n", status);
return FALSE;
}
sock = socket( AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
fprintf( stderr, "socket() failed, error %u\n", GetLastError());
return FALSE;
}
//
// Retrieve the IP address and TCP Port number
// Global variable szBuff contains the remote host name.
//
if (!FillAddr( &dest_sin, TRUE))
{
closesocket( sock );
return FALSE;
}
if ( connect( sock, (PSOCKADDR) &dest_sin, sizeof( dest_sin)) < 0)
{
fprintf( stderr, "connect() failed, error %d", WSAGetLastError());
closesocket( sock );
return FALSE;
}
}
CHAR * pch = BuildNextRequest();
if ( !pch )
return FALSE;
int cb = strlen( pch );
if ( fEchoSends )
{
printf( pch );
printf("==========================End=of=Request=====================================\n");
}
if ( send( sock, pch, cb, 0 ) != cb )
{
closesocket( sock );
fprintf( stderr, "send() failed, error %d", WSAGetLastError());
return FALSE;
}
return TRUE;
}
//
// ReadReply - Read's the server's reply and outputs it to stdout
//
BOOL ReadReply( VOID )
{
int cb;
DWORD cbTotal = 0;
BOOL fReadAny = FALSE;
CHAR * pchAuthField;
while ( (cb = recv( sock, bufftmp, sizeof(bufftmp), 0 )) > 0 )
{
DWORD cbHeaders = 0;
CHAR * pch;
CHAR * pchtmp;
fReadAny = TRUE;
bufftmp[cb] = '\0';
//
// Are we expecting a translated reply?
//
if ( fHexTranslation )
{
BOOL fAllHeaders;
//
// Print out the special HTTP headers
//
if ( !fReadHeaders )
{
//
// We currently assume we'll always get all of the
// HEX-HTTP headers
//
cbHeaders = PrintHeaders( &fAllHeaders );
}
//
// Now translate the data in place
//
pch = bufftmp + cbHeaders;
pchtmp = bufftmp;
while ( *pch )
{
*pchtmp = TOHEX( *pch ) * 16 + TOHEX( pch[1]);
pchtmp++;
pch += 2;
}
*(pchtmp) = '\0';
cb = pchtmp - bufftmp;
}
//
// Print out the regular HTTP headers if we haven't already
//
if ( !fReadHeaders )
{
cbHeaders = PrintHeaders( &fReadHeaders );
}
cbTotal += cb - cbHeaders;
fwrite( bufftmp + cbHeaders, 1, cb - cbHeaders, stdout );
//
// Get the content length field
//
if ( pch = strstr( bufftmp, "Content-Length:" ))
{
pch = strchr( pch, ':' );
pch++;
while ( *pch == ' ' )
pch++;
cbContentLength = (DWORD) atoi( pch );
}
//
// Did we successfully get the response?
//
if ( !fLoggedOn && strstr( bufftmp, " 200 OK\r\n" ))
{
fLoggedOn = TRUE;
}
//
// Were we denied access from a new response?
//
if ( fLoggedOn && strstr( bufftmp, " 401 Access Denied" ))
{
fLoggedOn = FALSE;
}
//
// Need to grab the first WWW-Authenticate headers and grab the server
// authentication response blob
//
pch = bufftmp;
while ( pch = strstr( pch, pchAuthRecvHeader ) )
{
//
// Skip the field name
//
CHAR * pchtmp = strchr( pch, ':');
pchtmp++;
//
// Get the scheme name (and ignore it)
//
int i = 0;
while ( *pchtmp == ' ' ) pchtmp++;
while ( *pchtmp != ' ' &&
*pchtmp )
{
//achScheme[i++] = *pchtmp;
pchtmp++;
}
//
// Note we copy a bunch of extra stuff here, uuencode stops
// after the first non-printable character
//
strncpy( achAuthData, pchtmp, sizeof( achAuthData ) );
achAuthData[sizeof(achAuthData)-1] = '\0';
//
// We only handle one authentication scheme
//
goto Exit;
}
if ( cbTotal >= cbContentLength )
{
if (!fPipelined)
{
break;
}
}
}
Exit:
if ( !fReadAny )
return FALSE;
if ( fEchoSends )
printf("\n==========================End=of=Reply=====================================\n");
return TRUE;
}
CHAR * BuildNextRequest( VOID )
{
//
// Are we done?
//
if ( !pchCurrentReq || !*pchCurrentReq )
return NULL;
//
// Expect a large chunk of data unless we here otherwise
//
cbContentLength = (DWORD) -1;
//
// If this isn't the first request, skip the HTTP terminator
//
if ( !strncmp( pchCurrentReq, HTTP_REQ_TERM, sizeof( HTTP_REQ_TERM ) - 1))
{
pchCurrentReq += sizeof(HTTP_REQ_TERM) + 1;
}
pchCurrentReq = SkipComments( pchCurrentReq );
//
// Look for the request terminator or end of file
//
CHAR * pchEnd = strstr( pchCurrentReq, HTTP_REQ_TERM );
UINT cb;
char ch;
if ( !pchEnd )
{
cb = strlen( pchCurrentReq );
}
else
{
ch = *pchEnd;
*pchEnd = '\0';
cb = pchEnd - pchCurrentReq;
}
//
// Watch for the case where the last request ends in $END
//
if ( !cb )
return FALSE;
CHAR * pch = pchCurrentReq;
//
// Copy the first line
//
pch = strchr( pch, '\n' );
strncpy( bufftmp, pchCurrentReq, pch - pchCurrentReq + 1);
bufftmp[pch - pchCurrentReq + 1] = '\0';
pch++;
//
// Add any optional lines (authentication, pragmas etc)
//
if ( fKeepConnection )
{
strcat( bufftmp, "Connection: keep-alive\r\n");
}
if ( !fLoggedOn )
{
if ( !AddAuthorizeHeaders( bufftmp ) )
return NULL;
}
//
// Append the rest of the request
//
strcat( bufftmp, pch );
//
// Do any translations on the outgoing data
//
#define HEX_HTTP_HEADER "GET * HEX-HTTP/1.0\r\n\r\n"
if ( fHexTranslation )
{
DWORD cbHdr = strlen( bufftmp );
bufftmp[cbHdr*2] = '\0';
while ( cbHdr > 0 )
{
CHAR ch = bufftmp[cbHdr-1];
bufftmp[(cbHdr-1)*2 + 1] = HEXDIGIT( ch % 16 );
ch /= 16;
bufftmp[(cbHdr-1)*2] = HEXDIGIT( ch % 16 );
cbHdr--;
}
//
// Copy the new HTTP header
//
memmove( bufftmp + sizeof(HEX_HTTP_HEADER) - 1,
bufftmp,
strlen(bufftmp) );
memcpy( bufftmp,
HEX_HTTP_HEADER,
sizeof(HEX_HTTP_HEADER) - 1 );
}
//
// Set up for the next request
//
if ( pchEnd )
*pchEnd = ch;
pchCurrentReq = pchEnd;
return bufftmp;
}
BOOL AddAuthorizeHeaders( CHAR * pch )
{
#ifdef TCP_AUTH
CHAR * pchSchemes = achAuth;
BUFFER buff;
CHAR achUserAndPass[256];
DWORD cbOut;
BOOL fNeedMoreData;
while ( *pchSchemes )
{
if ( !_stricmp( pchSchemes, "Basic" ))
{
strcpy( achUserAndPass, pchUserName );
strcat( achUserAndPass, ":" );
strcat( achUserAndPass, pchPassword );
uuencode( (BYTE *) achUserAndPass,
strlen( achUserAndPass ),
&buff );
strcat( pch, pchAuthSendHeader );
strcat( pch, "Basic " );
strcat( pch, achUserAndPass ),
strcat( pch, "\r\n");
}
else
{
if ( !pTcpAuth )
{
pTcpAuth = new TCP_AUTHENT( TCPAUTH_CLIENT | TCPAUTH_UUENCODE );
}
else
{
if ( fDumpAuth )
DumpAuthBuffer( achAuthData, "Server" );
}
//
// Assume this is an NT authentication scheme
//
if ( !pTcpAuth->Converse( (void *) achAuthData,
0,
&buff,
&cbOut,
&fNeedMoreData,
pchSchemes,
pchUserName,
pchPassword ))
{
printf("SSP conversation failed, error %x, scheme %s\n",
GetLastError(),
pchSchemes );
return FALSE;
}
if ( fDumpAuth )
{
DumpAuthBuffer( buff.QueryPtr(), "Client" );
}
strcat( pch, pchAuthSendHeader );
strcat( pch, pchSchemes );
strcat( pch, " " );
strcat( pch, (CHAR *) buff.QueryPtr() );
strcat( pch, "\r\n" );
}
pchSchemes += strlen(pchSchemes) + 1;
}
#endif
return TRUE;
}
//
// PrintUsage - Usage information
//
void PrintUsage( void )
{
printf("\n\nhttpcmd version %s - Command line HTTP client\n", HTTPCMD_VERSION );
printf("usage: httpcmd server input.file [options] \n\n");
printf("Where input.file contains a full HTTP request\n");
printf("The server's reply is sent to stdout");
printf("Options:\n"
" -k - Send Pragma: Keep-connection, input.file contains multiple requests\n"
" seperated by " HTTP_REQ_TERM " on a blank line\n"
"\n"
" -u:username:password - Use the specified user name and password for\n"
" authentication purposes\n"
"\n"
" -a:<authentication scheme>, can be \"Basic\", \"NTLM\" or \"MS-KERBEROS\"\n"
" Only one NT authentication scheme is currently supported\n"
"\n"
" -e - Echo send requests to stdout. Shows what's being requested\n"
"\n"
" -t - Do HEX-HTTP for filter testing\n"
"\n"
" -h - Don't echo HTTP headers\n"
"\n");
}
BOOL FillAddr( PSOCKADDR psin,
BOOL bConnect)
{
DWORD dwSize;
char szTemp[200];
int protocols[2];
BYTE buffer[2048];
DWORD bufferSize;
char aliasBuffer[512];
DWORD aliasBufferSize;
int count;
PCSADDR_INFO csaddrInfo;
GUID TcpGuid = SVCID_HOSTNAME;
GUID *pGuid;
bufferSize = sizeof(buffer);
aliasBufferSize = sizeof(aliasBuffer);
LPINET_SERVER_INFO pInetServerInfo;
DWORD err;
/*
* If we are setting up for a listen() call (bConnect = FALSE),
* fill servent with our address.
*/
if (!bConnect) {
/*
* Retrieve my ip address. Assuming the hosts file in
* in %systemroot%/system/drivers/etc/hosts contains my computer name.
*/
dwSize = sizeof(szTemp);
GetComputerName(szTemp, &dwSize);
}
else
{
strcpy( szTemp, pchServer );
}
protocols[0] = IPPROTO_TCP;
pGuid = &TcpGuid;
protocols[1] = 0;
count = GetAddressByName(
NS_DEFAULT,
pGuid,
szTemp,
protocols,
0,
NULL,
buffer,
&bufferSize,
aliasBuffer,
&aliasBufferSize
);
if (count <= 0) {
fprintf( stderr,
"GetAddressByName for machine %s failed with error %d.",
szTemp,
GetLastError());
return FALSE;
}
csaddrInfo = (PCSADDR_INFO)buffer;
memcpy((char FAR *)psin,
(char FAR *)(csaddrInfo->RemoteAddr.lpSockaddr),
csaddrInfo->RemoteAddr.iSockaddrLength);
((PSOCKADDR_IN) psin)->sin_port = htons(portno);
return TRUE;
}
//
// Skip comments. Comments (marked with "//") must be the first thing
// in the file and must be at the beginning of the line
//
CHAR * SkipComments( CHAR * pch )
{
while ( TRUE )
{
if ( *pch == '/' )
{
pch = strchr( pch, '\n' );
pch++;
}
else
{
break;
}
}
return pch;
}
#if TCP_AUTH
VOID DumpAuthBuffer( VOID * pbuff, char * pchSrc )
{
#define NUM_CHARS 16
BUFFER buftmp;
DWORD cb;
DWORD i, limit;
CHAR TextBuffer[NUM_CHARS + 1];
LPBYTE BufferPtr;
if ( !pbuff )
{
printf("No Authorization return buffer\n");
return;
}
TCP_REQUIRE( uudecode( (char *) pbuff,
&buftmp,
&cb ));
BufferPtr = (LPBYTE) buftmp.QueryPtr();
printf("%s: Authorization data - %d bytes:\n", pchSrc, cb );
//
// Hex dump of the bytes
//
limit = ((cb - 1) / NUM_CHARS + 1) * NUM_CHARS;
for (i = 0; i < limit; i++) {
if (i < cb) {
printf("%02x ", BufferPtr[i]);
if (BufferPtr[i] < 31 ) {
TextBuffer[i % NUM_CHARS] = '.';
} else if (BufferPtr[i] == '\0') {
TextBuffer[i % NUM_CHARS] = ' ';
} else {
TextBuffer[i % NUM_CHARS] = (CHAR) BufferPtr[i];
}
} else {
printf(" ");
TextBuffer[i % NUM_CHARS] = ' ';
}
if ((i + 1) % NUM_CHARS == 0) {
TextBuffer[NUM_CHARS] = 0;
printf(" %s\n", TextBuffer);
}
}
}
#endif
DWORD PrintHeaders( BOOL * pfAllHeaders )
{
CHAR * pch;
DWORD cbHeaders;
//
// Find the end of the headers
//
pch = bufftmp;
while ( pch = strchr( pch, '\n' ))
{
if ( *(pch = SkipWhite( pch + 1)) == '\n' )
break;
}
if ( pch )
{
pch++;
cbHeaders = pch - bufftmp;
*pfAllHeaders = TRUE;
}
else
{
//
// We don't have all of the headers yet
//
cbHeaders = strlen( bufftmp );
*pfAllHeaders = FALSE;
}
if ( !fDontEchoHeaders )
fwrite( bufftmp, 1, cbHeaders, stdout );
return cbHeaders;
}
CHAR * SkipWhite( CHAR * pch )
{
while ( ISWHITE( *pch ) )
{
pch++;
}
return pch;
}