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.
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 -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;
}
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->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 */
* @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)
{
/**
* 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)
+faim_export void aim_session_init(struct aim_session_t *sess, unsigned long flags)
{
if (!sess)
return;
sess->pendingjoin = NULL;
aim_initsnachash(sess);
sess->snac_nextid = 0x00000001;
- sess->snaclogin = 1; /* default to yes, oh gods yes. */
+
+ 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
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;
+}
newrx->lock = 0;
- sess->snaclogin = 0;
+ sess->flags &= ~AIM_SESS_FLAGS_SNACLOGIN;
+
return 0;
}
- sess->snaclogin = 1;
+ sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
aim_sendconnack(sess, conn);
newpacket->lock = 1;
- newpacket->hdr.oscar.type = sess->snaclogin?0x02:0x01;
+ newpacket->hdr.oscar.type = (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)?0x02:0x01;
- if (sess->snaclogin)
+ if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)
curbyte = aim_putsnac(newpacket->data, 0x0017, 0x0002, 0x0000, 0x00010000);
else {
curbyte = aimutil_put16(newpacket->data, 0x0000);
curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0001, strlen(sn), sn);
- if (sess->snaclogin) {
+ if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN) {
md5_byte_t digest[16];
aim_encode_password_md5(password, key, digest);
if (strlen(clientinfo->clientstring))
curbyte += aim_puttlv_str(newpacket->data+curbyte, 0x0003, strlen(clientinfo->clientstring), clientinfo->clientstring);
- if (sess->snaclogin) {
+ 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);
* For SNAC login, there's a 17/3 SNAC header in front.
*
*/
- if (sess->snaclogin)
+ if (sess->flags & AIM_SESS_FLAGS_SNACLOGIN)
tlvlist = aim_readtlvchain(command->data+10, command->commandlen-10);
else
tlvlist = aim_readtlvchain(command->data, command->commandlen);
head = aimutil_get32(workingPtr->data);
if ((head == 0x00000001) && (workingPtr->commandlen == 4)) {
faimdprintf(1, "got connection ack on auth line\n");
- workingPtr->handled = 1;
+ 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);
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;
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 {
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)
{
u_int i = 0;
+ if (!sess || !command)
+ return 1;
+
faimdprintf(1, "\nRecieved unknown packet:");
for (i = 0; i < command->commandlen; i++)
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.
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()
*
/* 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.
*/
#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
char password[128];
} socksproxy;
- int snaclogin;
+ 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.
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 *);
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 */
#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
"Not while on AOL"};
static int msgerrreasonslen = 25;
+static char *screenname,*password,*server=NULL;
+
int faimtest_reportinterval(struct aim_session_t *sess, struct command_rx_struct *command, ...)
{
if (command->data) {
return 1;
}
+int faimtest_flapversion(struct aim_session_t *sess, struct command_rx_struct *command, ...)
+{
+
+ printf("faimtest: using FLAP version %u\n", aimutil_get32(command->data));
+
+#if 0
+ /*
+ * This is an alternate location for starting the login process.
+ */
+ /* XXX should do more checking to make sure its really the right AUTH conn */
+ if (command->conn->type == AIM_CONN_TYPE_AUTH) {
+ /* do NOT send a connack/flapversion, request_login will send it if needed */
+ aim_request_login(sess, command->conn, screenname);
+ printf("faimtest: login request sent\n");
+ }
+#endif
+
+ return 1;
+}
+
+/*
+ * This is a frivilous callback. You don't need it. I only used it for
+ * debugging non-blocking connects.
+ *
+ * If packets are sent to a conn before its fully connected, they
+ * will be queued and then transmitted when the connection completes.
+ *
+ */
+int faimtest_conncomplete(struct aim_session_t *sess, struct command_rx_struct *command, ...)
+{
+ va_list ap;
+ struct aim_conn_t *conn;
+
+ va_start(ap, command);
+ conn = va_arg(ap, struct aim_conn_t *);
+ va_end(ap);
+
+ if (conn)
+ printf("faimtest: connection on %d completed\n", conn->fd);
+
+ return 1;
+}
+
#ifdef _WIN32
/*
* This is really all thats needed to link against libfaim on win32.
}
#endif /* _WIN32 */
-static char *screenname,*password,*server=NULL;
-
int main(void)
{
struct aim_session_t aimsess;
}
#endif /* _WIN32 */
- aim_session_init(&aimsess);
+ /* Pass zero as flags if you want blocking connects */
+ aim_session_init(&aimsess, AIM_SESS_FLAGS_NONBLOCKCONNECT);
#ifdef FILESUPPORT
if(listingpath) {
return -1;
}
+ aim_conn_addhandler(&aimsess, authconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_FLAPVER, faimtest_flapversion, 0);
+ aim_conn_addhandler(&aimsess, authconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE, faimtest_conncomplete, 0);
aim_conn_addhandler(&aimsess, authconn, 0x0017, 0x0007, faimtest_parse_login, 0);
- aim_conn_addhandler(&aimsess, authconn, 0x0017, 0x0003, faimtest_parse_authresp, 0);
-
- /* do NOT send a connack/flapversion, request_login will send it if needed */
- aim_request_login(&aimsess, authconn, screenname);
+ aim_conn_addhandler(&aimsess, authconn, 0x0017, 0x0003, faimtest_parse_authresp, 0);
aim_conn_addhandler(&aimsess, authconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_DEBUGCONN_CONNECT, faimtest_debugconn_connect, 0);
+ /* If the connection is in progress, this will just be queued */
+ aim_request_login(&aimsess, authconn, screenname);
printf("faimtest: login request sent\n");
while (keepgoing) {
struct aim_conn_t *tstconn;
/* Open a connection to the Auth */
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) )
fprintf(stderr, "faimtest: unable to reconnect with authorizer\n");
- else
+ else {
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE, faimtest_conncomplete, 0);
/* Send the cookie to the Auth */
aim_auth_sendcookie(sess, tstconn, cookie);
+ }
}
break;
{
struct aim_conn_t *tstconn = NULL;
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)) {
fprintf(stderr, "faimtest: unable to connect to chatnav server\n");
if (tstconn) aim_conn_kill(sess, &tstconn);
return 1;
aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_GEN, AIM_CB_SPECIAL_DEFAULT, aim_parse_unknown, 0);
#endif
aim_conn_addhandler(sess, tstconn, 0x0001, 0x0003, faimtest_serverready, 0);
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE, faimtest_conncomplete, 0);
aim_auth_sendcookie(sess, tstconn, cookie);
fprintf(stderr, "\achatnav: connected\n");
}
roomname = va_arg(ap, char *);
tstconn = aim_newconn(sess, AIM_CONN_TYPE_CHAT, ip);
- if ( (tstconn==NULL) || (tstconn->status >= AIM_CONN_STATUS_RESOLVERR))
+ if ( (tstconn==NULL) || (tstconn->status & AIM_CONN_STATUS_RESOLVERR))
{
fprintf(stderr, "faimtest: unable to connect to chat server\n");
if (tstconn) aim_conn_kill(sess, &tstconn);
aim_chat_attachname(tstconn, roomname);
aim_conn_addhandler(sess, tstconn, 0x0001, 0x0003, faimtest_serverready, 0);
+ aim_conn_addhandler(sess, tstconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE, faimtest_conncomplete, 0);
aim_auth_sendcookie(sess, tstconn, cookie);
}
break;
bosconn = aim_newconn(sess, AIM_CONN_TYPE_BOS, sess->logininfo.BOSIP);
if (bosconn == NULL) {
fprintf(stderr, "faimtest: could not connect to BOS: internal error\n");
- } else if (bosconn->status != 0) {
+ } else if (bosconn->status & AIM_CONN_STATUS_CONNERR) {
fprintf(stderr, "faimtest: could not connect to BOS\n");
aim_conn_kill(sess, &bosconn);
} else {
+ aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE, faimtest_conncomplete, 0);
aim_conn_addhandler(sess, bosconn, 0x0009, 0x0003, faimtest_bosrights, 0);
aim_conn_addhandler(sess, bosconn, 0x0001, 0x0007, faimtest_rateresp, 0); /* rate info */
aim_conn_addhandler(sess, bosconn, AIM_CB_FAM_ACK, AIM_CB_ACK_ACK, NULL, 0);