Annotation of ChivanetAimPidgin/oscarprpl/src/c/flap_connection.c, revision 1.1
1.1 ! snw 1: /*
! 2: * Purple's oscar protocol plugin
! 3: * This file is the legal property of its developers.
! 4: * Please see the AUTHORS file distributed alongside this file.
! 5: *
! 6: * This library is free software; you can redistribute it and/or
! 7: * modify it under the terms of the GNU Lesser General Public
! 8: * License as published by the Free Software Foundation; either
! 9: * version 2 of the License, or (at your option) any later version.
! 10: *
! 11: * This library is distributed in the hope that it will be useful,
! 12: * but WITHOUT ANY WARRANTY; without even the implied warranty of
! 13: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
! 14: * Lesser General Public License for more details.
! 15: *
! 16: * You should have received a copy of the GNU Lesser General Public
! 17: * License along with this library; if not, write to the Free Software
! 18: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
! 19: */
! 20:
! 21: #include "oscar.h"
! 22:
! 23: #include "eventloop.h"
! 24: #include "proxy.h"
! 25:
! 26: #ifndef _WIN32
! 27: #include <netdb.h>
! 28: #include <sys/socket.h>
! 29: #include <netinet/in.h>
! 30: #endif
! 31:
! 32: #ifdef _WIN32
! 33: #include "win32dep.h"
! 34: #endif
! 35:
! 36: /**
! 37: * This sends a channel 1 SNAC containing the FLAP version.
! 38: * The FLAP version is sent by itself at the beginning of every
! 39: * connection to a FLAP server. It is always the very first
! 40: * packet sent by both the server and the client after the SYN,
! 41: * SYN/ACK, ACK handshake.
! 42: */
! 43: void
! 44: flap_connection_send_version(OscarData *od, FlapConnection *conn)
! 45: {
! 46: FlapFrame *frame;
! 47:
! 48: frame = flap_frame_new(od, 0x01, 4);
! 49: byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
! 50: flap_connection_send(conn, frame);
! 51: }
! 52:
! 53: /**
! 54: * This sends a channel 1 FLAP containing the FLAP version and
! 55: * the authentication cookie. This is sent when connecting to
! 56: * any FLAP server after the initial connection to the auth
! 57: * server. It is always the very first packet sent by both the
! 58: * server and the client after the SYN, SYN/ACK, ACK handshake.
! 59: */
! 60: void
! 61: flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy)
! 62: {
! 63: FlapFrame *frame;
! 64: GSList *tlvlist = NULL;
! 65:
! 66: frame = flap_frame_new(od, 0x01, 4 + 2 + 2 + length);
! 67: byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
! 68: aim_tlvlist_add_raw(&tlvlist, 0x0006, length, chipsahoy);
! 69: aim_tlvlist_write(&frame->data, &tlvlist);
! 70: aim_tlvlist_free(tlvlist);
! 71:
! 72: flap_connection_send(conn, frame);
! 73: }
! 74:
! 75: void
! 76: flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_logins)
! 77: {
! 78: FlapFrame *frame;
! 79: GSList *tlvlist = NULL;
! 80:
! 81: frame = flap_frame_new(od, 0x01, 1152 + length);
! 82:
! 83: byte_stream_put32(&frame->data, 0x00000001); /* FLAP Version */
! 84: aim_tlvlist_add_raw(&tlvlist, 0x0006, length, chipsahoy);
! 85:
! 86: if (ci->clientstring != NULL)
! 87: aim_tlvlist_add_str(&tlvlist, 0x0003, ci->clientstring);
! 88: else {
! 89: gchar *clientstring = oscar_get_clientstring();
! 90: aim_tlvlist_add_str(&tlvlist, 0x0003, clientstring);
! 91: g_free(clientstring);
! 92: }
! 93: aim_tlvlist_add_16(&tlvlist, 0x0017, (guint16)ci->major);
! 94: aim_tlvlist_add_16(&tlvlist, 0x0018, (guint16)ci->minor);
! 95: aim_tlvlist_add_16(&tlvlist, 0x0019, (guint16)ci->point);
! 96: aim_tlvlist_add_16(&tlvlist, 0x001a, (guint16)ci->build);
! 97: aim_tlvlist_add_8(&tlvlist, 0x004a, (allow_multiple_logins ? 0x01 : 0x03));
! 98:
! 99: aim_tlvlist_write(&frame->data, &tlvlist);
! 100:
! 101: aim_tlvlist_free(tlvlist);
! 102:
! 103: flap_connection_send(conn, frame);
! 104: }
! 105:
! 106: static struct rateclass *
! 107: flap_connection_get_rateclass(FlapConnection *conn, guint16 family, guint16 subtype)
! 108: {
! 109: gconstpointer key;
! 110: gpointer rateclass;
! 111:
! 112: key = GUINT_TO_POINTER((family << 16) + subtype);
! 113: rateclass = g_hash_table_lookup(conn->rateclass_members, key);
! 114: if (rateclass != NULL)
! 115: return rateclass;
! 116:
! 117: return conn->default_rateclass;
! 118: }
! 119:
! 120: /*
! 121: * Attempt to calculate what our new current average would be if we
! 122: * were to send a SNAC in this rateclass at the given time.
! 123: */
! 124: static guint32
! 125: rateclass_get_new_current(FlapConnection *conn, struct rateclass *rateclass, struct timeval *now)
! 126: {
! 127: unsigned long timediff; /* In milliseconds */
! 128: guint32 current;
! 129:
! 130: /* This formula is documented at http://dev.aol.com/aim/oscar/#RATELIMIT */
! 131: timediff = (now->tv_sec - rateclass->last.tv_sec) * 1000 + (now->tv_usec - rateclass->last.tv_usec) / 1000;
! 132: current = ((rateclass->current * (rateclass->windowsize - 1)) + timediff) / rateclass->windowsize;
! 133:
! 134: return MIN(current, rateclass->max);
! 135: }
! 136:
! 137: /*
! 138: * Attempt to send the contents of a given queue
! 139: *
! 140: * @return TRUE if the queue was completely emptied or was initially
! 141: * empty; FALSE if rate limiting prevented it from being
! 142: * emptied.
! 143: */
! 144: static gboolean flap_connection_send_snac_queue(FlapConnection *conn, struct timeval now, GQueue *queue)
! 145: {
! 146: while (!g_queue_is_empty(queue))
! 147: {
! 148: QueuedSnac *queued_snac;
! 149: struct rateclass *rateclass;
! 150:
! 151: queued_snac = g_queue_peek_head(queue);
! 152:
! 153: rateclass = flap_connection_get_rateclass(conn, queued_snac->family, queued_snac->subtype);
! 154: if (rateclass != NULL)
! 155: {
! 156: guint32 new_current;
! 157:
! 158: new_current = rateclass_get_new_current(conn, rateclass, &now);
! 159:
! 160: if (rateclass->dropping_snacs || new_current <= rateclass->alert)
! 161: /* Not ready to send this SNAC yet--keep waiting. */
! 162: return FALSE;
! 163:
! 164: rateclass->current = new_current;
! 165: rateclass->last.tv_sec = now.tv_sec;
! 166: rateclass->last.tv_usec = now.tv_usec;
! 167: }
! 168:
! 169: flap_connection_send(conn, queued_snac->frame);
! 170: g_free(queued_snac);
! 171: g_queue_pop_head(queue);
! 172: }
! 173:
! 174: /* We emptied the queue */
! 175: return TRUE;
! 176: }
! 177:
! 178: static gboolean flap_connection_send_queued(gpointer data)
! 179: {
! 180: FlapConnection *conn;
! 181: struct timeval now;
! 182:
! 183: conn = data;
! 184: gettimeofday(&now, NULL);
! 185:
! 186: purple_debug_info("oscar", "Attempting to send %u queued SNACs and %u queued low-priority SNACs for %p\n",
! 187: (conn->queued_snacs ? conn->queued_snacs->length : 0),
! 188: (conn->queued_lowpriority_snacs ? conn->queued_lowpriority_snacs->length : 0),
! 189: conn);
! 190: if (!conn->queued_snacs || flap_connection_send_snac_queue(conn, now, conn->queued_snacs)) {
! 191: if (!conn->queued_lowpriority_snacs || flap_connection_send_snac_queue(conn, now, conn->queued_lowpriority_snacs)) {
! 192: /* Both queues emptied. */
! 193: conn->queued_timeout = 0;
! 194: return FALSE;
! 195: }
! 196: }
! 197:
! 198: /* We couldn't send all our SNACs. Keep trying */
! 199: return TRUE;
! 200: }
! 201:
! 202: /**
! 203: * This sends a channel 2 FLAP containing a SNAC. The SNAC family and
! 204: * subtype are looked up in the rate info for this connection, and if
! 205: * sending this SNAC will induce rate limiting then we delay sending
! 206: * of the SNAC by putting it into an outgoing holding queue.
! 207: *
! 208: * @param data The optional bytestream that makes up the data portion
! 209: * of this SNAC. For empty SNACs this should be NULL.
! 210: * @param high_priority If TRUE, the SNAC will be queued normally if
! 211: * needed. If FALSE, it will be queued separately, to be sent
! 212: * only if all high priority SNACs have been sent.
! 213: */
! 214: void
! 215: flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data, gboolean high_priority)
! 216: {
! 217: FlapFrame *frame;
! 218: guint32 length;
! 219: gboolean enqueue = FALSE;
! 220: struct rateclass *rateclass;
! 221:
! 222: length = data != NULL ? data->offset : 0;
! 223:
! 224: frame = flap_frame_new(od, 0x02, 10 + length);
! 225: aim_putsnac(&frame->data, family, subtype, snacid);
! 226:
! 227: if (length > 0)
! 228: {
! 229: byte_stream_rewind(data);
! 230: byte_stream_putbs(&frame->data, data, length);
! 231: }
! 232:
! 233: if (conn->queued_timeout != 0)
! 234: enqueue = TRUE;
! 235: else if ((rateclass = flap_connection_get_rateclass(conn, family, subtype)) != NULL)
! 236: {
! 237: struct timeval now;
! 238: guint32 new_current;
! 239:
! 240: gettimeofday(&now, NULL);
! 241: new_current = rateclass_get_new_current(conn, rateclass, &now);
! 242:
! 243: if (rateclass->dropping_snacs || new_current <= rateclass->alert)
! 244: {
! 245: purple_debug_info("oscar", "Current rate for conn %p would be %u, but we alert at %u; enqueueing\n", conn, new_current, rateclass->alert);
! 246:
! 247: enqueue = TRUE;
! 248: }
! 249: else
! 250: {
! 251: rateclass->current = new_current;
! 252: rateclass->last.tv_sec = now.tv_sec;
! 253: rateclass->last.tv_usec = now.tv_usec;
! 254: }
! 255: }
! 256:
! 257: if (enqueue)
! 258: {
! 259: /* We've been sending too fast, so delay this message */
! 260: QueuedSnac *queued_snac;
! 261:
! 262: queued_snac = g_new(QueuedSnac, 1);
! 263: queued_snac->family = family;
! 264: queued_snac->subtype = subtype;
! 265: queued_snac->frame = frame;
! 266:
! 267: if (high_priority) {
! 268: if (!conn->queued_snacs)
! 269: conn->queued_snacs = g_queue_new();
! 270: g_queue_push_tail(conn->queued_snacs, queued_snac);
! 271: } else {
! 272: if (!conn->queued_lowpriority_snacs)
! 273: conn->queued_lowpriority_snacs = g_queue_new();
! 274: g_queue_push_tail(conn->queued_lowpriority_snacs, queued_snac);
! 275: }
! 276:
! 277: if (conn->queued_timeout == 0)
! 278: conn->queued_timeout = purple_timeout_add(500, flap_connection_send_queued, conn);
! 279:
! 280: return;
! 281: }
! 282:
! 283: flap_connection_send(conn, frame);
! 284: }
! 285:
! 286: void
! 287: flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data)
! 288: {
! 289: flap_connection_send_snac_with_priority(od, conn, family, subtype, snacid, data, TRUE);
! 290: }
! 291:
! 292: /**
! 293: * This sends an empty channel 4 FLAP. This is sent to signify
! 294: * that we're logging off. This shouldn't really be necessary--
! 295: * usually the AIM server will detect that the TCP connection has
! 296: * been destroyed--but it's good practice.
! 297: */
! 298: static void
! 299: flap_connection_send_close(OscarData *od, FlapConnection *conn)
! 300: {
! 301: FlapFrame *frame;
! 302:
! 303: frame = flap_frame_new(od, 0x04, 0);
! 304: flap_connection_send(conn, frame);
! 305: }
! 306:
! 307: /**
! 308: * This sends an empty channel 5 FLAP. This is used as a keepalive
! 309: * packet in FLAP connections. WinAIM 4.x and higher send these
! 310: * _every minute_ to keep the connection alive.
! 311: */
! 312: void
! 313: flap_connection_send_keepalive(OscarData *od, FlapConnection *conn)
! 314: {
! 315: FlapFrame *frame;
! 316:
! 317: frame = flap_frame_new(od, 0x05, 0);
! 318: flap_connection_send(conn, frame);
! 319:
! 320: /* clean out SNACs over 60sec old */
! 321: aim_cleansnacs(od, 60);
! 322: }
! 323:
! 324: /**
! 325: * Allocate a new empty connection structure.
! 326: *
! 327: * @param od The oscar session associated with this connection.
! 328: * @param type Type of connection to create
! 329: *
! 330: * @return Returns the new connection structure.
! 331: */
! 332: FlapConnection *
! 333: flap_connection_new(OscarData *od, int type)
! 334: {
! 335: FlapConnection *conn;
! 336:
! 337: conn = g_new0(FlapConnection, 1);
! 338: conn->od = od;
! 339: conn->buffer_outgoing = purple_circ_buffer_new(0);
! 340: conn->fd = -1;
! 341: conn->subtype = -1;
! 342: conn->type = type;
! 343: conn->rateclass_members = g_hash_table_new(g_direct_hash, g_direct_equal);
! 344:
! 345: od->oscar_connections = g_slist_prepend(od->oscar_connections, conn);
! 346:
! 347: return conn;
! 348: }
! 349:
! 350: /**
! 351: * Close (but not free) a connection.
! 352: *
! 353: * This cancels any currently pending connection attempt,
! 354: * closes any open fd and frees the auth cookie.
! 355: *
! 356: * @param conn The connection to close.
! 357: */
! 358: void
! 359: flap_connection_close(OscarData *od, FlapConnection *conn)
! 360: {
! 361: if (conn->connect_data != NULL)
! 362: {
! 363: purple_proxy_connect_cancel(conn->connect_data);
! 364: conn->connect_data = NULL;
! 365: }
! 366:
! 367: if (conn->gsc != NULL && conn->gsc->connect_data != NULL)
! 368: {
! 369: purple_ssl_close(conn->gsc);
! 370: conn->gsc = NULL;
! 371: }
! 372:
! 373: if (conn->new_conn_data != NULL)
! 374: {
! 375: if (conn->type == SNAC_FAMILY_CHAT)
! 376: {
! 377: oscar_chat_destroy(conn->new_conn_data);
! 378: conn->new_conn_data = NULL;
! 379: }
! 380: }
! 381:
! 382: if ((conn->fd >= 0 || conn->gsc != NULL)
! 383: && conn->type == SNAC_FAMILY_LOCATE)
! 384: flap_connection_send_close(od, conn);
! 385:
! 386: if (conn->watcher_incoming != 0)
! 387: {
! 388: purple_input_remove(conn->watcher_incoming);
! 389: conn->watcher_incoming = 0;
! 390: }
! 391:
! 392: if (conn->watcher_outgoing != 0)
! 393: {
! 394: purple_input_remove(conn->watcher_outgoing);
! 395: conn->watcher_outgoing = 0;
! 396: }
! 397:
! 398: if (conn->fd >= 0)
! 399: {
! 400: close(conn->fd);
! 401: conn->fd = -1;
! 402: }
! 403:
! 404: if (conn->gsc != NULL)
! 405: {
! 406: purple_ssl_close(conn->gsc);
! 407: conn->gsc = NULL;
! 408: }
! 409:
! 410: g_free(conn->buffer_incoming.data.data);
! 411: conn->buffer_incoming.data.data = NULL;
! 412:
! 413: purple_circ_buffer_destroy(conn->buffer_outgoing);
! 414: conn->buffer_outgoing = NULL;
! 415: }
! 416:
! 417: /**
! 418: * Free a FlapFrame
! 419: *
! 420: * @param frame The frame to free.
! 421: */
! 422: static void
! 423: flap_frame_destroy(FlapFrame *frame)
! 424: {
! 425: g_free(frame->data.data);
! 426: g_free(frame);
! 427: }
! 428:
! 429: static gboolean
! 430: flap_connection_destroy_cb(gpointer data)
! 431: {
! 432: FlapConnection *conn;
! 433: OscarData *od;
! 434: PurpleAccount *account;
! 435: aim_rxcallback_t userfunc;
! 436:
! 437: conn = data;
! 438: /* Explicitly added for debugging #5927. Don't re-order this, only
! 439: * consider removing it.
! 440: */
! 441: purple_debug_info("oscar", "Destroying FLAP connection %p\n", conn);
! 442:
! 443: od = conn->od;
! 444: account = purple_connection_get_account(od->gc);
! 445:
! 446: purple_debug_info("oscar", "Destroying oscar connection (%p) of "
! 447: "type 0x%04hx. Disconnect reason is %d\n", conn,
! 448: conn->type, conn->disconnect_reason);
! 449:
! 450: od->oscar_connections = g_slist_remove(od->oscar_connections, conn);
! 451:
! 452: if ((userfunc = aim_callhandler(od, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR)))
! 453: userfunc(od, conn, NULL, conn->disconnect_code, conn->error_message);
! 454:
! 455: /*
! 456: * TODO: If we don't have a SNAC_FAMILY_LOCATE connection then
! 457: * we should try to request one instead of disconnecting.
! 458: */
! 459: if (!account->disconnecting && ((od->oscar_connections == NULL)
! 460: || (!flap_connection_getbytype(od, SNAC_FAMILY_LOCATE))))
! 461: {
! 462: /* No more FLAP connections! Sign off this PurpleConnection! */
! 463: gchar *tmp;
! 464: PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
! 465:
! 466: if (conn->disconnect_code == 0x0001) {
! 467: reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
! 468: tmp = g_strdup(_("You have signed on from another location"));
! 469: if (!purple_account_get_remember_password(account))
! 470: purple_account_set_password(account, NULL);
! 471: } else if (conn->disconnect_reason == OSCAR_DISCONNECT_REMOTE_CLOSED)
! 472: tmp = g_strdup(_("Server closed the connection"));
! 473: else if (conn->disconnect_reason == OSCAR_DISCONNECT_LOST_CONNECTION)
! 474: tmp = g_strdup_printf(_("Lost connection with server: %s"),
! 475: conn->error_message);
! 476: else if (conn->disconnect_reason == OSCAR_DISCONNECT_INVALID_DATA)
! 477: tmp = g_strdup(_("Received invalid data on connection with server"));
! 478: else if (conn->disconnect_reason == OSCAR_DISCONNECT_COULD_NOT_CONNECT)
! 479: tmp = g_strdup_printf(_("Unable to connect: %s"),
! 480: conn->error_message);
! 481: else
! 482: /*
! 483: * We shouldn't print a message for some disconnect_reasons.
! 484: * Like OSCAR_DISCONNECT_LOCAL_CLOSED.
! 485: */
! 486: tmp = NULL;
! 487:
! 488: if (tmp != NULL)
! 489: {
! 490: purple_connection_error_reason(od->gc, reason, tmp);
! 491: g_free(tmp);
! 492: }
! 493: }
! 494:
! 495: flap_connection_close(od, conn);
! 496:
! 497: g_free(conn->error_message);
! 498: g_free(conn->cookie);
! 499:
! 500: /*
! 501: * Free conn->internal, if necessary
! 502: */
! 503: if (conn->type == SNAC_FAMILY_CHAT)
! 504: flap_connection_destroy_chat(od, conn);
! 505:
! 506: g_slist_free(conn->groups);
! 507: while (conn->rateclasses != NULL)
! 508: {
! 509: g_free(conn->rateclasses->data);
! 510: conn->rateclasses = g_slist_delete_link(conn->rateclasses, conn->rateclasses);
! 511: }
! 512:
! 513: g_hash_table_destroy(conn->rateclass_members);
! 514:
! 515: if (conn->queued_snacs) {
! 516: while (!g_queue_is_empty(conn->queued_snacs))
! 517: {
! 518: QueuedSnac *queued_snac;
! 519: queued_snac = g_queue_pop_head(conn->queued_snacs);
! 520: flap_frame_destroy(queued_snac->frame);
! 521: g_free(queued_snac);
! 522: }
! 523: g_queue_free(conn->queued_snacs);
! 524: }
! 525:
! 526: if (conn->queued_lowpriority_snacs) {
! 527: while (!g_queue_is_empty(conn->queued_lowpriority_snacs))
! 528: {
! 529: QueuedSnac *queued_snac;
! 530: queued_snac = g_queue_pop_head(conn->queued_lowpriority_snacs);
! 531: flap_frame_destroy(queued_snac->frame);
! 532: g_free(queued_snac);
! 533: }
! 534: g_queue_free(conn->queued_lowpriority_snacs);
! 535: }
! 536:
! 537: if (conn->queued_timeout > 0)
! 538: purple_timeout_remove(conn->queued_timeout);
! 539:
! 540: g_free(conn);
! 541:
! 542: return FALSE;
! 543: }
! 544:
! 545: /**
! 546: * See the comments for the parameters of
! 547: * flap_connection_schedule_destroy().
! 548: */
! 549: void
! 550: flap_connection_destroy(FlapConnection *conn, OscarDisconnectReason reason, const gchar *error_message)
! 551: {
! 552: if (conn->destroy_timeout != 0)
! 553: purple_timeout_remove(conn->destroy_timeout);
! 554: conn->disconnect_reason = reason;
! 555: g_free(conn->error_message);
! 556: conn->error_message = g_strdup(error_message);
! 557: flap_connection_destroy_cb(conn);
! 558: }
! 559:
! 560: /**
! 561: * Schedule Purple to destroy the given FlapConnection as soon as we
! 562: * return control back to the program's main loop. We must do this
! 563: * if we want to destroy the connection but we are still using it
! 564: * for some reason.
! 565: *
! 566: * @param reason The reason for the disconnection.
! 567: * @param error_message A brief error message that gives more detail
! 568: * regarding the reason for the disconnecting. This should
! 569: * be NULL for everything except OSCAR_DISCONNECT_LOST_CONNECTION,
! 570: * in which case it should contain the value of g_strerror(errno),
! 571: * and OSCAR_DISCONNECT_COULD_NOT_CONNECT, in which case it
! 572: * should contain the error_message passed back from the call
! 573: * to purple_proxy_connect().
! 574: */
! 575: void
! 576: flap_connection_schedule_destroy(FlapConnection *conn, OscarDisconnectReason reason, const gchar *error_message)
! 577: {
! 578: if (conn->destroy_timeout != 0)
! 579: /* Already taken care of */
! 580: return;
! 581:
! 582: purple_debug_info("oscar", "Scheduling destruction of FLAP "
! 583: "connection %p of type 0x%04hx\n", conn, conn->type);
! 584: conn->disconnect_reason = reason;
! 585: g_free(conn->error_message);
! 586: conn->error_message = g_strdup(error_message);
! 587: conn->destroy_timeout = purple_timeout_add(0, flap_connection_destroy_cb, conn);
! 588: }
! 589:
! 590: /**
! 591: * In OSCAR, every connection has a set of SNAC groups associated
! 592: * with it. These are the groups that you can send over this connection
! 593: * without being guaranteed a "Not supported" SNAC error.
! 594: *
! 595: * The grand theory of things says that these associations transcend
! 596: * what libfaim calls "connection types" (conn->type). You can probably
! 597: * see the elegance here, but since I want to revel in it for a bit, you
! 598: * get to hear it all spelled out.
! 599: *
! 600: * So let us say that you have your core BOS connection running. One
! 601: * of your modules has just given you a SNAC of the group 0x0004 to send
! 602: * you. Maybe an IM destined for some twit in Greenland. So you start
! 603: * at the top of your connection list, looking for a connection that
! 604: * claims to support group 0x0004. You find one. Why, that neat BOS
! 605: * connection of yours can do that. So you send it on its way.
! 606: *
! 607: * Now, say, that fellow from Greenland has friends and they all want to
! 608: * meet up with you in a lame chat room. This has landed you a SNAC
! 609: * in the family 0x000e and you have to admit you're a bit lost. You've
! 610: * searched your connection list for someone who wants to make your life
! 611: * easy and deliver this SNAC for you, but there isn't one there.
! 612: *
! 613: * Here comes the good bit. Without even letting anyone know, particularly
! 614: * the module that decided to send this SNAC, and definitely not that twit
! 615: * in Greenland, you send out a service request. In this request, you have
! 616: * marked the need for a connection supporting group 0x000e. A few seconds
! 617: * later, you receive a service redirect with an IP address and a cookie in
! 618: * it. Great, you say. Now I have something to do. Off you go, making
! 619: * that connection. One of the first things you get from this new server
! 620: * is a message saying that indeed it does support the group you were looking
! 621: * for. So you continue and send rate confirmation and all that.
! 622: *
! 623: * Then you remember you had that SNAC to send, and now you have a means to
! 624: * do it, and you do, and everyone is happy. Except the Greenlander, who is
! 625: * still stuck in the bitter cold.
! 626: *
! 627: * Oh, and this is useful for building the Migration SNACs, too. In the
! 628: * future, this may help convince me to implement rate limit mitigation
! 629: * for real. We'll see.
! 630: *
! 631: * Just to make me look better, I'll say that I've known about this great
! 632: * scheme for quite some time now. But I still haven't convinced myself
! 633: * to make libfaim work that way. It would take a fair amount of effort,
! 634: * and probably some client API changes as well. (Whenever I don't want
! 635: * to do something, I just say it would change the client API. Then I
! 636: * instantly have a couple of supporters of not doing it.)
! 637: *
! 638: * Generally, addgroup is only called by the internal handling of the
! 639: * server ready SNAC. So if you want to do something before that, you'll
! 640: * have to be more creative. That is done rather early, though, so I don't
! 641: * think you have to worry about it. Unless you're me. I care deeply
! 642: * about such inane things.
! 643: *
! 644: */
! 645:
! 646: /**
! 647: * Find a FlapConnection that supports the given oscar
! 648: * family.
! 649: */
! 650: FlapConnection *
! 651: flap_connection_findbygroup(OscarData *od, guint16 group)
! 652: {
! 653: GSList *cur;
! 654:
! 655: for (cur = od->oscar_connections; cur != NULL; cur = cur->next)
! 656: {
! 657: FlapConnection *conn;
! 658: GSList *l;
! 659:
! 660: conn = cur->data;
! 661:
! 662: for (l = conn->groups; l != NULL; l = l->next)
! 663: {
! 664: if (GPOINTER_TO_UINT(l->data) == group)
! 665: return conn;
! 666: }
! 667: }
! 668:
! 669: return NULL;
! 670: }
! 671:
! 672: /**
! 673: * Locates a connection of the specified type in the
! 674: * specified session.
! 675: *
! 676: * TODO: Use flap_connection_findbygroup everywhere and get rid of this.
! 677: *
! 678: * @param od The session to search.
! 679: * @param type The type of connection to look for.
! 680: *
! 681: * @return Returns the first connection found of the given target type,
! 682: * or NULL if none could be found.
! 683: */
! 684: FlapConnection *
! 685: flap_connection_getbytype(OscarData *od, int type)
! 686: {
! 687: GSList *cur;
! 688:
! 689: for (cur = od->oscar_connections; cur != NULL; cur = cur->next)
! 690: {
! 691: FlapConnection *conn;
! 692: conn = cur->data;
! 693: if ((conn->type == type) && (conn->connected))
! 694: return conn;
! 695: }
! 696:
! 697: return NULL;
! 698: }
! 699:
! 700: FlapConnection *
! 701: flap_connection_getbytype_all(OscarData *od, int type)
! 702: {
! 703: GSList *cur;
! 704:
! 705: for (cur = od->oscar_connections; cur; cur = cur->next)
! 706: {
! 707: FlapConnection *conn;
! 708: conn = cur->data;
! 709: if (conn->type == type)
! 710: return conn;
! 711: }
! 712:
! 713: return NULL;
! 714: }
! 715:
! 716: /**
! 717: * Allocate a new FLAP frame.
! 718: *
! 719: * @param channel The FLAP channel. This is almost always 2.
! 720: */
! 721: FlapFrame *
! 722: flap_frame_new(OscarData *od, guint16 channel, int datalen)
! 723: {
! 724: FlapFrame *frame;
! 725:
! 726: frame = g_new0(FlapFrame, 1);
! 727: frame->channel = channel;
! 728:
! 729: if (datalen > 0)
! 730: byte_stream_new(&frame->data, datalen);
! 731:
! 732: return frame;
! 733: }
! 734:
! 735: static void
! 736: parse_snac(OscarData *od, FlapConnection *conn, FlapFrame *frame)
! 737: {
! 738: aim_module_t *cur;
! 739: aim_modsnac_t snac;
! 740:
! 741: if (byte_stream_bytes_left(&frame->data) < 10)
! 742: return;
! 743:
! 744: snac.family = byte_stream_get16(&frame->data);
! 745: snac.subtype = byte_stream_get16(&frame->data);
! 746: snac.flags = byte_stream_get16(&frame->data);
! 747: snac.id = byte_stream_get32(&frame->data);
! 748:
! 749: /* SNAC flags are apparently uniform across all SNACs, so we handle them here */
! 750: if (snac.flags & 0x0001) {
! 751: /*
! 752: * This means the SNAC will be followed by another SNAC with
! 753: * related information. We don't need to do anything about
! 754: * this here.
! 755: */
! 756: }
! 757: if (snac.flags & 0x8000) {
! 758: /*
! 759: * This packet contains the version of the family that this SNAC is
! 760: * in. You get this when your SSI module is version 2 or higher.
! 761: * For now we have no need for this, but you could always save
! 762: * it as a part of aim_modnsac_t, or something. The format is...
! 763: * 2 byte length of total mini-header (which is 6 bytes), then TLV
! 764: * of type 0x0001, length 0x0002, value is the 2 byte version
! 765: * number
! 766: */
! 767: byte_stream_advance(&frame->data, byte_stream_get16(&frame->data));
! 768: }
! 769:
! 770: for (cur = (aim_module_t *)od->modlistv; cur; cur = cur->next) {
! 771:
! 772: if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&
! 773: (cur->family != snac.family))
! 774: continue;
! 775:
! 776: if (cur->snachandler(od, conn, cur, frame, &snac, &frame->data))
! 777: return;
! 778: }
! 779: }
! 780:
! 781: static void
! 782: parse_fakesnac(OscarData *od, FlapConnection *conn, FlapFrame *frame, guint16 family, guint16 subtype)
! 783: {
! 784: aim_module_t *cur;
! 785: aim_modsnac_t snac;
! 786:
! 787: snac.family = family;
! 788: snac.subtype = subtype;
! 789: snac.flags = snac.id = 0;
! 790:
! 791: for (cur = (aim_module_t *)od->modlistv; cur; cur = cur->next) {
! 792:
! 793: if (!(cur->flags & AIM_MODFLAG_MULTIFAMILY) &&
! 794: (cur->family != snac.family))
! 795: continue;
! 796:
! 797: if (cur->snachandler(od, conn, cur, frame, &snac, &frame->data))
! 798: return;
! 799: }
! 800: }
! 801:
! 802: static void
! 803: parse_flap_ch4(OscarData *od, FlapConnection *conn, FlapFrame *frame)
! 804: {
! 805: GSList *tlvlist;
! 806: char *msg = NULL;
! 807:
! 808: if (byte_stream_bytes_left(&frame->data) == 0) {
! 809: /* XXX should do something with this */
! 810: return;
! 811: }
! 812:
! 813: /* An ICQ account is logging in */
! 814: if (conn->type == SNAC_FAMILY_AUTH)
! 815: {
! 816: parse_fakesnac(od, conn, frame, 0x0017, 0x0003);
! 817: return;
! 818: }
! 819:
! 820: tlvlist = aim_tlvlist_read(&frame->data);
! 821:
! 822: if (aim_tlv_gettlv(tlvlist, 0x0009, 1))
! 823: conn->disconnect_code = aim_tlv_get16(tlvlist, 0x0009, 1);
! 824:
! 825: if (aim_tlv_gettlv(tlvlist, 0x000b, 1))
! 826: msg = aim_tlv_getstr(tlvlist, 0x000b, 1);
! 827:
! 828: /*
! 829: * The server ended this FLAP connnection, so let's be nice and
! 830: * close the physical TCP connection
! 831: */
! 832: flap_connection_schedule_destroy(conn,
! 833: OSCAR_DISCONNECT_REMOTE_CLOSED, msg);
! 834:
! 835: aim_tlvlist_free(tlvlist);
! 836:
! 837: g_free(msg);
! 838: }
! 839:
! 840: /**
! 841: * Takes a new incoming FLAP frame and sends it to the appropriate
! 842: * handler function to be parsed.
! 843: */
! 844: static void
! 845: parse_flap(OscarData *od, FlapConnection *conn, FlapFrame *frame)
! 846: {
! 847: if (frame->channel == 0x01) {
! 848: guint32 flap_version = byte_stream_get32(&frame->data);
! 849: if (flap_version != 0x00000001)
! 850: {
! 851: /* Error! */
! 852: purple_debug_warning("oscar", "Expecting FLAP version "
! 853: "0x00000001 but received FLAP version %08x. Closing connection.\n",
! 854: flap_version);
! 855: flap_connection_schedule_destroy(conn,
! 856: OSCAR_DISCONNECT_INVALID_DATA, NULL);
! 857: }
! 858: else
! 859: conn->connected = TRUE;
! 860:
! 861: } else if (frame->channel == 0x02) {
! 862: parse_snac(od, conn, frame);
! 863:
! 864: } else if (frame->channel == 0x04) {
! 865: parse_flap_ch4(od, conn, frame);
! 866:
! 867: } else if (frame->channel == 0x05) {
! 868: /* TODO: Reset our keepalive watchdog? */
! 869:
! 870: }
! 871: }
! 872:
! 873: /**
! 874: * Read in all available data on the socket for a given connection.
! 875: * All complete FLAPs handled immedate after they're received.
! 876: * Incomplete FLAP data is stored locally and appended to the next
! 877: * time this callback is triggered.
! 878: *
! 879: * This is called by flap_connection_recv_cb and
! 880: * flap_connection_recv_cb_ssl for unencrypted/encrypted connections.
! 881: */
! 882: static void
! 883: flap_connection_recv(FlapConnection *conn)
! 884: {
! 885: gpointer buf;
! 886: gsize buflen;
! 887: gssize read;
! 888:
! 889: /* Read data until we run out of data and break out of the loop */
! 890: while (TRUE)
! 891: {
! 892: /* Start reading a new FLAP */
! 893: if (conn->buffer_incoming.data.data == NULL)
! 894: {
! 895: buf = conn->header + conn->header_received;
! 896: buflen = 6 - conn->header_received;
! 897:
! 898: /* Read the first 6 bytes (the FLAP header) */
! 899: if (conn->gsc)
! 900: read = purple_ssl_read(conn->gsc, buf, buflen);
! 901: else
! 902: read = recv(conn->fd, buf, buflen, 0);
! 903:
! 904: /* Check if the FLAP server closed the connection */
! 905: if (read == 0)
! 906: {
! 907: flap_connection_schedule_destroy(conn,
! 908: OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
! 909: break;
! 910: }
! 911:
! 912: /* If there was an error then close the connection */
! 913: if (read < 0)
! 914: {
! 915: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
! 916: /* No worries */
! 917: break;
! 918:
! 919: /* Error! */
! 920: flap_connection_schedule_destroy(conn,
! 921: OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
! 922: break;
! 923: }
! 924: conn->od->gc->last_received = time(NULL);
! 925:
! 926: /* If we don't even have a complete FLAP header then do nothing */
! 927: conn->header_received += read;
! 928: if (conn->header_received < 6)
! 929: break;
! 930:
! 931: /* All FLAP frames must start with the byte 0x2a */
! 932: if (aimutil_get8(&conn->header[0]) != 0x2a)
! 933: {
! 934: flap_connection_schedule_destroy(conn,
! 935: OSCAR_DISCONNECT_INVALID_DATA, NULL);
! 936: break;
! 937: }
! 938:
! 939: /* Initialize a new temporary FlapFrame for incoming data */
! 940: conn->buffer_incoming.channel = aimutil_get8(&conn->header[1]);
! 941: conn->buffer_incoming.seqnum = aimutil_get16(&conn->header[2]);
! 942: conn->buffer_incoming.data.len = aimutil_get16(&conn->header[4]);
! 943: conn->buffer_incoming.data.data = g_new(guint8, conn->buffer_incoming.data.len);
! 944: conn->buffer_incoming.data.offset = 0;
! 945: }
! 946:
! 947: buflen = conn->buffer_incoming.data.len - conn->buffer_incoming.data.offset;
! 948: if (buflen)
! 949: {
! 950: buf = &conn->buffer_incoming.data.data[conn->buffer_incoming.data.offset];
! 951: /* Read data into the temporary FlapFrame until it is complete */
! 952: if (conn->gsc)
! 953: read = purple_ssl_read(conn->gsc, buf, buflen);
! 954: else
! 955: read = recv(conn->fd, buf, buflen, 0);
! 956:
! 957: /* Check if the FLAP server closed the connection */
! 958: if (read == 0)
! 959: {
! 960: flap_connection_schedule_destroy(conn,
! 961: OSCAR_DISCONNECT_REMOTE_CLOSED, NULL);
! 962: break;
! 963: }
! 964:
! 965: if (read < 0)
! 966: {
! 967: if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
! 968: /* No worries */
! 969: break;
! 970:
! 971: /* Error! */
! 972: flap_connection_schedule_destroy(conn,
! 973: OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
! 974: break;
! 975: }
! 976:
! 977: conn->buffer_incoming.data.offset += read;
! 978: if (conn->buffer_incoming.data.offset < conn->buffer_incoming.data.len)
! 979: /* Waiting for more data to arrive */
! 980: break;
! 981: }
! 982:
! 983: /* We have a complete FLAP! Handle it and continue reading */
! 984: byte_stream_rewind(&conn->buffer_incoming.data);
! 985: parse_flap(conn->od, conn, &conn->buffer_incoming);
! 986: conn->lastactivity = time(NULL);
! 987:
! 988: g_free(conn->buffer_incoming.data.data);
! 989: conn->buffer_incoming.data.data = NULL;
! 990:
! 991: conn->header_received = 0;
! 992: }
! 993: }
! 994:
! 995: void
! 996: flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond)
! 997: {
! 998: FlapConnection *conn = data;
! 999:
! 1000: flap_connection_recv(conn);
! 1001: }
! 1002:
! 1003: void
! 1004: flap_connection_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
! 1005: {
! 1006: FlapConnection *conn = data;
! 1007:
! 1008: flap_connection_recv(conn);
! 1009: }
! 1010:
! 1011: /**
! 1012: * @param source When this function is called as a callback source is
! 1013: * set to the fd that triggered the callback. But this function
! 1014: * is also called directly from flap_connection_send_byte_stream(),
! 1015: * in which case source will be -1. So don't use source--use
! 1016: * conn->gsc or conn->fd instead.
! 1017: */
! 1018: static void
! 1019: send_cb(gpointer data, gint source, PurpleInputCondition cond)
! 1020: {
! 1021: FlapConnection *conn;
! 1022: int writelen, ret;
! 1023:
! 1024: conn = data;
! 1025: writelen = purple_circ_buffer_get_max_read(conn->buffer_outgoing);
! 1026:
! 1027: if (writelen == 0)
! 1028: {
! 1029: purple_input_remove(conn->watcher_outgoing);
! 1030: conn->watcher_outgoing = 0;
! 1031: return;
! 1032: }
! 1033:
! 1034: if (conn->gsc)
! 1035: ret = purple_ssl_write(conn->gsc, conn->buffer_outgoing->outptr,
! 1036: writelen);
! 1037: else
! 1038: ret = send(conn->fd, conn->buffer_outgoing->outptr, writelen, 0);
! 1039: if (ret <= 0)
! 1040: {
! 1041: if (ret < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
! 1042: /* No worries */
! 1043: return;
! 1044:
! 1045: /* Error! */
! 1046: purple_input_remove(conn->watcher_outgoing);
! 1047: conn->watcher_outgoing = 0;
! 1048: if (conn->gsc) {
! 1049: purple_ssl_close(conn->gsc);
! 1050: conn->gsc = NULL;
! 1051: } else {
! 1052: close(conn->fd);
! 1053: conn->fd = -1;
! 1054: }
! 1055: flap_connection_schedule_destroy(conn,
! 1056: OSCAR_DISCONNECT_LOST_CONNECTION, g_strerror(errno));
! 1057: return;
! 1058: }
! 1059:
! 1060: purple_circ_buffer_mark_read(conn->buffer_outgoing, ret);
! 1061: }
! 1062:
! 1063: static void
! 1064: flap_connection_send_byte_stream(ByteStream *bs, FlapConnection *conn, size_t count)
! 1065: {
! 1066: if (conn == NULL)
! 1067: return;
! 1068:
! 1069: /* Make sure we don't send past the end of the bs */
! 1070: if (count > byte_stream_bytes_left(bs))
! 1071: count = byte_stream_bytes_left(bs); /* truncate to remaining space */
! 1072:
! 1073: if (count == 0)
! 1074: return;
! 1075:
! 1076: /* Add everything to our outgoing buffer */
! 1077: purple_circ_buffer_append(conn->buffer_outgoing, bs->data, count);
! 1078:
! 1079: /* If we haven't already started writing stuff, then start the cycle */
! 1080: if (conn->watcher_outgoing == 0)
! 1081: {
! 1082: if (conn->gsc) {
! 1083: conn->watcher_outgoing = purple_input_add(conn->gsc->fd,
! 1084: PURPLE_INPUT_WRITE, send_cb, conn);
! 1085: send_cb(conn, -1, 0);
! 1086: } else if (conn->fd >= 0) {
! 1087: conn->watcher_outgoing = purple_input_add(conn->fd,
! 1088: PURPLE_INPUT_WRITE, send_cb, conn);
! 1089: send_cb(conn, -1, 0);
! 1090: }
! 1091: }
! 1092: }
! 1093:
! 1094: static void
! 1095: sendframe_flap(FlapConnection *conn, FlapFrame *frame)
! 1096: {
! 1097: ByteStream bs;
! 1098: int payloadlen, bslen;
! 1099:
! 1100: payloadlen = byte_stream_curpos(&frame->data);
! 1101:
! 1102: byte_stream_new(&bs, 6 + payloadlen);
! 1103:
! 1104: /* FLAP header */
! 1105: byte_stream_put8(&bs, 0x2a);
! 1106: byte_stream_put8(&bs, frame->channel);
! 1107: byte_stream_put16(&bs, frame->seqnum);
! 1108: byte_stream_put16(&bs, payloadlen);
! 1109:
! 1110: /* Payload */
! 1111: byte_stream_rewind(&frame->data);
! 1112: byte_stream_putbs(&bs, &frame->data, payloadlen);
! 1113:
! 1114: bslen = byte_stream_curpos(&bs);
! 1115: byte_stream_rewind(&bs);
! 1116: flap_connection_send_byte_stream(&bs, conn, bslen);
! 1117:
! 1118: byte_stream_destroy(&bs);
! 1119: }
! 1120:
! 1121: void
! 1122: flap_connection_send(FlapConnection *conn, FlapFrame *frame)
! 1123: {
! 1124: frame->seqnum = ++(conn->seqnum_out);
! 1125: sendframe_flap(conn, frame);
! 1126: flap_frame_destroy(frame);
! 1127: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>