source: protocols/oscar/conn.c @ 434a2d0

Last change on this file since 434a2d0 was 6042a54, checked in by Wilmer van der Gaast <wilmer@…>, at 2012-10-19T23:38:33Z

Massive cleanup in OSCAR.

  • Property mode set to 100644
File size: 14.4 KB
Line 
1
2/*
3 * conn.c
4 *
5 * Does all this gloriously nifty connection handling stuff...
6 *
7 */
8
9#include <aim.h> 
10#include "sock.h"
11
12static int aim_logoff(aim_session_t *sess);
13
14/*
15 * In OSCAR, every connection has a set of SNAC groups associated
16 * with it.  These are the groups that you can send over this connection
17 * without being guarenteed a "Not supported" SNAC error. 
18 *
19 * The grand theory of things says that these associations transcend
20 * what libfaim calls "connection types" (conn->type).  You can probably
21 * see the elegance here, but since I want to revel in it for a bit, you
22 * get to hear it all spelled out.
23 *
24 * So let us say that you have your core BOS connection running.  One
25 * of your modules has just given you a SNAC of the group 0x0004 to send
26 * you.  Maybe an IM destined for some twit in Greenland.  So you start
27 * at the top of your connection list, looking for a connection that
28 * claims to support group 0x0004.  You find one.  Why, that neat BOS
29 * connection of yours can do that.  So you send it on its way.
30 *
31 * Now, say, that fellow from Greenland has friends and they all want to
32 * meet up with you in a lame chat room.  This has landed you a SNAC
33 * in the family 0x000e and you have to admit you're a bit lost.  You've
34 * searched your connection list for someone who wants to make your life
35 * easy and deliver this SNAC for you, but there isn't one there.
36 *
37 * Here comes the good bit.  Without even letting anyone know, particularly
38 * the module that decided to send this SNAC, and definitly not that twit
39 * in Greenland, you send out a service request.  In this request, you have
40 * marked the need for a connection supporting group 0x000e.  A few seconds
41 * later, you receive a service redirect with an IP address and a cookie in
42 * it.  Great, you say.  Now I have something to do.  Off you go, making
43 * that connection.  One of the first things you get from this new server
44 * is a message saying that indeed it does support the group you were looking
45 * for.  So you continue and send rate confirmation and all that. 
46 *
47 * Then you remember you had that SNAC to send, and now you have a means to
48 * do it, and you do, and everyone is happy.  Except the Greenlander, who is
49 * still stuck in the bitter cold.
50 *
51 * Oh, and this is useful for building the Migration SNACs, too.  In the
52 * future, this may help convince me to implement rate limit mitigation
53 * for real.  We'll see.
54 *
55 * Just to make me look better, I'll say that I've known about this great
56 * scheme for quite some time now.  But I still haven't convinced myself
57 * to make libfaim work that way.  It would take a fair amount of effort,
58 * and probably some client API changes as well.  (Whenever I don't want
59 * to do something, I just say it would change the client API.  Then I
60 * instantly have a couple of supporters of not doing it.)
61 *
62 * Generally, addgroup is only called by the internal handling of the
63 * server ready SNAC.  So if you want to do something before that, you'll
64 * have to be more creative.  That is done rather early, though, so I don't
65 * think you have to worry about it.  Unless you're me.  I care deeply
66 * about such inane things.
67 *
68 */
69void aim_conn_addgroup(aim_conn_t *conn, guint16 group)
70{
71        aim_conn_inside_t *ins = (aim_conn_inside_t *)conn->inside;
72        struct snacgroup *sg;
73
74        if (!(sg = g_malloc(sizeof(struct snacgroup))))
75                return;
76
77        sg->group = group;
78
79        sg->next = ins->groups;
80        ins->groups = sg;
81
82        return;
83}
84
85aim_conn_t *aim_conn_findbygroup(aim_session_t *sess, guint16 group)
86{
87        aim_conn_t *cur;
88
89        for (cur = sess->connlist; cur; cur = cur->next) {
90                aim_conn_inside_t *ins = (aim_conn_inside_t *)cur->inside;
91                struct snacgroup *sg;
92
93                for (sg = ins->groups; sg; sg = sg->next) {
94                        if (sg->group == group)
95                                return cur;
96                }
97        }
98
99        return NULL;
100}
101
102static void connkill_snacgroups(struct snacgroup **head)
103{
104        struct snacgroup *sg;
105
106        for (sg = *head; sg; ) {
107                struct snacgroup *tmp;
108
109                tmp = sg->next;
110                g_free(sg);
111                sg = tmp;
112        }
113
114        *head = NULL;
115
116        return;
117}
118
119static void connkill_rates(struct rateclass **head)
120{
121        struct rateclass *rc;
122
123        for (rc = *head; rc; ) {
124                struct rateclass *tmp;
125                struct snacpair *sp;
126
127                tmp = rc->next;
128
129                for (sp = rc->members; sp; ) {
130                        struct snacpair *tmpsp;
131
132                        tmpsp = sp->next;
133                        g_free(sp);
134                        sp = tmpsp;
135                }
136                g_free(rc);
137
138                rc = tmp;
139        }
140
141        *head = NULL;
142
143        return;
144}
145
146static void connkill_real(aim_session_t *sess, aim_conn_t **deadconn)
147{
148
149        aim_rxqueue_cleanbyconn(sess, *deadconn);
150        aim_tx_cleanqueue(sess, *deadconn);
151
152        if ((*deadconn)->fd != -1) 
153                aim_conn_close(*deadconn);
154
155        /*
156         * XXX ->priv should never be touched by the library. I know
157         * it used to be, but I'm getting rid of all that.  Use
158         * ->internal instead.
159         */
160        if ((*deadconn)->priv)
161                g_free((*deadconn)->priv);
162
163        /*
164         * This will free ->internal if it necessary...
165         */
166        if ((*deadconn)->type == AIM_CONN_TYPE_CHAT)
167                aim_conn_kill_chat(sess, *deadconn);
168
169        if ((*deadconn)->inside) {
170                aim_conn_inside_t *inside = (aim_conn_inside_t *)(*deadconn)->inside;
171
172                connkill_snacgroups(&inside->groups);
173                connkill_rates(&inside->rates);
174
175                g_free(inside);
176        }
177
178        g_free(*deadconn);
179        *deadconn = NULL;
180
181        return;
182}
183
184/**
185 * aim_connrst - Clears out connection list, killing remaining connections.
186 * @sess: Session to be cleared
187 *
188 * Clears out the connection list and kills any connections left.
189 *
190 */
191static void aim_connrst(aim_session_t *sess)
192{
193
194        if (sess->connlist) {
195                aim_conn_t *cur = sess->connlist, *tmp;
196
197                while (cur) {
198                        tmp = cur->next;
199                        aim_conn_close(cur);
200                        connkill_real(sess, &cur);
201                        cur = tmp;
202                }
203        }
204
205        sess->connlist = NULL;
206
207        return;
208}
209
210/**
211 * aim_conn_init - Reset a connection to default values.
212 * @deadconn: Connection to be reset
213 *
214 * Initializes and/or resets a connection structure.
215 *
216 */
217static void aim_conn_init(aim_conn_t *deadconn)
218{
219
220        if (!deadconn)
221                return;
222
223        deadconn->fd = -1;
224        deadconn->subtype = -1;
225        deadconn->type = -1;
226        deadconn->seqnum = 0;
227        deadconn->lastactivity = 0;
228        deadconn->forcedlatency = 0;
229        deadconn->handlerlist = NULL;
230        deadconn->priv = NULL;
231        memset(deadconn->inside, 0, sizeof(aim_conn_inside_t));
232
233        return;
234}
235
236/**
237 * aim_conn_getnext - Gets a new connection structure.
238 * @sess: Session
239 *
240 * Allocate a new empty connection structure.
241 *
242 */
243static aim_conn_t *aim_conn_getnext(aim_session_t *sess)
244{
245        aim_conn_t *newconn;
246
247        if (!(newconn = g_new0(aim_conn_t,1))) 
248                return NULL;
249
250        if (!(newconn->inside = g_new0(aim_conn_inside_t,1))) {
251                g_free(newconn);
252                return NULL;
253        }
254
255        aim_conn_init(newconn);
256
257        newconn->next = sess->connlist;
258        sess->connlist = newconn;
259
260        return newconn;
261}
262
263/**
264 * aim_conn_kill - Close and free a connection.
265 * @sess: Session for the connection
266 * @deadconn: Connection to be freed
267 *
268 * Close, clear, and free a connection structure. Should never be
269 * called from within libfaim.
270 *
271 */
272void aim_conn_kill(aim_session_t *sess, aim_conn_t **deadconn)
273{
274        aim_conn_t *cur, **prev;
275
276        if (!deadconn || !*deadconn)   
277                return;
278
279        for (prev = &sess->connlist; (cur = *prev); ) {
280                if (cur == *deadconn) {
281                        *prev = cur->next;
282                        break;
283                }
284                prev = &cur->next;
285        }
286
287        if (!cur)
288                return; /* oops */
289
290        connkill_real(sess, &cur);
291
292        return;
293}
294
295/**
296 * aim_conn_close - Close a connection
297 * @deadconn: Connection to close
298 *
299 * Close (but not free) a connection.
300 *
301 * This leaves everything untouched except for clearing the
302 * handler list and setting the fd to -1 (used to recognize
303 * dead connections).  It will also remove cookies if necessary.
304 *
305 */
306void aim_conn_close(aim_conn_t *deadconn)
307{
308
309        if (deadconn->fd >= 3)
310                closesocket(deadconn->fd);
311        deadconn->fd = -1;
312        if (deadconn->handlerlist)
313                aim_clearhandlers(deadconn);
314
315        return;
316}
317
318/**
319 * aim_getconn_type - Find a connection of a specific type
320 * @sess: Session to search
321 * @type: Type of connection to look for
322 *
323 * Searches for a connection of the specified type in the
324 * specified session.  Returns the first connection of that
325 * type found.
326 *
327 * XXX except for RENDEZVOUS, all uses of this should be removed and
328 * use aim_conn_findbygroup() instead.
329 */
330aim_conn_t *aim_getconn_type(aim_session_t *sess, int type)
331{
332        aim_conn_t *cur;
333
334        for (cur = sess->connlist; cur; cur = cur->next) {
335                if ((cur->type == type) && 
336                                !(cur->status & AIM_CONN_STATUS_INPROGRESS))
337                        break;
338        }
339
340        return cur;
341}
342
343aim_conn_t *aim_getconn_type_all(aim_session_t *sess, int type)
344{
345        aim_conn_t *cur;
346
347        for (cur = sess->connlist; cur; cur = cur->next) {
348                if (cur->type == type)
349                        break;
350        }
351
352        return cur;
353}
354
355/**
356 * aim_newconn - Open a new connection
357 * @sess: Session to create connection in
358 * @type: Type of connection to create
359 * @dest: Host to connect to (in "host:port" syntax)
360 *
361 * Opens a new connection to the specified dest host of specified
362 * type, using the proxy settings if available.  If @host is %NULL,
363 * the connection is allocated and returned, but no connection
364 * is made.
365 *
366 * FIXME: Return errors in a more sane way.
367 *
368 */
369aim_conn_t *aim_newconn(aim_session_t *sess, int type, const char *dest)
370{
371        aim_conn_t *connstruct;
372        guint16 port = AIM_LOGIN_PORT;
373        char *host;
374        int i;
375
376        if (!(connstruct = aim_conn_getnext(sess)))
377                return NULL;
378
379        connstruct->sessv = (void *)sess;
380        connstruct->type = type;
381
382        if (!dest) { /* just allocate a struct */
383                connstruct->fd = -1;
384                connstruct->status = 0;
385                return connstruct;
386        }
387
388        /*
389         * As of 23 Jul 1999, AOL now sends the port number, preceded by a
390         * colon, in the BOS redirect.  This fatally breaks all previous
391         * libfaims.  Bad, bad AOL.
392         *
393         * We put this here to catch every case.
394         *
395         */
396
397        for(i = 0; i < (int)strlen(dest); i++) {
398                if (dest[i] == ':') {
399                        port = atoi(&(dest[i+1]));
400                        break;
401                }
402        }
403
404        host = (char *)g_malloc(i+1);
405        strncpy(host, dest, i);
406        host[i] = '\0';
407
408        connstruct->fd = proxy_connect(host, port, NULL, NULL);
409
410        g_free(host);
411
412        return connstruct;
413}
414
415/**
416 * aim_conn_setlatency - Set a forced latency value for connection
417 * @conn: Conn to set latency for
418 * @newval: Number of seconds to force between transmits
419 *
420 * Causes @newval seconds to be spent between transmits on a connection.
421 *
422 * This is my lame attempt at overcoming not understanding the rate
423 * limiting.
424 *
425 * XXX: This should really be replaced with something that scales and
426 * backs off like the real rate limiting does.
427 *
428 */
429int aim_conn_setlatency(aim_conn_t *conn, int newval)
430{
431
432        if (!conn)
433                return -1;
434
435        conn->forcedlatency = newval;
436        conn->lastactivity = 0; /* reset this just to make sure */
437
438        return 0;
439}
440
441/**
442 * aim_session_init - Initializes a session structure
443 * @sess: Session to initialize
444 * @flags: Flags to use. Any of %AIM_SESS_FLAGS %OR'd together.
445 * @debuglevel: Level of debugging output (zero is least)
446 *
447 * Sets up the initial values for a session.
448 *
449 */
450void aim_session_init(aim_session_t *sess, guint32 flags, int debuglevel)
451{
452
453        if (!sess)
454                return;
455
456        memset(sess, 0, sizeof(aim_session_t));
457        aim_connrst(sess);
458        sess->queue_outgoing = NULL;
459        sess->queue_incoming = NULL;
460        aim_initsnachash(sess);
461        sess->msgcookies = NULL;
462        sess->snacid_next = 0x00000001;
463
464        sess->flags = 0;
465
466        sess->modlistv = NULL;
467
468        sess->ssi.received_data = 0;
469        sess->ssi.waiting_for_ack = 0;
470        sess->ssi.holding_queue = NULL;
471        sess->ssi.revision = 0;
472        sess->ssi.items = NULL;
473        sess->ssi.timestamp = (time_t)0;
474
475        sess->locate.userinfo = NULL;
476        sess->locate.torequest = NULL;
477        sess->locate.requested = NULL;
478        sess->locate.waiting_for_response = FALSE;
479
480        sess->icq_info = NULL;
481        sess->authinfo = NULL;
482        sess->emailinfo = NULL;
483        sess->oft_info = NULL;
484
485
486        /*
487         * Default to SNAC login unless XORLOGIN is explicitly set.
488         */
489        if (!(flags & AIM_SESS_FLAGS_XORLOGIN))
490                sess->flags |= AIM_SESS_FLAGS_SNACLOGIN;
491        sess->flags |= flags;
492
493        /*
494         * This must always be set.  Default to the queue-based
495         * version for back-compatibility. 
496         */
497        aim_tx_setenqueue(sess, AIM_TX_QUEUED, NULL);
498
499
500        /*
501         * Register all the modules for this session...
502         */
503        aim__registermodule(sess, misc_modfirst); /* load the catch-all first */
504        aim__registermodule(sess, general_modfirst);
505        aim__registermodule(sess, locate_modfirst);
506        aim__registermodule(sess, buddylist_modfirst);
507        aim__registermodule(sess, msg_modfirst);
508        aim__registermodule(sess, admin_modfirst);
509        aim__registermodule(sess, bos_modfirst);
510        aim__registermodule(sess, search_modfirst);
511        aim__registermodule(sess, stats_modfirst);
512        aim__registermodule(sess, chatnav_modfirst);
513        aim__registermodule(sess, chat_modfirst);
514        /* missing 0x0f - 0x12 */
515        aim__registermodule(sess, ssi_modfirst);
516        /* missing 0x14 */
517        aim__registermodule(sess, icq_modfirst);
518        /* missing 0x16 */
519        aim__registermodule(sess, auth_modfirst);
520
521        return;
522}
523
524/**
525 * aim_session_kill - Deallocate a session
526 * @sess: Session to kill
527 *
528 */
529void aim_session_kill(aim_session_t *sess)
530{
531        aim_cleansnacs(sess, -1);
532
533        aim_logoff(sess);
534
535        aim__shutdownmodules(sess);
536
537        return;
538}
539
540/*
541 * XXX this is nearly as ugly as proxyconnect().
542 */
543int aim_conn_completeconnect(aim_session_t *sess, aim_conn_t *conn)
544{
545        fd_set fds, wfds;
546        struct timeval tv;
547        int res, error = ETIMEDOUT;
548        aim_rxcallback_t userfunc;
549
550        if (!conn || (conn->fd == -1))
551                return -1;
552
553        if (!(conn->status & AIM_CONN_STATUS_INPROGRESS))
554                return -1;
555
556        FD_ZERO(&fds);
557        FD_SET(conn->fd, &fds);
558        FD_ZERO(&wfds);
559        FD_SET(conn->fd, &wfds);
560        tv.tv_sec = 0;
561        tv.tv_usec = 0;
562
563        if ((res = select(conn->fd+1, &fds, &wfds, NULL, &tv)) == -1) {
564                error = errno;
565                aim_conn_close(conn);
566                errno = error;
567                return -1;
568        } else if (res == 0) {
569                return 0; /* hasn't really completed yet... */
570        } 
571
572        if (FD_ISSET(conn->fd, &fds) || FD_ISSET(conn->fd, &wfds)) {
573                unsigned int len = sizeof(error);
574
575                if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
576                        error = errno;
577        }
578
579        if (error) {
580                aim_conn_close(conn);
581                errno = error;
582                return -1;
583        }
584
585        sock_make_blocking(conn->fd);
586
587        conn->status &= ~AIM_CONN_STATUS_INPROGRESS;
588
589        if ((userfunc = aim_callhandler(sess, conn, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNCOMPLETE)))
590                userfunc(sess, NULL, conn);
591
592        /* Flush out the queues if there was something waiting for this conn  */
593        aim_tx_flushqueue(sess);
594
595        return 0;
596}
597
598aim_session_t *aim_conn_getsess(aim_conn_t *conn)
599{
600
601        if (!conn)
602                return NULL;
603
604        return (aim_session_t *)conn->sessv;
605}
606
607/*
608 * aim_logoff()
609 *
610 * Closes -ALL- open connections.
611 *
612 */
613static int aim_logoff(aim_session_t *sess)
614{
615
616        aim_connrst(sess);  /* in case we want to connect again */
617
618        return 0;
619
620}
621
622/*
623 * aim_flap_nop()
624 *
625 * No-op.  WinAIM 4.x sends these _every minute_ to keep
626 * the connection alive. 
627 */
628int aim_flap_nop(aim_session_t *sess, aim_conn_t *conn)
629{
630        aim_frame_t *fr;
631
632        if (!(fr = aim_tx_new(sess, conn, AIM_FRAMETYPE_FLAP, 0x05, 0)))
633                return -ENOMEM;
634
635        aim_tx_enqueue(sess, fr);
636
637        return 0;
638}
639
640
Note: See TracBrowser for help on using the repository browser.