changeset 1081:efcacae6acdb

[gaim-migrate @ 1091] libfaim connects non-blocking committer: Tailor Script <tailor@pidgin.im>
author Eric Warmenhoven <eric@warmenhoven.org>
date Fri, 10 Nov 2000 22:49:02 +0000
parents e6637ff33957
children 13df4e342cff
files doc/CREDITS libfaim/CHANGES libfaim/aim_conn.c libfaim/aim_login.c libfaim/aim_rxhandlers.c libfaim/aim_rxqueue.c libfaim/aim_tlv.c libfaim/aim_txqueue.c libfaim/faim/aim.h libfaim/faim/aim_cbtypes.h plugins/spellchk.c src/buddy.c src/oscar.c
diffstat 13 files changed, 724 insertions(+), 134 deletions(-) [+]
line wrap: on
line diff
--- a/doc/CREDITS	Fri Nov 10 05:54:04 2000 +0000
+++ b/doc/CREDITS	Fri Nov 10 22:49:02 2000 +0000
@@ -47,6 +47,7 @@
 G. Sumner Hayes <IM: SumnerFool> Security Patches
 Brian Ryner for a little make file patch :)
 Ryan C. Gordon
+Elliot Tobin <elliot@bha.udel.edu>
 
 A big thanks to the X-Chat developers, who were kind enough to
 license X-Chat under the GPL so that I could learn to be as cool
--- a/libfaim/CHANGES	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/CHANGES	Fri Nov 10 22:49:02 2000 +0000
@@ -1,6 +1,39 @@
 
 No release numbers
 ------------------
+ - Fri Nov 10 08:24:34 UTC 2000
+  - Add sess->flags (replaces sess->snaclogin)
+  - Remove odd setstatus call in chat parser
+  - Remove aim_tx_enqueue macro, replace with a smarter one
+    - If a connection is in progress, enqueue instead of calling
+      the client-specified queuer
+  - Add support for nonblocking connects through the 
+     AIM_SESS_FLAG_NONBLOCKCONNECT flag to aim_session_init()
+  - Add AIM_CB_SPECIAL_CONNCOMPLETE callback. Not real useful.
+  - Add AIM_CB_SPECIAL_FLAPVER callback. Can be used as an alternate
+     way of starting the login process, or just to look like you 
+     know what you're doing. Fixed associated inconsistencies
+     in aim_rxhandlers too.
+  - Fix some connection status stupidities in faimtest.
+
+ - Wed Nov  8 13:11:18 UTC 2000
+  - Reenable/reimplement older login, but only use for ICQ UINs
+    - This is a fairly ugly hack.  But...eh.  It works.
+    - You'll need to remove the aim_sendconnack() before the
+      call to aim_request_login.  It will now do it automatically
+      if its needed.  (hint: ***CLIENT CHANGE***)
+
+ - Wed Nov  8 02:23:25 UTC 2000
+  - I'm trying out using a modified version of the kernel-doc scripts
+     to help document the libfaim code a bit.  See aim_conn.c and 
+     aim_tlv.c, as well as the scripts in utils/docs.
+
+ - Wed Oct 25 17:52:20 UTC 2000
+  - Cleanup warnings from newer gcc's (RH7.0)
+    - Aparently newer gcc's don't let you do as strong of integer types
+      on va_arg as older ones... So, let me know if this breaks on your
+      compiler
+
  - Tue Oct 17 02:10:13 UTC 2000
   - Rearrange a few things
 
--- a/libfaim/aim_conn.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_conn.c	Fri Nov 10 22:49:02 2000 +0000
@@ -14,8 +14,12 @@
 #include <netinet/in.h>
 #endif
 
-/*
- * Clears out connection list, killing remaining connections.
+/**
+ * aim_connrst - Clears out connection list, killing remaining connections.
+ * @sess: Session to be cleared
+ *
+ * Clears out the connection list and kills any connections left.
+ *
  */
 faim_internal void aim_connrst(struct aim_session_t *sess)
 {
@@ -34,8 +38,12 @@
   return;
 }
 
-/*
- * Gets a new connection structure.
+/**
+ * aim_conn_getnext - Gets a new connection structure.
+ * @sess: Session
+ *
+ * Allocate a new empty connection structure.
+ *
  */
 faim_internal struct aim_conn_t *aim_conn_getnext(struct aim_session_t *sess)
 {
@@ -61,6 +69,13 @@
   return newconn;
 }
 
+/**
+ * aim_conn_init - Reset a connection to default values.
+ * @deadconn: Connection to be reset
+ *
+ * Initializes and/or resets a connection structure.
+ *
+ */
 static void aim_conn_init(struct aim_conn_t *deadconn)
 {
   if (!deadconn)
@@ -80,6 +95,14 @@
   return;
 }
 
+/**
+ * aim_conn_kill - Close and free a connection.
+ * @sess: Session for the connection
+ * @deadconn: Connection to be freed
+ *
+ * Close, clear, and free a connection structure.
+ *
+ */
 faim_export void aim_conn_kill(struct aim_session_t *sess, struct aim_conn_t **deadconn)
 {
   struct aim_conn_t *cur;
@@ -117,6 +140,13 @@
   return;
 }
 
+/**
+ * aim_conn_close - Close a connection
+ * @deadconn: Connection to close
+ *
+ * Close (but not free) a connection.
+ *
+ */
 faim_export void aim_conn_close(struct aim_conn_t *deadconn)
 {
   int typesav = -1, subtypesav = -1;
@@ -147,6 +177,16 @@
   return;
 }
 
+/**
+ * aim_getconn_type - Find a connection of a specific type
+ * @sess: Session to search
+ * @type: Type of connection to look for
+ *
+ * Searches for a connection of the specified type in the 
+ * specified session.  Returns the first connection of that
+ * type found.
+ *
+ */
 faim_internal struct aim_conn_t *aim_getconn_type(struct aim_session_t *sess,
 						  int type)
 {
@@ -154,15 +194,24 @@
 
   faim_mutex_lock(&sess->connlistlock);
   for (cur = sess->connlist; cur; cur = cur->next) {
-    if (cur->type == type)
+    if ((cur->type == type) && !(cur->status & AIM_CONN_STATUS_INPROGRESS))
       break;
   }
   faim_mutex_unlock(&sess->connlistlock);
   return cur;
 }
 
-/*
- * An extrememly quick and dirty SOCKS5 interface. 
+/**
+ * aim_proxyconnect - An extrememly quick and dirty SOCKS5 interface. 
+ * @sess: Session to connect
+ * @host: Host to connect to
+ * @port: Port to connect to
+ * @statusret: Return value of the connection
+ *
+ * Attempts to connect to the specified host via the configured
+ * proxy settings, if present.  If no proxy is configured for
+ * this session, the connection is done directly.
+ *
  */
 static int aim_proxyconnect(struct aim_session_t *sess, 
 			    char *host, unsigned short port,
@@ -295,13 +344,24 @@
       return -1;
     }
 
