*
*/
+#define FAIM_INTERNAL
#include <faim/aim.h>
-/*
- * Clears out connection list, killing remaining connections.
+#ifndef _WIN32
+#include <netdb.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#endif
+
+/**
+ * aim_connrst - Clears out connection list, killing remaining connections.
+ * @sess: Session to be cleared
+ *
+ * Clears out the connection list and kills any connections left.
+ *
*/
-void aim_connrst(struct aim_session_t *sess)
+faim_internal void aim_connrst(struct aim_session_t *sess)
{
- faim_mutex_init(&sess->connlistlock, NULL);
+ faim_mutex_init(&sess->connlistlock);
if (sess->connlist) {
struct aim_conn_t *cur = sess->connlist, *tmp;
return;
}
-/*
- * Gets a new connection structure.
+/**
+ * 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)
+ return;
+
+ deadconn->fd = -1;
+ deadconn->subtype = -1;
+ deadconn->type = -1;
+ deadconn->seqnum = 0;
+ deadconn->lastactivity = 0;
+ deadconn->forcedlatency = 0;
+ deadconn->handlerlist = NULL;
+ deadconn->priv = NULL;
+ faim_mutex_init(&deadconn->active);
+ faim_mutex_init(&deadconn->seqnum_lock);
+
+ return;
+}
+
+/**
+ * aim_conn_getnext - Gets a new connection structure.
+ * @sess: Session
+ *
+ * Allocate a new empty connection structure.
+ *
*/
-struct aim_conn_t *aim_conn_getnext(struct aim_session_t *sess)
+faim_internal struct aim_conn_t *aim_conn_getnext(struct aim_session_t *sess)
{
struct aim_conn_t *newconn, *cur;
return NULL;
memset(newconn, 0, sizeof(struct aim_conn_t));
- aim_conn_close(newconn);
+ aim_conn_init(newconn);
newconn->next = NULL;
faim_mutex_lock(&sess->connlistlock);
return newconn;
}
-static void aim_conn_init(struct aim_conn_t *deadconn)
-{
- if (!deadconn)
- return;
-
- deadconn->fd = -1;
- deadconn->type = -1;
- deadconn->seqnum = 0;
- deadconn->lastactivity = 0;
- deadconn->forcedlatency = 0;
- deadconn->handlerlist = NULL;
- deadconn->priv = NULL;
- faim_mutex_init(&deadconn->active, NULL);
- faim_mutex_init(&deadconn->seqnum_lock, NULL);
-
- return;
-}
-
-void aim_conn_kill(struct aim_session_t *sess, struct aim_conn_t **deadconn)
+/**
+ * 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;
/* XXX: do we need this for txqueue too? */
aim_rxqueue_cleanbyconn(sess, *deadconn);
- aim_conn_init(*deadconn);
+ if ((*deadconn)->fd != -1)
+ aim_conn_close(*deadconn);
+ if ((*deadconn)->priv)
+ free((*deadconn)->priv);
free(*deadconn);
deadconn = NULL;
return;
}
-void aim_conn_close(struct aim_conn_t *deadconn)
+/**
+ * aim_conn_close - Close a connection
+ * @deadconn: Connection to close
+ *
+ * Close (but not free) a connection.
+ *
+ * This leaves everything untouched except for clearing the
+ * handler list and setting the fd to -1 (used to recognize
+ * dead connections).
+ *
+ */
+faim_export void aim_conn_close(struct aim_conn_t *deadconn)
{
- int typesav = -1;
faim_mutex_destroy(&deadconn->active);
faim_mutex_destroy(&deadconn->seqnum_lock);
if (deadconn->fd >= 3)
close(deadconn->fd);
- typesav = deadconn->type;
+ deadconn->fd = -1;
if (deadconn->handlerlist)
aim_clearhandlers(deadconn);
- if (deadconn->priv)
- free(deadconn->priv);
-
- aim_conn_init(deadconn);
-
- deadconn->type = typesav;
return;
}
-struct aim_conn_t *aim_getconn_type(struct aim_session_t *sess,
- int type)
+/**
+ * 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_export struct aim_conn_t *aim_getconn_type(struct aim_session_t *sess,
+ int type)
{
struct aim_conn_t *cur;
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;
}
-/*
- * aim_newconn(type, dest)
+/**
+ * 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
*
- * Opens a new connection to the specified dest host of type type.
+ * 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,
+ int *statusret)
+{
+ int fd = -1;
+
+ if (strlen(sess->socksproxy.server)) { /* connecting via proxy */
+ int i;
+ unsigned char buf[512];
+ struct sockaddr_in sa;
+ struct hostent *hp;
+ char *proxy;
+ unsigned short proxyport = 1080;
+
+ for(i=0;i<(int)strlen(sess->socksproxy.server);i++) {
+ if (sess->socksproxy.server[i] == ':') {
+ proxyport = atoi(&(sess->socksproxy.server[i+1]));
+ break;
+ }
+ }
+ proxy = (char *)malloc(i+1);
+ strncpy(proxy, sess->socksproxy.server, i);
+ proxy[i] = '\0';
+
+ if (!(hp = gethostbyname(proxy))) {
+ printf("proxyconnect: unable to resolve proxy name\n");
+ *statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
+ return -1;
+ }
+ free(proxy);
+
+ memset(&sa.sin_zero, 0, 8);
+ sa.sin_port = htons(proxyport);
+ 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 (connect(fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in)) < 0) {
+ printf("proxyconnect: unable to connect to proxy\n");
+ close(fd);
+ return -1;
+ }
+
+ i = 0;
+ buf[0] = 0x05; /* SOCKS version 5 */
+ if (strlen(sess->socksproxy.username)) {
+ buf[1] = 0x02; /* two methods */
+ buf[2] = 0x00; /* no authentication */
+ buf[3] = 0x02; /* username/password authentication */
+ i = 4;
+ } else {
+ buf[1] = 0x01;
+ buf[2] = 0x00;
+ i = 3;
+ }
+
+ if (write(fd, buf, i) < i) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+
+ if (read(fd, buf, 2) < 2) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+
+ if ((buf[0] != 0x05) || (buf[1] == 0xff)) {
+ *statusret = EINVAL;
+ close(fd);
+ return -1;
+ }
+
+ /* check if we're doing username authentication */
+ if (buf[1] == 0x02) {
+ i = aimutil_put8(buf, 0x01); /* version 1 */
+ i += aimutil_put8(buf+i, strlen(sess->socksproxy.username));
+ i += aimutil_putstr(buf+i, sess->socksproxy.username, strlen(sess->socksproxy.username));
+ i += aimutil_put8(buf+i, strlen(sess->socksproxy.password));
+ i += aimutil_putstr(buf+i, sess->socksproxy.password, strlen(sess->socksproxy.password));
+ if (write(fd, buf, i) < i) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+ if (read(fd, buf, 2) < 2) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+ if ((buf[0] != 0x01) || (buf[1] != 0x00)) {
+ *statusret = EINVAL;
+ close(fd);
+ return -1;
+ }
+ }
+
+ i = aimutil_put8(buf, 0x05);
+ i += aimutil_put8(buf+i, 0x01); /* CONNECT */
+ i += aimutil_put8(buf+i, 0x00); /* reserved */
+ i += aimutil_put8(buf+i, 0x03); /* address type: host name */
+ i += aimutil_put8(buf+i, strlen(host));
+ i += aimutil_putstr(buf+i, host, strlen(host));
+ i += aimutil_put16(buf+i, port);
+
+ if (write(fd, buf, i) < i) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+ if (read(fd, buf, 10) < 10) {
+ *statusret = errno;
+ close(fd);
+ return -1;
+ }
+ if ((buf[0] != 0x05) || (buf[1] != 0x00)) {
+ *statusret = EINVAL;
+ close(fd);
+ return -1;
+ }
+
+ } else { /* connecting directly */
+ struct sockaddr_in sa;
+ struct hostent *hp;
+
+ if (!(hp = gethostbyname(host))) {
+ *statusret = (h_errno | AIM_CONN_STATUS_RESOLVERR);
+ return -1;
+ }
+
+ 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;
+ }
+ }
+ return fd;
+}
+
+faim_internal struct aim_conn_t *aim_cloneconn(struct aim_session_t *sess,
+ struct aim_conn_t *src)
+{
+ struct aim_conn_t *conn;
+ struct aim_rxcblist_t *cur;
+
+ if (!(conn = aim_conn_getnext(sess)))
+ return NULL;
+
+
+ faim_mutex_lock(&conn->active);
+
+ conn->fd = src->fd;
+ conn->type = src->type;
+ conn->subtype = src->subtype;
+ conn->seqnum = src->seqnum;
+ conn->priv = src->priv;
+ conn->lastactivity = src->lastactivity;
+ conn->forcedlatency = src->forcedlatency;
+
+ /* clone handler list */
+ for (cur = src->handlerlist; cur; cur = cur->next) {
+ aim_conn_addhandler(sess, conn, cur->family, cur->type,
+ cur->handler, cur->flags);
+ }
+
+ faim_mutex_unlock(&conn->active);
+
+ return conn;
+}
+
+/**
+ * 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 specified
+ * type, using the proxy settings if available. If @host is %NULL,
+ * the connection is allocated and returned, but no connection
+ * is made.
*
- * TODO: fix for proxies
* FIXME: Return errors in a more sane way.
*
*/
-struct aim_conn_t *aim_newconn(struct aim_session_t *sess,
- int type, char *dest)
+faim_export struct aim_conn_t *aim_newconn(struct aim_session_t *sess,
+ int type, char *dest)
{
struct aim_conn_t *connstruct;
int ret;
- struct sockaddr_in sa;
- struct hostent *hp;
u_short port = FAIM_LOGIN_PORT;
char *host = NULL;
int i=0;
*
*/
- for(i=0;i<strlen(dest);i++) {
+ for(i=0;i<(int)strlen(dest);i++) {
if (dest[i] == ':') {
port = atoi(&(dest[i+1]));
break;
strncpy(host, dest, i);
host[i] = '\0';
- hp = gethostbyname(host);
- free(host);
-
- if (hp == NULL) {
- connstruct->status = (h_errno | AIM_CONN_STATUS_RESOLVERR);
- faim_mutex_unlock(&connstruct->active);
- return connstruct;
- }
-
- memset(&sa.sin_zero, 0, 8);
- sa.sin_port = htons(port);
- memcpy(&sa.sin_addr, hp->h_addr, hp->h_length);
- sa.sin_family = hp->h_addrtype;
-
- connstruct->fd = socket(hp->h_addrtype, SOCK_STREAM, 0);
- ret = connect(connstruct->fd, (struct sockaddr *)&sa, sizeof(struct sockaddr_in));
- if(ret < 0) {
+ if ((ret = aim_proxyconnect(sess, host, port, &connstruct->status)) < 0) {
connstruct->fd = -1;
connstruct->status = (errno | AIM_CONN_STATUS_CONNERR);
+ free(host);
faim_mutex_unlock(&connstruct->active);
return connstruct;
- }
+ } else
+ connstruct->fd = ret;
faim_mutex_unlock(&connstruct->active);
+ free(host);
+
return connstruct;
}
-int aim_conngetmaxfd(struct aim_session_t *sess)
+/**
+ * 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;
struct aim_conn_t *cur;
return j;
}
-int aim_countconn(struct aim_session_t *sess)
+/**
+ * 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)
{
- int cnt = 0;
struct aim_conn_t *cur;
faim_mutex_lock(&sess->connlistlock);
- for (cur = sess->connlist; cur; cur = cur->next)
- cnt++;
+ for(cur = sess->connlist; cur; cur = cur->next)
+ if(cur == conn) {
+ faim_mutex_unlock(&sess->connlistlock);
+ return 1;
+ }
faim_mutex_unlock(&sess->connlistlock);
-
- return cnt;
+ 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.
*
*/
-struct aim_conn_t *aim_select(struct aim_session_t *sess,
- struct timeval *timeout, int *status)
+faim_export struct aim_conn_t *aim_select(struct aim_session_t *sess,
+ 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) {
}
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->fd == -1) {
+ /* don't let invalid/dead connections sit around */
+ *status = 2;
+ faim_mutex_unlock(&sess->connlistlock);
+ return cur;
+ } else 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 */
+ } else if ((i == -1) && (errno == EINTR)) /* treat interrupts as a timeout */
+ *status = 0;
+ else
+ *status = i; /* can be 0 or -1 */
faim_mutex_unlock(&sess->connlistlock);
- *status = i; /* may be 0 or -1 */
return NULL; /* no waiting or error, return */
}
-int aim_conn_isready(struct aim_conn_t *conn)
+/**
+ * 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)
return (conn->status & 0x0001);
return -1;
}
-int aim_conn_setstatus(struct aim_conn_t *conn, int status)
+/**
+ * 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;
return val;
}
-int aim_conn_setlatency(struct aim_conn_t *conn, int newval)
+/**
+ * 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)
return -1;
return 0;
}
-void aim_session_init(struct aim_session_t *sess)
+/**
+ * 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
+ * args, it will clear out a previously set proxy.
+ *
+ * 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)
+{
+ /* clear out the proxy info */
+ if (!server || !strlen(server)) {
+ memset(sess->socksproxy.server, 0, sizeof(sess->socksproxy.server));
+ memset(sess->socksproxy.username, 0, sizeof(sess->socksproxy.username));
+ memset(sess->socksproxy.password, 0, sizeof(sess->socksproxy.password));
+ return;
+ }
+
+ strncpy(sess->socksproxy.server, server, sizeof(sess->socksproxy.server));
+ if (username && strlen(username))
+ strncpy(sess->socksproxy.username, username, sizeof(sess->socksproxy.username));
+ if (password && strlen(password))
+ strncpy(sess->socksproxy.password, password, sizeof(sess->socksproxy.password));
+ return;
+}
+
+/**
+ * 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;
sess->queue_outgoing = NULL;
sess->queue_incoming = NULL;
sess->pendingjoin = NULL;
- sess->outstanding_snacs = NULL;
+ sess->pendingjoinexchange = 0;
+ aim_initsnachash(sess);
+ sess->msgcookies = NULL;
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.
*/
- sess->tx_enqueue = &aim_tx_enqueue__queuebased;
+ aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL);
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;
+}