source: protocols/oscar/chat.c @ 0b510af

Last change on this file since 0b510af was 84b045d, checked in by Wilmer van der Gaast <wilmer@…>, at 2007-04-16T01:03:08Z

s/imc/imcb/ for callback functions. Moved things aroundin nogaim.h a
little bit, grouping things by category instead of original Gaim 0.58
filename.

  • Property mode set to 100644
File size: 15.2 KB
Line 
1/*
2 * aim_chat.c
3 *
4 * Routines for the Chat service.
5 *
6 */
7
8#include <aim.h> 
9#include <glib.h>
10#include "info.h"
11
12/* Stored in the ->priv of chat connections */
13struct chatconnpriv {
14        guint16 exchange;
15        char *name;
16        guint16 instance;
17};
18
19void aim_conn_kill_chat(aim_session_t *sess, aim_conn_t *conn)
20{
21        struct chatconnpriv *ccp = (struct chatconnpriv *)conn->priv;
22
23        if (ccp)
24                g_free(ccp->name);
25        g_free(ccp);
26
27        return;
28}
29
30char *aim_chat_getname(aim_conn_t *conn)
31{
32        struct chatconnpriv *ccp;
33
34        if (!conn)
35                return NULL;
36
37        if (conn->type != AIM_CONN_TYPE_CHAT)
38                return NULL;
39
40        ccp = (struct chatconnpriv *)conn->priv;
41
42        return ccp->name;
43}
44
45/* XXX get this into conn.c -- evil!! */
46aim_conn_t *aim_chat_getconn(aim_session_t *sess, const char *name)
47{
48        aim_conn_t *cur;
49
50        for (cur = sess->connlist; cur; cur = cur->next) {
51                struct chatconnpriv *ccp = (struct chatconnpriv *)cur->priv;
52
53                if (cur->type != AIM_CONN_TYPE_CHAT)
54                        continue;
55                if (!cur->priv) {
56                        imcb_error(sess->aux_data, "chat connection with no name!");
57                        continue;
58                }
59
60                if (strcmp(ccp->name, name) == 0)
61                        break;
62        }
63
64        return cur;
65}
66
67int aim_chat_attachname(aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance)
68{
69        struct chatconnpriv *ccp;
70
71        if (!conn || !roomname)
72                return -EINVAL;
73
74        if (conn->priv)
75                g_free(conn->priv);
76
77        if (!(ccp = g_malloc(sizeof(struct chatconnpriv))))
78                return -ENOMEM;
79
80        ccp->exchange = exchange;
81        ccp->name = g_strdup(roomname);
82        ccp->instance = instance;
83
84        conn->priv = (void *)ccp;
85
86        return 0;
87}
88
89/*
90 * Send a Chat Message.
91 *
92 * Possible flags:
93 *   AIM_CHATFLAGS_NOREFLECT   --  Unset the flag that requests messages
94 *                                 should be sent to their sender.
95 *   AIM_CHATFLAGS_AWAY        --  Mark the message as an autoresponse
96 *                                 (Note that WinAIM does not honor this,
97 *                                 and displays the message as normal.)
98 *
99 * XXX convert this to use tlvchains
100 */
101int aim_chat_send_im(aim_session_t *sess, aim_conn_t *conn, guint16 flags, const char *msg, int msglen)
102{   
103        int i;
104        aim_frame_t *fr;
105        aim_msgcookie_t *cookie;
106        aim_snacid_t snacid;
107        guint8 ckstr[8];
108        aim_tlvlist_t *otl = NULL, *itl = NULL;
109
110        if (!sess || !conn || !msg || (msglen <= 0))
111                return 0;
112
113        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152)))
114                return -ENOMEM;
115
116        snacid = aim_cachesnac(sess, 0x000e, 0x0005, 0x0000, NULL, 0);
117        aim_putsnac(&fr->data, 0x000e, 0x0005, 0x0000, snacid);
118
119
120        /*
121         * Generate a random message cookie.
122         *
123         * XXX mkcookie should generate the cookie and cache it in one
124         * operation to preserve uniqueness.
125         *
126         */
127        for (i = 0; i < sizeof(ckstr); i++)
128                aimutil_put8(ckstr+i, (guint8) rand());
129
130        cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_CHAT, NULL);
131        cookie->data = NULL; /* XXX store something useful here */
132
133        aim_cachecookie(sess, cookie);
134
135        for (i = 0; i < sizeof(ckstr); i++)
136                aimbs_put8(&fr->data, ckstr[i]);
137
138
139        /*
140         * Channel ID.
141         */
142        aimbs_put16(&fr->data, 0x0003);
143
144
145        /*
146         * Type 1: Flag meaning this message is destined to the room.
147         */
148        aim_addtlvtochain_noval(&otl, 0x0001);
149
150        /*
151         * Type 6: Reflect
152         */
153        if (!(flags & AIM_CHATFLAGS_NOREFLECT))
154                aim_addtlvtochain_noval(&otl, 0x0006);
155
156        /*
157         * Type 7: Autoresponse
158         */
159        if (flags & AIM_CHATFLAGS_AWAY)
160                aim_addtlvtochain_noval(&otl, 0x0007);
161       
162        /* [WvG] This wasn't there originally, but we really should send
163                 the right charset flags, as we also do with normal
164                 messages. Hope this will work. :-) */
165        /*
166        if (flags & AIM_CHATFLAGS_UNICODE)
167                aimbs_put16(&fr->data, 0x0002);
168        else if (flags & AIM_CHATFLAGS_ISO_8859_1)
169                aimbs_put16(&fr->data, 0x0003);
170        else
171                aimbs_put16(&fr->data, 0x0000);
172       
173        aimbs_put16(&fr->data, 0x0000);
174        */
175       
176        /*
177         * SubTLV: Type 1: Message
178         */
179        aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg);
180
181        /*
182         * Type 5: Message block.  Contains more TLVs.
183         *
184         * This could include other information... We just
185         * put in a message TLV however. 
186         *
187         */
188        aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl);
189
190        aim_writetlvchain(&fr->data, &otl);
191       
192        aim_freetlvchain(&itl);
193        aim_freetlvchain(&otl);
194       
195        aim_tx_enqueue(sess, fr);
196
197        return 0;
198}
199
200/*
201 * Join a room of name roomname.  This is the first step to joining an
202 * already created room.  It's basically a Service Request for
203 * family 0x000e, with a little added on to specify the exchange and room
204 * name.
205 */
206int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance)
207{
208        aim_frame_t *fr;
209        aim_snacid_t snacid;
210        aim_tlvlist_t *tl = NULL;
211        struct chatsnacinfo csi;
212       
213        if (!sess || !conn || !roomname || !strlen(roomname))
214                return -EINVAL;
215
216        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
217                return -ENOMEM;
218
219        memset(&csi, 0, sizeof(csi));
220        csi.exchange = exchange;
221        strncpy(csi.name, roomname, sizeof(csi.name));
222        csi.instance = instance;
223
224        snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
225        aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
226
227        /*
228         * Requesting service chat (0x000e)
229         */
230        aimbs_put16(&fr->data, 0x000e);
231
232        aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance);
233        aim_writetlvchain(&fr->data, &tl);
234        aim_freetlvchain(&tl);
235
236        aim_tx_enqueue(sess, fr);
237
238        return 0; 
239}
240
241int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo)
242{
243        int namelen;
244
245        if (!bs || !outinfo)
246                return 0;
247
248        outinfo->exchange = aimbs_get16(bs);
249        namelen = aimbs_get8(bs);
250        outinfo->name = aimbs_getstr(bs, namelen);
251        outinfo->instance = aimbs_get16(bs);
252
253        return 0;
254}
255
256int aim_chat_leaveroom(aim_session_t *sess, const char *name)
257{
258        aim_conn_t *conn;
259
260        if (!(conn = aim_chat_getconn(sess, name)))
261                return -ENOENT;
262
263        aim_conn_close(conn);
264
265        return 0;
266}
267
268/*
269 * conn must be a BOS connection!
270 */
271int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance)
272{
273        int i;
274        aim_frame_t *fr;
275        aim_msgcookie_t *cookie;
276        struct aim_invite_priv *priv;
277        guint8 ckstr[8];
278        aim_snacid_t snacid;
279        aim_tlvlist_t *otl = NULL, *itl = NULL;
280        guint8 *hdr;
281        int hdrlen;
282        aim_bstream_t hdrbs;
283       
284        if (!sess || !conn || !sn || !msg || !roomname)
285                return -EINVAL;
286
287        if (conn->type != AIM_CONN_TYPE_BOS)
288                return -EINVAL;
289
290        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
291                return -ENOMEM;
292
293        snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
294        aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
295
296
297        /*
298         * Cookie
299         */
300        for (i = 0; i < sizeof(ckstr); i++)
301                aimutil_put8(ckstr, (guint8) rand());
302
303        /* XXX should be uncached by an unwritten 'invite accept' handler */
304        if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) {
305                priv->sn = g_strdup(sn);
306                priv->roomname = g_strdup(roomname);
307                priv->exchange = exchange;
308                priv->instance = instance;
309        }
310
311        if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv)))
312                aim_cachecookie(sess, cookie);
313        else
314                g_free(priv);
315
316        for (i = 0; i < sizeof(ckstr); i++)
317                aimbs_put8(&fr->data, ckstr[i]);
318
319
320        /*
321         * Channel (2)
322         */
323        aimbs_put16(&fr->data, 0x0002);
324
325        /*
326         * Dest sn
327         */
328        aimbs_put8(&fr->data, strlen(sn));
329        aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
330
331        /*
332         * TLV t(0005)
333         *
334         * Everything else is inside this TLV.
335         *
336         * Sigh.  AOL was rather inconsistent right here.  So we have
337         * to play some minor tricks.  Right inside the type 5 is some
338         * raw data, followed by a series of TLVs. 
339         *
340         */
341        hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
342        hdr = g_malloc(hdrlen);
343        aim_bstream_init(&hdrbs, hdr, hdrlen);
344       
345        aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
346        aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */
347        aim_putcap(&hdrbs, AIM_CAPS_CHAT);
348
349        aim_addtlvtochain16(&itl, 0x000a, 0x0001);
350        aim_addtlvtochain_noval(&itl, 0x000f);
351        aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg);
352        aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
353        aim_writetlvchain(&hdrbs, &itl);
354       
355        aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
356
357        aim_writetlvchain(&fr->data, &otl);
358
359        g_free(hdr);
360        aim_freetlvchain(&itl);
361        aim_freetlvchain(&otl);
362       
363        aim_tx_enqueue(sess, fr);
364
365        return 0;
366}
367
368/*
369 * General room information.  Lots of stuff.
370 *
371 * Values I know are in here but I havent attached
372 * them to any of the 'Unknown's:
373 *      - Language (English)
374 *
375 * SNAC 000e/0002
376 */
377static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
378{
379        aim_userinfo_t *userinfo = NULL;
380        aim_rxcallback_t userfunc;
381        int ret = 0;
382        int usercount = 0;
383        guint8 detaillevel = 0;
384        char *roomname = NULL;
385        struct aim_chat_roominfo roominfo;
386        guint16 tlvcount = 0;
387        aim_tlvlist_t *tlvlist;
388        char *roomdesc = NULL;
389        guint16 flags = 0;
390        guint32 creationtime = 0;
391        guint16 maxmsglen = 0, maxvisiblemsglen = 0;
392        guint16 unknown_d2 = 0, unknown_d5 = 0;
393
394        aim_chat_readroominfo(bs, &roominfo);
395
396        detaillevel = aimbs_get8(bs);
397
398        if (detaillevel != 0x02) {
399                imcb_error(sess->aux_data, "Only detaillevel 0x2 is support at the moment");
400                return 1;
401        }
402
403        tlvcount = aimbs_get16(bs);
404
405        /*
406         * Everything else are TLVs.
407         */ 
408        tlvlist = aim_readtlvchain(bs);
409
410        /*
411         * TLV type 0x006a is the room name in Human Readable Form.
412         */
413        if (aim_gettlv(tlvlist, 0x006a, 1))
414                roomname = aim_gettlv_str(tlvlist, 0x006a, 1);
415
416        /*
417         * Type 0x006f: Number of occupants.
418         */
419        if (aim_gettlv(tlvlist, 0x006f, 1))
420                usercount = aim_gettlv16(tlvlist, 0x006f, 1);
421
422        /*
423         * Type 0x0073:  Occupant list.
424         */
425        if (aim_gettlv(tlvlist, 0x0073, 1)) {   
426                int curoccupant = 0;
427                aim_tlv_t *tmptlv;
428                aim_bstream_t occbs;
429
430                tmptlv = aim_gettlv(tlvlist, 0x0073, 1);
431
432                /* Allocate enough userinfo structs for all occupants */
433                userinfo = g_new0(aim_userinfo_t, usercount);
434
435                aim_bstream_init(&occbs, tmptlv->value, tmptlv->length);
436
437                while (curoccupant < usercount)
438                        aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]);
439        }
440
441        /*
442         * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG)
443         */
444        if (aim_gettlv(tlvlist, 0x00c9, 1))
445                flags = aim_gettlv16(tlvlist, 0x00c9, 1);
446
447        /*
448         * Type 0x00ca: Creation time (4 bytes)
449         */
450        if (aim_gettlv(tlvlist, 0x00ca, 1))
451                creationtime = aim_gettlv32(tlvlist, 0x00ca, 1);
452
453        /*
454         * Type 0x00d1: Maximum Message Length
455         */
456        if (aim_gettlv(tlvlist, 0x00d1, 1))
457                maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1);
458
459        /*
460         * Type 0x00d2: Unknown. (2 bytes)
461         */
462        if (aim_gettlv(tlvlist, 0x00d2, 1))
463                unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1);
464
465        /*
466         * Type 0x00d3: Room Description
467         */
468        if (aim_gettlv(tlvlist, 0x00d3, 1))
469                roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1);
470
471        /*
472         * Type 0x000d4: Unknown (flag only)
473         */
474        if (aim_gettlv(tlvlist, 0x000d4, 1))
475                ;
476
477        /*
478         * Type 0x00d5: Unknown. (1 byte)
479         */
480        if (aim_gettlv(tlvlist, 0x00d5, 1))
481                unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1);
482
483
484        /*
485         * Type 0x00d6: Encoding 1 ("us-ascii")
486         */
487        if (aim_gettlv(tlvlist, 0x000d6, 1))
488                ;
489       
490        /*
491         * Type 0x00d7: Language 1 ("en")
492         */
493        if (aim_gettlv(tlvlist, 0x000d7, 1))
494                ;
495
496        /*
497         * Type 0x00d8: Encoding 2 ("us-ascii")
498         */
499        if (aim_gettlv(tlvlist, 0x000d8, 1))
500                ;
501       
502        /*
503         * Type 0x00d9: Language 2 ("en")
504         */
505        if (aim_gettlv(tlvlist, 0x000d9, 1))
506                ;
507
508        /*
509         * Type 0x00da: Maximum visible message length
510         */
511        if (aim_gettlv(tlvlist, 0x000da, 1))
512                maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1);
513
514        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
515                ret = userfunc(sess,
516                                rx, 
517                                &roominfo,
518                                roomname,
519                                usercount,
520                                userinfo,       
521                                roomdesc,
522                                flags,
523                                creationtime,
524                                maxmsglen,
525                                unknown_d2,
526                                unknown_d5,
527                                maxvisiblemsglen);
528        }
529
530        g_free(roominfo.name);
531        g_free(userinfo);
532        g_free(roomname);
533        g_free(roomdesc);
534        aim_freetlvchain(&tlvlist);
535
536        return ret;
537}
538
539static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
540{
541        aim_userinfo_t *userinfo = NULL;
542        aim_rxcallback_t userfunc;
543        int curcount = 0, ret = 0;
544
545        while (aim_bstream_empty(bs)) {
546                curcount++;
547                userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t));
548                aim_extractuserinfo(sess, bs, &userinfo[curcount-1]);
549        }
550
551        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
552                ret = userfunc(sess, rx, curcount, userinfo);
553
554        g_free(userinfo);
555
556        return ret;
557}
558
559/*
560 * We could probably include this in the normal ICBM parsing
561 * code as channel 0x0003, however, since only the start
562 * would be the same, we might as well do it here.
563 *
564 * General outline of this SNAC:
565 *   snac
566 *   cookie
567 *   channel id
568 *   tlvlist
569 *     unknown
570 *     source user info
571 *       name
572 *       evility
573 *       userinfo tlvs
574 *         online time
575 *         etc
576 *     message metatlv
577 *       message tlv
578 *         message string
579 *       possibly others
580 * 
581 */
582static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
583{
584        aim_userinfo_t userinfo;
585        aim_rxcallback_t userfunc;     
586        int ret = 0;
587        guint8 *cookie;
588        guint16 channel;
589        aim_tlvlist_t *otl;
590        char *msg = NULL;
591        aim_msgcookie_t *ck;
592
593        memset(&userinfo, 0, sizeof(aim_userinfo_t));
594
595        /*
596         * ICBM Cookie.  Uncache it.
597         */
598        cookie = aimbs_getraw(bs, 8);
599
600        if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) {
601                g_free(ck->data);
602                g_free(ck);
603        }
604
605        /*
606         * Channel ID
607         *
608         * Channels 1 and 2 are implemented in the normal ICBM
609         * parser.
610         *
611         * We only do channel 3 here.
612         *
613         */
614        channel = aimbs_get16(bs);
615
616        if (channel != 0x0003) {
617                imcb_error(sess->aux_data, "unknown channel!");
618                return 0;
619        }
620
621        /*
622         * Start parsing TLVs right away.
623         */
624        otl = aim_readtlvchain(bs);
625
626        /*
627         * Type 0x0003: Source User Information
628         */
629        if (aim_gettlv(otl, 0x0003, 1)) {
630                aim_tlv_t *userinfotlv;
631                aim_bstream_t tbs;
632
633                userinfotlv = aim_gettlv(otl, 0x0003, 1);
634
635                aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length);
636                aim_extractuserinfo(sess, &tbs, &userinfo);
637        }
638
639        /*
640         * Type 0x0001: If present, it means it was a message to the
641         * room (as opposed to a whisper).
642         */
643        if (aim_gettlv(otl, 0x0001, 1))
644                ;
645
646        /*
647         * Type 0x0005: Message Block.  Conains more TLVs.
648         */
649        if (aim_gettlv(otl, 0x0005, 1)) {
650                aim_tlvlist_t *itl;
651                aim_tlv_t *msgblock;
652                aim_bstream_t tbs;
653
654                msgblock = aim_gettlv(otl, 0x0005, 1);
655                aim_bstream_init(&tbs, msgblock->value, msgblock->length);
656                itl = aim_readtlvchain(&tbs);
657
658                /*
659                 * Type 0x0001: Message.
660                 */     
661                if (aim_gettlv(itl, 0x0001, 1))
662                        msg = aim_gettlv_str(itl, 0x0001, 1);
663
664                aim_freetlvchain(&itl); 
665        }
666
667        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
668                ret = userfunc(sess, rx, &userinfo, msg);
669
670        g_free(cookie);
671        g_free(msg);
672        aim_freetlvchain(&otl);
673
674        return ret;
675}
676
677static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
678{
679
680        if (snac->subtype == 0x0002)
681                return infoupdate(sess, mod, rx, snac, bs);
682        else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004))
683                return userlistchange(sess, mod, rx, snac, bs);
684        else if (snac->subtype == 0x0006)
685                return incomingmsg(sess, mod, rx, snac, bs);
686
687        return 0;
688}
689
690int chat_modfirst(aim_session_t *sess, aim_module_t *mod)
691{
692
693        mod->family = 0x000e;
694        mod->version = 0x0001;
695        mod->toolid = 0x0010;
696        mod->toolversion = 0x0629;
697        mod->flags = 0;
698        strncpy(mod->name, "chat", sizeof(mod->name));
699        mod->snachandler = snachandler;
700
701        return 0;
702}
Note: See TracBrowser for help on using the repository browser.