-    memset(&sa.sin_zero, 0, 8);
+    memset(&sa, 0, sizeof(struct sockaddr_in));
     sa.sin_port = htons(port);
     memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
     sa.sin_family = hp->h_addrtype;
   
     fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
+
+    if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT)
+      fcntl(fd, F_SETFL, O_NONBLOCK); /* XXX save flags */
+
     if (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
+      if (sess->flags & AIM_SESS_FLAGS_NONBLOCKCONNECT) {
+	if ((errno == EINPROGRESS) || (errno == EINTR)) {
+	  if (statusret)
+	    *statusret |= AIM_CONN_STATUS_INPROGRESS;
+	  return fd;
+	}
+      }
       close(fd);
       fd = -1;
     }
@@ -309,10 +369,16 @@
   return fd;
 }
 
-/*
- * aim_newconn(type, dest)
+/**
+ * aim_newconn - Open a new connection
+ * @sess: Session to create connection in
+ * @type: Type of connection to create
+ * @dest: Host to connect to (in "host:port" syntax)
  *
- * Opens a new connection to the specified dest host of type type.
+ * Opens a new connection to the specified dest host of specified
+ * type, using the proxy settings if available.  If @host is %NULL,
+ * the connection is allocated and returned, but no connection 
+ * is made.
  *
  * FIXME: Return errors in a more sane way.
  *
@@ -375,6 +441,14 @@
   return connstruct;
 }
 
+/**
+ * aim_conngetmaxfd - Return the highest valued file discriptor in session
+ * @sess: Session to search
+ *
+ * Returns the highest valued filed descriptor of all open 
+ * connections in @sess.
+ *
+ */
 faim_export int aim_conngetmaxfd(struct aim_session_t *sess)
 {
   int j = 0;
@@ -390,6 +464,13 @@
   return j;
 }
 
+/**
+ * aim_countconn - Return the number of open connections in the session
+ * @sess: Session to look at
+ *
+ * Returns the number of number connections in @sess.
+ *
+ */
 static int aim_countconn(struct aim_session_t *sess)
 {
   int cnt = 0;
@@ -403,6 +484,15 @@
   return cnt;
 }
 
+/**
+ * aim_conn_in_sess - Predicate to test the precense of a connection in a sess
+ * @sess: Session to look in
+ * @conn: Connection to look for
+ *
+ * Searches @sess for the passed connection.  Returns 1 if its present,
+ * zero otherwise.
+ *
+ */
 faim_export int aim_conn_in_sess(struct aim_session_t *sess, struct aim_conn_t *conn)
 {
   struct aim_conn_t *cur;
@@ -417,16 +507,19 @@
   return 0;
 }
 
-/*
- * aim_select(timeout)
+/**
+ * aim_select - Wait for a socket with data or timeout
+ * @sess: Session to wait on
+ * @timeout: How long to wait
+ * @status: Return status
  *
  * Waits for a socket with data or for timeout, whichever comes first.
- * See select(2).
+ * See select().
  * 
  * Return codes in *status:
- *   -1  error in select() (NULL returned)
- *    0  no events pending (NULL returned)
- *    1  outgoing data pending (NULL returned)
+ *   -1  error in select() (%NULL returned)
+ *    0  no events pending (%NULL returned)
+ *    1  outgoing data pending (%NULL returned)
  *    2  incoming data pending (connection with pending data returned)
  *
  * XXX: we could probably stand to do a little courser locking here.
@@ -436,9 +529,9 @@
 					  struct timeval *timeout, int *status)
 {
   struct aim_conn_t *cur;
-  fd_set fds;
+  fd_set fds, wfds;
   int maxfd = 0;
-  int i;
+  int i, haveconnecting = 0;
 
   faim_mutex_lock(&sess->connlistlock);
   if (sess->connlist == NULL) {
@@ -448,32 +541,50 @@
   }
   faim_mutex_unlock(&sess->connlistlock);
 
-  /* 
-   * If we have data waiting to be sent, return immediatly
-   */
-  if (sess->queue_outgoing != NULL) {
-    *status = 1;
-    return NULL;
-  } 
-
   FD_ZERO(&fds);
+  FD_ZERO(&wfds);
   maxfd = 0;
 
   faim_mutex_lock(&sess->connlistlock);
   for (cur = sess->connlist; cur; cur = cur->next) {
+    if (cur->status & AIM_CONN_STATUS_INPROGRESS) {
+      FD_SET(cur->fd, &wfds);
+      haveconnecting++;
+    }
     FD_SET(cur->fd, &fds);
     if (cur->fd > maxfd)
       maxfd = cur->fd;
   }
   faim_mutex_unlock(&sess->connlistlock);
 
