tOops! Network and error modules added. ;) (Plus, link to "dopewars -h" added to Win32 installer) - vaccinewars - be a doctor and try to vaccinate the world (HTM) git clone git://src.adamsgaard.dk/vaccinewars (DIR) Log (DIR) Files (DIR) Refs (DIR) README (DIR) LICENSE --- (DIR) commit 2d06fc42ffd5356336f0c442bffa716cdf1e5980 (DIR) parent 75487908600e5379fe2f8412948355e8df683006 (HTM) Author: Ben Webb <ben@salilab.org> Date: Mon, 1 Oct 2001 18:14:27 +0000 Oops! Network and error modules added. ;) (Plus, link to "dopewars -h" added to Win32 installer) Diffstat: A src/error.c | 162 ++++++++++++++++++++++++++++++ A src/error.h | 58 ++++++++++++++++++++++++++++++ A src/network.c | 657 +++++++++++++++++++++++++++++++ A src/network.h | 152 +++++++++++++++++++++++++++++++ M win32/filelist | 4 +++- 5 files changed, 1032 insertions(+), 1 deletion(-) --- (DIR) diff --git a/src/error.c b/src/error.c t@@ -0,0 +1,162 @@ +/* error.c Error-handling routines for dopewars */ +/* Copyright (C) 1998-2001 Ben Webb */ +/* Email: ben@bellatrix.pcl.ox.ac.uk */ +/* WWW: http://bellatrix.pcl.ox.ac.uk/~ben/dopewars/ */ + +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, */ +/* MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> /* For GString functions */ +#include <string.h> /* For strerror */ + +#ifdef CYGWIN +#include <windows.h> /* For FormatMessage() etc. */ +#include <winsock.h> /* For WSAxxx constants */ +#else +#include <netdb.h> /* For h_errno error codes */ +#endif + +#include "error.h" +#include "nls.h" + +typedef struct _ErrTable { + gint code; + gchar *string; +} ErrTable; + +void ClearError(LastError *error) { + error->type=NULL; +} + +gboolean IsError(LastError *error) { + return (error->type!=NULL); +} + +void SetError(LastError *error,ErrorType *type,gint code) { + error->type=type; + error->code=code; +} + +static void LookupErrorCode(GString *str,gint code,ErrTable *table, + gchar *fallbackstr) { + for (;table && table->string;table++) { + if (code==table->code) { + g_string_append(str,_(table->string)); + return; + } + } + g_string_sprintfa(str,fallbackstr,code); +} + +/* "Custom" error handling */ +static ErrTable CustomErrStr[] = { + { E_FULLBUF,N_("Connection dropped due to full buffer") }, + { 0,NULL } +}; + +void CustomAppendError(GString *str,LastError *error) { + LookupErrorCode(str,error->code,CustomErrStr,_("Internal error code %d")); +} + +static ErrorType ETCustom = { CustomAppendError }; +ErrorType *ET_CUSTOM = &ETCustom; + +/* "errno" error handling */ +void ErrnoAppendError(GString *str,LastError *error) { + g_string_append(str,strerror(error->code)); +} + +static ErrorType ETErrno = { ErrnoAppendError }; +ErrorType *ET_ERRNO = &ETErrno; + +#ifdef CYGWIN + +/* Winsock error handling */ +static ErrTable WSAErrStr[] = { +/* These are the explanations of the various Windows Sockets error codes */ + { WSANOTINITIALISED,N_("WinSock has not been properly initialised") }, + { WSAENETDOWN,N_("The network subsystem has failed") }, + { WSAEADDRINUSE,N_("Address already in use") }, + { WSAENETDOWN,N_("Cannot reach the network") }, + { WSAETIMEDOUT,N_("The connection timed out") }, + { WSAEMFILE,N_("Out of file descriptors") }, + { WSAENOBUFS,N_("Out of buffer space") }, + { WSAEOPNOTSUPP,N_("Operation not supported") }, + { WSAECONNABORTED,N_("Connection aborted due to failure") }, + { WSAECONNRESET,N_("Connection reset by remote host") }, + { WSAECONNREFUSED,N_("Connection refused") }, + { WSAEAFNOSUPPORT,N_("Address family not supported") }, + { WSAEPROTONOSUPPORT,N_("Protocol not supported") }, + { WSAESOCKTNOSUPPORT,N_("Socket type not supported") }, + { WSAHOST_NOT_FOUND,N_("Host not found") }, + { WSATRY_AGAIN,N_("Temporary name server error - try again later") }, + { WSANO_RECOVERY,N_("Failed to contact nameserver") }, + { WSANO_DATA,N_("Valid name, but no DNS data record present") }, + { 0,NULL } +}; + +void WinsockAppendError(GString *str,LastError *error) { + LookupErrorCode(str,error->code,WSAErrStr,_("Network error code %d")); +} + +static ErrorType ETWinsock = { WinsockAppendError }; +ErrorType *ET_WINSOCK = &ETWinsock; + +/* Standard Win32 "GetLastError" handling */ +void Win32AppendError(GString *str,LastError *error) { + LPTSTR lpMsgBuf; + + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, + NULL,error->code,MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT), + (LPTSTR)&lpMsgBuf,0,NULL); + g_string_append(str,lpMsgBuf); + LocalFree(lpMsgBuf); +} + +static ErrorType ETWin32 = { Win32AppendError }; +ErrorType *ET_WIN32 = &ETWin32; + +#else + +/* h_errno error handling */ +static ErrTable DNSErrStr[] = { +/* These are the explanations of the various name server error codes */ + { HOST_NOT_FOUND,N_("Host not found") }, + { TRY_AGAIN,N_("Temporary name server error - try again later") }, + { 0,NULL } +}; + +void HErrnoAppendError(GString *str,LastError *error) { + LookupErrorCode(str,error->code,DNSErrStr,_("Name server error code %d")); +} + +static ErrorType ETHErrno = { HErrnoAppendError }; +ErrorType *ET_HERRNO = ÐErrno; + +#endif /* CYGWIN */ + +void g_string_assign_error(GString *str,LastError *error) { + g_string_truncate(str,0); + g_string_append_error(str,error); +} + +void g_string_append_error(GString *str,LastError *error) { + if (!error->type) return; + (*error->type->AppendErrorString)(str,error); +} (DIR) diff --git a/src/error.h b/src/error.h t@@ -0,0 +1,58 @@ +/* error.h Header file for dopewars error-handling routines */ +/* Copyright (C) 1998-2001 Ben Webb */ +/* Email: ben@bellatrix.pcl.ox.ac.uk */ +/* WWW: http://bellatrix.pcl.ox.ac.uk/~ben/dopewars/ */ + +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, */ +/* MA 02111-1307, USA. */ + + +#ifndef __ERROR_H__ +#define __ERROR_H__ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> + +struct _LastError; +typedef struct _ErrorType { + void (*AppendErrorString)(GString *str,struct _LastError *error); +} ErrorType; + +typedef struct _LastError { + gint code; + ErrorType *type; +} LastError; + +extern ErrorType *ET_CUSTOM,*ET_ERRNO; +#ifdef CYGWIN +extern ErrorType *ET_WIN32,*ET_WINSOCK; +#else +extern ErrorType *ET_HERRNO; +#endif + +typedef enum { + E_FULLBUF +} CustomErrorCode; + +void ClearError(LastError *error); +gboolean IsError(LastError *error); +void SetError(LastError *error,ErrorType *type,gint code); +void g_string_assign_error(GString *str,LastError *error); +void g_string_append_error(GString *str,LastError *error); + +#endif /* __ERROR_H__ */ (DIR) diff --git a/src/network.c b/src/network.c t@@ -0,0 +1,657 @@ +/* network.c Low-level networking routines */ +/* Copyright (C) 1998-2001 Ben Webb */ +/* Email: ben@bellatrix.pcl.ox.ac.uk */ +/* WWW: http://bellatrix.pcl.ox.ac.uk/~ben/dopewars/ */ + +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, */ +/* MA 02111-1307, USA. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef NETWORKING + +#ifdef CYGWIN +#include <windows.h> /* For datatypes such as BOOL */ +#include <winsock.h> /* For network functions */ +#else +#include <sys/types.h> /* For size_t etc. */ +#include <sys/socket.h> /* For struct sockaddr etc. */ +#include <netinet/in.h> /* For struct sockaddr_in etc. */ +#include <arpa/inet.h> /* For socklen_t */ +#include <string.h> /* For memcpy, strlen etc. */ +#ifdef HAVE_UNISTD_H +#include <unistd.h> /* For close(), various types and constants */ +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> /* For fcntl() */ +#endif +#include <netdb.h> /* For gethostbyname() */ +#endif /* CYGWIN */ + +#include <glib.h> +#include <errno.h> /* For errno and Unix error codes */ +#include <stdlib.h> /* For exit() and atoi() */ +#include <stdio.h> /* For perror() */ + +#include "error.h" +#include "network.h" +#include "nls.h" + +/* Maximum sizes (in bytes) of read and write buffers - connections should + be dropped if either buffer is filled */ +#define MAXREADBUF (32768) +#define MAXWRITEBUF (65536) + +#ifdef CYGWIN + +void StartNetworking() { + WSADATA wsaData; + if (WSAStartup(MAKEWORD(1,0),&wsaData)!=0) { + g_warning(_("Cannot initialise WinSock!")); + exit(1); + } +} + +void StopNetworking() { + WSACleanup(); +} + +void SetReuse(SOCKET sock) { + BOOL i=TRUE; + if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&i,sizeof(i))==-1) { + perror("setsockopt"); exit(1); + } +} + +void SetBlocking(SOCKET sock,gboolean blocking) { + unsigned long param; + param = blocking ? 0 : 1; + ioctlsocket(sock,FIONBIO,¶m); +} + +#else + +void StartNetworking() {} +void StopNetworking() {} + +void SetReuse(int sock) { + int i=1; + if (setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))==-1) { + perror("setsockopt"); exit(1); + } +} + +void SetBlocking(int sock,gboolean blocking) { + fcntl(sock,F_SETFL,blocking ? 0 : O_NONBLOCK); +} + +#endif /* CYGWIN */ + +static gboolean FinishConnect(int fd,LastError *error); + +static void NetBufCallBack(NetworkBuffer *NetBuf) { + if (NetBuf && NetBuf->CallBack) { + (*NetBuf->CallBack)(NetBuf,!NetBuf->WaitConnect, + NetBuf->WriteBuf.DataPresent || NetBuf->WaitConnect); + } +} + +static void NetBufCallBackStop(NetworkBuffer *NetBuf) { + if (NetBuf && NetBuf->CallBack) (*NetBuf->CallBack)(NetBuf,FALSE,FALSE); +} + +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar) { +/* Initialises the passed network buffer, ready for use. Messages sent */ +/* or received on the buffered connection will be terminated by the */ +/* given character, and if they end in "StripChar" it will be removed */ +/* before the messages are sent or received. */ + NetBuf->fd=-1; + NetBuf->InputTag=0; + NetBuf->CallBack=NULL; + NetBuf->CallBackData=NULL; + NetBuf->Terminator=Terminator; + NetBuf->StripChar=StripChar; + NetBuf->ReadBuf.Data=NetBuf->WriteBuf.Data=NULL; + NetBuf->ReadBuf.Length=NetBuf->WriteBuf.Length=0; + NetBuf->ReadBuf.DataPresent=NetBuf->WriteBuf.DataPresent=0; + NetBuf->WaitConnect=FALSE; + ClearError(&NetBuf->error); +} + +void SetNetworkBufferCallBack(NetworkBuffer *NetBuf,NBCallBack CallBack, + gpointer CallBackData) { + NetBufCallBackStop(NetBuf); + NetBuf->CallBack=CallBack; + NetBuf->CallBackData=CallBackData; + NetBufCallBack(NetBuf); +} + +void BindNetworkBufferToSocket(NetworkBuffer *NetBuf,int fd) { +/* Sets up the given network buffer to handle data being sent/received */ +/* through the given socket */ + NetBuf->fd=fd; +} + +gboolean IsNetworkBufferActive(NetworkBuffer *NetBuf) { +/* Returns TRUE if the pointer is to a valid network buffer, and it's */ +/* connected to an active socket. */ + return (NetBuf && NetBuf->fd>=0); +} + +gboolean StartNetworkBufferConnect(NetworkBuffer *NetBuf,gchar *RemoteHost, + unsigned RemotePort) { + ShutdownNetworkBuffer(NetBuf); + if (StartConnect(&NetBuf->fd,RemoteHost,RemotePort,TRUE,&NetBuf->error)) { + NetBuf->WaitConnect=TRUE; + +/* Notify the owner if necessary to check for the connection completing */ + NetBufCallBack(NetBuf); + + return TRUE; + } else { + return FALSE; + } +} + +void ShutdownNetworkBuffer(NetworkBuffer *NetBuf) { +/* Frees the network buffer's data structures (leaving it in the */ +/* 'initialised' state) and closes the accompanying socket. */ + + NetBufCallBackStop(NetBuf); + + if (NetBuf->fd>=0) CloseSocket(NetBuf->fd); + + g_free(NetBuf->ReadBuf.Data); + g_free(NetBuf->WriteBuf.Data); + + InitNetworkBuffer(NetBuf,NetBuf->Terminator,NetBuf->StripChar); +} + +void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds, + fd_set *writefds,fd_set *errorfds,int *MaxSock) { +/* Updates the sets of read and write file descriptors to monitor */ +/* input to/output from the given network buffer. MaxSock is updated */ +/* with the highest-numbered file descriptor (plus 1) for use in a */ +/* later select() call. */ + if (!NetBuf || NetBuf->fd<=0) return; + FD_SET(NetBuf->fd,readfds); + if (errorfds) FD_SET(NetBuf->fd,errorfds); + if (NetBuf->fd >= *MaxSock) *MaxSock=NetBuf->fd+1; + if (NetBuf->WriteBuf.DataPresent || NetBuf->WaitConnect) { + FD_SET(NetBuf->fd,writefds); + } +} + +static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady, + gboolean WriteReady,gboolean ErrorReady, + gboolean *ReadOK,gboolean *WriteOK, + gboolean *ErrorOK) { +/* Reads and writes data if the network connection is ready. Sets the */ +/* various OK variables to TRUE if no errors occurred in the relevant */ +/* operations, and returns TRUE if data was read and is waiting for */ +/* processing. */ + gboolean DataWaiting=FALSE,ConnectDone=FALSE; + gboolean retval; + *ReadOK=*WriteOK=*ErrorOK=TRUE; + + if (ErrorReady) *ErrorOK=FALSE; + else if (NetBuf->WaitConnect) { + if (WriteReady) { + retval=FinishConnect(NetBuf->fd,&NetBuf->error); + ConnectDone=TRUE; + NetBuf->WaitConnect=FALSE; + + if (!retval) { + *WriteOK=FALSE; + } + } + } else { + if (WriteReady) *WriteOK=WriteDataToWire(NetBuf); + + if (ReadReady) { + *ReadOK=ReadDataFromWire(NetBuf); + if (NetBuf->ReadBuf.DataPresent>0) DataWaiting=TRUE; + } + } + + if (!(*ErrorOK && *WriteOK && *ReadOK)) { +/* We don't want to check the socket any more */ + NetBufCallBackStop(NetBuf); +/* If there were errors, then the socket is now useless - so close it */ + CloseSocket(NetBuf->fd); + NetBuf->fd=-1; + } else if (ConnectDone) { +/* If we just connected, then no need to listen for write-ready status + any more */ + NetBufCallBack(NetBuf); + } else if (WriteReady && NetBuf->WriteBuf.DataPresent==0) { +/* If we wrote out everything, then tell the owner so that the socket no + longer needs to be checked for write-ready status */ + NetBufCallBack(NetBuf); + } + + return DataWaiting; +} + +gboolean RespondToSelect(NetworkBuffer *NetBuf,fd_set *readfds, + fd_set *writefds,fd_set *errorfds, + gboolean *DoneOK) { +/* Responds to a select() call by reading/writing data as necessary. */ +/* If any data were read, TRUE is returned. "DoneOK" is set TRUE */ +/* unless a fatal error (i.e. the connection was broken) occurred. */ + gboolean ReadOK,WriteOK,ErrorOK; + gboolean DataWaiting=FALSE; + + *DoneOK=TRUE; + if (!NetBuf || NetBuf->fd<=0) return DataWaiting; + DataWaiting=DoNetworkBufferStuff(NetBuf,FD_ISSET(NetBuf->fd,readfds), + FD_ISSET(NetBuf->fd,writefds), + errorfds ? FD_ISSET(NetBuf->fd,errorfds) : FALSE, + &ReadOK,&WriteOK,&ErrorOK); + *DoneOK=(WriteOK && ErrorOK && ReadOK); + return DataWaiting; +} + +gboolean NetBufHandleNetwork(NetworkBuffer *NetBuf,gboolean ReadReady, + gboolean WriteReady,gboolean *DoneOK) { + gboolean ReadOK,WriteOK,ErrorOK; + gboolean DataWaiting=FALSE; + + *DoneOK=TRUE; + if (!NetBuf || NetBuf->fd<=0) return DataWaiting; + + DataWaiting=DoNetworkBufferStuff(NetBuf,ReadReady,WriteReady,FALSE, + &ReadOK,&WriteOK,&ErrorOK); + + *DoneOK=(WriteOK && ErrorOK && ReadOK); + return DataWaiting; +} + +gint CountWaitingMessages(NetworkBuffer *NetBuf) { +/* Returns the number of complete (terminated) messages waiting in the */ +/* given network buffer. This is the number of times that */ +/* GetWaitingMessage() can be safely called without it returning NULL. */ + ConnBuf *conn; + gint i,msgs=0; + + conn=&NetBuf->ReadBuf; + + if (conn->Data) for (i=0;i<conn->DataPresent;i++) { + if (conn->Data[i]==NetBuf->Terminator) msgs++; + } + return msgs; +} + +gchar *GetWaitingMessage(NetworkBuffer *NetBuf) { +/* Reads a complete (terminated) message from the network buffer. The */ +/* message is removed from the buffer, and returned as a null-terminated */ +/* string (the network terminator is removed). If no complete message is */ +/* waiting, NULL is returned. The string is dynamically allocated, and */ +/* so must be g_free'd by the caller. */ + ConnBuf *conn; + int MessageLen; + char *SepPt; + gchar *NewMessage; + conn=&NetBuf->ReadBuf; + if (!conn->Data || !conn->DataPresent) return NULL; + SepPt=memchr(conn->Data,NetBuf->Terminator,conn->DataPresent); + if (!SepPt) return NULL; + *SepPt='\0'; + MessageLen=SepPt-conn->Data+1; + SepPt--; + if (NetBuf->StripChar && *SepPt==NetBuf->StripChar) *SepPt='\0'; + NewMessage=g_new(gchar,MessageLen); + memcpy(NewMessage,conn->Data,MessageLen); + if (MessageLen<conn->DataPresent) { + memmove(&conn->Data[0],&conn->Data[MessageLen], + conn->DataPresent-MessageLen); + } + conn->DataPresent-=MessageLen; + return NewMessage; +} + +gboolean ReadDataFromWire(NetworkBuffer *NetBuf) { +/* Reads any waiting data on the given network buffer's TCP/IP connection */ +/* into the read buffer. Returns FALSE if the connection was closed, or */ +/* if the read buffer's maximum size was reached. */ + ConnBuf *conn; + int CurrentPosition,BytesRead; + conn=&NetBuf->ReadBuf; + CurrentPosition=conn->DataPresent; + while(1) { + if (CurrentPosition>=conn->Length) { + if (conn->Length==MAXREADBUF) { + SetError(&NetBuf->error,ET_CUSTOM,E_FULLBUF); + return FALSE; /* drop connection */ + } + if (conn->Length==0) conn->Length=256; else conn->Length*=2; + if (conn->Length>MAXREADBUF) conn->Length=MAXREADBUF; + conn->Data=g_realloc(conn->Data,conn->Length); + } + BytesRead=recv(NetBuf->fd,&conn->Data[CurrentPosition], + conn->Length-CurrentPosition,0); + if (BytesRead==SOCKET_ERROR) { +#ifdef CYGWIN + int Error = WSAGetLastError(); + if (Error==WSAEWOULDBLOCK) break; + else { SetError(&NetBuf->error,ET_WINSOCK,Error); return FALSE; } +#else + if (errno==EAGAIN) break; + else if (errno!=EINTR) { + SetError(&NetBuf->error,ET_ERRNO,errno); + return FALSE; + } +#endif + } else if (BytesRead==0) { + return FALSE; + } else { + CurrentPosition+=BytesRead; + conn->DataPresent=CurrentPosition; + } + } + return TRUE; +} + +void QueueMessageForSend(NetworkBuffer *NetBuf,gchar *data) { +/* Writes the null-terminated string "data" to the network buffer, ready */ +/* to be sent to the wire when the network connection becomes free. The */ +/* message is automatically terminated. Fails to write the message without */ +/* error if the buffer reaches its maximum size (although this error will */ +/* be detected when an attempt is made to write the buffer to the wire). */ + int AddLength,NewLength; + ConnBuf *conn; + conn=&NetBuf->WriteBuf; + AddLength=strlen(data)+1; + NewLength=conn->DataPresent+AddLength; + if (NewLength > conn->Length) { + conn->Length*=2; + conn->Length=MAX(conn->Length,NewLength); + if (conn->Length > MAXWRITEBUF) conn->Length=MAXWRITEBUF; + if (NewLength > conn->Length) return; + conn->Data=g_realloc(conn->Data,conn->Length); + } + memcpy(&conn->Data[conn->DataPresent],data,AddLength); + conn->DataPresent=NewLength; + conn->Data[NewLength-1]=NetBuf->Terminator; + +/* If the buffer was empty before, we may need to tell the owner to check + the socket for write-ready status */ + if (NewLength==AddLength) NetBufCallBack(NetBuf); +} + +gboolean WriteDataToWire(NetworkBuffer *NetBuf) { +/* Writes any waiting data in the network buffer to the wire. Returns */ +/* TRUE on success, or FALSE if the buffer's maximum length is */ +/* reached, or the remote end has closed the connection. */ + ConnBuf *conn; + int CurrentPosition,BytesSent; + conn=&NetBuf->WriteBuf; + if (!conn->Data || !conn->DataPresent) return TRUE; + if (conn->Length==MAXWRITEBUF) { + SetError(&NetBuf->error,ET_CUSTOM,E_FULLBUF); + return FALSE; + } + CurrentPosition=0; + while (CurrentPosition<conn->DataPresent) { + BytesSent=send(NetBuf->fd,&conn->Data[CurrentPosition], + conn->DataPresent-CurrentPosition,0); + if (BytesSent==SOCKET_ERROR) { +#ifdef CYGWIN + int Error=WSAGetLastError(); + if (Error==WSAEWOULDBLOCK) break; + else { SetError(&NetBuf->error,ET_WINSOCK,Error); return FALSE; } +#else + if (errno==EAGAIN) break; + else if (errno!=EINTR) { + SetError(&NetBuf->error,ET_ERRNO,errno); + return FALSE; + } +#endif + } else { + CurrentPosition+=BytesSent; + } + } + if (CurrentPosition>0 && CurrentPosition<conn->DataPresent) { + memmove(&conn->Data[0],&conn->Data[CurrentPosition], + conn->DataPresent-CurrentPosition); + } + conn->DataPresent-=CurrentPosition; + return TRUE; +} + +static void SendHttpRequest(HttpConnection *conn) { + GString *text; + + conn->Tries++; + conn->StatusCode=0; + conn->Status=HS_CONNECTING; + + text=g_string_new(""); + + if (conn->Redirect) { + g_string_sprintf(text,"%s %s HTTP/1.0",conn->Method,conn->Redirect); + g_free(conn->Redirect); conn->Redirect=NULL; + } else { + g_string_sprintf(text,"%s http://%s:%u%s HTTP/1.0", + conn->Method,conn->HostName,conn->Port,conn->Query); + } + QueueMessageForSend(&conn->NetBuf,text->str); + + if (conn->Headers) QueueMessageForSend(&conn->NetBuf,conn->Headers); + + g_string_sprintf(text,"User-Agent: dopewars/%s",VERSION); + QueueMessageForSend(&conn->NetBuf,text->str); + +/* Insert a blank line between headers and body */ + QueueMessageForSend(&conn->NetBuf,""); + + if (conn->Body) QueueMessageForSend(&conn->NetBuf,conn->Body); + + g_string_free(text,TRUE); +} + +static gboolean StartHttpConnect(HttpConnection *conn) { + gchar *ConnectHost; + unsigned ConnectPort; + + if (conn->Proxy) { + ConnectHost=conn->Proxy; ConnectPort=conn->ProxyPort; + } else { + ConnectHost=conn->HostName; ConnectPort=conn->Port; + } + + if (!StartNetworkBufferConnect(&conn->NetBuf,ConnectHost,ConnectPort)) { + return FALSE; + } + return TRUE; +} + +gboolean OpenHttpConnection(HttpConnection **connpt,gchar *HostName, + unsigned Port,gchar *Proxy,unsigned ProxyPort, + gchar *Method,gchar *Query,gchar *Headers, + gchar *Body) { + HttpConnection *conn; + g_assert(HostName && Method && Query && connpt); + + conn=g_new0(HttpConnection,1); + InitNetworkBuffer(&conn->NetBuf,'\n','\r'); + conn->HostName=g_strdup(HostName); + if (Proxy && Proxy[0]) conn->Proxy=g_strdup(Proxy); + conn->Method=g_strdup(Method); + conn->Query=g_strdup(Query); + if (Headers && Headers[0]) conn->Headers=g_strdup(Headers); + if (Body && Body[0]) conn->Body=g_strdup(Body); + conn->Port = Port; + conn->ProxyPort = ProxyPort; + *connpt = conn; + + if (StartHttpConnect(conn)) { + SendHttpRequest(conn); + return TRUE; + } else { + return FALSE; + } +} + +void CloseHttpConnection(HttpConnection *conn) { + ShutdownNetworkBuffer(&conn->NetBuf); + g_free(conn->HostName); + g_free(conn->Proxy); + g_free(conn->Method); + g_free(conn->Query); + g_free(conn->Headers); + g_free(conn->Body); + g_free(conn->Redirect); + g_free(conn); +} + +gboolean IsHttpError(HttpConnection *conn) { + return IsError(&conn->NetBuf.error); +} + +gchar *ReadHttpResponse(HttpConnection *conn) { + gchar *msg,**split; + + msg=GetWaitingMessage(&conn->NetBuf); + if (msg) switch(conn->Status) { + case HS_CONNECTING: /* OK, we should have the HTTP status line */ + conn->Status=HS_READHEADERS; + split=g_strsplit(msg," ",2); + if (split[0] && split[1]) { + conn->StatusCode=atoi(split[1]); + g_print("HTTP status code %d returned\n",conn->StatusCode); + } else g_warning("Invalid HTTP status line %s",msg); + g_strfreev(split); + break; + case HS_READHEADERS: + if (msg[0]==0) conn->Status=HS_READSEPARATOR; + else { + split=g_strsplit(msg," ",1); + if (split[0] && split[1]) { + if (conn->StatusCode==302 && strcmp(split[0],"Location:")==0) { + g_print("Redirect to %s\n",split[1]); + g_free(conn->Redirect); + conn->Redirect = g_strdup(split[1]); + } +/* g_print("Header %s (value %s) read\n",split[0],split[1]);*/ + } + g_strfreev(split); + } + break; + case HS_READSEPARATOR: + conn->Status=HS_READBODY; + break; + case HS_READBODY: /* At present, we do nothing special with the body */ + break; + } + return msg; +} + +gboolean HandleHttpCompletion(HttpConnection *conn) { + NBCallBack CallBack; + gpointer CallBackData; + if (conn->Redirect) { + g_print("Following redirect\n"); + CallBack=conn->NetBuf.CallBack; + CallBackData=conn->NetBuf.CallBackData; + ShutdownNetworkBuffer(&conn->NetBuf); + if (StartHttpConnect(conn)) { + SendHttpRequest(conn); + SetNetworkBufferCallBack(&conn->NetBuf,CallBack,CallBackData); + return FALSE; + } + } + return TRUE; +} + +gboolean StartConnect(int *fd,gchar *RemoteHost,unsigned RemotePort, + gboolean NonBlocking,LastError *error) { + struct sockaddr_in ClientAddr; + struct hostent *he; + + if ((he=gethostbyname(RemoteHost))==NULL) { +#ifdef CYGWIN + if (error) SetError(error,ET_WINSOCK,WSAGetLastError()); +#else + if (error) SetError(error,ET_HERRNO,h_errno); +#endif + return FALSE; + } + *fd=socket(AF_INET,SOCK_STREAM,0); + if (*fd==SOCKET_ERROR) { +#ifdef CYGWIN + if (error) SetError(error,ET_WINSOCK,WSAGetLastError()); +#else + if (error) SetError(error,ET_ERRNO,errno); +#endif + return FALSE; + } + + ClientAddr.sin_family=AF_INET; + ClientAddr.sin_port=htons(RemotePort); + ClientAddr.sin_addr=*((struct in_addr *)he->h_addr); + memset(ClientAddr.sin_zero,0,sizeof(ClientAddr.sin_zero)); + + SetBlocking(*fd,!NonBlocking); + + if (connect(*fd,(struct sockaddr *)&ClientAddr, + sizeof(struct sockaddr))==SOCKET_ERROR) { +#ifdef CYGWIN + int errcode=WSAGetLastError(); + if (errcode==WSAEWOULDBLOCK) return TRUE; + else if (error) SetError(error,ET_WINSOCK,errcode); +#else + if (errno==EINPROGRESS) return TRUE; + else if (error) SetError(error,ET_ERRNO,errno); +#endif + CloseSocket(*fd); *fd=-1; + return FALSE; + } else { + SetBlocking(*fd,FALSE); /* All connected sockets should be nonblocking */ + } + return TRUE; +} + +gboolean FinishConnect(int fd,LastError *error) { + int errcode; +#ifdef CYGWIN + errcode = WSAGetLastError(); + if (errcode==0) return TRUE; + else { + if (error) { SetError(error,ET_WINSOCK,errcode); } + return FALSE; + } +#else +#ifdef HAVE_SOCKLEN_T + socklen_t optlen; +#else + int optlen; +#endif + + optlen=sizeof(errcode); + if (getsockopt(fd,SOL_SOCKET,SO_ERROR,&errcode,&optlen)==-1) { + errcode = errno; + } + if (errcode==0) return TRUE; + else { + if (error) { SetError(error,ET_ERRNO,errcode); } + return FALSE; + } +#endif /* CYGWIN */ +} + +#endif /* NETWORKING */ (DIR) diff --git a/src/network.h b/src/network.h t@@ -0,0 +1,152 @@ +/* network.h Header file for low-level networking routines */ +/* Copyright (C) 1998-2001 Ben Webb */ +/* Email: ben@bellatrix.pcl.ox.ac.uk */ +/* WWW: http://bellatrix.pcl.ox.ac.uk/~ben/dopewars/ */ + +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ + +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ + +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, */ +/* MA 02111-1307, USA. */ + + +#ifndef __NETWORK_H__ +#define __NETWORK_H__ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +/* Various includes necessary for select() calls */ +#include <sys/types.h> +/* Be careful not to include both sys/time.h and time.h on those systems */ +/* which don't like it */ +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include <glib.h> + +#include "error.h" + +#ifdef NETWORKING + +#ifndef SOCKET_ERROR +#define SOCKET_ERROR -1 +#endif + +typedef struct _ConnBuf { + gchar *Data; /* bytes waiting to be read/written */ + gint Length; /* allocated length of the "Data" buffer */ + gint DataPresent; /* number of bytes currently in "Data" */ +} ConnBuf; + +typedef struct _NetworkBuffer NetworkBuffer; + +typedef void (*NBCallBack)(NetworkBuffer *NetBuf,gboolean Read,gboolean Write); + +/* Handles reading and writing messages from/to a network connection */ +struct _NetworkBuffer { + int fd; /* File descriptor of the socket */ + gint InputTag; /* Identifier for gdk_input routines */ + NBCallBack CallBack; /* Function called when the socket read- or + write-able status changes */ + gpointer CallBackData; /* Data accessible to the callback function */ + char Terminator; /* Character that separates messages */ + char StripChar; /* Character that should be removed from messages */ + ConnBuf ReadBuf; /* New data, waiting for the application */ + ConnBuf WriteBuf; /* Data waiting to be written to the wire */ + gboolean WaitConnect; /* TRUE if a non-blocking connect is in progress */ + LastError error; /* Any error from the last operation */ +}; + +/* Keeps track of the progress of an HTTP connection */ +typedef enum { + HS_CONNECTING, HS_READHEADERS, HS_READSEPARATOR, HS_READBODY +} HttpStatus; + +/* A structure used to keep track of an HTTP connection */ +typedef struct _HttpConnection { + gchar *HostName; /* The machine on which the desired page resides */ + unsigned Port; /* The port */ + gchar *Proxy; /* If non-NULL, a web proxy to use */ + unsigned ProxyPort; /* The port to use for talking to the proxy */ + gchar *Method; /* e.g. GET, POST */ + gchar *Query; /* e.g. the path of the desired webpage */ + gchar *Headers; /* if non-NULL, e.g. Content-Type */ + gchar *Body; /* if non-NULL, data to send */ + gchar *Redirect; /* if non-NULL, a URL to redirect to */ + NetworkBuffer NetBuf; /* The actual network connection itself */ + gint Tries; /* Number of requests actually sent so far */ + gint StatusCode; /* 0=no status yet, otherwise an HTTP status code */ + HttpStatus Status; +} HttpConnection; + +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar); +void SetNetworkBufferCallBack(NetworkBuffer *NetBuf,NBCallBack CallBack, + gpointer CallBackData); +gboolean IsNetworkBufferActive(NetworkBuffer *NetBuf); +void BindNetworkBufferToSocket(NetworkBuffer *NetBuf,int fd); +gboolean StartNetworkBufferConnect(NetworkBuffer *NetBuf,gchar *RemoteHost, + unsigned RemotePort); +void ShutdownNetworkBuffer(NetworkBuffer *NetBuf); +void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds, + fd_set *writefds,fd_set *errorfds,int *MaxSock); +gboolean RespondToSelect(NetworkBuffer *NetBuf,fd_set *readfds, + fd_set *writefds,fd_set *errorfds, + gboolean *DoneOK); +gboolean NetBufHandleNetwork(NetworkBuffer *NetBuf,gboolean ReadReady, + gboolean WriteReady,gboolean *DoneOK); +gboolean ReadDataFromWire(NetworkBuffer *NetBuf); +gboolean WriteDataToWire(NetworkBuffer *NetBuf); +void QueueMessageForSend(NetworkBuffer *NetBuf,gchar *data); +gint CountWaitingMessages(NetworkBuffer *NetBuf); +gchar *GetWaitingMessage(NetworkBuffer *NetBuf); + +gboolean OpenHttpConnection(HttpConnection **conn,gchar *HostName, + unsigned Port,gchar *Proxy,unsigned ProxyPort, + gchar *Method,gchar *Query, + gchar *Headers,gchar *Body); +void CloseHttpConnection(HttpConnection *conn); +gboolean IsHttpError(HttpConnection *conn); +gchar *ReadHttpResponse(HttpConnection *conn); +gboolean HandleHttpCompletion(HttpConnection *conn); + +gboolean StartConnect(int *fd,gchar *RemoteHost,unsigned RemotePort, + gboolean NonBlocking,LastError *error); +void StartNetworking(void); +void StopNetworking(void); + +#ifdef CYGWIN +#define CloseSocket(sock) closesocket(sock) +void SetReuse(SOCKET sock); +void SetBlocking(SOCKET sock,gboolean blocking); +#else +#define CloseSocket(sock) close(sock) +void SetReuse(int sock); +void SetBlocking(int sock,gboolean blocking); +#endif + +#endif /* NETWORKING */ + +#endif /* __NETWORK_H__ */ (DIR) diff --git a/win32/filelist b/win32/filelist t@@ -40,7 +40,9 @@ dopewars.exe dopewars server.lnk dopewars.exe -s - +dopewars command line options.lnk +dopewars.exe +-h dopewars help.lnk index.html