tBasic SOCKS4 and SOCKS5 support (currently GTK+ client only) - 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 cae0ece3d9108923211a37f2ac70323526e5bcc0
 (DIR) parent d9cf98ef94f58e7d0914a85a526529b809cd5321
 (HTM) Author: Ben Webb <ben@salilab.org>
       Date:   Sat,  6 Oct 2001 19:55:26 +0000
       
       Basic SOCKS4 and SOCKS5 support (currently GTK+ client only)
       
       
       Diffstat:
         M src/dopewars.c                      |      22 +++++++++++++++++++++-
         M src/dopewars.h                      |       2 ++
         M src/error.c                         |       9 ++-------
         M src/error.h                         |       7 +++++++
         M src/gtk_client.c                    |      10 ++++++++--
         M src/network.c                       |     413 +++++++++++++++++++++++++++----
         M src/network.h                       |      51 +++++++++++++++++++++++--------
       
       7 files changed, 450 insertions(+), 64 deletions(-)
       ---
 (DIR) diff --git a/src/dopewars.c b/src/dopewars.c
       t@@ -165,6 +165,9 @@ struct METASERVER DefaultMetaServer = {
           "","","dopewars server"
        };
        
       +SocksServer Socks = { NULL,0,0 };
       +gboolean UseSocks;
       +
        int NumTurns=31;
        
        int PlayerArmour=100,BitchArmour=50;
       t@@ -182,6 +185,18 @@ struct GLOBALS Globals[] = {
             N_("Name of the high score file"),NULL,NULL,0,"",NULL,NULL },
           { NULL,NULL,NULL,&ServerName,NULL,"Server",
             N_("Name of the server to connect to"),NULL,NULL,0,"",NULL,NULL },
       +   { NULL,&UseSocks,NULL,NULL,NULL,"Socks.Active",
       +     N_("TRUE if a SOCKS server should be used for networking"),
       +     NULL,NULL,0,"",NULL,NULL },
       +   { NULL,NULL,NULL,&Socks.name,NULL,"Socks.Name",
       +     N_("The hostname of a SOCKS server to use"),
       +     NULL,NULL,0,"",NULL,NULL },
       +   { &Socks.port,NULL,NULL,NULL,NULL,"Socks.Port",
       +     N_("The port number of a SOCKS server to use"),
       +     NULL,NULL,0,"",NULL,NULL },
       +   { &Socks.version,NULL,NULL,NULL,NULL,"Socks.Version",
       +     N_("The version of the SOCKS protocol to use (4 or 5)"),
       +     NULL,NULL,0,"",NULL,NULL },
           { NULL,&MetaServer.Active,NULL,NULL,NULL,"MetaServer.Active",
             N_("TRUE if server should report to a metaserver"),
             NULL,NULL,0,"",NULL,NULL },
       t@@ -642,7 +657,7 @@ GSList *AddPlayer(int fd,Player *NewPlayer,GSList *First) {
           NewPlayer->CoatSize=100;
           NewPlayer->Flags=0;
        #if NETWORKING
       -   InitNetworkBuffer(&NewPlayer->NetBuf,'\n','\r');
       +   InitNetworkBuffer(&NewPlayer->NetBuf,'\n','\r',UseSocks ? &Socks : NULL);
           if (Server) BindNetworkBufferToSocket(&NewPlayer->NetBuf,fd);
        #endif
           InitAbilities(NewPlayer);
       t@@ -1665,6 +1680,11 @@ void SetupParameters() {
           CopyMetaServer(&MetaServer,&DefaultMetaServer);
           CopyDrugs(&Drugs,&DefaultDrugs);
        
       +   AssignName(&Socks.name,"socks");
       +   Socks.port = 1080;
       +   Socks.version = 4;
       +   UseSocks = FALSE;
       +
           ResizeLocations(sizeof(DefaultLocation)/sizeof(DefaultLocation[0]));
           for (i=0;i<NumLocation;i++) CopyLocation(&Location[i],&DefaultLocation[i]);
           ResizeCops(sizeof(DefaultCop)/sizeof(DefaultCop[0]));
 (DIR) diff --git a/src/dopewars.h b/src/dopewars.h
       t@@ -148,6 +148,8 @@ extern struct BITCH Bitch;
        extern price_t StartCash,StartDebt;
        extern struct NAMES Names;
        extern struct METASERVER MetaServer;
       +extern SocksServer Socks;
       +extern gboolean UseSocks;
        extern int NumTurns;
        extern int PlayerArmour,BitchArmour;
        extern int LogLevel;
 (DIR) diff --git a/src/error.c b/src/error.c
       t@@ -35,11 +35,6 @@
        #include "error.h"
        #include "nls.h"
        
       -typedef struct _ErrTable {
       -  gint code;
       -  gchar *string;
       -} ErrTable;
       -
        void ClearError(LastError *error) {
          error->type=NULL;
        }
       t@@ -53,8 +48,8 @@ void SetError(LastError *error,ErrorType *type,gint code) {
          error->code=code;
        }
        
       -static void LookupErrorCode(GString *str,gint code,ErrTable *table,
       -                            gchar *fallbackstr) {
       +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));
 (DIR) diff --git a/src/error.h b/src/error.h
       t@@ -49,9 +49,16 @@ typedef enum {
           E_FULLBUF
        } CustomErrorCode;
        
       +typedef struct _ErrTable {
       +  gint code;
       +  gchar *string;
       +} ErrTable;
       +
        void ClearError(LastError *error);
        gboolean IsError(LastError *error);
        void SetError(LastError *error,ErrorType *type,gint code);
       +void LookupErrorCode(GString *str,gint code,ErrTable *table,
       +                     gchar *fallbackstr);
        void g_string_assign_error(GString *str,LastError *error);
        void g_string_append_error(GString *str,LastError *error);
        
 (DIR) diff --git a/src/gtk_client.c b/src/gtk_client.c
       t@@ -283,7 +283,7 @@ void GetClientMessage(gpointer data,gint socket,
           gboolean DoneOK,Connecting;
        
           NetBuf = &ClientData.Play->NetBuf;
       -   Connecting = NetBuf->WaitConnect;
       +   Connecting = NetBuf->status != NBS_CONNECTED;
           if (PlayerHandleNetwork(ClientData.Play,condition&GDK_INPUT_READ,
                                   condition&GDK_INPUT_WRITE,&DoneOK) && !Connecting) {
              while ((pt=GetWaitingPlayerMessage(ClientData.Play))!=NULL) {
       t@@ -291,8 +291,14 @@ void GetClientMessage(gpointer data,gint socket,
                 g_free(pt);
              }
           }
       -   if (Connecting && (!NetBuf->WaitConnect || !DoneOK)) {
       +   if (Connecting && (NetBuf->status==NBS_CONNECTED || !DoneOK)) {
              FinishServerConnect(data,DoneOK);
       +      if (DoneOK) {   /* Just in case, clean up any messages that came in */
       +         while ((pt=GetWaitingPlayerMessage(ClientData.Play))!=NULL) {
       +            HandleClientMessage(pt,ClientData.Play);
       +            g_free(pt);
       +         }
       +      }
           } else if (!DoneOK) {
              if (InGame) {
        /* The network connection to the server was dropped unexpectedly */
 (DIR) diff --git a/src/network.c b/src/network.c
       t@@ -32,6 +32,7 @@
        #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 <pwd.h>        /* For getpwuid */
        #include <string.h>     /* For memcpy, strlen etc. */
        #ifdef HAVE_UNISTD_H
        #include <unistd.h>     /* For close(), various types and constants */
       t@@ -56,6 +57,18 @@
        #define MAXREADBUF   (32768)
        #define MAXWRITEBUF  (65536)
        
       +/* SOCKS5 authentication method codes */
       +typedef enum {
       +   SM_NOAUTH =  0,   /* No authentication required */
       +   SM_GSSAPI =  1,   /* GSSAPI */
       +   SM_USERPWD = 2    /* Username/password authentication */
       +} SocksMethods;
       +
       +static gboolean StartSocksNegotiation(NetworkBuffer *NetBuf,gchar *RemoteHost,
       +                                      unsigned RemotePort);
       +static gchar *GetWaitingData(NetworkBuffer *NetBuf,int numbytes);
       +static gchar *ExpandWriteBuffer(ConnBuf *conn,int numbytes);
       +
        #ifdef CYGWIN
        
        void StartNetworking() {
       t@@ -105,8 +118,12 @@ 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);
       +      (*NetBuf->CallBack)(NetBuf,NetBuf->status!=NBS_PRECONNECT,
       +                          (NetBuf->status==NBS_CONNECTED &&
       +                           NetBuf->WriteBuf.DataPresent) ||
       +                          (NetBuf->status==NBS_SOCKSCONNECT &&
       +                           NetBuf->negbuf.DataPresent) ||
       +                          NetBuf->WaitConnect);
           }
        }
        
       t@@ -114,7 +131,19 @@ static void NetBufCallBackStop(NetworkBuffer *NetBuf) {
           if (NetBuf && NetBuf->CallBack) (*NetBuf->CallBack)(NetBuf,FALSE,FALSE);
        }
        
       -void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar) {
       +static void InitConnBuf(ConnBuf *buf) {
       +   buf->Data=NULL;
       +   buf->Length=0;
       +   buf->DataPresent=0;
       +}
       +
       +static void FreeConnBuf(ConnBuf *buf) {
       +   g_free(buf->Data);
       +   InitConnBuf(buf);
       +}
       +
       +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar,
       +                       SocksServer *socks) {
        /* 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  */
       t@@ -125,10 +154,13 @@ void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar) {
           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;
       +   InitConnBuf(&NetBuf->ReadBuf);
       +   InitConnBuf(&NetBuf->WriteBuf);
       +   InitConnBuf(&NetBuf->negbuf);
           NetBuf->WaitConnect=FALSE;
       +   NetBuf->status = NBS_PRECONNECT;
       +   NetBuf->socks = socks;
       +   NetBuf->host = NULL;
           ClearError(&NetBuf->error);
        }
        
       t@@ -144,6 +176,7 @@ 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;
       +   NetBuf->status=NBS_CONNECTED; /* Assume the socket is connected */
        }
        
        gboolean IsNetworkBufferActive(NetworkBuffer *NetBuf) {
       t@@ -154,10 +187,32 @@ gboolean IsNetworkBufferActive(NetworkBuffer *NetBuf) {
        
        gboolean StartNetworkBufferConnect(NetworkBuffer *NetBuf,gchar *RemoteHost,
                                           unsigned RemotePort) {
       +  gchar *realhost;
       +  unsigned realport;
       +
          ShutdownNetworkBuffer(NetBuf);
       -  if (StartConnect(&NetBuf->fd,RemoteHost,RemotePort,TRUE,&NetBuf->error)) {
       +
       +  if (NetBuf->socks) {
       +    realhost = NetBuf->socks->name;
       +    realport = NetBuf->socks->port;
       +  } else {
       +    realhost = RemoteHost;
       +    realport = RemotePort;
       +  }
       +
       +  if (StartConnect(&NetBuf->fd,realhost,realport,TRUE,&NetBuf->error)) {
            NetBuf->WaitConnect=TRUE;
        
       +    if (NetBuf->socks) {
       +      if (!StartSocksNegotiation(NetBuf,RemoteHost,RemotePort)) {
       +        NetBuf->WaitConnect=FALSE;
       +        return FALSE;
       +      } else {
       +        NetBuf->status   = NBS_SOCKSCONNECT;
       +        NetBuf->sockstat = NBSS_METHODS;
       +      }
       +    }
       +
        /* Notify the owner if necessary to check for the connection completing */
            NetBufCallBack(NetBuf);
        
       t@@ -175,10 +230,13 @@ void ShutdownNetworkBuffer(NetworkBuffer *NetBuf) {
        
           if (NetBuf->fd>=0) CloseSocket(NetBuf->fd);
        
       -   g_free(NetBuf->ReadBuf.Data);
       -   g_free(NetBuf->WriteBuf.Data);
       +   FreeConnBuf(&NetBuf->ReadBuf);
       +   FreeConnBuf(&NetBuf->WriteBuf);
       +   FreeConnBuf(&NetBuf->negbuf);
        
       -   InitNetworkBuffer(NetBuf,NetBuf->Terminator,NetBuf->StripChar);
       +   g_free(NetBuf->host);
       +
       +   InitNetworkBuffer(NetBuf,NetBuf->Terminator,NetBuf->StripChar,NetBuf->socks);
        }
        
        void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds,
       t@@ -191,11 +249,150 @@ void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds,
           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) {
       +   if ((NetBuf->status==NBS_CONNECTED && NetBuf->WriteBuf.DataPresent) ||
       +       (NetBuf->status==NBS_SOCKSCONNECT && NetBuf->negbuf.DataPresent) ||
       +       NetBuf->WaitConnect) {
              FD_SET(NetBuf->fd,writefds);
           }
        }
        
       +typedef enum {
       +   SEC_5FAILURE = 1,
       +   SEC_5RULESET = 2,
       +   SEC_5NETDOWN = 3,
       +   SEC_5UNREACH = 4,
       +   SEC_5CONNREF = 5,
       +   SEC_5TTLEXPIRED = 6,
       +   SEC_5COMMNOSUPP = 7,
       +   SEC_5ADDRNOSUPP = 8,
       +
       +   SEC_REJECT = 91,
       +   SEC_NOIDENTD = 92,
       +   SEC_IDMISMATCH = 93,
       +
       +   SEC_UNKNOWN = 200,
       +   SEC_REPLYVERSION,
       +   SEC_VERSION,
       +   SEC_NOMETHODS
       +} SocksErrorCode;
       +
       +static ErrTable SocksErrStr[] = {
       +/* SOCKS version 5 error messages */
       +  { SEC_5FAILURE,N_("SOCKS server general failure") },
       +  { SEC_5RULESET,N_("Connection denied by SOCKS ruleset") },
       +  { SEC_5NETDOWN,N_("SOCKS: Network unreachable") },
       +  { SEC_5UNREACH,N_("SOCKS: Host unreachable") },
       +  { SEC_5CONNREF,N_("SOCKS: Connection refused") },
       +  { SEC_5TTLEXPIRED,N_("SOCKS: TTL expired") },
       +  { SEC_5COMMNOSUPP,N_("SOCKS: Command not supported") },
       +  { SEC_5ADDRNOSUPP,N_("SOCKS: Address type not supported") },
       +  { SEC_NOMETHODS,N_("SOCKS server rejected all offered methods") },
       +
       +/* SOCKS version 4 error messages */
       +  { SEC_REJECT,N_("SOCKS: Request rejected or failed") },
       +  { SEC_NOIDENTD,N_("SOCKS: Rejected - unable to contact identd") },
       +  { SEC_IDMISMATCH,N_("SOCKS: Rejected - identd reports different user-id") },
       +
       +/* SOCKS errors due to protocol violations */
       +  { SEC_UNKNOWN,N_("Unknown SOCKS reply code") },
       +  { SEC_REPLYVERSION,N_("Unknown SOCKS reply version code") },
       +  { SEC_VERSION,N_("Unknown SOCKS server version") },
       +  { 0,NULL }
       +};
       +
       +static void SocksAppendError(GString *str,LastError *error) {
       +   LookupErrorCode(str,error->code,SocksErrStr,_("SOCKS error code %d"));
       +}
       +
       +static ErrorType ETSocks = { SocksAppendError };
       +
       +static gboolean Socks5Connect(NetworkBuffer *NetBuf) {
       +   guchar *addpt;
       +   guint addlen,hostlen;
       +   ConnBuf *conn;
       +   unsigned short int netport;
       +
       +   conn=&NetBuf->negbuf;
       +   g_assert(NetBuf->host);
       +   hostlen=strlen(NetBuf->host);
       +   if (hostlen>255) return FALSE;
       +
       +   netport = htons(NetBuf->port);
       +   g_assert(sizeof(netport)==2);
       +
       +   addlen = hostlen + 7;
       +   addpt = ExpandWriteBuffer(conn,addlen);
       +   if (!addpt) {
       +      g_print("FIXME: buffer size exceeded\n"); return FALSE;
       +   }
       +   addpt[0] = 5;
       +   addpt[1] = 1;
       +   addpt[2] = 0;
       +   addpt[3] = 3;
       +   addpt[4] = hostlen;
       +   strcpy(&addpt[5],NetBuf->host);
       +   memcpy(&addpt[5+hostlen],&netport,sizeof(netport));
       +
       +   NetBuf->sockstat = NBSS_CONNECT;
       +   g_print("FIXME: SOCKS5 CONNECT request sent\n");
       +
       +   conn->DataPresent+=addlen;
       +
       +/* If the buffer was empty before, we may need to tell the owner to check
       +   the socket for write-ready status */
       +   if ((gchar *)addpt==conn->Data) NetBufCallBack(NetBuf);
       +
       +   return TRUE;
       +}
       +
       +static gboolean HandleSocksReply(NetworkBuffer *NetBuf) {
       +   guchar *data;
       +   gboolean retval=TRUE;
       +   if (NetBuf->socks->version==5) {
       +      if (NetBuf->sockstat == NBSS_METHODS) {
       +         data = GetWaitingData(NetBuf,2);
       +         if (data) {
       +            retval=FALSE;
       +            g_print("FIXME: Reply from SOCKS5 server: %d %d\n",data[0],data[1]);
       +            if (data[0]!=5) {
       +               SetError(&NetBuf->error,&ETSocks,SEC_VERSION);
       +            } else if (data[1]!=0 && data[1]!=2) {
       +               SetError(&NetBuf->error,&ETSocks,SEC_NOMETHODS);
       +            } else {
       +               g_print("FIXME: Using SOCKS5 method %d\n",data[1]);
       +               if (data[1]==SM_NOAUTH) {
       +                  retval=Socks5Connect(NetBuf);
       +               }
       +            }
       +            g_free(data);
       +         }
       +      } else if (NetBuf->sockstat == NBSS_CONNECT) {
       +g_print("FIXME: SOCKS5 connect reply\n");
       +      }
       +      return retval;
       +   } else {
       +      data = GetWaitingData(NetBuf,8);
       +      if (data) {
       +         retval=FALSE;
       +         g_print("FIXME: Reply from SOCKS4 server: %d %d\n",data[0],data[1]);
       +         if (data[0]!=0) {
       +            SetError(&NetBuf->error,&ETSocks,SEC_REPLYVERSION);
       +         } else {
       +            if (data[0]==90) {
       +               NetBuf->status = NBS_CONNECTED;
       +               retval=TRUE;
       +            } else if (data[0]>=SEC_REJECT && data[0]<=SEC_IDMISMATCH) {
       +               SetError(&NetBuf->error,&ETSocks,data[0]);
       +            } else {
       +               SetError(&NetBuf->error,&ETSocks,SEC_UNKNOWN);
       +            }
       +         }
       +         g_free(data);
       +      }
       +      return retval;
       +   }
       +}
       +
        static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady,
                                             gboolean WriteReady,gboolean ErrorReady,
                                             gboolean *ReadOK,gboolean *WriteOK,
       t@@ -214,6 +411,7 @@ static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady,
                 retval=FinishConnect(NetBuf->fd,&NetBuf->error);
                 ConnectDone=TRUE;
                 NetBuf->WaitConnect=FALSE;
       +         if (!NetBuf->socks) NetBuf->status = NBS_CONNECTED;
        
                 if (!retval) {
                    *WriteOK=FALSE;
       t@@ -224,7 +422,14 @@ static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady,
        
              if (ReadReady) {
                 *ReadOK=ReadDataFromWire(NetBuf);
       -         if (NetBuf->ReadBuf.DataPresent>0) DataWaiting=TRUE;
       +         if (NetBuf->ReadBuf.DataPresent>0 &&
       +             NetBuf->status==NBS_SOCKSCONNECT) {
       +            if (!HandleSocksReply(NetBuf)) *ErrorOK=FALSE;
       +         }
       +         if (NetBuf->ReadBuf.DataPresent>0 &&
       +             NetBuf->status!=NBS_SOCKSCONNECT) {
       +            DataWaiting=TRUE;
       +         }
              }
           }
        
       t@@ -238,7 +443,11 @@ static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady,
        /* If we just connected, then no need to listen for write-ready status
           any more */
              NetBufCallBack(NetBuf);
       -   } else if (WriteReady && NetBuf->WriteBuf.DataPresent==0) {
       +   } else if (WriteReady && 
       +              ((NetBuf->status==NBS_CONNECTED &&
       +                NetBuf->WriteBuf.DataPresent==0) ||
       +               (NetBuf->status==NBS_SOCKSCONNECT &&
       +                NetBuf->negbuf.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);
       t@@ -296,6 +505,21 @@ gint CountWaitingMessages(NetworkBuffer *NetBuf) {
           return msgs;
        }
        
       +gchar *GetWaitingData(NetworkBuffer *NetBuf,int numbytes) {
       +   ConnBuf *conn;
       +   gchar *data;
       +   conn=&NetBuf->ReadBuf;
       +   if (!conn->Data || conn->DataPresent < numbytes) return NULL;
       +
       +   data = g_new(gchar,numbytes);
       +   memcpy(data,conn->Data,numbytes);
       +
       +   memmove(&conn->Data[0],&conn->Data[numbytes],conn->DataPresent-numbytes);
       +   conn->DataPresent-=numbytes;
       +
       +   return data;
       +}
       +
        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 */
       t@@ -307,7 +531,9 @@ gchar *GetWaitingMessage(NetworkBuffer *NetBuf) {
           char *SepPt;
           gchar *NewMessage;
           conn=&NetBuf->ReadBuf;
       -   if (!conn->Data || !conn->DataPresent) return NULL;
       +   if (!conn->Data || !conn->DataPresent/* || NetBuf->status!=NBS_CONNECTED*/) {
       +      return NULL;
       +   }
           SepPt=memchr(conn->Data,NetBuf->Terminator,conn->DataPresent);
           if (!SepPt) return NULL;
           *SepPt='\0';
       t@@ -366,40 +592,137 @@ gboolean ReadDataFromWire(NetworkBuffer *NetBuf) {
           return TRUE;
        }
        
       +gchar *ExpandWriteBuffer(ConnBuf *conn,int numbytes) {
       +   int newlen;
       +   newlen = conn->DataPresent + numbytes;
       +   if (newlen > conn->Length) {
       +      conn->Length*=2;
       +      conn->Length=MAX(conn->Length,newlen);
       +      if (conn->Length > MAXWRITEBUF) conn->Length=MAXWRITEBUF;
       +      if (newlen > conn->Length) return NULL;
       +      conn->Data=g_realloc(conn->Data,conn->Length);
       +   }
       +
       +   return (&conn->Data[conn->DataPresent]);
       +}
       +
        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;
       +   gchar *addpt;
       +   guint addlen;
           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 (!data) return;
       +   addlen = strlen(data)+1;
       +   addpt = ExpandWriteBuffer(conn,addlen);
       +   if (!addpt) return;
       +
       +   memcpy(addpt,data,addlen);
       +   conn->DataPresent+=addlen;
       +   addpt[addlen-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);
       +   if (addpt==conn->Data) 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.              */
       +static struct hostent *LookupHostname(gchar *host,LastError *error) {
       +   struct hostent *he;
       +   if ((he=gethostbyname(host))==NULL) {
       +#ifdef CYGWIN
       +      if (error) SetError(error,ET_WINSOCK,WSAGetLastError());
       +#else
       +      if (error) SetError(error,ET_HERRNO,h_errno);
       +#endif
       +   }
       +   return he;
       +}
       +
       +gboolean StartSocksNegotiation(NetworkBuffer *NetBuf,gchar *RemoteHost,
       +                               unsigned RemotePort) {
       +   static SocksMethods methods[] = {
       +      SM_NOAUTH, SM_GSSAPI, SM_USERPWD
       +   };
       +   guint num_methods = sizeof(methods)/sizeof(methods[0]);
           ConnBuf *conn;
       +   struct hostent *he;
       +   guchar *addpt;
       +   guint addlen,i;
       +   struct in_addr *h_addr;
       +   unsigned short int netport;
       +   struct passwd *pwd;
       +
       +   conn=&NetBuf->negbuf;
       +
       +   if (NetBuf->socks->version==5) {
       +      addlen=2+num_methods;
       +      addpt = ExpandWriteBuffer(conn,addlen);
       +      if (!addpt) {
       +         g_print("FIXME: buffer size exceeded\n"); return FALSE;
       +      }
       +      addpt[0] = 5;   /* SOCKS version 5 */
       +      addpt[1] = num_methods;
       +      for (i=0;i<num_methods;i++) {
       +         addpt[2+i] = (guchar)methods[i];
       +      }
       +      conn->DataPresent+=addlen;
       +
       +      g_free(NetBuf->host);
       +      NetBuf->host = g_strdup(RemoteHost);
       +      NetBuf->port = RemotePort;
       +
       +/* If the buffer was empty before, we may need to tell the owner to check
       +   the socket for write-ready status */
       +      if ((gchar *)addpt==conn->Data) NetBufCallBack(NetBuf);
       +
       +      return TRUE;
       +   }
       +
       +   he = LookupHostname(RemoteHost,&NetBuf->error);
       +   if (!he) return FALSE;
       +
       +   pwd = getpwuid(getuid());
       +   if (!pwd || !pwd->pw_name) return FALSE;
       +g_print("username %s\n",pwd->pw_name);
       +
       +   addlen=9+strlen(pwd->pw_name);
       +
       +   h_addr = (struct in_addr *)he->h_addr;
       +   g_assert(sizeof(struct in_addr)==4);
       +
       +   netport = htons(RemotePort);
       +   g_assert(sizeof(netport)==2);
       +
       +   addpt = ExpandWriteBuffer(conn,addlen);
       +   if (!addpt) {
       +      g_print("FIXME: buffer size exceeded\n"); return FALSE;
       +   }
       +
       +   addpt[0] = 4;  /* SOCKS version */
       +   addpt[1] = 1;  /* CONNECT */
       +   memcpy(&addpt[2],&netport,sizeof(netport));
       +   memcpy(&addpt[4],h_addr,sizeof(struct in_addr));
       +   strcpy(&addpt[8],pwd->pw_name);
       +   addpt[addlen-1] = '\0';
       +
       +   g_print("FIXME: SOCKS CONNECT request sent\n");
       +   
       +   conn->DataPresent+=addlen;
       +
       +/* If the buffer was empty before, we may need to tell the owner to check
       +   the socket for write-ready status */
       +   if ((gchar *)addpt==conn->Data) NetBufCallBack(NetBuf);
       +
       +   return TRUE;
       +}
       +
       +static gboolean WriteBufToWire(NetworkBuffer *NetBuf,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);
       t@@ -433,6 +756,17 @@ gboolean WriteDataToWire(NetworkBuffer *NetBuf) {
           return TRUE;
        }
        
       +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.              */
       +   if (NetBuf->status==NBS_SOCKSCONNECT) {
       +      return WriteBufToWire(NetBuf,&NetBuf->negbuf);
       +   } else {
       +      return WriteBufToWire(NetBuf,&NetBuf->WriteBuf);
       +   }
       +}
       +
        static void SendHttpRequest(HttpConnection *conn) {
           GString *text;
           char *userpasswd;
       t@@ -495,7 +829,7 @@ gboolean OpenHttpConnection(HttpConnection **connpt,gchar *HostName,
           g_assert(HostName && Method && Query && connpt);
        
           conn=g_new0(HttpConnection,1);
       -   InitNetworkBuffer(&conn->NetBuf,'\n','\r');
       +   InitNetworkBuffer(&conn->NetBuf,'\n','\r',NULL);
           conn->HostName=g_strdup(HostName);
           if (Proxy && Proxy[0]) conn->Proxy=g_strdup(Proxy);
           conn->Method=g_strdup(Method);
       t@@ -687,14 +1021,9 @@ gboolean StartConnect(int *fd,gchar *RemoteHost,unsigned RemotePort,
           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;
       -   }
       +   he = LookupHostname(RemoteHost,error);
       +   if (!he) return FALSE;
       +
           *fd=socket(AF_INET,SOCK_STREAM,0);
           if (*fd==SOCKET_ERROR) {
        #ifdef CYGWIN
 (DIR) diff --git a/src/network.h b/src/network.h
       t@@ -65,19 +65,45 @@ typedef struct _NetworkBuffer NetworkBuffer;
        
        typedef void (*NBCallBack)(NetworkBuffer *NetBuf,gboolean Read,gboolean Write);
        
       +/* Information about a SOCKS server */
       +typedef struct _SocksServer {
       +   gchar *name;     /* hostname */
       +   unsigned port;   /* port number */
       +   int version;     /* desired protocol version (usually 4 or 5) */
       +} SocksServer;
       +
       +/* The status of a network buffer */
       +typedef enum {
       +   NBS_PRECONNECT,    /* Socket is not connected */
       +   NBS_SOCKSCONNECT,  /* A CONNECT request is being sent to a SOCKS server */
       +   NBS_CONNECTED      /* Socket is connected */
       +} NBStatus;
       +
       +/* Status of a SOCKS v5 negotiation */
       +typedef enum {
       +   NBSS_METHODS,      /* Negotiation of available methods */
       +   NBSS_CONNECT       /* CONNECT request is being sent */
       +} NBSocksStatus;
       +
        /* 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 */
       +   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 */
       +   ConnBuf negbuf;         /* Output for protocol negotiation (e.g. SOCKS) */
       +   gboolean WaitConnect;   /* TRUE if a non-blocking connect is in progress */
       +   NBStatus status;        /* Status of the connection (if any) */
       +   NBSocksStatus sockstat; /* Status of SOCKS negotiation (if any) */
       +   SocksServer *socks;     /* If non-NULL, a SOCKS server to use */
       +   gchar *host;            /* If non-NULL, the host to connect to */
       +   unsigned port;          /* If non-NULL, the port to connect to */
       +   LastError error;        /* Any error from the last operation */
        };
        
        /* Keeps track of the progress of an HTTP connection */
       t@@ -114,7 +140,8 @@ struct _HttpConnection {
           HttpStatus Status;
        };
        
       -void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar);
       +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator,char StripChar,
       +                       SocksServer *socks);
        void SetNetworkBufferCallBack(NetworkBuffer *NetBuf,NBCallBack CallBack,
                                      gpointer CallBackData);
        gboolean IsNetworkBufferActive(NetworkBuffer *NetBuf);