-  if ((i = select(maxfd+1, &fds, NULL, NULL, timeout))>=1) {
+  /* 
+   * If we have data waiting to be sent, return
+   *
+   * We have to not do this if theres at least one
+   * connection thats still connecting, since that connection
+   * may have queued data and this return would prevent
+   * the connection from ever completing!  This is a major
+   * inadequacy of the libfaim way of doing things.  It means
+   * that nothing can transmit as long as there's connecting
+   * sockets. Evil.
+   *
+   * But its still better than having blocking connects.
+   *
+   */
+  if (!haveconnecting && (sess->queue_outgoing != NULL)) {
+    *status = 1;
+    return NULL;
+  } 
+
+  if ((i = select(maxfd+1, &fds, &wfds, NULL, timeout))>=1) {
     faim_mutex_lock(&sess->connlistlock);
     for (cur = sess->connlist; cur; cur = cur->next) {
-      if (FD_ISSET(cur->fd, &fds)) {
+      if ((FD_ISSET(cur->fd, &fds)) || 
+	  ((cur->status & AIM_CONN_STATUS_INPROGRESS) && 
+	   FD_ISSET(cur->fd, &wfds))) {
 	*status = 2;
 	faim_mutex_unlock(&sess->connlistlock);
-	return cur;
+	return cur; /* XXX race condition here -- shouldnt unlock connlist */
       }
     }
     *status = 0; /* shouldn't happen */
@@ -486,6 +597,16 @@
   return NULL;  /* no waiting or error, return */
 }
 
+/**
+ * aim_conn_isready - Test if a connection is marked ready
+ * @conn: Connection to test
+ *
+ * Returns true if the connection is ready, false otherwise.
+ * Returns -1 if the connection is invalid.
+ *
+ * XXX: This is deprecated.
+ *
+ */
 faim_export int aim_conn_isready(struct aim_conn_t *conn)
 {
   if (conn)
@@ -493,6 +614,17 @@
   return -1;
 }
 
+/**
+ * aim_conn_setstatus - Set the status of a connection
+ * @conn: Connection
+ * @status: New status
+ *
+ * @newstatus is %XOR'd with the previous value of the connection
+ * status and returned.  Returns -1 if the connection is invalid.
+ *
+ * This isn't real useful.
+ *
+ */
 faim_export int aim_conn_setstatus(struct aim_conn_t *conn, int status)
 {
   int val;
@@ -506,6 +638,20 @@
   return val;
 }
 
+/**
+ * aim_conn_setlatency - Set a forced latency value for connection
+ * @conn: Conn to set latency for
+ * @newval: Number of seconds to force between transmits
+ *
+ * Causes @newval seconds to be spent between transmits on a connection.
+ *
+ * This is my lame attempt at overcoming not understanding the rate
+ * limiting. 
+ *
+ * XXX: This should really be replaced with something that scales and
+ * backs off like the real rate limiting does.
+ *
+ */
 faim_export int aim_conn_setlatency(struct aim_conn_t *conn, int newval)
 {
   if (!conn)
@@ -519,12 +665,18 @@
   return 0;
 }
 
-/*
+/**
+ * aim_setupproxy - Configure a proxy for this session
+ * @sess: Session to set proxy for
+ * @server: SOCKS server
+ * @username: SOCKS username
+ * @password: SOCKS password
+ *
  * Call this with your SOCKS5 proxy server parameters before
- * the first call to aim_newconn().  If called with all NULL
+ * the first call to aim_newconn().  If called with all %NULL
  * args, it will clear out a previously set proxy.  
  *
- * Set username and password to NULL if not applicable.
+ * Set username and password to %NULL if not applicable.
  *
  */
 faim_export void aim_setupproxy(struct aim_session_t *sess, char *server, char *username, char *password)
@@ -545,7 +697,15 @@
   return;
 }
 
-faim_export void aim_session_init(struct aim_session_t *sess)
+/**
+ * aim_session_init - Initializes a session structure
+ * @sess: Session to initialize
+ * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together.
+ *
+ * Sets up the initial values for a session.
+ *
+ */
+faim_export void aim_session_init(struct aim_session_t *sess, unsigned long flags)
 {
   if (!sess)
     return;
@@ -558,6 +718,15 @@
   aim_initsnachash(sess);
   sess->snac_nextid = 0x00000001;
 
+  sess->flags = 0;
+
+  /*
+   * Default to SNAC login unless XORLOGIN is explicitly set.
+   */
+  if (!(flags & AIM_SESS_FLAGS_XORLOGIN))
+    sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+  sess->flags |= flags;
+
   /*
    * This must always be set.  Default to the queue-based
    * version for back-compatibility.  
@@ -566,3 +735,75 @@
 
   return;
 }
+
+/**
+ * aim_conn_isconnecting - Determine if a connection is connecting
+ * @conn: Connection to examine
+ *
+ * Returns nonzero if the connection is in the process of
+ * connecting (or if it just completed and aim_conn_completeconnect()
+ * has yet to be called on it).
+ *
+ */
+faim_export int aim_conn_isconnecting(struct aim_conn_t *conn)
+{
+  if (!conn)
+    return 0;
+  return (conn->status & AIM_CONN_STATUS_INPROGRESS)?1:0;
+}
+
+faim_export int aim_conn_completeconnect(struct aim_session_t *sess, struct aim_conn_t *conn)
+{
+  fd_set fds, wfds;
+  struct timeval tv;
+  int res, error = ETIMEDOUT;
+  rxcallback_t userfunc;
+
+  if (!conn || (conn->fd == -1))
+    return -1;
+
+  if (!(conn->status & AIM_CONN_STATUS_INPROGRESS))
+    return -1;
+
+  FD_ZERO(&fds);
+  FD_SET(conn->fd, &fds);
+  FD_ZERO(&wfds);
+  FD_SET(conn->fd, &wfds);
+  tv.tv_sec = 0;
+  tv.tv_usec = 0;
+
+  if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) {
+    error = errno;
+    aim_conn_close(conn);
+    errno = error;
+    return -1;
+  } else if (res == 0) {
+    printf("faim: aim_conn_completeconnect: false alarm on %d\n", conn->fd);
+    return 0; /* hasn't really completed yet... */
+  } 
+
+  if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) {
+    int len = sizeof(error);
+
+    if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
+      error = errno;
+  }
+
+  if (error) {
+    aim_conn_close(conn);
+    errno = error;
+    return -1;
+  }
+
+  fcntl(conn->fd, F_SETFL, 0); /* XXX should restore original flags */
+
+  conn->status &= ~AIM_CONN_STATUS_INPROGRESS;
+
+  if ((userfunc = aim_callhandler(conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE)))
+    userfunc(sess, NULL, conn);
+
+  /* Flush out the queues if there was something waiting for this conn  */
+  aim_tx_flushqueue(sess);
+
+  return 0;
+}
--- a/libfaim/aim_login.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_login.c	Fri Nov 10 22:49:02 2000 +0000
@@ -10,6 +10,7 @@
 #include "md5.h"
 
 static int aim_encode_password_md5(const char *password, const char *key, md5_byte_t *digest);
+static int aim_encode_password(const char *password, unsigned char *encoded);
 
 /*
  * FIXME: Reimplement the TIS stuff.
@@ -49,19 +50,76 @@
 				  struct aim_conn_t *conn, 
 				  char *sn)
 {
-  int curbyte=0;
-  
+  int curbyte;
   struct command_tx_struct *newpacket;
 
+  if (!sess || !conn || !sn)
+    return -1;
+
+  /*
+   * For ICQ, we enable the ancient horrible login and stuff
+   * a key packet into the queue to make it look like we got
+   * a reply back. This is so the client doesn't know we're
+   * really not doing MD5 login.
+   *
+   * This may sound stupid, but I'm not in the best of moods and 
+   * I don't plan to keep support for this crap around much longer.
+   * Its all AOL's fault anyway, really. I hate AOL.  Really.  They
+   * always seem to be able to piss me off by doing the dumbest little
+   * things.  Like disabling MD5 logins for ICQ UINs, or adding purposefully
+   * wrong TLV lengths, or adding superfluous information to host strings,
+   * or... I'll stop.
+   *
+   */
+  if ((sn[0] >= '0') && (sn[0] <= '9')) {
+    struct command_rx_struct *newrx;
+    int i;
+
+    if (!(newrx = (struct command_rx_struct *)malloc(sizeof(struct command_rx_struct))))
+      return -1;
+    memset(newrx, 0x00, sizeof(struct command_rx_struct));
+    newrx->lock = 1; 
+    newrx->hdrtype = AIM_FRAMETYPE_OSCAR;
+    newrx->hdr.oscar.type = 0x02;
+    newrx->hdr.oscar.seqnum = 0;
+    newrx->commandlen = 10+2+1;
+    newrx->nofree = 0; 
+    if (!(newrx->data = malloc(newrx->commandlen))) {
+      free(newrx);
+      return -1;
+    }
+
+    i = aim_putsnac(newrx->data, 0x0017, 0x0007, 0x0000, 0x0000);
+    i += aimutil_put16(newrx->data+i, 0x01);
+    i += aimutil_putstr(newrx->data+i, "0", 1);
+
+    newrx->conn = conn;
+
+    newrx->next = sess->queue_incoming;
+    sess->queue_incoming = newrx;
+
+    newrx->lock = 0;
+
+    sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN;
+
+    return 0;
+  } 
+
+  sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
+
+  aim_sendconnack(sess, conn);
+
   if (!(newpacket = aim_tx_new(AIM_FRAMETYPE_OSCAR, 0x0002, conn, 10+2+2+strlen(sn))))
     return -1;
 
   newpacket->lock = 1;
   
