source: protocols/oscar/chat.c @ da3b536

Last change on this file since da3b536 was 73cf7fd, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-05-22T09:11:49Z

Trying to fix charset issues with outgoing AIM chat messages.

  • 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                        do_error_dialog(sess->aux_data, "chat connection with no name!", "Gaim");
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        if (flags & AIM_CHATFLAGS_UNICODE)
166                aimbs_put16(&fr->data, 0x0002);
167        else if (flags & AIM_CHATFLAGS_ISO_8859_1)
168                aimbs_put16(&fr->data, 0x0003);
169        else
170                aimbs_put16(&fr->data, 0x0000);
171       
172        aimbs_put16(&fr->data, 0x0000);
173       
174        /*
175         * SubTLV: Type 1: Message
176         */
177        aim_addtlvtochain_raw(&itl, 0x0001, strlen(msg), (guint8 *)msg);
178
179        /*
180         * Type 5: Message block.  Contains more TLVs.
181         *
182         * This could include other information... We just
183         * put in a message TLV however. 
184         *
185         */
186        aim_addtlvtochain_frozentlvlist(&otl, 0x0005, &itl);
187
188        aim_writetlvchain(&fr->data, &otl);
189       
190        aim_freetlvchain(&itl);
191        aim_freetlvchain(&otl);
192       
193        aim_tx_enqueue(sess, fr);
194
195        return 0;
196}
197
198/*
199 * Join a room of name roomname.  This is the first step to joining an
200 * already created room.  It's basically a Service Request for
201 * family 0x000e, with a little added on to specify the exchange and room
202 * name.
203 */
204int aim_chat_join(aim_session_t *sess, aim_conn_t *conn, guint16 exchange, const char *roomname, guint16 instance)
205{
206        aim_frame_t *fr;
207        aim_snacid_t snacid;
208        aim_tlvlist_t *tl = NULL;
209        struct chatsnacinfo csi;
210       
211        if (!sess || !conn || !roomname || !strlen(roomname))
212                return -EINVAL;
213
214        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 512)))
215                return -ENOMEM;
216
217        memset(&csi, 0, sizeof(csi));
218        csi.exchange = exchange;
219        strncpy(csi.name, roomname, sizeof(csi.name));
220        csi.instance = instance;
221
222        snacid = aim_cachesnac(sess, 0x0001, 0x0004, 0x0000, &csi, sizeof(csi));
223        aim_putsnac(&fr->data, 0x0001, 0x0004, 0x0000, snacid);
224
225        /*
226         * Requesting service chat (0x000e)
227         */
228        aimbs_put16(&fr->data, 0x000e);
229
230        aim_addtlvtochain_chatroom(&tl, 0x0001, exchange, roomname, instance);
231        aim_writetlvchain(&fr->data, &tl);
232        aim_freetlvchain(&tl);
233
234        aim_tx_enqueue(sess, fr);
235
236        return 0; 
237}
238
239int aim_chat_readroominfo(aim_bstream_t *bs, struct aim_chat_roominfo *outinfo)
240{
241        int namelen;
242
243        if (!bs || !outinfo)
244                return 0;
245
246        outinfo->exchange = aimbs_get16(bs);
247        namelen = aimbs_get8(bs);
248        outinfo->name = aimbs_getstr(bs, namelen);
249        outinfo->instance = aimbs_get16(bs);
250
251        return 0;
252}
253
254int aim_chat_leaveroom(aim_session_t *sess, const char *name)
255{
256        aim_conn_t *conn;
257
258        if (!(conn = aim_chat_getconn(sess, name)))
259                return -ENOENT;
260
261        aim_conn_close(conn);
262
263        return 0;
264}
265
266/*
267 * conn must be a BOS connection!
268 */
269int aim_chat_invite(aim_session_t *sess, aim_conn_t *conn, const char *sn, const char *msg, guint16 exchange, const char *roomname, guint16 instance)
270{
271        int i;
272        aim_frame_t *fr;
273        aim_msgcookie_t *cookie;
274        struct aim_invite_priv *priv;
275        guint8 ckstr[8];
276        aim_snacid_t snacid;
277        aim_tlvlist_t *otl = NULL, *itl = NULL;
278        guint8 *hdr;
279        int hdrlen;
280        aim_bstream_t hdrbs;
281       
282        if (!sess || !conn || !sn || !msg || !roomname)
283                return -EINVAL;
284
285        if (conn->type != AIM_CONN_TYPE_BOS)
286                return -EINVAL;
287
288        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x02, 1152+strlen(sn)+strlen(roomname)+strlen(msg))))
289                return -ENOMEM;
290
291        snacid = aim_cachesnac(sess, 0x0004, 0x0006, 0x0000, sn, strlen(sn)+1);
292        aim_putsnac(&fr->data, 0x0004, 0x0006, 0x0000, snacid);
293
294
295        /*
296         * Cookie
297         */
298        for (i = 0; i < sizeof(ckstr); i++)
299                aimutil_put8(ckstr, (guint8) rand());
300
301        /* XXX should be uncached by an unwritten 'invite accept' handler */
302        if ((priv = g_malloc(sizeof(struct aim_invite_priv)))) {
303                priv->sn = g_strdup(sn);
304                priv->roomname = g_strdup(roomname);
305                priv->exchange = exchange;
306                priv->instance = instance;
307        }
308
309        if ((cookie = aim_mkcookie(ckstr, AIM_COOKIETYPE_INVITE, priv)))
310                aim_cachecookie(sess, cookie);
311        else
312                g_free(priv);
313
314        for (i = 0; i < sizeof(ckstr); i++)
315                aimbs_put8(&fr->data, ckstr[i]);
316
317
318        /*
319         * Channel (2)
320         */
321        aimbs_put16(&fr->data, 0x0002);
322
323        /*
324         * Dest sn
325         */
326        aimbs_put8(&fr->data, strlen(sn));
327        aimbs_putraw(&fr->data, (guint8 *)sn, strlen(sn));
328
329        /*
330         * TLV t(0005)
331         *
332         * Everything else is inside this TLV.
333         *
334         * Sigh.  AOL was rather inconsistent right here.  So we have
335         * to play some minor tricks.  Right inside the type 5 is some
336         * raw data, followed by a series of TLVs. 
337         *
338         */
339        hdrlen = 2+8+16+6+4+4+strlen(msg)+4+2+1+strlen(roomname)+2;
340        hdr = g_malloc(hdrlen);
341        aim_bstream_init(&hdrbs, hdr, hdrlen);
342       
343        aimbs_put16(&hdrbs, 0x0000); /* Unknown! */
344        aimbs_putraw(&hdrbs, ckstr, sizeof(ckstr)); /* I think... */
345        aim_putcap(&hdrbs, AIM_CAPS_CHAT);
346
347        aim_addtlvtochain16(&itl, 0x000a, 0x0001);
348        aim_addtlvtochain_noval(&itl, 0x000f);
349        aim_addtlvtochain_raw(&itl, 0x000c, strlen(msg), (guint8 *)msg);
350        aim_addtlvtochain_chatroom(&itl, 0x2711, exchange, roomname, instance);
351        aim_writetlvchain(&hdrbs, &itl);
352       
353        aim_addtlvtochain_raw(&otl, 0x0005, aim_bstream_curpos(&hdrbs), hdr);
354
355        aim_writetlvchain(&fr->data, &otl);
356
357        g_free(hdr);
358        aim_freetlvchain(&itl);
359        aim_freetlvchain(&otl);
360       
361        aim_tx_enqueue(sess, fr);
362
363        return 0;
364}
365
366/*
367 * General room information.  Lots of stuff.
368 *
369 * Values I know are in here but I havent attached
370 * them to any of the 'Unknown's:
371 *      - Language (English)
372 *
373 * SNAC 000e/0002
374 */
375static int infoupdate(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
376{
377        aim_userinfo_t *userinfo = NULL;
378        aim_rxcallback_t userfunc;
379        int ret = 0;
380        int usercount = 0;
381        guint8 detaillevel = 0;
382        char *roomname = NULL;
383        struct aim_chat_roominfo roominfo;
384        guint16 tlvcount = 0;
385        aim_tlvlist_t *tlvlist;
386        char *roomdesc = NULL;
387        guint16 flags = 0;
388        guint32 creationtime = 0;
389        guint16 maxmsglen = 0, maxvisiblemsglen = 0;
390        guint16 unknown_d2 = 0, unknown_d5 = 0;
391
392        aim_chat_readroominfo(bs, &roominfo);
393
394        detaillevel = aimbs_get8(bs);
395
396        if (detaillevel != 0x02) {
397                do_error_dialog(sess->aux_data, "Only detaillevel 0x2 is support at the moment", "Gaim");
398                return 1;
399        }
400
401        tlvcount = aimbs_get16(bs);
402
403        /*
404         * Everything else are TLVs.
405         */ 
406        tlvlist = aim_readtlvchain(bs);
407
408        /*
409         * TLV type 0x006a is the room name in Human Readable Form.
410         */
411        if (aim_gettlv(tlvlist, 0x006a, 1))
412                roomname = aim_gettlv_str(tlvlist, 0x006a, 1);
413
414        /*
415         * Type 0x006f: Number of occupants.
416         */
417        if (aim_gettlv(tlvlist, 0x006f, 1))
418                usercount = aim_gettlv16(tlvlist, 0x006f, 1);
419
420        /*
421         * Type 0x0073:  Occupant list.
422         */
423        if (aim_gettlv(tlvlist, 0x0073, 1)) {   
424                int curoccupant = 0;
425                aim_tlv_t *tmptlv;
426                aim_bstream_t occbs;
427
428                tmptlv = aim_gettlv(tlvlist, 0x0073, 1);
429
430                /* Allocate enough userinfo structs for all occupants */
431                userinfo = g_new0(aim_userinfo_t, usercount);
432
433                aim_bstream_init(&occbs, tmptlv->value, tmptlv->length);
434
435                while (curoccupant < usercount)
436                        aim_extractuserinfo(sess, &occbs, &userinfo[curoccupant++]);
437        }
438
439        /*
440         * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG)
441         */
442        if (aim_gettlv(tlvlist, 0x00c9, 1))
443                flags = aim_gettlv16(tlvlist, 0x00c9, 1);
444
445        /*
446         * Type 0x00ca: Creation time (4 bytes)
447         */
448        if (aim_gettlv(tlvlist, 0x00ca, 1))
449                creationtime = aim_gettlv32(tlvlist, 0x00ca, 1);
450
451        /*
452         * Type 0x00d1: Maximum Message Length
453         */
454        if (aim_gettlv(tlvlist, 0x00d1, 1))
455                maxmsglen = aim_gettlv16(tlvlist, 0x00d1, 1);
456
457        /*
458         * Type 0x00d2: Unknown. (2 bytes)
459         */
460        if (aim_gettlv(tlvlist, 0x00d2, 1))
461                unknown_d2 = aim_gettlv16(tlvlist, 0x00d2, 1);
462
463        /*
464         * Type 0x00d3: Room Description
465         */
466        if (aim_gettlv(tlvlist, 0x00d3, 1))
467                roomdesc = aim_gettlv_str(tlvlist, 0x00d3, 1);
468
469        /*
470         * Type 0x000d4: Unknown (flag only)
471         */
472        if (aim_gettlv(tlvlist, 0x000d4, 1))
473                ;
474
475        /*
476         * Type 0x00d5: Unknown. (1 byte)
477         */
478        if (aim_gettlv(tlvlist, 0x00d5, 1))
479                unknown_d5 = aim_gettlv8(tlvlist, 0x00d5, 1);
480
481
482        /*
483         * Type 0x00d6: Encoding 1 ("us-ascii")
484         */
485        if (aim_gettlv(tlvlist, 0x000d6, 1))
486                ;
487       
488        /*
489         * Type 0x00d7: Language 1 ("en")
490         */
491        if (aim_gettlv(tlvlist, 0x000d7, 1))
492                ;
493
494        /*
495         * Type 0x00d8: Encoding 2 ("us-ascii")
496         */
497        if (aim_gettlv(tlvlist, 0x000d8, 1))
498                ;
499       
500        /*
501         * Type 0x00d9: Language 2 ("en")
502         */
503        if (aim_gettlv(tlvlist, 0x000d9, 1))
504                ;
505
506        /*
507         * Type 0x00da: Maximum visible message length
508         */
509        if (aim_gettlv(tlvlist, 0x000da, 1))
510                maxvisiblemsglen = aim_gettlv16(tlvlist, 0x00da, 1);
511
512        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype))) {
513                ret = userfunc(sess,
514                                rx, 
515                                &roominfo,
516                                roomname,
517                                usercount,
518                                userinfo,       
519                                roomdesc,
520                                flags,
521                                creationtime,
522                                maxmsglen,
523                                unknown_d2,
524                                unknown_d5,
525                                maxvisiblemsglen);
526        }
527
528        g_free(roominfo.name);
529        g_free(userinfo);
530        g_free(roomname);
531        g_free(roomdesc);
532        aim_freetlvchain(&tlvlist);
533
534        return ret;
535}
536
537static int userlistchange(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
538{
539        aim_userinfo_t *userinfo = NULL;
540        aim_rxcallback_t userfunc;
541        int curcount = 0, ret = 0;
542
543        while (aim_bstream_empty(bs)) {
544                curcount++;
545                userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t));
546                aim_extractuserinfo(sess, bs, &userinfo[curcount-1]);
547        }
548
549        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
550                ret = userfunc(sess, rx, curcount, userinfo);
551
552        g_free(userinfo);
553
554        return ret;
555}
556
557/*
558 * We could probably include this in the normal ICBM parsing
559 * code as channel 0x0003, however, since only the start
560 * would be the same, we might as well do it here.
561 *
562 * General outline of this SNAC:
563 *   snac
564 *   cookie
565 *   channel id
566 *   tlvlist
567 *     unknown
568 *     source user info
569 *       name
570 *       evility
571 *       userinfo tlvs
572 *         online time
573 *         etc
574 *     message metatlv
575 *       message tlv
576 *         message string
577 *       possibly others
578 * 
579 */
580static int incomingmsg(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
581{
582        aim_userinfo_t userinfo;
583        aim_rxcallback_t userfunc;     
584        int ret = 0;
585        guint8 *cookie;
586        guint16 channel;
587        aim_tlvlist_t *otl;
588        char *msg = NULL;
589        aim_msgcookie_t *ck;
590
591        memset(&userinfo, 0, sizeof(aim_userinfo_t));
592
593        /*
594         * ICBM Cookie.  Uncache it.
595         */
596        cookie = aimbs_getraw(bs, 8);
597
598        if ((ck = aim_uncachecookie(sess, cookie, AIM_COOKIETYPE_CHAT))) {
599                g_free(ck->data);
600                g_free(ck);
601        }
602
603        /*
604         * Channel ID
605         *
606         * Channels 1 and 2 are implemented in the normal ICBM
607         * parser.
608         *
609         * We only do channel 3 here.
610         *
611         */
612        channel = aimbs_get16(bs);
613
614        if (channel != 0x0003) {
615                do_error_dialog(sess->aux_data, "unknown channel!", "Gaim");
616                return 0;
617        }
618
619        /*
620         * Start parsing TLVs right away.
621         */
622        otl = aim_readtlvchain(bs);
623
624        /*
625         * Type 0x0003: Source User Information
626         */
627        if (aim_gettlv(otl, 0x0003, 1)) {
628                aim_tlv_t *userinfotlv;
629                aim_bstream_t tbs;
630
631                userinfotlv = aim_gettlv(otl, 0x0003, 1);
632
633                aim_bstream_init(&tbs, userinfotlv->value, userinfotlv->length);
634                aim_extractuserinfo(sess, &tbs, &userinfo);
635        }
636
637        /*
638         * Type 0x0001: If present, it means it was a message to the
639         * room (as opposed to a whisper).
640         */
641        if (aim_gettlv(otl, 0x0001, 1))
642                ;
643
644        /*
645         * Type 0x0005: Message Block.  Conains more TLVs.
646         */
647        if (aim_gettlv(otl, 0x0005, 1)) {
648                aim_tlvlist_t *itl;
649                aim_tlv_t *msgblock;
650                aim_bstream_t tbs;
651
652                msgblock = aim_gettlv(otl, 0x0005, 1);
653                aim_bstream_init(&tbs, msgblock->value, msgblock->length);
654                itl = aim_readtlvchain(&tbs);
655
656                /*
657                 * Type 0x0001: Message.
658                 */     
659                if (aim_gettlv(itl, 0x0001, 1))
660                        msg = aim_gettlv_str(itl, 0x0001, 1);
661
662                aim_freetlvchain(&itl); 
663        }
664
665        if ((userfunc = aim_callhandler(sess, rx->conn, snac->family, snac->subtype)))
666                ret = userfunc(sess, rx, &userinfo, msg);
667
668        g_free(cookie);
669        g_free(msg);
670        aim_freetlvchain(&otl);
671
672        return ret;
673}
674
675static int snachandler(aim_session_t *sess, aim_module_t *mod, aim_frame_t *rx, aim_modsnac_t *snac, aim_bstream_t *bs)
676{
677
678        if (snac->subtype == 0x0002)
679                return infoupdate(sess, mod, rx, snac, bs);
680        else if ((snac->subtype == 0x0003) || (snac->subtype == 0x0004))
681                return userlistchange(sess, mod, rx, snac, bs);
682        else if (snac->subtype == 0x0006)
683                return incomingmsg(sess, mod, rx, snac, bs);
684
685        return 0;
686}
687
688int chat_modfirst(aim_session_t *sess, aim_module_t *mod)
689{
690
691        mod->family = 0x000e;
692        mod->version = 0x0001;
693        mod->toolid = 0x0010;
694        mod->toolversion = 0x0629;
695        mod->flags = 0;
696        strncpy(mod->name, "chat", sizeof(mod->name));
697        mod->snachandler = snachandler;
698
699        return 0;
700}
Note: See TracBrowser for help on using the repository browser.