openmohaa/code/gamespy/Chat/chatSocket.c

1033 lines
20 KiB
C
Raw Normal View History

2023-02-04 21:00:01 +01:00
/*
GameSpy Chat SDK
Dan "Mr. Pants" Schoenblum
dan@gamespy.com
Copyright 1999-2007 GameSpy Industries, Inc
devsupport@gamespy.com
*/
/*************
** INCLUDES **
*************/
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#include "chatMain.h"
#include "chatSocket.h"
#if defined(_WIN32)
// Silence the "conditional expression is constant" on the FD_SET macros
#pragma warning(disable:4127)
#endif
/************
** DEFINES **
************/
#define BUFFER_INC 8192
#define RECV_LEN 4096
/***********
** MACROS **
***********/
#define ASSERT_SOCK(sock) {\
assert((sock) != NULL);\
assert(((sock)->connectState == ciNotConnected) ||\
((sock)->connectState == ciConnected) ||\
((sock)->connectState == ciDisconnected));\
ASSERT_BUFFER(&(sock)->inputQueue);\
ASSERT_BUFFER(&(sock)->outputQueue);\
}
#define ASSERT_CONNECTED(sock) assert((sock)->connectState == ciConnected)
#define ASSERT_BUFFER(buffer) {\
assert((buffer) != NULL);\
assert((buffer)->size >= 0);\
assert(((buffer)->size % BUFFER_INC) == 0);\
assert((buffer)->length >= 0);\
assert((buffer)->length <= (buffer)->size);\
}
#define RESET(ptr) {if (ptr) {gsifree(ptr); ptr = NULL;} }
/*********
** TIME **
*********/
#ifdef IRC_LOG
static const char * ciGetTime(void)
{
#if defined(UNDER_CE) || defined(_PS2) || defined(_NITRO)
return "";
#else
static char buffer[256];
time_t timer;
struct tm * now;
timer = time(NULL);
now = localtime(&timer);
if(now) // fixes the date > 2060 crash (23apr03/bgw)
{
if(now->tm_year > 99)
now->tm_year -= 100;
sprintf(buffer, "%02d.%02d.%02d %02d:%02d.%02d", now->tm_mon + 1, now->tm_mday, now->tm_year, now->tm_hour, now->tm_min, now->tm_sec);
}
else
strcpy(buffer, "00.00.00 00:00.00");
return buffer;
#endif
}
#endif
/***********
** BUFFER **
***********/
static CHATBool ciBufferInit(ciBuffer * buffer)
{
assert(buffer != NULL);
buffer->length = 0;
buffer->size = BUFFER_INC;
buffer->buffer = (char *)gsimalloc((unsigned int)buffer->size + 1);
if(buffer->buffer == NULL)
return CHATFalse;
// Just for fun.
////////////////
buffer->buffer[0] = '\0';
return CHATTrue;
}
static void ciBufferFree(ciBuffer * buffer)
{
gsifree(buffer->buffer);
}
static CHATBool ciBufferPreAppend(ciBuffer * buffer, int len)
{
int total;
char * tempPtr;
ASSERT_BUFFER(buffer);
assert(len >= 0);
assert(len <= SHRT_MAX); // sanity check
// Check if the buffer is big enough.
/////////////////////////////////////
total = (buffer->length + len);
if(total <= buffer->size)
return CHATTrue;
// Figure out the new size.
///////////////////////////
total += BUFFER_INC;
total -= (total % BUFFER_INC);
// Allocate the memory.
///////////////////////
tempPtr = (char *)gsirealloc(buffer->buffer, (unsigned int)total + 1);
if(tempPtr == NULL)
return CHATFalse;
// Update the buffer.
/////////////////////
buffer->buffer = tempPtr;
buffer->size = total;
return CHATTrue;
}
static void ciBufferClipFront(ciBuffer * buffer, int len)
{
ASSERT_BUFFER(buffer);
assert(len >= 0);
assert(len <= buffer->length);
buffer->length -= len;
memmove(buffer->buffer, &buffer->buffer[len], (unsigned int)buffer->length);
buffer->buffer[buffer->length] = '\0';
}
#ifdef __MWERKS__ // CodeWarrior will warn if not prototyped
/***************
** PROTOTYPES **
****************/
CHATBool ciParseParam(const char *pText, ciServerMessage * message);
#endif
/**************
** FUNCTIONS **
**************/
CHATBool ciSocketInit(ciSocket * sock, const char * nick)
{
#ifdef IRC_LOG
FILE * log;
#endif
assert(sock != NULL);
memset(sock, 0, sizeof(ciSocket));
sock->sock = INVALID_SOCKET;
if(!ciBufferInit(&sock->inputQueue))
return CHATFalse;
if(!ciBufferInit(&sock->outputQueue))
{
ciBufferFree(&sock->inputQueue);
return CHATFalse;
}
#ifdef IRC_LOG
sprintf(sock->filename, "%s_irc.log", nick);
log = fopen(sock->filename, "at");
if(log != NULL)
{
fprintf(log, "\n\n\n\n\nCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n");
fclose(log);
}
#endif
GSI_UNUSED(nick);
return CHATTrue;
}
CHATBool ciSocketConnect(ciSocket * sock,
const char * serverAddress,
int port)
{
unsigned int ip;
HOSTENT * host;
SOCKADDR_IN address;
int rcode;
#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION)
int keepalive;
#endif
ASSERT_SOCK(sock);
assert(serverAddress != NULL);
assert(port >= 0);
assert(port <= USHRT_MAX);
assert(sock->connectState == ciNotConnected);
// Copy off the address.
////////////////////////
strzcpy(sock->serverAddress, serverAddress, 255);
// Try resolving the string as an IP a.b.c.d number.
////////////////////////////////////////////////////
ip = inet_addr(serverAddress);
if(ip == INADDR_NONE)
{
// Try resolving with DNS.
//////////////////////////
host = gethostbyname((char *)serverAddress);
if(host == NULL)
return CHATFalse;
// Get the ip.
//////////////
ip = *(unsigned int *)host->h_addr_list[0];
}
// Setup the address.
/////////////////////
memset(&address, 0, sizeof(SOCKADDR_IN));
address.sin_family = AF_INET;
address.sin_addr.s_addr = ip;
address.sin_port = htons((unsigned short)port);
// Create the socket.
/////////////////////
sock->sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock->sock == INVALID_SOCKET)
return CHATFalse;
// Enable keep-alive.
/////////////////////
#if !defined(INSOCK) && !defined(_NITRO) && !defined(_REVOLUTION)
keepalive = 1;
rcode = setsockopt(sock->sock, SOL_SOCKET, SO_KEEPALIVE, (char *)&keepalive, sizeof(int));
//assert(gsiSocketIsNotError(rcode));
#endif
// Try and connect.
///////////////////
rcode = connect(sock->sock, (SOCKADDR *)&address, sizeof(SOCKADDR_IN));
if(gsiSocketIsError(rcode))
{
closesocket(sock->sock);
return CHATFalse;
}
// We're connected.
///////////////////
sock->connectState = ciConnected;
return CHATTrue;
}
void ciSocketDisconnect(ciSocket * sock)
{
int i;
ASSERT_SOCK(sock);
// Shutdown the socket.
///////////////////////
if(sock->sock != INVALID_SOCKET)
{
shutdown(sock->sock, 2);
closesocket(sock->sock);
}
// We're disconnected.
//////////////////////
sock->connectState = ciDisconnected;
// gsifree the buffers.
////////////////////
ciBufferFree(&sock->inputQueue);
ciBufferFree(&sock->outputQueue);
// gsifree the last-message pointers.
//////////////////////////////////
gsifree(sock->lastMessage.message);
gsifree(sock->lastMessage.server);
gsifree(sock->lastMessage.nick);
gsifree(sock->lastMessage.user);
gsifree(sock->lastMessage.host);
gsifree(sock->lastMessage.command);
gsifree(sock->lastMessage.middle);
gsifree(sock->lastMessage.param);
for(i = 0 ; i < sock->lastMessage.numParams ; i++)
gsifree(sock->lastMessage.params[i]);
gsifree(sock->lastMessage.params);
}
static void ciSocketSelect(SOCKET sock, CHATBool * readFlag, CHATBool * writeFlag, CHATBool * exceptFlag)
{
int aReadFlag = 0;
int aWriteFlag = 0;
int aExceptFlag = 0;
// Call generic select GSISocketSelect
GSISocketSelect(sock, &aReadFlag, &aWriteFlag, &aExceptFlag);
// Translate the int flags to ChatBool flags
if (readFlag)
*readFlag = (CHATBool)aReadFlag;
if (writeFlag)
*writeFlag = (CHATBool)aWriteFlag;
if (exceptFlag)
*exceptFlag = (CHATBool)aExceptFlag;
}
static void ciSocketThinkSend(ciSocket * sock)
{
int rcode;
int len;
ASSERT_SOCK(sock);
ASSERT_CONNECTED(sock);
// While there's data to send...
////////////////////////////////
while(sock->outputQueue.length > 0)
{
CHATBool writeFlag;
// Can we send?
///////////////
ciSocketSelect(sock->sock, NULL, &writeFlag, NULL);
if(!writeFlag)
return;
// Try and send some.
/////////////////////
len = min(sock->outputQueue.length, 1024);
rcode = send(sock->sock, sock->outputQueue.buffer, len, 0);
if(rcode == 0)
return;
if(gsiSocketIsError(rcode))
return; //ERRCON
// Update the output queue.
///////////////////////////
ciBufferClipFront(&sock->outputQueue, rcode);
}
}
static void ciSocketThinkRecv(ciSocket * sock)
{
CHATBool readFlag;
int len;
int rcode;
char * pos;
ASSERT_SOCK(sock);
ASSERT_CONNECTED(sock);
while(CHATTrue)
{
// Check the read flag.
///////////////////////
ciSocketSelect(sock->sock, &readFlag, NULL, NULL);
// Nothing to read?
///////////////////
if(!readFlag)
return;
// Make sure the buffer is ready.
/////////////////////////////////
if(!ciBufferPreAppend(&sock->inputQueue, RECV_LEN))
return; //ERRCON
// Recv into the buffer.
////////////////////////
pos = &sock->inputQueue.buffer[sock->inputQueue.length];
rcode = recv(sock->sock, pos, RECV_LEN, 0);
// Connection closed?
/////////////////////
if(rcode <= 0) //crt -- handle remote disconnections
{
sock->connectState = ciDisconnected;
return;
}
// Set the number of bytes read.
////////////////////////////////
len = rcode;
#if 0
{
char * buffer;
int i;
buffer = (char *)gsimalloc(len + 2);
assert(buffer != NULL);
memcpy(buffer, &sock->inputQueue.buffer[sock->inputQueue.length], len);
buffer[len] = '\0';
OutputDebugString("--->>>XXXRECV\n");
for(i = 0 ; i < len ; i += 1023)
OutputDebugString(&buffer[i]);
OutputDebugString("<<<---XXXRECV\n");
}
#endif
// Decrypt data if the socket is secure.
////////////////////////////////////////
if(sock->secure)
gs_crypt((unsigned char *)pos, len, &sock->inKey);
// Update the buffer length.
////////////////////////////
sock->inputQueue.length += len;
// NUL terminate the buffer.
////////////////////////////
sock->inputQueue.buffer[sock->inputQueue.length] = '\0';
}
}
void ciSocketThink(ciSocket * sock)
{
ASSERT_SOCK(sock);
ASSERT_CONNECTED(sock);
// Disconnected?
////////////////
if(sock->connectState == ciDisconnected)
return;
// Send any waiting data.
/////////////////////////
ciSocketThinkSend(sock);
// Recv any waiting data.
/////////////////////////
ciSocketThinkRecv(sock);
}
CHATBool ciSocketSend(ciSocket * sock,
const char * buffer)
{
#ifdef IRC_LOG
FILE * log;
#endif
int len;
char * pos;
ASSERT_SOCK(sock);
ASSERT_CONNECTED(sock);
assert(buffer != NULL);
// Disconnected?
////////////////
if(sock->connectState == ciDisconnected)
return CHATTrue;
// Get the buffer length.
/////////////////////////
len = (int)strlen(buffer);
// Make sure the buffer is big enough.
//////////////////////////////////////
if(!ciBufferPreAppend(&sock->outputQueue, len + 2))
return CHATFalse;
// Append to the output buffer.
///////////////////////////////
pos = &sock->outputQueue.buffer[sock->outputQueue.length];
memcpy(pos, buffer, (unsigned int)len);
// Update the buffer length.
////////////////////////////
sock->outputQueue.length += len;
// Add the CRLF.
////////////////
sock->outputQueue.buffer[sock->outputQueue.length++] = 0x0D;
sock->outputQueue.buffer[sock->outputQueue.length++] = 0x0A;
// Encrypt data if the socket is secure.
////////////////////////////////////////
if(sock->secure)
gs_crypt((unsigned char *)pos, len + 2, &sock->outKey);
#ifdef IRC_LOG
// Write it to the log.
///////////////////////
log = fopen(sock->filename, "at");
if(log != NULL)
{
fprintf(log, "%s | OUT | %s\n", ciGetTime(), buffer);
fclose(log);
}
#endif
return CHATTrue;
}
CHATBool ciSocketSendf(ciSocket * sock,
const char * format,
...)
{
static char buffer[4096];
int num;
va_list args;
ASSERT_SOCK(sock);
ASSERT_CONNECTED(sock);
assert(buffer != NULL);
// Disconnected?
////////////////
if(sock->connectState == ciDisconnected)
return CHATTrue;
// Do the formatting.
/////////////////////
va_start(args, format);
num = vsprintf(buffer, format, args);
if(num != -1)
buffer[num] = '\0';
else
buffer[sizeof(buffer) - 1] = '\0'; //ERRCON
// Send.
////////
return ciSocketSend(sock, buffer);
}
static CHATBool ciParseUser(const char *pText, ciServerMessage * message)
{
char *pTmpNick = NULL, *pTmpUsername = NULL, *pTmpHost = NULL;
int nNick = 0, nUsername = 0, nHost = 0;
char *p;
if(pText == NULL || pText[0] == '\0')
{
assert(0);
return CHATFalse; //ERRCON
}
p = pTmpNick = (char *)pText;
while(*p != '\0')
{
if(*p != '!')
{
++p;
++nNick;
}
else
{
pTmpUsername = ++p;
while(*p != '\0')
{
if(*p != '@')
{
++p;
++nUsername;
}
else
{
pTmpHost = ++p;
while(*p != '\0')
{
++p;
++nHost;
}
}
}
}
}
if(nNick)
{
message->nick = (char *)gsimalloc((unsigned int)nNick + 1);
if(message->nick)
{
memcpy(message->nick, pTmpNick, (unsigned int)nNick);
message->nick[nNick] = '\0';
}
}
else
message->nick = NULL;
if(nUsername)
{
message->user = (char *)gsimalloc((unsigned int)nUsername + 1);
if(message->user)
{
memcpy(message->user, pTmpUsername, (unsigned int)nUsername);
message->user[nUsername] = '\0';
}
}
else
message->user = NULL;
if(nHost)
{
message->host = (char *)gsimalloc((unsigned int)nHost + 1);
if(message->host)
{
memcpy(message->host, pTmpHost, (unsigned int)nHost);
message->host[nHost] = '\0';
}
}
else
message->host = NULL;
return CHATTrue;
}
static CHATBool ciAddParam(const char *param, ciServerMessage * message)
{
void * tempPtr;
// Reallocate the parameter array.
//////////////////////////////////
tempPtr = gsirealloc(message->params, sizeof(char *) * (message->numParams + 1));
if(tempPtr == NULL)
return CHATFalse; //ERRCON
message->params = (char **)tempPtr;
// Allocate mem for the param.
//////////////////////////////
tempPtr = gsimalloc(strlen(param) + 1);
if(tempPtr == NULL)
return CHATFalse; //ERRCON
// Copy the param.
//////////////////
strcpy((char *)tempPtr, param);
message->params[message->numParams++] = (char *)tempPtr;
return CHATTrue;
}
CHATBool ciParseParam(const char *pText, ciServerMessage * message)
{
char * colon;
char * str;
char * p;
assert(pText != NULL);
assert(message != NULL);
// Copy off the text.
/////////////////////
p = (char *)gsimalloc(strlen(pText) + 1);
if(p == NULL)
return CHATFalse; //ERRCON
strcpy(p, pText);
// Find the colon.
//////////////////
if(p[0] == ':')
{
p[0] = '\0';
colon = &p[1];
}
else
{
colon = strstr(p, " :");
if(colon != NULL)
{
*colon = '\0';
colon += 2;
}
}
str = strtok(p, " ");
while(str != NULL)
{
// Add the param.
/////////////////
if(!ciAddParam(str, message))
{
gsifree(p);
return CHATFalse; //ERRCON
}
// Get the next one.
////////////////////
str = strtok(NULL, " ");
}
if(colon != NULL)
{
// Add the last param.
//////////////////////
if(!ciAddParam(colon, message))
{
gsifree(p);
return CHATFalse; //ERRCON
}
}
gsifree(p);
return CHATTrue;
}
static CHATBool ciParseMessage(ciSocket * sock, const char *sText)
{
// TRACE("ServM: Parse Data=%s\n", sText);
int nMessage = 0, nServer = 0, nCommand = 0, nMiddle = 0, nParam = 0;
char *p, *temp;
ciServerMessage * message = &sock->lastMessage;
if(sText == NULL || sText[0] == '\0')
{
assert(0);
return CHATFalse; //ERRCON
}
nMessage = (int)strlen(sText);
message->message = (char *)gsimalloc((unsigned int)nMessage + 1);
if(message->message == NULL)
return CHATFalse; //ERRCON
memcpy(message->message, sText, (unsigned int)nMessage);
message->message[nMessage] = '\0';
p = (char *)sText;
while(*p == '\n' || *p == '\r')
++p;
if(*p == ':')
{ // server
message->server = ++p; // ?? BUGBUG
if(*p != '\0')
{
while(*p != ' ' && *p != '\0')
{
++nServer;
++p;
}
}
}
while(*p == ' ')
// skip spaces
++p;
if(*p != '\0')
{ // command
message->command = p;
while(*p != ' ' && *p != '\0')
{
++nCommand;
++p;
}
}
while(*p == ' ')
// skip spaces
++p;
if(*p != ':' && *p != '\0')
{ // middle
message->middle = p;
while(*p != ' ' && *p != '\0')
{
++nMiddle;
++p;
}
}
while(*p == ' ')
// skip spaces
++p;
//if(*p == ':')
// params delimiter
// ++p;
if(*p != '\0')
{ // params
message->param = p;
while(*p != '\0')
{
++nParam;
++p;
}
}
if(nServer)
{
temp = message->server;
message->server = (char *)gsimalloc((unsigned int)nServer + 1);
if(message->server)
{
memcpy(message->server, temp, (unsigned int)nServer);
message->server[nServer] = '\0';
}
if(!ciParseUser(message->server, message))
{
RESET(message->message);
RESET(message->server);
return CHATFalse; //ERRCON
}
}
else
{
message->server = NULL;
message->nick = NULL;
message->user = NULL;
message->host = NULL;
}
if(nMiddle)
{
if(!ciParseParam(message->middle, message))
{
RESET(message->message);
RESET(message->server);
RESET(message->nick);
RESET(message->user);
RESET(message->host);
return CHATFalse; //ERRCON
}
}
else if(nParam)
{
if(!ciParseParam(message->param, message))
{
RESET(message->message);
RESET(message->server);
RESET(message->nick);
RESET(message->user);
RESET(message->host);
return CHATFalse; //ERRCON
}
}
else
{
message->params = NULL;
message->numParams = 0;
}
if(nParam)
{
temp = message->param;
message->param = (char *)gsimalloc((unsigned int)nParam + 1);
if(message->param)
{
memcpy(message->param, temp, (unsigned int)nParam);
message->param[nParam] = '\0';
}
}
else
{
message->param = NULL;
}
if(nCommand)
{
temp = message->command;
message->command = (char *)gsimalloc((unsigned int)nCommand + 1);
if(message->command)
{
memcpy(message->command, temp, (unsigned int)nCommand);
message->command[nCommand] = '\0';
}
}
else
message->command = NULL;
if(nMiddle)
{
temp = message->middle;
message->middle = (char *)gsimalloc((unsigned int)nMiddle + 1);
if(message->middle)
{
memcpy(message->middle, temp, (unsigned int)nMiddle);
message->middle[nMiddle] = '\0';
}
}
else
message->middle = NULL;
return CHATTrue;
}
static CHATBool ciParseInput(ciSocket * sock)
{
char *p, *q, *r;
char temp;
int i;
p = sock->inputQueue.buffer; // start
if(*p != '\0')
{
// eat all CRs & LFs
while(*p == 13 || *p == 10)
++p;
// end of string ?
if(*p != '\0')
{
r = q = p;
// everything between last-nonspace char and CRLF should be discarded
while(*q != 10 && *q != 13 && *q != '\0')
{
if(*q != ' ')
r = q;
++q;
}
if(*q != '\0')
{
++r;
temp = *r;
*r = '\0';
// gsifree the old message stuff.
//////////////////////////////
RESET(sock->lastMessage.message);
RESET(sock->lastMessage.server);
RESET(sock->lastMessage.nick);
RESET(sock->lastMessage.user);
RESET(sock->lastMessage.host);
RESET(sock->lastMessage.command);
RESET(sock->lastMessage.middle);
RESET(sock->lastMessage.param);
for(i = 0 ; i < sock->lastMessage.numParams ; i++)
RESET(sock->lastMessage.params[i]);
RESET(sock->lastMessage.params);
sock->lastMessage.numParams = 0;
// Parse the message.
/////////////////////
memset(&sock->lastMessage,0,sizeof(sock->lastMessage));
if(!ciParseMessage(sock, p))
{
memset(&sock->lastMessage,0,sizeof(sock->lastMessage));
return CHATFalse; //ERRCON
}
// Restore the temp character.
//////////////////////////////
*r = temp;
// Take the message out of the buffer.
//////////////////////////////////////
ciBufferClipFront(&sock->inputQueue, (q - sock->inputQueue.buffer));
return CHATTrue;
}
}
}
return CHATFalse;
}
ciServerMessage * ciSocketRecv(ciSocket * sock)
{
#ifdef IRC_LOG
FILE * log;
#endif
ASSERT_SOCK(sock);
// Bill: 08-10-04
// There may be unprocessed messages in the queue
//if(sock->connectState == ciDisconnected)
// return NULL;
// Check for an empty buffer.
/////////////////////////////
if(sock->inputQueue.length == 0)
return NULL;
// Check for a message.
///////////////////////
if(!ciParseInput(sock))
{
// No message.
//////////////
return NULL;
}
#ifdef IRC_LOG
// Write it to the log.
///////////////////////
log = fopen(sock->filename, "at");
if(log != NULL)
{
fprintf(log, "%s | IN | %s\n", ciGetTime(), sock->lastMessage.message);
fclose(log);
}
#endif
// Got a message.
/////////////////
return &sock->lastMessage;
}