/** * Subtype 0x0005 - Receive a buddy icon. * * This is sent in response to a buddy icon request. */ static int parseicon(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; char *sn; fu16_t flags, iconlen; fu8_t iconcsumtype, iconcsumlen, *iconcsum, *icon; sn = aimbs_getstr(bs, aimbs_get8(bs)); flags = aimbs_get16(bs); iconcsumtype = aimbs_get8(bs); iconcsumlen = aimbs_get8(bs); iconcsum = aimbs_getraw(bs, iconcsumlen); iconlen = aimbs_get16(bs); icon = aimbs_getraw(bs, iconlen); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, sn, iconcsumtype, iconcsum, iconcsumlen, icon, iconlen); free(sn); free(iconcsum); free(icon); return ret; }
/* * Read a FLAP header from conn into fr, and return the number of bytes in the payload. */ static int aim_get_command_flap(aim_session_t *sess, aim_conn_t *conn, aim_frame_t *fr) { fu8_t flaphdr_raw[6]; aim_bstream_t flaphdr; fu16_t payloadlen; aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); /* * Read FLAP header. Six bytes: * 0 char -- Always 0x2a * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login. * 2 short -- Sequence number * 4 short -- Number of data bytes that follow. */ if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { aim_conn_close(conn); return -1; } aim_bstream_rewind(&flaphdr); /* * This shouldn't happen unless the socket breaks, the server breaks, * or we break. We must handle it just in case. */ if (aimbs_get8(&flaphdr) != 0x2a) { fu8_t start; aim_bstream_rewind(&flaphdr); start = aimbs_get8(&flaphdr); faimdprintf(sess, 0, "FLAP framing disrupted (0x%02x)", start); aim_conn_close(conn); return -1; } /* we're doing FLAP if we're here */ fr->hdrtype = AIM_FRAMETYPE_FLAP; fr->hdr.flap.type = aimbs_get8(&flaphdr); fr->hdr.flap.seqnum = aimbs_get16(&flaphdr); payloadlen = aimbs_get16(&flaphdr); /* length of payload */ return payloadlen; }
faim_internal int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo) { int namelen; if (!bs || !outinfo) return 0; outinfo->exchange = aimbs_get16(bs); namelen = aimbs_get8(bs); outinfo->name = aimbs_getstr(bs, namelen); outinfo->instance = aimbs_get16(bs); return 0; }
/** * Subtype 0x0003 - Acknowledgement for uploading a buddy icon. * * You get this honky after you upload a buddy icon. */ static int uploadack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; fu16_t something, somethingelse; fu8_t onemorething; something = aimbs_get16(bs); somethingelse = aimbs_get16(bs); onemorething = aimbs_get8(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx); return ret; }
faim_internal int aim_parse_unknown(aim_session_t *sess, aim_frame_t *frame, ...) { int i; faimdprintf(sess, 1, "\nRecieved unknown packet:"); for (i = 0; aim_bstream_empty(&frame->data); i++) { if ((i % 8) == 0) faimdprintf(sess, 1, "\n\t"); faimdprintf(sess, 1, "0x%2x ", aimbs_get8(&frame->data)); } faimdprintf(sess, 1, "\n\n"); return 1; }
/* * Subtype 0x0014 - Receive a mini typing notification (mtn) packet. * * This is supported by winaim5 and newer, MacAIM bleh and newer, iChat bleh and newer, * and Gaim 0.60 and newer. * */ static int mtn_receive(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; char *sn; guint8 snlen; guint16 type1, type2; aim_bstream_advance(bs, 8); /* Unknown - All 0's */ type1 = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); type2 = aimbs_get16(bs); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, type1, sn, type2); g_free(sn); return ret; }
static int msgack(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_rxcallback_t userfunc; guint16 type; guint8 snlen, *ck; char *sn; int ret = 0; ck = aimbs_getraw(bs, 8); type = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, type, sn); g_free(sn); g_free(ck); return ret; }
static void dumptlv(aim_session_t *sess, fu16_t type, aim_bstream_t *bs, fu8_t len) { int i; if (!sess || !bs || !len) return; faimdprintf(sess, 0, "userinfo: type =0x%04x\n", type); faimdprintf(sess, 0, "userinfo: length=0x%04x\n", len); faimdprintf(sess, 0, "userinfo: value:\n"); for (i = 0; i < len; i++) { if ((i % 8) == 0) faimdprintf(sess, 0, "\nuserinfo: "); faimdprintf(sess, 0, "0x%2x ", aimbs_get8(bs)); } faimdprintf(sess, 0, "\n"); return; }
/* * Grab a single command sequence off the socket, and enqueue * it in the incoming event queue in a seperate struct. */ faim_export int aim_get_command(aim_session_t *sess, aim_conn_t *conn) { fu8_t flaphdr_raw[6]; aim_bstream_t flaphdr; aim_frame_t *newrx; fu16_t payloadlen; if (!sess || !conn) return 0; if (conn->fd == -1) return -1; /* its a aim_conn_close()'d connection */ 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. */ if (conn->type == AIM_CONN_TYPE_RENDEZVOUS) return aim_get_command_rendezvous(sess, conn); else if (conn->type == AIM_CONN_TYPE_RENDEZVOUS_OUT) { faimdprintf(sess, 0, "AIM_CONN_TYPE_RENDEZVOUS_OUT on fd %d\n", conn->fd); return 0; } aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); /* * Read FLAP header. Six bytes: * * 0 char -- Always 0x2a * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login. * 2 short -- Sequence number * 4 short -- Number of data bytes that follow. */ if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { aim_conn_close(conn); return -1; } aim_bstream_rewind(&flaphdr); /* * This shouldn't happen unless the socket breaks, the server breaks, * or we break. We must handle it just in case. */ if (aimbs_get8(&flaphdr) != 0x2a) { fu8_t start; aim_bstream_rewind(&flaphdr); start = aimbs_get8(&flaphdr); faimdprintf(sess, 0, "FLAP framing disrupted (0x%02x)", start); aim_conn_close(conn); return -1; } /* allocate a new struct */ if (!(newrx = (aim_frame_t *)malloc(sizeof(aim_frame_t)))) return -1; memset(newrx, 0, sizeof(aim_frame_t)); /* we're doing FLAP if we're here */ newrx->hdrtype = AIM_FRAMETYPE_FLAP; newrx->hdr.flap.type = aimbs_get8(&flaphdr); newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr); payloadlen = aimbs_get16(&flaphdr); newrx->nofree = 0; /* free by default */ if (payloadlen) { fu8_t *payload = NULL; if (!(payload = (fu8_t *) malloc(payloadlen))) { aim_frame_destroy(newrx); return -1; } aim_bstream_init(&newrx->data, payload, payloadlen); /* read the payload */ if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) { free(payload); aim_frame_destroy(newrx); aim_conn_close(conn); return -1; } } else aim_bstream_init(&newrx->data, NULL, 0); aim_bstream_rewind(&newrx->data); newrx->conn = conn; newrx->next = NULL; /* this will always be at the bottom */ if (!sess->queue_incoming) sess->queue_incoming = newrx; else { aim_frame_t *cur; for (cur = sess->queue_incoming; cur->next; cur = cur->next) ; cur->next = newrx; } newrx->conn->lastactivity = time(NULL); return 0; }
/* * General room information. Lots of stuff. * * Values I know are in here but I havent attached * them to any of the 'Unknown's: * - Language (English) * * SNAC 000e/0002 */ static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { aim_userinfo_t *userinfo = NULL; aim_rxcallback_t userfunc; int ret = 0; int usercount = 0; fu8_t detaillevel = 0; char *roomname = NULL; struct aim_chat_roominfo roominfo; fu16_t tlvcount = 0; aim_tlvlist_t *tlvlist; char *roomdesc = NULL; fu16_t flags = 0; fu32_t creationtime = 0; fu16_t maxmsglen = 0, maxvisiblemsglen = 0; fu16_t unknown_d2 = 0, unknown_d5 = 0; aim_chat_readroominfo(bs, &roominfo); detaillevel = aimbs_get8(bs); if (detaillevel != 0x02) { faimdprintf(sess, 0, "faim: chat_roomupdateinfo: detail level %d not supported\n", detaillevel); return 1; } tlvcount = aimbs_get16(bs); /* * Everything else are TLVs. */ tlvlist = aim_readtlvchain(bs); /* * TLV type 0x006a is the room name in Human Readable Form. */ if (aim_gettlv(tlvlist, 0x006a, 1)) roomname = aim_gettlv_str(tlvlist, 0x006a, 1); /* * Type 0x006f: Number of occupants. */ if (aim_gettlv(tlvlist, 0x006f, 1)) usercount = aim_gettlv16(tlvlist, 0x006f, 1); /* * Type 0x0073: Occupant list. */ if (aim_gettlv(tlvlist, 0x0073, 1)) { int curoccupant = 0; aim_tlv_t *tmptlv; aim_bstream_t occbs; tmptlv = aim_gettlv(tlvlist, 0x0073, 1); /* Allocate enough userinfo structs for all occupants */ userinfo = calloc(usercount, sizeof(aim_userinfo_t)); aim_bstream_init(&occbs, tmptlv->value, tmptlv->length); while (curoccupant < usercount) aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]); } /* * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG) */ if (aim_gettlv(tlvlist, 0x00c9, 1)) flags = aim_gettlv16(tlvlist, 0x00c9, 1); /* * Type 0x00ca: Creation time (4 bytes) */ if (aim_gettlv(tlvlist, 0x00ca, 1)) creationtime = aim_gettlv32(tlvlist, 0x00ca, 1); /* * Type 0x00d1: Maximum Message Length */ if (aim_gettlv(tlvlist, 0x00d1, 1)) maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1); /* * Type 0x00d2: Unknown. (2 bytes) */ if (aim_gettlv(tlvlist, 0x00d2, 1)) unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1); /* * Type 0x00d3: Room Description */ if (aim_gettlv(tlvlist, 0x00d3, 1)) roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1); /* * Type 0x000d4: Unknown (flag only) */ if (aim_gettlv(tlvlist, 0x000d4, 1)) ; /* * Type 0x00d5: Unknown. (1 byte) */ if (aim_gettlv(tlvlist, 0x00d5, 1)) unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1); /* * Type 0x00d6: Encoding 1 ("us-ascii") */ if (aim_gettlv(tlvlist, 0x000d6, 1)) ; /* * Type 0x00d7: Language 1 ("en") */ if (aim_gettlv(tlvlist, 0x000d7, 1)) ; /* * Type 0x00d8: Encoding 2 ("us-ascii") */ if (aim_gettlv(tlvlist, 0x000d8, 1)) ; /* * Type 0x00d9: Language 2 ("en") */ if (aim_gettlv(tlvlist, 0x000d9, 1)) ; /* * Type 0x00da: Maximum visible message length */ if (aim_gettlv(tlvlist, 0x000da, 1)) maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { ret = userfunc(sess, rx, &roominfo, roomname, usercount, userinfo, roomdesc, flags, creationtime, maxmsglen, unknown_d2, unknown_d5, maxvisiblemsglen); } free(roominfo.name); free(userinfo); free(roomname); free(roomdesc); aim_freetlvchain(&tlvlist); return ret; }
/* * Grab a single command sequence off the socket, and enqueue * it in the incoming event queue in a seperate struct. */ int aim_get_command(aim_session_t *sess, aim_conn_t *conn) { guint8 flaphdr_raw[6]; aim_bstream_t flaphdr; aim_frame_t *newrx; guint16 payloadlen; if (!sess || !conn) return 0; if (conn->fd == -1) return -1; /* its a aim_conn_close()'d connection */ /* KIDS, THIS IS WHAT HAPPENS IF YOU USE CODE WRITTEN FOR GUIS IN A DAEMON! And wouldn't it make sense to return something that prevents this function from being called again IMMEDIATELY (and making the program suck up all CPU time)?... if (conn->fd < 3) return 0; */ if (conn->status & AIM_CONN_STATUS_INPROGRESS) return aim_conn_completeconnect(sess, conn); aim_bstream_init(&flaphdr, flaphdr_raw, sizeof(flaphdr_raw)); /* * Read FLAP header. Six bytes: * * 0 char -- Always 0x2a * 1 char -- Channel ID. Usually 2 -- 1 and 4 are used during login. * 2 short -- Sequence number * 4 short -- Number of data bytes that follow. */ if (aim_bstream_recv(&flaphdr, conn->fd, 6) < 6) { aim_conn_close(conn); return -1; } aim_bstream_rewind(&flaphdr); /* * This shouldn't happen unless the socket breaks, the server breaks, * or we break. We must handle it just in case. */ if (aimbs_get8(&flaphdr) != 0x2a) { aim_bstream_rewind(&flaphdr); aimbs_get8(&flaphdr); imcb_error(sess->aux_data, "FLAP framing disrupted"); aim_conn_close(conn); return -1; } /* allocate a new struct */ if (!(newrx = (aim_frame_t *)g_new0(aim_frame_t,1))) return -1; /* we're doing FLAP if we're here */ newrx->hdrtype = AIM_FRAMETYPE_FLAP; newrx->hdr.flap.type = aimbs_get8(&flaphdr); newrx->hdr.flap.seqnum = aimbs_get16(&flaphdr); payloadlen = aimbs_get16(&flaphdr); newrx->nofree = 0; /* free by default */ if (payloadlen) { guint8 *payload = NULL; if (!(payload = (guint8 *) g_malloc(payloadlen))) { aim_frame_destroy(newrx); return -1; } aim_bstream_init(&newrx->data, payload, payloadlen); /* read the payload */ if (aim_bstream_recv(&newrx->data, conn->fd, payloadlen) < payloadlen) { aim_frame_destroy(newrx); /* free's payload */ aim_conn_close(conn); return -1; } } else aim_bstream_init(&newrx->data, NULL, 0); aim_bstream_rewind(&newrx->data); newrx->conn = conn; newrx->next = NULL; /* this will always be at the bottom */ if (!sess->queue_incoming) sess->queue_incoming = newrx; else { aim_frame_t *cur; for (cur = sess->queue_incoming; cur->next; cur = cur->next) ; cur->next = newrx; } newrx->conn->lastactivity = time(NULL); return 0; }
/* * Receive the response from an ICQ status message request. This contains the * ICQ status message. Go figure. */ static int clientautoresp(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 channel, reason; char *sn; guint8 *ck, snlen; ck = aimbs_getraw(bs, 8); channel = aimbs_get16(bs); snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); reason = aimbs_get16(bs); switch (reason) { case 0x0003: { /* ICQ status message. Maybe other stuff too, you never know with these people. */ guint8 statusmsgtype, *msg; guint16 len; guint32 state; len = aimbs_getle16(bs); /* Should be 0x001b */ aim_bstream_advance(bs, len); /* Unknown */ len = aimbs_getle16(bs); /* Should be 0x000e */ aim_bstream_advance(bs, len); /* Unknown */ statusmsgtype = aimbs_getle8(bs); switch (statusmsgtype) { case 0xe8: state = AIM_ICQ_STATE_AWAY; break; case 0xe9: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_BUSY; break; case 0xea: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_OUT; break; case 0xeb: state = AIM_ICQ_STATE_AWAY | AIM_ICQ_STATE_DND | AIM_ICQ_STATE_BUSY; break; case 0xec: state = AIM_ICQ_STATE_CHAT; break; default: state = 0; break; } aimbs_getle8(bs); /* Unknown - 0x03 Maybe this means this is an auto-reply */ aimbs_getle16(bs); /* Unknown - 0x0000 */ aimbs_getle16(bs); /* Unknown - 0x0000 */ len = aimbs_getle16(bs); msg = aimbs_getraw(bs, len); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, reason, state, msg); g_free(msg); } break; default: { if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, reason); } break; } /* end switch */ g_free(ck); g_free(sn); return ret; }
/* * Subtype 0x0006 * * We could probably include this in the normal ICBM parsing * code as channel 0x0003, however, since only the start * would be the same, we might as well do it here. * * General outline of this SNAC: * snac * cookie * channel id * tlvlist * unknown * source user info * name * evility * userinfo tlvs * online time * etc * message metatlv * message tlv * message string * possibly others * */ static int incomingim_ch3(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int ret = 0, i; aim_rxcallback_t userfunc; aim_userinfo_t userinfo; fu8_t cookie[8]; fu16_t channel; aim_tlvlist_t *otl; char *msg = NULL; int len = 0; char *encoding = NULL, *language = NULL; aim_msgcookie_t *ck; memset(&userinfo, 0, sizeof(aim_userinfo_t)); /* * Read ICBM Cookie. */ for (i = 0; i < 8; i++) cookie[i] = aimbs_get8(bs); if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) { free(ck->data); free(ck); } /* * Channel ID * * Channel 0x0003 is used for chat messages. * */ channel = aimbs_get16(bs); if (channel != 0x0003) { faimdprintf(sess, 0, "faim: chat_incoming: unknown channel! (0x%04x)\n", channel); return 0; } /* * Start parsing TLVs right away. */ otl = aim_tlvlist_read(bs); /* * Type 0x0003: Source User Information */ if (aim_tlv_gettlv(otl, 0x0003, 1)) { aim_tlv_t *userinfotlv; aim_bstream_t tbs; userinfotlv = aim_tlv_gettlv(otl, 0x0003, 1); aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length); aim_info_extract(sess, &tbs, &userinfo); } /* * Type 0x0001: If present, it means it was a message to the * room (as opposed to a whisper). */ if (aim_tlv_gettlv(otl, 0x0001, 1)) ; /* * Type 0x0005: Message Block. Conains more TLVs. */ if (aim_tlv_gettlv(otl, 0x0005, 1)) { aim_tlvlist_t *itl; aim_tlv_t *msgblock; aim_bstream_t tbs; msgblock = aim_tlv_gettlv(otl, 0x0005, 1); aim_bstream_init(&tbs, msgblock->value, msgblock->length); itl = aim_tlvlist_read(&tbs); /* * Type 0x0001: Message. */ if (aim_tlv_gettlv(itl, 0x0001, 1)) { msg = aim_tlv_getstr(itl, 0x0001, 1); len = aim_tlv_gettlv(itl, 0x0001, 1)->length; } /* * Type 0x0002: Encoding. */ if (aim_tlv_gettlv(itl, 0x0002, 1)) encoding = aim_tlv_getstr(itl, 0x0002, 1); /* * Type 0x0003: Language. */ if (aim_tlv_gettlv(itl, 0x0003, 1)) language = aim_tlv_getstr(itl, 0x0003, 1); aim_tlvlist_free(&itl); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, &userinfo, len, msg, encoding, language); aim_info_free(&userinfo); free(msg); aim_tlvlist_free(&otl); return ret; }
/* * AIM is fairly regular about providing user info. This is a generic * routine to extract it in its standard form. */ faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo) { int curtlv, tlvcnt; fu8_t snlen; if (!bs || !outinfo) return -EINVAL; /* Clear out old data first */ memset(outinfo, 0x00, sizeof(aim_userinfo_t)); /* * Screen name. Stored as an unterminated string prepended with a * byte containing its length. */ snlen = aimbs_get8(bs); aimbs_getrawbuf(bs, outinfo->sn, snlen); /* * Warning Level. Stored as an unsigned short. */ outinfo->warnlevel = aimbs_get16(bs); /* * TLV Count. Unsigned short representing the number of * Type-Length-Value triples that follow. */ tlvcnt = aimbs_get16(bs); /* * Parse out the Type-Length-Value triples as they're found. */ for (curtlv = 0; curtlv < tlvcnt; curtlv++) { int endpos; fu16_t type, length; type = aimbs_get16(bs); length = aimbs_get16(bs); endpos = aim_bstream_curpos(bs) + length; if (type == 0x0001) { /* * Type = 0x0001: User flags * * Specified as any of the following ORed together: * 0x0001 Trial (user less than 60days) * 0x0002 Unknown bit 2 * 0x0004 AOL Main Service user * 0x0008 Unknown bit 4 * 0x0010 Free (AIM) user * 0x0020 Away * 0x0400 ActiveBuddy * */ outinfo->flags = aimbs_get16(bs); } else if (type == 0x0002) { /* * Type = 0x0002: Member-Since date. * * The time/date that the user originally registered for * the service, stored in time_t format. */ outinfo->membersince = aimbs_get32(bs); } else if (type == 0x0003) { /* * Type = 0x0003: On-Since date. * * The time/date that the user started their current * session, stored in time_t format. */ outinfo->onlinesince = aimbs_get32(bs); } else if (type == 0x0004) { /* * Type = 0x0004: Idle time. * * Number of seconds since the user actively used the * service. * * Note that the client tells the server when to start * counting idle times, so this may or may not be * related to reality. */ outinfo->idletime = aimbs_get16(bs); } else if (type == 0x0006) { /* * Type = 0x0006: ICQ Online Status * * ICQ's Away/DND/etc "enriched" status. Some decoding * of values done by Scott <*****@*****.**> */ aimbs_get16(bs); outinfo->icqinfo.status = aimbs_get16(bs); } else if (type == 0x000a) { /* * Type = 0x000a * * ICQ User IP Address. * Ahh, the joy of ICQ security. */ outinfo->icqinfo.ipaddr = aimbs_get32(bs); } else if (type == 0x000c) { /* * Type = 0x000c * * random crap containing the IP address, * apparently a port number, and some Other Stuff. * */ aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); } else if (type == 0x000d) { /* * Type = 0x000d * * Capability information. * */ outinfo->capabilities = aim_getcap(sess, bs, length); outinfo->capspresent = 1; } else if (type == 0x000e) { /* * Type = 0x000e * * Unknown. Always of zero length, and always only * on AOL users. * * Ignore. * */ } else if ((type == 0x000f) || (type == 0x0010)) { /* * Type = 0x000f: Session Length. (AIM) * Type = 0x0010: Session Length. (AOL) * * The duration, in seconds, of the user's current * session. * * Which TLV type this comes in depends on the * service the user is using (AIM or AOL). * */ outinfo->sessionlen = aimbs_get32(bs); } else { /* * Reaching here indicates that either AOL has * added yet another TLV for us to deal with, * or the parsing has gone Terribly Wrong. * * Either way, inform the owner and attempt * recovery. * */ faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV:\n"); faimdprintf(sess, 0, "userinfo: sn =%s\n", outinfo->sn); faimdprintf(sess, 0, "userinfo: type =0x%04x\n",type); faimdprintf(sess, 0, "userinfo: length=0x%04x\n", length); } /* Save ourselves. */ aim_bstream_setpos(bs, endpos); } return 0; }
static int incomingim_ch1(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, aim_bstream_t *bs, guint8 *cookie) { guint16 type, length; aim_rxcallback_t userfunc; int ret = 0; struct aim_incomingim_ch1_args args; int endpos; memset(&args, 0, sizeof(args)); aim_mpmsg_init(sess, &args.mpmsg); /* * This used to be done using tlvchains. For performance reasons, * I've changed it to process the TLVs in-place. This avoids lots * of per-IM memory allocations. */ while (aim_bstream_empty(bs)) { type = aimbs_get16(bs); length = aimbs_get16(bs); endpos = aim_bstream_curpos(bs) + length; if (type == 0x0002) { /* Message Block */ /* * This TLV consists of the following: * - 0501 -- Unknown * - Features: Don't know how to interpret these * - 0101 -- Unknown * - Message * */ aimbs_get8(bs); /* 05 */ aimbs_get8(bs); /* 01 */ args.featureslen = aimbs_get16(bs); /* XXX XXX this is all evil! */ args.features = bs->data + bs->offset; aim_bstream_advance(bs, args.featureslen); args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES; /* * The rest of the TLV contains one or more message * blocks... */ incomingim_ch1_parsemsgs(sess, bs->data + bs->offset /* XXX evil!!! */, length - 2 - 2 - args.featureslen, &args); } else if (type == 0x0003) { /* Server Ack Requested */ args.icbmflags |= AIM_IMFLAGS_ACK; } else if (type == 0x0004) { /* Message is Auto Response */ args.icbmflags |= AIM_IMFLAGS_AWAY; } else if (type == 0x0006) { /* Message was received offline. */ /* XXX not sure if this actually gets sent. */ args.icbmflags |= AIM_IMFLAGS_OFFLINE; } else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */ args.iconlen = aimbs_get32(bs); aimbs_get16(bs); /* 0x0001 */ args.iconsum = aimbs_get16(bs); args.iconstamp = aimbs_get32(bs); /* * This looks to be a client bug. MacAIM 4.3 will * send this tag, but with all zero values, in the * first message of a conversation. This makes no * sense whatsoever, so I'm going to say its a bug. * * You really shouldn't advertise a zero-length icon * anyway. * */ if (args.iconlen) args.icbmflags |= AIM_IMFLAGS_HASICON; } else if (type == 0x0009) { args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; } else if (type == 0x0017) { args.extdatalen = length; args.extdata = aimbs_getraw(bs, args.extdatalen); } else { // imcb_error(sess->aux_data, "Unknown TLV encountered"); } /* * This is here to protect ourselves from ourselves. That * is, if something above doesn't completly parse its value * section, or, worse, overparses it, this will set the * stream where it needs to be in order to land on the next * TLV when the loop continues. * */ aim_bstream_setpos(bs, endpos); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, userinfo, &args); aim_mpmsg_free(sess, &args.mpmsg); g_free(args.extdata); return ret; }
/* * Start by building the multipart structures, then pick the first * human-readable section and stuff it into args->msg so no one gets * suspicious. * */ static int incomingim_ch1_parsemsgs(aim_session_t *sess, guint8 *data, int len, struct aim_incomingim_ch1_args *args) { static const guint16 charsetpri[] = { 0x0000, /* ASCII first */ 0x0003, /* then ISO-8859-1 */ 0x0002, /* UNICODE as last resort */ }; static const int charsetpricount = 3; int i; aim_bstream_t mbs; aim_mpmsg_section_t *sec; aim_bstream_init(&mbs, data, len); while (aim_bstream_empty(&mbs)) { guint16 msglen, flag1, flag2; char *msgbuf; aimbs_get8(&mbs); /* 01 */ aimbs_get8(&mbs); /* 01 */ /* Message string length, including character set info. */ msglen = aimbs_get16(&mbs); /* Character set info */ flag1 = aimbs_get16(&mbs); flag2 = aimbs_get16(&mbs); /* Message. */ msglen -= 4; /* * For now, we don't care what the encoding is. Just copy * it into a multipart struct and deal with it later. However, * always pad the ending with a NULL. This makes it easier * to treat ASCII sections as strings. It won't matter for * UNICODE or binary data, as you should never read past * the specified data length, which will not include the pad. * * XXX There's an API bug here. For sending, the UNICODE is * given in host byte order (aim_mpmsg_addunicode), but here * the received messages are given in network byte order. * */ msgbuf = aimbs_getstr(&mbs, msglen); mpmsg_addsection(sess, &args->mpmsg, flag1, flag2, (guint8 *)msgbuf, (guint16) msglen); } /* while */ args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */ /* * Clients that support multiparts should never use args->msg, as it * will point to an arbitrary section. * * Here, we attempt to provide clients that do not support multipart * messages with something to look at -- hopefully a human-readable * string. But, failing that, a UNICODE message, or nothing at all. * * Which means that even if args->msg is NULL, it does not mean the * message was blank. * */ for (i = 0; i < charsetpricount; i++) { for (sec = args->mpmsg.parts; sec; sec = sec->next) { if (sec->charset != charsetpri[i]) continue; /* Great. We found one. Fill it in. */ args->charset = sec->charset; args->charsubset = sec->charsubset; args->icbmflags |= AIM_IMFLAGS_CUSTOMCHARSET; /* Set up the simple flags */ if (args->charset == 0x0000) ; /* ASCII */ else if (args->charset == 0x0002) args->icbmflags |= AIM_IMFLAGS_UNICODE; else if (args->charset == 0x0003) args->icbmflags |= AIM_IMFLAGS_ISO_8859_1; else if (args->charset == 0xffff) ; /* no encoding (yeep!) */ if (args->charsubset == 0x0000) ; /* standard subencoding? */ else if (args->charsubset == 0x000b) args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH; else if (args->charsubset == 0xffff) ; /* no subencoding */ #if 0 /* XXX this isn't really necesary... */ if ( ((args.flag1 != 0x0000) && (args.flag1 != 0x0002) && (args.flag1 != 0x0003) && (args.flag1 != 0xffff)) || ((args.flag2 != 0x0000) && (args.flag2 != 0x000b) && (args.flag2 != 0xffff))) { faimdprintf(sess, 0, "icbm: **warning: encoding flags are being used! {%04x, %04x}\n", args.flag1, args.flag2); } #endif args->msg = (char *)sec->data; args->msglen = sec->datalen; return 0; } } /* No human-readable sections found. Oh well. */ args->charset = args->charsubset = 0xffff; args->msg = NULL; args->msglen = 0; return 0; }
static int outgoingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int i, ret = 0; aim_rxcallback_t userfunc; guint16 channel; aim_tlvlist_t *tlvlist; char *sn; int snlen; guint16 icbmflags = 0; guint8 flag1 = 0, flag2 = 0; char *msg = NULL; aim_tlv_t *msgblock; /* ICBM Cookie. */ for (i = 0; i < 8; i++) aimbs_get8(bs); /* Channel ID */ channel = aimbs_get16(bs); if (channel != 0x01) { imcb_error(sess->aux_data, "icbm: ICBM received on unsupported channel. Ignoring."); return 0; } snlen = aimbs_get8(bs); sn = aimbs_getstr(bs, snlen); tlvlist = aim_readtlvchain(bs); if (aim_gettlv(tlvlist, 0x0003, 1)) icbmflags |= AIM_IMFLAGS_ACK; if (aim_gettlv(tlvlist, 0x0004, 1)) icbmflags |= AIM_IMFLAGS_AWAY; if ((msgblock = aim_gettlv(tlvlist, 0x0002, 1))) { aim_bstream_t mbs; int featurelen, msglen; aim_bstream_init(&mbs, msgblock->value, msgblock->length); aimbs_get8(&mbs); aimbs_get8(&mbs); for (featurelen = aimbs_get16(&mbs); featurelen; featurelen--) aimbs_get8(&mbs); aimbs_get8(&mbs); aimbs_get8(&mbs); msglen = aimbs_get16(&mbs) - 4; /* final block length */ flag1 = aimbs_get16(&mbs); flag2 = aimbs_get16(&mbs); msg = aimbs_getstr(&mbs, msglen); } if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) ret = userfunc(sess, rx, channel, sn, msg, icbmflags, flag1, flag2); g_free(sn); aim_freetlvchain(&tlvlist); return ret; }
/* * AIM is fairly regular about providing user info. This is a generic * routine to extract it in its standard form. */ faim_internal int aim_extractuserinfo(aim_session_t *sess, aim_bstream_t *bs, aim_userinfo_t *outinfo) { int curtlv, tlvcnt; fu8_t snlen; if (!bs || !outinfo) return -EINVAL; /* Clear out old data first */ memset(outinfo, 0x00, sizeof(aim_userinfo_t)); /* * Screen name. Stored as an unterminated string prepended with a * byte containing its length. */ snlen = aimbs_get8(bs); aimbs_getrawbuf(bs, outinfo->sn, snlen); /* * Warning Level. Stored as an unsigned short. */ outinfo->warnlevel = aimbs_get16(bs); /* * TLV Count. Unsigned short representing the number of * Type-Length-Value triples that follow. */ tlvcnt = aimbs_get16(bs); /* * Parse out the Type-Length-Value triples as they're found. */ for (curtlv = 0; curtlv < tlvcnt; curtlv++) { int endpos; fu16_t type, length; type = aimbs_get16(bs); length = aimbs_get16(bs); endpos = aim_bstream_curpos(bs) + length; if (type == 0x0001) { /* * Type = 0x0001: User flags * * Specified as any of the following ORed together: * 0x0001 Trial (user less than 60days) * 0x0002 Unknown bit 2 * 0x0004 AOL Main Service user * 0x0008 Unknown bit 4 * 0x0010 Free (AIM) user * 0x0020 Away * 0x0400 ActiveBuddy * */ outinfo->flags = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_FLAGS; } else if (type == 0x0002) { /* * Type = 0x0002: Account creation time. * * The time/date that the user originally registered for * the service, stored in time_t format. * * I'm not sure how this differs from type 5 ("member * since"). * * Note: This is the field formerly known as "member * since". All these years and I finally found out * that I got the name wrong. */ outinfo->createtime = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_CREATETIME; } else if (type == 0x0003) { /* * Type = 0x0003: On-Since date. * * The time/date that the user started their current * session, stored in time_t format. */ outinfo->onlinesince = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_ONLINESINCE; } else if (type == 0x0004) { /* * Type = 0x0004: Idle time. * * Number of seconds since the user actively used the * service. * * Note that the client tells the server when to start * counting idle times, so this may or may not be * related to reality. */ outinfo->idletime = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_IDLE; } else if (type == 0x0005) { /* * Type = 0x0005: Member since date. * * The time/date that the user originally registered for * the service, stored in time_t format. * * This is sometimes sent instead of type 2 ("account * creation time"), particularly in the self-info. */ outinfo->membersince = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_MEMBERSINCE; } else if (type == 0x0006) { /* * Type = 0x0006: ICQ Online Status * * ICQ's Away/DND/etc "enriched" status. Some decoding * of values done by Scott <*****@*****.**> */ aimbs_get16(bs); outinfo->icqinfo.status = aimbs_get16(bs); outinfo->present |= AIM_USERINFO_PRESENT_ICQEXTSTATUS; } else if (type == 0x000a) { /* * Type = 0x000a * * ICQ User IP Address. * Ahh, the joy of ICQ security. */ outinfo->icqinfo.ipaddr = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_ICQIPADDR; } else if (type == 0x000c) { /* * Type = 0x000c * * random crap containing the IP address, * apparently a port number, and some Other Stuff. * */ aimbs_getrawbuf(bs, outinfo->icqinfo.crap, 0x25); outinfo->present |= AIM_USERINFO_PRESENT_ICQDATA; } else if (type == 0x000d) { /* * Type = 0x000d * * Capability information. * */ outinfo->capabilities = aim_getcap(sess, bs, length); outinfo->present |= AIM_USERINFO_PRESENT_CAPABILITIES; } else if (type == 0x000e) { /* * Type = 0x000e * * Unknown. Always of zero length, and always only * on AOL users. * * Ignore. * */ } else if ((type == 0x000f) || (type == 0x0010)) { /* * Type = 0x000f: Session Length. (AIM) * Type = 0x0010: Session Length. (AOL) * * The duration, in seconds, of the user's current * session. * * Which TLV type this comes in depends on the * service the user is using (AIM or AOL). * */ outinfo->sessionlen = aimbs_get32(bs); outinfo->present |= AIM_USERINFO_PRESENT_SESSIONLEN; } else if (type == 0x001d) { /* * Type 29: Unknown. * * Currently very rare. Always 18 bytes of mostly zero. */ } else if (type == 0x001e) { /* * Type 30: Unknown. * * Always four bytes, but it doesn't look like an int. */ } else { /* * Reaching here indicates that either AOL has * added yet another TLV for us to deal with, * or the parsing has gone Terribly Wrong. * * Either way, inform the owner and attempt * recovery. * */ faimdprintf(sess, 0, "userinfo: **warning: unexpected TLV:\n"); faimdprintf(sess, 0, "userinfo: sn =%s\n", outinfo->sn); dumptlv(sess, type, bs, length); } /* Save ourselves. */ aim_bstream_setpos(bs, endpos); } return 0; }
static int parseinfo_create(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs, aim_snac_t *snac2) { aim_rxcallback_t userfunc; aim_tlvlist_t *tlvlist, *innerlist; char *ck = NULL, *fqcn = NULL, *name = NULL; fu16_t exchange = 0, instance = 0, unknown = 0, flags = 0, maxmsglen = 0, maxoccupancy = 0; fu32_t createtime = 0; fu8_t createperms = 0, detaillevel; int cklen; aim_tlv_t *bigblock; int ret = 0; aim_bstream_t bbbs; tlvlist = aim_readtlvchain(bs); if (!(bigblock = aim_gettlv(tlvlist, 0x0004, 1))) { faimdprintf(sess, 0, "no bigblock in top tlv in create room response\n"); aim_freetlvchain(&tlvlist); return 0; } aim_bstream_init(&bbbs, bigblock->value, bigblock->length); exchange = aimbs_get16(&bbbs); cklen = aimbs_get8(&bbbs); ck = aimbs_getstr(&bbbs, cklen); instance = aimbs_get16(&bbbs); detaillevel = aimbs_get8(&bbbs); if (detaillevel != 0x02) { faimdprintf(sess, 0, "unknown detaillevel in create room response (0x%02x)\n", detaillevel); aim_freetlvchain(&tlvlist); free(ck); return 0; } unknown = aimbs_get16(&bbbs); innerlist = aim_readtlvchain(&bbbs); if (aim_gettlv(innerlist, 0x006a, 1)) fqcn = aim_gettlv_str(innerlist, 0x006a, 1); if (aim_gettlv(innerlist, 0x00c9, 1)) flags = aim_gettlv16(innerlist, 0x00c9, 1); if (aim_gettlv(innerlist, 0x00ca, 1)) createtime = aim_gettlv32(innerlist, 0x00ca, 1); if (aim_gettlv(innerlist, 0x00d1, 1)) maxmsglen = aim_gettlv16(innerlist, 0x00d1, 1); if (aim_gettlv(innerlist, 0x00d2, 1)) maxoccupancy = aim_gettlv16(innerlist, 0x00d2, 1); if (aim_gettlv(innerlist, 0x00d3, 1)) name = aim_gettlv_str(innerlist, 0x00d3, 1); if (aim_gettlv(innerlist, 0x00d5, 1)) createperms = aim_gettlv8(innerlist, 0x00d5, 1); if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) { ret = userfunc(sess, rx, snac2->type, fqcn, instance, exchange, flags, createtime, maxmsglen, maxoccupancy, createperms, unknown, name, ck); } free(ck); free(name); free(fqcn); aim_freetlvchain(&innerlist); aim_freetlvchain(&tlvlist); return ret; }
/* * It can easily be said that parsing ICBMs is THE single * most difficult thing to do in the in AIM protocol. In * fact, I think I just did say that. * * Below is the best damned solution I've come up with * over the past sixteen months of battling with it. This * can parse both away and normal messages from every client * I have access to. Its not fast, its not clean. But it works. * */ static int incomingim(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs) { int i, ret = 0; guint8 cookie[8]; guint16 channel; aim_userinfo_t userinfo; memset(&userinfo, 0x00, sizeof(aim_userinfo_t)); /* * Read ICBM Cookie. And throw away. */ for (i = 0; i < 8; i++) cookie[i] = aimbs_get8(bs); /* * Channel ID. * * Channel 0x0001 is the message channel. There are * other channels for things called "rendevous" * which represent chat and some of the other new * features of AIM2/3/3.5. * * Channel 0x0002 is the Rendevous channel, which * is where Chat Invitiations and various client-client * connection negotiations come from. * * Channel 0x0004 is used for ICQ authorization, or * possibly any system notice. * */ channel = aimbs_get16(bs); /* * Extract the standard user info block. * * Note that although this contains TLVs that appear contiguous * with the TLVs read below, they are two different pieces. The * userinfo block contains the number of TLVs that contain user * information, the rest are not even though there is no seperation. * aim_extractuserinfo() returns the number of bytes used by the * userinfo tlvs, so you can start reading the rest of them right * afterward. * * That also means that TLV types can be duplicated between the * userinfo block and the rest of the message, however there should * never be two TLVs of the same type in one block. * */ aim_extractuserinfo(sess, bs, &userinfo); /* * From here on, its depends on what channel we're on. * * Technically all channels have a TLV list have this, however, * for the common channel 1 case, in-place parsing is used for * performance reasons (less memory allocation). */ if (channel == 1) { ret = incomingim_ch1(sess, mod, rx, snac, channel, &userinfo, bs, cookie); } else if (channel == 2) { aim_tlvlist_t *tlvlist; /* * Read block of TLVs (not including the userinfo data). All * further data is derived from what is parsed here. */ tlvlist = aim_readtlvchain(bs); ret = incomingim_ch2(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); aim_freetlvchain(&tlvlist); } else if (channel == 4) { aim_tlvlist_t *tlvlist; tlvlist = aim_readtlvchain(bs); ret = incomingim_ch4(sess, mod, rx, snac, channel, &userinfo, tlvlist, cookie); aim_freetlvchain(&tlvlist); } else { imcb_error(sess->aux_data, "ICBM received on an unsupported channel. Ignoring."); return 0; } return ret; }