source: protocols/oscar/chat.c @ 68b50b5f

Last change on this file since 68b50b5f was b7d3cc34, checked in by Wilmer van der Gaast <wilmer@…>, at 2005-11-06T18:23:18Z

Initial repository (0.99 release tree)

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