diff src/protocols/icq/tcplink.c @ 2086:424a40f12a6c

[gaim-migrate @ 2096] moving protocols from plugins/ to src/protocols. making it so that you can select which protocols are compiled statically. committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Tue, 31 Jul 2001 01:00:39 +0000
parents
children f0a2a9afdb77
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/protocols/icq/tcplink.c	Tue Jul 31 01:00:39 2001 +0000
@@ -0,0 +1,902 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/*
+ * $Id: tcplink.c 2096 2001-07-31 01:00:39Z warmenhoven $
+ *
+ * Copyright (C) 1998-2001, Denis V. Dmitrienko <denis@null.net> and
+ *                          Bill Soudan <soudan@kde.org>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <stdlib.h>
+
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef _WIN32
+#include <winsock.h>
+#define EINPROGRESS   WSAEINPROGRESS
+#define ENETUNREACH   WSAENETUNREACH
+#define ECONNREFUSED  WSAECONNREFUSED
+#define ETIMEDOUT     WSAETIMEDOUT
+#define EOPNOTSUPP    WSAEOPNOTSUPP
+#define EAFNOSUPPORT  WSAEAFNOSUPPORT
+#define EWOULDBLOCK   WSAEWOULDBLOCK
+#else
+#include <netdb.h>
+#endif
+
+#include "icqlib.h"
+#include "stdpackets.h"
+#include "tcp.h"
+#include "errno.h"
+#include "chatsession.h"
+#include "filesession.h"
+#include "contacts.h"
+
+icq_TCPLink *icq_TCPLinkNew(icq_Link *icqlink)
+{
+  icq_TCPLink *p=(icq_TCPLink *)malloc(sizeof(icq_TCPLink));
+
+  p->socket=-1;
+  p->icqlink=icqlink;
+  p->mode=0;
+  p->session=0L;
+  p->type=TCP_LINK_MESSAGE;
+  p->buffer_count=0;
+  p->send_queue=icq_ListNew();
+  p->received_queue=icq_ListNew();
+  p->id=0;
+  p->remote_uin=0;
+  p->remote_version=0;
+  p->flags=0;
+  p->proxy_status = 0;
+  p->connect_timeout = NULL;
+
+  if(p)
+    icq_ListEnqueue(icqlink->d->icq_TCPLinks, p);
+
+  return p;
+}
+
+int _icq_TCPLinkDelete(void *pv, va_list data)
+{
+  icq_Packet *p=(icq_Packet *)pv;
+  icq_Link *icqlink=va_arg(data, icq_Link *);
+
+  /* notify the app the packet didn't make it */
+  if(p->id)
+    invoke_callback(icqlink, icq_RequestNotify)(icqlink, p->id,
+      ICQ_NOTIFY_FAILED, 0, 0);
+
+  return 0;
+}
+
+void icq_TCPLinkDelete(void *pv)
+{
+  icq_TCPLink *p=(icq_TCPLink *)pv;
+
+  /* process anything left in the received queue */
+  icq_TCPLinkProcessReceived(p);
+
+  /* make sure we notify app that packets in send queue didn't make it */
+  (void)icq_ListTraverse(p->send_queue, _icq_TCPLinkDelete, p->icqlink);
+
+  /* destruct all packets still waiting on queues */
+  icq_ListDelete(p->send_queue, icq_PacketDelete);
+  icq_ListDelete(p->received_queue, icq_PacketDelete);
+
+  /* if this is a chat or file link, delete the associated session as
+   * well, but make sure we unassociate ourself first so the session
+   * doesn't try to close us */
+  if(p->session)
+  {
+    if(p->type==TCP_LINK_CHAT)
+    {
+      icq_ChatSession *psession=p->session;
+      psession->tcplink=NULL;
+      icq_ChatSessionClose(psession);
+    }
+
+    if(p->type==TCP_LINK_FILE) {
+      icq_FileSession *psession=p->session;
+      psession->tcplink=NULL;
+      icq_FileSessionClose(psession);
+    }
+  }
+
+  /* close the socket after we notify app so app can read errno if necessary */
+  if (p->socket > -1)
+  {
+    icq_SocketDelete(p->socket);
+  }
+
+  if (p->connect_timeout)
+  {
+    icq_TimeoutDelete(p->connect_timeout);
+  }
+
+  free(p);
+}
+
+void icq_TCPLinkClose(icq_TCPLink *plink)
+{
+  icq_ListRemove(plink->icqlink->d->icq_TCPLinks, plink);
+  icq_TCPLinkDelete(plink);
+}
+
+int icq_TCPLinkProxyConnect(icq_TCPLink *plink, DWORD uin, int port)
+{
+  struct sockaddr_in prsin;
+  struct hostent *host_struct;
+  int conct;
+
+  (void)uin; (void)port;
+
+  prsin.sin_addr.s_addr = htonl(plink->icqlink->icq_ProxyIP);
+  if(prsin.sin_addr.s_addr  == (unsigned long)-1)
+  {
+    prsin.sin_addr.s_addr = inet_addr(plink->icqlink->icq_ProxyHost);
+    if(prsin.sin_addr.s_addr  == (unsigned long)-1) /* name isn't n.n.n.n so must be DNS */
+    {
+      host_struct = gethostbyname(plink->icqlink->icq_ProxyHost);
+      if(host_struct == 0L)
+      {
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Can't find hostname: %s\n",
+                   plink->icqlink->icq_ProxyHost);
+        return -1;
+      }
+      prsin.sin_addr = *((struct in_addr *)host_struct->h_addr);
+    }
+  }
+  prsin.sin_family = AF_INET; /* we're using the inet not appletalk*/
+  prsin.sin_port = htons(plink->icqlink->icq_ProxyPort); /* port */
+/*   flags = fcntl(plink->socket, F_GETFL, 0); */
+/*   fcntl(plink->socket, F_SETFL, flags & (~O_NONBLOCK)); */
+  plink->mode |= TCP_LINK_SOCKS_CONNECTING;
+  conct = connect(plink->socket, (struct sockaddr *) &prsin, sizeof(prsin));
+  if(conct == -1) /* did we connect ?*/
+  {
+    if(errno != EINPROGRESS)
+    {
+      conct = errno;
+      icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Connection refused\n");
+      return conct;
+    }
+    return 1;
+  }
+  return 0;
+}
+
+int icq_TCPLinkProxyRequestAuthorization(icq_TCPLink *plink)
+{
+  char buf[1024];
+
+  int hasName = plink->icqlink->icq_ProxyName &&
+    strlen(plink->icqlink->icq_ProxyName);
+  int hasPass = plink->icqlink->icq_ProxyPass &&
+    strlen(plink->icqlink->icq_ProxyPass);
+  int authEnabled = hasName && hasPass && plink->icqlink->icq_ProxyAuth;
+
+  plink->mode = (plink->mode & (~TCP_LINK_SOCKS_CONNECTING));
+  buf[0] = 5; /* protocol version */
+  buf[1] = 1; /* number of methods */
+  buf[2] = authEnabled ? 2 : 0; /* authentication method */
+
+  plink->mode |= authEnabled ? TCP_LINK_SOCKS_AUTHORIZATION :
+    TCP_LINK_SOCKS_NOAUTHSTATUS;
+
+#ifdef _WIN32
+  if(send(plink->socket, buf, 3, 0) != 3)
+    return errno;
+#else
+  if(write(plink->socket, buf, 3) != 3)
+    return errno;
+#endif
+  return 0;
+}
+
+int icq_TCPLinkProxyAuthorization(icq_TCPLink *plink)
+{
+  int res;
+  char buf[1024];
+
+  plink->mode &= ~TCP_LINK_SOCKS_AUTHORIZATION;
+  plink->mode |= TCP_LINK_SOCKS_AUTHSTATUS;
+
+#ifdef _WIN32
+  res = recv(plink->socket, buf, 2, 0);
+#else
+  res = read(plink->socket, buf, 2);
+#endif
+  if(res != 2 || buf[0] != 5 || buf[1] != 2) /* username/password authentication*/
+  {
+    icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Authentication method incorrect\n");
+    icq_SocketDelete(plink->socket);
+    return -1;
+  }
+  buf[0] = 1; /* version of subnegotiation */
+  buf[1] = strlen(plink->icqlink->icq_ProxyName);
+  memcpy(&buf[2], plink->icqlink->icq_ProxyName, buf[1]);
+  buf[2+buf[1]] = strlen(plink->icqlink->icq_ProxyPass);
+  memcpy(&buf[3+buf[1]], plink->icqlink->icq_ProxyPass, buf[2+buf[1]]);
+#ifdef _WIN32
+  if(send(plink->socket, buf, buf[1]+buf[2+buf[1]]+3, 0) != buf[1] + buf[2+buf[1]]+3)
+    return errno;
+#else
+  if(write(plink->socket, buf, buf[1]+buf[2+buf[1]]+3) != buf[1] + buf[2+buf[1]]+3)
+    return errno;
+#endif
+  return 0;
+}
+
+int icq_TCPLinkProxyAuthStatus(icq_TCPLink *plink)
+{
+  int res;
+  char buf[20];
+
+  plink->mode = (plink->mode & (~TCP_LINK_SOCKS_AUTHSTATUS)) | TCP_LINK_SOCKS_CROSSCONNECT;
+#ifdef _WIN32
+  res = recv(plink->socket, buf, 2, 0);
+#else
+  res = read(plink->socket, buf, 2);
+#endif
+  if(res != 2 || buf[0] != 1 || buf[1] != 0)
+  {
+    icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Authorization failure\n");
+    icq_SocketDelete(plink->socket);
+    return -1;
+  }
+  return 0;
+}
+
+int icq_TCPLinkProxyNoAuthStatus(icq_TCPLink *plink)
+{
+  int res;
+  char buf[20];
+
+  plink->mode = (plink->mode & (~TCP_LINK_SOCKS_NOAUTHSTATUS)) | TCP_LINK_SOCKS_CROSSCONNECT;
+#ifdef _WIN32
+  res = recv(plink->socket, buf, 2, 0);
+#else
+  res = read(plink->socket, buf, 2);
+#endif
+  if(res != 2 || buf[0] != 5 || buf[1] != 0) /* no authentication required */
+  {
+    icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Authentication method incorrect\n");
+    icq_SocketDelete(plink->socket);
+    return -1;
+  }
+  return 0;
+}
+
+int icq_TCPLinkProxyCrossConnect(icq_TCPLink *plink)
+{
+  char buf[20];
+
+  plink->mode = (plink->mode & ~(TCP_LINK_SOCKS_CROSSCONNECT)) | TCP_LINK_SOCKS_CONNSTATUS;
+  buf[0] = 5; /* protocol version */
+  buf[1] = 1; /* command connect */
+  buf[2] = 0; /* reserved */
+  buf[3] = 1; /* address type IP v4 */
+  memcpy(&buf[4], &plink->remote_address.sin_addr.s_addr, 4);
+  memcpy(&buf[8], &plink->remote_address.sin_port, 2);
+#ifdef _WIN32
+  if(send(plink->socket, buf, 10, 0) != 10)
+    return errno;
+#else
+  if(write(plink->socket, buf, 10) != 10)
+    return errno;
+#endif
+  return 0;
+}
+
+int icq_TCPLinkProxyConnectStatus(icq_TCPLink *plink)
+{
+  int res;
+  char buf[1024];
+
+  plink->mode = (plink->mode & (~TCP_LINK_SOCKS_CONNSTATUS));
+#ifdef _WIN32
+  res = recv(plink->socket, buf, 10, 0);
+#else
+  res = read(plink->socket, buf, 10);
+#endif
+  if(res != 10 || buf[0] != 5 || buf[1] != 0)
+  {
+    switch(buf[1])
+    {
+      case 1:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] General SOCKS server failure\n");
+        res = EFAULT;
+        break;
+      case 2:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Connection not allowed by ruleset\n");
+        res = EACCES;
+        break;
+      case 3:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Network unreachable\n");
+        res = ENETUNREACH;
+        break;
+      case 4:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Host unreachable\n");
+        res = ENETUNREACH;
+        break;
+      case 5:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Connection refused\n");
+        res = ECONNREFUSED;
+        break;
+      case 6:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] TTL expired\n");
+        res = ETIMEDOUT;
+        break;
+      case 7:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Command not supported\n");
+        res = EOPNOTSUPP;
+        break;
+      case 8:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Address type not supported\n");
+        res = EAFNOSUPPORT;
+        break;
+      default:
+        icq_FmtLog(plink->icqlink, ICQ_LOG_FATAL, "[SOCKS] Unknown SOCKS server failure\n");
+        res = EFAULT;
+        break;
+    }
+    icq_SocketDelete(plink->socket);
+    return res;
+  }
+  return 0;
+}
+
+int icq_TCPLinkConnect(icq_TCPLink *plink, DWORD uin, int port)
+{
+  icq_ContactItem *pcontact=icq_ContactFind(plink->icqlink, uin);
+  icq_Packet *p;
+  int result;
+
+#ifndef _WIN32
+  int flags;
+#else
+  u_long iosflag;
+#endif
+
+  /* these return values never and nowhere checked */
+  /*                                   denis.      */
+  if(!pcontact)
+    return -2;
+
+  if((plink->socket=icq_SocketNew(AF_INET, SOCK_STREAM, 0)) < 0)
+    return -3;
+
+/*   bzero(&(plink->remote_address), sizeof(plink->remote_address));   Win32 incompatible... */
+  memset(&(plink->remote_address), 0, sizeof(plink->remote_address));
+  plink->remote_address.sin_family = AF_INET;
+
+  /* if our IP is the same as the remote user's ip, connect to real_ip
+     instead since we're both probably behind a firewall */
+  icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE,
+    "local IP is %08X:%d, remote real IP is %08X:%d, remote IP is %08X:%d, port is %d\n",
+    plink->icqlink->icq_OurIP,
+    plink->icqlink->icq_OurPort,
+    pcontact->remote_real_ip,
+    pcontact->remote_port,
+    pcontact->remote_ip,
+    pcontact->remote_port,
+    port
+    );
+  if (plink->icqlink->icq_OurIP == pcontact->remote_ip) 
+    plink->remote_address.sin_addr.s_addr = htonl(pcontact->remote_real_ip);
+  else 
+    plink->remote_address.sin_addr.s_addr = htonl(pcontact->remote_ip);
+
+  if(plink->type==TCP_LINK_MESSAGE)
+  {
+    plink->remote_address.sin_port = htons(pcontact->remote_port);
+    icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE, 
+      "initiating message connect to %d (%s:%d)\n", uin, 
+      inet_ntoa(*((struct in_addr *)(&(plink->remote_address.sin_addr)))),
+      pcontact->remote_port);
+  }
+  else
+  {
+  plink->remote_address.sin_port = htons(port);
+    icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE, 
+      "initiating file/chat connect to %d (%s:%d)\n", uin, 
+      inet_ntoa(*((struct in_addr *)(&(plink->remote_address.sin_addr)))),
+      port);
+  }
+
+  /* set the socket to non-blocking */
+#ifdef _WIN32
+  iosflag = TRUE;
+  ioctlsocket(plink->socket, FIONBIO, &iosflag);
+#else
+  flags=fcntl(plink->socket, F_GETFL, 0);
+  fcntl(plink->socket, F_SETFL, flags | O_NONBLOCK);
+#endif
+
+  if(!plink->icqlink->icq_UseProxy)
+    result=connect(plink->socket, (struct sockaddr *)&(plink->remote_address),
+       sizeof(plink->remote_address));
+  else /* SOCKS proxy support */
+    result=icq_TCPLinkProxyConnect(plink, uin, port);
+  /* FIXME: Here we should check for errors on connection */
+  /* because of proxy support - it can't be checked       */
+  /* by getsockopt() later in _handle_ready_sockets()     */
+  /*                                  denis.              */
+
+  plink->mode|=TCP_LINK_MODE_CONNECTING;
+
+  plink->remote_uin=uin;
+
+  /* Send the hello packet */
+  p=icq_TCPCreateInitPacket(plink);
+  icq_TCPLinkSend(plink, p);
+
+#ifdef TCP_PACKET_TRACE
+  printf("hello packet queued for %lu\n", uin);
+#endif /* TCP_PACKET_TRACE */
+
+  icq_SocketSetHandler(plink->socket, ICQ_SOCKET_WRITE,
+    icq_TCPLinkOnConnect, plink);
+  plink->connect_timeout=icq_TimeoutNew(TCP_LINK_CONNECT_TIMEOUT,
+    (icq_TimeoutHandler)icq_TCPLinkClose, plink);
+  
+  return 1;
+}
+
+icq_TCPLink *icq_TCPLinkAccept(icq_TCPLink *plink)
+{
+#ifdef _WIN32
+  u_long iosflag;
+#else
+  int flags;
+#endif
+  int socket_fd;
+  size_t remote_length;
+  icq_TCPLink *pnewlink=icq_TCPLinkNew(plink->icqlink);
+  
+  if(pnewlink)
+  {
+    remote_length = sizeof(struct sockaddr_in);
+    socket_fd=icq_SocketAccept(plink->socket,
+      (struct sockaddr *)&(plink->remote_address), &remote_length);
+
+    icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE,
+      "accepting tcp connection from %s:%d\n",
+      inet_ntoa(*((struct in_addr *)(&(plink->remote_address.sin_addr)))),
+      ntohs(plink->remote_address.sin_port));
+
+    /* FIXME: make sure accept succeeded */
+
+    pnewlink->type=plink->type;
+    pnewlink->socket=socket_fd;
+
+    /* first packet sent on an icq tcp link is always the hello packet */
+    pnewlink->mode|=TCP_LINK_MODE_HELLOWAIT;
+
+    /* install socket handler for new socket */
+    icq_SocketSetHandler(socket_fd, ICQ_SOCKET_READ, 
+      icq_TCPLinkOnDataReceived, pnewlink);
+  }
+
+  /* set the socket to non-blocking */
+#ifdef _WIN32
+  iosflag = TRUE;
+  ioctlsocket(pnewlink->socket, FIONBIO, &iosflag);
+#else
+  flags=fcntl(pnewlink->socket, F_GETFL, 0);
+  fcntl(pnewlink->socket, F_SETFL, flags | O_NONBLOCK);
+#endif
+  
+  return pnewlink;
+}
+
+int icq_TCPLinkListen(icq_TCPLink *plink)
+{
+  unsigned int t;
+
+  /* listening links have 0 uin */
+  plink->remote_uin=0;
+
+  /* create tcp listen socket */
+  if((plink->socket=icq_SocketNew(AF_INET, SOCK_STREAM, 0)) < 0)
+    return -1;
+
+  /* must use memset, no bzero for Win32! */
+  memset(&plink->socket_address, 0, sizeof(struct sockaddr_in));
+  plink->socket_address.sin_family=AF_INET;
+  plink->socket_address.sin_addr.s_addr=htonl(INADDR_ANY);
+  plink->socket_address.sin_port=0;
+
+  if(bind(plink->socket, (struct sockaddr *)&plink->socket_address, sizeof(struct sockaddr_in)) < 0)
+    return -2;
+
+  if(listen(plink->socket, 5) < 0)
+    return -3;
+
+  t=sizeof(struct sockaddr_in);
+  if(getsockname(plink->socket, (struct sockaddr *)&plink->socket_address, &t) < 0)
+    return -4;
+
+  icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE,
+             "created tcp listening socket %d, local address=%s:%d\n",
+             plink->socket,
+             inet_ntoa(*((struct in_addr *)(&plink->socket_address.sin_addr))),
+             ntohs(plink->socket_address.sin_port));
+
+  plink->mode|=TCP_LINK_MODE_LISTEN;
+
+  icq_SocketSetHandler(plink->socket, ICQ_SOCKET_READ, icq_TCPLinkAccept,
+    plink);
+
+  return 0;
+}
+
+/* Doing Cyrillic translations for Chat dialog sessions */
+void icq_ChatRusConv_n(const char to[4], char *t_in, int t_len)
+{
+  int i, j;
+  
+  for(i = j = 0; i < t_len; ++i)
+  {
+    if((((unsigned char)t_in[i]) < ' ') && (t_in[i] != '\r'))
+    {
+      if(i - 1 > j)
+        icq_RusConv_n(to, &t_in[j], i - j - 1);
+      switch(t_in[i])
+      {
+        case '\x07': /* Bell */
+        case '\x08': /* BackSpace */
+        case '\x03': /* Chat is active */
+        case '\x04': /* Chat is not active */
+             break;
+        case '\x00': /* Foregroung color (RR GG BB ?? ) */
+        case '\x01': /* Background color (RR GG BB ?? ) */
+        case '\x11': /* Font style change (Bold - 1, Italic - 2, Underline - 4) */
+        case '\x12': /* Font size change */
+             i += 4;
+             break;
+        case '\x10': /* Font family and encoding change */
+             i += t_in[i+1] + 2 + 2;
+             icq_RusConv_n(to, &t_in[i+3], t_in[i+1]);
+             break;
+      }
+      j = i + 1;
+    }
+  }
+  if(i > t_len)
+    i = t_len;
+  if(j > t_len)
+    j = t_len;
+  if(i > j)
+    icq_RusConv_n(to, &t_in[j], i - j);
+}
+
+void icq_TCPLinkOnDataReceived(icq_TCPLink *plink)
+{
+  int process_count=0, recv_result=0;
+  char *buffer=plink->buffer;
+
+  do { /* while recv_result > 0 */
+
+    int done=0;
+
+    /* append received data onto end of buffer */
+    if((recv_result=recv(plink->socket, buffer+plink->buffer_count,
+      icq_TCPLinkBufferSize-plink->buffer_count, 0)) < 1)
+    {
+      /* either there was an error or the remote side has closed 
+       * the connection - fall out of the loop */
+      continue;
+    };
+  
+    plink->buffer_count+=recv_result;
+
+#ifdef TCP_BUFFER_TRACE
+    printf("received %d bytes from link %x, new buffer count %d\n",
+      recv_result, plink, plink->buffer_count);
+
+    hex_dump(plink->buffer, plink->buffer_count);
+#endif /*TCP_BUFFER_TRACE*/
+
+    process_count+=recv_result;
+
+    /* don't do any packet processing if we're in raw mode */
+    if(plink->mode & TCP_LINK_MODE_RAW) {
+      /* notify the app with the new data */
+      if(plink->type == TCP_LINK_CHAT)
+        icq_ChatRusConv_n("wk", plink->buffer, plink->buffer_count);
+      invoke_callback(plink->icqlink, icq_ChatNotify)(plink->session,
+        CHAT_NOTIFY_DATA, plink->buffer_count, plink->buffer);
+      plink->buffer_count=0;
+      continue;
+    }
+
+    /* remove packets from the buffer until the buffer is empty
+     * or the remaining bytes do not equal a full packet */
+    while((unsigned)plink->buffer_count>sizeof(WORD) && !done)
+    {
+      WORD packet_size=(*((WORD *)buffer));
+
+      /* warn if the buffer is too small to hold the whole packet */
+      if(packet_size>icq_TCPLinkBufferSize-sizeof(WORD))
+      {
+        icq_FmtLog(plink->icqlink, ICQ_LOG_WARNING, "tcplink buffer "
+          "overflow, packet size = %d, buffer size = %d, closing link\n",
+          packet_size, icq_TCPLinkBufferSize);
+        return;
+      }
+
+      if(packet_size+sizeof(WORD) <= (unsigned)plink->buffer_count)
+      {
+        /* copy the packet into memory */
+        icq_Packet *p=icq_PacketNew();
+        icq_PacketAppend(p, buffer+sizeof(WORD), packet_size);
+
+        /* remove it from the buffer */
+        memcpy(buffer, buffer+packet_size+sizeof(WORD),
+             plink->buffer_count-packet_size-sizeof(WORD));
+
+        plink->buffer_count-=(packet_size+sizeof(WORD));
+
+        icq_TCPLinkOnPacketReceived(plink, p);
+      }
+      else
+      {
+        /* not enough bytes in buffer to form the complete packet.
+         * we're done for now */
+        done=1;
+      }
+    } /* while packets remain in buffer */
+
+  } while (recv_result > 0);
+
+#ifdef _WIN32
+  if (recv_result <= 0 && WSAGetLastError()!=EWOULDBLOCK) {
+    /* receive error - log it */
+    icq_FmtLog(plink->icqlink, ICQ_LOG_WARNING, "recv failed from %d (%d),"
+      " closing link\n", plink->remote_uin, WSAGetLastError());
+#else
+  if (recv_result <= 0 && errno!=EWOULDBLOCK) {
+    /* receive error - log it */
+    icq_FmtLog(plink->icqlink, ICQ_LOG_WARNING, "recv failed from %d (%d-%s),"
+      " closing link\n", plink->remote_uin, errno, strerror(errno));
+#endif
+
+    icq_TCPLinkClose(plink);
+
+  } else {
+
+    icq_TCPLinkProcessReceived(plink);
+
+  }
+
+}
+
+void icq_TCPLinkOnPacketReceived(icq_TCPLink *plink, icq_Packet *p)
+{
+
+#ifdef TCP_RAW_TRACE
+  printf("packet received! { length=%d }\n", p->length);
+  icq_PacketDump(p);
+#endif
+
+  /* Stick packet on ready packet linked icq_List */
+  icq_ListEnqueue(plink->received_queue, p);
+}
+
+void icq_TCPLinkOnConnect(icq_TCPLink *plink)
+{
+#ifdef _WIN32
+  int len;
+#else
+  size_t len;
+#endif
+  int error;
+  
+  icq_TimeoutDelete(plink->connect_timeout);
+  plink->connect_timeout = NULL;
+
+  /* check getsockopt */
+  len=sizeof(error);
+
+#ifndef __BEOS__
+#ifdef _WIN32
+  getsockopt(plink->socket, SOL_SOCKET, SO_ERROR, (char *)&error, &len);
+#else
+  getsockopt(plink->socket, SOL_SOCKET, SO_ERROR, &error, &len);
+#endif
+#endif
+  if(!error && (plink->mode & (TCP_LINK_SOCKS_CONNECTING | TCP_LINK_SOCKS_AUTHORIZATION |
+                               TCP_LINK_SOCKS_AUTHSTATUS | TCP_LINK_SOCKS_NOAUTHSTATUS |
+                               TCP_LINK_SOCKS_CROSSCONNECT | TCP_LINK_SOCKS_CONNSTATUS)))
+  {
+    if(plink->mode & TCP_LINK_SOCKS_CONNECTING)
+       error = icq_TCPLinkProxyRequestAuthorization(plink);
+    else if(plink->mode & TCP_LINK_SOCKS_AUTHORIZATION)
+      error = icq_TCPLinkProxyAuthorization(plink);
+    else if(plink->mode & TCP_LINK_SOCKS_AUTHSTATUS)
+      error = icq_TCPLinkProxyAuthStatus(plink);
+    else if(plink->mode & TCP_LINK_SOCKS_NOAUTHSTATUS)
+      error = icq_TCPLinkProxyNoAuthStatus(plink);
+    else if(plink->mode & TCP_LINK_SOCKS_CROSSCONNECT)
+      error = icq_TCPLinkProxyCrossConnect(plink);
+    else if(plink->mode & TCP_LINK_SOCKS_CONNSTATUS)
+      error = icq_TCPLinkProxyConnectStatus(plink);
+    else
+      error = EINVAL;
+  }
+
+  if(error)
+  {
+    /* connection failed- close the link, which takes care
+     * of notifying the app about packets that didn't make it */
+    icq_FmtLog(plink->icqlink, ICQ_LOG_WARNING, "connect failed to %d (%d-%s),"
+       " closing link\n", plink->remote_uin, error, strerror(error));
+
+    icq_TCPLinkClose(plink);
+    return;
+  }
+
+  if(plink->mode & (TCP_LINK_SOCKS_CONNECTING | TCP_LINK_SOCKS_AUTHORIZATION | TCP_LINK_SOCKS_AUTHSTATUS | TCP_LINK_SOCKS_NOAUTHSTATUS | TCP_LINK_SOCKS_CROSSCONNECT | TCP_LINK_SOCKS_CONNSTATUS))
+  {
+    icq_SocketSetHandler(plink->socket, ICQ_SOCKET_WRITE, NULL, NULL);
+    icq_SocketSetHandler(plink->socket, ICQ_SOCKET_READ, 
+      icq_TCPLinkOnConnect, plink);
+    return;
+  }
+
+  len=sizeof(plink->socket_address);
+  getsockname(plink->socket, (struct sockaddr *)&plink->socket_address, &len);
+
+  icq_FmtLog(plink->icqlink, ICQ_LOG_MESSAGE,
+             "connected to uin %d, socket=%d local address=%s:%d remote address=%s:%d\n",
+             plink->remote_uin, plink->socket,
+             inet_ntoa(*((struct in_addr *)(&plink->socket_address.sin_addr))),
+             ntohs(plink->socket_address.sin_port),
+             inet_ntoa(*((struct in_addr *)(&plink->remote_address.sin_addr))),
+             ntohs(plink->remote_address.sin_port));
+
+  plink->mode&= ~TCP_LINK_MODE_CONNECTING;
+
+  icq_SocketSetHandler(plink->socket, ICQ_SOCKET_READ, 
+    icq_TCPLinkOnDataReceived, plink);
+  icq_SocketSetHandler(plink->socket, ICQ_SOCKET_WRITE, NULL, NULL);
+
+  /* socket is now connected, notify each request that connection
+   * has been established and send pending data */
+  while(plink->send_queue->count>0)
+  {
+    icq_Packet *p=icq_ListDequeue(plink->send_queue);
+    if(p->id)
+      if(plink->icqlink->icq_RequestNotify)
+        (*plink->icqlink->icq_RequestNotify)(plink->icqlink, p->id, ICQ_NOTIFY_CONNECTED, 0, 0);
+    icq_TCPLinkSend(plink, p);
+  }
+
+  /* yeah this probably shouldn't be here.  oh well :) */
+  if(plink->type==TCP_LINK_CHAT)
+  {
+    icq_ChatSessionSetStatus((icq_ChatSession *)plink->session,
+      CHAT_STATUS_CONNECTED);
+    icq_ChatSessionSetStatus((icq_ChatSession *)plink->session, 
+      CHAT_STATUS_WAIT_ALLINFO);
+  }
+
+  if(plink->type==TCP_LINK_FILE)
+  {
+    icq_FileSessionSetStatus((icq_FileSession *)plink->session,
+      FILE_STATUS_CONNECTED);
+  }
+
+}
+
+unsigned long icq_TCPLinkSendSeq(icq_TCPLink *plink, icq_Packet *p,
+  unsigned long sequence)
+{
+  /* append the next sequence number on the packet */
+  if (!sequence)
+    sequence=plink->icqlink->d->icq_TCPSequence--;
+  p->id=sequence;
+  icq_PacketEnd(p);
+  icq_PacketAppend32(p, sequence);
+
+  /* if the link is currently connecting, queue the packets for
+   * later, else send immediately */
+  if(plink->mode & TCP_LINK_MODE_CONNECTING) {
+    icq_ListInsert(plink->send_queue, 0, p);
+    if(plink->icqlink->icq_RequestNotify)
+      (*plink->icqlink->icq_RequestNotify)(plink->icqlink, p->id, ICQ_NOTIFY_CONNECTING, 0, 0);
+  } else {
+    icq_PacketSend(p, plink->socket);
+    if(p->id)
+      if(plink->icqlink->icq_RequestNotify)
+        (*plink->icqlink->icq_RequestNotify)(plink->icqlink, p->id, ICQ_NOTIFY_SENT, 0, 0);
+    icq_PacketDelete(p);
+  }
+  return sequence;
+}
+
+void icq_TCPLinkSend(icq_TCPLink *plink, icq_Packet *p)
+{
+  /* if the link is currently connecting, queue the packets for
+   * later, else send immediately */
+  if(plink->mode & TCP_LINK_MODE_CONNECTING) {
+    icq_ListInsert(plink->send_queue, 0, p);
+    if(plink->icqlink->icq_RequestNotify)
+      (*plink->icqlink->icq_RequestNotify)(plink->icqlink, p->id, ICQ_NOTIFY_CONNECTING, 0, 0);
+  } else {
+    icq_PacketSend(p, plink->socket);
+    if(p->id)
+      if(plink->icqlink->icq_RequestNotify)
+        (*plink->icqlink->icq_RequestNotify)(plink->icqlink, p->id, ICQ_NOTIFY_SENT, 0, 0);
+    icq_PacketDelete(p);
+  }
+}
+
+void icq_TCPLinkProcessReceived(icq_TCPLink *plink)
+{
+  icq_List *plist=plink->received_queue;
+  while(plist->count>0)
+
+  {
+    icq_Packet *p=icq_ListDequeue(plist);
+
+    if(plink->mode & TCP_LINK_MODE_HELLOWAIT)
+    {
+      icq_TCPProcessHello(p, plink);
+    }
+    else
+    {
+
+      switch (plink->type) {
+
+        case TCP_LINK_MESSAGE:
+          icq_TCPProcessPacket(p, plink);
+          break;
+
+        case TCP_LINK_CHAT:
+          icq_TCPProcessChatPacket(p, plink);
+          break;
+
+        case TCP_LINK_FILE:
+          icq_TCPProcessFilePacket(p, plink);
+          break;
+
+      }
+    }
+
+    icq_PacketDelete(p);
+  }
+
+}
+
+int _icq_FindTCPLink(void *p, va_list data)
+{
+  icq_TCPLink *plink=(icq_TCPLink *)p;
+  unsigned long uin=va_arg(data, unsigned long);
+  int type=va_arg(data, int);
+
+  return ( (plink->remote_uin == uin ) && (plink->type == type) );
+}
+
+icq_TCPLink *icq_FindTCPLink(icq_Link *icqlink, unsigned long uin, int type)
+{
+  return icq_ListTraverse(icqlink->d->icq_TCPLinks, _icq_FindTCPLink, uin, type);
+}