X-Git-Url: http://andersk.mit.edu/gitweb/libfaim.git/blobdiff_plain/9ba272ca73923773d44b27896645bdb13480736f..1449ad2bc8b00bd9171d392d71da0a45dff71f0a:/aim_conn.c diff --git a/aim_conn.c b/aim_conn.c index 4452ebb..836c0d4 100644 --- a/aim_conn.c +++ b/aim_conn.c @@ -8,80 +8,393 @@ #include -void aim_connrst(struct aim_session_t *sess) +#ifndef _WIN32 +#include +#include +#include +#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. + * + */ +faim_internal void aim_connrst(struct aim_session_t *sess) { - int i; - for (i = 0; i < AIM_CONN_MAX; i++) - { - sess->conns[i].fd = -1; - sess->conns[i].type = -1; - sess->conns[i].status = 0; - sess->conns[i].seqnum = 0; - sess->conns[i].lastactivity = 0; - sess->conns[i].forcedlatency = 0; - aim_clearhandlers(&(sess->conns[i])); - sess->conns[i].handlerlist = NULL; - } + faim_mutex_init(&sess->connlistlock); + if (sess->connlist) { + struct aim_conn_t *cur = sess->connlist, *tmp; + while(cur) { + tmp = cur->next; + aim_conn_close(cur); + free(cur); + cur = tmp; + } + } + sess->connlist = NULL; + return; } -struct aim_conn_t *aim_conn_getnext(struct aim_session_t *sess) +/** + * 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) { - int i; - for (i=0;iconns[i].fd == -1) - return &(sess->conns[i]); - return NULL; -} + if (!deadconn) + return; -void aim_conn_close(struct aim_conn_t *deadconn) -{ - if (deadconn->fd >= 3) - close(deadconn->fd); deadconn->fd = -1; + deadconn->subtype = -1; deadconn->type = -1; deadconn->seqnum = 0; deadconn->lastactivity = 0; deadconn->forcedlatency = 0; - aim_clearhandlers(deadconn); deadconn->handlerlist = NULL; + deadconn->priv = NULL; + faim_mutex_init(&deadconn->active); + faim_mutex_init(&deadconn->seqnum_lock); + + return; } -struct aim_conn_t *aim_getconn_type(struct aim_session_t *sess, - int type) +/** + * 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) { - int i; - for (i=0; iconns[i].type == type) - return &(sess->conns[i]); - return NULL; + struct aim_conn_t *newconn, *cur; + + if (!(newconn = malloc(sizeof(struct aim_conn_t)))) + return NULL; + + memset(newconn, 0, sizeof(struct aim_conn_t)); + aim_conn_init(newconn); + newconn->next = NULL; + + faim_mutex_lock(&sess->connlistlock); + if (sess->connlist == NULL) + sess->connlist = newconn; + else { + for (cur = sess->connlist; cur->next; cur = cur->next) + ; + cur->next = newconn; + } + faim_mutex_unlock(&sess->connlistlock); + + return newconn; } -/* - * aim_newconn(type, dest) +/** + * aim_conn_kill - Close and free a connection. + * @sess: Session for the connection + * @deadconn: Connection to be freed * - * Opens a new connection to the specified dest host of type type. + * 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; + + if (!deadconn || !*deadconn) + return; + + faim_mutex_lock(&sess->connlistlock); + if (sess->connlist == NULL) + ; + else if (sess->connlist->next == NULL) { + if (sess->connlist == *deadconn) + sess->connlist = NULL; + } else { + cur = sess->connlist; + while (cur->next) { + if (cur->next == *deadconn) { + cur->next = cur->next->next; + break; + } + cur = cur->next; + } + } + faim_mutex_unlock(&sess->connlistlock); + + /* XXX: do we need this for txqueue too? */ + aim_rxqueue_cleanbyconn(sess, *deadconn); + + if ((*deadconn)->fd != -1) + aim_conn_close(*deadconn); + if ((*deadconn)->priv) + free((*deadconn)->priv); + free(*deadconn); + deadconn = NULL; + + return; +} + +/** + * 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) +{ + + faim_mutex_destroy(&deadconn->active); + faim_mutex_destroy(&deadconn->seqnum_lock); + if (deadconn->fd >= 3) + close(deadconn->fd); + deadconn->fd = -1; + if (deadconn->handlerlist) + aim_clearhandlers(deadconn); + + 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) +{ + struct aim_conn_t *cur; + + faim_mutex_lock(&sess->connlistlock); + for (cur = sess->connlist; cur; cur = cur->next) { + if ((cur->type == type) && !(cur->status & AIM_CONN_STATUS_INPROGRESS)) + break; + } + faim_mutex_unlock(&sess->connlistlock); + return cur; +} + +/** + * 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, + 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; +} + +/** + * 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; - - if (!dest || ((connstruct=aim_conn_getnext(sess))==NULL)) + + if ((connstruct=aim_conn_getnext(sess))==NULL) return NULL; + faim_mutex_lock(&connstruct->active); + connstruct->type = type; + if (!dest) { /* just allocate a struct */ + connstruct->fd = -1; + connstruct->status = 0; + faim_mutex_unlock(&connstruct->active); + return connstruct; + } + /* * As of 23 Jul 1999, AOL now sends the port number, preceded by a * colon, in the BOS redirect. This fatally breaks all previous @@ -91,178 +404,402 @@ struct aim_conn_t *aim_newconn(struct aim_session_t *sess, * */ - for(i=0;istatus)) < 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); - hp = gethostbyname2(host, AF_INET); free(host); - if (hp == NULL) - { - connstruct->status = (h_errno | AIM_CONN_STATUS_RESOLVERR); - 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) - { - connstruct->fd = -1; - connstruct->status = (errno | AIM_CONN_STATUS_CONNERR); - return connstruct; - } - 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 i,j; - j=0; - for (i=0;iconns[i].fd > j) - j = sess->conns[i].fd; + int j = 0; + struct aim_conn_t *cur; + + faim_mutex_lock(&sess->connlistlock); + for (cur = sess->connlist; cur; cur = cur->next) { + if (cur->fd > j) + j = cur->fd; + } + faim_mutex_unlock(&sess->connlistlock); + return j; } -int aim_countconn(struct aim_session_t *sess) +/** + * 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 i,cnt; - cnt = 0; - for (i=0;iconns[i].fd > -1) - cnt++; + int cnt = 0; + struct aim_conn_t *cur; + + faim_mutex_lock(&sess->connlistlock); + for (cur = sess->connlist; cur; cur = cur->next) + cnt++; + faim_mutex_unlock(&sess->connlistlock); + return cnt; } -/* - * aim_select(timeout) +/** + * 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; + + faim_mutex_lock(&sess->connlistlock); + for(cur = sess->connlist; cur; cur = cur->next) + if(cur == conn) { + faim_mutex_unlock(&sess->connlistlock); + return 1; + } + faim_mutex_unlock(&sess->connlistlock); + return 0; +} + +/** + * 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) + * 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) +faim_export struct aim_conn_t *aim_select(struct aim_session_t *sess, + struct timeval *timeout, int *status) { - fd_set fds; - fd_set errfds; - int i; + struct aim_conn_t *cur; + fd_set fds, wfds; + int maxfd = 0; + int i, haveconnecting = 0; - if (aim_countconn(sess) <= 0) - return 0; + faim_mutex_lock(&sess->connlistlock); + if (sess->connlist == NULL) { + faim_mutex_unlock(&sess->connlistlock); + *status = -1; + return NULL; + } + faim_mutex_unlock(&sess->connlistlock); + + 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 we have data waiting to be sent, return immediatly + * 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 (sess->queue_outgoing) - return (struct aim_conn_t *)1; + if (!haveconnecting && (sess->queue_outgoing != NULL)) { + *status = 1; + return NULL; + } - FD_ZERO(&fds); - FD_ZERO(&errfds); - - for(i=0;iconns[i].fd>-1) - { - FD_SET(sess->conns[i].fd, &fds); - FD_SET(sess->conns[i].fd, &errfds); + 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)) || + ((cur->status & AIM_CONN_STATUS_INPROGRESS) && + FD_ISSET(cur->fd, &wfds))) { + *status = 2; + faim_mutex_unlock(&sess->connlistlock); + return cur; /* XXX race condition here -- shouldnt unlock connlist */ } - - i = select(aim_conngetmaxfd(sess)+1, &fds, NULL, &errfds, timeout); - if (i>=1) - { - int j; - for (j=0;jconns[j].fd > -1) - { - if ((FD_ISSET(sess->conns[j].fd, &errfds))) - { - /* got an exception; close whats left of it up */ - aim_conn_close(&(sess->conns[j])); - return (struct aim_conn_t *)-1; - } - else if ((FD_ISSET(sess->conns[j].fd, &fds))) - return &(sess->conns[j]); /* return the first waiting struct */ - } - } - /* should never get here */ } + *status = 0; /* shouldn't happen */ + } else if ((i == -1) && (errno == EINTR)) /* treat interrupts as a timeout */ + *status = 0; else - return (struct aim_conn_t *)i; /* no waiting or error, return -- FIXME: return type funnies */ - return NULL; /* NO REACH */ + *status = i; /* can be 0 or -1 */ + + faim_mutex_unlock(&sess->connlistlock); + 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); - else - return -1; + 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) { - if (conn) - return (conn->status ^= status); - else + int val; + + if (!conn) return -1; + + faim_mutex_lock(&conn->active); + val = conn->status ^= status; + faim_mutex_unlock(&conn->active); + 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; - + + faim_mutex_lock(&conn->active); conn->forcedlatency = newval; conn->lastactivity = 0; /* reset this just to make sure */ + faim_mutex_unlock(&conn->active); 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) { - int i; + /* 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->logininfo.screen_name[0] = '\0'; - sess->logininfo.BOSIP = NULL; - sess->logininfo.cookie[0] = '\0'; - sess->logininfo.email = NULL; - sess->logininfo.regstatus = 0x00; - - for (i = 0; i < AIM_CONN_MAX; i++) - { - sess->conns[i].fd = -1; - sess->conns[i].type = -1; - sess->conns[i].status = 0; - sess->conns[i].seqnum = 0; - sess->conns[i].lastactivity = 0; - sess->conns[i].forcedlatency = 0; - sess->conns[i].handlerlist = NULL; - } - + memset(sess, 0, sizeof(struct aim_session_t)); + aim_connrst(sess); sess->queue_outgoing = NULL; sess->queue_incoming = NULL; - sess->outstanding_snacs = NULL; + sess->pendingjoin = 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; + 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; +}