-  curbyte += aim_putsnac(newpacket->data+curbyte, 0x0017, 0x0006, 0x0000, 0x00010000);
+  curbyte  = aim_putsnac(newpacket->data, 0x0017, 0x0006, 0x0000, 0x00010000);
   curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0001, strlen(sn), sn);
 
+  newpacket->commandlen = curbyte;
   newpacket->lock = 0;
+
   return aim_tx_enqueue(sess, newpacket);
 }
 
@@ -82,8 +140,6 @@
 				char *key)
 {
   int curbyte=0;
-  md5_byte_t digest[16];
-  
   struct command_tx_struct *newpacket;
 
   if (!clientinfo || !sn || !password)
@@ -94,27 +150,52 @@
 
   newpacket->lock = 1;
 
-  newpacket->hdr.oscar.type = 0x02;
+  newpacket->hdr.oscar.type = (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)?0x02:0x01;
   
-  curbyte = aim_putsnac(newpacket->data+curbyte, 0x0017, 0x0002, 0x0000, 0x00010000);
+  if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)
+    curbyte = aim_putsnac(newpacket->data, 0x0017, 0x0002, 0x0000, 0x00010000);
+  else {
+    curbyte  = aimutil_put16(newpacket->data, 0x0000);
+    curbyte += aimutil_put16(newpacket->data+curbyte, 0x0001);
+  }
 
-  curbyte+= aim_puttlv_str(newpacket->data+curbyte, 0x0001, strlen(sn), sn);
+  curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0001, strlen(sn), sn);
   
-  aim_encode_password_md5(password, key, digest);
-  curbyte+= aim_puttlv_str(newpacket->data+curbyte, 0x0025, 16, (char *)digest);
-  
+  if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN) {
+    md5_byte_t digest[16];
+
+    aim_encode_password_md5(password, key, digest);
+    curbyte+= aim_puttlv_str(newpacket->data+curbyte, 0x0025, 16, (char *)digest);
+  } else { 
+    char *password_encoded;
+
+    password_encoded = (char *) malloc(strlen(password));
+    aim_encode_password(password, password_encoded);
+    curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0002, strlen(password), password_encoded);
+    free(password_encoded);
+  }
+
   /* XXX is clientstring required by oscar? */
   if (strlen(clientinfo->clientstring))
     curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0003, strlen(clientinfo->clientstring), clientinfo->clientstring);
 
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0016, (unsigned short)clientinfo->major2);
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0017, (unsigned short)clientinfo->major);
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0018, (unsigned short)clientinfo->minor);
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0019, (unsigned short)clientinfo->minor2);
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x001a, (unsigned short)clientinfo->build);
+  if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN) {
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0016, (unsigned short)clientinfo->major2);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0017, (unsigned short)clientinfo->major);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0018, (unsigned short)clientinfo->minor);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0019, (unsigned short)clientinfo->minor2);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x001a, (unsigned short)clientinfo->build);
   
-  curbyte += aim_puttlv_32(newpacket->data+curbyte, 0x0014, clientinfo->unknown);
-  curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0009, 0x0015);
+    curbyte += aim_puttlv_32(newpacket->data+curbyte, 0x0014, clientinfo->unknown);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0009, 0x0015);
+  } else {
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0016, 0x010a);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0017, 0x0004);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0018, 0x003c);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x0019, 0x0001);
+    curbyte += aim_puttlv_16(newpacket->data+curbyte, 0x001a, 0x0cce);
+    curbyte += aim_puttlv_32(newpacket->data+curbyte, 0x0014, 0x00000055);
+  }
 
   if (strlen(clientinfo->country))
     curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x000e, strlen(clientinfo->country), clientinfo->country);
@@ -145,25 +226,23 @@
   return 0;
 }
 
-/*
- *  int encode_password(
- *                      const char *password,
- *	                char *encoded
- *	                ); 
+/**
+ * aim_encode_password - Encode a password using old XOR method
+ * @password: incoming password
+ * @encoded: buffer to put encoded password
  *
  * This takes a const pointer to a (null terminated) string
  * containing the unencoded password.  It also gets passed
  * an already allocated buffer to store the encoded password.
  * This buffer should be the exact length of the password without
- * the null.  The encoded password buffer IS NOT NULL TERMINATED.
+ * the null.  The encoded password buffer /is not %NULL terminated/.
  *
  * The encoding_table seems to be a fixed set of values.  We'll
  * hope it doesn't change over time!  
  *
- * NOTE: This is no longer used. Its here for historical reference.
+ * This is only used for the XOR method, not the better MD5 method.
  *
  */
-#if 0
 static int aim_encode_password(const char *password, unsigned char *encoded)
 {
   u_char encoding_table[] = {
@@ -187,7 +266,6 @@
 
   return 0;
 }
-#endif
 
 /*
  * This is sent back as a general response to the login command.
@@ -215,7 +293,10 @@
    * For SNAC login, there's a 17/3 SNAC header in front.
    *
    */
-  tlvlist = aim_readtlvchain(command->data+10, command->commandlen-10);
+  if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)
+    tlvlist = aim_readtlvchain(command->data+10, command->commandlen-10);
+  else
+    tlvlist = aim_readtlvchain(command->data, command->commandlen);
 
   /*
    * No matter what, we should have a screen name.
--- a/libfaim/aim_rxhandlers.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_rxhandlers.c	Fri Nov 10 22:49:02 2000 +0000
@@ -343,16 +343,17 @@
 	workingPtr->handled = 1;
 	break;
       case AIM_CONN_TYPE_AUTH: {
-	u_long head;
+	unsigned long head;
 	
 	head = aimutil_get32(workingPtr->data);
-	if (head == 0x00000001) {
+	if ((head == 0x00000001) && (workingPtr->commandlen == 4)) {
 	  faimdprintf(1, "got connection ack on auth line\n");
-	  workingPtr->handled = 1;
-	} else if (workingPtr->hdr.oscar.type == 0x0004) {
+	  workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, workingPtr);
+	} else if (workingPtr->hdr.oscar.type == 0x04) {
+	  /* Used only by the older login protocol */
 	  workingPtr->handled = aim_authparse(sess, workingPtr);
         } else {
-	  u_short family,subtype;
+	  unsigned short family,subtype;
 	  
 	  family = aimutil_get16(workingPtr->data);
 	  subtype = aimutil_get16(workingPtr->data+2);
@@ -404,7 +405,7 @@
 	switch (family) {
 	case 0x0000: /* not really a family, but it works */
 	  if (subtype == 0x0001)
-	    workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, 0x0000, 0x0001, workingPtr);
+	    workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, workingPtr);
 	  else
 	    workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_UNKNOWN, workingPtr);
 	  break;
@@ -540,10 +541,9 @@
 	u_short subtype;
 	family = aimutil_get16(workingPtr->data);
 	subtype= aimutil_get16(workingPtr->data+2);
