source: protocols/oscar/chat.c @ df70eafa

Last change on this file since df70eafa was b8ef1b1, checked in by Nelson Elhage <nelhage@…>, at 2005-12-04T19:32:14Z

Merging the Hanji groupchat patch

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