-	
-	if ((family == 0x0002) && (subtype == 0x0006)) {
-	  workingPtr->handled = 1;
-	  aim_conn_setstatus(workingPtr->conn, AIM_CONN_STATUS_READY);
+
+	if ((family == 0x0000) && (subtype == 0x00001)) {
+	  workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, workingPtr);
 	} else if ((family == 0x000d) && (subtype == 0x0009)) {
 	  workingPtr->handled = aim_chatnav_parse_info(sess, workingPtr);
 	} else {
@@ -557,9 +557,9 @@
 	family = aimutil_get16(workingPtr->data);
 	subtype= aimutil_get16(workingPtr->data+2);
 	
-	if ((family == 0x0000) && (subtype == 0x00001))
-	  workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, 0x0000, 0x0001, workingPtr);
-	else if (family == 0x0001) {
+	if ((family == 0x0000) && (subtype == 0x00001)) {
+	  workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, workingPtr);
+	} else if (family == 0x0001) {
 	  if (subtype == 0x0001)
 	    workingPtr->handled = aim_callhandler_noparam(sess, workingPtr->conn, 0x0001, 0x0001, workingPtr);
 	  else if (subtype == 0x0003)
@@ -803,6 +803,9 @@
 {
   u_int i = 0;
 
+  if (!sess || !command)
+    return 1;
+
   faimdprintf(1, "\nRecieved unknown packet:");
 
   for (i = 0; i < command->commandlen; i++)
--- a/libfaim/aim_rxqueue.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_rxqueue.c	Fri Nov 10 22:49:02 2000 +0000
@@ -60,6 +60,9 @@
   if (conn->fd < 3)  /* can happen when people abuse the interface */
     return 0;
 
+  if (conn->status & AIM_CONN_STATUS_INPROGRESS)
+    return aim_conn_completeconnect(sess, conn);
+
   /*
    * Rendezvous (client-client) connections do not speak
    * FLAP, so this function will break on them.
--- a/libfaim/aim_tlv.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_tlv.c	Fri Nov 10 22:49:02 2000 +0000
@@ -1,6 +1,17 @@
 #include <faim/aim.h>
 
-faim_internal struct aim_tlvlist_t *aim_readtlvchain(u_char *buf, int maxlen)
+/**
+ * aim_readtlvchain - Read a TLV chain from a buffer.
+ * @buf: Input buffer
+ * @maxlen: Length of input buffer
+ *
+ * Reads and parses a series of TLV patterns from a data buffer; the
+ * returned structure is manipulatable with the rest of the TLV
+ * routines.  When done with a TLV chain, aim_freetlvchain() should
+ * be called to free the dynamic substructures.
+ *
+ */
+faim_export struct aim_tlvlist_t *aim_readtlvchain(u_char *buf, int maxlen)
 {
   int pos;
   struct aim_tlvlist_t *list;
@@ -62,7 +73,16 @@
   return list;
 }
 
-faim_internal void aim_freetlvchain(struct aim_tlvlist_t **list)
+/**
+ * aim_freetlvchain - Free a TLV chain structure
+ * @list: Chain to be freed
+ *
+ * Walks the list of TLVs in the passed TLV chain and
+ * frees each one. Note that any references to this data
+ * should be removed before calling this.
+ *
+ */
+faim_export void aim_freetlvchain(struct aim_tlvlist_t **list)
 {
   struct aim_tlvlist_t *cur, *cur2;
 
@@ -81,7 +101,14 @@
   return;
 }
 
-faim_internal int aim_counttlvchain(struct aim_tlvlist_t **list)
+/**
+ * aim_counttlvchain - Count the number of TLVs in a chain
+ * @list: Chain to be counted
+ *
+ * Returns the number of TLVs stored in the passed chain.
+ *
+ */
+faim_export int aim_counttlvchain(struct aim_tlvlist_t **list)
 {
   struct aim_tlvlist_t *cur;
   int count = 0;
@@ -95,6 +122,14 @@
   return count;
 }
 
+/**
+ * aim_sizetlvchain - Count the number of bytes in a TLV chain
+ * @list: Chain to be sized
+ *
+ * Returns the number of bytes that would be needed to 
+ * write the passed TLV chain to a data buffer.
+ *
+ */
 faim_export int aim_sizetlvchain(struct aim_tlvlist_t **list)
 {
   struct aim_tlvlist_t *cur;
@@ -109,8 +144,18 @@
   return size;
 }
 
-
-faim_internal int aim_addtlvtochain_str(struct aim_tlvlist_t **list, unsigned short type, char *str, int len)
+/**
+ * aim_addtlvtochain_str - Add a string to a TLV chain
+ * @list: Desination chain (%NULL pointer if empty)
+ * @type: TLV type
+ * @str: String to add
+ * @len: Length of string to add (not including %NULL)
+ *
+ * Adds the passed string as a TLV element of the passed type
+ * to the TLV chain.
+ *
+ */
+faim_export int aim_addtlvtochain_str(struct aim_tlvlist_t **list, unsigned short type, char *str, int len)
 {
   struct aim_tlvlist_t *newtlv;
   struct aim_tlvlist_t *cur;
@@ -141,7 +186,16 @@
   return newtlv->tlv->length;
 }
 
-faim_internal int aim_addtlvtochain16(struct aim_tlvlist_t **list, unsigned short type, unsigned short val)
+/**
+ * aim_addtlvtochain16 - Add a 16bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a two-byte unsigned integer to a TLV chain.
+ *
+ */
+faim_export int aim_addtlvtochain16(struct aim_tlvlist_t **list, unsigned short type, unsigned short val)
 {
   struct aim_tlvlist_t *newtl;
   struct aim_tlvlist_t *cur;
@@ -172,7 +226,16 @@
   return 2;
 }
 
-faim_internal int aim_addtlvtochain32(struct aim_tlvlist_t **list, unsigned short type, unsigned long val)
+/**
+ * aim_addtlvtochain32 - Add a 32bit integer to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @val: Value to add
+ *
+ * Adds a four-byte unsigned integer to a TLV chain.
+ *
+ */
+faim_export int aim_addtlvtochain32(struct aim_tlvlist_t **list, unsigned short type, unsigned long val)
 {
   struct aim_tlvlist_t *newtl;
   struct aim_tlvlist_t *cur;
@@ -203,7 +266,29 @@
   return 4;
 }
 
-faim_internal int aim_addtlvtochain_caps(struct aim_tlvlist_t **list, unsigned short type, unsigned short caps)
+/**
+ * aim_addtlvtochain_caps - Add a capability block to a TLV chain
+ * @list: Destination chain
+ * @type: TLV type to add
+ * @caps: Bitfield of capability flags to send
+ *
+ * Adds a block of capability blocks to a TLV chain. The bitfield
+ * passed in should be a bitwise %OR of any of the %AIM_CAPS constants:
+ *
+ *      %AIM_CAPS_BUDDYICON   Supports Buddy Icons
+ *
+ *      %AIM_CAPS_VOICE       Supports Voice Chat
+ *
+ *      %AIM_CAPS_IMIMAGE     Supports DirectIM/IMImage
+ *
+ *      %AIM_CAPS_CHAT        Supports Chat
+ *
+ *      %AIM_CAPS_GETFILE     Supports Get File functions
+ *
+ *      %AIM_CAPS_SENDFILE    Supports Send File functions
+ *
+ */
+faim_export int aim_addtlvtochain_caps(struct aim_tlvlist_t **list, unsigned short type, unsigned short caps)
 {
   unsigned char buf[128]; /* icky fixed length buffer */
   struct aim_tlvlist_t *newtl;
@@ -236,7 +321,19 @@
   return newtl->tlv->length;
 }
 
-faim_internal int aim_writetlvchain(u_char *buf, int buflen, struct aim_tlvlist_t **list)
+/**
+ * aim_writetlvchain - Write a TLV chain into a data buffer.
+ * @buf: Destination buffer
+ * @buflen: Maximum number of bytes that will be written to buffer
+ * @list: Source TLV chain
+ *
+ * Copies a TLV chain into a raw data buffer, writing only the number
+ * of bytes specified. This operation does not free the chain; 
+ * aim_freetlvchain() must still be called to free up the memory used
+ * by the chain structures.
+ *
+ */
+faim_export int aim_writetlvchain(u_char *buf, int buflen, struct aim_tlvlist_t **list)
 {
   int goodbuflen = 0;
   int i = 0;
@@ -266,10 +363,19 @@
 }
 
 
-/*
- * Grab the Nth TLV of type type in the TLV list list.
+/**
+ * aim_gettlv - Grab the Nth TLV of type type in the TLV list list.
+ * @list: Source chain
+ * @type: Requested TLV type
+ * @nth: Index of TLV of type to get
+ *
+ * Returns a pointer to an aim_tlv_t of the specified type; 
+ * %NULL on error.  The @nth parameter is specified starting at %1.
+ * In most cases, there will be no more than one TLV of any type
+ * in a chain.
+ *
  */
-faim_internal struct aim_tlv_t *aim_gettlv(struct aim_tlvlist_t *list, u_short type, int nth)
+faim_export struct aim_tlv_t *aim_gettlv(struct aim_tlvlist_t *list, u_short type, int nth)
 {
   int i;
   struct aim_tlvlist_t *cur;
@@ -288,7 +394,18 @@
   return NULL;
 }
 
-faim_internal char *aim_gettlv_str(struct aim_tlvlist_t *list, u_short type, int nth)
+/**
+ * aim_gettlv_str - Retrieve the Nth TLV in chain as a string.
+ * @list: Source TLV chain
+ * @type: TLV type to search for
+ * @nth: Index of TLV to return
+ *
+ * Same as aim_gettlv(), except that the return value is a %NULL-
+ * terminated string instead of an aim_tlv_t.  This is a 
+ * dynamic buffer and must be freed by the caller.
+ *
+ */
+faim_export char *aim_gettlv_str(struct aim_tlvlist_t *list, u_short type, int nth)
 {
   struct aim_tlv_t *tlv;
   char *newstr;
@@ -303,7 +420,19 @@
   return newstr;
 }
 
-faim_internal struct aim_tlv_t *aim_grabtlv(u_char *src)
+/**
+ * aim_grabtlv - Grab a single TLV from a data buffer
+ * @src: Source data buffer (must be at least 4 bytes long)
+ *
+ * Creates a TLV structure aim_tlv_t and returns it
+ * filled with values from a buffer, possibly including a 
+ * dynamically allocated buffer for the value portion.
+ *
+ * Both the aim_tlv_t and the tlv->value pointer
+ * must be freed by the caller if non-%NULL.
+ *
+ */
+faim_export struct aim_tlv_t *aim_grabtlv(u_char *src)
 {
   struct aim_tlv_t *dest = NULL;
 
@@ -323,7 +452,20 @@
   return dest;
 }
 
-faim_internal struct aim_tlv_t *aim_grabtlvstr(u_char *src)
+/**
+ * aim_grabtlvstr - Grab a single TLV from a data buffer as string
+ * @src: Source data buffer (must be at least 4 bytes long)
+ *
+ * Creates a TLV structure aim_tlv_t and returns it
+ * filled with values from a buffer, possibly including a 
+ * dynamically allocated buffer for the value portion, which 
+ * is %NULL-terminated as a string.
+ *
+ * Both the aim_tlv_t and the tlv->value pointer
+ * must be freed by the caller if non-%NULL.
+ *
+ */
+faim_export struct aim_tlv_t *aim_grabtlvstr(u_char *src)
 {
   struct aim_tlv_t *dest = NULL;
 
@@ -344,7 +486,19 @@
   return dest;
 }
 
-faim_internal int aim_puttlv(u_char *dest, struct aim_tlv_t *newtlv)
+/**
+ * aim_puttlv - Write a aim_tlv_t into a data buffer
+ * @dest: Destination data buffer
+ * @newtlv: Source TLV structure
+ *
+ * Writes out the passed TLV structure into the buffer. No bounds
+ * checking is done on the output buffer.
+ *
+ * The passed aim_tlv_t is not freed. aim_freetlv() should
+ * still be called by the caller to free the structure.
+ *
+ */
+faim_export int aim_puttlv(u_char *dest, struct aim_tlv_t *newtlv)
 {
   int i=0;
 
@@ -357,15 +511,31 @@
   return i;
 }
 
-faim_internal struct aim_tlv_t *aim_createtlv(void)
+/**
+ * aim_createtlv - Generate an aim_tlv_t structure.
+ * 
+ * Allocates an empty TLV structure and returns a pointer
+ * to it; %NULL on error.
+ *
+ */
+faim_export struct aim_tlv_t *aim_createtlv(void)
 {
-  struct aim_tlv_t *newtlv = NULL;
-  newtlv = (struct aim_tlv_t *)malloc(sizeof(struct aim_tlv_t));
+  struct aim_tlv_t *newtlv;
+
+  if (!(newtlv = (struct aim_tlv_t *)malloc(sizeof(struct aim_tlv_t))))
+    return NULL;
   memset(newtlv, 0, sizeof(struct aim_tlv_t));
   return newtlv;
 }
 
-faim_internal int aim_freetlv(struct aim_tlv_t **oldtlv)
+/**
+ * aim_freetlv - Free a aim_tlv_t structure
+ * @oldtlv: TLV to be destroyed
+ *
+ * Frees both the TLV structure and the value portion.
+ *
+ */
+faim_export int aim_freetlv(struct aim_tlv_t **oldtlv)
 {
   if (!oldtlv)
     return -1;
@@ -379,7 +549,16 @@
   return 0;
 }
 
-faim_internal int aim_puttlv_16(u_char *buf, u_short t, u_short v)
+/**
+ * aim_puttlv_16 - Write a two-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a two-byte integer value portion.
+ *
+ */
+faim_export int aim_puttlv_16(u_char *buf, u_short t, u_short v)
 {
   int curbyte=0;
   curbyte += aimutil_put16(buf+curbyte, (u_short)(t&0xffff));
@@ -388,7 +567,16 @@
   return curbyte;
 }
 
-faim_internal int aim_puttlv_32(u_char *buf, u_short t, u_long v)
+/**
+ * aim_puttlv_32 - Write a four-byte TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @v: Value
+ *
+ * Writes a TLV with a four-byte integer value portion.
+ *
+ */
+faim_export int aim_puttlv_32(u_char *buf, u_short t, u_long v)
 {
   int curbyte=0;
   curbyte += aimutil_put16(buf+curbyte, (u_short)(t&0xffff));
@@ -397,7 +585,19 @@
   return curbyte;
 }
 
-faim_internal int aim_puttlv_str(u_char *buf, u_short t, int l, char *v)
+/**
+ * aim_puttlv_str - Write a string TLV.
+ * @buf: Destination buffer
+ * @t: TLV type
+ * @l: Length of string
+ * @v: String to write
+ *
+ * Writes a TLV with a string value portion.  (Only the first @l
+ * bytes of the passed string will be written, which should not
+ * include the terminating NULL.)
+ *
+ */
+faim_export int aim_puttlv_str(u_char *buf, u_short t, int l, char *v)
 {
   int curbyte;
   
--- a/libfaim/aim_txqueue.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/aim_txqueue.c	Fri Nov 10 22:49:02 2000 +0000
@@ -151,6 +151,19 @@
   return 0;
 }
 
+faim_internal int aim_tx_enqueue(struct aim_session_t *sess, struct command_tx_struct *command)
+{
+  /*
+   * If we want to send a connection thats inprogress, we have to force
+   * them to use the queue based version. Otherwise, use whatever they
+   * want.
+   */
+  if (command && command->conn && (command->conn->status & AIM_CONN_STATUS_INPROGRESS)) {
+    return aim_tx_enqueue__queuebased(sess, command);
+  }
+  return (*sess->tx_enqueue)(sess, command);
+}
+
 /* 
  *  aim_get_next_txseqnum()
  *
@@ -347,6 +360,9 @@
     /* only process if its unlocked and unsent */
     if (!cur->lock && !cur->sent) {
 
+      if (cur->conn && (cur->conn->status & AIM_CONN_STATUS_INPROGRESS))
+	continue;
+
       /*
        * And now for the meager attempt to force transmit
        * latency and avoid missed messages.
--- a/libfaim/faim/aim.h	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/faim/aim.h	Fri Nov 10 22:49:02 2000 +0000
@@ -208,8 +208,9 @@
  */
 #define AIM_CONN_STATUS_READY       0x0001
 #define AIM_CONN_STATUS_INTERNALERR 0x0002
-#define AIM_CONN_STATUS_RESOLVERR   0x0080
-#define AIM_CONN_STATUS_CONNERR     0x0040
+#define AIM_CONN_STATUS_RESOLVERR   0x0040
+#define AIM_CONN_STATUS_CONNERR     0x0080
+#define AIM_CONN_STATUS_INPROGRESS  0x0100
 
 #define AIM_FRAMETYPE_OSCAR 0x0000
 #define AIM_FRAMETYPE_OFT 0x0001
@@ -350,9 +351,15 @@
     char password[128];
   } socksproxy;
 
+  unsigned long flags;
+
   struct aim_msgcookie_t *msgcookies;
 };
 
+/* Values for sess->flags */
+#define AIM_SESS_FLAGS_SNACLOGIN       0x00000001
+#define AIM_SESS_FLAGS_XORLOGIN        0x00000002
+#define AIM_SESS_FLAGS_NONBLOCKCONNECT 0x00000004
 
 /*
  * AIM User Info, Standard Form.
@@ -466,7 +473,7 @@
 faim_internal struct command_tx_struct *aim_tx_new(unsigned char framing, int chan, struct aim_conn_t *conn, int datalen);
 faim_internal int aim_tx_enqueue__queuebased(struct aim_session_t *, struct command_tx_struct *);
 faim_internal int aim_tx_enqueue__immediate(struct aim_session_t *, struct command_tx_struct *);
-#define aim_tx_enqueue(x, y) ((*(x->tx_enqueue))(x, y))
+faim_internal int aim_tx_enqueue(struct aim_session_t *, struct command_tx_struct *);
 faim_internal int aim_tx_sendframe(struct aim_session_t *sess, struct command_tx_struct *cur);
 faim_internal unsigned int aim_get_next_txseqnum(struct aim_conn_t *);
 faim_export int aim_tx_flushqueue(struct aim_session_t *);
@@ -515,7 +522,9 @@
 faim_export struct aim_conn_t *aim_select(struct aim_session_t *, struct timeval *, int *);
 faim_export int aim_conn_isready(struct aim_conn_t *);
 faim_export int aim_conn_setstatus(struct aim_conn_t *, int);
-faim_export void aim_session_init(struct aim_session_t *);
+faim_export int aim_conn_completeconnect(struct aim_session_t *sess, struct aim_conn_t *conn);
+faim_export int aim_conn_isconnecting(struct aim_conn_t *conn);
+faim_export void aim_session_init(struct aim_session_t *, unsigned long flags);
 faim_export void aim_setupproxy(struct aim_session_t *sess, char *server, char *username, char *password);
 
 /* aim_misc.c */
--- a/libfaim/faim/aim_cbtypes.h	Fri Nov 10 05:54:04 2000 +0000
+++ b/libfaim/faim/aim_cbtypes.h	Fri Nov 10 22:49:02 2000 +0000
@@ -214,6 +214,8 @@
 #define AIM_CB_SPECIAL_AUTHSUCCESS 0x0001
 #define AIM_CB_SPECIAL_AUTHOTHER 0x0002
 #define AIM_CB_SPECIAL_CONNERR 0x0003
+#define AIM_CB_SPECIAL_CONNCOMPLETE 0x0004
+#define AIM_CB_SPECIAL_FLAPVER 0x0005
 #define AIM_CB_SPECIAL_DEBUGCONN_CONNECT 0xe001
 #define AIM_CB_SPECIAL_UNKNOWN 0xffff
 #define AIM_CB_SPECIAL_DEFAULT AIM_CB_SPECIAL_UNKNOWN
--- a/plugins/spellchk.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/plugins/spellchk.c	Fri Nov 10 22:49:02 2000 +0000
@@ -26,12 +26,12 @@
 
 GList *words = NULL;
 
-int num_words(char *);
-int get_word(char *, int);
-char *have_word(char *, int);
-void substitute(char **, int, int, char *);
+static int num_words(char *);
+static int get_word(char *, int);
+static char *have_word(char *, int);
+static void substitute(char **, int, int, char *);
 
-void substitute_words(struct gaim_connection *gc, char *who, char **message, void *m) {
+static void substitute_words(struct gaim_connection *gc, char *who, char **message, void *m) {
 	int i, l;
 	int word;
 	GList *w;
@@ -60,7 +60,7 @@
 	}
 }
 
-int buf_get_line(char *ibuf, char **buf, int *position, int len) {
+static int buf_get_line(char *ibuf, char **buf, int *position, int len) {
 	int pos = *position, spos = pos;
 
 	if (pos == len)
@@ -78,7 +78,7 @@
 	return 1;
 }
 
-void load_conf() {
+static void load_conf() {
 	char *defaultconf = "BAD r\nGOOD are\n\n"
 			"BAD u\nGOOD you\n\n"
 			"BAD teh\nGOOD the\n\n";
@@ -155,7 +155,7 @@
 	return "Watches outgoing IM text and corrects common spelling errors.";
 }
 
-int num_words(char *m) {
+static int num_words(char *m) {
 	int count = 0;
 	int pos;
 	int state = 0;
@@ -184,7 +184,7 @@
 	return count;
 }
 
-int get_word(char *m, int word) {
+static int get_word(char *m, int word) {
 	int count = 0;
 	int pos = 0;
 	int state = 0;
@@ -213,7 +213,7 @@
 	return pos - 1;
 }
 
-char *have_word(char *m, int pos) {
+static char *have_word(char *m, int pos) {
 	char *tmp = strpbrk(&m[pos], "' \t\f\r\n\".?!-,");
 	int len = (int)(tmp - &m[pos]);
 
@@ -229,7 +229,7 @@
 	return tmp;
 }
 
-void substitute(char **mes, int pos, int m, char *text) {
+static void substitute(char **mes, int pos, int m, char *text) {
 	char *new = g_malloc(strlen(*mes) + strlen(text) + 1);
 	char *tmp;
 	new[0] = 0;
@@ -243,17 +243,17 @@
 	g_free(tmp);
 }
 
-GtkWidget *configwin = NULL;
-GtkWidget *list;
-GtkWidget *bad_entry;
-GtkWidget *good_entry;
+static GtkWidget *configwin = NULL;
+static GtkWidget *list;
+static GtkWidget *bad_entry;
+static GtkWidget *good_entry;
 
-void row_unselect() {
+static void row_unselect() {
 	gtk_entry_set_text(GTK_ENTRY(bad_entry), "");
 	gtk_entry_set_text(GTK_ENTRY(good_entry), "");
 }
 
-void row_select() {
+static void row_select() {
 	char *badwrd, *goodwrd;
 	int row;
 
@@ -271,7 +271,7 @@
 	}
 }
 
-void list_add_new() {
+static void list_add_new() {
 	int i;
 	gchar *item[2] = {"*NEW*", "EDIT ME"};
 
@@ -280,7 +280,7 @@
 	gtk_clist_moveto(GTK_CLIST(list), i, 0, 0.5, 0);
 }
 
-void list_delete() {
+static void list_delete() {
 	int row;
 
 	if (GTK_CLIST(list)->selection)
@@ -293,13 +293,13 @@
 	}
 }
 
-void close_config() {
+static void close_config() {
 	if (configwin)
 		gtk_widget_destroy(configwin);
 	configwin = NULL;
 }
 
-void save_list() {
+static void save_list() {
 	int fh, i = 0;
 	char buf[512];
 	char *a, *b;
@@ -319,7 +319,7 @@
 	load_conf();
 }
 
-void bad_changed() {
+static void bad_changed() {
 	int row;
 	char *m;
 
@@ -333,7 +333,7 @@
 	}
 }
 
-void good_changed() {
+static void good_changed() {
 	int row;
 	char *m;
 
--- a/src/buddy.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/src/buddy.c	Fri Nov 10 22:49:02 2000 +0000
@@ -262,9 +262,10 @@
 	struct group_show *g;
 	GSList *m;
 	struct buddy_show *b;
+	gboolean remove_group;
 
 	while (s) {
-		gboolean remove_group = FALSE;
+		remove_group = FALSE;
 		g = (struct group_show *)s->data;
 		m = g->members;
 		while (m) {
@@ -287,10 +288,11 @@
 				g_free(b->name);
 				g_free(b->show);
 				g_free(b);
-			} else {
+			} else if (g_slist_find(b->connlist, gc)) {
 				b->connlist = g_slist_remove(b->connlist, gc);
 				m = g_slist_next(m);
-			}
+			} else
+				m = g_slist_next(m);
 		}
 		if (remove_group)
 			s = shows;
@@ -316,13 +318,13 @@
 {
 	destroy_buddies(gc);
 	plugin_event(event_signoff, gc, 0, 0, 0);
+	update_keepalive(gc, FALSE);
 	serv_close(gc);
 
 	if (connections) return;
 
 	sprintf(debug_buff, "date: %s\n", full_date());
 	debug_print(debug_buff);
-	update_keepalive(gc, FALSE);
         destroy_all_dialogs();
         destroy_buddy();
 #ifdef USE_APPLET
--- a/src/oscar.c	Fri Nov 10 05:54:04 2000 +0000
+++ b/src/oscar.c	Fri Nov 10 22:49:02 2000 +0000
@@ -311,7 +311,7 @@
 	debug_print(debug_buff);
 
 	sess = g_new0(struct aim_session_t, 1);
-	aim_session_init(sess);
+	aim_session_init(sess, AIM_SESS_FLAGS_NONBLOCKCONNECT);
 	/* we need an immediate queue because we don't use a while-loop to
 	 * see if things need to be sent. */
 	sess->tx_enqueue = &aim_tx_enqueue__immediate;
@@ -347,7 +347,6 @@
 
 	aim_conn_addhandler(sess, conn, 0x0017, 0x0007, gaim_parse_login, 0);
 	aim_conn_addhandler(sess, conn, 0x0017, 0x0003, gaim_parse_auth_resp, 0);
-	aim_sendconnack(sess, conn);
 	aim_request_login(sess, conn, gc->username);
 
 	gc->inpa = gdk_input_add(conn->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION,
@@ -446,7 +445,7 @@
 		hide_login_progress(gc, _("Internal Error"));
 		destroy_gaim_conn(gc);
 		return -1;
-	} else if (bosconn->status != 0) {
+	} else if (bosconn->status & AIM_CONN_STATUS_CONNERR) {
 #ifdef USE_APPLET
 		set_user_state(offline);
 #endif
@@ -558,7 +557,7 @@
 		debug_print("Reconnecting with authorizor...\n");
 		{
 		struct aim_conn_t *tstconn = aim_newconn(sess, AIM_CONN_TYPE_AUTH, ip);
-		if (tstconn == NULL || tstconn->status >= AIM_CONN_STATUS_RESOLVERR)
+		if (tstconn == NULL || tstconn->status & AIM_CONN_STATUS_RESOLVERR)
 			debug_print("unable to reconnect with authorizer\n");
 		else {
 			odata->paspa = gdk_input_add(tstconn->fd,
@@ -571,7 +570,7 @@
 	case 0xd: /* ChatNav */
 		{
 		struct aim_conn_t *tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHATNAV, ip);
-		if (tstconn == NULL || tstconn->status >= AIM_CONN_STATUS_RESOLVERR) {
+		if (tstconn == NULL || tstconn->status & AIM_CONN_STATUS_RESOLVERR) {
 			debug_print("unable to connect to chatnav server\n");
 			return 1;
 		}