diff --git a/purple/config/version.txt b/purple/config/version.txt --- a/purple/config/version.txt +++ b/purple/config/version.txt @@ -1,1 +1,1 @@ -2.7.3 +2.7.9 diff --git a/purple/libpurple/account.c b/purple/libpurple/account.c --- a/purple/libpurple/account.c +++ b/purple/libpurple/account.c @@ -505,18 +505,56 @@ migrate_yahoo_japan(PurpleAccount *accou } /* these should always be nuked */ purple_account_remove_setting(account, "yahoojp"); purple_account_remove_setting(account, "serverjp"); purple_account_remove_setting(account, "xferjp_host"); } - - return; +} + +static void +migrate_icq_server(PurpleAccount *account) +{ + /* Migrate the login server setting for ICQ accounts. See + * 'mtn log --last 1 --no-graph --from b6d7712e90b68610df3bd2d8cbaf46d94c8b3794' + * for details on the change. */ + + if(purple_strequal(purple_account_get_protocol_id(account), "prpl-icq")) { + const char *tmp = purple_account_get_string(account, "server", NULL); + + /* Non-secure server */ + if(purple_strequal(tmp, "login.messaging.aol.com") || + purple_strequal(tmp, "login.oscar.aol.com")) + purple_account_set_string(account, "server", "login.icq.com"); + + /* Secure server */ + if(purple_strequal(tmp, "slogin.oscar.aol.com")) + purple_account_set_string(account, "server", "slogin.icq.com"); + } +} + +static void +migrate_xmpp_encryption(PurpleAccount *account) +{ + /* When this is removed, nuke the "old_ssl" and "require_tls" settings */ + if (g_str_equal(purple_account_get_protocol_id(account), "prpl-jabber")) { + const char *sec = purple_account_get_string(account, "connection_security", ""); + + if (g_str_equal("", sec)) { + const char *val = "require_tls"; + if (purple_account_get_bool(account, "old_ssl", FALSE)) + val = "old_ssl"; + else if (!purple_account_get_bool(account, "require_tls", TRUE)) + val = "opportunistic_tls"; + + purple_account_set_string(account, "connection_security", val); + } + } } static void parse_settings(xmlnode *node, PurpleAccount *account) { const char *ui; xmlnode *child; @@ -576,16 +614,22 @@ parse_settings(xmlnode *node, PurpleAcco } g_free(data); } /* we do this here because we need access to account settings to determine * if we can/should migrate an old Yahoo! JAPAN account */ migrate_yahoo_japan(account); + /* we do this here because we need access to account settings to determine + * if we can/should migrate an ICQ account's server setting */ + migrate_icq_server(account); + /* we do this here because we need to do it before the user views the + * Edit Account dialog. */ + migrate_xmpp_encryption(account); } static GList * parse_status_attrs(xmlnode *node, PurpleStatus *status) { GList *list = NULL; xmlnode *child; PurpleValue *attr_value; @@ -1043,16 +1087,19 @@ purple_account_destroy(PurpleAccount *ac g_free(account->password); g_free(account->user_info); g_free(account->buddy_icon_path); g_free(account->protocol_id); g_hash_table_destroy(account->settings); g_hash_table_destroy(account->ui_settings); + if (account->proxy_info) + purple_proxy_info_destroy(account->proxy_info); + purple_account_set_status_types(account, NULL); purple_presence_destroy(account->presence); #if 0 if(account->system_log) purple_log_free(account->system_log); #endif @@ -1131,17 +1178,17 @@ request_password_ok_cb(PurpleAccount *ac purple_account_set_password(account, entry); _purple_connection_new(account, FALSE, entry); } static void request_password_cancel_cb(PurpleAccount *account, PurpleRequestFields *fields) { - /* Disable the account as the user has canceled connecting */ + /* Disable the account as the user has cancelled connecting */ purple_account_set_enabled(account, purple_core_get_ui(), FALSE); } void purple_account_request_password(PurpleAccount *account, GCallback ok_cb, GCallback cancel_cb, void *user_data) { diff --git a/purple/libpurple/connection.c b/purple/libpurple/connection.c --- a/purple/libpurple/connection.c +++ b/purple/libpurple/connection.c @@ -130,17 +130,17 @@ _purple_connection_new(PurpleAccount *ac return; } else { if (((password == NULL) || (*password == '\0')) && !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) { - purple_debug_error("connection", "Can not connect to account %s without " + purple_debug_error("connection", "Cannot connect to account %s without " "a password.\n", purple_account_get_username(account)); return; } } gc = g_new0(PurpleConnection, 1); PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection); @@ -205,17 +205,17 @@ _purple_connection_new_unregister(Purple prpl_info->unregister_user(account, cb, user_data); return; } if (((password == NULL) || (*password == '\0')) && !(prpl_info->options & OPT_PROTO_NO_PASSWORD) && !(prpl_info->options & OPT_PROTO_PASSWORD_OPTIONAL)) { - purple_debug_error("connection", "Can not connect to account %s without " + purple_debug_error("connection", "Cannot connect to account %s without " "a password.\n", purple_account_get_username(account)); return; } gc = g_new0(PurpleConnection, 1); PURPLE_DBUS_REGISTER_POINTER(gc, PurpleConnection); gc->prpl = prpl; diff --git a/purple/libpurple/conversation.c b/purple/libpurple/conversation.c --- a/purple/libpurple/conversation.c +++ b/purple/libpurple/conversation.c @@ -1143,25 +1143,23 @@ purple_conv_im_get_typing_state(const Pu return im->typing_state; } void purple_conv_im_start_typing_timeout(PurpleConvIm *im, int timeout) { PurpleConversation *conv; - const char *name; g_return_if_fail(im != NULL); if (im->typing_timeout > 0) purple_conv_im_stop_typing_timeout(im); conv = purple_conv_im_get_conversation(im); - name = purple_conversation_get_name(conv); im->typing_timeout = purple_timeout_add_seconds(timeout, reset_typing_cb, conv); } void purple_conv_im_stop_typing_timeout(PurpleConvIm *im) { g_return_if_fail(im != NULL); @@ -1539,26 +1537,24 @@ purple_conv_chat_get_id(const PurpleConv void purple_conv_chat_write(PurpleConvChat *chat, const char *who, const char *message, PurpleMessageFlags flags, time_t mtime) { PurpleAccount *account; PurpleConversation *conv; PurpleConnection *gc; - PurplePluginProtocolInfo *prpl_info; g_return_if_fail(chat != NULL); g_return_if_fail(who != NULL); g_return_if_fail(message != NULL); conv = purple_conv_chat_get_conversation(chat); gc = purple_conversation_get_gc(conv); account = purple_connection_get_account(gc); - prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(purple_connection_get_prpl(gc)); /* Don't display this if the person who wrote it is ignored. */ if (purple_conv_chat_is_user_ignored(chat, who)) return; if (!(flags & PURPLE_MESSAGE_WHISPER)) { const char *str; diff --git a/purple/libpurple/dnssrv.c b/purple/libpurple/dnssrv.c --- a/purple/libpurple/dnssrv.c +++ b/purple/libpurple/dnssrv.c @@ -529,17 +529,17 @@ resolved(gpointer data, gint source, Pur /** The Jabber Server code was inspiration for parts of this. */ static gboolean res_main_thread_cb(gpointer data) { PurpleSrvResponse *srvres = NULL; PurpleSrvQueryData *query_data = data; if(query_data->error_message != NULL) { - purple_debug_error("dnssrv", query_data->error_message); + purple_debug_error("dnssrv", "%s", query_data->error_message); if (query_data->type == DNS_TYPE_SRV) { if (query_data->cb.srv) query_data->cb.srv(srvres, 0, query_data->extradata); } else if (query_data->type == DNS_TYPE_TXT) { if (query_data->cb.txt) query_data->cb.txt(NULL, query_data->extradata); } } else { diff --git a/purple/libpurple/ft.c b/purple/libpurple/ft.c --- a/purple/libpurple/ft.c +++ b/purple/libpurple/ft.c @@ -747,16 +747,17 @@ purple_xfer_get_remote_user(const Purple PurpleXferStatusType purple_xfer_get_status(const PurpleXfer *xfer) { g_return_val_if_fail(xfer != NULL, PURPLE_XFER_STATUS_UNKNOWN); return xfer->status; } +/* FIXME: Rename with cancelled for 3.0.0. */ gboolean purple_xfer_is_canceled(const PurpleXfer *xfer) { g_return_val_if_fail(xfer != NULL, TRUE); if ((purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) || (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_REMOTE)) return TRUE; @@ -1285,16 +1286,21 @@ transfer_cb(gpointer data, gint source, } static void begin_transfer(PurpleXfer *xfer, PurpleInputCondition cond) { PurpleXferType type = purple_xfer_get_type(xfer); PurpleXferUiOps *ui_ops = purple_xfer_get_ui_ops(xfer); + if (xfer->start_time != 0) { + purple_debug_error("xfer", "Transfer is being started multiple times\n"); + g_return_if_reached(); + } + if (ui_ops == NULL || (ui_ops->ui_read == NULL && ui_ops->ui_write == NULL)) { xfer->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer), type == PURPLE_XFER_RECEIVE ? "wb" : "rb"); if (xfer->dest_fp == NULL) { purple_xfer_show_file_error(xfer, purple_xfer_get_local_filename(xfer)); purple_xfer_cancel_local(xfer); return; @@ -1477,16 +1483,30 @@ purple_xfer_add(PurpleXfer *xfer) void purple_xfer_cancel_local(PurpleXfer *xfer) { PurpleXferUiOps *ui_ops; char *msg = NULL; g_return_if_fail(xfer != NULL); + /* TODO: We definitely want to close any open request dialogs associated + with this transfer. However, in some cases the request dialog might + own a reference on the xfer. This happens at least with the "%s wants + to send you %s" dialog from purple_xfer_ask_recv(). In these cases + the ref count will not be decremented when the request dialog is + closed, so the ref count will never reach 0 and the xfer will never + be freed. This is a memleak and should be fixed. It's not clear what + the correct fix is. Probably requests should have a destroy function + that is called when the request is destroyed. But also, ref counting + xfer objects makes this code REALLY complicated. An alternate fix is + to not ref count and instead just make sure the object still exists + when we try to use it. */ + purple_request_close_with_handle(xfer); + purple_xfer_set_status(xfer, PURPLE_XFER_STATUS_CANCEL_LOCAL); xfer->end_time = time(NULL); if (purple_xfer_get_filename(xfer) != NULL) { msg = g_strdup_printf(_("You cancelled the transfer of %s"), purple_xfer_get_filename(xfer)); } diff --git a/purple/libpurple/ft.h b/purple/libpurple/ft.h --- a/purple/libpurple/ft.h +++ b/purple/libpurple/ft.h @@ -53,18 +53,18 @@ typedef enum */ typedef enum { PURPLE_XFER_STATUS_UNKNOWN = 0, /**< Unknown, the xfer may be null. */ PURPLE_XFER_STATUS_NOT_STARTED, /**< It hasn't started yet. */ PURPLE_XFER_STATUS_ACCEPTED, /**< Receive accepted, but destination file not selected yet */ PURPLE_XFER_STATUS_STARTED, /**< purple_xfer_start has been called. */ PURPLE_XFER_STATUS_DONE, /**< The xfer completed successfully. */ - PURPLE_XFER_STATUS_CANCEL_LOCAL, /**< The xfer was canceled by us. */ - PURPLE_XFER_STATUS_CANCEL_REMOTE /**< The xfer was canceled by the other end, or we couldn't connect. */ + PURPLE_XFER_STATUS_CANCEL_LOCAL, /**< The xfer was cancelled by us. */ + PURPLE_XFER_STATUS_CANCEL_REMOTE /**< The xfer was cancelled by the other end, or we couldn't connect. */ } PurpleXferStatusType; /** * File transfer UI operations. * * Any UI representing a file transfer must assign a filled-out * PurpleXferUiOps structure to the purple_xfer. */ @@ -299,21 +299,22 @@ const char *purple_xfer_get_remote_user( * * @param xfer The file transfer. * * @return The status. */ PurpleXferStatusType purple_xfer_get_status(const PurpleXfer *xfer); /** - * Returns true if the file transfer was canceled. + * Returns true if the file transfer was cancelled. * * @param xfer The file transfer. * - * @return Whether or not the transfer was canceled. + * @return Whether or not the transfer was cancelled. + * FIXME: This should be renamed using cancelled for 3.0.0. */ gboolean purple_xfer_is_canceled(const PurpleXfer *xfer); /** * Returns the completed state for a file transfer. * * @param xfer The file transfer. * diff --git a/purple/libpurple/mime.c b/purple/libpurple/mime.c --- a/purple/libpurple/mime.c +++ b/purple/libpurple/mime.c @@ -431,16 +431,44 @@ doc_parts_load(PurpleMimeDocument *doc, } b = tail; } g_free(bnd); } +#define BOUNDARY "boundary=" +static char * +parse_boundary(const char *ct) +{ + char *boundary_begin = g_strstr_len(ct, -1, BOUNDARY); + char *boundary_end; + + if (!boundary_begin) + return NULL; + + boundary_begin += sizeof(BOUNDARY) - 1; + + if (*boundary_begin == '"') { + boundary_end = strchr(++boundary_begin, '"'); + if (!boundary_end) + return NULL; + } else { + boundary_end = strchr(boundary_begin, ' '); + if (!boundary_end) { + boundary_end = strchr(boundary_begin, ';'); + if (!boundary_end) + boundary_end = boundary_begin + strlen(boundary_begin); + } + } + + return g_strndup(boundary_begin, boundary_end - boundary_begin); +} +#undef BOUNDARY PurpleMimeDocument * purple_mime_document_parsen(const char *buf, gsize len) { PurpleMimeDocument *doc; char *b = (char *) buf; gsize n = len; @@ -451,20 +479,21 @@ purple_mime_document_parsen(const char * if (!len) return doc; fields_load(&doc->fields, &b, &n); { const char *ct = fields_get(&doc->fields, "content-type"); - if(ct && purple_str_has_prefix(ct, "multipart")) { - char *bd = strrchr(ct, '='); - if(bd++) { + if (ct && purple_str_has_prefix(ct, "multipart")) { + char *bd = parse_boundary(ct); + if (bd) { doc_parts_load(doc, bd, b, n); + g_free(bd); } } } return doc; } diff --git a/purple/libpurple/network.c b/purple/libpurple/network.c --- a/purple/libpurple/network.c +++ b/purple/libpurple/network.c @@ -350,46 +350,44 @@ purple_network_set_upnp_port_mapping_cb( purple_network_get_port_from_fd(listen_data->listenfd), (listen_data->socket_type == SOCK_STREAM) ? "TCP" : "UDP", purple_network_set_upnp_port_mapping_cb, listen_data); return; } if (success) { /* add port mapping to hash table */ - gint *key = g_new(gint, 1); - gint *value = g_new(gint, 1); - *key = purple_network_get_port_from_fd(listen_data->listenfd); - *value = listen_data->socket_type; - g_hash_table_insert(upnp_port_mappings, key, value); + gint key = purple_network_get_port_from_fd(listen_data->listenfd); + gint value = listen_data->socket_type; + g_hash_table_insert(upnp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value)); } if (listen_data->cb) listen_data->cb(listen_data->listenfd, listen_data->cb_data); - /* Clear the UPnP mapping data, since it's complete and purple_netweork_listen_cancel() will try to cancel + /* Clear the UPnP mapping data, since it's complete and purple_network_listen_cancel() will try to cancel * it otherwise. */ listen_data->mapping_data = NULL; purple_network_listen_cancel(listen_data); } static gboolean purple_network_finish_pmp_map_cb(gpointer data) { PurpleNetworkListenData *listen_data; - gint *key = g_new(gint, 1); - gint *value = g_new(gint, 1); + gint key; + gint value; listen_data = data; listen_data->timer = 0; /* add port mapping to hash table */ - *key = purple_network_get_port_from_fd(listen_data->listenfd); - *value = listen_data->socket_type; - g_hash_table_insert(nat_pmp_port_mappings, key, value); + key = purple_network_get_port_from_fd(listen_data->listenfd); + value = listen_data->socket_type; + g_hash_table_insert(nat_pmp_port_mappings, GINT_TO_POINTER(key), GINT_TO_POINTER(value)); if (listen_data->cb) listen_data->cb(listen_data->listenfd, listen_data->cb_data); purple_network_listen_cancel(listen_data); return FALSE; } @@ -693,17 +691,17 @@ static gboolean wpurple_network_change_t current_network_count = new_count; return FALSE; } static gboolean _print_debug_msg(gpointer data) { gchar *msg = data; - purple_debug_warning("network", msg); + purple_debug_warning("network", "%s", msg); g_free(msg); return FALSE; } static gpointer wpurple_network_change_thread(gpointer data) { WSAQUERYSET qs; WSAEVENT *nla_event; @@ -1048,52 +1046,52 @@ purple_network_upnp_mapping_remove_cb(gb /* the reason for these functions to have these signatures is to be able to use them for g_hash_table_foreach to clean remaining port mappings, which is not yet done */ static void purple_network_upnp_mapping_remove(gpointer key, gpointer value, gpointer user_data) { - gint port = (gint) *((gint *) key); - gint protocol = (gint) *((gint *) value); + gint port = GPOINTER_TO_INT(key); + gint protocol = GPOINTER_TO_INT(value); purple_debug_info("network", "removing UPnP port mapping for port %d\n", port); - purple_upnp_remove_port_mapping(port, - protocol == SOCK_STREAM ? "TCP" : "UDP", + purple_upnp_remove_port_mapping(port, + protocol == SOCK_STREAM ? "TCP" : "UDP", purple_network_upnp_mapping_remove_cb, NULL); - g_hash_table_remove(upnp_port_mappings, key); + g_hash_table_remove(upnp_port_mappings, GINT_TO_POINTER(port)); } static void purple_network_nat_pmp_mapping_remove(gpointer key, gpointer value, gpointer user_data) { - gint port = (gint) *((gint *) key); - gint protocol = (gint) *((gint *) value); + gint port = GPOINTER_TO_INT(key); + gint protocol = GPOINTER_TO_INT(value); purple_debug_info("network", "removing NAT-PMP port mapping for port %d\n", port); purple_pmp_destroy_map( - protocol == SOCK_STREAM ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP, + protocol == SOCK_STREAM ? PURPLE_PMP_TYPE_TCP : PURPLE_PMP_TYPE_UDP, port); - g_hash_table_remove(nat_pmp_port_mappings, key); + g_hash_table_remove(nat_pmp_port_mappings, GINT_TO_POINTER(port)); } void purple_network_remove_port_mapping(gint fd) { int port = purple_network_get_port_from_fd(fd); - gint *protocol = g_hash_table_lookup(upnp_port_mappings, &port); + gint protocol = GPOINTER_TO_INT(g_hash_table_lookup(upnp_port_mappings, GINT_TO_POINTER(port))); if (protocol) { - purple_network_upnp_mapping_remove(&port, protocol, NULL); + purple_network_upnp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL); } else { - protocol = g_hash_table_lookup(nat_pmp_port_mappings, &port); + protocol = GPOINTER_TO_INT(g_hash_table_lookup(nat_pmp_port_mappings, GINT_TO_POINTER(port))); if (protocol) { - purple_network_nat_pmp_mapping_remove(&port, protocol, NULL); + purple_network_nat_pmp_mapping_remove(GINT_TO_POINTER(port), GINT_TO_POINTER(protocol), NULL); } } } int purple_network_convert_idn_to_ascii(const gchar *in, gchar **out) { #ifdef USE_IDN char *tmp; @@ -1181,26 +1179,24 @@ purple_network_init(void) } #endif purple_signal_register(purple_network_get_handle(), "network-configuration-changed", purple_marshal_VOID, NULL, 0); purple_pmp_init(); purple_upnp_init(); - + purple_network_set_stun_server( purple_prefs_get_string("/purple/network/stun_server")); purple_network_set_turn_server( purple_prefs_get_string("/purple/network/turn_server")); - upnp_port_mappings = - g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free); - nat_pmp_port_mappings = - g_hash_table_new_full(g_int_hash, g_int_equal, g_free, g_free); + upnp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal); + nat_pmp_port_mappings = g_hash_table_new(g_direct_hash, g_direct_equal); } void purple_network_uninit(void) { #ifdef HAVE_NETWORKMANAGER @@ -1234,24 +1230,24 @@ purple_network_uninit(void) network_change_handle = NULL; } g_static_mutex_unlock(&mutex); #endif purple_signal_unregister(purple_network_get_handle(), "network-configuration-changed"); - + if (stun_ip) g_free(stun_ip); g_hash_table_destroy(upnp_port_mappings); g_hash_table_destroy(nat_pmp_port_mappings); - /* TODO: clean up remaining port mappings, note calling + /* TODO: clean up remaining port mappings, note calling purple_upnp_remove_port_mapping from here doesn't quite work... */ } void purple_network_configuration_changed(void) { purple_signal_emit(purple_network_get_handle(), "network-configuration-changed", NULL); } diff --git a/purple/libpurple/network.h b/purple/libpurple/network.h --- a/purple/libpurple/network.h +++ b/purple/libpurple/network.h @@ -234,17 +234,17 @@ PurpleNetworkListenData *purple_network_ unsigned short start, unsigned short end, int socket_family, int socket_type, PurpleNetworkListenCallback cb, gpointer cb_data); /** * This can be used to cancel any in-progress listener connection * by passing in the return value from either purple_network_listen() * or purple_network_listen_range(). * - * @param listen_data This listener attempt will be canceled and + * @param listen_data This listener attempt will be cancelled and * the struct will be freed. */ void purple_network_listen_cancel(PurpleNetworkListenData *listen_data); /** * Gets a port number from a file descriptor. * * @param fd The file descriptor. This should be a tcp socket. The current diff --git a/purple/libpurple/ntlm.c b/purple/libpurple/ntlm.c --- a/purple/libpurple/ntlm.c +++ b/purple/libpurple/ntlm.c @@ -147,19 +147,24 @@ purple_ntlm_gen_type1(const gchar *hostn guint8 * purple_ntlm_parse_type2(const gchar *type2, guint32 *flags) { gsize retlen; struct type2_message *tmsg; static guint8 nonce[8]; tmsg = (struct type2_message*)purple_base64_decode(type2, &retlen); - memcpy(nonce, tmsg->nonce, 8); - if (flags != NULL) - *flags = GUINT16_FROM_LE(tmsg->flags); + if (tmsg != NULL && retlen >= (sizeof(struct type2_message) - 1)) { + memcpy(nonce, tmsg->nonce, 8); + if (flags != NULL) + *flags = GUINT16_FROM_LE(tmsg->flags); + } else { + purple_debug_error("ntlm", "Unable to parse type2 message - returning empty nonce.\n"); + memset(nonce, 0, 8); + } g_free(tmsg); return nonce; } /** * Create a 64bit DES key by taking a 56bit key and adding * a parity bit after every 7th bit. diff --git a/purple/libpurple/protocols/gg/lib/dcc7.c b/purple/libpurple/protocols/gg/lib/dcc7.c --- a/purple/libpurple/protocols/gg/lib/dcc7.c +++ b/purple/libpurple/protocols/gg/lib/dcc7.c @@ -728,16 +728,17 @@ int gg_dcc7_handle_info(struct gg_sessio if (dcc->type == GG_SESSION_DCC7_SEND) { e->type = GG_EVENT_DCC7_ACCEPT; e->event.dcc7_accept.dcc7 = dcc; e->event.dcc7_accept.type = gg_fix32(p->type); e->event.dcc7_accept.remote_ip = dcc->remote_addr; e->event.dcc7_accept.remote_port = dcc->remote_port; } else { e->type = GG_EVENT_DCC7_PENDING; + e->event.dcc7_pending.dcc7 = dcc; } if (gg_dcc7_connect(sess, dcc) == -1) { if (gg_dcc7_reverse_connect(dcc) == -1) { e->type = GG_EVENT_DCC7_ERROR; e->event.dcc7_error = GG_ERROR_DCC7_NET; return 0; } @@ -1001,16 +1002,17 @@ struct gg_event *gg_dcc7_watch_fd(struct if (dcc->timeout == 0) error = ETIMEDOUT; if (error || (res = getsockopt(dcc->fd, SOL_SOCKET, SO_ERROR, &error, &error_size)) == -1 || error != 0) { gg_debug_dcc(dcc, GG_DEBUG_MISC, "// gg_dcc7_watch_fd() connection failed (%s)\n", (res == -1) ? strerror(errno) : strerror(error)); if (gg_dcc7_reverse_connect(dcc) != -1) { e->type = GG_EVENT_DCC7_PENDING; + e->event.dcc7_pending.dcc7 = dcc; } else { e->type = GG_EVENT_DCC7_ERROR; e->event.dcc_error = GG_ERROR_DCC7_NET; } return e; } diff --git a/purple/libpurple/protocols/gg/lib/events.c b/purple/libpurple/protocols/gg/lib/events.c --- a/purple/libpurple/protocols/gg/lib/events.c +++ b/purple/libpurple/protocols/gg/lib/events.c @@ -1144,37 +1144,37 @@ static int gg_watch_fd_connected(struct { struct gg_notify_reply80 *n = (void*) p; unsigned int length = h->length, i = 0; gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() received a notify reply\n"); e->type = GG_EVENT_NOTIFY60; e->event.notify60 = malloc(sizeof(*e->event.notify60)); + if (!e->event.notify60) { gg_debug_session(sess, GG_DEBUG_MISC, "// gg_watch_fd_connected() not enough memory for notify data\n"); goto fail; } e->event.notify60[0].uin = 0; while (length >= sizeof(struct gg_notify_reply80)) { uint32_t descr_len; char *tmp; - + e->event.notify60[i].uin = gg_fix32(n->uin); e->event.notify60[i].status = gg_fix32(n->status); e->event.notify60[i].remote_ip = n->remote_ip; e->event.notify60[i].remote_port= gg_fix16(n->remote_port); e->event.notify60[i].image_size = n->image_size; e->event.notify60[i].descr = NULL; e->event.notify60[i].version = 0x00; /* not-supported */ e->event.notify60[i].time = 0; /* not-supported */ - descr_len = gg_fix32(n->descr_len); length -= sizeof(struct gg_notify_reply80); n = (void*) ((char*) n + sizeof(struct gg_notify_reply80)); if (descr_len) { if (length >= descr_len) { /* XXX, GG_S_D(n->status) */ diff --git a/purple/libpurple/protocols/gg/lib/libgadu.c b/purple/libpurple/protocols/gg/lib/libgadu.c --- a/purple/libpurple/protocols/gg/lib/libgadu.c +++ b/purple/libpurple/protocols/gg/lib/libgadu.c @@ -64,17 +64,17 @@ #include #include #include #ifdef GG_CONFIG_HAVE_OPENSSL # include # include #endif -#define GG_LIBGADU_VERSION "1.9.0-rc2" +#define GG_LIBGADU_VERSION "1.9.0" /** * Poziom rejestracji informacji odpluskwiających. Zmienna jest maską bitową * składającą się ze stałych \c GG_DEBUG_... * * \ingroup debug */ int gg_debug_level = 0; @@ -175,17 +175,17 @@ char *gg_proxy_password = NULL; #ifndef DOXYGEN #ifndef lint static char rcsid[] #ifdef __GNUC__ __attribute__ ((unused)) #endif -= "$Id: libgadu.c 878 2009-11-16 23:48:19Z wojtekka $"; += "$Id: libgadu.c 923 2010-03-09 20:03:29Z wojtekka $"; #endif #endif /* DOXYGEN */ /** * Zwraca wersję biblioteki. * * \return Wskaźnik na statyczny bufor z wersją biblioteki. @@ -1311,51 +1311,75 @@ static void gg_append(char *dst, int *po *pos += len; } /** * \internal Zamienia tekst z formatowaniem Gadu-Gadu na HTML. * * \param dst Bufor wynikowy (może być \c NULL) - * \param utf_msg Tekst źródłowy + * \param src Tekst źródłowy w UTF-8 * \param format Atrybuty tekstu źródłowego * \param format_len Długość bloku atrybutów tekstu źródłowego * + * \note Wynikowy tekst nie jest idealnym kodem HTML, ponieważ ma jak + * dokładniej odzwierciedlać to, co wygenerowałby oryginalny klient. + * * \note Dokleja \c \\0 na końcu bufora wynikowego. * * \return Długość tekstu wynikowego bez \c \\0 (nawet jeśli \c dst to \c NULL). */ -static int gg_convert_to_html(char *dst, const char *utf_msg, const unsigned char *format, int format_len) +static int gg_convert_to_html(char *dst, const char *src, const unsigned char *format, int format_len) { const char span_fmt[] = ""; const int span_len = 75; - const char img_fmt[] = ""; - const int img_len = 28; + const char img_fmt[] = ""; + const int img_len = 29; int char_pos = 0; - int format_idx = 3; + int format_idx = 0; unsigned char old_attr = 0; const unsigned char *color = (const unsigned char*) "\x00\x00\x00"; int len, i; len = 0; - for (i = 0; utf_msg[i] != 0; i++) { - unsigned char attr; - int attr_pos; + /* Nie mamy atrybutów dla pierwsze znaku, a tekst nie jest pusty, więc + * tak czy inaczej trzeba otworzyć . */ - if (format_idx + 3 <= format_len) { + if (src[0] != 0 && (format_idx + 3 > format_len || (format[format_idx] | (format[format_idx + 1] << 8)) != 0)) { + if (dst != NULL) + sprintf(&dst[len], span_fmt, 0, 0, 0); + + len += span_len; + } + + /* Pętla przechodzi też przez kończące \0, żeby móc dokleić obrazek + * na końcu tekstu. */ + + for (i = 0; ; i++) { + /* Analizuj atrybuty tak długo jak dotyczą aktualnego znaku. */ + for (;;) { + unsigned char attr; + int attr_pos; + + if (format_idx + 3 > format_len) + break; + attr_pos = format[format_idx] | (format[format_idx + 1] << 8); + + if (attr_pos != char_pos) + break; + attr = format[format_idx + 2]; - } else { - attr_pos = -1; - attr = 0; - } - if (attr_pos == char_pos) { + /* Nie doklejaj atrybutów na końcu, co najwyżej obrazki. */ + + if (src[i] == 0) + attr &= ~(GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR); + format_idx += 3; if ((attr & (GG_FONT_BOLD | GG_FONT_ITALIC | GG_FONT_UNDERLINE | GG_FONT_COLOR)) != 0) { if (char_pos != 0) { if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "", 4); if ((old_attr & GG_FONT_ITALIC) != 0) @@ -1372,17 +1396,17 @@ static int gg_convert_to_html(char *dst, format_idx += 3; } else { color = (const unsigned char*) "\x00\x00\x00"; } if (dst != NULL) sprintf(&dst[len], span_fmt, color[0], color[1], color[2]); len += span_len; - } else if (char_pos == 0) { + } else if (char_pos == 0 && src[0] != 0) { if (dst != NULL) sprintf(&dst[len], span_fmt, 0, 0, 0); len += span_len; } if ((attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "", 3); @@ -1405,24 +1429,21 @@ static int gg_convert_to_html(char *dst, format[format_idx + 2]); } len += img_len; format_idx += 10; } old_attr = attr; - } else if (i == 0) { - if (dst != NULL) - sprintf(&dst[len], span_fmt, 0, 0, 0); - - len += span_len; } - switch (utf_msg[i]) { + /* Doklej znak zachowując htmlowe escapowanie. */ + + switch (src[i]) { case '&': gg_append(dst, &len, "&", 5); break; case '<': gg_append(dst, &len, "<", 4); break; case '>': gg_append(dst, &len, ">", 4); @@ -1432,48 +1453,46 @@ static int gg_convert_to_html(char *dst, break; case '\"': gg_append(dst, &len, """, 6); break; case '\n': gg_append(dst, &len, "
", 4); break; case '\r': + case 0: break; default: if (dst != NULL) - dst[len] = utf_msg[i]; + dst[len] = src[i]; len++; } /* Sprawdź, czy bajt nie jest kontynuacją znaku unikodowego. */ - if ((utf_msg[i] & 0xc0) != 0xc0) + if ((src[i] & 0xc0) != 0xc0) char_pos++; + + if (src[i] == 0) + break; } + /* Zamknij tagi. */ + if ((old_attr & GG_FONT_UNDERLINE) != 0) gg_append(dst, &len, "", 4); if ((old_attr & GG_FONT_ITALIC) != 0) gg_append(dst, &len, "", 4); if ((old_attr & GG_FONT_BOLD) != 0) gg_append(dst, &len, "
", 4); - /* Dla pustych tekstów dodaj pusty . */ - - if (i == 0) { - if (dst != NULL) - sprintf(&dst[len], span_fmt, 0, 0, 0); - - len += span_len; - } - - gg_append(dst, &len, "", 7); + if (src[0] != 0) + gg_append(dst, &len, "
", 7); if (dst != NULL) dst[len] = 0; return len; } /** @@ -1559,26 +1578,26 @@ int gg_send_message_confer_richtext(stru sess->seq = seq_no; if (format == NULL || formatlen < 3) { format = (unsigned char*) "\x02\x06\x00\x00\x00\x08\x00\x00\x00"; formatlen = 9; } - len = gg_convert_to_html(NULL, utf_msg, format, formatlen); + len = gg_convert_to_html(NULL, utf_msg, format + 3, formatlen - 3); html_msg = malloc(len + 1); if (html_msg == NULL) { seq_no = -1; goto cleanup; } - gg_convert_to_html(html_msg, utf_msg, format, formatlen); + gg_convert_to_html(html_msg, utf_msg, format + 3, formatlen - 3); s80.seq = gg_fix32(seq_no); s80.msgclass = gg_fix32(msgclass); s80.offset_plain = gg_fix32(sizeof(s80) + strlen(html_msg) + 1); s80.offset_attr = gg_fix32(sizeof(s80) + strlen(html_msg) + 1 + strlen(cp_msg) + 1); } if (recipients_count > 1) { diff --git a/purple/libpurple/protocols/gg/lib/libgadu.h b/purple/libpurple/protocols/gg/lib/libgadu.h --- a/purple/libpurple/protocols/gg/lib/libgadu.h +++ b/purple/libpurple/protocols/gg/lib/libgadu.h @@ -862,16 +862,23 @@ struct gg_event_xml_event { * Opis zdarzenia \c GG_EVENT_DCC7_CONNECTED. */ struct gg_event_dcc7_connected { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ // XXX czy coś się przyda? }; /** + * Opis zdarzenia \c GG_EVENT_DCC7_PENDING. + */ +struct gg_event_dcc7_pending { + struct gg_dcc7 *dcc7; /**< Struktura połączenia */ +}; + +/** * Opis zdarzenia \c GG_EVENT_DCC7_REJECT. */ struct gg_event_dcc7_reject { struct gg_dcc7 *dcc7; /**< Struktura połączenia */ int reason; /**< powód odrzucenia */ }; /** @@ -905,16 +912,17 @@ union gg_event_union { gg_pubdir50_t pubdir50; /**< Odpowiedź katalogu publicznego (\c GG_EVENT_PUBDIR50_*) */ struct gg_event_xml_event xml_event; /**< Zdarzenie systemowe (\c GG_EVENT_XML_EVENT) */ struct gg_dcc *dcc_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC_NEW) */ enum gg_error_t dcc_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC_ERROR) */ struct gg_event_dcc_voice_data dcc_voice_data; /**< Dane połączenia głosowego (\c GG_EVENT_DCC_VOICE_DATA) */ struct gg_dcc7 *dcc7_new; /**< Nowe połączenie bezpośrednie (\c GG_EVENT_DCC7_NEW) */ enum gg_error_t dcc7_error; /**< Błąd połączenia bezpośredniego (\c GG_EVENT_DCC7_ERROR) */ struct gg_event_dcc7_connected dcc7_connected; /**< Informacja o zestawieniu połączenia bezpośredniego (\c GG_EVENT_DCC7_CONNECTED) */ + struct gg_event_dcc7_pending dcc7_pending; /**< Trwa próba połączenia bezpośredniego (\c GG_EVENT_DCC7_PENDING) */ struct gg_event_dcc7_reject dcc7_reject; /**< Odrzucono połączenia bezpośredniego (\c GG_EVENT_DCC7_REJECT) */ struct gg_event_dcc7_accept dcc7_accept; /**< Zaakceptowano połączenie bezpośrednie (\c GG_EVENT_DCC7_ACCEPT) */ }; /** * Opis zdarzenia. * * Zwracany przez funkcje \c gg_watch_fd(), \c gg_dcc_watch_fd() diff --git a/purple/libpurple/protocols/irc/msgs.c b/purple/libpurple/protocols/irc/msgs.c --- a/purple/libpurple/protocols/irc/msgs.c +++ b/purple/libpurple/protocols/irc/msgs.c @@ -600,29 +600,35 @@ void irc_msg_names(struct irc_conn *irc, void irc_msg_motd(struct irc_conn *irc, const char *name, const char *from, char **args) { char *escaped; if (!args || !args[0]) return; - if (!irc->motd) - irc->motd = g_string_new(""); - if (!strcmp(name, "375")) { if (irc->motd) g_string_free(irc->motd, TRUE); irc->motd = g_string_new(""); return; } else if (!strcmp(name, "376")) { /* dircproxy 1.0.5 does not send 251 on reconnection, so * finalize the connection here if it is not already done. */ irc_connected(irc, args[0]); return; + } else if (!strcmp(name, "422")) { + /* in case there is no 251, and no MOTD set, finalize the connection. + * (and clear the motd for good measure). */ + + if (irc->motd) + g_string_free(irc->motd, TRUE); + + irc_connected(irc, args[0]); + return; } if (!irc->motd) { purple_debug_error("irc", "IRC server sent MOTD without STARTMOTD\n"); return; } if (!args[1]) diff --git a/purple/libpurple/protocols/irc/parse.c b/purple/libpurple/protocols/irc/parse.c --- a/purple/libpurple/protocols/irc/parse.c +++ b/purple/libpurple/protocols/irc/parse.c @@ -81,16 +81,17 @@ static struct _irc_msg { { "375", "n:", irc_msg_motd }, /* Start MOTD */ { "376", "n:", irc_msg_motd }, /* End of MOTD */ { "391", "nv:", irc_msg_time }, /* Time reply */ { "401", "nt:", irc_msg_nonick }, /* No such nick/chan */ { "406", "nt:", irc_msg_nonick }, /* No such nick for WHOWAS */ { "403", "nc:", irc_msg_nochan }, /* No such channel */ { "404", "nt:", irc_msg_nosend }, /* Cannot send to chan */ { "421", "nv:", irc_msg_unknown }, /* Unknown command */ + { "422", "n:", irc_msg_motd }, /* MOTD file missing */ { "432", "vn:", irc_msg_badnick }, /* Erroneous nickname */ { "433", "vn:", irc_msg_nickused }, /* Nickname already in use */ { "437", "nc:", irc_msg_unavailable }, /* Nick/channel is unavailable */ { "438", "nn:", irc_msg_nochangenick }, /* Nick may not change */ { "442", "nc:", irc_msg_notinchan }, /* Not in channel */ { "473", "nc:", irc_msg_inviteonly }, /* Tried to join invite-only */ { "474", "nc:", irc_msg_banned }, /* Banned from channel */ { "477", "nc:", irc_msg_regonly }, /* Registration Required */ diff --git a/purple/libpurple/protocols/jabber/Makefile.in b/purple/libpurple/protocols/jabber/Makefile.in --- a/purple/libpurple/protocols/jabber/Makefile.in +++ b/purple/libpurple/protocols/jabber/Makefile.in @@ -53,17 +53,23 @@ CSRCS = \ auth_digest_md5.c \ auth_plain.c \ auth_scram.c \ buddy.c \ bosh.c \ chat.c \ disco.c \ data.c \ - google.c \ + google/gmail.c \ + google/google.c \ + google/google_presence.c \ + google/google_roster.c \ + google/google_session.c \ + google/jingleinfo.c \ + google/relay.c \ ibb.c \ iq.c \ jabber.c \ jutil.c \ message.c \ oob.c \ parser.c \ ping.c \ @@ -77,8 +83,11 @@ CSRCS = \ useravatar.c \ usermood.c \ usernick.c \ usertune.c \ libxmpp.c \ $(NULL) include $(srcdir)/../prpl-rules.mk + +export:: + mkdir -p google diff --git a/purple/libpurple/protocols/jabber/adhoccommands.c b/purple/libpurple/protocols/jabber/adhoccommands.c --- a/purple/libpurple/protocols/jabber/adhoccommands.c +++ b/purple/libpurple/protocols/jabber/adhoccommands.c @@ -120,17 +120,18 @@ static void do_adhoc_action_cb(JabberStr xmlnode_set_attrib(iq->node, "to", actionInfo->who); command = xmlnode_new_child(iq->node,"command"); xmlnode_set_namespace(command,"http://jabber.org/protocol/commands"); xmlnode_set_attrib(command,"sessionid",actionInfo->sessionid); xmlnode_set_attrib(command,"node",actionInfo->node); /* cancel is handled differently on ad-hoc commands than regular forms */ - if(!strcmp(xmlnode_get_namespace(result),"jabber:x:data") && !strcmp(xmlnode_get_attrib(result, "type"),"cancel")) { + if (purple_strequal(xmlnode_get_namespace(result), "jabber:x:data") && + purple_strequal(xmlnode_get_attrib(result, "type"), "cancel")) { xmlnode_set_attrib(command,"action","cancel"); } else { if(actionhandle) xmlnode_set_attrib(command,"action",actionhandle); xmlnode_insert_child(command,result); } for(action = actionInfo->actionslist; action; action = g_list_next(action)) { diff --git a/purple/libpurple/protocols/jabber/auth.c b/purple/libpurple/protocols/jabber/auth.c --- a/purple/libpurple/protocols/jabber/auth.c +++ b/purple/libpurple/protocols/jabber/auth.c @@ -118,17 +118,17 @@ auth_old_pass_cb(PurpleConnection *gc, P static void auth_no_pass_cb(PurpleConnection *gc, PurpleRequestFields *fields) { /* The password prompt dialog doesn't get disposed if the account disconnects */ if (!PURPLE_CONNECTION_IS_VALID(gc)) return; - /* Disable the account as the user has canceled connecting */ + /* Disable the account as the user has cancelled connecting */ purple_account_set_enabled(purple_connection_get_account(gc), purple_core_get_ui(), FALSE); } #endif void jabber_auth_start(JabberStream *js, xmlnode *packet) { GSList *mechanisms = NULL; @@ -246,17 +246,18 @@ static void auth_old_cb(JabberStream *js if (type == JABBER_IQ_ERROR) { PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR; char *msg = jabber_parse_error(js, packet, &reason); purple_connection_error_reason(js->gc, reason, msg); g_free(msg); } else if (type == JABBER_IQ_RESULT) { query = xmlnode_get_child(packet, "query"); - if(js->stream_id && xmlnode_get_child(query, "digest")) { + if (js->stream_id && *js->stream_id && + xmlnode_get_child(query, "digest")) { char *s, *hash; iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:auth"); query = xmlnode_get_child(iq->node, "query"); x = xmlnode_new_child(query, "username"); xmlnode_insert_data(x, js->user->node, -1); x = xmlnode_new_child(query, "resource"); xmlnode_insert_data(x, js->user->resource, -1); @@ -264,18 +265,20 @@ static void auth_old_cb(JabberStream *js x = xmlnode_new_child(query, "digest"); s = g_strdup_printf("%s%s", js->stream_id, pw); hash = jabber_calculate_data_hash(s, strlen(s), "sha1"); xmlnode_insert_data(x, hash, -1); g_free(hash); g_free(s); jabber_iq_set_callback(iq, auth_old_result_cb, NULL); jabber_iq_send(iq); - - } else if(js->stream_id && (x = xmlnode_get_child(query, "crammd5"))) { + } else if ((x = xmlnode_get_child(query, "crammd5"))) { + /* For future reference, this appears to be a custom OS X extension + * to non-SASL authentication. + */ const char *challenge; gchar digest[33]; PurpleCipherContext *hmac; /* Calculate the MHAC-MD5 digest */ challenge = xmlnode_get_attrib(x, "challenge"); hmac = purple_cipher_context_new_by_name("hmac", NULL); purple_cipher_context_set_option(hmac, "hash", "md5"); @@ -335,17 +338,18 @@ void jabber_auth_start_old(JabberStream account = purple_connection_get_account(js->gc); /* * We can end up here without encryption if the server doesn't support * and we're not using old-style SSL. If the user * is requiring SSL/TLS, we need to enforce it. */ if (!jabber_stream_is_ssl(js) && - purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + g_str_equal("require_tls", + purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); return; } if (js->registration) { jabber_register_start(js); @@ -493,29 +497,39 @@ static gint compare_mech(gconstpointer a if (mech_a->priority > mech_b->priority) return -1; else if (mech_a->priority < mech_b->priority) return 1; /* This really shouldn't happen */ return 0; } +void jabber_auth_add_mech(JabberSaslMech *mech) +{ + auth_mechs = g_slist_insert_sorted(auth_mechs, mech, compare_mech); +} + +void jabber_auth_remove_mech(JabberSaslMech *mech) +{ + auth_mechs = g_slist_remove(auth_mechs, mech); +} + void jabber_auth_init(void) { JabberSaslMech **tmp; gint count, i; - auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_plain_mech(), compare_mech); - auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_digest_md5_mech(), compare_mech); + jabber_auth_add_mech(jabber_auth_get_plain_mech()); + jabber_auth_add_mech(jabber_auth_get_digest_md5_mech()); #ifdef HAVE_CYRUS_SASL - auth_mechs = g_slist_insert_sorted(auth_mechs, jabber_auth_get_cyrus_mech(), compare_mech); + jabber_auth_add_mech(jabber_auth_get_cyrus_mech()); #endif tmp = jabber_auth_get_scram_mechs(&count); for (i = 0; i < count; ++i) - auth_mechs = g_slist_insert_sorted(auth_mechs, tmp[i], compare_mech); + jabber_auth_add_mech(tmp[i]); } void jabber_auth_uninit(void) { g_slist_free(auth_mechs); auth_mechs = NULL; } diff --git a/purple/libpurple/protocols/jabber/auth.h b/purple/libpurple/protocols/jabber/auth.h --- a/purple/libpurple/protocols/jabber/auth.h +++ b/purple/libpurple/protocols/jabber/auth.h @@ -53,12 +53,15 @@ void jabber_auth_handle_failure(JabberSt JabberSaslMech *jabber_auth_get_plain_mech(void); JabberSaslMech *jabber_auth_get_digest_md5_mech(void); JabberSaslMech **jabber_auth_get_scram_mechs(gint *count); #ifdef HAVE_CYRUS_SASL JabberSaslMech *jabber_auth_get_cyrus_mech(void); #endif +void jabber_auth_add_mech(JabberSaslMech *); +void jabber_auth_remove_mech(JabberSaslMech *); + void jabber_auth_init(void); void jabber_auth_uninit(void); #endif /* PURPLE_JABBER_AUTH_H_ */ diff --git a/purple/libpurple/protocols/jabber/auth_digest_md5.c b/purple/libpurple/protocols/jabber/auth_digest_md5.c --- a/purple/libpurple/protocols/jabber/auth_digest_md5.c +++ b/purple/libpurple/protocols/jabber/auth_digest_md5.c @@ -177,17 +177,19 @@ digest_md5_handle_challenge(JabberStream if (!enc_in) { *msg = g_strdup(_("Invalid response from server")); return JABBER_SASL_STATE_FAIL; } dec_in = (char *)purple_base64_decode(enc_in, NULL); purple_debug_misc("jabber", "decoded challenge (%" - G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); + G_GSIZE_FORMAT "): %s\n", + dec_in != NULL ? strlen(dec_in) : 0, + dec_in != NULL ? dec_in : "(null)"); parts = parse_challenge(dec_in); if (g_hash_table_lookup(parts, "rspauth")) { char *rspauth = g_hash_table_lookup(parts, "rspauth"); char *expected_rspauth = js->auth_mech_data; if (rspauth && purple_strequal(rspauth, expected_rspauth)) { diff --git a/purple/libpurple/protocols/jabber/auth_plain.c b/purple/libpurple/protocols/jabber/auth_plain.c --- a/purple/libpurple/protocols/jabber/auth_plain.c +++ b/purple/libpurple/protocols/jabber/auth_plain.c @@ -35,21 +35,18 @@ static xmlnode *finish_plaintext_authent { xmlnode *auth; GString *response; gchar *enc_out; auth = xmlnode_new("auth"); xmlnode_set_namespace(auth, NS_XMPP_SASL); - if (g_str_equal(js->user->domain, "gmail.com") || - g_str_equal(js->user->domain, "googlemail.com")) { - xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); - xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); - } + xmlnode_set_attrib(auth, "xmlns:ga", "http://www.google.com/talk/protocol/auth"); + xmlnode_set_attrib(auth, "ga:client-uses-full-bind-result", "true"); response = g_string_new(""); response = g_string_append_c(response, '\0'); response = g_string_append(response, js->user->node); response = g_string_append_c(response, '\0'); response = g_string_append(response, purple_connection_get_password(js->gc)); diff --git a/purple/libpurple/protocols/jabber/bosh.c b/purple/libpurple/protocols/jabber/bosh.c --- a/purple/libpurple/protocols/jabber/bosh.c +++ b/purple/libpurple/protocols/jabber/bosh.c @@ -706,21 +706,20 @@ jabber_bosh_http_connection_process(Purp if (!conn->headers_done) { const char *content_length = purple_strcasestr(cursor, "\r\nContent-Length"); const char *end_of_headers = strstr(cursor, "\r\n\r\n"); /* Make sure Content-Length is in headers, not body */ if (content_length && (!end_of_headers || content_length < end_of_headers)) { const char *sep; - const char *eol; int len; if ((sep = strstr(content_length, ": ")) == NULL || - (eol = strstr(sep, "\r\n")) == NULL) + strstr(sep, "\r\n") == NULL) /* * The packet ends in the middle of the Content-Length line. * We'll try again later when we have more. */ return; len = atoi(sep + 2); if (len == 0) diff --git a/purple/libpurple/protocols/jabber/buddy.c b/purple/libpurple/protocols/jabber/buddy.c --- a/purple/libpurple/protocols/jabber/buddy.c +++ b/purple/libpurple/protocols/jabber/buddy.c @@ -33,17 +33,17 @@ #include "chat.h" #include "jabber.h" #include "iq.h" #include "presence.h" #include "useravatar.h" #include "xdata.h" #include "pep.h" #include "adhoccommands.h" -#include "google.h" +#include "google/google.h" typedef struct { long idle_seconds; } JabberBuddyInfoResource; typedef struct { JabberStream *js; JabberBuddy *jb; diff --git a/purple/libpurple/protocols/jabber/disco.c b/purple/libpurple/protocols/jabber/disco.c --- a/purple/libpurple/protocols/jabber/disco.c +++ b/purple/libpurple/protocols/jabber/disco.c @@ -25,17 +25,19 @@ #include "network.h" #include "prefs.h" #include "debug.h" #include "request.h" #include "adhoccommands.h" #include "buddy.h" #include "disco.h" -#include "google.h" +#include "google/google.h" +#include "google/gmail.h" +#include "google/jingleinfo.h" #include "iq.h" #include "jabber.h" #ifdef USE_JINGLE #include "jingle/jingle.h" #endif #include "pep.h" #include "presence.h" #include "roster.h" @@ -253,17 +255,17 @@ static void jabber_disco_info_cb(JabberS if(!strcmp(category, "conference") && !strcmp(type, "text")) { /* we found a groupchat or MUC server, add it to the list */ /* XXX: actually check for protocol/muc or gc-1.0 support */ js->chat_servers = g_list_prepend(js->chat_servers, g_strdup(from)); } else if(!strcmp(category, "directory") && !strcmp(type, "user")) { /* we found a JUD */ js->user_directories = g_list_prepend(js->user_directories, g_strdup(from)); - } else if(!strcmp(category, "proxy") && !strcmp(type, NS_BYTESTREAMS)) { + } else if(!strcmp(category, "proxy") && !strcmp(type, "bytestreams")) { /* This is a bytestream proxy */ JabberIq *iq; JabberBytestreamsStreamhost *sh; purple_debug_info("jabber", "Found bytestream proxy server: %s\n", from); sh = g_new0(JabberBytestreamsStreamhost, 1); sh->jid = g_strdup(from); @@ -594,24 +596,24 @@ jabber_disco_server_items_result_cb(Jabb js->chat_servers = g_list_delete_link(js->chat_servers, js->chat_servers); } query = xmlnode_get_child(packet, "query"); for(child = xmlnode_get_child(query, "item"); child; child = xmlnode_get_next_twin(child)) { JabberIq *iq; - const char *jid, *node; + const char *jid; if(!(jid = xmlnode_get_attrib(child, "jid"))) continue; /* we don't actually care about the specific nodes, * so we won't query them */ - if((node = xmlnode_get_attrib(child, "node"))) + if(xmlnode_get_attrib(child, "node") != NULL) continue; iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_DISCO_INFO); xmlnode_set_attrib(iq->node, "to", jid); jabber_iq_set_callback(iq, jabber_disco_info_cb, NULL); jabber_iq_send(iq); } } diff --git a/purple/libpurple/protocols/jabber/google/gmail.c b/purple/libpurple/protocols/jabber/google/gmail.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/gmail.c @@ -0,0 +1,207 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" +#include "jabber.h" +#include "gmail.h" + +static void +jabber_gmail_parse(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer nul) +{ + xmlnode *child; + xmlnode *message; + const char *to, *url; + const char *in_str; + char *to_name; + + int i, count = 1, returned_count; + + const char **tos, **froms, **urls; + char **subjects; + + if (type == JABBER_IQ_ERROR) + return; + + child = xmlnode_get_child(packet, "mailbox"); + if (!child) + return; + + in_str = xmlnode_get_attrib(child, "total-matched"); + if (in_str && *in_str) + count = atoi(in_str); + + /* If Gmail doesn't tell us who the mail is to, let's use our JID */ + to = xmlnode_get_attrib(packet, "to"); + + message = xmlnode_get_child(child, "mail-thread-info"); + + if (count == 0 || !message) { + if (count > 0) { + char *bare_jid = jabber_get_bare_jid(to); + const char *default_tos[2] = { bare_jid }; + + purple_notify_emails(js->gc, count, FALSE, NULL, NULL, default_tos, NULL, NULL, NULL); + g_free(bare_jid); + } else { + purple_notify_emails(js->gc, count, FALSE, NULL, NULL, NULL, NULL, NULL, NULL); + } + + return; + } + + /* Loop once to see how many messages were returned so we can allocate arrays + * accordingly */ + for (returned_count = 0; message; returned_count++, message=xmlnode_get_next_twin(message)); + + froms = g_new0(const char* , returned_count + 1); + tos = g_new0(const char* , returned_count + 1); + subjects = g_new0(char* , returned_count + 1); + urls = g_new0(const char* , returned_count + 1); + + to = xmlnode_get_attrib(packet, "to"); + to_name = jabber_get_bare_jid(to); + url = xmlnode_get_attrib(child, "url"); + if (!url || !*url) + url = "http://www.gmail.com"; + + message= xmlnode_get_child(child, "mail-thread-info"); + for (i=0; message; message = xmlnode_get_next_twin(message), i++) { + xmlnode *sender_node, *subject_node; + const char *from, *tid; + char *subject; + + subject_node = xmlnode_get_child(message, "subject"); + sender_node = xmlnode_get_child(message, "senders"); + sender_node = xmlnode_get_child(sender_node, "sender"); + + while (sender_node && (!xmlnode_get_attrib(sender_node, "unread") || + !strcmp(xmlnode_get_attrib(sender_node, "unread"),"0"))) + sender_node = xmlnode_get_next_twin(sender_node); + + if (!sender_node) { + i--; + continue; + } + + from = xmlnode_get_attrib(sender_node, "name"); + if (!from || !*from) + from = xmlnode_get_attrib(sender_node, "address"); + subject = xmlnode_get_data(subject_node); + /* + * url = xmlnode_get_attrib(message, "url"); + */ + tos[i] = (to_name != NULL ? to_name : ""); + froms[i] = (from != NULL ? from : ""); + subjects[i] = (subject != NULL ? subject : g_strdup("")); + urls[i] = url; + + tid = xmlnode_get_attrib(message, "tid"); + if (tid && + (js->gmail_last_tid == NULL || strcmp(tid, js->gmail_last_tid) > 0)) { + g_free(js->gmail_last_tid); + js->gmail_last_tid = g_strdup(tid); + } + } + + if (i>0) + purple_notify_emails(js->gc, count, count == i, (const char**) subjects, froms, tos, + urls, NULL, NULL); + + g_free(to_name); + g_free(tos); + g_free(froms); + for (i = 0; i < returned_count; i++) + g_free(subjects[i]); + g_free(subjects); + g_free(urls); + + in_str = xmlnode_get_attrib(child, "result-time"); + if (in_str && *in_str) { + g_free(js->gmail_last_time); + js->gmail_last_time = g_strdup(in_str); + } +} + +void +jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, + const char *id, xmlnode *new_mail) +{ + xmlnode *query; + JabberIq *iq; + + /* bail if the user isn't interested */ + if (!purple_account_get_check_mail(js->gc->account)) + return; + + /* Is this an initial incoming mail notification? If so, send a request for more info */ + if (type != JABBER_IQ_SET) + return; + + /* Acknowledge the notification */ + iq = jabber_iq_new(js, JABBER_IQ_RESULT); + if (from) + xmlnode_set_attrib(iq->node, "to", from); + xmlnode_set_attrib(iq->node, "id", id); + jabber_iq_send(iq); + + purple_debug_misc("jabber", + "Got new mail notification. Sending request for more info\n"); + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_MAIL_NOTIFY); + jabber_iq_set_callback(iq, jabber_gmail_parse, NULL); + query = xmlnode_get_child(iq->node, "query"); + + if (js->gmail_last_time) + xmlnode_set_attrib(query, "newer-than-time", js->gmail_last_time); + if (js->gmail_last_tid) + xmlnode_set_attrib(query, "newer-than-tid", js->gmail_last_tid); + + jabber_iq_send(iq); + return; +} + +void jabber_gmail_init(JabberStream *js) { + JabberIq *iq; + xmlnode *usersetting, *mailnotifications; + + if (!purple_account_get_check_mail(purple_connection_get_account(js->gc))) + return; + + /* + * Quoting http://code.google.com/apis/talk/jep_extensions/usersettings.html: + * To ensure better compatibility with other clients, rather than + * setting this value to "false" to turn off notifications, it is + * recommended that a client set this to "true" and filter incoming + * email notifications itself. + */ + iq = jabber_iq_new(js, JABBER_IQ_SET); + usersetting = xmlnode_new_child(iq->node, "usersetting"); + xmlnode_set_namespace(usersetting, "google:setting"); + mailnotifications = xmlnode_new_child(usersetting, "mailnotifications"); + xmlnode_set_attrib(mailnotifications, "value", "true"); + jabber_iq_send(iq); + + iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_MAIL_NOTIFY); + jabber_iq_set_callback(iq, jabber_gmail_parse, NULL); + jabber_iq_send(iq); +} diff --git a/purple/libpurple/protocols/jabber/google.h b/purple/libpurple/protocols/jabber/google/gmail.h rename from purple/libpurple/protocols/jabber/google.h rename to purple/libpurple/protocols/jabber/google/gmail.h --- a/purple/libpurple/protocols/jabber/google.h +++ b/purple/libpurple/protocols/jabber/google/gmail.h @@ -13,48 +13,18 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef PURPLE_JABBER_GOOGLE_H_ -#define PURPLE_JABBER_GOOGLE_H_ - -/* This is a place for Google Talk-specific XMPP extensions to live - * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ +#ifndef PURPLE_JABBER_GOOGLE_GMAIL_H_ +#define PURPLE_JABBER_GOOGLE_GMAIL_H_ #include "jabber.h" -#define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com" - void jabber_gmail_init(JabberStream *js); void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *new_mail); -void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); - -/* Returns FALSE if this should short-circuit processing of this roster item, or TRUE - * if this roster item should continue to be processed - */ -gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); - -void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); -char *jabber_google_presence_outgoing(PurpleStatus *tune); - -void jabber_google_roster_add_deny(JabberStream *js, const char *who); -void jabber_google_roster_rem_deny(JabberStream *js, const char *who); - -char *jabber_google_format_to_html(const char *text); - -gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); -void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); - -void jabber_google_handle_jingle_info(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *child); -void jabber_google_send_jingle_info(JabberStream *js); - -void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); - -#endif /* PURPLE_JABBER_GOOGLE_H_ */ +#endif /* PURPLE_JABBER_GOOGLE_GMAIL_H_ */ diff --git a/purple/libpurple/protocols/jabber/google/google.c b/purple/libpurple/protocols/jabber/google/google.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/google.c @@ -0,0 +1,172 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" + +#include "google.h" +#include "jabber.h" +#include "chat.h" + +/* This does two passes on the string. The first pass goes through + * and determine if all the structured text is properly balanced, and + * how many instances of each there is. The second pass goes and converts + * everything to HTML, depending on what's figured out by the first pass. + * It will short circuit once it knows it has no more replacements to make + */ +char *jabber_google_format_to_html(const char *text) +{ + const char *p; + + /* The start of the screen may be consdiered a space for this purpose */ + gboolean preceding_space = TRUE; + + gboolean in_bold = FALSE, in_italic = FALSE; + gboolean in_tag = FALSE; + + gint bold_count = 0, italic_count = 0; + + GString *str; + + for (p = text; *p != '\0'; p = g_utf8_next_char(p)) { + gunichar c = g_utf8_get_char(p); + if (c == '*' && !in_tag) { + if (in_bold && (g_unichar_isspace(*(p+1)) || + *(p+1) == '\0' || + *(p+1) == '<')) { + bold_count++; + in_bold = FALSE; + } else if (preceding_space && !in_bold && !g_unichar_isspace(*(p+1))) { + bold_count++; + in_bold = TRUE; + } + preceding_space = TRUE; + } else if (c == '_' && !in_tag) { + if (in_italic && (g_unichar_isspace(*(p+1)) || + *(p+1) == '\0' || + *(p+1) == '<')) { + italic_count++; + in_italic = FALSE; + } else if (preceding_space && !in_italic && !g_unichar_isspace(*(p+1))) { + italic_count++; + in_italic = TRUE; + } + preceding_space = TRUE; + } else if (c == '<' && !in_tag) { + in_tag = TRUE; + } else if (c == '>' && in_tag) { + in_tag = FALSE; + } else if (!in_tag) { + if (g_unichar_isspace(c)) + preceding_space = TRUE; + else + preceding_space = FALSE; + } + } + + str = g_string_new(NULL); + in_bold = in_italic = in_tag = FALSE; + preceding_space = TRUE; + + for (p = text; *p != '\0'; p = g_utf8_next_char(p)) { + gunichar c = g_utf8_get_char(p); + + if (bold_count < 2 && italic_count < 2 && !in_bold && !in_italic) { + g_string_append(str, p); + return g_string_free(str, FALSE); + } + + + if (c == '*' && !in_tag) { + if (in_bold && + (g_unichar_isspace(*(p+1))||*(p+1)=='<')) { /* This is safe in UTF-8 */ + str = g_string_append(str, ""); + in_bold = FALSE; + bold_count--; + } else if (preceding_space && bold_count > 1 && !g_unichar_isspace(*(p+1))) { + str = g_string_append(str, ""); + bold_count--; + in_bold = TRUE; + } else { + str = g_string_append_unichar(str, c); + } + preceding_space = TRUE; + } else if (c == '_' && !in_tag) { + if (in_italic && + (g_unichar_isspace(*(p+1))||*(p+1)=='<')) { + str = g_string_append(str, ""); + italic_count--; + in_italic = FALSE; + } else if (preceding_space && italic_count > 1 && !g_unichar_isspace(*(p+1))) { + str = g_string_append(str, ""); + italic_count--; + in_italic = TRUE; + } else { + str = g_string_append_unichar(str, c); + } + preceding_space = TRUE; + } else if (c == '<' && !in_tag) { + str = g_string_append_unichar(str, c); + in_tag = TRUE; + } else if (c == '>' && in_tag) { + str = g_string_append_unichar(str, c); + in_tag = FALSE; + } else if (!in_tag) { + str = g_string_append_unichar(str, c); + if (g_unichar_isspace(c)) + preceding_space = TRUE; + else + preceding_space = FALSE; + } else { + str = g_string_append_unichar(str, c); + } + } + return g_string_free(str, FALSE); +} + + + +void google_buddy_node_chat(PurpleBlistNode *node, gpointer data) +{ + PurpleBuddy *buddy; + PurpleConnection *gc; + JabberStream *js; + JabberChat *chat; + gchar *room; + gchar *uuid = purple_uuid_random(); + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = PURPLE_BUDDY(node); + gc = purple_account_get_connection(purple_buddy_get_account(buddy)); + g_return_if_fail(gc != NULL); + js = purple_connection_get_protocol_data(gc); + + room = g_strdup_printf("private-chat-%s", uuid); + chat = jabber_join_chat(js, room, GOOGLE_GROUPCHAT_SERVER, js->user->node, + NULL, NULL); + if (chat) { + chat->muc = TRUE; + jabber_chat_invite(gc, chat->id, "", purple_buddy_get_name(buddy)); + } + + g_free(room); + g_free(uuid); +} diff --git a/purple/libpurple/protocols/jabber/google.h b/purple/libpurple/protocols/jabber/google/google.h copy from purple/libpurple/protocols/jabber/google.h copy to purple/libpurple/protocols/jabber/google/google.h --- a/purple/libpurple/protocols/jabber/google.h +++ b/purple/libpurple/protocols/jabber/google/google.h @@ -23,38 +23,13 @@ /* This is a place for Google Talk-specific XMPP extensions to live * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ #include "jabber.h" #define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com" -void jabber_gmail_init(JabberStream *js); -void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, - const char *id, xmlnode *new_mail); - -void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); - -/* Returns FALSE if this should short-circuit processing of this roster item, or TRUE - * if this roster item should continue to be processed - */ -gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); - -void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); -char *jabber_google_presence_outgoing(PurpleStatus *tune); - -void jabber_google_roster_add_deny(JabberStream *js, const char *who); -void jabber_google_roster_rem_deny(JabberStream *js, const char *who); - char *jabber_google_format_to_html(const char *text); -gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); -void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); - -void jabber_google_handle_jingle_info(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *child); -void jabber_google_send_jingle_info(JabberStream *js); - void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); #endif /* PURPLE_JABBER_GOOGLE_H_ */ diff --git a/purple/libpurple/protocols/jabber/google/google_presence.c b/purple/libpurple/protocols/jabber/google/google_presence.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/google_presence.c @@ -0,0 +1,43 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" +#include "google_presence.h" + +void jabber_google_presence_incoming(JabberStream *js, const char *user, JabberBuddyResource *jbr) +{ + if (!js->googletalk) + return; + if (jbr->status && purple_str_has_prefix(jbr->status, "♫ ")) { + purple_prpl_got_user_status(js->gc->account, user, "tune", + PURPLE_TUNE_TITLE, jbr->status + strlen("♫ "), NULL); + g_free(jbr->status); + jbr->status = NULL; + } else { + purple_prpl_got_user_status_deactive(js->gc->account, user, "tune"); + } +} + +char *jabber_google_presence_outgoing(PurpleStatus *tune) +{ + const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); + return attr ? g_strdup_printf("♫ %s", attr) : g_strdup(""); +} diff --git a/purple/libpurple/protocols/jabber/google.h b/purple/libpurple/protocols/jabber/google/google_presence.h copy from purple/libpurple/protocols/jabber/google.h copy to purple/libpurple/protocols/jabber/google/google_presence.h --- a/purple/libpurple/protocols/jabber/google.h +++ b/purple/libpurple/protocols/jabber/google/google_presence.h @@ -13,48 +13,20 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef PURPLE_JABBER_GOOGLE_H_ -#define PURPLE_JABBER_GOOGLE_H_ - -/* This is a place for Google Talk-specific XMPP extensions to live - * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ +#ifndef PURPLE_JABBER_GOOGLE_PRESENCE_H_ +#define PURPLE_JABBER_GOOGLE_PRESENCE_H_ #include "jabber.h" - -#define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com" - -void jabber_gmail_init(JabberStream *js); -void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, - const char *id, xmlnode *new_mail); - -void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); - -/* Returns FALSE if this should short-circuit processing of this roster item, or TRUE - * if this roster item should continue to be processed - */ -gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); +#include "buddy.h" +#include "status.h" void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); char *jabber_google_presence_outgoing(PurpleStatus *tune); -void jabber_google_roster_add_deny(JabberStream *js, const char *who); -void jabber_google_roster_rem_deny(JabberStream *js, const char *who); -char *jabber_google_format_to_html(const char *text); - -gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); -void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); - -void jabber_google_handle_jingle_info(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *child); -void jabber_google_send_jingle_info(JabberStream *js); - -void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); - -#endif /* PURPLE_JABBER_GOOGLE_H_ */ +#endif /* PURPLE_JABBER_GOOGLE_PRESENCE_H_ */ \ No newline at end of file diff --git a/purple/libpurple/protocols/jabber/google/google_roster.c b/purple/libpurple/protocols/jabber/google/google_roster.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/google_roster.c @@ -0,0 +1,206 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "google_roster.h" +#include "jabber.h" +#include "presence.h" +#include "debug.h" +#include "xmlnode.h" + +void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item) +{ + PurpleAccount *account = purple_connection_get_account(js->gc); + GSList *list = account->deny; + const char *jid = xmlnode_get_attrib(item, "jid"); + char *jid_norm = (char *)jabber_normalize(account, jid); + + while (list) { + if (!strcmp(jid_norm, (char*)list->data)) { + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); + xmlnode_set_attrib(query, "gr:ext", "2"); + xmlnode_set_attrib(item, "gr:t", "B"); + return; + } + list = list->next; + } +} + +gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item) +{ + PurpleAccount *account = purple_connection_get_account(js->gc); + const char *jid = xmlnode_get_attrib(item, "jid"); + gboolean on_block_list = FALSE; + + char *jid_norm; + + const char *grt = xmlnode_get_attrib_with_namespace(item, "t", NS_GOOGLE_ROSTER); + const char *subscription = xmlnode_get_attrib(item, "subscription"); + const char *ask = xmlnode_get_attrib(item, "ask"); + + if ((!subscription || !strcmp(subscription, "none")) && !ask) { + /* The Google Talk servers will automatically add people from your Gmail address book + * with subscription=none. If we see someone with subscription=none, ignore them. + */ + return FALSE; + } + + jid_norm = g_strdup(jabber_normalize(account, jid)); + + on_block_list = NULL != g_slist_find_custom(account->deny, jid_norm, + (GCompareFunc)strcmp); + + if (grt && (*grt == 'H' || *grt == 'h')) { + /* Hidden; don't show this buddy. */ + GSList *buddies = purple_find_buddies(account, jid_norm); + if (buddies) + purple_debug_info("jabber", "Removing %s from local buddy list\n", + jid_norm); + + for ( ; buddies; buddies = g_slist_delete_link(buddies, buddies)) { + purple_blist_remove_buddy(buddies->data); + } + + g_free(jid_norm); + return FALSE; + } + + if (!on_block_list && (grt && (*grt == 'B' || *grt == 'b'))) { + purple_debug_info("jabber", "Blocking %s\n", jid_norm); + purple_privacy_deny_add(account, jid_norm, TRUE); + } else if (on_block_list && (!grt || (*grt != 'B' && *grt != 'b' ))){ + purple_debug_info("jabber", "Unblocking %s\n", jid_norm); + purple_privacy_deny_remove(account, jid_norm, TRUE); + } + + g_free(jid_norm); + return TRUE; +} + +void jabber_google_roster_add_deny(JabberStream *js, const char *who) +{ + PurpleAccount *account; + GSList *buddies; + JabberIq *iq; + xmlnode *query; + xmlnode *item; + xmlnode *group; + PurpleBuddy *b; + JabberBuddy *jb; + const char *balias; + + jb = jabber_buddy_find(js, who, TRUE); + + account = purple_connection_get_account(js->gc); + buddies = purple_find_buddies(account, who); + if(!buddies) + return; + + b = buddies->data; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); + + query = xmlnode_get_child(iq->node, "query"); + item = xmlnode_new_child(query, "item"); + + while(buddies) { + PurpleGroup *g; + + b = buddies->data; + g = purple_buddy_get_group(b); + + group = xmlnode_new_child(item, "group"); + xmlnode_insert_data(group, purple_group_get_name(g), -1); + + buddies = buddies->next; + } + + balias = purple_buddy_get_local_buddy_alias(b); + xmlnode_set_attrib(item, "jid", who); + xmlnode_set_attrib(item, "name", balias ? balias : ""); + xmlnode_set_attrib(item, "gr:t", "B"); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); + xmlnode_set_attrib(query, "gr:ext", "2"); + + jabber_iq_send(iq); + + /* Synthesize a sign-off */ + if (jb) { + JabberBuddyResource *jbr; + GList *l = jb->resources; + while (l) { + jbr = l->data; + if (jbr && jbr->name) + { + purple_debug_misc("jabber", "Removing resource %s\n", jbr->name); + jabber_buddy_remove_resource(jb, jbr->name); + } + l = l->next; + } + } + + purple_prpl_got_user_status(account, who, "offline", NULL); +} + +void jabber_google_roster_rem_deny(JabberStream *js, const char *who) +{ + GSList *buddies; + JabberIq *iq; + xmlnode *query; + xmlnode *item; + xmlnode *group; + PurpleBuddy *b; + const char *balias; + + buddies = purple_find_buddies(purple_connection_get_account(js->gc), who); + if(!buddies) + return; + + b = buddies->data; + + iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); + + query = xmlnode_get_child(iq->node, "query"); + item = xmlnode_new_child(query, "item"); + + while(buddies) { + PurpleGroup *g; + + b = buddies->data; + g = purple_buddy_get_group(b); + + group = xmlnode_new_child(item, "group"); + xmlnode_insert_data(group, purple_group_get_name(g), -1); + + buddies = buddies->next; + } + + balias = purple_buddy_get_local_buddy_alias(b); + xmlnode_set_attrib(item, "jid", who); + xmlnode_set_attrib(item, "name", balias ? balias : ""); + xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); + xmlnode_set_attrib(query, "gr:ext", "2"); + + jabber_iq_send(iq); + + /* See if he's online */ + jabber_presence_subscription_set(js, who, "probe"); +} + diff --git a/purple/libpurple/protocols/jabber/google.h b/purple/libpurple/protocols/jabber/google/google_roster.h copy from purple/libpurple/protocols/jabber/google.h copy to purple/libpurple/protocols/jabber/google/google_roster.h --- a/purple/libpurple/protocols/jabber/google.h +++ b/purple/libpurple/protocols/jabber/google/google_roster.h @@ -13,48 +13,25 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef PURPLE_JABBER_GOOGLE_H_ -#define PURPLE_JABBER_GOOGLE_H_ - -/* This is a place for Google Talk-specific XMPP extensions to live - * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ +#ifndef PURPLE_JABBER_GOOGLE_ROSTER_H_ +#define PURPLE_JABBER_GOOGLE_ROSTER_H_ #include "jabber.h" -#define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com" - -void jabber_gmail_init(JabberStream *js); -void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, - const char *id, xmlnode *new_mail); - void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); /* Returns FALSE if this should short-circuit processing of this roster item, or TRUE * if this roster item should continue to be processed */ gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); -void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); -char *jabber_google_presence_outgoing(PurpleStatus *tune); - void jabber_google_roster_add_deny(JabberStream *js, const char *who); void jabber_google_roster_rem_deny(JabberStream *js, const char *who); -char *jabber_google_format_to_html(const char *text); -gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); -void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); - -void jabber_google_handle_jingle_info(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *child); -void jabber_google_send_jingle_info(JabberStream *js); - -void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); - -#endif /* PURPLE_JABBER_GOOGLE_H_ */ +#endif /* PURPLE_JABBER_GOOGLE_ROSTER_H_ */ diff --git a/purple/libpurple/protocols/jabber/google.c b/purple/libpurple/protocols/jabber/google/google_session.c rename from purple/libpurple/protocols/jabber/google.c rename to purple/libpurple/protocols/jabber/google/google_session.c --- a/purple/libpurple/protocols/jabber/google.c +++ b/purple/libpurple/protocols/jabber/google/google_session.c @@ -15,75 +15,60 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" #include "debug.h" -#ifdef USE_VV -#include "mediamanager.h" -#endif -#include "util.h" -#include "privacy.h" -#include "dnsquery.h" -#include "network.h" - -#include "buddy.h" -#include "google.h" -#include "jabber.h" -#include "presence.h" -#include "roster.h" -#include "iq.h" -#include "chat.h" - -#ifdef USE_JINGLE -#include "jingle/jingle.h" -#endif +#include "google_session.h" +#include "relay.h" #ifdef USE_VV -typedef struct { - char *id; - char *initiator; -} GoogleSessionId; - -typedef enum { - UNINIT, - SENT_INITIATE, - RECEIVED_INITIATE, - IN_PRORESS, - TERMINATED -} GoogleSessionState; +#include "jingle/jingle.h" typedef struct { - GoogleSessionId id; - GoogleSessionState state; PurpleMedia *media; - JabberStream *js; - char *remote_jid; gboolean video; -} GoogleSession; + GList *remote_audio_candidates; /* list of PurpleMediaCandidate */ + GList *remote_video_candidates; /* list of PurpleMediaCandidate */ + gboolean added_streams; /* this indicates if the streams have been + to media (ie. after getting relay credentials */ +} GoogleAVSessionData; static gboolean google_session_id_equal(gconstpointer a, gconstpointer b) { GoogleSessionId *c = (GoogleSessionId*)a; GoogleSessionId *d = (GoogleSessionId*)b; return !strcmp(c->id, d->id) && !strcmp(c->initiator, d->initiator); } static void google_session_destroy(GoogleSession *session) { + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; g_free(session->id.id); g_free(session->id.initiator); g_free(session->remote_jid); + + if (session_data->remote_audio_candidates) + purple_media_candidate_list_free(session_data->remote_audio_candidates); + + if (session_data->remote_video_candidates) + purple_media_candidate_list_free(session_data->remote_video_candidates); + + if (session->description) + xmlnode_free(session->description); + + g_free(session->session_data); g_free(session); } static xmlnode * google_session_create_xmlnode(GoogleSession *session, const char *type) { xmlnode *node = xmlnode_new("session"); xmlnode_set_namespace(node, NS_GOOGLE_SESSION); @@ -92,18 +77,22 @@ google_session_create_xmlnode(GoogleSess xmlnode_set_attrib(node, "type", type); return node; } static void google_session_send_candidates(PurpleMedia *media, gchar *session_id, gchar *participant, GoogleSession *session) { - GList *candidates = purple_media_get_local_candidates( - session->media, session_id, session->remote_jid), *iter; + PurpleMedia *session_media = + ((GoogleAVSessionData *) session->session_data)->media; + GList *candidates = + purple_media_get_local_candidates(session_media, session_id, + session->remote_jid); + GList *iter; PurpleMediaCandidate *transport; gboolean video = FALSE; if (!strcmp(session_id, "google-video")) video = TRUE; for (iter = candidates; iter; iter = iter->next) { JabberIq *iq; @@ -172,17 +161,20 @@ google_session_send_candidates(PurpleMed jabber_iq_send(iq); } purple_media_candidate_list_free(candidates); } static void google_session_ready(GoogleSession *session) { - PurpleMedia *media = session->media; + PurpleMedia *media = + ((GoogleAVSessionData *)session->session_data)->media; + gboolean video = + ((GoogleAVSessionData *)session->session_data)->video; if (purple_media_codecs_ready(media, NULL) && purple_media_candidates_prepared(media, NULL, NULL)) { gchar *me = g_strdup_printf("%s@%s/%s", session->js->user->node, session->js->user->domain, session->js->user->resource); JabberIq *iq; xmlnode *sess, *desc, *payload; @@ -197,29 +189,29 @@ google_session_ready(GoogleSession *sess iq = jabber_iq_new(session->js, JABBER_IQ_SET); if (is_initiator) { xmlnode_set_attrib(iq->node, "to", session->remote_jid); xmlnode_set_attrib(iq->node, "from", session->id.initiator); sess = google_session_create_xmlnode(session, "initiate"); } else { - google_session_send_candidates(session->media, + google_session_send_candidates(media, "google-voice", session->remote_jid, session); - google_session_send_candidates(session->media, + google_session_send_candidates(media, "google-video", session->remote_jid, session); xmlnode_set_attrib(iq->node, "to", session->remote_jid); xmlnode_set_attrib(iq->node, "from", me); sess = google_session_create_xmlnode(session, "accept"); } xmlnode_insert_child(iq->node, sess); desc = xmlnode_new_child(sess, "description"); - if (session->video) + if (video) xmlnode_set_namespace(desc, NS_GOOGLE_SESSION_VIDEO); else xmlnode_set_namespace(desc, NS_GOOGLE_SESSION_PHONE); codecs = purple_media_get_codecs(media, "google-video"); for (iter = codecs; iter; iter = g_list_next(iter)) { PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; @@ -244,17 +236,17 @@ google_session_ready(GoogleSession *sess PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; gchar *id = g_strdup_printf("%d", purple_media_codec_get_id(codec)); gchar *encoding_name = purple_media_codec_get_encoding_name(codec); gchar *clock_rate = g_strdup_printf("%d", purple_media_codec_get_clock_rate(codec)); payload = xmlnode_new_child(desc, "payload-type"); - if (session->video) + if (video) xmlnode_set_namespace(payload, NS_GOOGLE_SESSION_PHONE); xmlnode_set_attrib(payload, "id", id); /* * Hack to make Gmail accept speex as the codec. * It shouldn't have to be case sensitive. */ if (purple_strequal(encoding_name, "SPEEX")) xmlnode_set_attrib(payload, "name", "speex"); @@ -265,25 +257,25 @@ google_session_ready(GoogleSession *sess g_free(encoding_name); g_free(id); } purple_media_codec_list_free(codecs); jabber_iq_send(iq); if (is_initiator) { - google_session_send_candidates(session->media, + google_session_send_candidates(media, "google-voice", session->remote_jid, session); - google_session_send_candidates(session->media, + google_session_send_candidates(media, "google-video", session->remote_jid, session); } - g_signal_handlers_disconnect_by_func(G_OBJECT(session->media), + g_signal_handlers_disconnect_by_func(G_OBJECT(media), G_CALLBACK(google_session_ready), session); } } static void google_session_state_changed_cb(PurpleMedia *media, PurpleMediaState state, gchar *sid, gchar *name, GoogleSession *session) { @@ -321,44 +313,96 @@ google_session_stream_info_cb(PurpleMedi jabber_iq_send(iq); } else if (type == PURPLE_MEDIA_INFO_ACCEPT && local == TRUE) { google_session_ready(session); } } static GParameter * -jabber_google_session_get_params(JabberStream *js, guint *num) +jabber_google_session_get_params(JabberStream *js, const gchar *relay_ip, + guint16 relay_udp, guint16 relay_tcp, guint16 relay_ssltcp, + const gchar *relay_username, const gchar *relay_password, guint *num) { guint num_params; - GParameter *params = jingle_get_params(js, &num_params); + GParameter *params = + jingle_get_params(js, relay_ip, relay_udp, relay_tcp, relay_ssltcp, + relay_username, relay_password, &num_params); GParameter *new_params = g_new0(GParameter, num_params + 1); memcpy(new_params, params, sizeof(GParameter) * num_params); purple_debug_info("jabber", "setting Google jingle compatibility param\n"); new_params[num_params].name = "compatibility-mode"; g_value_init(&new_params[num_params].value, G_TYPE_UINT); g_value_set_uint(&new_params[num_params].value, 1); /* NICE_COMPATIBILITY_GOOGLE */ g_free(params); *num = num_params + 1; return new_params; } +static void +jabber_google_relay_response_session_initiate_cb(GoogleSession *session, + const gchar *relay_ip, guint relay_udp, guint relay_tcp, guint relay_ssltcp, + const gchar *relay_username, const gchar *relay_password) +{ + GParameter *params; + guint num_params; + JabberStream *js = session->js; + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + + session_data->media = purple_media_manager_create_media( + purple_media_manager_get(), + purple_connection_get_account(js->gc), + "fsrtpconference", session->remote_jid, TRUE); + + purple_media_set_prpl_data(session_data->media, session); + + g_signal_connect_swapped(G_OBJECT(session_data->media), + "candidates-prepared", + G_CALLBACK(google_session_ready), session); + g_signal_connect_swapped(G_OBJECT(session_data->media), "codecs-changed", + G_CALLBACK(google_session_ready), session); + g_signal_connect(G_OBJECT(session_data->media), "state-changed", + G_CALLBACK(google_session_state_changed_cb), session); + g_signal_connect(G_OBJECT(session_data->media), "stream-info", + G_CALLBACK(google_session_stream_info_cb), session); + + params = + jabber_google_session_get_params(js, relay_ip, relay_udp, relay_tcp, + relay_ssltcp, relay_username, relay_password, &num_params); + + if (purple_media_add_stream(session_data->media, "google-voice", + session->remote_jid, PURPLE_MEDIA_AUDIO, + TRUE, "nice", num_params, params) == FALSE || + (session_data->video && purple_media_add_stream( + session_data->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + TRUE, "nice", num_params, params) == FALSE)) { + purple_media_error(session_data->media, "Error adding stream."); + purple_media_end(session_data->media, NULL, NULL); + } else { + session_data->added_streams = TRUE; + } + + g_free(params); +} + + gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type) { GoogleSession *session; JabberBuddy *jb; JabberBuddyResource *jbr; gchar *jid; - GParameter *params; - guint num_params; + GoogleAVSessionData *session_data = NULL; /* construct JID to send to */ jb = jabber_buddy_find(js, who, FALSE); if (!jb) { purple_debug_error("jingle-rtp", "Could not find Jabber buddy\n"); return FALSE; } @@ -376,135 +420,104 @@ jabber_google_session_initiate(JabberStr session = g_new0(GoogleSession, 1); session->id.id = jabber_get_next_id(js); session->id.initiator = g_strdup_printf("%s@%s/%s", js->user->node, js->user->domain, js->user->resource); session->state = SENT_INITIATE; session->js = js; session->remote_jid = jid; + session_data = g_new0(GoogleAVSessionData, 1); + session->session_data = session_data; + + if (type & PURPLE_MEDIA_VIDEO) + session_data->video = TRUE; - if (type & PURPLE_MEDIA_VIDEO) - session->video = TRUE; + /* if we got a relay token and relay host in google:jingleinfo, issue an + HTTP request to get that data */ + if (js->google_relay_host && js->google_relay_token) { + jabber_google_do_relay_request(js, session, + jabber_google_relay_response_session_initiate_cb); + } else { + jabber_google_relay_response_session_initiate_cb(session, NULL, 0, 0, 0, + NULL, NULL); + } + + /* we don't actually know yet wether it succeeded... maybe this is very + wrong... */ + return TRUE; +} - session->media = purple_media_manager_create_media( - purple_media_manager_get(), - purple_connection_get_account(js->gc), - "fsrtpconference", session->remote_jid, TRUE); +static void +jabber_google_relay_response_session_handle_initiate_cb(GoogleSession *session, + const gchar *relay_ip, guint relay_udp, guint relay_tcp, guint relay_ssltcp, + const gchar *relay_username, const gchar *relay_password) +{ + GParameter *params; + guint num_params; + JabberStream *js = session->js; + xmlnode *codec_element; + const gchar *xmlns; + PurpleMediaCodec *codec; + GList *video_codecs = NULL; + GList *codecs = NULL; + JabberIq *result; + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; - purple_media_set_prpl_data(session->media, session); + params = + jabber_google_session_get_params(js, relay_ip, relay_udp, relay_tcp, + relay_ssltcp, relay_username, relay_password, &num_params); - g_signal_connect_swapped(G_OBJECT(session->media), - "candidates-prepared", - G_CALLBACK(google_session_ready), session); - g_signal_connect_swapped(G_OBJECT(session->media), "codecs-changed", - G_CALLBACK(google_session_ready), session); - g_signal_connect(G_OBJECT(session->media), "state-changed", - G_CALLBACK(google_session_state_changed_cb), session); - g_signal_connect(G_OBJECT(session->media), "stream-info", - G_CALLBACK(google_session_stream_info_cb), session); + if (purple_media_add_stream(session_data->media, "google-voice", + session->remote_jid, PURPLE_MEDIA_AUDIO, FALSE, + "nice", num_params, params) == FALSE || + (session_data->video && purple_media_add_stream( + session_data->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + FALSE, "nice", num_params, params) == FALSE)) { + purple_media_error(session_data->media, "Error adding stream."); + purple_media_stream_info(session_data->media, + PURPLE_MEDIA_INFO_REJECT, NULL, NULL, TRUE); + } else { + /* successfully added stream(s) */ + session_data->added_streams = TRUE; - params = jabber_google_session_get_params(js, &num_params); - - if (purple_media_add_stream(session->media, "google-voice", - session->remote_jid, PURPLE_MEDIA_AUDIO, - TRUE, "nice", num_params, params) == FALSE || - (session->video && purple_media_add_stream( - session->media, "google-video", - session->remote_jid, PURPLE_MEDIA_VIDEO, - TRUE, "nice", num_params, params) == FALSE)) { - purple_media_error(session->media, "Error adding stream."); - purple_media_end(session->media, NULL, NULL); - g_free(params); - return FALSE; + if (session_data->remote_audio_candidates) { + purple_media_add_remote_candidates(session_data->media, + "google-voice", session->remote_jid, + session_data->remote_audio_candidates); + purple_media_candidate_list_free(session_data->remote_audio_candidates); + session_data->remote_audio_candidates = NULL; + } + if (session_data->remote_video_candidates) { + purple_media_add_remote_candidates(session_data->media, + "google-video", session->remote_jid, + session_data->remote_video_candidates); + purple_media_candidate_list_free(session_data->remote_video_candidates); + session_data->remote_video_candidates = NULL; + } } - + g_free(params); - return (session->media != NULL) ? TRUE : FALSE; -} - -static gboolean -google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) -{ - JabberIq *result; - GList *codecs = NULL, *video_codecs = NULL; - xmlnode *desc_element, *codec_element; - PurpleMediaCodec *codec; - const char *xmlns; - GParameter *params; - guint num_params; - - if (session->state != UNINIT) { - purple_debug_error("jabber", "Received initiate for active session.\n"); - return FALSE; - } - - desc_element = xmlnode_get_child(sess, "description"); - xmlns = xmlnode_get_namespace(desc_element); - - if (purple_strequal(xmlns, NS_GOOGLE_SESSION_PHONE)) - session->video = FALSE; - else if (purple_strequal(xmlns, NS_GOOGLE_SESSION_VIDEO)) - session->video = TRUE; - else { - purple_debug_error("jabber", "Received initiate with " - "invalid namespace %s.\n", xmlns); - return FALSE; - } - - session->media = purple_media_manager_create_media( - purple_media_manager_get(), - purple_connection_get_account(js->gc), - "fsrtpconference", session->remote_jid, FALSE); - - purple_media_set_prpl_data(session->media, session); - - g_signal_connect_swapped(G_OBJECT(session->media), - "candidates-prepared", - G_CALLBACK(google_session_ready), session); - g_signal_connect_swapped(G_OBJECT(session->media), "codecs-changed", - G_CALLBACK(google_session_ready), session); - g_signal_connect(G_OBJECT(session->media), "state-changed", - G_CALLBACK(google_session_state_changed_cb), session); - g_signal_connect(G_OBJECT(session->media), "stream-info", - G_CALLBACK(google_session_stream_info_cb), session); - - params = jabber_google_session_get_params(js, &num_params); - - if (purple_media_add_stream(session->media, "google-voice", - session->remote_jid, PURPLE_MEDIA_AUDIO, FALSE, - "nice", num_params, params) == FALSE || - (session->video && purple_media_add_stream( - session->media, "google-video", - session->remote_jid, PURPLE_MEDIA_VIDEO, - FALSE, "nice", num_params, params) == FALSE)) { - purple_media_error(session->media, "Error adding stream."); - purple_media_stream_info(session->media, - PURPLE_MEDIA_INFO_REJECT, NULL, NULL, TRUE); - g_free(params); - return FALSE; - } - - g_free(params); - - for (codec_element = xmlnode_get_child(desc_element, "payload-type"); + for (codec_element = xmlnode_get_child(session->description, "payload-type"); codec_element; codec_element = codec_element->next) { const char *id, *encoding_name, *clock_rate, *width, *height, *framerate; gboolean video; if (codec_element->name && strcmp(codec_element->name, "payload-type")) continue; xmlns = xmlnode_get_namespace(codec_element); encoding_name = xmlnode_get_attrib(codec_element, "name"); id = xmlnode_get_attrib(codec_element, "id"); - if (!session->video || + if (!session_data->video || (xmlns && !strcmp(xmlns, NS_GOOGLE_SESSION_PHONE))) { clock_rate = xmlnode_get_attrib( codec_element, "clockrate"); video = FALSE; } else { width = xmlnode_get_attrib(codec_element, "width"); height = xmlnode_get_attrib(codec_element, "height"); framerate = xmlnode_get_attrib( @@ -522,57 +535,115 @@ google_session_handle_initiate(JabberStr video_codecs = g_list_append( video_codecs, codec); else codecs = g_list_append(codecs, codec); } } if (codecs) - purple_media_set_remote_codecs(session->media, "google-voice", + purple_media_set_remote_codecs(session_data->media, "google-voice", session->remote_jid, codecs); if (video_codecs) - purple_media_set_remote_codecs(session->media, "google-video", + purple_media_set_remote_codecs(session_data->media, "google-video", session->remote_jid, video_codecs); purple_media_codec_list_free(codecs); purple_media_codec_list_free(video_codecs); result = jabber_iq_new(js, JABBER_IQ_RESULT); - jabber_iq_set_id(result, iq_id); + jabber_iq_set_id(result, session->iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); jabber_iq_send(result); +} + +static gboolean +google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) +{ + const gchar *xmlns; + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + + if (session->state != UNINIT) { + purple_debug_error("jabber", "Received initiate for active session.\n"); + return FALSE; + } + + session->description = xmlnode_copy(xmlnode_get_child(sess, "description")); + xmlns = xmlnode_get_namespace(session->description); + + if (purple_strequal(xmlns, NS_GOOGLE_SESSION_PHONE)) + session_data->video = FALSE; + else if (purple_strequal(xmlns, NS_GOOGLE_SESSION_VIDEO)) + session_data->video = TRUE; + else { + purple_debug_error("jabber", "Received initiate with " + "invalid namespace %s.\n", xmlns); + return FALSE; + } + + session_data->media = purple_media_manager_create_media( + purple_media_manager_get(), + purple_connection_get_account(js->gc), + "fsrtpconference", session->remote_jid, FALSE); + + purple_media_set_prpl_data(session_data->media, session); + + g_signal_connect_swapped(G_OBJECT(session_data->media), + "candidates-prepared", + G_CALLBACK(google_session_ready), session); + g_signal_connect_swapped(G_OBJECT(session_data->media), "codecs-changed", + G_CALLBACK(google_session_ready), session); + g_signal_connect(G_OBJECT(session_data->media), "state-changed", + G_CALLBACK(google_session_state_changed_cb), session); + g_signal_connect(G_OBJECT(session_data->media), "stream-info", + G_CALLBACK(google_session_stream_info_cb), session); + + session->iq_id = g_strdup(iq_id); + + if (js->google_relay_host && js->google_relay_token) { + jabber_google_do_relay_request(js, session, + jabber_google_relay_response_session_handle_initiate_cb); + } else { + jabber_google_relay_response_session_handle_initiate_cb(session, NULL, + 0, 0, 0, NULL, NULL); + } return TRUE; } + static void google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; GList *list = NULL, *video_list = NULL; xmlnode *cand; static int name = 0; char n[4]; - + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { PurpleMediaCandidate *info; const gchar *cname = xmlnode_get_attrib(cand, "name"); const gchar *type = xmlnode_get_attrib(cand, "type"); const gchar *protocol = xmlnode_get_attrib(cand, "protocol"); const gchar *address = xmlnode_get_attrib(cand, "address"); const gchar *port = xmlnode_get_attrib(cand, "port"); + const gchar *preference = xmlnode_get_attrib(cand, "preference"); guint component_id; if (cname && type && address && port) { PurpleMediaCandidateType candidate_type; - + guint prio = preference ? g_ascii_strtod(preference, NULL) * 1000 : 0; + g_snprintf(n, sizeof(n), "S%d", name++); - + if (g_str_equal(type, "local")) candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; else if (g_str_equal(type, "stun")) candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX; else if (g_str_equal(type, "relay")) candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_RELAY; else candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; @@ -586,34 +657,48 @@ google_session_handle_candidates(JabberS info = purple_media_candidate_new(n, component_id, candidate_type, purple_strequal(protocol, "udp") ? PURPLE_MEDIA_NETWORK_PROTOCOL_UDP : PURPLE_MEDIA_NETWORK_PROTOCOL_TCP, address, atoi(port)); g_object_set(info, "username", xmlnode_get_attrib(cand, "username"), - "password", xmlnode_get_attrib(cand, "password"), NULL); - if (!strncmp(cname, "video_", 6)) - video_list = g_list_append(video_list, info); - else - list = g_list_append(list, info); + "password", xmlnode_get_attrib(cand, "password"), + "priority", prio, NULL); + if (!strncmp(cname, "video_", 6)) { + if (session_data->added_streams) { + video_list = g_list_append(video_list, info); + } else { + session_data->remote_video_candidates = + g_list_append(session_data->remote_video_candidates, + info); + } + } else { + if (session_data->added_streams) { + list = g_list_append(list, info); + } else { + session_data->remote_audio_candidates = + g_list_append(session_data->remote_audio_candidates, + info); + } + } } } - if (list) - purple_media_add_remote_candidates( - session->media, "google-voice", - session->remote_jid, list); - if (video_list) - purple_media_add_remote_candidates( - session->media, "google-video", - session->remote_jid, video_list); - purple_media_candidate_list_free(list); - purple_media_candidate_list_free(video_list); + if (list) { + purple_media_add_remote_candidates(session_data->media, "google-voice", + session->remote_jid, list); + purple_media_candidate_list_free(list); + } + if (video_list) { + purple_media_add_remote_candidates(session_data->media, "google-video", + session->remote_jid, video_list); + purple_media_candidate_list_free(video_list); + } result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); jabber_iq_send(result); } static void @@ -621,17 +706,19 @@ google_session_handle_accept(JabberStrea { xmlnode *desc_element = xmlnode_get_child(sess, "description"); xmlnode *codec_element = xmlnode_get_child( desc_element, "payload-type"); GList *codecs = NULL, *video_codecs = NULL; JabberIq *result = NULL; const gchar *xmlns = xmlnode_get_namespace(desc_element); gboolean video = (xmlns && !strcmp(xmlns, NS_GOOGLE_SESSION_VIDEO)); - + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + for (; codec_element; codec_element = codec_element->next) { const gchar *xmlns, *encoding_name, *id, *clock_rate, *width, *height, *framerate; gboolean video_codec = FALSE; if (!purple_strequal(codec_element->name, "payload-type")) continue; @@ -661,41 +748,45 @@ google_session_handle_accept(JabberStrea video_codecs = g_list_append( video_codecs, codec); else codecs = g_list_append(codecs, codec); } } if (codecs) - purple_media_set_remote_codecs(session->media, "google-voice", + purple_media_set_remote_codecs(session_data->media, "google-voice", session->remote_jid, codecs); if (video_codecs) - purple_media_set_remote_codecs(session->media, "google-video", + purple_media_set_remote_codecs(session_data->media, "google-video", session->remote_jid, video_codecs); - purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_ACCEPT, + purple_media_stream_info(session_data->media, PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, FALSE); result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); jabber_iq_send(result); } static void google_session_handle_reject(JabberStream *js, GoogleSession *session, xmlnode *sess) { - purple_media_end(session->media, NULL, NULL); + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + purple_media_end(session_data->media, NULL, NULL); } static void google_session_handle_terminate(JabberStream *js, GoogleSession *session, xmlnode *sess) { - purple_media_end(session->media, NULL, NULL); + GoogleAVSessionData *session_data = + (GoogleAVSessionData *) session->session_data; + purple_media_end(session_data->media, NULL, NULL); } static void google_session_parse_iq(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { const char *type = xmlnode_get_attrib(sess, "type"); if (!strcmp(type, "initiate")) { @@ -761,673 +852,15 @@ jabber_google_session_parse(JabberStream if (!desc_node) return; session = g_new0(GoogleSession, 1); session->id.id = g_strdup(id.id); session->id.initiator = g_strdup(id.initiator); session->state = UNINIT; session->js = js; session->remote_jid = g_strdup(session->id.initiator); + session->session_data = g_new0(GoogleAVSessionData, 1); google_session_handle_initiate(js, session, session_node, iq_id); } #endif /* USE_VV */ -static void -jabber_gmail_parse(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *packet, gpointer nul) -{ - xmlnode *child; - xmlnode *message; - const char *to, *url; - const char *in_str; - char *to_name; - int i, count = 1, returned_count; - - const char **tos, **froms, **urls; - char **subjects; - - if (type == JABBER_IQ_ERROR) - return; - - child = xmlnode_get_child(packet, "mailbox"); - if (!child) - return; - - in_str = xmlnode_get_attrib(child, "total-matched"); - if (in_str && *in_str) - count = atoi(in_str); - - /* If Gmail doesn't tell us who the mail is to, let's use our JID */ - to = xmlnode_get_attrib(packet, "to"); - - message = xmlnode_get_child(child, "mail-thread-info"); - - if (count == 0 || !message) { - if (count > 0) { - char *bare_jid = jabber_get_bare_jid(to); - const char *default_tos[2] = { bare_jid }; - - purple_notify_emails(js->gc, count, FALSE, NULL, NULL, default_tos, NULL, NULL, NULL); - g_free(bare_jid); - } else { - purple_notify_emails(js->gc, count, FALSE, NULL, NULL, NULL, NULL, NULL, NULL); - } - - return; - } - - /* Loop once to see how many messages were returned so we can allocate arrays - * accordingly */ - for (returned_count = 0; message; returned_count++, message=xmlnode_get_next_twin(message)); - - froms = g_new0(const char* , returned_count + 1); - tos = g_new0(const char* , returned_count + 1); - subjects = g_new0(char* , returned_count + 1); - urls = g_new0(const char* , returned_count + 1); - - to = xmlnode_get_attrib(packet, "to"); - to_name = jabber_get_bare_jid(to); - url = xmlnode_get_attrib(child, "url"); - if (!url || !*url) - url = "http://www.gmail.com"; - - message= xmlnode_get_child(child, "mail-thread-info"); - for (i=0; message; message = xmlnode_get_next_twin(message), i++) { - xmlnode *sender_node, *subject_node; - const char *from, *tid; - char *subject; - - subject_node = xmlnode_get_child(message, "subject"); - sender_node = xmlnode_get_child(message, "senders"); - sender_node = xmlnode_get_child(sender_node, "sender"); - - while (sender_node && (!xmlnode_get_attrib(sender_node, "unread") || - !strcmp(xmlnode_get_attrib(sender_node, "unread"),"0"))) - sender_node = xmlnode_get_next_twin(sender_node); - - if (!sender_node) { - i--; - continue; - } - - from = xmlnode_get_attrib(sender_node, "name"); - if (!from || !*from) - from = xmlnode_get_attrib(sender_node, "address"); - subject = xmlnode_get_data(subject_node); - /* - * url = xmlnode_get_attrib(message, "url"); - */ - tos[i] = (to_name != NULL ? to_name : ""); - froms[i] = (from != NULL ? from : ""); - subjects[i] = (subject != NULL ? subject : g_strdup("")); - urls[i] = url; - - tid = xmlnode_get_attrib(message, "tid"); - if (tid && - (js->gmail_last_tid == NULL || strcmp(tid, js->gmail_last_tid) > 0)) { - g_free(js->gmail_last_tid); - js->gmail_last_tid = g_strdup(tid); - } - } - - if (i>0) - purple_notify_emails(js->gc, count, count == i, (const char**) subjects, froms, tos, - urls, NULL, NULL); - - g_free(to_name); - g_free(tos); - g_free(froms); - for (i = 0; i < returned_count; i++) - g_free(subjects[i]); - g_free(subjects); - g_free(urls); - - in_str = xmlnode_get_attrib(child, "result-time"); - if (in_str && *in_str) { - g_free(js->gmail_last_time); - js->gmail_last_time = g_strdup(in_str); - } -} - -void -jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, - const char *id, xmlnode *new_mail) -{ - xmlnode *query; - JabberIq *iq; - - /* bail if the user isn't interested */ - if (!purple_account_get_check_mail(js->gc->account)) - return; - - /* Is this an initial incoming mail notification? If so, send a request for more info */ - if (type != JABBER_IQ_SET) - return; - - /* Acknowledge the notification */ - iq = jabber_iq_new(js, JABBER_IQ_RESULT); - if (from) - xmlnode_set_attrib(iq->node, "to", from); - xmlnode_set_attrib(iq->node, "id", id); - jabber_iq_send(iq); - - purple_debug_misc("jabber", - "Got new mail notification. Sending request for more info\n"); - - iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_MAIL_NOTIFY); - jabber_iq_set_callback(iq, jabber_gmail_parse, NULL); - query = xmlnode_get_child(iq->node, "query"); - - if (js->gmail_last_time) - xmlnode_set_attrib(query, "newer-than-time", js->gmail_last_time); - if (js->gmail_last_tid) - xmlnode_set_attrib(query, "newer-than-tid", js->gmail_last_tid); - - jabber_iq_send(iq); - return; -} - -void jabber_gmail_init(JabberStream *js) { - JabberIq *iq; - xmlnode *usersetting, *mailnotifications; - - if (!purple_account_get_check_mail(purple_connection_get_account(js->gc))) - return; - - /* - * Quoting http://code.google.com/apis/talk/jep_extensions/usersettings.html: - * To ensure better compatibility with other clients, rather than - * setting this value to "false" to turn off notifications, it is - * recommended that a client set this to "true" and filter incoming - * email notifications itself. - */ - iq = jabber_iq_new(js, JABBER_IQ_SET); - usersetting = xmlnode_new_child(iq->node, "usersetting"); - xmlnode_set_namespace(usersetting, "google:setting"); - mailnotifications = xmlnode_new_child(usersetting, "mailnotifications"); - xmlnode_set_attrib(mailnotifications, "value", "true"); - jabber_iq_send(iq); - - iq = jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_MAIL_NOTIFY); - jabber_iq_set_callback(iq, jabber_gmail_parse, NULL); - jabber_iq_send(iq); -} - -void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item) -{ - PurpleAccount *account = purple_connection_get_account(js->gc); - GSList *list = account->deny; - const char *jid = xmlnode_get_attrib(item, "jid"); - char *jid_norm = (char *)jabber_normalize(account, jid); - - while (list) { - if (!strcmp(jid_norm, (char*)list->data)) { - xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); - xmlnode_set_attrib(query, "gr:ext", "2"); - xmlnode_set_attrib(item, "gr:t", "B"); - return; - } - list = list->next; - } -} - -gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item) -{ - PurpleAccount *account = purple_connection_get_account(js->gc); - const char *jid = xmlnode_get_attrib(item, "jid"); - gboolean on_block_list = FALSE; - - char *jid_norm; - - const char *grt = xmlnode_get_attrib_with_namespace(item, "t", NS_GOOGLE_ROSTER); - const char *subscription = xmlnode_get_attrib(item, "subscription"); - const char *ask = xmlnode_get_attrib(item, "ask"); - - if ((!subscription || !strcmp(subscription, "none")) && !ask) { - /* The Google Talk servers will automatically add people from your Gmail address book - * with subscription=none. If we see someone with subscription=none, ignore them. - */ - return FALSE; - } - - jid_norm = g_strdup(jabber_normalize(account, jid)); - - on_block_list = NULL != g_slist_find_custom(account->deny, jid_norm, - (GCompareFunc)strcmp); - - if (grt && (*grt == 'H' || *grt == 'h')) { - /* Hidden; don't show this buddy. */ - GSList *buddies = purple_find_buddies(account, jid_norm); - if (buddies) - purple_debug_info("jabber", "Removing %s from local buddy list\n", - jid_norm); - - for ( ; buddies; buddies = g_slist_delete_link(buddies, buddies)) { - purple_blist_remove_buddy(buddies->data); - } - - g_free(jid_norm); - return FALSE; - } - - if (!on_block_list && (grt && (*grt == 'B' || *grt == 'b'))) { - purple_debug_info("jabber", "Blocking %s\n", jid_norm); - purple_privacy_deny_add(account, jid_norm, TRUE); - } else if (on_block_list && (!grt || (*grt != 'B' && *grt != 'b' ))){ - purple_debug_info("jabber", "Unblocking %s\n", jid_norm); - purple_privacy_deny_remove(account, jid_norm, TRUE); - } - - g_free(jid_norm); - return TRUE; -} - -void jabber_google_roster_add_deny(JabberStream *js, const char *who) -{ - PurpleAccount *account; - GSList *buddies; - JabberIq *iq; - xmlnode *query; - xmlnode *item; - xmlnode *group; - PurpleBuddy *b; - JabberBuddy *jb; - const char *balias; - - jb = jabber_buddy_find(js, who, TRUE); - - account = purple_connection_get_account(js->gc); - buddies = purple_find_buddies(account, who); - if(!buddies) - return; - - b = buddies->data; - - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); - - query = xmlnode_get_child(iq->node, "query"); - item = xmlnode_new_child(query, "item"); - - while(buddies) { - PurpleGroup *g; - - b = buddies->data; - g = purple_buddy_get_group(b); - - group = xmlnode_new_child(item, "group"); - xmlnode_insert_data(group, purple_group_get_name(g), -1); - - buddies = buddies->next; - } - - balias = purple_buddy_get_local_buddy_alias(b); - xmlnode_set_attrib(item, "jid", who); - xmlnode_set_attrib(item, "name", balias ? balias : ""); - xmlnode_set_attrib(item, "gr:t", "B"); - xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); - xmlnode_set_attrib(query, "gr:ext", "2"); - - jabber_iq_send(iq); - - /* Synthesize a sign-off */ - if (jb) { - JabberBuddyResource *jbr; - GList *l = jb->resources; - while (l) { - jbr = l->data; - if (jbr && jbr->name) - { - purple_debug_misc("jabber", "Removing resource %s\n", jbr->name); - jabber_buddy_remove_resource(jb, jbr->name); - } - l = l->next; - } - } - - purple_prpl_got_user_status(account, who, "offline", NULL); -} - -void jabber_google_roster_rem_deny(JabberStream *js, const char *who) -{ - GSList *buddies; - JabberIq *iq; - xmlnode *query; - xmlnode *item; - xmlnode *group; - PurpleBuddy *b; - const char *balias; - - buddies = purple_find_buddies(purple_connection_get_account(js->gc), who); - if(!buddies) - return; - - b = buddies->data; - - iq = jabber_iq_new_query(js, JABBER_IQ_SET, "jabber:iq:roster"); - - query = xmlnode_get_child(iq->node, "query"); - item = xmlnode_new_child(query, "item"); - - while(buddies) { - PurpleGroup *g; - - b = buddies->data; - g = purple_buddy_get_group(b); - - group = xmlnode_new_child(item, "group"); - xmlnode_insert_data(group, purple_group_get_name(g), -1); - - buddies = buddies->next; - } - - balias = purple_buddy_get_local_buddy_alias(b); - xmlnode_set_attrib(item, "jid", who); - xmlnode_set_attrib(item, "name", balias ? balias : ""); - xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); - xmlnode_set_attrib(query, "gr:ext", "2"); - - jabber_iq_send(iq); - - /* See if he's online */ - jabber_presence_subscription_set(js, who, "probe"); -} - -/* This does two passes on the string. The first pass goes through - * and determine if all the structured text is properly balanced, and - * how many instances of each there is. The second pass goes and converts - * everything to HTML, depending on what's figured out by the first pass. - * It will short circuit once it knows it has no more replacements to make - */ -char *jabber_google_format_to_html(const char *text) -{ - const char *p; - - /* The start of the screen may be consdiered a space for this purpose */ - gboolean preceding_space = TRUE; - - gboolean in_bold = FALSE, in_italic = FALSE; - gboolean in_tag = FALSE; - - gint bold_count = 0, italic_count = 0; - - GString *str; - - for (p = text; *p != '\0'; p = g_utf8_next_char(p)) { - gunichar c = g_utf8_get_char(p); - if (c == '*' && !in_tag) { - if (in_bold && (g_unichar_isspace(*(p+1)) || - *(p+1) == '\0' || - *(p+1) == '<')) { - bold_count++; - in_bold = FALSE; - } else if (preceding_space && !in_bold && !g_unichar_isspace(*(p+1))) { - bold_count++; - in_bold = TRUE; - } - preceding_space = TRUE; - } else if (c == '_' && !in_tag) { - if (in_italic && (g_unichar_isspace(*(p+1)) || - *(p+1) == '\0' || - *(p+1) == '<')) { - italic_count++; - in_italic = FALSE; - } else if (preceding_space && !in_italic && !g_unichar_isspace(*(p+1))) { - italic_count++; - in_italic = TRUE; - } - preceding_space = TRUE; - } else if (c == '<' && !in_tag) { - in_tag = TRUE; - } else if (c == '>' && in_tag) { - in_tag = FALSE; - } else if (!in_tag) { - if (g_unichar_isspace(c)) - preceding_space = TRUE; - else - preceding_space = FALSE; - } - } - - str = g_string_new(NULL); - in_bold = in_italic = in_tag = FALSE; - preceding_space = TRUE; - - for (p = text; *p != '\0'; p = g_utf8_next_char(p)) { - gunichar c = g_utf8_get_char(p); - - if (bold_count < 2 && italic_count < 2 && !in_bold && !in_italic) { - g_string_append(str, p); - return g_string_free(str, FALSE); - } - - - if (c == '*' && !in_tag) { - if (in_bold && - (g_unichar_isspace(*(p+1))||*(p+1)=='<')) { /* This is safe in UTF-8 */ - str = g_string_append(str, ""); - in_bold = FALSE; - bold_count--; - } else if (preceding_space && bold_count > 1 && !g_unichar_isspace(*(p+1))) { - str = g_string_append(str, ""); - bold_count--; - in_bold = TRUE; - } else { - str = g_string_append_unichar(str, c); - } - preceding_space = TRUE; - } else if (c == '_' && !in_tag) { - if (in_italic && - (g_unichar_isspace(*(p+1))||*(p+1)=='<')) { - str = g_string_append(str, ""); - italic_count--; - in_italic = FALSE; - } else if (preceding_space && italic_count > 1 && !g_unichar_isspace(*(p+1))) { - str = g_string_append(str, ""); - italic_count--; - in_italic = TRUE; - } else { - str = g_string_append_unichar(str, c); - } - preceding_space = TRUE; - } else if (c == '<' && !in_tag) { - str = g_string_append_unichar(str, c); - in_tag = TRUE; - } else if (c == '>' && in_tag) { - str = g_string_append_unichar(str, c); - in_tag = FALSE; - } else if (!in_tag) { - str = g_string_append_unichar(str, c); - if (g_unichar_isspace(c)) - preceding_space = TRUE; - else - preceding_space = FALSE; - } else { - str = g_string_append_unichar(str, c); - } - } - return g_string_free(str, FALSE); -} - -void jabber_google_presence_incoming(JabberStream *js, const char *user, JabberBuddyResource *jbr) -{ - if (!js->googletalk) - return; - if (jbr->status && purple_str_has_prefix(jbr->status, "♫ ")) { - purple_prpl_got_user_status(js->gc->account, user, "tune", - PURPLE_TUNE_TITLE, jbr->status + strlen("♫ "), NULL); - g_free(jbr->status); - jbr->status = NULL; - } else { - purple_prpl_got_user_status_deactive(js->gc->account, user, "tune"); - } -} - -char *jabber_google_presence_outgoing(PurpleStatus *tune) -{ - const char *attr = purple_status_get_attr_string(tune, PURPLE_TUNE_TITLE); - return attr ? g_strdup_printf("♫ %s", attr) : g_strdup(""); -} - -static void -jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, - const char *error_message) -{ - JabberStream *js = (JabberStream *) data; - - if (error_message) { - purple_debug_error("jabber", "Google STUN lookup failed: %s\n", - error_message); - g_slist_free(hosts); - js->stun_query = NULL; - return; - } - - if (hosts && g_slist_next(hosts)) { - struct sockaddr *addr = g_slist_next(hosts)->data; - char dst[INET6_ADDRSTRLEN]; - int port; - - if (addr->sa_family == AF_INET6) { - inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, - dst, sizeof(dst)); - port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); - } else { - inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, - dst, sizeof(dst)); - port = ntohs(((struct sockaddr_in *) addr)->sin_port); - } - - if (js->stun_ip) - g_free(js->stun_ip); - js->stun_ip = g_strdup(dst); - js->stun_port = port; - - purple_debug_info("jabber", "set Google STUN IP/port address: " - "%s:%d\n", dst, port); - - /* unmark ongoing query */ - js->stun_query = NULL; - } - - while (hosts != NULL) { - hosts = g_slist_delete_link(hosts, hosts); - /* Free the address */ - g_free(hosts->data); - hosts = g_slist_delete_link(hosts, hosts); - } -} - -static void -jabber_google_jingle_info_common(JabberStream *js, const char *from, - JabberIqType type, xmlnode *query) -{ - const xmlnode *stun = xmlnode_get_child(query, "stun"); - gchar *my_bare_jid; - - /* - * Make sure that random people aren't sending us STUN servers. Per - * http://code.google.com/apis/talk/jep_extensions/jingleinfo.html, these - * stanzas are stamped from our bare JID. - */ - if (from) { - my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); - if (!purple_strequal(from, my_bare_jid)) { - purple_debug_warning("jabber", "got google:jingleinfo with invalid from (%s)\n", - from); - g_free(my_bare_jid); - return; - } - - g_free(my_bare_jid); - } - - if (type == JABBER_IQ_ERROR || type == JABBER_IQ_GET) - return; - - purple_debug_info("jabber", "got google:jingleinfo\n"); - - if (stun) { - xmlnode *server = xmlnode_get_child(stun, "server"); - - if (server) { - const gchar *host = xmlnode_get_attrib(server, "host"); - const gchar *udp = xmlnode_get_attrib(server, "udp"); - - if (host && udp) { - int port = atoi(udp); - /* if there, would already be an ongoing query, - cancel it */ - if (js->stun_query) - purple_dnsquery_destroy(js->stun_query); - - js->stun_query = purple_dnsquery_a(host, port, - jabber_google_stun_lookup_cb, js); - } - } - } - /* should perhaps handle relays later on, or maybe wait until - Google supports a common standard... */ -} - -static void -jabber_google_jingle_info_cb(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *packet, gpointer data) -{ - xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", - NS_GOOGLE_JINGLE_INFO); - - if (query) - jabber_google_jingle_info_common(js, from, type, query); - else - purple_debug_warning("jabber", "Got invalid google:jingleinfo\n"); -} - -void -jabber_google_handle_jingle_info(JabberStream *js, const char *from, - JabberIqType type, const char *id, - xmlnode *child) -{ - jabber_google_jingle_info_common(js, from, type, child); -} - -void -jabber_google_send_jingle_info(JabberStream *js) -{ - JabberIq *jingle_info = - jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_JINGLE_INFO); - - jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb, - NULL); - purple_debug_info("jabber", "sending google:jingleinfo query\n"); - jabber_iq_send(jingle_info); -} - -void google_buddy_node_chat(PurpleBlistNode *node, gpointer data) -{ - PurpleBuddy *buddy; - PurpleConnection *gc; - JabberStream *js; - JabberChat *chat; - gchar *room; - gchar *uuid = purple_uuid_random(); - - g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); - - buddy = PURPLE_BUDDY(node); - gc = purple_account_get_connection(purple_buddy_get_account(buddy)); - g_return_if_fail(gc != NULL); - js = purple_connection_get_protocol_data(gc); - - room = g_strdup_printf("private-chat-%s", uuid); - chat = jabber_join_chat(js, room, GOOGLE_GROUPCHAT_SERVER, js->user->node, - NULL, NULL); - if (chat) { - chat->muc = TRUE; - jabber_chat_invite(gc, chat->id, "", purple_buddy_get_name(buddy)); - } - - g_free(room); - g_free(uuid); -} diff --git a/purple/libpurple/protocols/jabber/google/google_session.h b/purple/libpurple/protocols/jabber/google/google_session.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/google_session.h @@ -0,0 +1,56 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef PURPLE_JABBER_GOOGLE_SESSION_H_ +#define PURPLE_JABBER_GOOGLE_SESSION_H_ + +#include "jabber.h" + +typedef struct { + char *id; + char *initiator; +} GoogleSessionId; + +typedef enum { + UNINIT, + SENT_INITIATE, + RECEIVED_INITIATE, + IN_PRORESS, + TERMINATED +} GoogleSessionState; + +typedef struct { + GoogleSessionId id; + GoogleSessionState state; + JabberStream *js; + char *remote_jid; + char *iq_id; + xmlnode *description; /* store incoming description through + relay credential fetching */ + gpointer session_data; +} GoogleSession; + +gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, + PurpleMediaSessionType type); + +void jabber_google_session_parse(JabberStream *js, const char *from, + JabberIqType type, const char *iq, xmlnode *session); + +#endif /* PURPLE_JABBER_GOOGLE_SESSION_H_ */ \ No newline at end of file diff --git a/purple/libpurple/protocols/jabber/google/jingleinfo.c b/purple/libpurple/protocols/jabber/google/jingleinfo.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/jingleinfo.c @@ -0,0 +1,174 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" +#include "jingleinfo.h" + +static void +jabber_google_stun_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + JabberStream *js = (JabberStream *) data; + + if (error_message) { + purple_debug_error("jabber", "Google STUN lookup failed: %s\n", + error_message); + g_slist_free(hosts); + js->stun_query = NULL; + return; + } + + if (hosts && g_slist_next(hosts)) { + struct sockaddr *addr = g_slist_next(hosts)->data; + char dst[INET6_ADDRSTRLEN]; + int port; + + if (addr->sa_family == AF_INET6) { + inet_ntop(addr->sa_family, &((struct sockaddr_in6 *) addr)->sin6_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port); + } else { + inet_ntop(addr->sa_family, &((struct sockaddr_in *) addr)->sin_addr, + dst, sizeof(dst)); + port = ntohs(((struct sockaddr_in *) addr)->sin_port); + } + + if (js->stun_ip) + g_free(js->stun_ip); + js->stun_ip = g_strdup(dst); + js->stun_port = port; + + purple_debug_info("jabber", "set Google STUN IP/port address: " + "%s:%d\n", dst, port); + + /* unmark ongoing query */ + js->stun_query = NULL; + } + + while (hosts != NULL) { + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } +} + +static void +jabber_google_jingle_info_common(JabberStream *js, const char *from, + JabberIqType type, xmlnode *query) +{ + const xmlnode *stun = xmlnode_get_child(query, "stun"); + const xmlnode *relay = xmlnode_get_child(query, "relay"); + gchar *my_bare_jid; + + /* + * Make sure that random people aren't sending us STUN servers. Per + * http://code.google.com/apis/talk/jep_extensions/jingleinfo.html, these + * stanzas are stamped from our bare JID. + */ + if (from) { + my_bare_jid = g_strdup_printf("%s@%s", js->user->node, js->user->domain); + if (!purple_strequal(from, my_bare_jid)) { + purple_debug_warning("jabber", "got google:jingleinfo with invalid from (%s)\n", + from); + g_free(my_bare_jid); + return; + } + + g_free(my_bare_jid); + } + + if (type == JABBER_IQ_ERROR || type == JABBER_IQ_GET) + return; + + purple_debug_info("jabber", "got google:jingleinfo\n"); + + if (stun) { + xmlnode *server = xmlnode_get_child(stun, "server"); + + if (server) { + const gchar *host = xmlnode_get_attrib(server, "host"); + const gchar *udp = xmlnode_get_attrib(server, "udp"); + + if (host && udp) { + int port = atoi(udp); + /* if there, would already be an ongoing query, + cancel it */ + if (js->stun_query) + purple_dnsquery_destroy(js->stun_query); + + js->stun_query = purple_dnsquery_a(host, port, + jabber_google_stun_lookup_cb, js); + } + } + } + + if (relay) { + xmlnode *token = xmlnode_get_child(relay, "token"); + xmlnode *server = xmlnode_get_child(relay, "server"); + + if (token) { + gchar *relay_token = xmlnode_get_data(token); + + /* we let js own the string returned from xmlnode_get_data */ + js->google_relay_token = relay_token; + } + + if (server) { + js->google_relay_host = + g_strdup(xmlnode_get_attrib(server, "host")); + } + } +} + +static void +jabber_google_jingle_info_cb(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *packet, gpointer data) +{ + xmlnode *query = xmlnode_get_child_with_namespace(packet, "query", + NS_GOOGLE_JINGLE_INFO); + + if (query) + jabber_google_jingle_info_common(js, from, type, query); + else + purple_debug_warning("jabber", "Got invalid google:jingleinfo\n"); +} + +void +jabber_google_handle_jingle_info(JabberStream *js, const char *from, + JabberIqType type, const char *id, + xmlnode *child) +{ + jabber_google_jingle_info_common(js, from, type, child); +} + +void +jabber_google_send_jingle_info(JabberStream *js) +{ + JabberIq *jingle_info = + jabber_iq_new_query(js, JABBER_IQ_GET, NS_GOOGLE_JINGLE_INFO); + + jabber_iq_set_callback(jingle_info, jabber_google_jingle_info_cb, + NULL); + purple_debug_info("jabber", "sending google:jingleinfo query\n"); + jabber_iq_send(jingle_info); +} diff --git a/purple/libpurple/protocols/jabber/google.h b/purple/libpurple/protocols/jabber/google/jingleinfo.h copy from purple/libpurple/protocols/jabber/google.h copy to purple/libpurple/protocols/jabber/google/jingleinfo.h --- a/purple/libpurple/protocols/jabber/google.h +++ b/purple/libpurple/protocols/jabber/google/jingleinfo.h @@ -13,48 +13,20 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#ifndef PURPLE_JABBER_GOOGLE_H_ -#define PURPLE_JABBER_GOOGLE_H_ - -/* This is a place for Google Talk-specific XMPP extensions to live - * such that they don't intermingle with code for the XMPP RFCs and XEPs :) */ +#ifndef PURPLE_JABBER_GOOGLE_ROSTER_H_ +#define PURPLE_JABBER_GOOGLE_ROSTER_H_ #include "jabber.h" -#define GOOGLE_GROUPCHAT_SERVER "groupchat.google.com" - -void jabber_gmail_init(JabberStream *js); -void jabber_gmail_poke(JabberStream *js, const char *from, JabberIqType type, - const char *id, xmlnode *new_mail); - -void jabber_google_roster_outgoing(JabberStream *js, xmlnode *query, xmlnode *item); - -/* Returns FALSE if this should short-circuit processing of this roster item, or TRUE - * if this roster item should continue to be processed - */ -gboolean jabber_google_roster_incoming(JabberStream *js, xmlnode *item); - -void jabber_google_presence_incoming(JabberStream *js, const char *who, JabberBuddyResource *jbr); -char *jabber_google_presence_outgoing(PurpleStatus *tune); - -void jabber_google_roster_add_deny(JabberStream *js, const char *who); -void jabber_google_roster_rem_deny(JabberStream *js, const char *who); - -char *jabber_google_format_to_html(const char *text); - -gboolean jabber_google_session_initiate(JabberStream *js, const gchar *who, PurpleMediaSessionType type); -void jabber_google_session_parse(JabberStream *js, const char *from, JabberIqType type, const char *iq, xmlnode *session); - void jabber_google_handle_jingle_info(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *child); void jabber_google_send_jingle_info(JabberStream *js); -void google_buddy_node_chat(PurpleBlistNode *node, gpointer data); -#endif /* PURPLE_JABBER_GOOGLE_H_ */ +#endif /* PURPLE_JABBER_GOOGLE_ROSTER_H_ */ \ No newline at end of file diff --git a/purple/libpurple/protocols/jabber/google/relay.c b/purple/libpurple/protocols/jabber/google/relay.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/relay.c @@ -0,0 +1,151 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" + +#include "relay.h" + +typedef struct { + GoogleSession *session; + JabberGoogleRelayCallback *cb; +} JabberGoogleRelayCallbackData; + +static void +jabber_google_relay_parse_response(const gchar *response, gchar **ip, + guint *udp, guint *tcp, guint *ssltcp, gchar **username, gchar **password) +{ + gchar **lines = g_strsplit(response, "\n", -1); + int i = 0; + + for (; lines[i] ; i++) { + gchar *line = lines[i]; + gchar **parts = g_strsplit(line, "=", 2); + + if (parts[0] && parts[1]) { + if (purple_strequal(parts[0], "relay.ip")) { + *ip = g_strdup(parts[1]); + } else if (purple_strequal(parts[0], "relay.udp_port")) { + *udp = atoi(parts[1]); + } else if (purple_strequal(parts[0], "relay.tcp_port")) { + *tcp = atoi(parts[1]); + } else if (purple_strequal(parts[0], "relay.ssltcp_port")) { + *ssltcp = atoi(parts[1]); + } else if (purple_strequal(parts[0], "username")) { + *username = g_strdup(parts[1]); + } else if (purple_strequal(parts[0], "password")) { + *password = g_strdup(parts[1]); + } + } + g_strfreev(parts); + } + + g_strfreev(lines); +} + +static void +jabber_google_relay_remove_url_data(JabberStream *js, + PurpleUtilFetchUrlData *url_data) +{ + GList *iter = js->google_relay_requests; + + while (iter) { + if (iter->data == url_data) { + js->google_relay_requests = + g_list_delete_link(js->google_relay_requests, iter); + break; + } + } +} + +static void +jabber_google_relay_fetch_cb(PurpleUtilFetchUrlData *url_data, + gpointer user_data, const gchar *url_text, gsize len, + const gchar *error_message) +{ + JabberGoogleRelayCallbackData *data = + (JabberGoogleRelayCallbackData *) user_data; + GoogleSession *session = data->session; + JabberStream *js = session->js; + JabberGoogleRelayCallback *cb = data->cb; + gchar *relay_ip = NULL; + guint relay_udp = 0; + guint relay_tcp = 0; + guint relay_ssltcp = 0; + gchar *relay_username = NULL; + gchar *relay_password = NULL; + + g_free(data); + + if (url_data) { + jabber_google_relay_remove_url_data(js, url_data); + } + + purple_debug_info("jabber", "got response on HTTP request to relay server\n"); + + if (url_text && len > 0) { + purple_debug_info("jabber", "got Google relay request response:\n%s\n", + url_text); + jabber_google_relay_parse_response(url_text, &relay_ip, &relay_udp, + &relay_tcp, &relay_ssltcp, &relay_username, &relay_password); + } + + if (cb) + cb(session, relay_ip, relay_udp, relay_tcp, relay_ssltcp, + relay_username, relay_password); + + g_free(relay_ip); + g_free(relay_username); + g_free(relay_password); +} + +void +jabber_google_do_relay_request(JabberStream *js, GoogleSession *session, + JabberGoogleRelayCallback cb) +{ + PurpleUtilFetchUrlData *url_data = NULL; + gchar *url = g_strdup_printf("http://%s", js->google_relay_host); + /* yes, the relay token is included twice as different request headers, + this is apparently needed to make Google's relay servers work... */ + gchar *request = + g_strdup_printf("GET /create_session HTTP/1.0\r\n" + "Host: %s\r\n" + "X-Talk-Google-Relay-Auth: %s\r\n" + "X-Google-Relay-Auth: %s\r\n\r\n", + js->google_relay_host, js->google_relay_token, js->google_relay_token); + JabberGoogleRelayCallbackData *data = g_new0(JabberGoogleRelayCallbackData, 1); + + data->session = session; + data->cb = cb; + purple_debug_info("jabber", + "sending Google relay request %s to %s\n", request, url); + url_data = + purple_util_fetch_url_request(url, FALSE, NULL, FALSE, request, FALSE, + jabber_google_relay_fetch_cb, data); + if (url_data) { + js->google_relay_requests = + g_list_prepend(js->google_relay_requests, url_data); + } else { + purple_debug_error("jabber", "unable to create Google relay request\n"); + jabber_google_relay_fetch_cb(NULL, data, NULL, 0, NULL); + } + g_free(url); + g_free(request); +} \ No newline at end of file diff --git a/purple/libpurple/protocols/jabber/google/relay.h b/purple/libpurple/protocols/jabber/google/relay.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/jabber/google/relay.h @@ -0,0 +1,33 @@ +/** + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef JABBER_GOOGLE_RELAY +#define JABBER_GOOGLE_RELAY + +#include "google_session.h" + +typedef void (JabberGoogleRelayCallback)(GoogleSession *session, const gchar *ip, + guint udp_port, guint tcp_port, guint tls_port, + const gchar *username, const gchar *password); + +void jabber_google_do_relay_request(JabberStream *js, GoogleSession *session, + JabberGoogleRelayCallback cb); + +#endif /* JABBER_GOOGLE_RELAY */ diff --git a/purple/libpurple/protocols/jabber/iq.c b/purple/libpurple/protocols/jabber/iq.c --- a/purple/libpurple/protocols/jabber/iq.c +++ b/purple/libpurple/protocols/jabber/iq.c @@ -23,17 +23,20 @@ #include "internal.h" #include "core.h" #include "debug.h" #include "prefs.h" #include "util.h" #include "buddy.h" #include "disco.h" -#include "google.h" +#include "google/gmail.h" +#include "google/google.h" +#include "google/jingleinfo.h" +#include "google/google_session.h" #include "iq.h" #ifdef USE_JINGLE #include "jingle/jingle.h" #endif #include "oob.h" #include "roster.h" #include "si.h" #include "ping.h" diff --git a/purple/libpurple/protocols/jabber/jabber.c b/purple/libpurple/protocols/jabber/jabber.c --- a/purple/libpurple/protocols/jabber/jabber.c +++ b/purple/libpurple/protocols/jabber/jabber.c @@ -46,17 +46,19 @@ #include "xmlnode.h" #include "auth.h" #include "buddy.h" #include "caps.h" #include "chat.h" #include "data.h" #include "disco.h" -#include "google.h" +#include "google/google.h" +#include "google/google_roster.h" +#include "google/google_session.h" #include "ibb.h" #include "iq.h" #include "jutil.h" #include "message.h" #include "parser.h" #include "presence.h" #include "jabber.h" #include "roster.h" @@ -202,61 +204,84 @@ static char *jabber_prep_resource(char * strcpy(hostname, "localhost"); } hostname[sizeof(hostname) - 1] = '\0'; /* We want only the short hostname, not the FQDN - this will prevent the * resource string from being unreasonably long on systems which stuff the * whole FQDN in the hostname */ if((dot = strchr(hostname, '.'))) - dot = '\0'; + *dot = '\0'; return purple_strreplace(input, "__HOSTNAME__", hostname); } static gboolean jabber_process_starttls(JabberStream *js, xmlnode *packet) { PurpleAccount *account; xmlnode *starttls; account = purple_connection_get_account(js->gc); +#if 0 + /* + * This code DOES NOT EXIST, will never be enabled by default, and + * will never ever be supported (by me). + * It's literally *only* for developer testing. + */ + { + const gchar *connection_security = purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS); + if (!g_str_equal(connection_security, "none") && + purple_ssl_is_supported()) { + jabber_send_raw(js, + "", -1); + return TRUE; + } + } +#else if(purple_ssl_is_supported()) { jabber_send_raw(js, "", -1); return TRUE; + } else { + purple_debug_warning("jabber", "No libpurple TLS/SSL support found."); } +#endif starttls = xmlnode_get_child(packet, "starttls"); if(xmlnode_get_child(starttls, "required")) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Server requires TLS/SSL, but no TLS/SSL support was found.")); return TRUE; } - if(purple_account_get_bool(account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS)) { + if (g_str_equal("require_tls", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("You require encryption, but no TLS/SSL support was found.")); return TRUE; } return FALSE; } void jabber_stream_features_parse(JabberStream *js, xmlnode *packet) { - if(xmlnode_get_child(packet, "starttls")) { - if(jabber_process_starttls(js, packet)) { + PurpleAccount *account = purple_connection_get_account(js->gc); + const char *connection_security = + purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS); + + if (xmlnode_get_child(packet, "starttls")) { + if (jabber_process_starttls(js, packet)) { jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); return; } - } else if(purple_account_get_bool(js->gc->account, "require_tls", JABBER_DEFAULT_REQUIRE_TLS) && !jabber_stream_is_ssl(js)) { + } else if (g_str_equal(connection_security, "require_tls") && !jabber_stream_is_ssl(js)) { purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, _("You require encryption, but it is not available on this server.")); return; } if(js->registration) { jabber_register_start(js); @@ -449,17 +474,17 @@ void jabber_send_raw(JabberStream *js, c account = purple_connection_get_account(gc); /* because printing a tab to debug every minute gets old */ if(strcmp(data, "\t")) { const char *username; char *text = NULL, *last_part = NULL, *tag_start = NULL; /* Because debug logs with plaintext passwords make me sad */ - if(js->state != JABBER_STREAM_CONNECTED && + if (!purple_debug_is_unsafe() && js->state != JABBER_STREAM_CONNECTED && /* Either or ... */ (((tag_start = strstr(data, "") && (tag_start = strstr(tag_start, ""))))) { char *data_start, *tag_end = strchr(tag_start, '>'); text = g_strdup(data); @@ -734,47 +759,47 @@ jabber_login_callback_ssl(gpointer data, /* Tell the app that we're doing encryption */ jabber_stream_set_state(js, JABBER_STREAM_INITIALIZING_ENCRYPTION); } static void txt_resolved_cb(GList *responses, gpointer data) { JabberStream *js = data; + gboolean found = FALSE; js->srv_query_data = NULL; - if (responses == NULL) { - purple_debug_warning("jabber", "Unable to find alternative XMPP connection " - "methods after failing to connect directly.\n"); - purple_connection_error_reason(js->gc, - PURPLE_CONNECTION_ERROR_NETWORK_ERROR, - _("Unable to connect")); - return; - } - while (responses) { PurpleTxtResponse *resp = responses->data; gchar **token; token = g_strsplit(purple_txt_response_get_content(resp), "=", 2); if (!strcmp(token[0], "_xmpp-client-xbosh")) { purple_debug_info("jabber","Found alternative connection method using %s at %s.\n", token[0], token[1]); js->bosh = jabber_bosh_connection_init(js, token[1]); g_strfreev(token); break; } g_strfreev(token); purple_txt_response_destroy(resp); responses = g_list_delete_link(responses, responses); } if (js->bosh) { + found = TRUE; jabber_bosh_connection_connect(js->bosh); - } else { - purple_debug_info("jabber","Didn't find an alternative connection method.\n"); + } + + if (!found) { + purple_debug_warning("jabber", "Unable to find alternative XMPP connection " + "methods after failing to connect directly.\n"); + purple_connection_error_reason(js->gc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Unable to connect")); + return; } if (responses) { g_list_foreach(responses, (GFunc)purple_txt_response_destroy, NULL); g_list_free(responses); } } @@ -940,16 +965,21 @@ jabber_stream_new(PurpleAccount *account g_free(user); /* Destroying the connection will free the JabberStream */ return NULL; } js->buddies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)jabber_buddy_free); + /* This is overridden during binding, but we need it here + * in case the server only does legacy non-sasl auth!. + */ + purple_connection_set_display_name(gc, user); + js->user_jb = jabber_buddy_find(js, user, TRUE); g_free(user); if (!js->user_jb) { /* This basically *can't* fail, but for good measure... */ purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, _("Invalid XMPP ID")); /* Destroying the connection will free the JabberStream */ @@ -969,16 +999,19 @@ jabber_stream_new(PurpleAccount *account js->max_inactivity = DEFAULT_INACTIVITY_TIME; /* Set the default protocol version to 1.0. Overridden in parser.c. */ js->protocol_version.major = 1; js->protocol_version.minor = 0; js->sessions = NULL; js->stun_ip = NULL; js->stun_port = 0; js->stun_query = NULL; + js->google_relay_token = NULL; + js->google_relay_host = NULL; + js->google_relay_requests = NULL; /* if we are idle, set idle-ness on the stream (this could happen if we get disconnected and the reconnects while being idle. I don't think it makes sense to do this when registering a new account... */ presence = purple_account_get_presence(account); if (purple_presence_is_idle(presence)) js->idle = purple_presence_get_idle_time(presence); @@ -1011,17 +1044,17 @@ jabber_stream_connect(JabberStream *js) } return; } js->certificate_CN = g_strdup(connect_server[0] ? connect_server : js->user->domain); /* if they've got old-ssl mode going, we probably want to ignore SRV lookups */ - if(purple_account_get_bool(account, "old_ssl", FALSE)) { + if (g_str_equal("old_ssl", purple_account_get_string(account, "connection_security", JABBER_DEFAULT_REQUIRE_TLS))) { if(purple_ssl_is_supported()) { js->gsc = purple_ssl_connect(account, js->certificate_CN, purple_account_get_int(account, "port", 5223), jabber_login_callback_ssl, jabber_ssl_connect_failure, gc); if (!js->gsc) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("Unable to establish SSL connection")); @@ -1630,16 +1663,18 @@ void jabber_close(PurpleConnection *gc) if (js->auth_mech && js->auth_mech->dispose) js->auth_mech->dispose(js); #ifdef HAVE_CYRUS_SASL if(js->sasl) sasl_dispose(&js->sasl); if(js->sasl_mechs) g_string_free(js->sasl_mechs, TRUE); g_free(js->sasl_cb); + /* Note: _not_ g_free. See auth_cyrus.c:jabber_sasl_cb_secret */ + free(js->sasl_secret); #endif g_free(js->serverFQDN); while(js->commands) { JabberAdHocCommands *cmd = js->commands->data; g_free(cmd->jid); g_free(cmd->node); g_free(cmd->name); g_free(cmd); @@ -1672,16 +1707,31 @@ void jabber_close(PurpleConnection *gc) js->stun_ip = NULL; /* cancel DNS query for STUN, if one is ongoing */ if (js->stun_query) { purple_dnsquery_destroy(js->stun_query); js->stun_query = NULL; } + /* remove Google relay-related stuff */ + g_free(js->google_relay_token); + g_free(js->google_relay_host); + if (js->google_relay_requests) { + while (js->google_relay_requests) { + PurpleUtilFetchUrlData *url_data = + (PurpleUtilFetchUrlData *) js->google_relay_requests->data; + purple_util_fetch_url_cancel(url_data); + g_free(url_data); + js->google_relay_requests = + g_list_delete_link(js->google_relay_requests, + js->google_relay_requests); + } + } + g_free(js); gc->proto_data = NULL; } void jabber_stream_set_state(JabberStream *js, JabberStreamState state) { #define JABBER_CONNECT_STEPS ((js->gsc || js->state == JABBER_STREAM_INITIALIZING_ENCRYPTION) ? 9 : 5) @@ -2614,23 +2664,29 @@ char *jabber_parse_error(JabberStream *j const char *code = NULL, *text = NULL; const char *xmlns = xmlnode_get_namespace(packet); char *cdata = NULL; #define SET_REASON(x) \ if(reason != NULL) { *reason = x; } if((error = xmlnode_get_child(packet, "error"))) { + xmlnode *t = xmlnode_get_child_with_namespace(error, "text", NS_XMPP_STANZAS); + if (t) + cdata = xmlnode_get_data(t); +#if 0 cdata = xmlnode_get_data(error); +#endif code = xmlnode_get_attrib(error, "code"); /* Stanza errors */ if(xmlnode_get_child(error, "bad-request")) { text = _("Bad Request"); } else if(xmlnode_get_child(error, "conflict")) { + SET_REASON(PURPLE_CONNECTION_ERROR_NAME_IN_USE); text = _("Conflict"); } else if(xmlnode_get_child(error, "feature-not-implemented")) { text = _("Feature Not Implemented"); } else if(xmlnode_get_child(error, "forbidden")) { text = _("Forbidden"); } else if(xmlnode_get_child(error, "gone")) { text = _("Gone"); } else if(xmlnode_get_child(error, "internal-server-error")) { @@ -3228,19 +3284,19 @@ jabber_initiate_media(PurpleAccount *acc /* they've specified a resource, no need to ask or * default or anything, just do it */ jb = jabber_buddy_find(js, who, FALSE); jbr = jabber_buddy_find_resource(jb, resource); g_free(resource); if (type & PURPLE_MEDIA_AUDIO && - !jabber_resource_has_capability(jbr, - JINGLE_APP_RTP_SUPPORT_AUDIO) && - jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE)) + !jabber_resource_has_capability(jbr, + JINGLE_APP_RTP_SUPPORT_AUDIO) && + jabber_resource_has_capability(jbr, NS_GOOGLE_VOICE)) return jabber_google_session_initiate(js, who, type); else return jingle_rtp_initiate_media(js, who, type); } jb = jabber_buddy_find(js, who, FALSE); if(!jb || !jb->resources) { @@ -3492,17 +3548,17 @@ gboolean jabber_can_receive_file(PurpleC static PurpleCmdRet jabber_cmd_mood(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { JabberStream *js = conv->account->gc->proto_data; if (js->pep) { /* if no argument was given, unset mood */ - if (!args | !args[0]) { + if (!args || !args[0]) { jabber_mood_set(js, NULL, NULL); } else if (!args[1]) { jabber_mood_set(js, args[0], NULL); } else { jabber_mood_set(js, args[0], args[1]); } return PURPLE_CMD_RET_OK; diff --git a/purple/libpurple/protocols/jabber/jabber.h b/purple/libpurple/protocols/jabber/jabber.h --- a/purple/libpurple/protocols/jabber/jabber.h +++ b/purple/libpurple/protocols/jabber/jabber.h @@ -77,17 +77,17 @@ typedef struct _JabberStream JabberStrea #include "bosh.h" #ifdef HAVE_CYRUS_SASL #include #endif #define CAPS0115_NODE "http://pidgin.im/" -#define JABBER_DEFAULT_REQUIRE_TLS TRUE +#define JABBER_DEFAULT_REQUIRE_TLS "require_starttls" #define JABBER_DEFAULT_FT_PROXIES "proxy.eu.jabber.org" /* Index into attention_types list */ #define JABBER_BUZZ 0 typedef enum { JABBER_STREAM_OFFLINE, JABBER_STREAM_CONNECTING, @@ -203,16 +203,17 @@ struct _JabberStream char *gmail_last_time; char *gmail_last_tid; char *serverFQDN; #ifdef HAVE_CYRUS_SASL sasl_conn_t *sasl; sasl_callback_t *sasl_cb; + sasl_secret_t *sasl_secret; const char *current_mech; int auth_fail_count; int sasl_state; int sasl_maxbuf; GString *sasl_mechs; #endif @@ -272,17 +273,22 @@ struct _JabberStream /* keep a hash table of JingleSessions */ GHashTable *sessions; /* maybe this should only be present when USE_VV? */ gchar *stun_ip; int stun_port; PurpleDnsQueryData *stun_query; - /* later add stuff to handle TURN relays... */ + + /* stuff for Google's relay handling */ + gchar *google_relay_token; + gchar *google_relay_host; + GList *google_relay_requests; /* the HTTP requests to get */ + /* relay info */ }; typedef gboolean (JabberFeatureEnabled)(JabberStream *js, const gchar *namespace); typedef struct _JabberFeature { gchar *namespace; JabberFeatureEnabled *is_enabled; @@ -329,21 +335,35 @@ char *jabber_get_next_id(JabberStream *j * reason. * @param js the stream on which the error occurred. * @param packet the error packet * @param reason where to store the disconnection reason, or @c NULL if you * don't care or you don't intend to close the connection. */ char *jabber_parse_error(JabberStream *js, xmlnode *packet, PurpleConnectionError *reason); -void jabber_add_feature(const gchar *namespace, JabberFeatureEnabled cb); /* cb may be NULL */ +/** + * Add a feature to the list of features advertised via disco#info. If you + * call this while accounts are connected, Bad Things(TM) will happen because + * the Entity Caps hash will be out-of-date (which should be fixed :/) + * + * @param namespace The namespace of the feature + * @param cb A callback determining whether or not this feature + * will advertised; may be NULL. + */ +void jabber_add_feature(const gchar *namespace, JabberFeatureEnabled cb); void jabber_remove_feature(const gchar *namespace); -/** Adds an identity to this jabber library instance. For list of valid values visit the - * website of the XMPP Registrar ( http://www.xmpp.org/registrar/disco-categories.html#client ). +/** Adds an identity to this jabber library instance. For list of valid values + * visit the website of the XMPP Registrar + * (http://www.xmpp.org/registrar/disco-categories.html#client). + * + * Like with jabber_add_feature, if you call this while accounts are connected, + * Bad Things will happen. + * * @param category the category of the identity. * @param type the type of the identity. * @param language the language localization of the name. Can be NULL. * @param name the name of the identity. */ void jabber_add_identity(const gchar *category, const gchar *type, const gchar *lang, const gchar *name); /** diff --git a/purple/libpurple/protocols/jabber/libxmpp.c b/purple/libpurple/protocols/jabber/libxmpp.c --- a/purple/libpurple/protocols/jabber/libxmpp.c +++ b/purple/libpurple/protocols/jabber/libxmpp.c @@ -36,17 +36,17 @@ #include "jabber.h" #include "chat.h" #include "disco.h" #include "message.h" #include "roster.h" #include "si.h" #include "message.h" #include "presence.h" -#include "google.h" +#include "google/google.h" #include "pep.h" #include "usermood.h" #include "usertune.h" #include "caps.h" #include "data.h" #include "ibb.h" static PurplePlugin *my_protocol = NULL; @@ -248,33 +248,47 @@ static gboolean xmpp_uri_handler(const c } static void init_plugin(PurplePlugin *plugin) { PurpleAccountUserSplit *split; PurpleAccountOption *option; + GList *encryption_values = NULL; /* Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im */ split = purple_account_user_split_new(_("Domain"), "gmail.com", '@'); purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); split = purple_account_user_split_new(_("Resource"), "Instantbird", '/'); purple_account_user_split_set_reverse(split, FALSE); prpl_info.user_splits = g_list_append(prpl_info.user_splits, split); - option = purple_account_option_bool_new(_("Require SSL/TLS"), "require_tls", JABBER_DEFAULT_REQUIRE_TLS); +#define ADD_VALUE(list, desc, v) { \ + PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \ + kvp->key = g_strdup((desc)); \ + kvp->value = g_strdup((v)); \ + list = g_list_prepend(list, kvp); \ +} + + ADD_VALUE(encryption_values, _("Require encryption"), "require_tls"); + ADD_VALUE(encryption_values, _("Use encryption if available"), "opportunistic_tls"); + ADD_VALUE(encryption_values, _("Use old-style SSL"), "old_ssl"); +#if 0 + ADD_VALUE(encryption_values, "None", "none"); +#endif + encryption_values = g_list_reverse(encryption_values); + +#undef ADD_VALUE + + option = purple_account_option_list_new(_("Connection security"), "connection_security", encryption_values); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); - - option = purple_account_option_bool_new(_("Force old (port 5223) SSL"), "old_ssl", FALSE); - prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, - option); + option); option = purple_account_option_bool_new( _("Allow plaintext auth over unencrypted streams"), "auth_plain_in_clear", FALSE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_int_new(_("Connect port"), "port", 5222); diff --git a/purple/libpurple/protocols/jabber/message.c b/purple/libpurple/protocols/jabber/message.c --- a/purple/libpurple/protocols/jabber/message.c +++ b/purple/libpurple/protocols/jabber/message.c @@ -25,17 +25,17 @@ #include "debug.h" #include "notify.h" #include "server.h" #include "util.h" #include "adhoccommands.h" #include "buddy.h" #include "chat.h" #include "data.h" -#include "google.h" +#include "google/google.h" #include "message.h" #include "xmlnode.h" #include "pep.h" #include "smiley.h" #include "iq.h" #include @@ -292,30 +292,29 @@ static void handle_error(JabberMessage * purple_notify_formatted(jm->js->gc, _("XMPP Message Error"), _("XMPP Message Error"), buf, jm->xhtml ? jm->xhtml : jm->body, NULL, NULL); g_free(buf); } static void handle_buzz(JabberMessage *jm) { - PurpleBuddy *buddy; PurpleAccount *account; /* Delayed buzz MUST NOT be accepted */ if(jm->delayed) return; /* Reject buzz when it's not enabled */ if(!jm->js->allowBuzz) return; account = purple_connection_get_account(jm->js->gc); - if ((buddy = purple_find_buddy(account, jm->from)) == NULL) + if (purple_find_buddy(account, jm->from) == NULL) return; /* Do not accept buzzes from unknown people */ /* xmpp only has 1 attention type, so index is 0 */ purple_prpl_got_attention(jm->js->gc, jm->from, 0); } /* used internally by the functions below */ typedef struct { diff --git a/purple/libpurple/protocols/jabber/oob.c b/purple/libpurple/protocols/jabber/oob.c --- a/purple/libpurple/protocols/jabber/oob.c +++ b/purple/libpurple/protocols/jabber/oob.c @@ -180,17 +180,17 @@ static void jabber_oob_xfer_recv_error(P jabber_oob_xfer_free(xfer); } static void jabber_oob_xfer_recv_denied(PurpleXfer *xfer) { jabber_oob_xfer_recv_error(xfer, "406"); } -static void jabber_oob_xfer_recv_canceled(PurpleXfer *xfer) { +static void jabber_oob_xfer_recv_cancelled(PurpleXfer *xfer) { jabber_oob_xfer_recv_error(xfer, "404"); } void jabber_oob_parse(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *querynode) { JabberOOBXfer *jox; PurpleXfer *xfer; char *filename; @@ -228,17 +228,17 @@ void jabber_oob_parse(JabberStream *js, purple_xfer_set_filename(xfer, filename); g_free(filename); purple_xfer_set_init_fnc(xfer, jabber_oob_xfer_init); purple_xfer_set_end_fnc(xfer, jabber_oob_xfer_end); purple_xfer_set_request_denied_fnc(xfer, jabber_oob_xfer_recv_denied); - purple_xfer_set_cancel_recv_fnc(xfer, jabber_oob_xfer_recv_canceled); + purple_xfer_set_cancel_recv_fnc(xfer, jabber_oob_xfer_recv_cancelled); purple_xfer_set_read_fnc(xfer, jabber_oob_xfer_read); purple_xfer_set_start_fnc(xfer, jabber_oob_xfer_start); js->oob_file_transfers = g_list_append(js->oob_file_transfers, xfer); purple_xfer_request(xfer); } } diff --git a/purple/libpurple/protocols/jabber/parser.c b/purple/libpurple/protocols/jabber/parser.c --- a/purple/libpurple/protocols/jabber/parser.c +++ b/purple/libpurple/protocols/jabber/parser.c @@ -88,20 +88,35 @@ jabber_parser_element_start_libxml(void } else if(!xmlStrcmp(attributes[i], (xmlChar*) "id")) { g_free(js->stream_id); js->stream_id = attrib; } else { g_free(attrib); } } - if (js->stream_id == NULL) + if (js->stream_id == NULL) { +#if 0 + /* This was underspecified in rfc3920 as only being a SHOULD, so + * we cannot rely on it. See #12331 and Oracle's server. + */ purple_connection_error_reason(js->gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, _("XMPP stream missing ID")); +#else + /* Instead, let's make up a placeholder stream ID, which we need + * to do because we flag on it being NULL as a special case + * in this parsing code. + */ + js->stream_id = g_strdup(""); + purple_debug_info("jabber", "Server failed to specify a stream " + "ID (underspecified in rfc3920, but intended " + "to be a MUST; digest legacy auth may fail.\n"); +#endif + } } else { if(js->current) node = xmlnode_new_child(js->current, (const char*) element_name); else node = xmlnode_new((const char*) element_name); xmlnode_set_namespace(node, (const char*) namespace); xmlnode_set_prefix(node, (const char *)prefix); diff --git a/purple/libpurple/protocols/jabber/presence.c b/purple/libpurple/protocols/jabber/presence.c --- a/purple/libpurple/protocols/jabber/presence.c +++ b/purple/libpurple/protocols/jabber/presence.c @@ -29,17 +29,18 @@ #include "request.h" #include "server.h" #include "status.h" #include "util.h" #include "xmlnode.h" #include "buddy.h" #include "chat.h" -#include "google.h" +#include "google/google.h" +#include "google/google_presence.h" #include "presence.h" #include "iq.h" #include "jutil.h" #include "adhoccommands.h" #include "usermood.h" #include "usertune.h" @@ -989,22 +990,24 @@ void jabber_presence_parse(JabberStream presence.state = JABBER_BUDDY_STATE_UNKNOWN; } else { purple_debug_warning("jabber", "Ignoring presence with invalid type " "'%s'\n", type); goto out; } for (child = packet->child; child; child = child->next) { + const char *xmlns; char *key; JabberPresenceHandler *pih; if (child->type != XMLNODE_TYPE_TAG) continue; - key = g_strdup_printf("%s %s", child->name, xmlnode_get_namespace(child)); + xmlns = xmlnode_get_namespace(child); + key = g_strdup_printf("%s %s", child->name, xmlns ? xmlns : ""); pih = g_hash_table_lookup(presence_handlers, key); g_free(key); if (pih) pih(js, &presence, child); } if (presence.delayed && presence.idle) { /* Delayed and idle, so update idle time */ diff --git a/purple/libpurple/protocols/jabber/roster.c b/purple/libpurple/protocols/jabber/roster.c --- a/purple/libpurple/protocols/jabber/roster.c +++ b/purple/libpurple/protocols/jabber/roster.c @@ -22,17 +22,18 @@ */ #include "internal.h" #include "debug.h" #include "server.h" #include "util.h" #include "buddy.h" #include "chat.h" -#include "google.h" +#include "google/google.h" +#include "google/google_roster.h" #include "presence.h" #include "roster.h" #include "iq.h" #include /* Take a list of strings and join them with a ", " separator */ static gchar *roster_groups_join(GSList *list) @@ -71,22 +72,19 @@ static void roster_request_cb(JabberStre } jabber_roster_parse(js, from, type, id, query); jabber_stream_set_state(js, JABBER_STREAM_CONNECTED); } void jabber_roster_request(JabberStream *js) { - PurpleAccount *account; JabberIq *iq; xmlnode *query; - account = purple_connection_get_account(js->gc); - iq = jabber_iq_new_query(js, JABBER_IQ_GET, "jabber:iq:roster"); query = xmlnode_get_child(iq->node, "query"); if (js->server_caps & JABBER_CAP_GOOGLE_ROSTER) { xmlnode_set_attrib(query, "xmlns:gr", NS_GOOGLE_ROSTER); xmlnode_set_attrib(query, "gr:ext", "2"); } diff --git a/purple/libpurple/protocols/jabber/si.c b/purple/libpurple/protocols/jabber/si.c --- a/purple/libpurple/protocols/jabber/si.c +++ b/purple/libpurple/protocols/jabber/si.c @@ -978,39 +978,28 @@ jabber_si_xfer_bytestreams_send_init(Pur } } static void jabber_si_xfer_ibb_error_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); - JabberStream *js = jabber_ibb_session_get_js(sess); - PurpleConnection *gc = js->gc; - PurpleAccount *account = purple_connection_get_account(gc); purple_debug_error("jabber", "an error occurred during IBB file transfer\n"); - purple_xfer_error(purple_xfer_get_type(xfer), account, - jabber_ibb_session_get_who(sess), - _("An error occurred on the in-band bytestream transfer\n")); purple_xfer_cancel_remote(xfer); } static void jabber_si_xfer_ibb_closed_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); - JabberStream *js = jabber_ibb_session_get_js(sess); - PurpleConnection *gc = js->gc; - PurpleAccount *account = purple_connection_get_account(gc); purple_debug_info("jabber", "the remote user closed the transfer\n"); if (purple_xfer_get_bytes_remaining(xfer) > 0) { - purple_xfer_error(purple_xfer_get_type(xfer), account, - jabber_ibb_session_get_who(sess), _("Transfer was closed.")); purple_xfer_cancel_remote(xfer); } else { purple_xfer_set_completed(xfer, TRUE); purple_xfer_end(xfer); } } static void @@ -1132,28 +1121,22 @@ jabber_si_xfer_ibb_sent_cb(JabberIBBSess purple_xfer_prpl_ready(xfer); } } static void jabber_si_xfer_ibb_opened_cb(JabberIBBSession *sess) { PurpleXfer *xfer = (PurpleXfer *) jabber_ibb_session_get_user_data(sess); - JabberStream *js = jabber_ibb_session_get_js(sess); - PurpleConnection *gc = js->gc; - PurpleAccount *account = purple_connection_get_account(gc); if (jabber_ibb_session_get_state(sess) == JABBER_IBB_SESSION_OPENED) { purple_xfer_start(xfer, -1, NULL, 0); purple_xfer_prpl_ready(xfer); } else { /* error */ - purple_xfer_error(purple_xfer_get_type(xfer), account, - jabber_ibb_session_get_who(sess), - _("Failed to open in-band bytestream")); purple_xfer_end(xfer); } } static void jabber_si_xfer_ibb_send_init(JabberStream *js, PurpleXfer *xfer) { JabberSIXfer *jsx = (JabberSIXfer *) xfer->data; @@ -1661,22 +1644,18 @@ PurpleXfer *jabber_si_new_xfer(PurpleCon js->file_transfers = g_list_append(js->file_transfers, xfer); } return xfer; } void jabber_si_xfer_send(PurpleConnection *gc, const char *who, const char *file) { - JabberStream *js; - PurpleXfer *xfer; - js = gc->proto_data; - xfer = jabber_si_new_xfer(gc, who); if (file) purple_xfer_request_accepted(xfer, file); else purple_xfer_request(xfer); } @@ -1729,17 +1708,17 @@ void jabber_si_parse(JabberStream *js, c return; if(!from) return; /* if they've already sent us this file transfer with the same damn id * then we're gonna ignore it, until I think of something better to do * with it */ - if((xfer = jabber_si_xfer_find(js, stream_id, from))) + if(jabber_si_xfer_find(js, stream_id, from) != NULL) return; jsx = g_new0(JabberSIXfer, 1); jsx->local_streamhost_fd = -1; jsx->ibb_session = NULL; for(field = xmlnode_get_child(x, "field"); field; field = xmlnode_get_next_twin(field)) { diff --git a/purple/libpurple/protocols/msn/Makefile.in b/purple/libpurple/protocols/msn/Makefile.in --- a/purple/libpurple/protocols/msn/Makefile.in +++ b/purple/libpurple/protocols/msn/Makefile.in @@ -43,39 +43,42 @@ PROTOCOL = msn include $(srcdir)/../prpl.mk DEFINES += -DPACKAGE_NAME=\"purple\" CSRCS = \ cmdproc.c \ command.c \ contact.c \ - dialog.c \ directconn.c \ error.c \ group.c \ history.c \ httpconn.c \ msg.c \ msn.c \ nexus.c \ notification.c \ object.c \ oim.c \ + p2p.c \ page.c \ + sbconn.c \ servconn.c \ session.c \ slp.c \ slpcall.c \ slplink.c \ slpmsg.c \ + slpmsg_part.c \ soap.c \ state.c \ switchboard.c \ - sync.c \ table.c \ + tlv.c \ transaction.c \ user.c \ userlist.c \ + xfer.c \ msnutils.c \ $(NULL) include $(srcdir)/../prpl-rules.mk diff --git a/purple/libpurple/protocols/msn/README b/purple/libpurple/protocols/msn/README deleted file mode 100644 --- a/purple/libpurple/protocols/msn/README +++ /dev/null @@ -1,55 +0,0 @@ -MSNP14 Implementation -by Ma Yuan - -1. Introduction -------------- - -MSNP14 Protocol, proposed by Windows Live Messenger, is new, and there is no available implementation except the official one on Windows Platform. - -It has introduced many new features attractable to many users, such as: -* Offline Instant Message - You can send the offline Message to the offline User, - The message will be posted to that user the next time when he is online. - -* Communicate with Yahoo User - U can chat with the Yahoo User in MSN, That's Fantastic! Till now , - you can send text/Nudge to Yahoo User. - -* Windows Live ID authentition - WLM use the Window Live ID Authentication process,Known as Passport 3.0, - The procedure is totally different to the previous Passport 2.0 - -* Video/Audio Conversation - U can communicate with other's via Video/Audio. -(Though very interesting, not implemented in this version) - -2.New Features Added ------------------ - -Till now, This project has implemented the following Feature: -* Windows Live ID authentication. - -* Offline Instant Message -Now can send and receive the Offline Instant Message to MSN user and Yahoo User. - -*contact management -Can add/delete Contact -Can add/delete Group - -* Communicate with Yahoo User -Can send/receive Message/Nudge to Yahoo User. - -*. Changes to made to fit MSNP14 Protocol - -3. Reference -------------- - -The very useful sites of MSN Protocol: -MSNpiki site: -reverse engineer of MSN Protocol.up to dated. -http://msnpiki.msnfanatic.com/index.php/MSN_Protocol_Version_13 - -hypothetic site: -old MSN Protocol Introduction,but very useful for basic idea of MSN protocol -http://www.hypothetic.org/docs/msn/index.php - diff --git a/purple/libpurple/protocols/msn/cmdproc.c b/purple/libpurple/protocols/msn/cmdproc.c --- a/purple/libpurple/protocols/msn/cmdproc.c +++ b/purple/libpurple/protocols/msn/cmdproc.c @@ -16,18 +16,22 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" + #include "cmdproc.h" +#include "error.h" MsnCmdProc * msn_cmdproc_new(MsnSession *session) { MsnCmdProc *cmdproc; cmdproc = g_new0(MsnCmdProc, 1); @@ -49,17 +53,17 @@ msn_cmdproc_destroy(MsnCmdProc *cmdproc) while ((trans = g_queue_pop_head(cmdproc->txqueue)) != NULL) msn_transaction_destroy(trans); g_queue_free(cmdproc->txqueue); msn_history_destroy(cmdproc->history); if (cmdproc->last_cmd != NULL) - msn_command_destroy(cmdproc->last_cmd); + msn_command_unref(cmdproc->last_cmd); g_hash_table_destroy(cmdproc->multiparts); g_free(cmdproc); } void msn_cmdproc_process_queue(MsnCmdProc *cmdproc) @@ -113,21 +117,22 @@ msn_cmdproc_send_trans(MsnCmdProc *cmdpr size_t len; g_return_if_fail(cmdproc != NULL); g_return_if_fail(trans != NULL); servconn = cmdproc->servconn; if (!servconn->connected) { - /* TODO: Need to free trans */ + msn_transaction_destroy(trans); return; } - msn_history_add(cmdproc->history, trans); + if (trans->saveable) + msn_history_add(cmdproc->history, trans); data = msn_transaction_to_string(trans); len = strlen(data); show_debug_cmd(cmdproc, FALSE, data); if (trans->callbacks == NULL) @@ -146,89 +151,22 @@ msn_cmdproc_send_trans(MsnCmdProc *cmdpr */ g_free(trans->payload); trans->payload = NULL; trans->payload_len = 0; } msn_servconn_write(servconn, data, len); + if (!trans->saveable) + msn_transaction_destroy(trans); g_free(data); } void -msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command, - const char *format, ...) -{ - MsnServConn *servconn; - char *data; - char *params = NULL; - va_list arg; - size_t len; - - g_return_if_fail(cmdproc != NULL); - g_return_if_fail(command != NULL); - - servconn = cmdproc->servconn; - - if (!servconn->connected) - return; - - if (format != NULL) - { - va_start(arg, format); - params = g_strdup_vprintf(format, arg); - va_end(arg); - } - - if (params != NULL) - data = g_strdup_printf("%s %s\r\n", command, params); - else - data = g_strdup_printf("%s\r\n", command); - - g_free(params); - - len = strlen(data); - - show_debug_cmd(cmdproc, FALSE, data); - - msn_servconn_write(servconn, data, len); - - g_free(data); -} - -void -msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command, - const char *format, ...) -{ - MsnTransaction *trans; - va_list arg; - - g_return_if_fail(cmdproc != NULL); - g_return_if_fail(command != NULL); - - if (!cmdproc->servconn->connected) - return; - - trans = g_new0(MsnTransaction, 1); - - trans->cmdproc = cmdproc; - trans->command = g_strdup(command); - - if (format != NULL) - { - va_start(arg, format); - trans->params = g_strdup_vprintf(format, arg); - va_end(arg); - } - - msn_cmdproc_send_trans(cmdproc, trans); -} - -void msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload, int payload_len) { MsnCommand *last; g_return_if_fail(cmdproc != NULL); last = cmdproc->last_cmd; @@ -238,68 +176,83 @@ msn_cmdproc_process_payload(MsnCmdProc * if (last->payload_cb != NULL) last->payload_cb(cmdproc, last, payload, payload_len); } void msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { MsnMsgTypeCb cb; - const char *messageId = NULL; + const char *message_id = NULL; /* Multi-part messages */ - if ((messageId = msn_message_get_attr(msg, "Message-ID")) != NULL) { - const char *chunk_text = msn_message_get_attr(msg, "Chunks"); + message_id = msn_message_get_header_value(msg, "Message-ID"); + if (message_id != NULL) { + /* This is the first in a series of chunks */ + + const char *chunk_text = msn_message_get_header_value(msg, "Chunks"); guint chunk; if (chunk_text != NULL) { chunk = strtol(chunk_text, NULL, 10); - /* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent + /* 1024 chunks of ~1300 bytes is ~1MB, which seems OK to prevent some random client causing pidgin to hog a ton of memory. Probably should figure out the maximum that the official client actually supports, though. */ if (chunk > 0 && chunk < 1024) { msg->total_chunks = chunk; msg->received_chunks = 1; - g_hash_table_insert(cmdproc->multiparts, (gpointer)messageId, msn_message_ref(msg)); - purple_debug_info("msn", "Received chunked message, messageId: '%s', total chunks: %d\n", - messageId, chunk); + g_hash_table_insert(cmdproc->multiparts, (gpointer)message_id, msn_message_ref(msg)); + purple_debug_info("msn", "Received chunked message, message_id: '%s', total chunks: %d\n", + message_id, chunk); } else { - purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", messageId, chunk); + purple_debug_error("msn", "MessageId '%s' has too many chunks: %d\n", message_id, chunk); } return; } else { - chunk_text = msn_message_get_attr(msg, "Chunk"); + chunk_text = msn_message_get_header_value(msg, "Chunk"); if (chunk_text != NULL) { - MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, messageId); + /* This is one chunk in a series of chunks */ + + MsnMessage *first = g_hash_table_lookup(cmdproc->multiparts, message_id); chunk = strtol(chunk_text, NULL, 10); - if (first == NULL) { - purple_debug_error("msn", - "Unable to find first chunk of messageId '%s' to correspond with chunk %d.\n", - messageId, chunk+1); - } else if (first->received_chunks == chunk) { + if (first != NULL) { + if (first->received_chunks != chunk) { + /* + * We received an out of order chunk number (i.e. not the + * next one in the sequence). Not sure if this can happen + * legitimately, but we definitely don't handle it right + * now. + */ + g_hash_table_remove(cmdproc->multiparts, message_id); + return; + } + /* Chunk is from 1 to total-1 (doesn't count first one) */ - purple_debug_info("msn", "Received chunk %d of %d, messageId: '%s'\n", - chunk+1, first->total_chunks, messageId); + purple_debug_info("msn", "Received chunk %d of %d, message_id: '%s'\n", + chunk + 1, first->total_chunks, message_id); first->body = g_realloc(first->body, first->body_len + msg->body_len); memcpy(first->body + first->body_len, msg->body, msg->body_len); first->body_len += msg->body_len; first->received_chunks++; if (first->received_chunks != first->total_chunks) + /* We're waiting for more chunks */ return; - else - /* We're done! Send it along... The caller takes care of - freeing the old one. */ - msg = first; + + /* + * We have all the chunks for this message, great! Send + * it along... The caller takes care of freeing the old one. + */ + msg = first; } else { - /* TODO: Can you legitimately receive chunks out of order? */ - g_hash_table_remove(cmdproc->multiparts, messageId); - return; + purple_debug_error("msn", + "Unable to find first chunk of message_id '%s' to correspond with chunk %d.\n", + message_id, chunk + 1); } } else { - purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", messageId); + purple_debug_error("msn", "Received MessageId '%s' with no chunk number!\n", message_id); } } } if (msn_message_get_content_type(msg) == NULL) { purple_debug_misc("msn", "failed to find message content\n"); return; @@ -309,18 +262,18 @@ msn_cmdproc_process_msg(MsnCmdProc *cmdp msn_message_get_content_type(msg)); if (cb != NULL) cb(cmdproc, msg); else purple_debug_warning("msn", "Unhandled content-type '%s'\n", msn_message_get_content_type(msg)); - if (messageId != NULL) - g_hash_table_remove(cmdproc->multiparts, messageId); + if (message_id != NULL) + g_hash_table_remove(cmdproc->multiparts, message_id); } void msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnTransCb cb = NULL; MsnTransaction *trans = NULL; @@ -371,14 +324,14 @@ msn_cmdproc_process_cmd(MsnCmdProc *cmdp } void msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command) { show_debug_cmd(cmdproc, TRUE, command); if (cmdproc->last_cmd != NULL) - msn_command_destroy(cmdproc->last_cmd); + msn_command_unref(cmdproc->last_cmd); cmdproc->last_cmd = msn_command_from_string(command); msn_cmdproc_process_cmd(cmdproc, cmdproc->last_cmd); } diff --git a/purple/libpurple/protocols/msn/cmdproc.h b/purple/libpurple/protocols/msn/cmdproc.h --- a/purple/libpurple/protocols/msn/cmdproc.h +++ b/purple/libpurple/protocols/msn/cmdproc.h @@ -22,17 +22,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_CMDPROC_H #define MSN_CMDPROC_H typedef struct _MsnCmdProc MsnCmdProc; #include "command.h" -#include "error.h" #include "history.h" #include "servconn.h" #include "session.h" #include "table.h" struct _MsnCmdProc { MsnSession *session; @@ -46,28 +45,55 @@ struct _MsnCmdProc MsnHistory *history; GHashTable *multiparts; /**< Multi-part message ID's */ void *data; /**< Extra data, like the switchboard. */ }; +/** + * Creates a MsnCmdProc structure. + * + * @param session The session to associate with. + * + * @return A new MsnCmdProc structure. + */ MsnCmdProc *msn_cmdproc_new(MsnSession *session); + +/** + * Destroys an MsnCmdProc. + * + * @param cmdproc The object structure. + */ void msn_cmdproc_destroy(MsnCmdProc *cmdproc); +/** + * Process the queued transactions. + * + * @param cmdproc The MsnCmdProc. + */ void msn_cmdproc_process_queue(MsnCmdProc *cmdproc); +/** + * Sends transaction using this servconn. + * + * @param cmdproc The MsnCmdProc to be used. + * @param trans The MsnTransaction to be sent. + */ void msn_cmdproc_send_trans(MsnCmdProc *cmdproc, MsnTransaction *trans); + +/** + * Add a transaction to the queue to be processed latter. + * + * @param cmdproc The MsnCmdProc in which the transaction will be queued. + * @param trans The MsnTransaction to be queued. + */ void msn_cmdproc_queue_trans(MsnCmdProc *cmdproc, MsnTransaction *trans); -void msn_cmdproc_send(MsnCmdProc *cmdproc, const char *command, - const char *format, ...); -void msn_cmdproc_send_quick(MsnCmdProc *cmdproc, const char *command, - const char *format, ...); void msn_cmdproc_process_msg(MsnCmdProc *cmdproc, MsnMessage *msg); void msn_cmdproc_process_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd); void msn_cmdproc_process_cmd_text(MsnCmdProc *cmdproc, const char *command); void msn_cmdproc_process_payload(MsnCmdProc *cmdproc, char *payload, int payload_len); diff --git a/purple/libpurple/protocols/msn/command.c b/purple/libpurple/protocols/msn/command.c --- a/purple/libpurple/protocols/msn/command.c +++ b/purple/libpurple/protocols/msn/command.c @@ -16,17 +16,18 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" +#include "internal.h" + #include "command.h" static gboolean is_num(const char *str) { const char *c; for (c = str; *c; c++) { if (!(g_ascii_isdigit(*c))) @@ -73,52 +74,40 @@ msn_command_from_string(const char *stri cmd->trId = 0; } msn_command_ref(cmd); return cmd; } -void +static void msn_command_destroy(MsnCommand *cmd) { - g_return_if_fail(cmd != NULL); - - if (cmd->ref_count > 0) - { - msn_command_unref(cmd); - return; - } - - if (cmd->payload != NULL) - g_free(cmd->payload); - + g_free(cmd->payload); g_free(cmd->command); g_strfreev(cmd->params); g_free(cmd); } MsnCommand * msn_command_ref(MsnCommand *cmd) { g_return_val_if_fail(cmd != NULL, NULL); cmd->ref_count++; return cmd; } -MsnCommand * +void msn_command_unref(MsnCommand *cmd) { - g_return_val_if_fail(cmd != NULL, NULL); - g_return_val_if_fail(cmd->ref_count > 0, NULL); + g_return_if_fail(cmd != NULL); + g_return_if_fail(cmd->ref_count > 0); cmd->ref_count--; if (cmd->ref_count == 0) { msn_command_destroy(cmd); - return NULL; } +} - return cmd; -} diff --git a/purple/libpurple/protocols/msn/command.h b/purple/libpurple/protocols/msn/command.h --- a/purple/libpurple/protocols/msn/command.h +++ b/purple/libpurple/protocols/msn/command.h @@ -38,25 +38,47 @@ typedef void (*MsnPayloadCb)(MsnCmdProc struct _MsnCommand { unsigned int trId; char *command; char **params; int param_count; - int ref_count; + guint ref_count; MsnTransaction *trans; char *payload; size_t payload_len; MsnPayloadCb payload_cb; void *payload_cbdata; }; +/** + * Create a command object from the incoming string and ref it. + * + * @param string The incoming string. + * + * @return A MsnCommand object. + */ MsnCommand *msn_command_from_string(const char *string); -void msn_command_destroy(MsnCommand *cmd); + +/** + * Increment the ref count. + * + * @param cmd The MsnCommand to be ref. + * + * @return The ref command. + */ MsnCommand *msn_command_ref(MsnCommand *cmd); -MsnCommand *msn_command_unref(MsnCommand *cmd); + +/** + * Decrement the ref count. If the count goes to 0, destroy it. + * + * @param cmd The MsnCommand to be unref. + * + */ +void msn_command_unref(MsnCommand *cmd); #endif /* MSN_COMMAND_H */ + diff --git a/purple/libpurple/protocols/msn/contact.c b/purple/libpurple/protocols/msn/contact.c --- a/purple/libpurple/protocols/msn/contact.c +++ b/purple/libpurple/protocols/msn/contact.c @@ -19,30 +19,35 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#include "msn.h" +#include "internal.h" +#include "debug.h" + #include "contact.h" #include "xmlnode.h" #include "group.h" +#include "msnutils.h" #include "soap.h" #include "nexus.h" +#include "user.h" const char *MsnSoapPartnerScenarioText[] = { "Initial", "ContactSave", "MessengerPendingList", "ContactMsgrAPI", - "BlockUnblock" + "BlockUnblock", + "Timer" }; const char *MsnMemberRole[] = { "Forward", "Allow", "Block", "Reverse", @@ -180,16 +185,18 @@ msn_contact_operation_str(MsnCallbackAct if (action & MSN_ADD_GROUP) strcat(buf, "Adding Group,"); if (action & MSN_DEL_GROUP) strcat(buf, "Deleting Group,"); if (action & MSN_RENAME_GROUP) strcat(buf, "Renaming Group,"); if (action & MSN_UPDATE_INFO) strcat(buf, "Updating Contact Info,"); + if (action & MSN_ANNOTATE_USER) + strcat(buf, "Annotating Contact,"); return buf; } static gboolean msn_contact_request(MsnCallbackState *state); static void msn_contact_request_cb(MsnSoapMessage *req, MsnSoapMessage *resp, @@ -522,26 +529,30 @@ msn_get_contact_list_cb(MsnSoapMessage * gpointer data) { MsnCallbackState *state = data; MsnSession *session = state->session; g_return_if_fail(session != NULL); if (resp != NULL) { +#ifdef MSN_PARTIAL_LISTS const char *abLastChange; const char *dynamicItemLastChange; +#endif purple_debug_misc("msn", "Got the contact list!\n"); msn_parse_contact_list(session, resp->xml); +#ifdef MSN_PARTIAL_LISTS abLastChange = purple_account_get_string(session->account, "ablastChange", NULL); dynamicItemLastChange = purple_account_get_string(session->account, - "dynamicItemLastChange", NULL); + "DynamicItemLastChanged", NULL); +#endif if (state->partner_scenario == MSN_PS_INITIAL) { #ifdef MSN_PARTIAL_LISTS /* XXX: this should be enabled when we can correctly do partial syncs with the server. Currently we need to retrieve the whole list to detect sync issues */ msn_get_address_book(session, MSN_PS_INITIAL, abLastChange, dynamicItemLastChange); #else @@ -679,43 +690,61 @@ msn_parse_addressbook_contacts(MsnSessio PurpleConnection *pc = purple_account_get_connection(session->account); for(contactNode = xmlnode_get_child(node, "Contact"); contactNode; contactNode = xmlnode_get_next_twin(contactNode)) { xmlnode *contactId, *contactInfo, *contactType, *passportName, *displayName, *guid, *groupIds; xmlnode *annotation; MsnUser *user; + g_free(passport); + g_free(Name); + g_free(uid); + g_free(type); + g_free(mobile_number); + g_free(alias); + passport = Name = uid = type = mobile_number = alias = NULL; + mobile = FALSE; + if (!(contactId = xmlnode_get_child(contactNode,"contactId")) || !(contactInfo = xmlnode_get_child(contactNode, "contactInfo")) || !(contactType = xmlnode_get_child(contactInfo, "contactType"))) continue; - g_free(passport); - g_free(Name); - g_free(alias); - g_free(uid); - g_free(type); - g_free(mobile_number); - passport = Name = uid = type = mobile_number = alias = NULL; - mobile = FALSE; - uid = xmlnode_get_data(contactId); type = xmlnode_get_data(contactType); - /*setup the Display Name*/ + /* Find out our settings */ if (type && !strcmp(type, "Me")) { + /* setup the Display Name */ if (purple_connection_get_display_name(pc) == NULL) { char *friendly = NULL; if ((displayName = xmlnode_get_child(contactInfo, "displayName"))) friendly = xmlnode_get_data(displayName); purple_connection_set_display_name(pc, friendly ? purple_url_decode(friendly) : NULL); g_free(friendly); } + + for (annotation = xmlnode_get_child(contactInfo, "annotations/Annotation"); + annotation; + annotation = xmlnode_get_next_twin(annotation)) { + char *name, *value; + name = xmlnode_get_data(xmlnode_get_child(annotation, "Name")); + value = xmlnode_get_data(xmlnode_get_child(annotation, "Value")); + if (!strcmp(name, "MSN.IM.MPOP")) { + if (!value || atoi(value) != 0) + session->enable_mpop = TRUE; + else + session->enable_mpop = FALSE; + } + g_free(name); + g_free(value); + } + continue; /* Not adding own account as buddy to buddylist */ } passportName = xmlnode_get_child(contactInfo, "passportName"); if (passportName == NULL) { xmlnode *emailsNode, *contactEmailNode, *emailNode; xmlnode *messengerEnabledNode; char *msnEnabled; @@ -831,16 +860,17 @@ msn_parse_addressbook_contacts(MsnSessio purple_serv_got_private_alias(pc, passport, alias); } g_free(passport); g_free(Name); g_free(uid); g_free(type); g_free(mobile_number); + g_free(alias); } static gboolean msn_parse_addressbook(MsnSession *session, xmlnode *node) { xmlnode *result; xmlnode *groups; xmlnode *contacts; @@ -880,27 +910,27 @@ msn_parse_addressbook(MsnSession *sessio /* I don't see this "groups" tag documented on msnpiki, need to find out if they are really there, and update msnpiki */ /*Process Group List*/ groups = xmlnode_get_child(result, "groups"); if (groups != NULL) { msn_parse_addressbook_groups(session, groups); } - /*add a default No group to set up the no group Membership*/ + /* Add an "Other Contacts" group for buddies who aren't in a group */ msn_group_new(session->userlist, MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME); purple_debug_misc("msn", "AB group_id:%s name:%s\n", MSN_INDIVIDUALS_GROUP_ID, MSN_INDIVIDUALS_GROUP_NAME); if ((purple_find_group(MSN_INDIVIDUALS_GROUP_NAME)) == NULL){ PurpleGroup *g = purple_group_new(MSN_INDIVIDUALS_GROUP_NAME); purple_blist_add_group(g, NULL); } - /*add a default No group to set up the no group Membership*/ + /* Add a "Non-IM Contacts" group */ msn_group_new(session->userlist, MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME); purple_debug_misc("msn", "AB group_id:%s name:%s\n", MSN_NON_IM_GROUP_ID, MSN_NON_IM_GROUP_NAME); if ((purple_find_group(MSN_NON_IM_GROUP_NAME)) == NULL) { PurpleGroup *g = purple_group_new(MSN_NON_IM_GROUP_NAME); purple_blist_add_group(g, NULL); } /*Process contact List*/ @@ -1058,28 +1088,16 @@ msn_add_contact_read_cb(MsnSoapMessage * /* add a Contact in MSN_INDIVIDUALS_GROUP */ void msn_add_contact(MsnSession *session, MsnCallbackState *state, const char *passport) { MsnUser *user; gchar *body = NULL; gchar *contact_xml = NULL; -#if 0 - gchar *escaped_displayname; - - - if (displayname != NULL) { - escaped_displayname = g_markup_decode_text(displayname, -1); - } else { - escaped_displayname = passport; - } - contact_xml = g_strdup_printf(MSN_XML_ADD_CONTACT, escaped_displayname, passport); -#endif - purple_debug_info("msn", "Adding contact %s to contact list\n", passport); user = msn_userlist_find_user(session->userlist, passport); if (user == NULL) { purple_debug_warning("msn", "Unable to retrieve user %s from the userlist!\n", passport); return; /* guess this never happened! */ } @@ -1162,17 +1180,17 @@ msn_add_contact_to_group_read_cb(MsnSoap msn_user_set_uid(user, uid); purple_debug_info("msn", "Set %s guid to %s.\n", state->who, uid); g_free(uid); } msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_AL); msn_userlist_add_buddy_to_list(userlist, state->who, MSN_LIST_FL); - if (msn_userlist_user_is_in_list(user, MSN_LIST_PL)) { + if (msn_user_is_in_list(user, MSN_LIST_PL)) { msn_del_contact_from_list(state->session, NULL, state->who, MSN_LIST_PL); return; } } if (state->action & MSN_MOVE_BUDDY) { msn_del_contact_from_group(state->session, state->who, state->old_group_name); } @@ -1483,16 +1501,111 @@ msn_update_contact(MsnSession *session, state->post_action = MSN_CONTACT_UPDATE_SOAP_ACTION; state->post_url = MSN_ADDRESS_BOOK_POST_URL; state->cb = msn_update_contact_read_cb; contact = xmlnode_get_child(state->body, "Body/ABContactUpdate/contacts/Contact"); xmlnode_insert_child(contact, contact_info); xmlnode_insert_child(contact, changes); + xmlnode_insert_data(xmlnode_get_child(state->body, + "Header/ABApplicationHeader/PartnerScenario"), + MsnSoapPartnerScenarioText[MSN_PS_SAVE_CONTACT], -1); + + if (user) { + xmlnode *contactId = xmlnode_new_child(contact, "contactId"); + msn_callback_state_set_uid(state, user->uid); + xmlnode_insert_data(contactId, state->uid, -1); + } else { + xmlnode *contactType = xmlnode_new_child(contact_info, "contactType"); + xmlnode_insert_data(contactType, "Me", -1); + } + + msn_contact_request(state); +} + +static void +msn_annotate_contact_read_cb(MsnSoapMessage *req, MsnSoapMessage *resp, + gpointer data) +{ + MsnCallbackState *state = (MsnCallbackState *)data; + xmlnode *fault; + + /* We don't know how to respond to this faultcode, so log it */ + fault = xmlnode_get_child(resp->xml, "Body/Fault"); + if (fault != NULL) { + char *fault_str = xmlnode_to_str(fault, NULL); + purple_debug_error("msn", "Operation {%s} Failed, SOAP Fault was: %s\n", + msn_contact_operation_str(state->action), fault_str); + g_free(fault_str); + return; + } + + purple_debug_info("msn", "Contact annotated successfully\n"); +} + +/* Update a contact's annotations */ +void +msn_annotate_contact(MsnSession *session, const char *passport, ...) +{ + va_list params; + MsnCallbackState *state; + xmlnode *contact; + xmlnode *contact_info; + xmlnode *annotations; + MsnUser *user = NULL; + + g_return_if_fail(passport != NULL); + + if (strcmp(passport, "Me") != 0) { + user = msn_userlist_find_user(session->userlist, passport); + if (!user) + return; + } + + contact_info = xmlnode_new("contactInfo"); + annotations = xmlnode_new_child(contact_info, "annotations"); + + va_start(params, passport); + while (TRUE) { + const char *name; + const char *value; + xmlnode *a, *n, *v; + + name = va_arg(params, const char *); + if (!name) + break; + + value = va_arg(params, const char *); + if (!value) + break; + + a = xmlnode_new_child(annotations, "Annotation"); + n = xmlnode_new_child(a, "Name"); + xmlnode_insert_data(n, name, -1); + v = xmlnode_new_child(a, "Value"); + xmlnode_insert_data(v, value, -1); + } + va_end(params); + + state = msn_callback_state_new(session); + + state->body = xmlnode_from_str(MSN_CONTACT_ANNOTATE_TEMPLATE, -1); + state->action = MSN_ANNOTATE_USER; + state->post_action = MSN_CONTACT_ANNOTATE_SOAP_ACTION; + state->post_url = MSN_ADDRESS_BOOK_POST_URL; + state->cb = msn_annotate_contact_read_cb; + + xmlnode_insert_data(xmlnode_get_child(state->body, + "Header/ABApplicationHeader/PartnerScenario"), + MsnSoapPartnerScenarioText[MSN_PS_SAVE_CONTACT], -1); + + contact = xmlnode_get_child(state->body, "Body/ABContactUpdate/contacts/Contact"); + xmlnode_insert_child(contact, contact_info); + if (user) { xmlnode *contactId = xmlnode_new_child(contact, "contactId"); msn_callback_state_set_uid(state, user->uid); xmlnode_insert_data(contactId, state->uid, -1); } else { xmlnode *contactType = xmlnode_new_child(contact_info, "contactType"); xmlnode_insert_data(contactType, "Me", -1); } @@ -1559,17 +1672,17 @@ msn_del_contact_from_list(MsnSession *se } msn_callback_state_set_list_id(state, list); msn_callback_state_set_who(state, passport); user = msn_userlist_find_user(session->userlist, passport); if (list == MSN_LIST_PL) { partner_scenario = MSN_PS_CONTACT_API; - if (user && user->networkid != MSN_NETWORK_PASSPORT) + if (user->networkid != MSN_NETWORK_PASSPORT) member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML, "EmailMember", "Email", user->member_id_on_pending_list); else member = g_strdup_printf(MSN_MEMBER_MEMBERSHIPID_XML, "PassportMember", "Passport", user->member_id_on_pending_list); } else { diff --git a/purple/libpurple/protocols/msn/contact.h b/purple/libpurple/protocols/msn/contact.h --- a/purple/libpurple/protocols/msn/contact.h +++ b/purple/libpurple/protocols/msn/contact.h @@ -31,33 +31,35 @@ typedef enum { MSN_ADD_BUDDY = 0x01, MSN_MOVE_BUDDY = 0x02, MSN_ACCEPTED_BUDDY = 0x04, MSN_DENIED_BUDDY = 0x08, MSN_ADD_GROUP = 0x10, MSN_DEL_GROUP = 0x20, MSN_RENAME_GROUP = 0x40, - MSN_UPDATE_INFO = 0x80 + MSN_UPDATE_INFO = 0x80, + MSN_ANNOTATE_USER = 0x100 } MsnCallbackAction; typedef enum { MSN_UPDATE_DISPLAY, /* Real display name */ MSN_UPDATE_ALIAS, /* Aliased display name */ MSN_UPDATE_COMMENT } MsnContactUpdateType; typedef enum { MSN_PS_INITIAL, MSN_PS_SAVE_CONTACT, MSN_PS_PENDING_LIST, MSN_PS_CONTACT_API, - MSN_PS_BLOCK_UNBLOCK + MSN_PS_BLOCK_UNBLOCK, + MSN_PS_TIMER } MsnSoapPartnerScenario; #include "session.h" #include "soap.h" #define MSN_APPLICATION_ID "CFE80F9D-180F-4399-82AB-413F33A1FA11" #define MSN_CONTACT_SERVER "omega.contacts.msn.com" @@ -403,17 +405,17 @@ typedef enum " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""\ " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""\ " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\""\ " xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">"\ ""\ ""\ "" MSN_APPLICATION_ID ""\ "false"\ - "Timer"\ + ""\ ""\ ""\ "false"\ "EMPTY"\ ""\ ""\ ""\ ""\ @@ -422,16 +424,47 @@ typedef enum ""\ ""\ ""\ ""\ ""\ ""\ "" +/* Update Contact Annotations */ +#define MSN_CONTACT_ANNOTATE_SOAP_ACTION "http://www.msn.com/webservices/AddressBook/ABContactUpdate" +#define MSN_CONTACT_ANNOTATE_TEMPLATE ""\ +""\ + ""\ + ""\ + "" MSN_APPLICATION_ID ""\ + "false"\ + ""\ + ""\ + ""\ + "false"\ + "EMPTY"\ + ""\ + ""\ + ""\ + ""\ + "00000000-0000-0000-0000-000000000000"\ + ""\ + ""\ + "Annotation"\ + ""\ + ""\ + ""\ + ""\ +"" + /******************************************************* * Add/Delete contact from lists SOAP actions *******************************************************/ /* block means delete from allow list and add contact to block list */ #define MSN_SHARE_POST_URL "/abservice/SharingService.asmx" #define MSN_ADD_MEMBER_TO_LIST_SOAP_ACTION "http://www.msn.com/webservices/AddressBook/AddMember" @@ -670,27 +703,28 @@ void msn_callback_state_set_old_group_na const gchar *old_group_name); void msn_callback_state_set_new_group_name(MsnCallbackState *state, const gchar *new_group_name); void msn_callback_state_set_guid(MsnCallbackState *state, const gchar *guid); void msn_callback_state_set_list_id(MsnCallbackState *state, MsnListId list_id); void msn_callback_state_set_action(MsnCallbackState *state, MsnCallbackAction action); -void msn_contact_connect(MsnSession *session); void msn_get_contact_list(MsnSession *session, const MsnSoapPartnerScenario partner_scenario, const char *update); void msn_get_address_book(MsnSession *session, const MsnSoapPartnerScenario partner_scenario, const char * update, const char * gupdate); /* contact SOAP operations */ void msn_update_contact(MsnSession *session, const char *passport, MsnContactUpdateType type, const char* value); +void msn_annotate_contact(MsnSession *session, const char *passport, ...) G_GNUC_NULL_TERMINATED; + void msn_add_contact(MsnSession *session, MsnCallbackState *state, const char *passport); void msn_delete_contact(MsnSession *session, MsnUser *user); void msn_add_contact_to_group(MsnSession *session, MsnCallbackState *state, const char *passport, const char *groupId); void msn_del_contact_from_group(MsnSession *session, const char *passport, const char *group_name); diff --git a/purple/libpurple/protocols/msn/dialog.c b/purple/libpurple/protocols/msn/dialog.c deleted file mode 100644 --- a/purple/libpurple/protocols/msn/dialog.c +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @file dialog.c Dialog functions - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ - -#include "msn.h" -#include "dialog.h" - -typedef struct -{ - PurpleConnection *gc; - char *who; - char *group; - gboolean add; - -} MsnAddRemData; - -/* Remove the buddy referenced by the MsnAddRemData before the serverside list is changed. - * If the buddy will be added, he'll be added back; if he will be removed, he won't be. */ -/* Actually with our MSNP14 code that isn't true yet, he won't be added back :( */ -static void -msn_complete_sync_issue(MsnAddRemData *data) -{ - PurpleBuddy *buddy; - PurpleGroup *group = NULL; - - if (data->group != NULL) - group = purple_find_group(data->group); - - if (group != NULL) - buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group); - else - buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who); - - if (buddy != NULL) - purple_blist_remove_buddy(buddy); -} - - -static void -msn_add_cb(MsnAddRemData *data) -{ -#if 0 - /* this *should* be necessary !! */ - msn_complete_sync_issue(data); -#endif - - if (g_list_find(purple_connections_get_all(), data->gc) != NULL) - { - MsnSession *session = data->gc->proto_data; - MsnUserList *userlist = session->userlist; - - msn_userlist_add_buddy(userlist, data->who, data->group); - } - - g_free(data->group); - g_free(data->who); - g_free(data); -} - -static void -msn_rem_cb(MsnAddRemData *data) -{ - msn_complete_sync_issue(data); - - if (g_list_find(purple_connections_get_all(), data->gc) != NULL) - { - MsnSession *session = data->gc->proto_data; - MsnUserList *userlist = session->userlist; - - if (data->group == NULL) { - msn_userlist_rem_buddy_from_list(userlist, data->who, MSN_LIST_FL); - } else { - g_free(data->group); - } - } - - g_free(data->who); - g_free(data); -} - -void -msn_show_sync_issue(MsnSession *session, const char *passport, - const char *group_name) -{ - PurpleConnection *gc; - PurpleAccount *account; - MsnAddRemData *data; - char *msg, *reason; - - account = session->account; - gc = purple_account_get_connection(account); - - data = g_new0(MsnAddRemData, 1); - data->who = g_strdup(passport); - data->group = g_strdup(group_name); - data->gc = gc; - - msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"), - purple_account_get_username(account), - purple_account_get_protocol_name(account)); - - if (group_name != NULL) - { - reason = g_strdup_printf(_("%s on the local list is " - "inside the group \"%s\" but not on " - "the server list. " - "Do you want this buddy to be added?"), - passport, group_name); - } - else - { - reason = g_strdup_printf(_("%s is on the local list but " - "not on the server list. " - "Do you want this buddy to be added?"), - passport); - } - - purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE, - purple_connection_get_account(gc), data->who, NULL, - data, 2, - _("Yes"), G_CALLBACK(msn_add_cb), - _("No"), G_CALLBACK(msn_rem_cb)); - - g_free(reason); - g_free(msg); -} diff --git a/purple/libpurple/protocols/msn/dialog.h b/purple/libpurple/protocols/msn/dialog.h deleted file mode 100644 --- a/purple/libpurple/protocols/msn/dialog.h +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file dialog.h Dialog functions - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ -#ifndef MSN_DIALOG_H -#define MSN_DIALOG_H - -void msn_show_sync_issue(MsnSession *session, const char *passport, - const char *group_name); - -#endif /* MSN_DIALOG_H */ diff --git a/purple/libpurple/protocols/msn/directconn.c b/purple/libpurple/protocols/msn/directconn.c --- a/purple/libpurple/protocols/msn/directconn.c +++ b/purple/libpurple/protocols/msn/directconn.c @@ -16,40 +16,30 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" +#include "cipher.h" +#include "debug.h" + #include "msn.h" #include "directconn.h" #include "slp.h" #include "slpmsg.h" +#include "p2p.h" -#pragma pack(push,1) -typedef struct { - guint32 session_id; - guint32 seq_id; - guint64 offset; - guint64 total_size; - guint32 length; - guint32 flags; - guint32 ack_id; - guint32 ack_uid; - guint64 ack_size; -/* guint8 body[1]; */ -} MsnDcContext; -#pragma pack(pop) - -#define DC_PACKET_HEADER_SIZE sizeof(MsnDcContext) #define DC_MAX_BODY_SIZE 8*1024 -#define DC_MAX_PACKET_SIZE (DC_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE) +#define DC_MAX_PACKET_SIZE (P2P_PACKET_HEADER_SIZE + DC_MAX_BODY_SIZE) static void msn_dc_calculate_nonce_hash(MsnDirectConnNonceType type, const guchar nonce[16], gchar nonce_hash[37]) { guchar digest[20]; if (type == DC_NONCE_SHA1) { @@ -116,18 +106,18 @@ msn_dc_new_packet(guint32 length) return p; } static void msn_dc_destroy_packet(MsnDirectConnPacket *p) { g_free(p->data); - if (p->msg) - msn_message_unref(p->msg); + if (p->part) + msn_slpmsgpart_unref(p->part); g_free(p); } MsnDirectConn * msn_dc_new(MsnSlpCall *slpcall) { MsnDirectConn *dc; @@ -186,17 +176,17 @@ msn_dc_destroy(MsnDirectConn *dc) if (dc->slpcall != NULL) dc->slpcall->wait_for_socket = FALSE; slplink = dc->slplink; if (slplink) { slplink->dc = NULL; if (slplink->swboard == NULL) - msn_slplink_destroy(slplink); + msn_slplink_unref(slplink); } g_free(dc->msg_body); if (dc->prev_ack) { msn_slpmsg_destroy(dc->prev_ack); } @@ -348,71 +338,26 @@ msn_dc_fallback_to_sb(MsnDirectConn *dc) msn_dc_destroy(dc); if (slpcall) { msn_slpcall_session_init(slpcall); if (queue) { while (!g_queue_is_empty(queue)) { MsnDirectConnPacket *p = g_queue_pop_head(queue); - msn_slplink_send_msg(slplink, p->msg); + msn_slplink_send_msgpart(slplink, (MsnSlpMessage*)p->part->ack_data); msn_dc_destroy_packet(p); } g_queue_free(queue); } } msn_slplink_unref(slplink); } static void -msn_dc_parse_binary_header(MsnDirectConn *dc) -{ - MsnSlpHeader *h; - MsnDcContext *context; - - g_return_if_fail(dc != NULL); - - h = &dc->header; - /* Skip packet size */ - context = (MsnDcContext *)(dc->in_buffer + 4); - - h->session_id = GUINT32_FROM_LE(context->session_id); - h->id = GUINT32_FROM_LE(context->seq_id); - h->offset = GUINT64_FROM_LE(context->offset); - h->total_size = GUINT64_FROM_LE(context->total_size); - h->length = GUINT32_FROM_LE(context->length); - h->flags = GUINT32_FROM_LE(context->flags); - h->ack_id = GUINT32_FROM_LE(context->ack_id); - h->ack_sub_id = GUINT32_FROM_LE(context->ack_uid); - h->ack_size = GUINT64_FROM_LE(context->ack_size); -} - -static const gchar * -msn_dc_serialize_binary_header(MsnDirectConn *dc) { - MsnSlpHeader *h; - static MsnDcContext bin_header; - - g_return_val_if_fail(dc != NULL, NULL); - - h = &dc->header; - - bin_header.session_id = GUINT32_TO_LE(h->session_id); - bin_header.seq_id = GUINT32_TO_LE(h->id); - bin_header.offset = GUINT64_TO_LE(h->offset); - bin_header.total_size = GUINT64_TO_LE(h->total_size); - bin_header.length = GUINT32_TO_LE(h->length); - bin_header.flags = GUINT32_TO_LE(h->flags); - bin_header.ack_id = GUINT32_TO_LE(h->ack_id); - bin_header.ack_uid = GUINT32_TO_LE(h->ack_sub_id); - bin_header.ack_size = GUINT64_TO_LE(h->ack_size); - - return (const gchar *)&bin_header; -} - -static void msn_dc_send_cb(gpointer data, gint fd, PurpleInputCondition cond) { MsnDirectConn *dc = data; MsnDirectConnPacket *p; int bytes_to_send; int bytes_sent; g_return_if_fail(dc != NULL); @@ -497,64 +442,65 @@ msn_dc_send_foo(MsnDirectConn *dc) msn_dc_enqueue_packet(dc, p); } static void msn_dc_send_handshake_with_nonce(MsnDirectConn *dc, MsnDirectConnPacket *p) { const gchar *h; - h = msn_dc_serialize_binary_header(dc); - memcpy(p->data, h, DC_PACKET_HEADER_SIZE); + h = msn_p2p_header_to_wire(&dc->header); - memcpy(p->data + offsetof(MsnDcContext, ack_id), dc->nonce, 16); + memcpy(p->data, h, P2P_PACKET_HEADER_SIZE); + + memcpy(p->data + P2P_HEADER_ACK_ID_OFFSET, dc->nonce, 16); msn_dc_enqueue_packet(dc, p); } static void msn_dc_send_handshake(MsnDirectConn *dc) { MsnDirectConnPacket *p; - p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE); + p = msn_dc_new_packet(P2P_PACKET_HEADER_SIZE); dc->header.session_id = 0; dc->header.id = dc->slpcall->slplink->slp_seq_id++; dc->header.offset = 0; dc->header.total_size = 0; dc->header.length = 0; dc->header.flags = 0x100; msn_dc_send_handshake_with_nonce(dc, p); } static void msn_dc_send_handshake_reply(MsnDirectConn *dc) { MsnDirectConnPacket *p; - p = msn_dc_new_packet(DC_PACKET_HEADER_SIZE); + p = msn_dc_new_packet(P2P_PACKET_HEADER_SIZE); dc->header.id = dc->slpcall->slplink->slp_seq_id++; dc->header.length = 0; msn_dc_send_handshake_with_nonce(dc, p); } static gboolean msn_dc_verify_handshake(MsnDirectConn *dc, guint32 packet_length) { guchar nonce[16]; gchar nonce_hash[37]; - if (packet_length != DC_PACKET_HEADER_SIZE) + if (packet_length != P2P_PACKET_HEADER_SIZE) return FALSE; - memcpy(nonce, dc->in_buffer + 4 + offsetof(MsnDcContext, ack_id), 16); + memcpy(nonce, dc->in_buffer + 4 + P2P_HEADER_ACK_ID_OFFSET, 16); if (dc->nonce_type == DC_NONCE_PLAIN) { if (memcmp(dc->nonce, nonce, 16) == 0) { purple_debug_info("msn", "Nonce from buddy request and nonce from DC attempt match, " "allowing direct connection\n"); return TRUE; } else { @@ -584,41 +530,43 @@ msn_dc_verify_handshake(MsnDirectConn *d } } else return FALSE; } static void msn_dc_send_packet_cb(MsnDirectConnPacket *p) { - if (p->msg != NULL && p->msg->ack_cb != NULL) - p->msg->ack_cb(p->msg, p->msg->ack_data); + if (p->part != NULL && p->part->ack_cb != NULL) + p->part->ack_cb(p->part, p->part->ack_data); } void -msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg) +msn_dc_enqueue_part(MsnDirectConn *dc, MsnSlpMessagePart *part) { MsnDirectConnPacket *p; guint32 length; - length = msg->body_len + DC_PACKET_HEADER_SIZE; + length = part->size + P2P_PACKET_HEADER_SIZE; p = msn_dc_new_packet(length); - memcpy(p->data, &msg->msnslp_header, DC_PACKET_HEADER_SIZE); - memcpy(p->data + DC_PACKET_HEADER_SIZE, msg->body, msg->body_len); + memcpy(p->data, part->header, P2P_PACKET_HEADER_SIZE); + memcpy(p->data + P2P_PACKET_HEADER_SIZE, part->buffer, part->size); p->sent_cb = msn_dc_send_packet_cb; - p->msg = msn_message_ref(msg); + p->part = msn_slpmsgpart_ref(part); msn_dc_enqueue_packet(dc, p); } static int msn_dc_process_packet(MsnDirectConn *dc, guint32 packet_length) { + MsnSlpMessagePart *part; + g_return_val_if_fail(dc != NULL, DC_PROCESS_ERROR); switch (dc->state) { case DC_STATE_CLOSED: break; case DC_STATE_FOO: /* FOO message is always 4 bytes long */ @@ -645,22 +593,24 @@ msn_dc_process_packet(MsnDirectConn *dc, dc->state = DC_STATE_ESTABLISHED; msn_slpcall_session_init(dc->slpcall); dc->slpcall = NULL; break; case DC_STATE_ESTABLISHED: - msn_slplink_process_msg( - dc->slplink, - &dc->header, - dc->in_buffer + 4 + DC_PACKET_HEADER_SIZE, - dc->header.length - ); + + if (dc->header.length) { + part = msn_slpmsgpart_new_from_data(dc->in_buffer + 4, dc->header.length); + if (part) { + msn_slplink_process_msg(dc->slplink, part); + msn_slpmsgpart_unref(part); + } + } /* if (dc->num_calls == 0) { msn_dc_destroy(dc); return DC_PROCESS_CLOSE; } */ @@ -721,18 +671,23 @@ msn_dc_recv_cb(gpointer data, gint fd, P purple_debug_warning("msn", "msn_dc_recv_cb: oversized packet received\n"); return; } /* Wait for the whole packet to arrive */ if (dc->in_pos < 4 + packet_length) return; - if (dc->state != DC_STATE_FOO) { - msn_dc_parse_binary_header(dc); + if (dc->state != DC_STATE_FOO && packet_length >= P2P_PACKET_HEADER_SIZE) { + MsnP2PHeader *context; + + /* Skip packet size */ + context = msn_p2p_header_from_wire(dc->in_buffer + 4); + memcpy(&dc->header, context, P2P_PACKET_HEADER_SIZE); + g_free(context); } switch (msn_dc_process_packet(dc, packet_length)) { case DC_PROCESS_CLOSE: return; case DC_PROCESS_FALLBACK: purple_debug_warning("msn", "msn_dc_recv_cb: packet processing error, fall back to SB\n"); diff --git a/purple/libpurple/protocols/msn/directconn.h b/purple/libpurple/protocols/msn/directconn.h --- a/purple/libpurple/protocols/msn/directconn.h +++ b/purple/libpurple/protocols/msn/directconn.h @@ -25,20 +25,23 @@ #define MSN_DIRECTCONN_H typedef struct _MsnDirectConn MsnDirectConn; #include "network.h" #include "proxy.h" #include "circbuffer.h" -#include "msg.h" #include "slp.h" #include "slplink.h" #include "slpmsg.h" +#include "slpmsg_part.h" +#include "p2p.h" + +#define MSN_DCCONN_MAX_SIZE 1352 typedef enum { DC_STATE_CLOSED, /*< No socket opened yet */ DC_STATE_FOO, /*< Waiting for FOO message */ DC_STATE_HANDSHAKE, /*< Waiting for handshake message */ DC_STATE_HANDSHAKE_REPLY, /*< Waiting for handshake reply message */ DC_STATE_ESTABLISHED /*< Handshake complete */ @@ -63,17 +66,17 @@ typedef enum typedef struct _MsnDirectConnPacket MsnDirectConnPacket; struct _MsnDirectConnPacket { guint32 length; guchar *data; void (*sent_cb)(struct _MsnDirectConnPacket*); - MsnMessage *msg; + MsnSlpMessagePart *part; }; struct _MsnDirectConn { MsnDirectConnState state; /**< Direct connection status */ MsnSlpLink *slplink; /**< The slplink using this direct connection */ MsnSlpCall *slpcall; /**< The slpcall which initiated the direct connection */ char *msg_body; /**< The body of message sent by send_connection_info_msg_cb */ @@ -95,17 +98,17 @@ struct _MsnDirectConn guint send_handle; /**< The outgoing data callback handle */ gchar *in_buffer; /**< The receive buffer */ int in_size; /**< The receive buffer size */ int in_pos; /**< The first free position in receive buffer */ GQueue *out_queue; /**< The outgoing packet queue */ int msg_pos; /**< The position of next byte to be sent in the actual packet */ - MsnSlpHeader header; /**< SLP header for parsing / serializing */ + MsnP2PHeader header; /**< SLP header for parsing / serializing */ /** The callback used for sending information to the peer about the opened socket */ void (*send_connection_info_msg_cb)(MsnDirectConn *); gchar *ext_ip; /**< Our external IP address */ int ext_port; /**< Our external port */ guint timeout_handle; @@ -120,17 +123,17 @@ struct _MsnDirectConn #define DC_INCOMING_TIMEOUT (DC_OUTGOING_TIMEOUT * 3) /* Timeout for lack of activity */ #define DC_TIMEOUT (60) /* * Queues an MSN message to be sent via direct connection. */ void -msn_dc_enqueue_msg(MsnDirectConn *dc, MsnMessage *msg); +msn_dc_enqueue_part(MsnDirectConn *dc, MsnSlpMessagePart *part); /* * Creates, initializes, and returns a new MsnDirectConn structure. */ MsnDirectConn * msn_dc_new(MsnSlpCall *slpcall); /* diff --git a/purple/libpurple/protocols/msn/error.c b/purple/libpurple/protocols/msn/error.c --- a/purple/libpurple/protocols/msn/error.c +++ b/purple/libpurple/protocols/msn/error.c @@ -16,19 +16,33 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" +/* Masca: can we get rid of the sync issue dialog? */ +#include "request.h" + #include "error.h" +typedef struct +{ + PurpleConnection *gc; + char *who; + char *group; + gboolean add; + +} MsnAddRemData; + const char * msn_error_get_text(unsigned int type, gboolean *debug) { static char msg[256]; const char *result; *debug = FALSE; switch (type) { @@ -259,8 +273,120 @@ msn_error_handle(MsnSession *session, un msn_error_get_text(type, &debug)); if (debug) purple_debug_warning("msn", "error %d: %s\n", type, buf); else purple_notify_error(session->account->gc, NULL, buf, NULL); g_free(buf); } +/* Remove the buddy referenced by the MsnAddRemData before the serverside list + * is changed. If the buddy will be added, he'll be added back; if he will be + * removed, he won't be. */ +/* Actually with our MSNP14 code that isn't true yet, he won't be added back :( + * */ +static void +msn_complete_sync_issue(MsnAddRemData *data) +{ + PurpleBuddy *buddy; + PurpleGroup *group = NULL; + + if (data->group != NULL) + group = purple_find_group(data->group); + + if (group != NULL) + buddy = purple_find_buddy_in_group(purple_connection_get_account(data->gc), data->who, group); + else + buddy = purple_find_buddy(purple_connection_get_account(data->gc), data->who); + + if (buddy != NULL) + purple_blist_remove_buddy(buddy); +} + + +static void +msn_add_cb(MsnAddRemData *data) +{ +#if 0 + /* this *should* be necessary !! */ + msn_complete_sync_issue(data); +#endif + + if (g_list_find(purple_connections_get_all(), data->gc) != NULL) + { + MsnSession *session = data->gc->proto_data; + MsnUserList *userlist = session->userlist; + + msn_userlist_add_buddy(userlist, data->who, data->group); + } + + g_free(data->group); + g_free(data->who); + g_free(data); +} + +static void +msn_rem_cb(MsnAddRemData *data) +{ + msn_complete_sync_issue(data); + + if (g_list_find(purple_connections_get_all(), data->gc) != NULL) + { + MsnSession *session = data->gc->proto_data; + MsnUserList *userlist = session->userlist; + + if (data->group == NULL) { + msn_userlist_rem_buddy_from_list(userlist, data->who, MSN_LIST_FL); + } else { + g_free(data->group); + } + } + + g_free(data->who); + g_free(data); +} + +void +msn_error_sync_issue(MsnSession *session, const char *passport, + const char *group_name) +{ + PurpleConnection *gc; + PurpleAccount *account; + MsnAddRemData *data; + char *msg, *reason; + + account = session->account; + gc = purple_account_get_connection(account); + + data = g_new0(MsnAddRemData, 1); + data->who = g_strdup(passport); + data->group = g_strdup(group_name); + data->gc = gc; + + msg = g_strdup_printf(_("Buddy list synchronization issue in %s (%s)"), + purple_account_get_username(account), + purple_account_get_protocol_name(account)); + + if (group_name != NULL) + { + reason = g_strdup_printf(_("%s on the local list is " + "inside the group \"%s\" but not on " + "the server list. " + "Do you want this buddy to be added?"), + passport, group_name); + } + else + { + reason = g_strdup_printf(_("%s is on the local list but " + "not on the server list. " + "Do you want this buddy to be added?"), + passport); + } + + purple_request_action(gc, NULL, msg, reason, PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(gc), data->who, NULL, + data, 2, + _("Yes"), G_CALLBACK(msn_add_cb), + _("No"), G_CALLBACK(msn_rem_cb)); + + g_free(reason); + g_free(msg); +} diff --git a/purple/libpurple/protocols/msn/error.h b/purple/libpurple/protocols/msn/error.h --- a/purple/libpurple/protocols/msn/error.h +++ b/purple/libpurple/protocols/msn/error.h @@ -39,9 +39,19 @@ const char *msn_error_get_text(unsigned /** * Handles an error. * * @param session The current session. * @param type The error type. */ void msn_error_handle(MsnSession *session, unsigned int type); +/** + * Show the sync issue in a dialog using request api + * + * @param sesion MsnSession associated to this error. + * @param passport The passport associated with the error. + * @param group_name The group in the buddy is suppoused to be + */ +void msn_error_sync_issue(MsnSession *session, const char *passport, + const char *group_name); + #endif /* MSN_ERROR_H */ diff --git a/purple/libpurple/protocols/msn/group.h b/purple/libpurple/protocols/msn/group.h --- a/purple/libpurple/protocols/msn/group.h +++ b/purple/libpurple/protocols/msn/group.h @@ -21,17 +21,17 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_GROUP_H #define MSN_GROUP_H typedef struct _MsnGroup MsnGroup; -#include +#include "internal.h" #include "session.h" #include "user.h" #include "userlist.h" #define MSN_INDIVIDUALS_GROUP_ID "1983" #define MSN_INDIVIDUALS_GROUP_NAME _("Other Contacts") diff --git a/purple/libpurple/protocols/msn/history.h b/purple/libpurple/protocols/msn/history.h --- a/purple/libpurple/protocols/msn/history.h +++ b/purple/libpurple/protocols/msn/history.h @@ -19,16 +19,18 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_HISTORY_H #define MSN_HISTORY_H +#include "internal.h" + typedef struct _MsnHistory MsnHistory; #include "transaction.h" #define MSN_NS_HIST_ELEMS 0x300 #define MSN_SB_HIST_ELEMS 0x30 /** diff --git a/purple/libpurple/protocols/msn/httpconn.c b/purple/libpurple/protocols/msn/httpconn.c --- a/purple/libpurple/protocols/msn/httpconn.c +++ b/purple/libpurple/protocols/msn/httpconn.c @@ -1,10 +1,10 @@ /** - * @file httpmethod.c HTTP connection method + * @file httpconn.c HTTP connection method * * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify @@ -234,27 +234,29 @@ msn_httpconn_parse_data(MsnHttpConn *htt g_free(httpconn->session_id); httpconn->session_id = session_id; g_free(httpconn->host); httpconn->host = gw_ip; } else { + /* I'll be honest, I don't fully understand all this, but this + * causes crashes, Stu. */ +#if 0 MsnServConn *servconn; /* It's going to die. */ /* poor thing */ servconn = httpconn->servconn; - /* I'll be honest, I don't fully understand all this, but this - * causes crashes, Stu. */ - /* if (servconn != NULL) - servconn->wasted = TRUE; */ + if (servconn != NULL) + servconn->wasted = TRUE; +#endif g_free(full_session_id); g_free(session_id); g_free(gw_ip); } g_free(session_action); } diff --git a/purple/libpurple/protocols/msn/httpconn.h b/purple/libpurple/protocols/msn/httpconn.h --- a/purple/libpurple/protocols/msn/httpconn.h +++ b/purple/libpurple/protocols/msn/httpconn.h @@ -23,16 +23,17 @@ */ #ifndef MSN_HTTPCONN_H #define MSN_HTTPCONN_H typedef struct _MsnHttpConn MsnHttpConn; #include "circbuffer.h" #include "servconn.h" +#include "session.h" /** * An HTTP Connection. */ struct _MsnHttpConn { MsnSession *session; /**< The MSN Session. */ MsnServConn *servconn; /**< The connection object. */ diff --git a/purple/libpurple/protocols/msn/msg.c b/purple/libpurple/protocols/msn/msg.c --- a/purple/libpurple/protocols/msn/msg.c +++ b/purple/libpurple/protocols/msn/msg.c @@ -16,129 +16,129 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" +#include "debug.h" + #include "msn.h" #include "msg.h" #include "msnutils.h" +#include "slpmsg.h" +#include "slpmsg_part.h" MsnMessage * msn_message_new(MsnMsgType type) { MsnMessage *msg; msg = g_new0(MsnMessage, 1); msg->type = type; if (purple_debug_is_verbose()) purple_debug_info("msn", "message new (%p)(%d)\n", msg, type); - msg->attr_table = g_hash_table_new_full(g_str_hash, g_str_equal, + msg->header_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); msn_message_ref(msg); return msg; } -void +/** + * Destroys a message. + * + * @param msg The message to destroy. + */ +static void msn_message_destroy(MsnMessage *msg) { g_return_if_fail(msg != NULL); - if (msg->ref_count > 0) - { - msn_message_unref(msg); - - return; - } - if (purple_debug_is_verbose()) purple_debug_info("msn", "message destroy (%p)\n", msg); g_free(msg->remote_user); g_free(msg->body); g_free(msg->content_type); g_free(msg->charset); - g_hash_table_destroy(msg->attr_table); - g_list_free(msg->attr_list); + g_hash_table_destroy(msg->header_table); + g_list_free(msg->header_list); + if (msg->part) + msn_slpmsgpart_unref(msg->part); g_free(msg); } MsnMessage * msn_message_ref(MsnMessage *msg) { g_return_val_if_fail(msg != NULL, NULL); msg->ref_count++; if (purple_debug_is_verbose()) - purple_debug_info("msn", "message ref (%p)[%" G_GSIZE_FORMAT "]\n", msg, msg->ref_count); + purple_debug_info("msn", "message ref (%p)[%u]\n", msg, msg->ref_count); return msg; } -MsnMessage * +void msn_message_unref(MsnMessage *msg) { - g_return_val_if_fail(msg != NULL, NULL); - g_return_val_if_fail(msg->ref_count > 0, NULL); + g_return_if_fail(msg != NULL); + g_return_if_fail(msg->ref_count > 0); msg->ref_count--; if (purple_debug_is_verbose()) - purple_debug_info("msn", "message unref (%p)[%" G_GSIZE_FORMAT "]\n", msg, msg->ref_count); + purple_debug_info("msn", "message unref (%p)[%u]\n", msg, msg->ref_count); if (msg->ref_count == 0) - { msn_message_destroy(msg); - - return NULL; - } - - return msg; } MsnMessage * msn_message_new_plain(const char *message) { MsnMessage *msg; char *message_cr; msg = msn_message_new(MSN_MSG_TEXT); msg->retries = 1; - msn_message_set_attr(msg, "User-Agent", PACKAGE_NAME "/" VERSION); + msn_message_set_header(msg, "User-Agent", PACKAGE_NAME "/" VERSION); msn_message_set_content_type(msg, "text/plain"); msn_message_set_charset(msg, "UTF-8"); msn_message_set_flag(msg, 'A'); - msn_message_set_attr(msg, "X-MMS-IM-Format", + msn_message_set_header(msg, "X-MMS-IM-Format", "FN=Segoe%20UI; EF=; CO=0; CS=1;PF=0"); message_cr = purple_str_add_cr(message); msn_message_set_bin_data(msg, message_cr, strlen(message_cr)); g_free(message_cr); return msg; } MsnMessage * msn_message_new_msnslp(void) { MsnMessage *msg; msg = msn_message_new(MSN_MSG_SLP); - msn_message_set_attr(msg, "User-Agent", NULL); + msn_message_set_header(msg, "User-Agent", NULL); msg->msnslp_message = TRUE; msn_message_set_flag(msg, 'D'); msn_message_set_content_type(msg, "application/x-msnmsgrp2p"); return msg; } @@ -152,100 +152,60 @@ msn_message_new_nudge(void) msn_message_set_content_type(msg, "text/x-msnmsgr-datacast"); msn_message_set_flag(msg, 'N'); msn_message_set_bin_data(msg, "ID: 1\r\n", 7); return msg; } void -msn_message_parse_slp_body(MsnMessage *msg, const char *body, size_t len) -{ - MsnSlpHeader header; - const char *tmp; - int body_len; - - tmp = body; - - if (len < sizeof(header)) { - g_return_if_reached(); - } - - /* Import the header. */ - memcpy(&header, tmp, sizeof(header)); - tmp += sizeof(header); - - msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id); - msg->msnslp_header.id = GUINT32_FROM_LE(header.id); - msg->msnslp_header.offset = GUINT64_FROM_LE(header.offset); - msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size); - msg->msnslp_header.length = GUINT32_FROM_LE(header.length); - msg->msnslp_header.flags = GUINT32_FROM_LE(header.flags); - msg->msnslp_header.ack_id = GUINT32_FROM_LE(header.ack_id); - msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id); - msg->msnslp_header.ack_size = GUINT64_FROM_LE(header.ack_size); - - /* Import the body. */ - body_len = len - (tmp - body); - /* msg->body_len = msg->msnslp_header.length; */ - - if (body_len > 0) { - msg->body_len = len - (tmp - body); - msg->body = g_malloc(msg->body_len + 1); - memcpy(msg->body, tmp, msg->body_len); - msg->body[msg->body_len] = '\0'; - tmp += body_len; - } -} - -void msn_message_parse_payload(MsnMessage *msg, const char *payload, size_t payload_len, const char *line_dem,const char *body_dem) { char *tmp_base, *tmp; const char *content_type; char *end; char **elems, **cur, **tokens; g_return_if_fail(payload != NULL); tmp_base = tmp = g_malloc(payload_len + 1); memcpy(tmp_base, payload, payload_len); tmp_base[payload_len] = '\0'; - /* Parse the attributes. */ + /* Find the end of the headers */ end = strstr(tmp, body_dem); /* TODO? some clients use \r delimiters instead of \r\n, the official client * doesn't send such messages, but does handle receiving them. We'll just * avoid crashing for now */ if (end == NULL) { g_free(tmp_base); g_return_if_reached(); } *end = '\0'; + /* Split the headers and parse each one */ elems = g_strsplit(tmp, line_dem, 0); - for (cur = elems; *cur != NULL; cur++) { const char *key, *value; /* If this line starts with whitespace, it's been folded from the previous line and won't have ':'. */ if ((**cur == ' ') || (**cur == '\t')) { tokens = g_strsplit(g_strchug(*cur), "=\"", 2); key = tokens[0]; value = tokens[1]; /* The only one I care about is 'boundary' (which is folded from the key 'Content-Type'), so only process that. */ if (!strcmp(key, "boundary")) { char *end = strchr(value, '\"'); *end = '\0'; - msn_message_set_attr(msg, key, value); + msn_message_set_header(msg, key, value); } g_strfreev(tokens); continue; } tokens = g_strsplit(*cur, ": ", 2); @@ -273,95 +233,50 @@ msn_message_parse_payload(MsnMessage *ms *c = '\0'; } msn_message_set_content_type(msg, value); } else { - msn_message_set_attr(msg, key, value); + msn_message_set_header(msg, key, value); } g_strfreev(tokens); } - g_strfreev(elems); /* Proceed to the end of the "\r\n\r\n" */ tmp = end + strlen(body_dem); /* Now we *should* be at the body. */ content_type = msn_message_get_content_type(msg); if (content_type != NULL && - !strcmp(content_type, "application/x-msnmsgrp2p")) - { - MsnSlpHeader header; - MsnSlpFooter footer; - int body_len; + !strcmp(content_type, "application/x-msnmsgrp2p")) { + msg->msnslp_message = TRUE; + msg->part = msn_slpmsgpart_new_from_data(tmp, payload_len - (tmp - tmp_base)); + } - if (payload_len - (tmp - tmp_base) < sizeof(header)) { - g_free(tmp_base); - g_return_if_reached(); - } + if (payload_len - (tmp - tmp_base) > 0) { + msg->body_len = payload_len - (tmp - tmp_base); + g_free(msg->body); + msg->body = g_malloc(msg->body_len + 1); + memcpy(msg->body, tmp, msg->body_len); + msg->body[msg->body_len] = '\0'; + } - msg->msnslp_message = TRUE; - - /* Import the header. */ - memcpy(&header, tmp, sizeof(header)); - tmp += sizeof(header); - - msg->msnslp_header.session_id = GUINT32_FROM_LE(header.session_id); - msg->msnslp_header.id = GUINT32_FROM_LE(header.id); - msg->msnslp_header.offset = GUINT64_FROM_LE(header.offset); - msg->msnslp_header.total_size = GUINT64_FROM_LE(header.total_size); - msg->msnslp_header.length = GUINT32_FROM_LE(header.length); - msg->msnslp_header.flags = GUINT32_FROM_LE(header.flags); - msg->msnslp_header.ack_id = GUINT32_FROM_LE(header.ack_id); - msg->msnslp_header.ack_sub_id = GUINT32_FROM_LE(header.ack_sub_id); - msg->msnslp_header.ack_size = GUINT64_FROM_LE(header.ack_size); - - body_len = payload_len - (tmp - tmp_base) - sizeof(footer); - - /* Import the body. */ - if (body_len > 0) { - msg->body_len = body_len; - g_free(msg->body); - msg->body = g_malloc(msg->body_len + 1); - memcpy(msg->body, tmp, msg->body_len); - msg->body[msg->body_len] = '\0'; - tmp += body_len; - } - - /* Import the footer. */ - if (body_len >= 0) { - memcpy(&footer, tmp, sizeof(footer)); - tmp += sizeof(footer); - msg->msnslp_footer.value = GUINT32_FROM_BE(footer.value); - } - } - else - { - if (payload_len - (tmp - tmp_base) > 0) { - msg->body_len = payload_len - (tmp - tmp_base); - g_free(msg->body); - msg->body = g_malloc(msg->body_len + 1); - memcpy(msg->body, tmp, msg->body_len); - msg->body[msg->body_len] = '\0'; - } - - if ((!content_type || !strcmp(content_type, "text/plain")) + if ((!content_type || !strcmp(content_type, "text/plain")) && msg->charset == NULL) { - char *body = g_convert(msg->body, msg->body_len, "UTF-8", - "ISO-8859-1", NULL, &msg->body_len, NULL); - g_free(msg->body); - msg->body = body; - msg->charset = g_strdup("UTF-8"); - } + char *body = g_convert(msg->body, msg->body_len, "UTF-8", + "ISO-8859-1", NULL, &msg->body_len, NULL); + g_free(msg->body); + msg->body = body; + msg->charset = g_strdup("UTF-8"); } g_free(tmp_base); } MsnMessage * msn_message_new_from_cmd(MsnSession *session, MsnCommand *cmd) { @@ -374,58 +289,16 @@ msn_message_new_from_cmd(MsnSession *ses msg->remote_user = g_strdup(cmd->params[0]); /* msg->size = atoi(cmd->params[2]); */ msg->cmd = cmd; return msg; } char * -msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size) -{ - MsnSlpHeader header; - - char *tmp, *base; - const void *body; - size_t len, body_len; - - g_return_val_if_fail(msg != NULL, NULL); - - len = MSN_BUF_LEN; - - base = tmp = g_malloc(len + 1); - - body = msn_message_get_bin_data(msg, &body_len); - - header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id); - header.id = GUINT32_TO_LE(msg->msnslp_header.id); - header.offset = GUINT64_TO_LE(msg->msnslp_header.offset); - header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size); - header.length = GUINT32_TO_LE(msg->msnslp_header.length); - header.flags = GUINT32_TO_LE(msg->msnslp_header.flags); - header.ack_id = GUINT32_TO_LE(msg->msnslp_header.ack_id); - header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id); - header.ack_size = GUINT64_TO_LE(msg->msnslp_header.ack_size); - - memcpy(tmp, &header, 48); - tmp += 48; - - if (body != NULL) - { - memcpy(tmp, body, body_len); - tmp += body_len; - } - - if (ret_size != NULL) - *ret_size = tmp - base; - - return base; -} - -char * msn_message_gen_payload(MsnMessage *msg, size_t *ret_size) { GList *l; char *n, *base, *end; int len; size_t body_len = 0; const void *body; @@ -449,61 +322,43 @@ msn_message_gen_payload(MsnMessage *msg, g_snprintf(n, len, "MIME-Version: 1.0\r\n" "Content-Type: %s; charset=%s\r\n", msg->content_type, msg->charset); } n += strlen(n); - for (l = msg->attr_list; l != NULL; l = l->next) + for (l = msg->header_list; l != NULL; l = l->next) { const char *key; const char *value; key = l->data; - value = msn_message_get_attr(msg, key); + value = msn_message_get_header_value(msg, key); g_snprintf(n, end - n, "%s: %s\r\n", key, value); n += strlen(n); } n += g_strlcpy(n, "\r\n", end - n); body = msn_message_get_bin_data(msg, &body_len); if (msg->msnslp_message) { - MsnSlpHeader header; - MsnSlpFooter footer; + size_t siz; + char *body; + + body = msn_slpmsgpart_serialize(msg->part, &siz); - header.session_id = GUINT32_TO_LE(msg->msnslp_header.session_id); - header.id = GUINT32_TO_LE(msg->msnslp_header.id); - header.offset = GUINT64_TO_LE(msg->msnslp_header.offset); - header.total_size = GUINT64_TO_LE(msg->msnslp_header.total_size); - header.length = GUINT32_TO_LE(msg->msnslp_header.length); - header.flags = GUINT32_TO_LE(msg->msnslp_header.flags); - header.ack_id = GUINT32_TO_LE(msg->msnslp_header.ack_id); - header.ack_sub_id = GUINT32_TO_LE(msg->msnslp_header.ack_sub_id); - header.ack_size = GUINT64_TO_LE(msg->msnslp_header.ack_size); + memcpy(n, body, siz); + n += siz; - memcpy(n, &header, 48); - n += 48; - - if (body != NULL) - { - memcpy(n, body, body_len); - - n += body_len; - } - - footer.value = GUINT32_TO_BE(msg->msnslp_footer.value); - - memcpy(n, &footer, 4); - n += 4; + g_free(body); } else { if (body != NULL) { memcpy(n, body, body_len); n += body_len; *n = '\0'; @@ -605,63 +460,63 @@ const char * msn_message_get_charset(const MsnMessage *msg) { g_return_val_if_fail(msg != NULL, NULL); return msg->charset; } void -msn_message_set_attr(MsnMessage *msg, const char *attr, const char *value) +msn_message_set_header(MsnMessage *msg, const char *name, const char *value) { const char *temp; - char *new_attr; + char *new_name; g_return_if_fail(msg != NULL); - g_return_if_fail(attr != NULL); + g_return_if_fail(name != NULL); - temp = msn_message_get_attr(msg, attr); + temp = msn_message_get_header_value(msg, name); if (value == NULL) { if (temp != NULL) { GList *l; - for (l = msg->attr_list; l != NULL; l = l->next) + for (l = msg->header_list; l != NULL; l = l->next) { - if (!g_ascii_strcasecmp(l->data, attr)) + if (!g_ascii_strcasecmp(l->data, name)) { - msg->attr_list = g_list_remove(msg->attr_list, l->data); + msg->header_list = g_list_remove(msg->header_list, l->data); break; } } - g_hash_table_remove(msg->attr_table, attr); + g_hash_table_remove(msg->header_table, name); } return; } - new_attr = g_strdup(attr); + new_name = g_strdup(name); - g_hash_table_insert(msg->attr_table, new_attr, g_strdup(value)); + g_hash_table_insert(msg->header_table, new_name, g_strdup(value)); if (temp == NULL) - msg->attr_list = g_list_append(msg->attr_list, new_attr); + msg->header_list = g_list_append(msg->header_list, new_name); } const char * -msn_message_get_attr(const MsnMessage *msg, const char *attr) +msn_message_get_header_value(const MsnMessage *msg, const char *name) { g_return_val_if_fail(msg != NULL, NULL); - g_return_val_if_fail(attr != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); - return g_hash_table_lookup(msg->attr_table, attr); + return g_hash_table_lookup(msg->header_table, name); } GHashTable * msn_message_get_hashtable_from_body(const MsnMessage *msg) { GHashTable *table; size_t body_len; const char *body; @@ -736,69 +591,79 @@ msn_message_show_readable(MsnMessage *ms else { g_string_append_printf(str, "MIME-Version: 1.0\r\n" "Content-Type: %s; charset=%s\r\n", msg->content_type, msg->charset); } - for (l = msg->attr_list; l; l = l->next) + for (l = msg->header_list; l; l = l->next) { char *key; const char *value; key = l->data; - value = msn_message_get_attr(msg, key); + value = msn_message_get_header_value(msg, key); g_string_append_printf(str, "%s: %s\r\n", key, value); } g_string_append(str, "\r\n"); body = msn_message_get_bin_data(msg, &body_len); if (msg->msnslp_message) { - g_string_append_printf(str, "Session ID: %u\r\n", msg->msnslp_header.session_id); - g_string_append_printf(str, "ID: %u\r\n", msg->msnslp_header.id); - g_string_append_printf(str, "Offset: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.offset); - g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.total_size); - g_string_append_printf(str, "Length: %u\r\n", msg->msnslp_header.length); - g_string_append_printf(str, "Flags: 0x%x\r\n", msg->msnslp_header.flags); - g_string_append_printf(str, "ACK ID: %u\r\n", msg->msnslp_header.ack_id); - g_string_append_printf(str, "SUB ID: %u\r\n", msg->msnslp_header.ack_sub_id); - g_string_append_printf(str, "ACK Size: %" G_GUINT64_FORMAT "\r\n", msg->msnslp_header.ack_size); + g_string_append_printf(str, "Session ID: %u\r\n", msg->part->header->session_id); + g_string_append_printf(str, "ID: %u\r\n", msg->part->header->id); + g_string_append_printf(str, "Offset: %" G_GUINT64_FORMAT "\r\n", msg->part->header->offset); + g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", msg->part->header->total_size); + g_string_append_printf(str, "Length: %u\r\n", msg->part->header->length); + g_string_append_printf(str, "Flags: 0x%x\r\n", msg->part->header->flags); + g_string_append_printf(str, "ACK ID: %u\r\n", msg->part->header->ack_id); + g_string_append_printf(str, "SUB ID: %u\r\n", msg->part->header->ack_sub_id); + g_string_append_printf(str, "ACK Size: %" G_GUINT64_FORMAT "\r\n", msg->part->header->ack_size); if (purple_debug_is_verbose() && body != NULL) { if (text_body) { g_string_append_len(str, body, body_len); if (body[body_len - 1] == '\0') { str->len--; g_string_append(str, " 0x00"); } g_string_append(str, "\r\n"); } else { int i; - for (i = 0; i < msg->body_len; i++) + int bin_len; + + if (msg->part->footer->value == P2P_APPID_SESSION) + bin_len = P2P_PACKET_HEADER_SIZE; + else + bin_len = body_len; + + for (i = 0; i < bin_len; i++) { g_string_append_printf(str, "%.2hhX ", body[i]); if ((i % 16) == 15) g_string_append(str, "\r\n"); } + + if (bin_len == P2P_PACKET_HEADER_SIZE) + g_string_append_printf(str, "%s ", body + P2P_PACKET_HEADER_SIZE); g_string_append(str, "\r\n"); } } - g_string_append_printf(str, "Footer: %u\r\n", msg->msnslp_footer.value); + g_string_append_printf(str, "Footer: 0x%08X\r\n", msg->part->footer->value); } else { if (body != NULL) { g_string_append_len(str, body, body_len); g_string_append(str, "\r\n"); } @@ -812,46 +677,43 @@ msn_message_show_readable(MsnMessage *ms /************************************************************************** * Message Handlers **************************************************************************/ void msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { PurpleConnection *gc; const char *body; - char *body_str; char *body_enc; char *body_final; size_t body_len; const char *passport; const char *value; gc = cmdproc->session->account->gc; body = msn_message_get_bin_data(msg, &body_len); - body_str = g_strndup(body, body_len); - body_enc = g_markup_escape_text(body_str, -1); - g_free(body_str); + body_enc = g_markup_escape_text(body, body_len); passport = msg->remote_user; if (!strcmp(passport, "messenger@microsoft.com") && strstr(body, "immediate security update")) { return; } #if 0 - if ((value = msn_message_get_attr(msg, "User-Agent")) != NULL) + if ((value = msn_message_get_header_value(msg, "User-Agent")) != NULL) { purple_debug_misc("msn", "User-Agent = '%s'\n", value); } #endif - if ((value = msn_message_get_attr(msg, "X-MMS-IM-Format")) != NULL) + if ((value = msn_message_get_header_value(msg, "X-MMS-IM-Format")) != NULL) { char *pre, *post; msn_parse_format(value, &pre, &post); body_final = g_strdup_printf("%s%s%s", pre ? pre : "", body_enc ? body_enc : "", post ? post : ""); @@ -882,18 +744,19 @@ msn_plain_msg(MsnCmdProc *cmdproc, MsnMe serv_got_chat_in(gc, swboard->chat_id, passport, 0, body_final, time(NULL)); if (swboard->conv == NULL) { swboard->conv = purple_find_chat(gc, swboard->chat_id); swboard->flag |= MSN_SB_FLAG_IM; } } - else + else if (!g_str_equal(passport, purple_account_get_username(gc->account))) { + /* Don't im ourselves ... */ serv_got_im(gc, passport, body_final, 0, time(NULL)); if (swboard->conv == NULL) { swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, passport, purple_connection_get_account(gc)); swboard->flag |= MSN_SB_FLAG_IM; } } @@ -909,17 +772,17 @@ void msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { PurpleConnection *gc; char *passport; gc = cmdproc->session->account->gc; passport = msg->remote_user; - if (msn_message_get_attr(msg, "TypingUser") == NULL) + if (msn_message_get_header_value(msg, "TypingUser") == NULL) return; if (cmdproc->servconn->type == MSN_SERVCONN_SB) { MsnSwitchBoard *swboard = cmdproc->data; if (swboard->current_users == 1) { serv_got_typing(gc, passport, MSN_TYPING_RECV_TIMEOUT, @@ -1032,16 +895,155 @@ got_voiceclip_cb(MsnSlpCall *slpcall, co NULL); } if (f) fclose(f); g_free(path); } void +msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + MsnSlpLink *slplink; + + session = cmdproc->servconn->session; + slplink = msn_session_get_slplink(session, msg->remote_user); + + if (slplink->swboard == NULL) + { + /* + * We will need swboard in order to change its flags. If its + * NULL, something has probably gone wrong earlier on. I + * didn't want to do this, but MSN 7 is somehow causing us + * to crash here, I couldn't reproduce it to debug more, + * and people are reporting bugs. Hopefully this doesn't + * cause more crashes. Stu. + */ + if (cmdproc->data == NULL) + g_warning("msn_p2p_msg cmdproc->data was NULL\n"); + else { + slplink->swboard = (MsnSwitchBoard *)cmdproc->data; + slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); + } + } + + if (msg->part) { + msn_slplink_process_msg(slplink, msg->part); + } + else /* This should never happen. */ + purple_debug_fatal("msn", "P2P message without a Part.\n"); +} + +static void +got_emoticon(MsnSlpCall *slpcall, + const guchar *data, gsize size) +{ + PurpleConversation *conv; + MsnSwitchBoard *swboard; + + swboard = slpcall->slplink->swboard; + conv = swboard->conv; + + if (conv) { + /* FIXME: it would be better if we wrote the data as we received it + instead of all at once, calling write multiple times and + close once at the very end + */ + purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size); + purple_conv_custom_smiley_close(conv, slpcall->data_info ); + } + if (purple_debug_is_verbose()) + purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info); +} + +void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg) +{ + MsnSession *session; + MsnSlpLink *slplink; + MsnSwitchBoard *swboard; + MsnObject *obj; + char **tokens; + char *smile, *body_str; + const char *body, *who, *sha1; + guint tok; + size_t body_len; + + PurpleConversation *conv; + + session = cmdproc->servconn->session; + + if (!purple_account_get_bool(session->account, "custom_smileys", TRUE)) + return; + + swboard = cmdproc->data; + conv = swboard->conv; + + body = msn_message_get_bin_data(msg, &body_len); + if (!body || !body_len) + return; + body_str = g_strndup(body, body_len); + + /* MSN Messenger 7 may send more than one MSNObject in a single message... + * Maybe 10 tokens is a reasonable max value. */ + tokens = g_strsplit(body_str, "\t", 10); + + g_free(body_str); + + for (tok = 0; tok < 9; tok += 2) { + if (tokens[tok] == NULL || tokens[tok + 1] == NULL) { + break; + } + + smile = tokens[tok]; + obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1])); + + if (obj == NULL) + break; + + who = msn_object_get_creator(obj); + sha1 = msn_object_get_sha1(obj); + + slplink = msn_session_get_slplink(session, who); + if (slplink->swboard != swboard) { + if (slplink->swboard != NULL) + /* + * Apparently we're using a different switchboard now or + * something? I don't know if this is normal, but it + * definitely happens. So make sure the old switchboard + * doesn't still have a reference to us. + */ + slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink); + slplink->swboard = swboard; + slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); + } + + /* If the conversation doesn't exist then this is a custom smiley + * used in the first message in a MSN conversation: we need to create + * the conversation now, otherwise the custom smiley won't be shown. + * This happens because every GtkIMHtml has its own smiley tree: if + * the conversation doesn't exist then we cannot associate the new + * smiley with its GtkIMHtml widget. */ + if (!conv) { + conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who); + } + + if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) { + msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj); + } + + msn_object_destroy(obj); + obj = NULL; + who = NULL; + sha1 = NULL; + } + g_strfreev(tokens); +} + +void msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { GHashTable *body; const char *id; body = msn_message_get_hashtable_from_body(msg); id = g_hash_table_lookup(body, "ID"); @@ -1203,17 +1205,17 @@ msn_invite_msg(MsnCmdProc *cmdproc, MsnM text = g_strdup_printf("Invitation-Command: CANCEL\r\n" "Invitation-Cookie: %s\r\n" "Cancel-Code: REJECT_NOT_INSTALLED\r\n", cookie); msn_message_set_bin_data(cancel, text, strlen(text)); g_free(text); msn_switchboard_send_msg(swboard, cancel, TRUE); - msn_message_destroy(cancel); + msn_message_unref(cancel); } } else if (!strcmp(command, "CANCEL")) { const gchar *code = g_hash_table_lookup(body, "Cancel-Code"); purple_debug_info("msn", "MSMSGS invitation cancelled: %s.\n", code ? code : "no reason given"); } else { diff --git a/purple/libpurple/protocols/msn/msg.h b/purple/libpurple/protocols/msn/msg.h --- a/purple/libpurple/protocols/msn/msg.h +++ b/purple/libpurple/protocols/msn/msg.h @@ -53,69 +53,52 @@ typedef enum MSN_MSG_ERROR_SB, /**< The error comes from the switchboard. */ MSN_MSG_ERROR_UNKNOWN /**< An unknown error occurred. */ } MsnMsgErrorType; #include "command.h" #include "session.h" #include "transaction.h" #include "user.h" +#include "slpmsg.h" +#include "slpmsg_part.h" typedef void (*MsnMsgCb)(MsnMessage *, void *data); #define MSG_BODY_DEM "\r\n\r\n" #define MSG_LINE_DEM "\r\n" #define MSG_OIM_BODY_DEM "\n\n" #define MSG_OIM_LINE_DEM "\n" -typedef struct -{ - guint32 session_id; - guint32 id; - guint64 offset; - guint64 total_size; - guint32 length; - guint32 flags; - guint32 ack_id; - guint32 ack_sub_id; - guint64 ack_size; -} MsnSlpHeader; - -typedef struct -{ - guint32 value; -} MsnSlpFooter; - /** * A message. */ struct _MsnMessage { - size_t ref_count; /**< The reference count. */ + guint ref_count; /**< The reference count. */ MsnMsgType type; gboolean msnslp_message; + MsnSlpMessage *slpmsg; + MsnSlpMessagePart *part; char *remote_user; char flag; char *content_type; char *charset; char *body; gsize body_len; - guint total_chunks; /**< How many chunks in this multi-part message */ - guint received_chunks; /**< How many chunks we've received so far */ + guint total_chunks; /**< How many chunks in this multi-part message */ + guint received_chunks; /**< How many chunks we've received so far */ - MsnSlpHeader msnslp_header; - MsnSlpFooter msnslp_footer; - - GHashTable *attr_table; - GList *attr_list; + GHashTable *header_table; + GList *header_list; gboolean ack_ref; /**< A flag that states if this message has been ref'ed for using it in a callback. */ MsnCommand *cmd; MsnTransaction *trans; MsnMsgCb ack_cb; /**< The callback to call when we receive an ACK of this @@ -183,23 +166,16 @@ MsnMessage *msn_message_new_from_cmd(Msn * @param payload The payload. * @param payload_len The length of the payload. */ void msn_message_parse_payload(MsnMessage *msg, const char *payload, size_t payload_len, const char *line_dem,const char *body_dem); /** - * Destroys a message. - * - * @param msg The message to destroy. - */ -void msn_message_destroy(MsnMessage *msg); - -/** * Increments the reference count on a message. * * @param msg The message. * * @return @a msg */ MsnMessage *msn_message_ref(MsnMessage *msg); @@ -207,17 +183,17 @@ MsnMessage *msn_message_ref(MsnMessage * * Decrements the reference count on a message. * * This will destroy the structure if the count hits 0. * * @param msg The message. * * @return @a msg, or @c NULL if the new count is 0. */ -MsnMessage *msn_message_unref(MsnMessage *msg); +void msn_message_unref(MsnMessage *msg); /** * Generates the payload data of a message. * * @param msg The message. * @param ret_size The returned size of the payload. * * @return The payload data of the message. @@ -290,55 +266,74 @@ void msn_message_set_charset(MsnMessage * * @param msg The message. * * @return The charset. */ const char *msn_message_get_charset(const MsnMessage *msg); /** - * Sets an attribute in a message. + * Sets a header in a message. * - * @param msg The message. - * @param attr The attribute name. - * @param value The attribute value. + * @param msg The message. + * @param header The header name. + * @param value The header value. */ -void msn_message_set_attr(MsnMessage *msg, const char *attr, +void msn_message_set_header(MsnMessage *msg, const char *name, const char *value); /** - * Returns an attribute from a message. + * Returns the value of a header from a message. * - * @param msg The message. - * @param attr The attribute. + * @param msg The message. + * @param header The header value. * * @return The value, or @c NULL if not found. */ -const char *msn_message_get_attr(const MsnMessage *msg, const char *attr); +const char *msn_message_get_header_value(const MsnMessage *msg, const char *name); /** * Parses the body and returns it in the form of a hashtable. * * @param msg The message. * * @return The resulting hashtable. */ GHashTable *msn_message_get_hashtable_from_body(const MsnMessage *msg); void msn_message_show_readable(MsnMessage *msg, const char *info, gboolean text_body); -void msn_message_parse_slp_body(MsnMessage *msg, const char *body, - size_t len); - -char *msn_message_gen_slp_body(MsnMessage *msg, size_t *ret_size); - char *msn_message_to_string(MsnMessage *msg); void msn_plain_msg(MsnCmdProc *cmdproc, MsnMessage *msg); void msn_control_msg(MsnCmdProc *cmdproc, MsnMessage *msg); +/** + * Processes peer to peer messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + +/** + * Processes emoticon messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + void msn_datacast_msg(MsnCmdProc *cmdproc, MsnMessage *msg); +/** + * Processes INVITE messages. + * + * @param cmdproc The command processor. + * @param msg The message. + */ +void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg); + void msn_handwritten_msg(MsnCmdProc *cmdproc, MsnMessage *msg); #endif /* MSN_MSG_H */ diff --git a/purple/libpurple/protocols/msn/msn.c b/purple/libpurple/protocols/msn/msn.c --- a/purple/libpurple/protocols/msn/msn.c +++ b/purple/libpurple/protocols/msn/msn.c @@ -18,37 +18,41 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #define PHOTO_SUPPORT 1 -#include "msn.h" +#include "internal.h" + +#include "debug.h" +#include "request.h" + #include "accountopt.h" #include "contact.h" #include "msg.h" #include "page.h" #include "pluginpref.h" #include "prefs.h" #include "session.h" #include "smiley.h" #include "state.h" #include "util.h" #include "cmds.h" #include "core.h" #include "prpl.h" #include "msnutils.h" #include "version.h" +#include "error.h" #include "msg.h" #include "switchboard.h" #include "notification.h" -#include "sync.h" #include "slplink.h" #if PHOTO_SUPPORT #define MAX_HTTP_BUDDYICON_BYTES (200 * 1024) #include "imgstore.h" #endif typedef struct @@ -111,52 +115,29 @@ msn_normalize(const PurpleAccount *accou tmp = g_utf8_strdown(buf, -1); strncpy(buf, tmp, sizeof(buf)); g_free(tmp); return buf; } -gboolean -msn_email_is_valid(const char *passport) -{ - if (purple_email_is_valid(passport)) { - /* Special characters aren't allowed in domains, so only go to '@' */ - while (*passport != '@') { - if (*passport == '/') - return FALSE; - else if (*passport == '?') - return FALSE; - else if (*passport == '=') - return FALSE; - /* MSN also doesn't like colons, but that's checked already */ - - passport++; - } - - return TRUE; - } - - return FALSE; -} - static gboolean msn_send_attention(PurpleConnection *gc, const char *username, guint type) { MsnMessage *msg; MsnSession *session; MsnSwitchBoard *swboard; msg = msn_message_new_nudge(); session = gc->proto_data; swboard = msn_session_get_swboard(session, username, MSN_SB_FLAG_IM); msn_switchboard_send_msg(swboard, msg, TRUE); - msn_message_destroy(msg); + msn_message_unref(msg); return TRUE; } static GList * msn_attention_types(PurpleAccount *account) { static GList *list = NULL; @@ -262,53 +243,45 @@ prp_timeout_cb(MsnCmdProc *cmdproc, MsnT void msn_set_public_alias(PurpleConnection *pc, const char *alias, PurpleSetPublicAliasSuccessCallback success_cb, PurpleSetPublicAliasFailureCallback failure_cb) { MsnCmdProc *cmdproc; MsnSession *session; + MsnTransaction *trans; PurpleAccount *account; - const char *real_alias; - MsnTransaction *trans; + char real_alias[BUDDY_ALIAS_MAXLEN + 1]; struct public_alias_closure *closure; session = purple_connection_get_protocol_data(pc); cmdproc = session->notification->cmdproc; account = purple_connection_get_account(pc); - if (alias && *alias) - { - char *tmp = g_strdup(alias); - real_alias = purple_url_encode(g_strstrip(tmp)); - g_free(tmp); - } - else - real_alias = ""; - - if (strlen(real_alias) > BUDDY_ALIAS_MAXLEN) - { - if (failure_cb) { - struct public_alias_closure *closure = - g_new0(struct public_alias_closure, 1); - closure->account = account; - closure->failure_cb = failure_cb; - purple_timeout_add(0, set_public_alias_length_error, closure); - } else { - purple_notify_error(pc, NULL, - _("Your new MSN friendly name is too long."), - NULL); + if (alias && *alias) { + if (!msn_encode_spaces(alias, real_alias, BUDDY_ALIAS_MAXLEN + 1)) { + if (failure_cb) { + struct public_alias_closure *closure = + g_new0(struct public_alias_closure, 1); + closure->account = account; + closure->failure_cb = failure_cb; + purple_timeout_add(0, set_public_alias_length_error, closure); + } else { + purple_notify_error(pc, NULL, + _("Your new MSN friendly name is too long."), + NULL); + } + return; } - return; - } - - if (*real_alias == '\0') { - real_alias = purple_url_encode(purple_account_get_username(account)); - } + + if (real_alias[0] == '\0') + strcpy(real_alias, purple_account_get_username(account)); + } else + strcpy(real_alias, purple_account_get_username(account)); closure = g_new0(struct public_alias_closure, 1); closure->account = account; closure->success_cb = success_cb; closure->failure_cb = failure_cb; trans = msn_transaction_new(cmdproc, "PRP", "MFN %s", real_alias); msn_transaction_set_data(trans, closure); @@ -355,29 +328,31 @@ msn_act_id(PurpleConnection *gc, const c msn_set_public_alias(gc, entry, NULL, NULL); } static void msn_set_prp(PurpleConnection *gc, const char *type, const char *entry) { MsnCmdProc *cmdproc; MsnSession *session; + MsnTransaction *trans; session = gc->proto_data; cmdproc = session->notification->cmdproc; if (entry == NULL || *entry == '\0') { - msn_cmdproc_send(cmdproc, "PRP", "%s", type); + trans = msn_transaction_new(cmdproc, "PRP", "%s", type); } else { - msn_cmdproc_send(cmdproc, "PRP", "%s %s", type, + trans = msn_transaction_new(cmdproc, "PRP", "%s %s", type, purple_url_encode(entry)); } + msn_cmdproc_send_trans(cmdproc, trans); } static void msn_set_home_phone_cb(PurpleConnection *gc, const char *entry) { msn_set_prp(gc, "PHH", entry); } @@ -473,27 +448,212 @@ msn_show_set_friendly_name(PurplePluginA PurpleAccount *account; char *tmp; gc = (PurpleConnection *) action->context; account = purple_connection_get_account(gc); tmp = g_strdup_printf(_("Set friendly name for %s."), purple_account_get_username(account)); - purple_request_input(gc, _("Set your friendly name."), tmp, + purple_request_input(gc, _("Set Friendly Name"), tmp, _("This is the name that other MSN buddies will " "see you as."), purple_connection_get_display_name(gc), FALSE, FALSE, NULL, _("OK"), G_CALLBACK(msn_act_id), _("Cancel"), NULL, account, NULL, NULL, gc); g_free(tmp); } +typedef struct MsnLocationData { + PurpleAccount *account; + MsnSession *session; + PurpleRequestFieldGroup *group; +} MsnLocationData; + +static void +update_endpoint_cb(MsnLocationData *data, PurpleRequestFields *fields) +{ + PurpleAccount *account; + MsnSession *session; + const char *old_name; + const char *name; + GList *others; + + session = data->session; + account = data->account; + + /* Update the current location's name */ + old_name = purple_account_get_string(account, "endpoint-name", NULL); + name = purple_request_fields_get_string(fields, "endpoint-name"); + if (!g_str_equal(old_name, name)) { + purple_account_set_string(account, "endpoint-name", name); + msn_notification_send_uux_private_endpointdata(session); + } + + /* Sign out other locations */ + for (others = purple_request_field_group_get_fields(data->group); + others; + others = g_list_next(others)) { + PurpleRequestField *field = others->data; + if (purple_request_field_get_type(field) != PURPLE_REQUEST_FIELD_BOOLEAN) + continue; + if (purple_request_field_bool_get_value(field)) { + const char *id = purple_request_field_get_id(field); + char *user; + purple_debug_info("msn", "Disconnecting Endpoint %s\n", id); + + user = g_strdup_printf("%s;%s", purple_account_get_username(account), id); + msn_notification_send_uun(session, user, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye"); + g_free(user); + } + } + + g_free(data); +} + +static void +msn_show_locations(PurplePluginAction *action) +{ + PurpleConnection *pc; + PurpleAccount *account; + MsnSession *session; + PurpleRequestFields *fields; + PurpleRequestFieldGroup *group; + PurpleRequestField *field; + gboolean have_other_endpoints; + GSList *l; + MsnLocationData *data; + + pc = (PurpleConnection *)action->context; + account = purple_connection_get_account(pc); + session = purple_connection_get_protocol_data(pc); + + fields = purple_request_fields_new(); + + group = purple_request_field_group_new(_("This Location")); + purple_request_fields_add_group(fields, group); + field = purple_request_field_label_new("endpoint-label", _("This is the name that identifies this location")); + purple_request_field_group_add_field(group, field); + field = purple_request_field_string_new("endpoint-name", + _("Name"), + purple_account_get_string(account, "endpoint-name", NULL), + FALSE); + purple_request_field_set_required(field, TRUE); + purple_request_field_group_add_field(group, field); + + group = purple_request_field_group_new(_("Other Locations")); + purple_request_fields_add_group(fields, group); + + have_other_endpoints = FALSE; + for (l = session->user->endpoints; l; l = l->next) { + MsnUserEndpoint *ep = l->data; + + if (ep->id[0] != '\0' && strncasecmp(ep->id + 1, session->guid, 36) == 0) + /* Don't add myself to the list */ + continue; + + if (!have_other_endpoints) { + /* We do in fact have an endpoint other than ourselves... let's + add a label */ + field = purple_request_field_label_new("others-label", + _("You can sign out from other locations here")); + purple_request_field_group_add_field(group, field); + } + + have_other_endpoints = TRUE; + field = purple_request_field_bool_new(ep->id, ep->name, FALSE); + purple_request_field_group_add_field(group, field); + } + if (!have_other_endpoints) { + /* TODO: Due to limitations in our current request field API, the + following string will show up with a trailing colon. This should + be fixed either by adding an "include_colon" boolean, or creating + a separate purple_request_field_label_new_without_colon function, + or by never automatically adding the colon and requiring that + callers add the colon themselves. */ + field = purple_request_field_label_new("others-label", _("You are not signed in from any other locations.")); + purple_request_field_group_add_field(group, field); + } + + data = g_new0(MsnLocationData, 1); + data->account = account; + data->session = session; + data->group = group; + + purple_request_fields(pc, NULL, NULL, NULL, + fields, + _("OK"), G_CALLBACK(update_endpoint_cb), + _("Cancel"), G_CALLBACK(g_free), + account, NULL, NULL, + data); +} + +static void +enable_mpop_cb(PurpleConnection *pc) +{ + MsnSession *session = purple_connection_get_protocol_data(pc); + + purple_debug_info("msn", "Enabling MPOP\n"); + + session->enable_mpop = TRUE; + msn_annotate_contact(session, "Me", "MSN.IM.MPOP", "1", NULL); + + purple_prpl_got_account_actions(purple_connection_get_account(pc)); +} + +static void +disable_mpop_cb(PurpleConnection *pc) +{ + PurpleAccount *account = purple_connection_get_account(pc); + MsnSession *session = purple_connection_get_protocol_data(pc); + GSList *l; + + purple_debug_info("msn", "Disabling MPOP\n"); + + session->enable_mpop = FALSE; + msn_annotate_contact(session, "Me", "MSN.IM.MPOP", "0", NULL); + + for (l = session->user->endpoints; l; l = l->next) { + MsnUserEndpoint *ep = l->data; + char *user; + + if (ep->id[0] != '\0' && strncasecmp(ep->id + 1, session->guid, 36) == 0) + /* Don't kick myself */ + continue; + + purple_debug_info("msn", "Disconnecting Endpoint %s\n", ep->id); + + user = g_strdup_printf("%s;%s", purple_account_get_username(account), ep->id); + msn_notification_send_uun(session, user, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye"); + g_free(user); + } + + purple_prpl_got_account_actions(account); +} + +static void +msn_show_set_mpop(PurplePluginAction *action) +{ + PurpleConnection *pc; + + pc = (PurpleConnection *)action->context; + + purple_request_action(pc, NULL, _("Allow multiple logins?"), + _("Do you want to allow or disallow connecting from " + "multiple locations simultaneously?"), + PURPLE_DEFAULT_ACTION_NONE, + purple_connection_get_account(pc), NULL, NULL, + pc, 3, + _("Allow"), G_CALLBACK(enable_mpop_cb), + _("Disallow"), G_CALLBACK(disable_mpop_cb), + _("Cancel"), NULL); +} + static void msn_show_set_home_phone(PurplePluginAction *action) { PurpleConnection *gc; MsnSession *session; gc = (PurpleConnection *) action->context; session = gc->proto_data; @@ -617,30 +777,27 @@ msn_show_hotmail_inbox(PurplePluginActio purple_notify_uri(gc, session->passport_info.mail_url); } static void show_send_to_mobile_cb(PurpleBlistNode *node, gpointer ignored) { PurpleBuddy *buddy; PurpleConnection *gc; - MsnSession *session; MsnMobileData *data; PurpleAccount *account; const char *name; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; account = purple_buddy_get_account(buddy); gc = purple_account_get_connection(account); name = purple_buddy_get_name(buddy); - session = gc->proto_data; - data = g_new0(MsnMobileData, 1); data->gc = gc; data->passport = name; purple_request_input(gc, NULL, _("Send a mobile message."), NULL, NULL, TRUE, FALSE, NULL, _("Page"), G_CALLBACK(send_to_mobile_cb), _("Close"), G_CALLBACK(close_mobile_page_cb), @@ -654,26 +811,29 @@ msn_offline_message(const PurpleBuddy *b } void msn_send_privacy(PurpleConnection *gc) { PurpleAccount *account; MsnSession *session; MsnCmdProc *cmdproc; + MsnTransaction *trans; account = purple_connection_get_account(gc); session = gc->proto_data; cmdproc = session->notification->cmdproc; if (account->perm_deny == PURPLE_PRIVACY_ALLOW_ALL || account->perm_deny == PURPLE_PRIVACY_DENY_USERS) - msn_cmdproc_send(cmdproc, "BLP", "%s", "AL"); + trans = msn_transaction_new(cmdproc, "BLP", "%s", "AL"); else - msn_cmdproc_send(cmdproc, "BLP", "%s", "BL"); + trans = msn_transaction_new(cmdproc, "BLP", "%s", "BL"); + + msn_cmdproc_send_trans(cmdproc, trans); } static void initiate_chat_cb(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; PurpleAccount *account; @@ -707,19 +867,17 @@ initiate_chat_cb(PurpleBlistNode *node, purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), alias, NULL, PURPLE_CBFLAGS_NONE, TRUE); } static void t_msn_xfer_init(PurpleXfer *xfer) { - MsnSlpLink *slplink = xfer->data; - msn_slplink_request_ft(slplink, xfer); - msn_slplink_unref(slplink); + msn_request_ft(xfer); } static void t_msn_xfer_cancel_send(PurpleXfer *xfer) { MsnSlpLink *slplink = xfer->data; msn_slplink_unref(slplink); } @@ -1058,24 +1216,37 @@ msn_status_types(PurpleAccount *account) types = g_list_append(types, status); return types; } static GList * msn_actions(PurplePlugin *plugin, gpointer context) { + PurpleConnection *gc; + MsnSession *session; GList *m = NULL; PurplePluginAction *act; + gc = (PurpleConnection *) context; + session = gc->proto_data; + act = purple_plugin_action_new(_("Set Friendly Name..."), msn_show_set_friendly_name); m = g_list_append(m, act); m = g_list_append(m, NULL); + if (session->enable_mpop && session->protocol_ver >= 16) + { + act = purple_plugin_action_new(_("View Locations..."), + msn_show_locations); + m = g_list_append(m, act); + m = g_list_append(m, NULL); + } + act = purple_plugin_action_new(_("Set Home Phone Number..."), msn_show_set_home_phone); m = g_list_append(m, act); act = purple_plugin_action_new(_("Set Work Phone Number..."), msn_show_set_work_phone); m = g_list_append(m, act); @@ -1085,16 +1256,20 @@ msn_actions(PurplePlugin *plugin, gpoint m = g_list_append(m, NULL); #if 0 act = purple_plugin_action_new(_("Enable/Disable Mobile Devices..."), msn_show_set_mobile_support); m = g_list_append(m, act); #endif + act = purple_plugin_action_new(_("Allow/Disallow Multiple Logins..."), + msn_show_set_mpop); + m = g_list_append(m, act); + act = purple_plugin_action_new(_("Allow/Disallow Mobile Pages..."), msn_show_set_mobile_pages); m = g_list_append(m, act); /* QuLogic: Disabled until confirmed correct. */ #if 0 m = g_list_append(m, NULL); act = purple_plugin_action_new(_("View Blocked Text..."), @@ -1200,16 +1375,23 @@ msn_login(PurpleAccount *account) username = msn_normalize(account, purple_account_get_username(account)); if (strcmp(username, purple_account_get_username(account))) purple_account_set_username(account, username); username = purple_account_get_string(account, "display-name", NULL); purple_connection_set_display_name(gc, username); + if (purple_account_get_string(account, "endpoint-name", NULL) == NULL) { + GHashTable *ui_info = purple_core_get_ui_info(); + const gchar *ui_name = ui_info ? g_hash_table_lookup(ui_info, "name") : NULL; + purple_account_set_string(account, "endpoint-name", + ui_name && *ui_name ? ui_name : PACKAGE_NAME); + } + if (!msn_session_connect(session, host, port, http_method)) purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); } static void msn_close(PurpleConnection *gc) @@ -1271,17 +1453,17 @@ msn_send_emoticons(MsnSwitchBoard *swboa g_return_if_fail(body != NULL); msg = msn_message_new(MSN_MSG_SLP); msn_message_set_content_type(msg, "text/x-mms-emoticon"); msn_message_set_flag(msg, 'N'); msn_message_set_bin_data(msg, body->str, body->len); msn_switchboard_send_msg(swboard, msg, TRUE); - msn_message_destroy(msg); + msn_message_unref(msg); } static void msn_emoticon_destroy(MsnEmoticon *emoticon) { if (emoticon->obj) msn_object_destroy(emoticon->obj); g_free(emoticon->smile); g_free(emoticon); @@ -1410,17 +1592,17 @@ msn_send_im(PurpleConnection *gc, const g_free(msgformat); g_free(msgtext); return -E2BIG; } msg = msn_message_new_plain(msgtext); msg->remote_user = g_strdup(who); - msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat); + msn_message_set_header(msg, "X-MMS-IM-Format", msgformat); g_free(msgformat); g_free(msgtext); purple_debug_info("msn", "prepare to send online Message\n"); if (g_ascii_strcasecmp(who, username)) { if (flags & PURPLE_MESSAGE_AUTO_RESP) { @@ -1443,34 +1625,34 @@ msn_send_im(PurpleConnection *gc, const /* * In MSN, you can't send messages to yourself, so * we'll fake like we received it ;) */ body_str = msn_message_to_string(msg); body_enc = g_markup_escape_text(body_str, -1); g_free(body_str); - format = msn_message_get_attr(msg, "X-MMS-IM-Format"); + format = msn_message_get_header_value(msg, "X-MMS-IM-Format"); msn_parse_format(format, &pre, &post); body_str = g_strdup_printf("%s%s%s", pre ? pre : "", body_enc ? body_enc : "", post ? post : ""); g_free(body_enc); g_free(pre); g_free(post); serv_got_typing_stopped(gc, who); imdata->gc = gc; imdata->who = who; imdata->msg = body_str; imdata->flags = flags & ~PURPLE_MESSAGE_SEND; imdata->when = time(NULL); purple_timeout_add(0, msn_send_me_im, imdata); } - msn_message_destroy(msg); + msn_message_unref(msg); } else { /*send Offline Instant Message,only to MSN Passport User*/ char *friendname; purple_debug_info("msn", "prepare to send offline Message\n"); friendname = msn_encode_mime(account->username); msn_oim_prep_send_msg_info(session->oim, @@ -1518,23 +1700,23 @@ msn_send_typing(PurpleConnection *gc, co if (swboard == NULL || !msn_switchboard_can_send(swboard)) return 0; swboard->flag |= MSN_SB_FLAG_IM; msg = msn_message_new(MSN_MSG_TYPING); msn_message_set_content_type(msg, "text/x-msmsgscontrol"); msn_message_set_flag(msg, 'U'); - msn_message_set_attr(msg, "TypingUser", + msn_message_set_header(msg, "TypingUser", purple_account_get_username(account)); msn_message_set_bin_data(msg, "\r\n", 2); msn_switchboard_send_msg(swboard, msg, FALSE); - msn_message_destroy(msg); + msn_message_unref(msg); return MSN_TYPING_SEND_TIMEOUT; } static void msn_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *gc; @@ -1574,17 +1756,17 @@ add_pending_buddy(MsnSession *session, group = msn_user_remove_pending_group(user); if (network != MSN_NETWORK_UNKNOWN) { MsnUserList *userlist = session->userlist; MsnUser *user2 = msn_userlist_find_user(userlist, who); if (user2 != NULL) { /* User already in userlist, so just update it. */ - msn_user_destroy(user); + msn_user_unref(user); user = user2; } else { msn_userlist_add_user(userlist, user); } msn_user_set_network(user, network); msn_userlist_add_buddy(userlist, who, group); } @@ -1594,17 +1776,17 @@ add_pending_buddy(MsnSession *session, gchar *buf; buf = g_strdup_printf(_("Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses."), who); if (!purple_conv_present_error(who, session->account, buf)) purple_notify_error(purple_account_get_connection(session->account), NULL, _("Unable to Add"), buf); g_free(buf); /* Remove from local list */ purple_blist_remove_buddy(buddy); - msn_user_destroy(user); + msn_user_unref(user); } g_free(group); } static void finish_auth_request(MsnAddReqData *data, char *msg) { PurpleConnection *pc; @@ -1928,17 +2110,17 @@ msn_chat_send(PurpleConnection *gc, int { g_free(msgformat); g_free(msgtext); return -E2BIG; } msg = msn_message_new_plain(msgtext); - msn_message_set_attr(msg, "X-MMS-IM-Format", msgformat); + msn_message_set_header(msg, "X-MMS-IM-Format", msgformat); smileys = msn_msg_grab_emoticons(msg->body, username); while (smileys) { smile = (MsnEmoticon *)smileys->data; emoticons = msn_msg_emoticon_add(emoticons, smile); if (purple_conv_custom_smiley_add(swboard->conv, smile->smile, "sha1", purple_smiley_get_checksum(smile->ps), FALSE)) { @@ -1953,41 +2135,44 @@ msn_chat_send(PurpleConnection *gc, int } if (emoticons) { msn_send_emoticons(swboard, emoticons); g_string_free(emoticons, TRUE); } msn_switchboard_send_msg(swboard, msg, FALSE); - msn_message_destroy(msg); + msn_message_unref(msg); g_free(msgformat); g_free(msgtext); serv_got_chat_in(gc, id, purple_account_get_username(account), flags, message, time(NULL)); return 0; } static void msn_keepalive(PurpleConnection *gc) { MsnSession *session; + MsnTransaction *trans; session = gc->proto_data; if (!session->http_method) { MsnCmdProc *cmdproc; cmdproc = session->notification->cmdproc; - msn_cmdproc_send_quick(cmdproc, "PNG", NULL, NULL); + trans = msn_transaction_new(cmdproc, "PNG", NULL); + msn_transaction_set_saveable(trans, FALSE); + msn_cmdproc_send_trans(cmdproc, trans); } } static void msn_alias_buddy(PurpleConnection *pc, const char *name, const char *alias) { MsnSession *session; session = pc->proto_data; @@ -2082,21 +2267,19 @@ msn_set_buddy_icon(PurpleConnection *gc, msn_change_status(session); } static void msn_remove_group(PurpleConnection *gc, PurpleGroup *group) { MsnSession *session; - MsnCmdProc *cmdproc; const char *gname; session = gc->proto_data; - cmdproc = session->notification->cmdproc; gname = purple_group_get_name(group); purple_debug_info("msn", "Remove group %s\n", gname); /*we can't delete the default group*/ if(!strcmp(gname, MSN_INDIVIDUALS_GROUP_NAME)|| !strcmp(gname, MSN_NON_IM_GROUP_NAME)) { purple_debug_info("msn", "This group can't be removed, returning.\n"); @@ -2715,26 +2898,24 @@ msn_get_info(PurpleConnection *gc, const g_free(url); } static gboolean msn_load(PurplePlugin *plugin) { msn_notification_init(); msn_switchboard_init(); - msn_sync_init(); return TRUE; } static gboolean msn_unload(PurplePlugin *plugin) { msn_notification_end(); msn_switchboard_end(); - msn_sync_end(); return TRUE; } static PurpleAccount *find_acct(const char *prpl, const char *acct_id) { PurpleAccount *acct = NULL; @@ -2939,16 +3120,21 @@ init_plugin(PurplePlugin *plugin) prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); option = purple_account_option_bool_new(_("Allow direct connections"), "direct_connect", TRUE); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + option = purple_account_option_bool_new(_("Allow connecting from multiple locations"), + "mpop", TRUE); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, + option); + purple_cmd_register("nudge", "", PURPLE_CMD_P_PRPL, PURPLE_CMD_FLAG_IM | PURPLE_CMD_FLAG_PRPL_ONLY, "prpl-msn", msn_cmd_nudge, _("nudge: nudge a user to get their attention"), NULL); purple_prefs_remove("/plugins/prpl/msn"); purple_signal_connect(purple_get_core(), "uri-handler", plugin, diff --git a/purple/libpurple/protocols/msn/msn.h b/purple/libpurple/protocols/msn/msn.h --- a/purple/libpurple/protocols/msn/msn.h +++ b/purple/libpurple/protocols/msn/msn.h @@ -21,26 +21,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_H #define MSN_H typedef enum { - MSN_LIST_FL_OP = 0x01, - MSN_LIST_AL_OP = 0x02, - MSN_LIST_BL_OP = 0x04, - MSN_LIST_RL_OP = 0x08, - MSN_LIST_PL_OP = 0x10 -} MsnListOp; -#define MSN_LIST_OP_MASK 0x07 - -typedef enum -{ MSN_CLIENT_CAP_WIN_MOBILE = 0x0000001, MSN_CLIENT_CAP_INK_GIF = 0x0000004, MSN_CLIENT_CAP_INK_ISF = 0x0000008, MSN_CLIENT_CAP_VIDEO_CHAT = 0x0000010, MSN_CLIENT_CAP_PACKET = 0x0000020, MSN_CLIENT_CAP_MSNMOBILE = 0x0000040, MSN_CLIENT_CAP_MSNDIRECT = 0x0000080, MSN_CLIENT_CAP_WEBMSGR = 0x0000200, @@ -78,45 +68,29 @@ typedef enum MSN_CLIENT_VER_8_1 = 0x70, /* MSNC7 */ MSN_CLIENT_VER_8_5 = 0x80, /* MSNC8 */ MSN_CLIENT_VER_9_0 = 0x90, /* MSNC9 */ MSN_CLIENT_VER_14_0 = 0xA0 /* MSNC10 */ } MsnClientVerId; #include "internal.h" -#include "account.h" -#include "accountopt.h" -#include "blist.h" -#include "connection.h" -#include "conversation.h" -#include "debug.h" -#include "cipher.h" -#include "notify.h" -#include "privacy.h" -#include "proxy.h" -#include "prpl.h" -#include "request.h" -#include "servconn.h" -#include "sslconn.h" -#include "util.h" - -#include "ft.h" +#include "session.h" #include "msg.h" #define MSN_BUF_LEN 8192 /* Windows Live Messenger Server*/ #define MSN_SERVER "messenger.hotmail.com" #define MSN_HTTPCONN_SERVER "gateway.messenger.hotmail.com" #define MSN_PORT 1863 -#define WLM_PROT_VER 15 +#define WLM_PROT_VER 16 -#define WLM_MAX_PROTOCOL 15 +#define WLM_MAX_PROTOCOL 16 #define WLM_MIN_PROTOCOL 15 #define MSN_TYPING_RECV_TIMEOUT 6 #define MSN_TYPING_SEND_TIMEOUT 4 #define PROFILE_URL "http://spaces.live.com/profile.aspx?mem=" #define PHOTO_URL " contactparams:photopreauthurl=\"" @@ -129,26 +103,24 @@ typedef enum #define MSN_CLIENTINFO \ "Client-Name: Purple/" VERSION "\r\n" \ "Chat-Logging: Y\r\n" /* Index into attention_types */ #define MSN_NUDGE 0 -#define MSN_CLIENT_ID_VERSION MSN_CLIENT_VER_7_0 +#define MSN_CLIENT_ID_VERSION MSN_CLIENT_VER_9_0 #define MSN_CLIENT_ID_CAPABILITIES (MSN_CLIENT_CAP_PACKET|MSN_CLIENT_CAP_INK_GIF|MSN_CLIENT_CAP_VOICEIM) +#define MSN_CLIENT_ID_EXT_CAPS (0) #define MSN_CLIENT_ID \ ((MSN_CLIENT_ID_VERSION << 24) | \ (MSN_CLIENT_ID_CAPABILITIES)) -#define MSN_CLIENT_EXT_ID 0 - -gboolean msn_email_is_valid(const char *passport); void msn_set_public_alias(PurpleConnection *gc, const char *alias, PurpleSetPublicAliasSuccessCallback success_cb, PurpleSetPublicAliasFailureCallback failure_cb); void msn_send_privacy(PurpleConnection *gc); void msn_send_im_message(MsnSession *session, MsnMessage *msg); #endif /* MSN_H */ diff --git a/purple/libpurple/protocols/msn/msnutils.c b/purple/libpurple/protocols/msn/msnutils.c --- a/purple/libpurple/protocols/msn/msnutils.c +++ b/purple/libpurple/protocols/msn/msnutils.c @@ -16,28 +16,29 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" + #include "msn.h" #include "msnutils.h" #include "cipher.h" -char *rand_guid(void); - /************************************************************************** * Util **************************************************************************/ char * -rand_guid() +rand_guid(void) { return g_strdup_printf("%4X%4X-%4X-%4X-%4X-%4X%4X%4X", rand() % 0xAAFF + 0x1111, rand() % 0xAAFF + 0x1111, rand() % 0xAAFF + 0x1111, rand() % 0xAAFF + 0x1111, rand() % 0xAAFF + 0x1111, rand() % 0xAAFF + 0x1111, @@ -176,39 +177,50 @@ msn_encode_mime(const char *str) return retval; } /* * We need this because we're only supposed to encode spaces in the font * names. purple_url_encode() isn't acceptable. */ -static const char * -encode_spaces(const char *str) +gboolean +msn_encode_spaces(const char *str, char *buf, size_t len) { - static char buf[BUF_LEN]; - const char *c; - char *d; + char *nonspace = buf; - g_return_val_if_fail(str != NULL, NULL); + while (isspace(*str)) + str++; - for (c = str, d = buf; *c != '\0'; c++) - { - if (*c == ' ') - { - *d++ = '%'; - *d++ = '2'; - *d++ = '0'; + for (; *str && len > 1; str++) { + if (*str == '%') { + if (len < 4) + break; + *buf++ = '%'; + *buf++ = '2'; + *buf++ = '5'; + len -= 3; + nonspace = buf; + } else if (*str == ' ') { + if (len < 4) + break; + *buf++ = '%'; + *buf++ = '2'; + *buf++ = '0'; + len -= 3; + } else { + *buf++ = *str; + len--; + nonspace = buf; } - else - *d++ = *c; } - *d = '\0'; - return buf; + *nonspace = '\0'; + + return (*str == '\0'); } /* * Taken from the zephyr plugin. * This parses HTML formatting (put out by one of the gtkimhtml widgets * and converts it to msn formatting. It doesn't deal with the tag closing, * but gtkimhtml widgets give valid html. * It currently deals properly with , , , , @@ -217,16 +229,17 @@ encode_spaces(const char *str) */ void msn_import_html(const char *html, char **attributes, char **message) { int len, retcount = 0; const char *c; char *msg; char *fontface = NULL; + char fontface_encoded[BUF_LEN]; char fonteffect[5]; char fontcolor[7]; char direction = '0'; gboolean has_bold = FALSE; gboolean has_italic = FALSE; gboolean has_underline = FALSE; gboolean has_strikethrough = FALSE; @@ -449,18 +462,19 @@ msn_import_html(const char *html, char * } else msg[retcount++] = *c++; } if (fontface == NULL) fontface = g_strdup("Segoe UI"); + msn_encode_spaces(fontface, fontface_encoded, BUF_LEN); *attributes = g_strdup_printf("FN=%s; EF=%s; CO=%s; PF=0; RL=%c", - encode_spaces(fontface), + fontface_encoded, fonteffect, fontcolor, direction); *message = msg; g_free(fontface); } void msn_parse_socket(const char *str, char **ret_host, int *ret_port) @@ -477,16 +491,39 @@ msn_parse_socket(const char *str, char * } else { port = 1863; } *ret_host = host; *ret_port = port; } +gboolean +msn_email_is_valid(const char *passport) +{ + if (purple_email_is_valid(passport)) { + /* Special characters aren't allowed in domains, so only go to '@' */ + while (*passport != '@') { + if (*passport == '/') + return FALSE; + else if (*passport == '?') + return FALSE; + else if (*passport == '=') + return FALSE; + /* MSN also doesn't like colons, but that's checked already */ + + passport++; + } + + return TRUE; + } + + return FALSE; +} + /*************************************************************************** * MSN Challenge Computing Function ***************************************************************************/ /* * Handle MSN Challenge computation * This algorithm references * http://imfreedom.org/wiki/index.php/MSN:NS/Challenges @@ -542,17 +579,17 @@ msn_handle_chl(char *input, char *output buf[len + fix] = '\0'; len += fix; } /* split into integers */ chlStringParts = (unsigned int *)buf; /* this is magic */ - for (i = 0; i < (strlen(buf) / 4); i += 2) { + for (i = 0; i < (len / 4); i += 2) { long long temp; chlStringParts[i] = GUINT_TO_LE(chlStringParts[i]); chlStringParts[i + 1] = GUINT_TO_LE(chlStringParts[i + 1]); temp = (0x0E79A9C1 * (long long)chlStringParts[i]) % 0x7FFFFFFF; temp = (md5Parts[0] * (temp + nLow) + md5Parts[1]) % 0x7FFFFFFF; nHigh += temp; @@ -581,8 +618,92 @@ msn_handle_chl(char *input, char *output { output[i * 2] = hexChars[(newHash[i] >> 4) & 0xF]; output[(i * 2) + 1] = hexChars[newHash[i] & 0xF]; } output[32] = '\0'; } +guint8 +msn_read8(const char *buf) +{ + return (guint8)buf[0]; +} + +guint16 +msn_read16le(const char *buf) +{ + return GUINT16_FROM_LE(*(guint16 *)buf); +} + +guint16 +msn_read16be(const char *buf) +{ + return GUINT16_FROM_BE(*(guint16 *)buf); +} + +guint32 +msn_read32le(const char *buf) +{ + return GUINT32_FROM_LE(*(guint32 *)buf); +} + +guint32 +msn_read32be(const char *buf) +{ + return GUINT32_FROM_BE(*(guint32 *)buf); +} + +guint64 +msn_read64le(const char *buf) +{ + return GUINT64_FROM_LE(*(guint64 *)buf); +} + +guint64 +msn_read64be(const char *buf) +{ + return GUINT64_FROM_BE(*(guint64 *)buf); +} + +void +msn_write8(char *buf, guint8 data) +{ + *(guint8 *)buf = data; +} + +void +msn_write16le(char *buf, guint16 data) +{ + *(guint16 *)buf = GUINT16_TO_LE(data); +} + +void +msn_write16be(char *buf, guint16 data) +{ + *(guint16 *)buf = GUINT16_TO_BE(data); +} + +void +msn_write32le(char *buf, guint32 data) +{ + *(guint32 *)buf = GUINT32_TO_LE(data); +} + +void +msn_write32be(char *buf, guint32 data) +{ + *(guint32 *)buf = GUINT32_TO_BE(data); +} + +void +msn_write64le(char *buf, guint64 data) +{ + *(guint64 *)buf = GUINT64_TO_LE(data); +} + +void +msn_write64be(char *buf, guint64 data) +{ + *(guint64 *)buf = GUINT64_TO_BE(data); +} + diff --git a/purple/libpurple/protocols/msn/msnutils.h b/purple/libpurple/protocols/msn/msnutils.h --- a/purple/libpurple/protocols/msn/msnutils.h +++ b/purple/libpurple/protocols/msn/msnutils.h @@ -28,16 +28,28 @@ char *msn_encode_mime(const char *str); /** * Generate the Random GUID */ char *rand_guid(void); /** + * Encodes the spaces in a string + * + * @param str The string to be encoded. + * @param buf The buffer to hold the encoded string. + * @param len The maximum length (including NUL) to put in @buf. + * + * @return Whether @str was able to fit in @buf. + */ +gboolean +msn_encode_spaces(const char *str, char *buf, size_t len); + +/** * Parses the MSN message formatting into a format compatible with Purple. * * @param mime The mime header with the formatting. * @param pre_ret The returned prefix string. * @param post_ret The returned postfix string. * * @return The new message. */ @@ -49,12 +61,175 @@ void msn_parse_format(const char *mime, * @param html The html message to format. * @param attributes The returned attributes string. * @param message The returned message string. * * @return The new message. */ void msn_import_html(const char *html, char **attributes, char **message); +/** + * Parses a socket string. + * + * @param str A host:port string. + * @param ret_host Return string value of the host. + * @param ret_port Return integer value of the port. + */ void msn_parse_socket(const char *str, char **ret_host, int *ret_port); + +/** + * Verify if the email is a vaild passport. + * + * @param passport The email + * + * @return True if it is a valid passport, else FALSE + */ +gboolean msn_email_is_valid(const char *passport); + +/** + * Handle MSN Challenge Computation + * This algorithm references + * http://imfreedom.org/wiki/index.php/MSN:NS/Challenges + * + * @param input Challenge input. + * @param output Callenge output. + */ void msn_handle_chl(char *input, char *output); +/** + * Read a byte from a buffer + * + * @param buf Pointer to buffer. + * + * @return 8-bit byte + */ +guint8 msn_read8(const char *buf); + +/** + * Read a little-endian short from a buffer + * + * @param buf Pointer to buffer. + * + * @return 16-bit short + */ +guint16 msn_read16le(const char *buf); + +/** + * Read a big-endian short from a buffer + * + * @param buf Pointer to buffer. + * + * @return 16-bit short + */ +guint16 msn_read16be(const char *buf); + +/** + * Read a little-endian int from a buffer + * + * @param buf Pointer to buffer. + * + * @return 32-bit int + */ +guint32 msn_read32le(const char *buf); + +/** + * Read a big-endian int from a buffer + * + * @param buf Pointer to buffer. + * + * @return 32-bit int + */ +guint32 msn_read32be(const char *buf); + +/** + * Read a little-endian long from a buffer + * + * @param buf Pointer to buffer. + * + * @return 64-bit long + */ +guint64 msn_read64le(const char *buf); + +/** + * Read a big-endian long from a buffer + * + * @param buf Pointer to buffer. + * + * @return 64-bit long + */ +guint64 msn_read64be(const char *buf); + +/** + * Write a byte to a buffer + * + * @param buf Pointer to buffer. + * @param data 8-bit byte. + */ +void msn_write8(char *buf, guint8 data); + +/** + * Write a little-endian short to a buffer + * + * @param buf Pointer to buffer. + * @param data short. + */ +void msn_write16le(char *buf, guint16 data); + +/** + * Write a big-endian short to a buffer + * + * @param buf Pointer to buffer. + * @param data short. + */ +void msn_write16be(char *buf, guint16 data); + +/** + * Write a little-endian int to a buffer + * + * @param buf Pointer to buffer. + * @param data int. + */ +void msn_write32le(char *buf, guint32 data); + +/** + * Write a big-endian int to a buffer + * + * @param buf Pointer to buffer. + * @param data int. + */ +void msn_write32be(char *buf, guint32 data); + +/** + * Write a little-endian long to a buffer + * + * @param buf Pointer to buffer. + * @param data long. + */ +void msn_write64le(char *buf, guint64 data); + +/** + * Write a big-endian long to a buffer + * + * @param buf Pointer to buffer. + * @param data short + */ +void msn_write64be(char *buf, guint64 data); + +/** + * Same as above, but these increment the buf pointer. + */ +#define msn_pop8(buf) msn_read8((buf+=1)-1) +#define msn_pop16le(buf) msn_read16le((buf+=2)-2) +#define msn_pop16be(buf) msn_read16be((buf+=2)-2) +#define msn_pop32le(buf) msn_read32le((buf+=4)-4) +#define msn_pop32be(buf) msn_read32be((buf+=4)-4) +#define msn_pop64le(buf) msn_read64le((buf+=8)-8) +#define msn_pop64be(buf) msn_read64be((buf+=8)-8) +#define msn_push8(buf, data) msn_write8(buf, data), buf+=1 +#define msn_push16le(buf, data) msn_write16le(buf, data), buf+=2 +#define msn_push16be(buf, data) msn_write16be(buf, data), buf+=2 +#define msn_push32le(buf, data) msn_write32le(buf, data), buf+=4 +#define msn_push32be(buf, data) msn_write32be(buf, data), buf+=4 +#define msn_push64le(buf, data) msn_write64le(buf, data), buf+=8 +#define msn_push64be(buf, data) msn_write64be(buf, data), buf+=8 + #endif /* MSN_UTILS_H */ + diff --git a/purple/libpurple/protocols/msn/nexus.c b/purple/libpurple/protocols/msn/nexus.c --- a/purple/libpurple/protocols/msn/nexus.c +++ b/purple/libpurple/protocols/msn/nexus.c @@ -16,17 +16,21 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "cipher.h" +#include "debug.h" + #include "soap.h" #include "nexus.h" #include "notification.h" /************************************************************************** * Valid Ticket Tokens **************************************************************************/ diff --git a/purple/libpurple/protocols/msn/nexus.h b/purple/libpurple/protocols/msn/nexus.h --- a/purple/libpurple/protocols/msn/nexus.h +++ b/purple/libpurple/protocols/msn/nexus.h @@ -19,16 +19,18 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_NEXUS_H #define MSN_NEXUS_H +#include "internal.h" + typedef struct _MsnNexus MsnNexus; typedef struct _MsnTicketToken MsnTicketToken; typedef struct _MsnUsrKey MsnUsrKey; /* Index into ticket_tokens in nexus.c Keep updated! */ typedef enum { MSN_AUTH_MESSENGER = 0, diff --git a/purple/libpurple/protocols/msn/notification.c b/purple/libpurple/protocols/msn/notification.c --- a/purple/libpurple/protocols/msn/notification.c +++ b/purple/libpurple/protocols/msn/notification.c @@ -16,27 +16,29 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "cipher.h" +#include "core.h" +#include "debug.h" + #include "notification.h" + #include "contact.h" -#include "state.h" #include "error.h" #include "msnutils.h" -#include "page.h" - +#include "state.h" #include "userlist.h" -#include "sync.h" -#include "slplink.h" static MsnTable *cbs_table; /************************************************************************** * Main **************************************************************************/ static void @@ -87,42 +89,42 @@ msn_notification_destroy(MsnNotification * Connect **************************************************************************/ static void connect_cb(MsnServConn *servconn) { MsnCmdProc *cmdproc; MsnSession *session; - PurpleAccount *account; + MsnTransaction *trans; GString *vers; const char *ver_str; int i; g_return_if_fail(servconn != NULL); cmdproc = servconn->cmdproc; session = servconn->session; - account = session->account; vers = g_string_new(""); for (i = WLM_MAX_PROTOCOL; i >= WLM_MIN_PROTOCOL; i--) g_string_append_printf(vers, " MSNP%d", i); g_string_append(vers, " CVR0"); if (session->login_step == MSN_LOGIN_STEP_START) msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE); else msn_session_set_login_step(session, MSN_LOGIN_STEP_HANDSHAKE2); /* Skip the initial space */ ver_str = (vers->str + 1); - msn_cmdproc_send(cmdproc, "VER", "%s", ver_str); + trans = msn_transaction_new(cmdproc, "VER", "%s", ver_str); + msn_cmdproc_send_trans(cmdproc, trans); g_string_free(vers, TRUE); } gboolean msn_notification_connect(MsnNotification *notification, const char *host, int port) { MsnServConn *servconn; @@ -151,42 +153,46 @@ msn_notification_disconnect(MsnNotificat /************************************************************************** * Login **************************************************************************/ void msn_got_login_params(MsnSession *session, const char *ticket, const char *response) { MsnCmdProc *cmdproc; + MsnTransaction *trans; cmdproc = session->notification->cmdproc; msn_session_set_login_step(session, MSN_LOGIN_STEP_AUTH_END); - msn_cmdproc_send(cmdproc, "USR", "SSO S %s %s", ticket, response); + if (session->protocol_ver >= 16) + trans = msn_transaction_new(cmdproc, "USR", "SSO S %s %s %s", ticket, response, session->guid); + else + trans = msn_transaction_new(cmdproc, "USR", "SSO S %s %s", ticket, response); + + msn_cmdproc_send_trans(cmdproc, trans); } static void cvr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { PurpleAccount *account; + MsnTransaction *trans; account = cmdproc->session->account; - msn_cmdproc_send(cmdproc, "USR", "SSO I %s", purple_account_get_username(account)); + trans = msn_transaction_new(cmdproc, "USR", "SSO I %s", purple_account_get_username(account)); + msn_cmdproc_send_trans(cmdproc, trans); } static void usr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { - MsnSession *session; - PurpleAccount *account; - - session = cmdproc->session; - account = session->account; + MsnSession *session = cmdproc->session; if (!g_ascii_strcasecmp(cmd->params[1], "OK")) { /* authenticate OK */ msn_session_set_login_step(session, MSN_LOGIN_STEP_SYN); } else if (!g_ascii_strcasecmp(cmd->params[1], "SSO")) { @@ -226,51 +232,57 @@ usr_error(MsnCmdProc *cmdproc, MsnTransa msn_session_set_error(cmdproc->session, msnerr, NULL); } static void ver_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSession *session; + MsnTransaction *trans; PurpleAccount *account; gboolean protocol_supported = FALSE; - char proto_str[8]; + int proto_ver; size_t i; session = cmdproc->session; account = session->account; - g_snprintf(proto_str, sizeof(proto_str), "MSNP%d", session->protocol_ver); - + session->protocol_ver = 0; for (i = 1; i < cmd->param_count; i++) { - if (!strcmp(cmd->params[i], proto_str)) - { - protocol_supported = TRUE; - break; + if (sscanf(cmd->params[i], "MSNP%d", &proto_ver) == 1) { + if (proto_ver >= WLM_MIN_PROTOCOL + && proto_ver <= WLM_MAX_PROTOCOL + && proto_ver > session->protocol_ver) { + protocol_supported = TRUE; + session->protocol_ver = proto_ver; + } } } if (!protocol_supported) { msn_session_set_error(session, MSN_ERROR_UNSUPPORTED_PROTOCOL, NULL); return; } + purple_debug_info("msn", "Negotiated protocol version %d with the server.\n", session->protocol_ver); + /* * Windows Live Messenger 8.5 * Notice :CVR String discriminate! * reference of http://www.microsoft.com/globaldev/reference/oslocversion.mspx * to see the Local ID */ - msn_cmdproc_send(cmdproc, "CVR", + trans = msn_transaction_new(cmdproc, "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 8.5.1302 BC01 %s", purple_account_get_username(account)); + msn_cmdproc_send_trans(cmdproc, trans); } /************************************************************************** * Log out **************************************************************************/ static void out_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) @@ -282,22 +294,26 @@ out_cmd(MsnCmdProc *cmdproc, MsnCommand NULL); else if (!g_ascii_strcasecmp(cmd->params[0], "SSD")) msn_session_set_error(cmdproc->session, MSN_ERROR_SERV_DOWN, NULL); } void msn_notification_close(MsnNotification *notification) { + MsnTransaction *trans; + g_return_if_fail(notification != NULL); if (!notification->in_use) return; - msn_cmdproc_send_quick(notification->cmdproc, "OUT", NULL, NULL); + trans = msn_transaction_new(notification->cmdproc, "OUT", NULL); + msn_transaction_set_saveable(trans, FALSE); + msn_cmdproc_send_trans(notification->cmdproc, trans); msn_notification_disconnect(notification); } /************************************************************************** * Messages **************************************************************************/ @@ -310,17 +326,17 @@ msg_cmd_post(MsnCmdProc *cmdproc, MsnCom msg = msn_message_new_from_cmd(cmdproc->session, cmd); msn_message_parse_payload(msg, payload, len, MSG_LINE_DEM, MSG_BODY_DEM); if (purple_debug_is_verbose()) msn_message_show_readable(msg, "Notification", TRUE); msn_cmdproc_process_msg(cmdproc, msg); - msn_message_destroy(msg); + msn_message_unref(msg); } static void msg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { purple_debug_info("msn", "Processing MSG... \n"); /* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued @@ -366,17 +382,20 @@ static void ubm_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { purple_debug_info("msn", "Processing UBM... \n"); /* NOTE: cmd is not always cmdproc->last_cmd, sometimes cmd is a queued * command and we are processing it */ if (cmd->payload == NULL) { cmdproc->last_cmd->payload_cb = msg_cmd_post; - cmd->payload_len = atoi(cmd->params[3]); + if (cmdproc->session->protocol_ver >= 16) + cmd->payload_len = atoi(cmd->params[5]); + else + cmd->payload_len = atoi(cmd->params[3]); } else { g_return_if_fail(cmd->payload_cb != NULL); purple_debug_info("msn", "UBM payload:{%.*s}\n", (guint)(cmd->payload_len), cmd->payload); msg_cmd_post(cmdproc, cmd, cmd->payload, cmd->payload_len); } } @@ -705,21 +724,18 @@ adl_cmd_parse(MsnCmdProc *cmdproc, MsnCo if (root == NULL) { purple_debug_info("msn", "Invalid XML in ADL!\n"); return; } for (domain_node = xmlnode_get_child(root, "d"); domain_node; domain_node = xmlnode_get_next_twin(domain_node)) { - const gchar * domain = NULL; xmlnode *contact_node = NULL; - domain = xmlnode_get_attrib(domain_node, "n"); - for (contact_node = xmlnode_get_child(domain_node, "c"); contact_node; contact_node = xmlnode_get_next_twin(contact_node)) { const gchar *list; gint list_op = 0; list = xmlnode_get_attrib(contact_node, "l"); if (list != NULL) { @@ -994,85 +1010,103 @@ fln_cmd(MsnCmdProc *cmdproc, MsnCommand msn_user_set_state(user, NULL); msn_user_update(user); } static void iln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSession *session; - PurpleAccount *account; - PurpleConnection *gc; MsnUser *user; MsnObject *msnobj = NULL; - unsigned long clientid; + unsigned long clientid, extcaps; + char *extcap_str; int networkid = 0; const char *state, *passport; char *friendly; session = cmdproc->session; - account = session->account; - gc = purple_account_get_connection(account); state = cmd->params[1]; passport = cmd->params[2]; user = msn_userlist_find_user(session->userlist, passport); if (user == NULL) /* Where'd this come from? */ return; if (cmd->param_count == 8) { /* Yahoo! Buddy, looks like */ networkid = atoi(cmd->params[3]); friendly = g_strdup(purple_url_decode(cmd->params[4])); - clientid = strtoul(cmd->params[5], NULL, 10); + clientid = strtoul(cmd->params[5], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; /* cmd->params[7] seems to be a URL to a Yahoo! icon: https://sec.yimg.com/i/us/nt/b/purpley.1.0.png ... and it's purple, HAH! */ } else if (cmd->param_count == 7) { /* MSNP14+ with Display Picture object */ networkid = atoi(cmd->params[3]); friendly = g_strdup(purple_url_decode(cmd->params[4])); - clientid = strtoul(cmd->params[5], NULL, 10); + clientid = strtoul(cmd->params[5], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[6])); } else if (cmd->param_count == 6) { /* Yes, this is 5. The friendly name could start with a number, but the display picture object can't... */ if (isdigit(cmd->params[5][0])) { /* MSNP14 without Display Picture object */ networkid = atoi(cmd->params[3]); friendly = g_strdup(purple_url_decode(cmd->params[4])); - clientid = strtoul(cmd->params[5], NULL, 10); + clientid = strtoul(cmd->params[5], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; } else { /* MSNP8+ with Display Picture object */ friendly = g_strdup(purple_url_decode(cmd->params[3])); - clientid = strtoul(cmd->params[4], NULL, 10); + clientid = strtoul(cmd->params[4], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5])); } } else if (cmd->param_count == 5) { /* MSNP8+ without Display Picture object */ friendly = g_strdup(purple_url_decode(cmd->params[3])); - clientid = strtoul(cmd->params[4], NULL, 10); + clientid = strtoul(cmd->params[4], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; } else { purple_debug_warning("msn", "Received ILN with unknown number of parameters.\n"); return; } if (msn_user_set_friendly_name(user, friendly)) { msn_update_contact(session, passport, MSN_UPDATE_DISPLAY, friendly); } g_free(friendly); msn_user_set_object(user, msnobj); user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->extinfo && user->extinfo->phone_mobile && user->extinfo->phone_mobile[0] == '+'); msn_user_set_clientid(user, clientid); + msn_user_set_extcaps(user, extcaps); msn_user_set_network(user, networkid); msn_user_set_state(user, state); msn_user_update(user); } static void ipg_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) @@ -1173,17 +1207,17 @@ ipg_cmd_post(MsnCmdProc *cmdproc, MsnCom char *body_str = msn_message_to_string(msg); char *body_enc = g_markup_escape_text(body_str, -1); purple_conversation_write(conv, NULL, body_enc, PURPLE_MESSAGE_RAW, time(NULL)); g_free(body_str); g_free(body_enc); - msn_message_destroy(msg); + msn_message_unref(msg); trans->data = NULL; } } } } else { serv_got_im(gc, who, text, 0, time(NULL)); } @@ -1197,55 +1231,58 @@ ipg_cmd(MsnCmdProc *cmdproc, MsnCommand cmd->payload_len = atoi(cmd->params[0]); cmdproc->last_cmd->payload_cb = ipg_cmd_post; } static void nln_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSession *session; - PurpleAccount *account; - PurpleConnection *gc; MsnUser *user; MsnObject *msnobj; - unsigned long clientid; + unsigned long clientid, extcaps; + char *extcap_str; int networkid; const char *state, *passport, *friendly; session = cmdproc->session; - account = session->account; - gc = purple_account_get_connection(account); state = cmd->params[0]; passport = cmd->params[1]; networkid = atoi(cmd->params[2]); friendly = purple_url_decode(cmd->params[3]); user = msn_userlist_find_user(session->userlist, passport); if (user == NULL) return; - if (msn_user_set_friendly_name(user, friendly)) + if (msn_user_set_friendly_name(user, friendly) && user != session->user) { msn_update_contact(session, passport, MSN_UPDATE_DISPLAY, friendly); } if (cmd->param_count == 6) { msnobj = msn_object_new_from_string(purple_url_decode(cmd->params[5])); msn_user_set_object(user, msnobj); } else { msn_user_set_object(user, NULL); } - clientid = strtoul(cmd->params[4], NULL, 10); + clientid = strtoul(cmd->params[4], &extcap_str, 10); + if (session->protocol_ver >= 16 && extcap_str && *extcap_str) + extcaps = strtoul(extcap_str+1, NULL, 10); + else + extcaps = 0; + user->mobile = (clientid & MSN_CLIENT_CAP_MSNMOBILE) || (user->extinfo && user->extinfo->phone_mobile && user->extinfo->phone_mobile[0] == '+'); msn_user_set_clientid(user, clientid); + msn_user_set_extcaps(user, extcaps); msn_user_set_network(user, networkid); msn_user_set_state(user, state); msn_user_update(user); } #if 0 static void @@ -1396,17 +1433,17 @@ rng_cmd(MsnCmdProc *cmdproc, MsnCommand msn_parse_socket(cmd->params[1], &host, &port); if (session->http_method) port = 80; swboard = msn_switchboard_new(session); msn_switchboard_set_invited(swboard, TRUE); - msn_switchboard_set_session_id(swboard, cmd->params[0]); + msn_switchboard_set_session_id(swboard, session_id); msn_switchboard_set_auth_key(swboard, cmd->params[3]); swboard->im_user = g_strdup(cmd->params[4]); /* msn_switchboard_add_user(swboard, cmd->params[4]); */ if (!msn_switchboard_connect(swboard, host, port)) msn_switchboard_destroy(swboard); g_free(host); @@ -1503,16 +1540,102 @@ gcf_cmd(MsnCmdProc *cmdproc, MsnCommand static void sbs_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { purple_debug_info("msn", "Processing SBS... \n"); /*get the payload content*/ } +static void +parse_user_endpoints(MsnUser *user, xmlnode *payloadNode) +{ + MsnSession *session; + xmlnode *epNode, *capsNode; + MsnUserEndpoint data; + const char *id; + char *caps, *tmp; + gboolean is_me; + + purple_debug_info("msn", "Get EndpointData\n"); + + session = user->userlist->session; + is_me = (user == session->user); + + msn_user_clear_endpoints(user); + for (epNode = xmlnode_get_child(payloadNode, "EndpointData"); + epNode; + epNode = xmlnode_get_next_twin(epNode)) { + id = xmlnode_get_attrib(epNode, "id"); + capsNode = xmlnode_get_child(epNode, "Capabilities"); + + /* Disconnect others, if MPOP is disabled */ + if (is_me + && !session->enable_mpop + && strncasecmp(id + 1, session->guid, 36) != 0) { + purple_debug_info("msn", "Disconnecting Endpoint %s\n", id); + + tmp = g_strdup_printf("%s;%s", user->passport, id); + msn_notification_send_uun(session, tmp, MSN_UNIFIED_NOTIFICATION_MPOP, "goawyplzthxbye"); + g_free(tmp); + } else { + if (capsNode != NULL) { + caps = xmlnode_get_data(capsNode); + + data.clientid = strtoul(caps, &tmp, 10); + if (tmp && *tmp) + data.extcaps = strtoul(tmp + 1, NULL, 10); + else + data.extcaps = 0; + + g_free(caps); + } else { + data.clientid = 0; + data.extcaps = 0; + } + + msn_user_set_endpoint_data(user, id, &data); + } + } + + if (is_me && session->enable_mpop) { + for (epNode = xmlnode_get_child(payloadNode, "PrivateEndpointData"); + epNode; + epNode = xmlnode_get_next_twin(epNode)) { + MsnUserEndpoint *ep; + xmlnode *nameNode, *clientNode; + + /* + Endpoint Name + true/false + 1 + NLN + + */ + id = xmlnode_get_attrib(epNode, "id"); + ep = msn_user_get_endpoint_data(user, id); + + if (ep != NULL) { + nameNode = xmlnode_get_child(epNode, "EpName"); + if (nameNode != NULL) { + g_free(ep->name); + ep->name = xmlnode_get_data(nameNode); + } + + clientNode = xmlnode_get_child(epNode, "ClientType"); + if (clientNode != NULL) { + tmp = xmlnode_get_data(clientNode); + ep->type = strtoul(tmp, NULL, 10); + g_free(tmp); + } + } + } + } +} + static void parse_currentmedia(MsnUser *user, const char *cmedia) { char **cmedia_array; int strings = 0; if (!cmedia || cmedia[0] == '\0') { purple_debug_info("msn", "No currentmedia string\n"); return; @@ -1566,28 +1689,27 @@ static void parse_currentmedia(MsnUser * * Post it to the User status * Thanks for Chris 's code */ static void ubx_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, size_t len) { MsnSession *session; - PurpleAccount *account; MsnUser *user; const char *passport; - char *str; + xmlnode *payloadNode; + char *psm_str, *str; session = cmdproc->session; - account = session->account; passport = cmd->params[0]; user = msn_userlist_find_user(session->userlist, passport); if (user == NULL) { - str = g_strndup(payload, len); + char *str = g_strndup(payload, len); purple_debug_info("msn", "unknown user %s, payload is %s\n", passport, str); g_free(str); return; } /* Free any existing media info for this user */ if (user->extinfo) { @@ -1596,23 +1718,38 @@ ubx_cmd_post(MsnCmdProc *cmdproc, MsnCom g_free(user->extinfo->media_title); user->extinfo->media_album = NULL; user->extinfo->media_artist = NULL; user->extinfo->media_title = NULL; user->extinfo->media_type = CURRENT_MEDIA_UNKNOWN; } if (len != 0) { - str = msn_get_psm(cmd->payload,len); - msn_user_set_statusline(user, str); - g_free(str); - - str = msn_get_currentmedia(cmd->payload, len); + payloadNode = xmlnode_from_str(payload, len); + if (!payloadNode) { + purple_debug_error("msn", "UBX XML parse Error!\n"); + + msn_user_set_statusline(user, NULL); + + msn_user_update(user); + return; + } + + psm_str = msn_get_psm(payloadNode); + msn_user_set_statusline(user, psm_str); + g_free(psm_str); + + str = msn_get_currentmedia(payloadNode); parse_currentmedia(user, str); g_free(str); + + parse_user_endpoints(user, payloadNode); + + xmlnode_free(payloadNode); + } else { msn_user_set_statusline(user, NULL); } msn_user_update(user); } static void @@ -1635,81 +1772,229 @@ uux_cmd_post(MsnCmdProc *cmdproc, MsnCom static void uux_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { purple_debug_misc("msn", "UUX received.\n"); cmdproc->last_cmd->payload_cb = uux_cmd_post; cmd->payload_len = atoi(cmd->params[1]); } +void +msn_notification_send_uux(MsnSession *session, const char *payload) +{ + MsnTransaction *trans; + MsnCmdProc *cmdproc; + size_t len = strlen(payload); + + cmdproc = session->notification->cmdproc; + purple_debug_misc("msn", "Sending UUX command with payload: %s\n", payload); + trans = msn_transaction_new(cmdproc, "UUX", "%" G_GSIZE_FORMAT, len); + msn_transaction_set_payload(trans, payload, len); + msn_cmdproc_send_trans(cmdproc, trans); +} + +void msn_notification_send_uux_endpointdata(MsnSession *session) +{ + xmlnode *epDataNode; + xmlnode *capNode; + char *caps; + char *payload; + int length; + + epDataNode = xmlnode_new("EndpointData"); + + capNode = xmlnode_new_child(epDataNode, "Capabilities"); + if (session->protocol_ver >= 16) + caps = g_strdup_printf("%d:%02d", MSN_CLIENT_ID_CAPABILITIES, MSN_CLIENT_ID_EXT_CAPS); + else + caps = g_strdup_printf("%d", MSN_CLIENT_ID_CAPABILITIES); + xmlnode_insert_data(capNode, caps, -1); + g_free(caps); + + payload = xmlnode_to_str(epDataNode, &length); + + msn_notification_send_uux(session, payload); + + xmlnode_free(epDataNode); + g_free(payload); +} + +void msn_notification_send_uux_private_endpointdata(MsnSession *session) +{ + xmlnode *private; + const char *name; + xmlnode *epname; + xmlnode *idle; + GHashTable *ui_info; + const gchar *ui_type; + xmlnode *client_type; + xmlnode *state; + char *payload; + int length; + + private = xmlnode_new("PrivateEndpointData"); + + name = purple_account_get_string(session->account, "endpoint-name", NULL); + epname = xmlnode_new_child(private, "EpName"); + xmlnode_insert_data(epname, name, -1); + + idle = xmlnode_new_child(private, "Idle"); + xmlnode_insert_data(idle, "false", -1); + + /* ClientType info (from amsn guys): + 0: None + 1: Computer + 2: Website + 3: Mobile / none + 4: Xbox / phone /mobile + 9: MsnGroup + 32: Email member, currently Yahoo! + */ + client_type = xmlnode_new_child(private, "ClientType"); + ui_info = purple_core_get_ui_info(); + ui_type = ui_info ? g_hash_table_lookup(ui_info, "client_type") : NULL; + if (ui_type) { + if (strcmp(ui_type, "pc") == 0) + xmlnode_insert_data(client_type, "1", -1); + else if (strcmp(ui_type, "web") == 0) + xmlnode_insert_data(client_type, "2", -1); + else if (strcmp(ui_type, "phone") == 0) + xmlnode_insert_data(client_type, "3", -1); + else if (strcmp(ui_type, "handheld") == 0) + xmlnode_insert_data(client_type, "3", -1); + else + xmlnode_insert_data(client_type, "1", -1); + } + else + xmlnode_insert_data(client_type, "1", -1); + + state = xmlnode_new_child(private, "State"); + xmlnode_insert_data(state, msn_state_get_text(msn_state_from_account(session->account)), -1); + + payload = xmlnode_to_str(private, &length); + + msn_notification_send_uux(session, payload); + + xmlnode_free(private); + g_free(payload); +} + +static void +ubn_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, + size_t len) +{ + /* Do Nothing, right now. */ + if (payload != NULL) + purple_debug_info("msn", "UBN payload:\n%s\n", payload); +} + +static void +ubn_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + purple_debug_misc("msn", "UBN received from %s.\n", cmd->params[0]); + cmdproc->last_cmd->payload_cb = ubn_cmd_post; + cmd->payload_len = atoi(cmd->params[2]); +} + +static void +uun_cmd_post(MsnCmdProc *cmdproc, MsnCommand *cmd, char *payload, + size_t len) +{ + /* Do Nothing, right now. */ + if (payload != NULL) + purple_debug_info("msn", "UUN payload:\n%s\n", payload); +} + +static void +uun_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) +{ + if (strcmp(cmd->params[1], "OK") != 0) { + purple_debug_misc("msn", "UUN received.\n"); + cmdproc->last_cmd->payload_cb = uun_cmd_post; + cmd->payload_len = atoi(cmd->params[1]); + } + else + purple_debug_misc("msn", "UUN OK received.\n"); +} + +void +msn_notification_send_uun(MsnSession *session, const char *user, + MsnUnifiedNotificationType type, const char *payload) +{ + MsnTransaction *trans; + MsnCmdProc *cmdproc; + size_t len = strlen(payload); + + cmdproc = session->notification->cmdproc; + purple_debug_misc("msn", "Sending UUN command %d to %s with payload: %s\n", + type, user, payload); + trans = msn_transaction_new(cmdproc, "UUN", "%s %d %" G_GSIZE_FORMAT, + user, type, len); + msn_transaction_set_payload(trans, payload, len); + msn_cmdproc_send_trans(cmdproc, trans); +} + /************************************************************************** * Message Types **************************************************************************/ static void profile_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { MsnSession *session; const char *value; +#ifdef MSN_PARTIAL_LISTS const char *clLastChange; +#endif session = cmdproc->session; if (strcmp(msg->remote_user, "Hotmail")) /* This isn't an official message. */ return; - if ((value = msn_message_get_attr(msg, "kv")) != NULL) - { - g_free(session->passport_info.kv); - session->passport_info.kv = g_strdup(value); - } - - if ((value = msn_message_get_attr(msg, "sid")) != NULL) + if ((value = msn_message_get_header_value(msg, "sid")) != NULL) { g_free(session->passport_info.sid); session->passport_info.sid = g_strdup(value); } - if ((value = msn_message_get_attr(msg, "MSPAuth")) != NULL) + if ((value = msn_message_get_header_value(msg, "MSPAuth")) != NULL) { g_free(session->passport_info.mspauth); session->passport_info.mspauth = g_strdup(value); } - if ((value = msn_message_get_attr(msg, "ClientIP")) != NULL) + if ((value = msn_message_get_header_value(msg, "ClientIP")) != NULL) { g_free(session->passport_info.client_ip); session->passport_info.client_ip = g_strdup(value); } - if ((value = msn_message_get_attr(msg, "ClientPort")) != NULL) + if ((value = msn_message_get_header_value(msg, "ClientPort")) != NULL) { session->passport_info.client_port = ntohs(atoi(value)); } - if ((value = msn_message_get_attr(msg, "LoginTime")) != NULL) + if ((value = msn_message_get_header_value(msg, "LoginTime")) != NULL) session->passport_info.sl = atol(value); - if ((value = msn_message_get_attr(msg, "EmailEnabled")) != NULL) + if ((value = msn_message_get_header_value(msg, "EmailEnabled")) != NULL) session->passport_info.email_enabled = (gboolean)atol(value); +#ifdef MSN_PARTIAL_LISTS /*starting retrieve the contact list*/ clLastChange = purple_account_get_string(session->account, "CLLastChange", NULL); -#ifdef MSN_PARTIAL_LISTS /* msn_userlist_load defeats all attempts at trying to detect blist sync issues */ msn_userlist_load(session); msn_get_contact_list(session, MSN_PS_INITIAL, clLastChange); #else /* always get the full list? */ msn_get_contact_list(session, MSN_PS_INITIAL, NULL); #endif -#if 0 - msn_contact_connect(session); -#endif } static void initial_email_msg(MsnCmdProc *cmdproc, MsnMessage *msg) { MsnSession *session; PurpleConnection *gc; GHashTable *table; @@ -1893,17 +2178,17 @@ system_msg(MsnCmdProc *cmdproc, MsnMessa /* This isn't an official message. */ return; table = msn_message_get_hashtable_from_body(msg); if ((type_s = g_hash_table_lookup(table, "Type")) != NULL) { int type = atoi(type_s); - char buf[MSN_BUF_LEN]; + char buf[MSN_BUF_LEN] = ""; int minutes; switch (type) { case 1: minutes = atoi(g_hash_table_lookup(table, "Arg1")); g_snprintf(buf, sizeof(buf), dngettext(PACKAGE, "The MSN server will shut down for maintenance " @@ -2088,16 +2373,19 @@ msn_notification_init(void) msn_table_add_cmd(cbs_table, NULL, "NLN", nln_cmd); msn_table_add_cmd(cbs_table, NULL, "ILN", iln_cmd); msn_table_add_cmd(cbs_table, NULL, "OUT", out_cmd); msn_table_add_cmd(cbs_table, NULL, "RNG", rng_cmd); msn_table_add_cmd(cbs_table, NULL, "UBX", ubx_cmd); msn_table_add_cmd(cbs_table, NULL, "UUX", uux_cmd); + msn_table_add_cmd(cbs_table, NULL, "UBN", ubn_cmd); + msn_table_add_cmd(cbs_table, NULL, "UUN", uun_cmd); + msn_table_add_cmd(cbs_table, NULL, "URL", url_cmd); msn_table_add_cmd(cbs_table, "fallback", "XFR", xfr_cmd); msn_table_add_error(cbs_table, "ADL", adl_error); msn_table_add_error(cbs_table, "RML", rml_error); msn_table_add_error(cbs_table, "FQY", fqy_error); msn_table_add_error(cbs_table, "USR", usr_error); diff --git a/purple/libpurple/protocols/msn/notification.h b/purple/libpurple/protocols/msn/notification.h --- a/purple/libpurple/protocols/msn/notification.h +++ b/purple/libpurple/protocols/msn/notification.h @@ -44,16 +44,17 @@ typedef struct _MsnNotification MsnNotif #define MSNP10_PRODUCT_ID "PROD0038W!61ZTF9" #include "cmdproc.h" #include "msg.h" #include "session.h" #include "servconn.h" #include "state.h" #include "user.h" +#include "userlist.h" struct _MsnNotification { MsnSession *session; /** * This is a convenience pointer that always points to * servconn->cmdproc @@ -61,16 +62,25 @@ struct _MsnNotification MsnCmdProc *cmdproc; MsnServConn *servconn; gboolean in_use; }; typedef void (*MsnFqyCb)(MsnSession *session, const char *passport, MsnNetwork network, gpointer data); +/* Type used for msn_notification_send_uun */ +typedef enum { + MSN_UNIFIED_NOTIFICATION_SHARED_FOLDERS = 1, + MSN_UNIFIED_NOTIFICATION_UNKNOWN1 = 2, + MSN_UNIFIED_NOTIFICATION_P2P = 3, + MSN_UNIFIED_NOTIFICATION_MPOP = 4 + +} MsnUnifiedNotificationType; + void uum_send_msg(MsnSession *session, MsnMessage *msg); void msn_notification_end(void); void msn_notification_init(void); void msn_notification_add_buddy_to_list(MsnNotification *notification, MsnListId list_id, MsnUser *user); void msn_notification_rem_buddy_from_list(MsnNotification *notification, @@ -82,16 +92,27 @@ void msn_notification_send_fqy(MsnSessio MsnNotification *msn_notification_new(MsnSession *session); void msn_notification_destroy(MsnNotification *notification); gboolean msn_notification_connect(MsnNotification *notification, const char *host, int port); void msn_notification_disconnect(MsnNotification *notification); void msn_notification_dump_contact(MsnSession *session); +void msn_notification_send_uux(MsnSession *session, const char *payload); + +void msn_notification_send_uux_endpointdata(MsnSession *session); + +void msn_notification_send_uux_private_endpointdata(MsnSession *session); + +void msn_notification_send_uun(MsnSession *session, + const char *user, + MsnUnifiedNotificationType type, + const char *payload); + /** * Closes a notification. * * It's first closed, and then disconnected. * * @param notification The notification object to close. */ void msn_notification_close(MsnNotification *notification); diff --git a/purple/libpurple/protocols/msn/object.c b/purple/libpurple/protocols/msn/object.c --- a/purple/libpurple/protocols/msn/object.c +++ b/purple/libpurple/protocols/msn/object.c @@ -197,16 +197,18 @@ msn_object_destroy(MsnObject *obj) { g_return_if_fail(obj != NULL); g_free(obj->creator); g_free(obj->location); g_free(obj->friendly); g_free(obj->sha1d); g_free(obj->sha1c); + g_free(obj->url); + g_free(obj->url1); purple_imgstore_unref(obj->img); if (obj->local) local_objs = g_list_remove(local_objs, obj); g_free(obj); } @@ -395,17 +397,17 @@ msn_object_get_url(const MsnObject *obj) const char * msn_object_get_url1(const MsnObject *obj) { g_return_val_if_fail(obj != NULL, NULL); return obj->url1; } -static MsnObject * +MsnObject * msn_object_find_local(const char *sha1) { GList *l; g_return_val_if_fail(sha1 != NULL, NULL); for (l = local_objs; l != NULL; l = l->next) { diff --git a/purple/libpurple/protocols/msn/object.h b/purple/libpurple/protocols/msn/object.h --- a/purple/libpurple/protocols/msn/object.h +++ b/purple/libpurple/protocols/msn/object.h @@ -264,11 +264,13 @@ const char *msn_object_get_url(const Msn * Returns a MsnObject's url1 value. * * @param obj The object. * * @return The url1 value. */ const char *msn_object_get_url1(const MsnObject *obj); +MsnObject * msn_object_find_local(const char *sha1); + void msn_object_set_local(MsnObject *obj); #endif /* MSN_OBJECT_H */ diff --git a/purple/libpurple/protocols/msn/oim.c b/purple/libpurple/protocols/msn/oim.c --- a/purple/libpurple/protocols/msn/oim.c +++ b/purple/libpurple/protocols/msn/oim.c @@ -18,17 +18,20 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" + #include "soap.h" #include "oim.h" #include "msnutils.h" typedef struct _MsnOimSendReq { char *from_member; char *friendname; char *to_member; @@ -158,24 +161,20 @@ static gboolean msn_oim_request_helper(M static void msn_oim_request_cb(MsnSoapMessage *request, MsnSoapMessage *response, gpointer req_data) { MsnOimRequestData *data = (MsnOimRequestData *)req_data; xmlnode *fault = NULL; xmlnode *faultcode = NULL; - if (response == NULL) - return; + if (response != NULL) + fault = xmlnode_get_child(response->xml, "Body/Fault"); - fault = xmlnode_get_child(response->xml, "Body/Fault"); - if (fault) - faultcode = xmlnode_get_child(fault, "faultcode"); - - if (faultcode) { + if (fault && (faultcode = xmlnode_get_child(fault, "faultcode"))) { gchar *faultcode_str = xmlnode_get_data(faultcode); gboolean need_token_update = FALSE; if (faultcode_str) { if (g_str_equal(faultcode_str, "q0:BadContextToken") || g_str_equal(faultcode_str, "AuthenticationFailed")) need_token_update = TRUE; else if (g_str_equal(faultcode_str, "q0:AuthenticationFailed") && @@ -613,17 +612,17 @@ msn_oim_report_to_user(MsnOimRecvData *r time_t stamp; message = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(message, msg_str, strlen(msg_str), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); purple_debug_info("msn", "oim body:{%s}\n", message->body); - boundary = msn_message_get_attr(message, "boundary"); + boundary = msn_message_get_header_value(message, "boundary"); if (boundary != NULL) { char *bounds; char **part; bounds = g_strdup_printf("--%s" MSG_OIM_LINE_DEM, boundary); tokens = g_strsplit(message->body, bounds, 0); @@ -633,50 +632,50 @@ msn_oim_report_to_user(MsnOimRecvData *r const char *type; multipart = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(multipart, *part, strlen(*part), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); type = msn_message_get_content_type(multipart); if (type && !strcmp(type, "text/plain")) { decode_msg = (char *)purple_base64_decode(multipart->body, &body_len); - msn_message_destroy(multipart); + msn_message_unref(multipart); break; } - msn_message_destroy(multipart); + msn_message_unref(multipart); } g_strfreev(tokens); g_free(bounds); if (decode_msg == NULL) { purple_debug_error("msn", "Couldn't find text/plain OIM message.\n"); - msn_message_destroy(message); + msn_message_unref(message); return; } } else { decode_msg = (char *)purple_base64_decode(message->body, &body_len); } - from = msn_message_get_attr(message, "X-OIM-originatingSource"); + from = msn_message_get_header_value(message, "X-OIM-originatingSource"); /* Match number to user's mobile number, FROM is a phone number if the other side pages you using your phone number */ if (from && !strncmp(from, "tel:+", 5)) { MsnUser *user = msn_userlist_find_user_with_mobile_phone( rdata->oim->session->userlist, from + 4); if (user && user->passport) passport = g_strdup(user->passport); } if (passport == NULL) { char *start, *end; - from = msn_message_get_attr(message, "From"); + from = msn_message_get_header_value(message, "From"); tokens = g_strsplit(from, " ", 2); if (tokens[1] != NULL) from = (const char *)tokens[1]; start = strchr(from, '<'); if (start != NULL) { start++; @@ -685,32 +684,32 @@ msn_oim_report_to_user(MsnOimRecvData *r passport = g_strndup(start, end - start); } if (passport == NULL) passport = g_strdup(_("Unknown")); g_strfreev(tokens); } - date = msn_message_get_attr(message, "Date"); + date = msn_message_get_header_value(message, "Date"); stamp = msn_oim_parse_timestamp(date); purple_debug_info("msn", "oim Date:{%s},passport{%s}\n", date, passport); serv_got_im(rdata->oim->session->account->gc, passport, decode_msg, 0, stamp); /*Now get the oim message ID from the oim_list. * and append to read list to prepare for deleting the Offline Message when sign out */ msn_oim_post_delete_msg(rdata); g_free(passport); g_free(decode_msg); - msn_message_destroy(message); + msn_message_unref(message); } /* Parse the XML data, * prepare to report the OIM to user */ static void msn_oim_get_read_cb(MsnSoapMessage *request, MsnSoapMessage *response, gpointer data) diff --git a/purple/libpurple/protocols/msn/p2p.c b/purple/libpurple/protocols/msn/p2p.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/p2p.c @@ -0,0 +1,104 @@ +/** + * @file p2p.c MSN P2P functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" + +#include "p2p.h" +#include "msnutils.h" + +MsnP2PHeader * +msn_p2p_header_from_wire(const char *wire) +{ + MsnP2PHeader *header; + + header = g_new(MsnP2PHeader, 1); + + header->session_id = msn_pop32le(wire); + header->id = msn_pop32le(wire); + header->offset = msn_pop64le(wire); + header->total_size = msn_pop64le(wire); + header->length = msn_pop32le(wire); + header->flags = msn_pop32le(wire); + header->ack_id = msn_pop32le(wire); + header->ack_sub_id = msn_pop32le(wire); + header->ack_size = msn_pop64le(wire); + + return header; +} + +char * +msn_p2p_header_to_wire(MsnP2PHeader *header) +{ + char *wire; + char *tmp; + + tmp = wire = g_new(char, P2P_PACKET_HEADER_SIZE); + + msn_push32le(tmp, header->session_id); + msn_push32le(tmp, header->id); + msn_push64le(tmp, header->offset); + msn_push64le(tmp, header->total_size); + msn_push32le(tmp, header->length); + msn_push32le(tmp, header->flags); + msn_push32le(tmp, header->ack_id); + msn_push32le(tmp, header->ack_sub_id); + msn_push64le(tmp, header->ack_size); + + return wire; + +} + +MsnP2PFooter * +msn_p2p_footer_from_wire(const char *wire) +{ + MsnP2PFooter *footer; + + footer = g_new(MsnP2PFooter, 1); + + footer->value = msn_pop32be(wire); + + return footer; +} + +char * +msn_p2p_footer_to_wire(MsnP2PFooter *footer) +{ + char *wire; + char *tmp; + + tmp = wire = g_new(char, P2P_PACKET_FOOTER_SIZE); + + msn_push32be(tmp, footer->value); + + return wire; +} + +gboolean +msn_p2p_msg_is_data(const MsnP2PHeaderFlag flags) +{ + return (flags == P2P_MSN_OBJ_DATA || + flags == (P2P_WLM2009_COMP | P2P_MSN_OBJ_DATA) || + flags == P2P_FILE_DATA); +} + diff --git a/purple/libpurple/protocols/msn/p2p.h b/purple/libpurple/protocols/msn/p2p.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/p2p.h @@ -0,0 +1,109 @@ +/** + * @file p2p.h MSN P2P functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef MSN_P2P_H +#define MSN_P2P_H + +typedef struct { + guint32 session_id; + guint32 id; + /** + * In a MsnSlpMessage: + * For outgoing messages this is the number of bytes from buffer that + * have already been sent out. For incoming messages this is the + * number of bytes that have been written to buffer. + */ + guint64 offset; + guint64 total_size; + guint32 length; + guint32 flags; + guint32 ack_id; + guint32 ack_sub_id; + guint64 ack_size; +/* guint8 body[1]; */ +} MsnP2PHeader; +#define P2P_PACKET_HEADER_SIZE (6 * 4 + 3 * 8) + +/* Used for DCs to store nonces */ +#define P2P_HEADER_ACK_ID_OFFSET (2*4 + 2*8 + 2*4) + +typedef struct { + guint8 header_len; + guint8 opcode; + guint16 message_len; + guint32 base_id; +} MsnP2Pv2Header; + +typedef struct +{ + guint32 value; +} MsnP2PFooter; +#define P2P_PACKET_FOOTER_SIZE (1 * 4) + +typedef enum +{ + P2P_NO_FLAG = 0x0, /**< No flags specified */ + P2P_OUT_OF_ORDER = 0x1, /**< Chunk out-of-order */ + P2P_ACK = 0x2, /**< Acknowledgement */ + P2P_PENDING_INVITE = 0x4, /**< There is a pending invite */ + P2P_BINARY_ERROR = 0x8, /**< Error on the binary level */ + P2P_FILE = 0x10, /**< File */ + P2P_MSN_OBJ_DATA = 0x20, /**< MsnObject data */ + P2P_CLOSE = 0x40, /**< Close session */ + P2P_TLP_ERROR = 0x80, /**< Error at transport layer protocol */ + P2P_DC_HANDSHAKE = 0x100, /**< Direct Handshake */ + P2P_WLM2009_COMP = 0x1000000, /**< Compatibility with WLM 2009 */ + P2P_FILE_DATA = 0x1000030 /**< File transfer data */ +} MsnP2PHeaderFlag; +/* Info From: + * http://msnpiki.msnfanatic.com/index.php/MSNC:P2Pv1_Headers#Flags + * http://trac.kmess.org/changeset/ba04d0c825769d23370511031c47f6be75fe9b86 + * #7180 + */ + +typedef enum +{ + P2P_APPID_SESSION = 0x0, /**< Negotiating session */ + P2P_APPID_OBJ = 0x1, /**< MsnObject (Display or Emoticon) */ + P2P_APPID_FILE = 0x2, /**< File transfer */ + P2P_APPID_EMOTE = 0xB, /**< CustomEmoticon */ + P2P_APPID_DISPLAY = 0xC /**< Display Image */ +} MsnP2PAppId; + +MsnP2PHeader * +msn_p2p_header_from_wire(const char *wire); + +char * +msn_p2p_header_to_wire(MsnP2PHeader *header); + +MsnP2PFooter * +msn_p2p_footer_from_wire(const char *wire); + +char * +msn_p2p_footer_to_wire(MsnP2PFooter *footer); + +gboolean +msn_p2p_msg_is_data(const MsnP2PHeaderFlag flags); + +#endif /* MSN_P2P_H */ diff --git a/purple/libpurple/protocols/msn/sbconn.c b/purple/libpurple/protocols/msn/sbconn.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/sbconn.c @@ -0,0 +1,176 @@ +/** + * @file sbconn.c MSN Switchboard Connection + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" + +#include "msg.h" +#include "sbconn.h" + +void msn_sbconn_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part) +{ + MsnMessage *msg; + const char *passport; + char *data; + size_t size; + + msg = msn_message_new_msnslp(); + + passport = purple_normalize(slplink->session->account, slplink->remote_user); + msn_message_set_header(msg, "P2P-Dest", passport); + + msg->part = msn_slpmsgpart_ref(part); + data = msn_slpmsgpart_serialize(part, &size); + msn_message_set_bin_data(msg, data, size); + g_free(data); + + if (slplink->swboard == NULL) + { + slplink->swboard = msn_session_get_swboard(slplink->session, + slplink->remote_user, MSN_SB_FLAG_FT); + + g_return_if_fail(slplink->swboard != NULL); + + /* If swboard is destroyed we will be too */ + slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); + } + + msn_switchboard_send_msg(slplink->swboard, msg, TRUE); + msn_message_unref(msg); +} + +/** Called when a message times out. */ +static void +msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) +{ + MsnMessage *msg; + + msg = trans->data; + + msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT); +} + +static void +release_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + MsnCmdProc *cmdproc; + MsnTransaction *trans; + char *payload; + gsize payload_len; + char flag; + + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + cmdproc = swboard->cmdproc; + + payload = msn_message_gen_payload(msg, &payload_len); + + if (purple_debug_is_verbose()) { + purple_debug_info("msn", "SB length:{%" G_GSIZE_FORMAT "}\n", payload_len); + msn_message_show_readable(msg, "SB SEND", FALSE); + } + + flag = msn_message_get_flag(msg); + trans = msn_transaction_new(cmdproc, "MSG", "%c %" G_GSIZE_FORMAT, + flag, payload_len); + + /* Data for callbacks */ + msn_transaction_set_data(trans, msg); + + if (flag != 'U') { + if (msg->type == MSN_MSG_TEXT) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); + } + else if (msg->type == MSN_MSG_SLP) + { + msg->ack_ref = TRUE; + msn_message_ref(msg); + swboard->ack_list = g_list_append(swboard->ack_list, msg); + msn_transaction_set_timeout_cb(trans, msg_timeout); +#if 0 + if (msg->ack_cb != NULL) + { + msn_transaction_add_cb(trans, "ACK", msg_ack); + msn_transaction_add_cb(trans, "NAK", msg_nak); + } +#endif + } + } + + trans->payload = payload; + trans->payload_len = payload_len; + + msg->trans = trans; + + msn_cmdproc_send_trans(cmdproc, trans); +} + +static void +queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + purple_debug_info("msn", "Appending message to queue.\n"); + + g_queue_push_tail(swboard->msg_queue, msg); + + msn_message_ref(msg); +} + +void +msn_sbconn_process_queue(MsnSwitchBoard *swboard) +{ + MsnMessage *msg; + + g_return_if_fail(swboard != NULL); + + purple_debug_info("msn", "Processing queue\n"); + + while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) + { + purple_debug_info("msn", "Sending message\n"); + release_msg(swboard, msg); + msn_message_unref(msg); + } +} + +void +msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, + gboolean queue) +{ + g_return_if_fail(swboard != NULL); + g_return_if_fail(msg != NULL); + + purple_debug_info("msn", "switchboard send msg..\n"); + if (msn_switchboard_can_send(swboard)) + release_msg(swboard, msg); + else if (queue) + queue_msg(swboard, msg); +} diff --git a/purple/libpurple/protocols/msn/sbconn.h b/purple/libpurple/protocols/msn/sbconn.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/sbconn.h @@ -0,0 +1,41 @@ +/** + * @file sbconn.h MSN Switchboard Connection + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef MSN_SBCONN_H +#define MSN_SBCONN_H + +#include "msg.h" +#include "slplink.h" + +#define MSN_SBCONN_MAX_SIZE 1202 + +void msn_sbconn_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part); + +void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, + gboolean queue); + +void +msn_sbconn_process_queue(MsnSwitchBoard *swboard); + +#endif /* MSN_SBCONN_H */ diff --git a/purple/libpurple/protocols/msn/servconn.c b/purple/libpurple/protocols/msn/servconn.c --- a/purple/libpurple/protocols/msn/servconn.c +++ b/purple/libpurple/protocols/msn/servconn.c @@ -16,17 +16,19 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" +#include "internal.h" +#include "debug.h" + #include "servconn.h" #include "error.h" static void read_cb(gpointer data, gint source, PurpleInputCondition cond); static void servconn_timeout_renew(MsnServConn *servconn); /************************************************************************** * Main diff --git a/purple/libpurple/protocols/msn/servconn.h b/purple/libpurple/protocols/msn/servconn.h --- a/purple/libpurple/protocols/msn/servconn.h +++ b/purple/libpurple/protocols/msn/servconn.h @@ -41,16 +41,17 @@ typedef enum * Connection types. */ typedef enum { MSN_SERVCONN_NS, MSN_SERVCONN_SB } MsnServConnType; +#include "internal.h" #include "proxy.h" #include "cmdproc.h" #include "httpconn.h" #include "session.h" /** * A Connection. diff --git a/purple/libpurple/protocols/msn/session.c b/purple/libpurple/protocols/msn/session.c --- a/purple/libpurple/protocols/msn/session.c +++ b/purple/libpurple/protocols/msn/session.c @@ -16,41 +16,48 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" + +#include "error.h" +#include "msnutils.h" #include "session.h" #include "notification.h" #include "oim.h" -#include "dialog.h" - MsnSession * msn_session_new(PurpleAccount *account) { MsnSession *session; g_return_val_if_fail(account != NULL, NULL); session = g_new0(MsnSession, 1); session->account = account; session->notification = msn_notification_new(session); session->userlist = msn_userlist_new(session); session->user = msn_user_new(session->userlist, purple_account_get_username(account), NULL); + msn_userlist_add_user(session->userlist, msn_user_ref(session->user)); session->oim = msn_oim_new(session); - session->protocol_ver = WLM_PROT_VER; + session->protocol_ver = 0; + session->enable_mpop = TRUE; /* Default only */ + + session->guid = rand_guid(); return session; } void msn_session_destroy(MsnSession *session) { g_return_if_fail(session != NULL); @@ -67,45 +74,42 @@ msn_session_destroy(MsnSession *session) if (session->soap_cleanup_handle) purple_timeout_remove(session->soap_cleanup_handle); if (session->soap_table != NULL) g_hash_table_destroy(session->soap_table); while (session->slplinks != NULL) - msn_slplink_destroy(session->slplinks->data); + msn_slplink_unref(session->slplinks->data); while (session->switches != NULL) msn_switchboard_destroy(session->switches->data); - if (session->sync != NULL) - msn_sync_destroy(session->sync); - if (session->oim != NULL) msn_oim_destroy(session->oim); if (session->nexus != NULL) msn_nexus_destroy(session->nexus); if (session->user != NULL) - msn_user_destroy(session->user); + msn_user_unref(session->user); if (session->notification != NULL) msn_notification_destroy(session->notification); msn_userlist_destroy(session->userlist); g_free(session->psm); + g_free(session->guid); g_free(session->abch_cachekey); #if 0 g_free(session->blocked_text); #endif - g_free(session->passport_info.kv); g_free(session->passport_info.sid); g_free(session->passport_info.mspauth); g_free(session->passport_info.client_ip); g_free(session->passport_info.mail_url); g_free(session); } @@ -278,17 +282,17 @@ msn_login_timeout_cb(gpointer data) msn_session_finish_login(session); session->login_timeout = 0; return FALSE; } void msn_session_activate_login_timeout(MsnSession *session) { - if (!session->logged_in) { + if (!session->logged_in && session->connected) { session->login_timeout = purple_timeout_add_seconds(MSN_LOGIN_FQY_TIMEOUT, msn_login_timeout_cb, session); } } static void msn_session_sync_users(MsnSession *session) @@ -324,17 +328,17 @@ msn_session_sync_users(MsnSession *sessi /* We don't care if they're in a different group, as long as they're on the * list somewhere. If we check for the group, we cause pain, agony and * suffering for people who decide to re-arrange their buddy list elsewhere. */ if (!found) { if ((remote_user == NULL) || !(remote_user->list_op & MSN_LIST_FL_OP)) { /* The user is not on the server list */ - msn_show_sync_issue(session, buddy_name, group_name); + msn_error_sync_issue(session, buddy_name, group_name); } else { /* The user is not in that group on the server list */ to_remove = g_list_prepend(to_remove, buddy); } } } } @@ -478,11 +482,16 @@ msn_session_finish_login(MsnSession *ses session->logged_in = TRUE; purple_connection_set_state(gc, PURPLE_CONNECTED); /* Sync users */ msn_session_sync_users(session); } + if (session->protocol_ver >= 16) { + /* TODO: Send this when updating status instead? */ + msn_notification_send_uux_endpointdata(session); + msn_notification_send_uux_private_endpointdata(session); + } msn_change_status(session); } diff --git a/purple/libpurple/protocols/msn/session.h b/purple/libpurple/protocols/msn/session.h --- a/purple/libpurple/protocols/msn/session.h +++ b/purple/libpurple/protocols/msn/session.h @@ -56,77 +56,74 @@ typedef enum MSN_LOGIN_STEP_SYN, MSN_LOGIN_STEP_END } MsnLoginStep; #define MSN_LOGIN_STEPS MSN_LOGIN_STEP_END #define MSN_LOGIN_FQY_TIMEOUT 30 -#include "group.h" -#include "httpconn.h" +#define MSN_LOGIN_FQY_TIMEOUT 30 + #include "nexus.h" #include "notification.h" #include "oim.h" -#include "slpcall.h" -#include "sslconn.h" #include "switchboard.h" -#include "sync.h" #include "user.h" #include "userlist.h" struct _MsnSession { PurpleAccount *account; MsnUser *user; guint protocol_ver; MsnLoginStep login_step; /**< The current step in the login process. */ - gboolean connected; - gboolean logged_in; /**< A temporal flag to ignore local buddy list adds. */ + gboolean connected:1; + gboolean logged_in:1; /**< A temporal flag to ignore local buddy list adds. */ + gboolean destroying:1; /**< A flag that states if the session is being destroyed. */ + gboolean http_method:1; + gboolean enable_mpop:1; /**< Use Multiple Points of Presence? */ int adl_fqy; /**< A count of ADL/FQY so status is only changed once. */ guint login_timeout; /**< Timeout to force status change if ADL/FQY fail. */ - gboolean destroying; /**< A flag that states if the session is being destroyed. */ - gboolean http_method; MsnNotification *notification; MsnNexus *nexus; MsnOim *oim; - MsnSync *sync; MsnUserList *userlist; char *abch_cachekey; int servconns_count; /**< The count of server connections. */ GList *switches; /**< The list of all the switchboards. */ GList *slplinks; /**< The list of all the slplinks. */ /*psm info*/ char *psm; #if 0 char *blocked_text; #endif struct { - char *kv; char *sid; char *mspauth; unsigned long sl; char *client_ip; int client_port; char *mail_url; gulong mail_timestamp; gboolean email_enabled; } passport_info; GHashTable *soap_table; guint soap_cleanup_handle; + char *guid; GSList *url_datas; /**< PurpleUtilFetchUrlData to be cancelled on exit */ }; /** * Creates an MSN session. * * @param account The account. diff --git a/purple/libpurple/protocols/msn/slp.c b/purple/libpurple/protocols/msn/slp.c --- a/purple/libpurple/protocols/msn/slp.c +++ b/purple/libpurple/protocols/msn/slp.c @@ -16,202 +16,42 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" + #include "slp.h" #include "slpcall.h" #include "slpmsg.h" #include "msnutils.h" #include "object.h" #include "user.h" -#include "switchboard.h" +#include "sbconn.h" #include "directconn.h" - -#include "smiley.h" +#include "p2p.h" +#include "xfer.h" /* seconds to delay between sending buddy icon requests to the server. */ #define BUDDY_ICON_DELAY 20 -static void request_user_display(MsnUser *user); - typedef struct { MsnSession *session; const char *remote_user; const char *sha1; } MsnFetchUserDisplayData; /************************************************************************** - * Util - **************************************************************************/ - -static char * -get_token(const char *str, const char *start, const char *end) -{ - const char *c, *c2; - - if ((c = strstr(str, start)) == NULL) - return NULL; - - c += strlen(start); - - if (end != NULL) - { - if ((c2 = strstr(c, end)) == NULL) - return NULL; - - return g_strndup(c, c2 - c); - } - else - { - /* This has to be changed */ - return g_strdup(c); - } - -} - -/************************************************************************** - * Xfer - **************************************************************************/ - -static void -msn_xfer_init(PurpleXfer *xfer) -{ - MsnSlpCall *slpcall; - /* MsnSlpLink *slplink; */ - char *content; - - purple_debug_info("msn", "xfer_init\n"); - - slpcall = xfer->data; - - /* Send Ok */ - content = g_strdup_printf("SessionID: %lu\r\n\r\n", - slpcall->session_id); - - msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", - content); - - g_free(content); - msn_slplink_send_queued_slpmsgs(slpcall->slplink); -} - -void -msn_xfer_cancel(PurpleXfer *xfer) -{ - MsnSlpCall *slpcall; - char *content; - - g_return_if_fail(xfer != NULL); - g_return_if_fail(xfer->data != NULL); - - slpcall = xfer->data; - - if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) - { - if (slpcall->started) - { - msn_slpcall_close(slpcall); - } - else - { - content = g_strdup_printf("SessionID: %lu\r\n\r\n", - slpcall->session_id); - - msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", - content); - - g_free(content); - msn_slplink_send_queued_slpmsgs(slpcall->slplink); - - if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) - slpcall->wasted = TRUE; - else - msn_slpcall_destroy(slpcall); - } - } -} - -gssize -msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer) -{ - MsnSlpCall *slpcall; - - g_return_val_if_fail(xfer != NULL, -1); - g_return_val_if_fail(data != NULL, -1); - g_return_val_if_fail(len > 0, -1); - - g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1); - - slpcall = xfer->data; - /* Not sure I trust it'll be there */ - g_return_val_if_fail(slpcall != NULL, -1); - - g_return_val_if_fail(slpcall->xfer_msg != NULL, -1); - - slpcall->u.outgoing.len = len; - slpcall->u.outgoing.data = data; - msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg); - msn_message_unref(slpcall->xfer_msg->msg); - return MIN(1202, len); -} - -gssize -msn_xfer_read(guchar **data, PurpleXfer *xfer) -{ - MsnSlpCall *slpcall; - gsize len; - - g_return_val_if_fail(xfer != NULL, -1); - g_return_val_if_fail(data != NULL, -1); - - g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1); - - slpcall = xfer->data; - /* Not sure I trust it'll be there */ - g_return_val_if_fail(slpcall != NULL, -1); - - /* Just pass up the whole GByteArray. We'll make another. */ - *data = slpcall->u.incoming_data->data; - len = slpcall->u.incoming_data->len; - - g_byte_array_free(slpcall->u.incoming_data, FALSE); - slpcall->u.incoming_data = g_byte_array_new(); - - return len; -} - -void -msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session) -{ - if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) && - (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) && - (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL)) - { - purple_xfer_cancel_remote(slpcall->xfer); - } -} - -void -msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body, - gsize size) -{ - PurpleXfer *xfer = slpcall->xfer; - - purple_xfer_set_completed(xfer, TRUE); - purple_xfer_end(xfer); -} - -/************************************************************************** * SLP Control **************************************************************************/ void msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content) { MsnSlpLink *slplink; @@ -245,1052 +85,20 @@ msn_slp_send_decline(MsnSlpCall *slpcall branch, type, content); slpmsg->info = "SLP 603 Decline"; slpmsg->text_body = TRUE; msn_slplink_queue_slpmsg(slplink, slpmsg); } -/* XXX: this could be improved if we tracked custom smileys - * per-protocol, per-account, per-session or (ideally) per-conversation - */ -static PurpleStoredImage * -find_valid_emoticon(PurpleAccount *account, const char *path) -{ - GList *smileys; - - if (!purple_account_get_bool(account, "custom_smileys", TRUE)) - return NULL; - - smileys = purple_smileys_get_all(); - - for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { - PurpleSmiley *smiley; - PurpleStoredImage *img; - - smiley = smileys->data; - img = purple_smiley_get_stored_image(smiley); - - if (purple_strequal(path, purple_imgstore_get_filename(img))) { - g_list_free(smileys); - return img; - } - - purple_imgstore_unref(img); - } - - purple_debug_error("msn", "Received illegal request for file %s\n", path); - return NULL; -} - -static char * -parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype) -{ - char *nonce; - - *ntype = DC_NONCE_UNKNOWN; - - nonce = get_token(content, "Hashed-Nonce: {", "}\r\n"); - if (nonce) { - *ntype = DC_NONCE_SHA1; - } else { - guint32 n1, n6; - guint16 n2, n3, n4, n5; - nonce = get_token(content, "Nonce: {", "}\r\n"); - if (nonce - && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%04hx%08x", - &n1, &n2, &n3, &n4, &n5, &n6) == 6) { - *ntype = DC_NONCE_PLAIN; - g_free(nonce); - nonce = g_malloc(16); - *(guint32 *)(nonce + 0) = GUINT32_TO_LE(n1); - *(guint16 *)(nonce + 4) = GUINT16_TO_LE(n2); - *(guint16 *)(nonce + 6) = GUINT16_TO_LE(n3); - *(guint16 *)(nonce + 8) = GUINT16_TO_BE(n4); - *(guint16 *)(nonce + 10) = GUINT16_TO_BE(n5); - *(guint32 *)(nonce + 12) = GUINT32_TO_BE(n6); - } else { - /* Invalid nonce, so ignore request */ - g_free(nonce); - nonce = NULL; - } - } - - return nonce; -} - -static void -msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content) -{ - /* A direct connection negotiation response */ - char *bridge; - char *nonce; - char *listening; - MsnDirectConn *dc = slpcall->slplink->dc; - MsnDirectConnNonceType ntype; - - purple_debug_info("msn", "process_transresp\n"); - - /* Direct connections are disabled. */ - if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE)) - return; - - g_return_if_fail(dc != NULL); - g_return_if_fail(dc->state == DC_STATE_CLOSED); - - bridge = get_token(content, "Bridge: ", "\r\n"); - nonce = parse_dc_nonce(content, &ntype); - listening = get_token(content, "Listening: ", "\r\n"); - if (listening && bridge && !strcmp(bridge, "TCPv1")) { - /* Ok, the client supports direct TCP connection */ - - /* We always need this. */ - if (ntype == DC_NONCE_SHA1) { - strncpy(dc->remote_nonce, nonce, 36); - dc->remote_nonce[36] = '\0'; - } - - if (!g_ascii_strcasecmp(listening, "false")) { - if (dc->listen_data != NULL) { - /* - * We'll listen for incoming connections but - * the listening socket isn't ready yet so we cannot - * send the INVITE packet now. Put the slpcall into waiting mode - * and let the callback send the invite. - */ - slpcall->wait_for_socket = TRUE; - - } else if (dc->listenfd != -1) { - /* The listening socket is ready. Send the INVITE here. */ - msn_dc_send_invite(dc); - - } else { - /* We weren't able to create a listener either. Use SB. */ - msn_dc_fallback_to_sb(dc); - } - - } else { - /* - * We should connect to the client so parse - * IP/port from response. - */ - char *ip, *port_str; - int port = 0; - - if (ntype == DC_NONCE_PLAIN) { - /* Only needed for listening side. */ - memcpy(dc->nonce, nonce, 16); - } - - /* Cancel any listen attempts because we don't need them. */ - if (dc->listenfd_handle != 0) { - purple_input_remove(dc->listenfd_handle); - dc->listenfd_handle = 0; - } - if (dc->connect_timeout_handle != 0) { - purple_timeout_remove(dc->connect_timeout_handle); - dc->connect_timeout_handle = 0; - } - if (dc->listenfd != -1) { - purple_network_remove_port_mapping(dc->listenfd); - close(dc->listenfd); - dc->listenfd = -1; - } - if (dc->listen_data != NULL) { - purple_network_listen_cancel(dc->listen_data); - dc->listen_data = NULL; - } - - /* Save external IP/port for later use. We'll try local connection first. */ - dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n"); - port_str = get_token(content, "IPv4External-Port: ", "\r\n"); - if (port_str) { - dc->ext_port = atoi(port_str); - g_free(port_str); - } - - ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); - port_str = get_token(content, "IPv4Internal-Port: ", "\r\n"); - if (port_str) { - port = atoi(port_str); - g_free(port_str); - } - - if (ip && port) { - /* Try internal address first */ - dc->connect_data = purple_proxy_connect( - NULL, - slpcall->slplink->session->account, - ip, - port, - msn_dc_connected_to_peer_cb, - dc - ); - - if (dc->connect_data) { - /* Add connect timeout handle */ - dc->connect_timeout_handle = purple_timeout_add_seconds( - DC_OUTGOING_TIMEOUT, - msn_dc_outgoing_connection_timeout_cb, - dc - ); - } else { - /* - * Connection failed - * Try external IP/port (if specified) - */ - msn_dc_outgoing_connection_timeout_cb(dc); - } - - } else { - /* - * Omitted or invalid internal IP address / port - * Try external IP/port (if specified) - */ - msn_dc_outgoing_connection_timeout_cb(dc); - } - - g_free(ip); - } - - } else { - /* - * Invalid direct connect invitation or - * TCP connection is not supported - */ - } - - g_free(listening); - g_free(nonce); - g_free(bridge); - - return; -} - -static void -got_sessionreq(MsnSlpCall *slpcall, const char *branch, - const char *euf_guid, const char *context) -{ - gboolean accepted = FALSE; - - if (!strcmp(euf_guid, MSN_OBJ_GUID)) - { - /* Emoticon or UserDisplay */ - char *content; - gsize len; - MsnSlpLink *slplink; - MsnSlpMessage *slpmsg; - MsnObject *obj; - char *msnobj_data; - PurpleStoredImage *img = NULL; - int type; - - /* Send Ok */ - content = g_strdup_printf("SessionID: %lu\r\n\r\n", - slpcall->session_id); - - msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", - content); - - g_free(content); - - slplink = slpcall->slplink; - - msnobj_data = (char *)purple_base64_decode(context, &len); - obj = msn_object_new_from_string(msnobj_data); - type = msn_object_get_type(obj); - g_free(msnobj_data); - if (type == MSN_OBJECT_EMOTICON) { - img = find_valid_emoticon(slplink->session->account, obj->location); - } else if (type == MSN_OBJECT_USERTILE) { - img = msn_object_get_image(obj); - if (img) - purple_imgstore_ref(img); - } - msn_object_destroy(obj); - - if (img != NULL) { - /* DATA PREP */ - slpmsg = msn_slpmsg_new(slplink); - slpmsg->slpcall = slpcall; - slpmsg->session_id = slpcall->session_id; - msn_slpmsg_set_body(slpmsg, NULL, 4); - slpmsg->info = "SLP DATA PREP"; - msn_slplink_queue_slpmsg(slplink, slpmsg); - - /* DATA */ - slpmsg = msn_slpmsg_new(slplink); - slpmsg->slpcall = slpcall; - slpmsg->flags = 0x20; - slpmsg->info = "SLP DATA"; - msn_slpmsg_set_image(slpmsg, img); - msn_slplink_queue_slpmsg(slplink, slpmsg); - purple_imgstore_unref(img); - - accepted = TRUE; - - } else { - purple_debug_error("msn", "Wrong object.\n"); - } - } - - else if (!strcmp(euf_guid, MSN_FT_GUID)) - { - /* File Transfer */ - PurpleAccount *account; - PurpleXfer *xfer; - MsnFileContext *header; - gsize bin_len; - guint32 file_size; - char *file_name; - - account = slpcall->slplink->session->account; - - slpcall->end_cb = msn_xfer_end_cb; - slpcall->branch = g_strdup(branch); - - slpcall->pending = TRUE; - - xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, - slpcall->slplink->remote_user); - - header = (MsnFileContext *)purple_base64_decode(context, &bin_len); - if (bin_len >= sizeof(MsnFileContext) - 1 && - (header->version == 2 || - (header->version == 3 && header->length == sizeof(MsnFileContext) + 63))) { - file_size = GUINT64_FROM_LE(header->file_size); - - file_name = g_convert((const gchar *)&header->file_name, - MAX_FILE_NAME_LEN * 2, - "UTF-8", "UTF-16LE", - NULL, NULL, NULL); - - purple_xfer_set_filename(xfer, file_name ? file_name : ""); - g_free(file_name); - purple_xfer_set_size(xfer, file_size); - purple_xfer_set_init_fnc(xfer, msn_xfer_init); - purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); - purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); - purple_xfer_set_read_fnc(xfer, msn_xfer_read); - purple_xfer_set_write_fnc(xfer, msn_xfer_write); - - slpcall->u.incoming_data = g_byte_array_new(); - - slpcall->xfer = xfer; - purple_xfer_ref(slpcall->xfer); - - xfer->data = slpcall; - - if (header->type == 0 && bin_len >= sizeof(MsnFileContext)) { - purple_xfer_set_thumbnail(xfer, &header->preview, - bin_len - sizeof(MsnFileContext), - "image/png"); - } - - purple_xfer_request(xfer); - } - g_free(header); - - accepted = TRUE; - - } else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) { - purple_debug_info("msn", "Cam request.\n"); - if (slpcall && slpcall->slplink && - slpcall->slplink->session) { - PurpleConversation *conv; - gchar *from = slpcall->slplink->remote_user; - conv = purple_find_conversation_with_account( - PURPLE_CONV_TYPE_IM, from, - slpcall->slplink->session->account); - if (conv) { - char *buf; - buf = g_strdup_printf( - _("%s requests to view your " - "webcam, but this request is " - "not yet supported."), from); - purple_conversation_write(conv, NULL, buf, - PURPLE_MESSAGE_SYSTEM | - PURPLE_MESSAGE_NOTIFY, - time(NULL)); - g_free(buf); - } - } - - } else if (!strcmp(euf_guid, MSN_CAM_GUID)) { - purple_debug_info("msn", "Cam invite.\n"); - if (slpcall && slpcall->slplink && - slpcall->slplink->session) { - PurpleConversation *conv; - gchar *from = slpcall->slplink->remote_user; - conv = purple_find_conversation_with_account( - PURPLE_CONV_TYPE_IM, from, - slpcall->slplink->session->account); - if (conv) { - char *buf; - buf = g_strdup_printf( - _("%s invited you to view his/her webcam, but " - "this is not yet supported."), from); - purple_conversation_write(conv, NULL, buf, - PURPLE_MESSAGE_SYSTEM | - PURPLE_MESSAGE_NOTIFY, - time(NULL)); - g_free(buf); - } - } - - } else - purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid); - - if (!accepted) { - char *content = g_strdup_printf("SessionID: %lu\r\n\r\n", - slpcall->session_id); - msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); - g_free(content); - } -} - -void -send_bye(MsnSlpCall *slpcall, const char *type) -{ - MsnSlpLink *slplink; - PurpleAccount *account; - MsnSlpMessage *slpmsg; - char *header; - - slplink = slpcall->slplink; - - g_return_if_fail(slplink != NULL); - - account = slplink->session->account; - - header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0", - purple_account_get_username(account)); - - slpmsg = msn_slpmsg_sip_new(slpcall, 0, header, - "A0D624A6-6C0C-4283-A9E0-BC97B4B46D32", - type, - "\r\n"); - g_free(header); - - slpmsg->info = "SLP BYE"; - slpmsg->text_body = TRUE; - - msn_slplink_queue_slpmsg(slplink, slpmsg); -} - -static void -got_invite(MsnSlpCall *slpcall, - const char *branch, const char *type, const char *content) -{ - MsnSlpLink *slplink; - - slplink = slpcall->slplink; - - if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) - { - char *euf_guid, *context; - char *temp; - - euf_guid = get_token(content, "EUF-GUID: {", "}\r\n"); - - temp = get_token(content, "SessionID: ", "\r\n"); - if (temp != NULL) - slpcall->session_id = atoi(temp); - g_free(temp); - - temp = get_token(content, "AppID: ", "\r\n"); - if (temp != NULL) - slpcall->app_id = atoi(temp); - g_free(temp); - - context = get_token(content, "Context: ", "\r\n"); - - if (context != NULL) - got_sessionreq(slpcall, branch, euf_guid, context); - - g_free(context); - g_free(euf_guid); - } - else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) - { - /* A direct connection negotiation request */ - char *bridges; - char *nonce; - MsnDirectConnNonceType ntype; - - purple_debug_info("msn", "got_invite: transreqbody received\n"); - - /* Direct connections may be disabled. */ - if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) { - msn_slp_send_ok(slpcall, branch, - "application/x-msnmsgr-transrespbody", - "Bridge: TCPv1\r\n" - "Listening: false\r\n" - "Nonce: {00000000-0000-0000-0000-000000000000}\r\n" - "\r\n"); - msn_slpcall_session_init(slpcall); - - return; - } - - /* Don't do anything if we already have a direct connection */ - if (slplink->dc != NULL) - return; - - bridges = get_token(content, "Bridges: ", "\r\n"); - nonce = parse_dc_nonce(content, &ntype); - if (bridges && strstr(bridges, "TCPv1") != NULL) { - /* - * Ok, the client supports direct TCP connection - * Try to create a listening port - */ - MsnDirectConn *dc; - - dc = msn_dc_new(slpcall); - if (ntype == DC_NONCE_PLAIN) { - /* There is only one nonce for plain auth. */ - dc->nonce_type = ntype; - memcpy(dc->nonce, nonce, 16); - } else if (ntype == DC_NONCE_SHA1) { - /* Each side has a nonce in SHA1 auth. */ - dc->nonce_type = ntype; - strncpy(dc->remote_nonce, nonce, 36); - dc->remote_nonce[36] = '\0'; - } - - dc->listen_data = purple_network_listen_range( - 0, 0, - SOCK_STREAM, - msn_dc_listen_socket_created_cb, - dc - ); - - if (dc->listen_data == NULL) { - /* Listen socket creation failed */ - - purple_debug_info("msn", "got_invite: listening failed\n"); - - if (dc->nonce_type != DC_NONCE_PLAIN) - msn_slp_send_ok(slpcall, branch, - "application/x-msnmsgr-transrespbody", - "Bridge: TCPv1\r\n" - "Listening: false\r\n" - "Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n" - "\r\n"); - else - msn_slp_send_ok(slpcall, branch, - "application/x-msnmsgr-transrespbody", - "Bridge: TCPv1\r\n" - "Listening: false\r\n" - "Nonce: {00000000-0000-0000-0000-000000000000}\r\n" - "\r\n"); - - } else { - /* - * Listen socket created successfully. - * Don't send anything here because we don't know the parameters - * of the created socket yet. msn_dc_send_ok will be called from - * the callback function: dc_listen_socket_created_cb - */ - purple_debug_info("msn", "got_invite: listening socket created\n"); - - dc->send_connection_info_msg_cb = msn_dc_send_ok; - slpcall->wait_for_socket = TRUE; - } - - } else { - /* - * Invalid direct connect invitation or - * TCP connection is not supported. - */ - } - - g_free(nonce); - g_free(bridges); - } - else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) - { - /* A direct connection negotiation response */ - msn_slp_process_transresp(slpcall, content); - } -} - -static void -got_ok(MsnSlpCall *slpcall, - const char *type, const char *content) -{ - g_return_if_fail(slpcall != NULL); - g_return_if_fail(type != NULL); - - if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) - { - char *content; - char *header; - char *nonce = NULL; - MsnSession *session = slpcall->slplink->session; - MsnSlpMessage *msg; - MsnDirectConn *dc; - MsnUser *user; - - if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) { - /* Don't attempt a direct connection if disabled. */ - msn_slpcall_session_init(slpcall); - return; - } - - if (slpcall->slplink->dc != NULL) { - /* If we already have an established direct connection - * then just start the transfer. - */ - msn_slpcall_session_init(slpcall); - return; - } - - user = msn_userlist_find_user(session->userlist, - slpcall->slplink->remote_user); - if (!user || !(user->clientid & 0xF0000000)) { - /* Just start a normal SB transfer. */ - msn_slpcall_session_init(slpcall); - return; - } - - /* Try direct file transfer by sending a second INVITE */ - dc = msn_dc_new(slpcall); - slpcall->branch = rand_guid(); - - dc->listen_data = purple_network_listen_range( - 0, 0, - SOCK_STREAM, - msn_dc_listen_socket_created_cb, - dc - ); - - header = g_strdup_printf( - "INVITE MSNMSGR:%s MSNSLP/1.0", - slpcall->slplink->remote_user - ); - - if (dc->nonce_type == DC_NONCE_SHA1) - nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash); - - if (dc->listen_data == NULL) { - /* Listen socket creation failed */ - purple_debug_info("msn", "got_ok: listening failed\n"); - - content = g_strdup_printf( - "Bridges: TCPv1\r\n" - "NetID: %u\r\n" - "Conn-Type: IP-Restrict-NAT\r\n" - "UPnPNat: false\r\n" - "ICF: false\r\n" - "%s" - "\r\n", - - rand() % G_MAXUINT32, - nonce ? nonce : "" - ); - - } else { - /* Listen socket created successfully. */ - purple_debug_info("msn", "got_ok: listening socket created\n"); - - content = g_strdup_printf( - "Bridges: TCPv1\r\n" - "NetID: 0\r\n" - "Conn-Type: Direct-Connect\r\n" - "UPnPNat: false\r\n" - "ICF: false\r\n" - "%s" - "\r\n", - - nonce ? nonce : "" - ); - } - - msg = msn_slpmsg_sip_new( - slpcall, - 0, - header, - slpcall->branch, - "application/x-msnmsgr-transreqbody", - content - ); - msg->info = "DC INVITE"; - msg->text_body = TRUE; - g_free(nonce); - g_free(header); - g_free(content); - - msn_slplink_queue_slpmsg(slpcall->slplink, msg); - } - else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) - { - /* Do we get this? */ - purple_debug_info("msn", "OK with transreqbody\n"); - } - else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) - { - msn_slp_process_transresp(slpcall, content); - } -} - -static void -got_error(MsnSlpCall *slpcall, - const char *error, const char *type, const char *content) -{ - /* It's not valid. Kill this off. */ - purple_debug_error("msn", "Received non-OK result: %s\n", - error ? error : "Unknown"); - - if (type && (!strcmp(type, "application/x-msnmsgr-transreqbody") - || !strcmp(type, "application/x-msnmsgr-transrespbody"))) { - MsnDirectConn *dc = slpcall->slplink->dc; - if (dc) { - msn_dc_fallback_to_sb(dc); - return; - } - } - - slpcall->wasted = TRUE; -} - -MsnSlpCall * -msn_slp_sip_recv(MsnSlpLink *slplink, const char *body) -{ - MsnSlpCall *slpcall; - - if (body == NULL) - { - purple_debug_warning("msn", "received bogus message\n"); - return NULL; - } - - if (!strncmp(body, "INVITE", strlen("INVITE"))) - { - char *branch; - char *call_id; - char *content; - char *content_type; - - /* From: */ -#if 0 - slpcall->remote_user = get_token(body, "From: \r\n"); -#endif - - branch = get_token(body, ";branch={", "}"); - - call_id = get_token(body, "Call-ID: {", "}"); - -#if 0 - long content_len = -1; - - temp = get_token(body, "Content-Length: ", "\r\n"); - if (temp != NULL) - content_len = atoi(temp); - g_free(temp); -#endif - content_type = get_token(body, "Content-Type: ", "\r\n"); - - content = get_token(body, "\r\n\r\n", NULL); - - slpcall = NULL; - if (branch && call_id) - { - slpcall = msn_slplink_find_slp_call(slplink, call_id); - if (slpcall) - { - g_free(slpcall->branch); - slpcall->branch = g_strdup(branch); - got_invite(slpcall, branch, content_type, content); - } - else if (content_type && content) - { - slpcall = msn_slpcall_new(slplink); - slpcall->id = g_strdup(call_id); - got_invite(slpcall, branch, content_type, content); - } - } - - g_free(call_id); - g_free(branch); - g_free(content_type); - g_free(content); - } - else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 "))) - { - char *content; - char *content_type; - /* Make sure this is "OK" */ - const char *status = body + strlen("MSNSLP/1.0 "); - char *call_id; - - call_id = get_token(body, "Call-ID: {", "}"); - slpcall = msn_slplink_find_slp_call(slplink, call_id); - g_free(call_id); - - g_return_val_if_fail(slpcall != NULL, NULL); - - content_type = get_token(body, "Content-Type: ", "\r\n"); - - content = get_token(body, "\r\n\r\n", NULL); - - if (strncmp(status, "200 OK", 6)) - { - char *error = NULL; - const char *c; - - /* Eww */ - if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) || - (c = strchr(status, '\0'))) - { - size_t len = c - status; - error = g_strndup(status, len); - } - - got_error(slpcall, error, content_type, content); - g_free(error); - - } else { - /* Everything's just dandy */ - got_ok(slpcall, content_type, content); - } - - g_free(content_type); - g_free(content); - } - else if (!strncmp(body, "BYE", strlen("BYE"))) - { - char *call_id; - - call_id = get_token(body, "Call-ID: {", "}"); - slpcall = msn_slplink_find_slp_call(slplink, call_id); - g_free(call_id); - - if (slpcall != NULL) - slpcall->wasted = TRUE; - - /* msn_slpcall_destroy(slpcall); */ - } - else - slpcall = NULL; - - return slpcall; -} - /************************************************************************** * Msg Callbacks **************************************************************************/ -void -msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg) -{ - MsnSession *session; - MsnSlpLink *slplink; - const char *data; - gsize len; - - session = cmdproc->servconn->session; - slplink = msn_session_get_slplink(session, msg->remote_user); - - if (slplink->swboard == NULL) - { - /* - * We will need swboard in order to change its flags. If its - * NULL, something has probably gone wrong earlier on. I - * didn't want to do this, but MSN 7 is somehow causing us - * to crash here, I couldn't reproduce it to debug more, - * and people are reporting bugs. Hopefully this doesn't - * cause more crashes. Stu. - */ - if (cmdproc->data == NULL) - g_warning("msn_p2p_msg cmdproc->data was NULL\n"); - else { - slplink->swboard = (MsnSwitchBoard *)cmdproc->data; - slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); - } - } - - data = msn_message_get_bin_data(msg, &len); - - msn_slplink_process_msg(slplink, &msg->msnslp_header, data, len); -} - -static void -got_emoticon(MsnSlpCall *slpcall, - const guchar *data, gsize size) -{ - PurpleConversation *conv; - MsnSwitchBoard *swboard; - - swboard = slpcall->slplink->swboard; - conv = swboard->conv; - - if (conv) { - /* FIXME: it would be better if we wrote the data as we received it - instead of all at once, calling write multiple times and - close once at the very end - */ - purple_conv_custom_smiley_write(conv, slpcall->data_info, data, size); - purple_conv_custom_smiley_close(conv, slpcall->data_info ); - } - if (purple_debug_is_verbose()) - purple_debug_info("msn", "Got smiley: %s\n", slpcall->data_info); -} - -void -msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg) -{ - MsnSession *session; - MsnSlpLink *slplink; - MsnSwitchBoard *swboard; - MsnObject *obj; - char **tokens; - char *smile, *body_str; - const char *body, *who, *sha1; - guint tok; - size_t body_len; - - PurpleConversation *conv; - - session = cmdproc->servconn->session; - - if (!purple_account_get_bool(session->account, "custom_smileys", TRUE)) - return; - - swboard = cmdproc->data; - conv = swboard->conv; - - body = msn_message_get_bin_data(msg, &body_len); - if (!body || !body_len) - return; - body_str = g_strndup(body, body_len); - - /* MSN Messenger 7 may send more than one MSNObject in a single message... - * Maybe 10 tokens is a reasonable max value. */ - tokens = g_strsplit(body_str, "\t", 10); - - g_free(body_str); - - for (tok = 0; tok < 9; tok += 2) { - if (tokens[tok] == NULL || tokens[tok + 1] == NULL) { - break; - } - - smile = tokens[tok]; - obj = msn_object_new_from_string(purple_url_decode(tokens[tok + 1])); - - if (obj == NULL) - break; - - who = msn_object_get_creator(obj); - sha1 = msn_object_get_sha1(obj); - - slplink = msn_session_get_slplink(session, who); - if (slplink->swboard != swboard) { - if (slplink->swboard != NULL) - /* - * Apparently we're using a different switchboard now or - * something? I don't know if this is normal, but it - * definitely happens. So make sure the old switchboard - * doesn't still have a reference to us. - */ - slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink); - slplink->swboard = swboard; - slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); - } - - /* If the conversation doesn't exist then this is a custom smiley - * used in the first message in a MSN conversation: we need to create - * the conversation now, otherwise the custom smiley won't be shown. - * This happens because every GtkIMHtml has its own smiley tree: if - * the conversation doesn't exist then we cannot associate the new - * smiley with its GtkIMHtml widget. */ - if (!conv) { - conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, who); - } - - if (purple_conv_custom_smiley_add(conv, smile, "sha1", sha1, TRUE)) { - msn_slplink_request_object(slplink, smile, got_emoticon, NULL, obj); - } - - msn_object_destroy(obj); - obj = NULL; - who = NULL; - sha1 = NULL; - } - g_strfreev(tokens); -} - -static gboolean -buddy_icon_cached(PurpleConnection *gc, MsnObject *obj) -{ - PurpleAccount *account; - PurpleBuddy *buddy; - const char *old; - const char *new; - - g_return_val_if_fail(obj != NULL, FALSE); - - account = purple_connection_get_account(gc); - - buddy = purple_find_buddy(account, msn_object_get_creator(obj)); - if (buddy == NULL) - return FALSE; - - old = purple_buddy_icons_get_checksum_for_user(buddy); - new = msn_object_get_sha1(obj); - - if (new == NULL) - return FALSE; - - /* If the old and new checksums are the same, and the file actually exists, - * then return TRUE */ - if (old != NULL && !strcmp(old, new)) - return TRUE; - - return FALSE; -} - -static void -msn_release_buddy_icon_request(MsnUserList *userlist) -{ - MsnUser *user; - - g_return_if_fail(userlist != NULL); - - if (purple_debug_is_verbose()) - purple_debug_info("msn", "Releasing buddy icon request\n"); - - if (userlist->buddy_icon_window > 0) - { - GQueue *queue; - PurpleAccount *account; - const char *username; - - queue = userlist->buddy_icon_requests; - - if (g_queue_is_empty(userlist->buddy_icon_requests)) - return; - - user = g_queue_pop_head(queue); - - account = userlist->session->account; - username = user->passport; - - userlist->buddy_icon_window--; - request_user_display(user); - - if (purple_debug_is_verbose()) - purple_debug_info("msn", - "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n", - userlist->buddy_icon_window); - } -} - /* * Called on a timeout from end_user_display(). Frees a buddy icon window slow and dequeues the next * buddy icon request if there is one. */ static gboolean msn_release_buddy_icon_request_timeout(gpointer data) { MsnUserList *userlist = (MsnUserList *)data; @@ -1301,82 +109,33 @@ msn_release_buddy_icon_request_timeout(g /* Clear the tag for our former request timer */ userlist->buddy_icon_request_timer = 0; msn_release_buddy_icon_request(userlist); return FALSE; } -void -msn_queue_buddy_icon_request(MsnUser *user) -{ - PurpleAccount *account; - MsnObject *obj; - GQueue *queue; - - g_return_if_fail(user != NULL); - - account = user->userlist->session->account; - - obj = msn_user_get_object(user); - - if (obj == NULL) - { - purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL); - return; - } - - if (!buddy_icon_cached(account->gc, obj)) - { - MsnUserList *userlist; - - userlist = user->userlist; - queue = userlist->buddy_icon_requests; - - if (purple_debug_is_verbose()) - purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n", - user->passport, userlist->buddy_icon_window); - - g_queue_push_tail(queue, user); - - if (userlist->buddy_icon_window > 0) - msn_release_buddy_icon_request(userlist); - } -} - static void got_user_display(MsnSlpCall *slpcall, const guchar *data, gsize size) { - MsnUserList *userlist; const char *info; PurpleAccount *account; g_return_if_fail(slpcall != NULL); info = slpcall->data_info; if (purple_debug_is_verbose()) purple_debug_info("msn", "Got User Display: %s\n", slpcall->slplink->remote_user); - userlist = slpcall->slplink->session->userlist; account = slpcall->slplink->session->account; purple_buddy_icons_set_for_user(account, slpcall->slplink->remote_user, g_memdup(data, size), size, info); - -#if 0 - /* Free one window slot */ - userlist->buddy_icon_window++; - - purple_debug_info("msn", "got_user_display(): buddy_icon_window++ yields =%d\n", - userlist->buddy_icon_window); - - msn_release_buddy_icon_request(userlist); -#endif } static void end_user_display(MsnSlpCall *slpcall, MsnSession *session) { MsnUserList *userlist; g_return_if_fail(session != NULL); @@ -1426,17 +185,53 @@ fetched_user_display(PurpleUtilFetchUrlD } end_user_display(NULL, session); g_free(user_data); } static void -request_user_display(MsnUser *user) +request_own_user_display(MsnUser *user) +{ + PurpleAccount *account; + MsnSession *session; + MsnObject *my_obj = NULL; + gconstpointer data = NULL; + const char *info = NULL; + size_t len = 0; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "Requesting our own user display\n"); + + session = user->userlist->session; + account = session->account; + my_obj = msn_user_get_object(user); + + if (my_obj != NULL) { + PurpleStoredImage *img = msn_object_get_image(my_obj); + data = purple_imgstore_get_data(img); + len = purple_imgstore_get_size(img); + info = msn_object_get_sha1(my_obj); + } + + purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info); + + /* Free one window slot */ + session->userlist->buddy_icon_window++; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "msn_request_user_display(): buddy_icon_window++ yields =%d\n", + session->userlist->buddy_icon_window); + + msn_release_buddy_icon_request(session->userlist); +} + +void +msn_request_user_display(MsnUser *user) { PurpleAccount *account; MsnSession *session; MsnSlpLink *slplink; MsnObject *obj; const char *info; session = user->userlist->session; @@ -1462,37 +257,141 @@ request_user_display(MsnUser *user) fetched_user_display, data); session->url_datas = g_slist_prepend(session->url_datas, url_data); } else { msn_slplink_request_object(slplink, info, got_user_display, end_user_display, obj); } } else - { - MsnObject *my_obj = NULL; - gconstpointer data = NULL; - size_t len = 0; + request_own_user_display(user); +} - if (purple_debug_is_verbose()) - purple_debug_info("msn", "Requesting our own user display\n"); +static void +send_file_cb(MsnSlpCall *slpcall) +{ + MsnSlpMessage *slpmsg; + PurpleXfer *xfer; - my_obj = msn_user_get_object(session->user); + xfer = (PurpleXfer *)slpcall->xfer; + if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED) + return; - if (my_obj != NULL) - { - PurpleStoredImage *img = msn_object_get_image(my_obj); - data = purple_imgstore_get_data(img); - len = purple_imgstore_get_size(img); - } + purple_xfer_ref(xfer); + purple_xfer_start(xfer, -1, NULL, 0); + if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) { + purple_xfer_unref(xfer); + return; + } + purple_xfer_unref(xfer); - purple_buddy_icons_set_for_user(account, user->passport, g_memdup(data, len), len, info); + slpmsg = msn_slpmsg_file_new(slpcall, purple_xfer_get_size(xfer)); + msn_slpmsg_set_slplink(slpmsg, slpcall->slplink); - /* Free one window slot */ - session->userlist->buddy_icon_window++; + msn_slplink_send_slpmsg(slpcall->slplink, slpmsg); +} - if (purple_debug_is_verbose()) - purple_debug_info("msn", "request_user_display(): buddy_icon_window++ yields =%d\n", - session->userlist->buddy_icon_window); +static gchar * +gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path) +{ + gsize size = 0; + MsnFileContext header; + gchar *u8 = NULL; + gchar *ret; + gunichar2 *uni = NULL; + glong currentChar = 0; + glong len = 0; + const char *preview; + gsize preview_len; - msn_release_buddy_icon_request(session->userlist); + size = purple_xfer_get_size(xfer); + + purple_xfer_prepare_thumbnail(xfer, "png"); + + if (!file_name) { + gchar *basename = g_path_get_basename(file_path); + u8 = purple_utf8_try_convert(basename); + g_free(basename); + file_name = u8; } + + uni = g_utf8_to_utf16(file_name, -1, NULL, &len, NULL); + + if (u8) { + g_free(u8); + file_name = NULL; + u8 = NULL; + } + + preview = purple_xfer_get_thumbnail(xfer, &preview_len); + + header.length = MSN_FILE_CONTEXT_SIZE; + header.version = 2; /* V.3 contains additional unnecessary data */ + header.file_size = size; + if (preview) + header.type = 0; + else + header.type = 1; + + len = MIN(len, MAX_FILE_NAME_LEN); + for (currentChar = 0; currentChar < len; currentChar++) { + header.file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]); + } + memset(&header.file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2); + + memset(&header.unknown1, 0, sizeof(header.unknown1)); + header.unknown2 = 0xffffffff; + + /* Mind the cast, as in, don't free it after! */ + header.preview = (char *)preview; + header.preview_len = preview_len; + + u8 = msn_file_context_to_wire(&header); + ret = purple_base64_encode((const guchar *)u8, MSN_FILE_CONTEXT_SIZE + preview_len); + + g_free(uni); + g_free(u8); + + return ret; } + +void +msn_request_ft(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + MsnSlpLink *slplink; + char *context; + const char *fn; + const char *fp; + + fn = purple_xfer_get_filename(xfer); + fp = purple_xfer_get_local_filename(xfer); + + slplink = xfer->data; + + g_return_if_fail(slplink != NULL); + g_return_if_fail(fp != NULL); + + slpcall = msn_slpcall_new(slplink); + msn_slpcall_init(slpcall, MSN_SLPCALL_DC); + + slpcall->session_init_cb = send_file_cb; + slpcall->end_cb = msn_xfer_end_cb; + slpcall->cb = msn_xfer_completed_cb; + slpcall->xfer = xfer; + purple_xfer_ref(slpcall->xfer); + + slpcall->pending = TRUE; + + purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); + + xfer->data = slpcall; + + context = gen_context(xfer, fn, fp); + + msn_slpcall_invite(slpcall, MSN_FT_GUID, P2P_APPID_FILE, context); + msn_slplink_unref(slplink); + + g_free(context); +} + diff --git a/purple/libpurple/protocols/msn/slp.h b/purple/libpurple/protocols/msn/slp.h --- a/purple/libpurple/protocols/msn/slp.h +++ b/purple/libpurple/protocols/msn/slp.h @@ -20,57 +20,32 @@ * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_SLP_H #define MSN_SLP_H #include "internal.h" +#include "ft.h" -#include "ft.h" #include "session.h" #include "slpcall.h" +#include "slplink.h" +#include "user.h" -#define MAX_FILE_NAME_LEN 260 /* MAX_PATH in Windows */ - -/** - * The context data for a file transfer request - */ -#pragma pack(push,1) /* Couldn't they have made it the right size? */ -typedef struct -{ - guint32 length; /*< Length of header */ - guint32 version; /*< MSN version */ - guint64 file_size; /*< Size of file */ - guint32 type; /*< Transfer type */ - gunichar2 file_name[MAX_FILE_NAME_LEN]; /*< Self-explanatory */ - gchar unknown1[30]; /*< Used somehow for background sharing */ - guint32 unknown2; /*< Possibly for background sharing as well */ - gchar preview[1]; /*< File preview data, 96x96 PNG */ -} MsnFileContext; -#pragma pack(pop) - -MsnSlpCall * msn_slp_sip_recv(MsnSlpLink *slplink, - const char *body); void msn_slp_send_ok(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content); void msn_slp_send_decline(MsnSlpCall *slpcall, const char *branch, const char *type, const char *content); void send_bye(MsnSlpCall *slpcall, const char *type); -void msn_xfer_completed_cb(MsnSlpCall *slpcall, - const guchar *body, gsize size); -void msn_xfer_cancel(PurpleXfer *xfer); -gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer); -gssize msn_xfer_read(guchar **data, PurpleXfer *xfer); +void msn_request_user_display(MsnUser *user); -void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session); - -void msn_queue_buddy_icon_request(MsnUser *user); +void msn_request_ft(PurpleXfer *xfer); #endif /* MSN_SLP_H */ diff --git a/purple/libpurple/protocols/msn/slpcall.c b/purple/libpurple/protocols/msn/slpcall.c --- a/purple/libpurple/protocols/msn/slpcall.c +++ b/purple/libpurple/protocols/msn/slpcall.c @@ -16,21 +16,27 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" +#include "smiley.h" + #include "msnutils.h" #include "slpcall.h" #include "slp.h" +#include "p2p.h" +#include "xfer.h" /************************************************************************** * Main **************************************************************************/ static gboolean msn_slpcall_timeout(gpointer data) { @@ -135,17 +141,17 @@ msn_slpcall_session_init(MsnSlpCall *slp if (slpcall->session_init_cb) slpcall->session_init_cb(slpcall); slpcall->started = TRUE; } void msn_slpcall_invite(MsnSlpCall *slpcall, const char *euf_guid, - int app_id, const char *context) + MsnP2PAppId app_id, const char *context) { MsnSlpLink *slplink; MsnSlpMessage *slpmsg; char *header; char *content; g_return_if_fail(slpcall != NULL); g_return_if_fail(context != NULL); @@ -185,32 +191,889 @@ msn_slpcall_close(MsnSlpCall *slpcall) g_return_if_fail(slpcall != NULL); g_return_if_fail(slpcall->slplink != NULL); send_bye(slpcall, "application/x-msnmsgr-sessionclosebody"); msn_slplink_send_queued_slpmsgs(slpcall->slplink); msn_slpcall_destroy(slpcall); } +/***************************************************************************** + * Parse received SLP messages + ****************************************************************************/ + +/************************************************************************** + *** Util + **************************************************************************/ + +static char * +get_token(const char *str, const char *start, const char *end) +{ + const char *c, *c2; + + if ((c = strstr(str, start)) == NULL) + return NULL; + + c += strlen(start); + + if (end != NULL) + { + if ((c2 = strstr(c, end)) == NULL) + return NULL; + + return g_strndup(c, c2 - c); + } + else + { + /* This has to be changed */ + return g_strdup(c); + } + +} + +/* XXX: this could be improved if we tracked custom smileys + * per-protocol, per-account, per-session or (ideally) per-conversation + */ +static PurpleStoredImage * +find_valid_emoticon(PurpleAccount *account, const char *path) +{ + GList *smileys; + + if (!purple_account_get_bool(account, "custom_smileys", TRUE)) + return NULL; + + smileys = purple_smileys_get_all(); + + for (; smileys; smileys = g_list_delete_link(smileys, smileys)) { + PurpleSmiley *smiley; + PurpleStoredImage *img; + + smiley = smileys->data; + img = purple_smiley_get_stored_image(smiley); + + if (purple_strequal(path, purple_imgstore_get_filename(img))) { + g_list_free(smileys); + return img; + } + + purple_imgstore_unref(img); + } + + purple_debug_error("msn", "Received illegal request for file %s\n", path); + return NULL; +} + +static char * +parse_dc_nonce(const char *content, MsnDirectConnNonceType *ntype) +{ + char *nonce; + + *ntype = DC_NONCE_UNKNOWN; + + nonce = get_token(content, "Hashed-Nonce: {", "}\r\n"); + if (nonce) { + *ntype = DC_NONCE_SHA1; + } else { + guint32 n1, n6; + guint16 n2, n3, n4, n5; + nonce = get_token(content, "Nonce: {", "}\r\n"); + if (nonce + && sscanf(nonce, "%08x-%04hx-%04hx-%04hx-%04hx%08x", + &n1, &n2, &n3, &n4, &n5, &n6) == 6) { + *ntype = DC_NONCE_PLAIN; + g_free(nonce); + nonce = g_malloc(16); + *(guint32 *)(nonce + 0) = GUINT32_TO_LE(n1); + *(guint16 *)(nonce + 4) = GUINT16_TO_LE(n2); + *(guint16 *)(nonce + 6) = GUINT16_TO_LE(n3); + *(guint16 *)(nonce + 8) = GUINT16_TO_BE(n4); + *(guint16 *)(nonce + 10) = GUINT16_TO_BE(n5); + *(guint32 *)(nonce + 12) = GUINT32_TO_BE(n6); + } else { + /* Invalid nonce, so ignore request */ + g_free(nonce); + nonce = NULL; + } + } + + return nonce; +} + +static void +msn_slp_process_transresp(MsnSlpCall *slpcall, const char *content) +{ + /* A direct connection negotiation response */ + char *bridge; + char *nonce; + char *listening; + MsnDirectConn *dc = slpcall->slplink->dc; + MsnDirectConnNonceType ntype; + + purple_debug_info("msn", "process_transresp\n"); + + /* Direct connections are disabled. */ + if (!purple_account_get_bool(slpcall->slplink->session->account, "direct_connect", TRUE)) + return; + + g_return_if_fail(dc != NULL); + g_return_if_fail(dc->state == DC_STATE_CLOSED); + + bridge = get_token(content, "Bridge: ", "\r\n"); + nonce = parse_dc_nonce(content, &ntype); + listening = get_token(content, "Listening: ", "\r\n"); + if (listening && bridge && !strcmp(bridge, "TCPv1")) { + /* Ok, the client supports direct TCP connection */ + + /* We always need this. */ + if (ntype == DC_NONCE_SHA1) { + strncpy(dc->remote_nonce, nonce, 36); + dc->remote_nonce[36] = '\0'; + } + + if (!g_ascii_strcasecmp(listening, "false")) { + if (dc->listen_data != NULL) { + /* + * We'll listen for incoming connections but + * the listening socket isn't ready yet so we cannot + * send the INVITE packet now. Put the slpcall into waiting mode + * and let the callback send the invite. + */ + slpcall->wait_for_socket = TRUE; + + } else if (dc->listenfd != -1) { + /* The listening socket is ready. Send the INVITE here. */ + msn_dc_send_invite(dc); + + } else { + /* We weren't able to create a listener either. Use SB. */ + msn_dc_fallback_to_sb(dc); + } + + } else { + /* + * We should connect to the client so parse + * IP/port from response. + */ + char *ip, *port_str; + int port = 0; + + if (ntype == DC_NONCE_PLAIN) { + /* Only needed for listening side. */ + memcpy(dc->nonce, nonce, 16); + } + + /* Cancel any listen attempts because we don't need them. */ + if (dc->listenfd_handle != 0) { + purple_input_remove(dc->listenfd_handle); + dc->listenfd_handle = 0; + } + if (dc->connect_timeout_handle != 0) { + purple_timeout_remove(dc->connect_timeout_handle); + dc->connect_timeout_handle = 0; + } + if (dc->listenfd != -1) { + purple_network_remove_port_mapping(dc->listenfd); + close(dc->listenfd); + dc->listenfd = -1; + } + if (dc->listen_data != NULL) { + purple_network_listen_cancel(dc->listen_data); + dc->listen_data = NULL; + } + + /* Save external IP/port for later use. We'll try local connection first. */ + dc->ext_ip = get_token(content, "IPv4External-Addrs: ", "\r\n"); + port_str = get_token(content, "IPv4External-Port: ", "\r\n"); + if (port_str) { + dc->ext_port = atoi(port_str); + g_free(port_str); + } + + ip = get_token(content, "IPv4Internal-Addrs: ", "\r\n"); + port_str = get_token(content, "IPv4Internal-Port: ", "\r\n"); + if (port_str) { + port = atoi(port_str); + g_free(port_str); + } + + if (ip && port) { + /* Try internal address first */ + dc->connect_data = purple_proxy_connect( + NULL, + slpcall->slplink->session->account, + ip, + port, + msn_dc_connected_to_peer_cb, + dc + ); + + if (dc->connect_data) { + /* Add connect timeout handle */ + dc->connect_timeout_handle = purple_timeout_add_seconds( + DC_OUTGOING_TIMEOUT, + msn_dc_outgoing_connection_timeout_cb, + dc + ); + } else { + /* + * Connection failed + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + } + + } else { + /* + * Omitted or invalid internal IP address / port + * Try external IP/port (if specified) + */ + msn_dc_outgoing_connection_timeout_cb(dc); + } + + g_free(ip); + } + + } else { + /* + * Invalid direct connect invitation or + * TCP connection is not supported + */ + } + + g_free(listening); + g_free(nonce); + g_free(bridge); + + return; +} + +static void +got_sessionreq(MsnSlpCall *slpcall, const char *branch, + const char *euf_guid, const char *context) +{ + gboolean accepted = FALSE; + + if (!strcmp(euf_guid, MSN_OBJ_GUID)) + { + /* Emoticon or UserDisplay */ + char *content; + gsize len; + MsnSlpLink *slplink; + MsnSlpMessage *slpmsg; + MsnObject *obj; + char *msnobj_data; + PurpleStoredImage *img = NULL; + int type; + + /* Send Ok */ + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + msn_slp_send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + + slplink = slpcall->slplink; + + msnobj_data = (char *)purple_base64_decode(context, &len); + obj = msn_object_new_from_string(msnobj_data); + type = msn_object_get_type(obj); + g_free(msnobj_data); + if (type == MSN_OBJECT_EMOTICON) { + img = find_valid_emoticon(slplink->session->account, obj->location); + } else if (type == MSN_OBJECT_USERTILE) { + img = msn_object_get_image(obj); + if (img) + purple_imgstore_ref(img); + } + msn_object_destroy(obj); + + if (img != NULL) { + /* DATA PREP */ + slpmsg = msn_slpmsg_dataprep_new(slpcall); + msn_slpmsg_set_slplink(slpmsg, slplink); + msn_slplink_queue_slpmsg(slplink, slpmsg); + + /* DATA */ + slpmsg = msn_slpmsg_obj_new(slpcall, img); + msn_slpmsg_set_slplink(slpmsg, slplink); + msn_slplink_queue_slpmsg(slplink, slpmsg); + purple_imgstore_unref(img); + + accepted = TRUE; + + } else { + purple_debug_error("msn", "Wrong object.\n"); + } + } + + else if (!strcmp(euf_guid, MSN_FT_GUID)) + { + /* File Transfer */ + PurpleAccount *account; + PurpleXfer *xfer; + MsnFileContext *header; + char *buf; + gsize bin_len; + guint32 file_size; + char *file_name; + + account = slpcall->slplink->session->account; + + slpcall->end_cb = msn_xfer_end_cb; + slpcall->branch = g_strdup(branch); + + slpcall->pending = TRUE; + + xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, + slpcall->slplink->remote_user); + + buf = (char *)purple_base64_decode(context, &bin_len); + header = msn_file_context_from_wire(buf, bin_len); + + if (header != NULL) { + file_size = header->file_size; + + file_name = g_convert((const gchar *)&header->file_name, + MAX_FILE_NAME_LEN * 2, + "UTF-8", "UTF-16LE", + NULL, NULL, NULL); + + purple_xfer_set_filename(xfer, file_name ? file_name : ""); + g_free(file_name); + purple_xfer_set_size(xfer, file_size); + purple_xfer_set_init_fnc(xfer, msn_xfer_init); + purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); + purple_xfer_set_read_fnc(xfer, msn_xfer_read); + purple_xfer_set_write_fnc(xfer, msn_xfer_write); + + slpcall->u.incoming_data = g_byte_array_new(); + + slpcall->xfer = xfer; + purple_xfer_ref(slpcall->xfer); + + xfer->data = slpcall; + + if (header->preview) { + purple_xfer_set_thumbnail(xfer, header->preview, + header->preview_len, + "image/png"); + g_free(header->preview); + } + + purple_xfer_request(xfer); + } + g_free(header); + g_free(buf); + + accepted = TRUE; + + } else if (!strcmp(euf_guid, MSN_CAM_REQUEST_GUID)) { + purple_debug_info("msn", "Cam request.\n"); + if (slpcall->slplink && slpcall->slplink->session) { + PurpleConversation *conv; + gchar *from = slpcall->slplink->remote_user; + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + slpcall->slplink->session->account); + if (conv) { + char *buf; + buf = g_strdup_printf( + _("%s requests to view your " + "webcam, but this request is " + "not yet supported."), from); + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } + } + + } else if (!strcmp(euf_guid, MSN_CAM_GUID)) { + purple_debug_info("msn", "Cam invite.\n"); + if (slpcall->slplink && slpcall->slplink->session) { + PurpleConversation *conv; + gchar *from = slpcall->slplink->remote_user; + conv = purple_find_conversation_with_account( + PURPLE_CONV_TYPE_IM, from, + slpcall->slplink->session->account); + if (conv) { + char *buf; + buf = g_strdup_printf( + _("%s invited you to view his/her webcam, but " + "this is not yet supported."), from); + purple_conversation_write(conv, NULL, buf, + PURPLE_MESSAGE_SYSTEM | + PURPLE_MESSAGE_NOTIFY, + time(NULL)); + g_free(buf); + } + } + + } else + purple_debug_warning("msn", "SLP SessionReq with unknown EUF-GUID: %s\n", euf_guid); + + if (!accepted) { + char *content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + msn_slp_send_decline(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); + g_free(content); + } +} + +void +send_bye(MsnSlpCall *slpcall, const char *type) +{ + MsnSlpLink *slplink; + PurpleAccount *account; + MsnSlpMessage *slpmsg; + char *header; + + slplink = slpcall->slplink; + + g_return_if_fail(slplink != NULL); + + account = slplink->session->account; + + header = g_strdup_printf("BYE MSNMSGR:%s MSNSLP/1.0", + purple_account_get_username(account)); + + slpmsg = msn_slpmsg_sip_new(slpcall, 0, header, + "A0D624A6-6C0C-4283-A9E0-BC97B4B46D32", + type, + "\r\n"); + g_free(header); + + slpmsg->info = "SLP BYE"; + slpmsg->text_body = TRUE; + + msn_slplink_queue_slpmsg(slplink, slpmsg); +} + +static void +got_invite(MsnSlpCall *slpcall, + const char *branch, const char *type, const char *content) +{ + MsnSlpLink *slplink; + + slplink = slpcall->slplink; + + if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) + { + char *euf_guid, *context; + char *temp; + + euf_guid = get_token(content, "EUF-GUID: {", "}\r\n"); + + temp = get_token(content, "SessionID: ", "\r\n"); + if (temp != NULL) + slpcall->session_id = atoi(temp); + g_free(temp); + + temp = get_token(content, "AppID: ", "\r\n"); + if (temp != NULL) + slpcall->app_id = atoi(temp); + g_free(temp); + + context = get_token(content, "Context: ", "\r\n"); + + if (context != NULL) + got_sessionreq(slpcall, branch, euf_guid, context); + + g_free(context); + g_free(euf_guid); + } + else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) + { + /* A direct connection negotiation request */ + char *bridges; + char *nonce; + MsnDirectConnNonceType ntype; + + purple_debug_info("msn", "got_invite: transreqbody received\n"); + + /* Direct connections may be disabled. */ + if (!purple_account_get_bool(slplink->session->account, "direct_connect", TRUE)) { + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"); + msn_slpcall_session_init(slpcall); + + return; + } + + /* Don't do anything if we already have a direct connection */ + if (slplink->dc != NULL) + return; + + bridges = get_token(content, "Bridges: ", "\r\n"); + nonce = parse_dc_nonce(content, &ntype); + if (bridges && strstr(bridges, "TCPv1") != NULL) { + /* + * Ok, the client supports direct TCP connection + * Try to create a listening port + */ + MsnDirectConn *dc; + + dc = msn_dc_new(slpcall); + if (ntype == DC_NONCE_PLAIN) { + /* There is only one nonce for plain auth. */ + dc->nonce_type = ntype; + memcpy(dc->nonce, nonce, 16); + } else if (ntype == DC_NONCE_SHA1) { + /* Each side has a nonce in SHA1 auth. */ + dc->nonce_type = ntype; + strncpy(dc->remote_nonce, nonce, 36); + dc->remote_nonce[36] = '\0'; + } + + dc->listen_data = purple_network_listen_range( + 0, 0, + SOCK_STREAM, + msn_dc_listen_socket_created_cb, + dc + ); + + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + + purple_debug_info("msn", "got_invite: listening failed\n"); + + if (dc->nonce_type != DC_NONCE_PLAIN) + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"); + else + msn_slp_send_ok(slpcall, branch, + "application/x-msnmsgr-transrespbody", + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"); + + } else { + /* + * Listen socket created successfully. + * Don't send anything here because we don't know the parameters + * of the created socket yet. msn_dc_send_ok will be called from + * the callback function: dc_listen_socket_created_cb + */ + purple_debug_info("msn", "got_invite: listening socket created\n"); + + dc->send_connection_info_msg_cb = msn_dc_send_ok; + slpcall->wait_for_socket = TRUE; + } + + } else { + /* + * Invalid direct connect invitation or + * TCP connection is not supported. + */ + } + + g_free(nonce); + g_free(bridges); + } + else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) + { + /* A direct connection negotiation response */ + msn_slp_process_transresp(slpcall, content); + } +} + +static void +got_ok(MsnSlpCall *slpcall, + const char *type, const char *content) +{ + g_return_if_fail(slpcall != NULL); + g_return_if_fail(type != NULL); + + if (!strcmp(type, "application/x-msnmsgr-sessionreqbody")) + { + char *content; + char *header; + char *nonce = NULL; + MsnSession *session = slpcall->slplink->session; + MsnSlpMessage *msg; + MsnDirectConn *dc; + MsnUser *user; + + if (!purple_account_get_bool(session->account, "direct_connect", TRUE)) { + /* Don't attempt a direct connection if disabled. */ + msn_slpcall_session_init(slpcall); + return; + } + + if (slpcall->slplink->dc != NULL) { + /* If we already have an established direct connection + * then just start the transfer. + */ + msn_slpcall_session_init(slpcall); + return; + } + + user = msn_userlist_find_user(session->userlist, + slpcall->slplink->remote_user); + if (!user || !(user->clientid & 0xF0000000)) { + /* Just start a normal SB transfer. */ + msn_slpcall_session_init(slpcall); + return; + } + + /* Try direct file transfer by sending a second INVITE */ + dc = msn_dc_new(slpcall); + g_free(slpcall->branch); + slpcall->branch = rand_guid(); + + dc->listen_data = purple_network_listen_range( + 0, 0, + SOCK_STREAM, + msn_dc_listen_socket_created_cb, + dc + ); + + header = g_strdup_printf( + "INVITE MSNMSGR:%s MSNSLP/1.0", + slpcall->slplink->remote_user + ); + + if (dc->nonce_type == DC_NONCE_SHA1) + nonce = g_strdup_printf("Hashed-Nonce: {%s}\r\n", dc->nonce_hash); + + if (dc->listen_data == NULL) { + /* Listen socket creation failed */ + purple_debug_info("msn", "got_ok: listening failed\n"); + + content = g_strdup_printf( + "Bridges: TCPv1\r\n" + "NetID: %u\r\n" + "Conn-Type: IP-Restrict-NAT\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n" + "%s" + "\r\n", + + rand() % G_MAXUINT32, + nonce ? nonce : "" + ); + + } else { + /* Listen socket created successfully. */ + purple_debug_info("msn", "got_ok: listening socket created\n"); + + content = g_strdup_printf( + "Bridges: TCPv1\r\n" + "NetID: 0\r\n" + "Conn-Type: Direct-Connect\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n" + "%s" + "\r\n", + + nonce ? nonce : "" + ); + } + + msg = msn_slpmsg_sip_new( + slpcall, + 0, + header, + slpcall->branch, + "application/x-msnmsgr-transreqbody", + content + ); + msg->info = "DC INVITE"; + msg->text_body = TRUE; + g_free(nonce); + g_free(header); + g_free(content); + + msn_slplink_queue_slpmsg(slpcall->slplink, msg); + } + else if (!strcmp(type, "application/x-msnmsgr-transreqbody")) + { + /* Do we get this? */ + purple_debug_info("msn", "OK with transreqbody\n"); + } + else if (!strcmp(type, "application/x-msnmsgr-transrespbody")) + { + msn_slp_process_transresp(slpcall, content); + } +} + +static void +got_error(MsnSlpCall *slpcall, + const char *error, const char *type, const char *content) +{ + /* It's not valid. Kill this off. */ + purple_debug_error("msn", "Received non-OK result: %s\n", + error ? error : "Unknown"); + + if (type && !strcmp(type, "application/x-msnmsgr-transreqbody")) { + MsnDirectConn *dc = slpcall->slplink->dc; + if (dc) { + msn_dc_fallback_to_sb(dc); + return; + } + } + + slpcall->wasted = TRUE; +} + +static MsnSlpCall * +msn_slp_sip_recv(MsnSlpLink *slplink, const char *body) +{ + MsnSlpCall *slpcall; + + if (body == NULL) + { + purple_debug_warning("msn", "received bogus message\n"); + return NULL; + } + + if (!strncmp(body, "INVITE", strlen("INVITE"))) + { + /* This is an INVITE request */ + char *branch; + char *call_id; + char *content; + char *content_type; + + /* From: */ +#if 0 + slpcall->remote_user = get_token(body, "From: \r\n"); +#endif + + branch = get_token(body, ";branch={", "}"); + + call_id = get_token(body, "Call-ID: {", "}"); + +#if 0 + long content_len = -1; + + temp = get_token(body, "Content-Length: ", "\r\n"); + if (temp != NULL) + content_len = atoi(temp); + g_free(temp); +#endif + content_type = get_token(body, "Content-Type: ", "\r\n"); + + content = get_token(body, "\r\n\r\n", NULL); + + slpcall = NULL; + if (branch && call_id) + { + slpcall = msn_slplink_find_slp_call(slplink, call_id); + if (slpcall) + { + g_free(slpcall->branch); + slpcall->branch = g_strdup(branch); + got_invite(slpcall, branch, content_type, content); + } + else if (content_type && content) + { + slpcall = msn_slpcall_new(slplink); + slpcall->id = g_strdup(call_id); + got_invite(slpcall, branch, content_type, content); + } + } + + g_free(call_id); + g_free(branch); + g_free(content_type); + g_free(content); + } + else if (!strncmp(body, "MSNSLP/1.0 ", strlen("MSNSLP/1.0 "))) + { + /* This is a response */ + char *content; + char *content_type; + /* Make sure this is "OK" */ + const char *status = body + strlen("MSNSLP/1.0 "); + char *call_id; + + call_id = get_token(body, "Call-ID: {", "}"); + slpcall = msn_slplink_find_slp_call(slplink, call_id); + g_free(call_id); + + g_return_val_if_fail(slpcall != NULL, NULL); + + content_type = get_token(body, "Content-Type: ", "\r\n"); + + content = get_token(body, "\r\n\r\n", NULL); + + if (strncmp(status, "200 OK", 6)) + { + char *error = NULL; + const char *c; + + /* Eww */ + if ((c = strchr(status, '\r')) || (c = strchr(status, '\n')) || + (c = strchr(status, '\0'))) + { + size_t len = c - status; + error = g_strndup(status, len); + } + + got_error(slpcall, error, content_type, content); + g_free(error); + + } else { + /* Everything's just dandy */ + got_ok(slpcall, content_type, content); + } + + g_free(content_type); + g_free(content); + } + else if (!strncmp(body, "BYE", strlen("BYE"))) + { + /* This is a BYE request */ + char *call_id; + + call_id = get_token(body, "Call-ID: {", "}"); + slpcall = msn_slplink_find_slp_call(slplink, call_id); + g_free(call_id); + + if (slpcall != NULL) + slpcall->wasted = TRUE; + + /* msn_slpcall_destroy(slpcall); */ + } + else + slpcall = NULL; + + return slpcall; +} + MsnSlpCall * msn_slp_process_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { MsnSlpCall *slpcall; const guchar *body; gsize body_len; slpcall = NULL; body = slpmsg->buffer; - body_len = slpmsg->offset; + body_len = slpmsg->header->offset; - if (slpmsg->flags == 0x0 || slpmsg->flags == 0x1000000) + if (slpmsg->header->flags == P2P_NO_FLAG || slpmsg->header->flags == P2P_WLM2009_COMP) { char *body_str; - if (slpmsg->session_id == 64) + if (slpmsg->header->session_id == 64) { /* This is for handwritten messages (Ink) */ GError *error = NULL; gsize bytes_read, bytes_written; body_str = g_convert((const gchar *)body, body_len / 2, "UTF-8", "UTF-16LE", &bytes_read, &bytes_written, &error); @@ -257,46 +1120,44 @@ msn_slp_process_msg(MsnSlpLink *slplink, } else { body_str = g_strndup((const char *)body, body_len); slpcall = msn_slp_sip_recv(slplink, body_str); } g_free(body_str); } - else if (slpmsg->flags == 0x20 || - slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) + else if (msn_p2p_msg_is_data(slpmsg->header->flags)) { - slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id); + slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->header->session_id); if (slpcall != NULL) { if (slpcall->timer) { purple_timeout_remove(slpcall->timer); slpcall->timer = 0; } if (slpcall->cb) slpcall->cb(slpcall, body, body_len); slpcall->wasted = TRUE; } } #if 0 - else if (slpmsg->flags == 0x100) + else if (slpmsg->header->flags == 0x100) { slpcall = slplink->directconn->initial_call; if (slpcall != NULL) msn_slpcall_session_init(slpcall); } #endif - else if (slpmsg->flags == 0x2) + else if (slpmsg->header->flags == P2P_ACK) { /* Acknowledgement of previous message. Don't do anything currently. */ } else - purple_debug_warning("msn", "Unprocessed SLP message with flags 0x%08lx\n", - slpmsg->flags); + purple_debug_warning("msn", "Unprocessed SLP message with flags 0x%04x\n", + slpmsg->header->flags); return slpcall; } diff --git a/purple/libpurple/protocols/msn/slpcall.h b/purple/libpurple/protocols/msn/slpcall.h --- a/purple/libpurple/protocols/msn/slpcall.h +++ b/purple/libpurple/protocols/msn/slpcall.h @@ -29,18 +29,16 @@ typedef struct _MsnSlpCall MsnSlpCall; typedef enum { MSN_SLPCALL_ANY, MSN_SLPCALL_DC } MsnSlpCallType; #include "internal.h" -#include "ft.h" - #include "slplink.h" /* The official client seems to timeout slp calls after 5 minutes */ #define MSN_SLPCALL_TIMEOUT 300 struct _MsnSlpCall { /* Our parent slplink */ @@ -89,12 +87,12 @@ struct _MsnSlpCall guint timer; }; MsnSlpCall *msn_slpcall_new(MsnSlpLink *slplink); void msn_slpcall_init(MsnSlpCall *slpcall, MsnSlpCallType type); void msn_slpcall_session_init(MsnSlpCall *slpcall); void msn_slpcall_destroy(MsnSlpCall *slpcall); void msn_slpcall_invite(MsnSlpCall *slpcall, const char *euf_guid, - int app_id, const char *context); + MsnP2PAppId app_id, const char *context); void msn_slpcall_close(MsnSlpCall *slpcall); #endif /* MSN_SLPCALL_H */ diff --git a/purple/libpurple/protocols/msn/slplink.c b/purple/libpurple/protocols/msn/slplink.c --- a/purple/libpurple/protocols/msn/slplink.c +++ b/purple/libpurple/protocols/msn/slplink.c @@ -16,40 +16,47 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" +#include "debug.h" + #include "msn.h" #include "slplink.h" +#include "slpmsg_part.h" +#include "sbconn.h" #include "switchboard.h" #include "slp.h" +#include "p2p.h" #ifdef MSN_DEBUG_SLP_FILES static int m_sc = 0; static int m_rc = 0; static void -debug_msg_to_file(MsnMessage *msg, gboolean send) +debug_part_to_file(MsnSlpMessage *msg, gboolean send) { char *tmp; char *dir; - char *pload; + char *data; int c; - gsize pload_size; + gsize data_size; dir = send ? "send" : "recv"; c = send ? m_sc++ : m_rc++; - tmp = g_strdup_printf("%s/msntest/%s/%03d", g_get_home_dir(), dir, c); - pload = msn_message_gen_payload(msg, &pload_size); - if (!purple_util_write_data_to_file_absolute(tmp, pload, pload_size)) + tmp = g_strdup_printf("%s/msntest/%s/%03d", purple_user_dir(), dir, c); + data = msn_slpmsg_serialize(msg, &data_size); + if (!purple_util_write_data_to_file_absolute(tmp, data, data_size)) { purple_debug_error("msn", "could not save debug file\n"); } g_free(tmp); } #endif /************************************************************************** @@ -76,36 +83,29 @@ msn_slplink_new(MsnSession *session, con slplink->slp_msg_queue = g_queue_new(); session->slplinks = g_list_append(session->slplinks, slplink); return msn_slplink_ref(slplink); } -void +static void msn_slplink_destroy(MsnSlpLink *slplink) { MsnSession *session; if (purple_debug_is_verbose()) purple_debug_info("msn", "slplink_destroy: slplink(%p)\n", slplink); - g_return_if_fail(slplink != NULL); - if (slplink->swboard != NULL) { slplink->swboard->slplinks = g_list_remove(slplink->swboard->slplinks, slplink); slplink->swboard = NULL; } - if (slplink->refs > 1) { - slplink->refs--; - return; - } - session = slplink->session; if (slplink->dc != NULL) { slplink->dc->slplink = NULL; msn_dc_destroy(slplink->dc); slplink->dc = NULL; } @@ -259,213 +259,119 @@ msn_slplink_find_slp_call_with_session_i if (slpcall->session_id == id) return slpcall; } return NULL; } -void -msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg) +static void +msn_slplink_send_part(MsnSlpLink *slplink, MsnSlpMessagePart *part) { if (slplink->dc != NULL && slplink->dc->state == DC_STATE_ESTABLISHED) { - msn_dc_enqueue_msg(slplink->dc, msg); + msn_dc_enqueue_part(slplink->dc, part); } else { - if (slplink->swboard == NULL) - { - slplink->swboard = msn_session_get_swboard(slplink->session, - slplink->remote_user, MSN_SB_FLAG_FT); - - g_return_if_fail(slplink->swboard != NULL); - - /* If swboard is destroyed we will be too */ - slplink->swboard->slplinks = g_list_prepend(slplink->swboard->slplinks, slplink); - } - - msn_switchboard_send_msg(slplink->swboard, msg, TRUE); + msn_sbconn_send_part(slplink, part); } } void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { - MsnMessage *msg; + MsnSlpMessagePart *part; long long real_size; size_t len = 0; /* Maybe we will want to create a new msg for this slpmsg instead of * reusing the same one all the time. */ - msg = slpmsg->msg; + part = msn_slpmsgpart_new(slpmsg->header, slpmsg->footer); + part->ack_data = slpmsg; - real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size; + real_size = (slpmsg->header->flags == P2P_ACK) ? 0 : slpmsg->size; - if (slpmsg->offset < real_size) + if (slpmsg->header->offset < real_size) { if (slpmsg->slpcall && slpmsg->slpcall->xfer && purple_xfer_get_type(slpmsg->slpcall->xfer) == PURPLE_XFER_SEND && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) { - len = MIN(1202, slpmsg->slpcall->u.outgoing.len); - msn_message_set_bin_data(msg, slpmsg->slpcall->u.outgoing.data, len); + len = MIN(MSN_SBCONN_MAX_SIZE, slpmsg->slpcall->u.outgoing.len); + msn_slpmsgpart_set_bin_data(part, slpmsg->slpcall->u.outgoing.data, len); } else { - len = slpmsg->size - slpmsg->offset; + len = slpmsg->size - slpmsg->header->offset; - if (len > 1202) - len = 1202; + if (len > MSN_SBCONN_MAX_SIZE) + len = MSN_SBCONN_MAX_SIZE; - msn_message_set_bin_data(msg, slpmsg->buffer + slpmsg->offset, len); + msn_slpmsgpart_set_bin_data(part, slpmsg->buffer + slpmsg->header->offset, len); } - msg->msnslp_header.offset = slpmsg->offset; - msg->msnslp_header.length = len; + slpmsg->header->length = len; } +#if 0 + /* TODO: port this function to SlpMessageParts */ if (purple_debug_is_verbose()) msn_message_show_readable(msg, slpmsg->info, slpmsg->text_body); +#endif #ifdef MSN_DEBUG_SLP_FILES - debug_msg_to_file(msg, TRUE); + debug_part_to_file(slpmsg, TRUE); #endif - slpmsg->msgs = - g_list_append(slpmsg->msgs, msn_message_ref(msg)); - msn_slplink_send_msg(slplink, msg); + slpmsg->parts = g_list_append(slpmsg->parts, part); + msn_slplink_send_part(slplink, part); - if ((slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) && + + if (msn_p2p_msg_is_data(slpmsg->header->flags) && (slpmsg->slpcall != NULL)) { slpmsg->slpcall->progress = TRUE; if (slpmsg->slpcall->progress_cb != NULL) { slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size, - len, slpmsg->offset); + len, slpmsg->header->offset); } } /* slpmsg->offset += len; */ } -/* We have received the message ack */ -static void -msg_ack(MsnMessage *msg, void *data) -{ - MsnSlpMessage *slpmsg; - long long real_size; - - slpmsg = data; - - real_size = (slpmsg->flags == 0x2) ? 0 : slpmsg->size; - - slpmsg->offset += msg->msnslp_header.length; - - slpmsg->msgs = g_list_remove(slpmsg->msgs, msg); - - if (slpmsg->offset < real_size) - { - if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) - { - slpmsg->slpcall->xfer_msg = slpmsg; - msn_message_ref(msg); - purple_xfer_prpl_ready(slpmsg->slpcall->xfer); - } - else - msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); - } - else - { - /* The whole message has been sent */ - if (slpmsg->flags == 0x20 || - slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) - { - if (slpmsg->slpcall != NULL) - { - if (slpmsg->slpcall->cb) - slpmsg->slpcall->cb(slpmsg->slpcall, - NULL, 0); - } - } - } - - msn_message_unref(msg); -} - -/* We have received the message nak. */ -static void -msg_nak(MsnMessage *msg, void *data) -{ - MsnSlpMessage *slpmsg; - - slpmsg = data; - - msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); - - slpmsg->msgs = g_list_remove(slpmsg->msgs, msg); - msn_message_unref(msg); -} - static void msn_slplink_release_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { - MsnMessage *msg; - const char *passport; + slpmsg = slpmsg; + slpmsg->footer = g_new0(MsnP2PFooter, 1); - slpmsg->msg = msg = msn_message_new_msnslp(); - - if (slpmsg->flags == 0x0) + if (slpmsg->header->flags == P2P_NO_FLAG) { - msg->msnslp_header.session_id = slpmsg->session_id; - msg->msnslp_header.ack_id = rand() % 0xFFFFFF00; + slpmsg->header->ack_id = rand() % 0xFFFFFF00; } - else if (slpmsg->flags == 0x2) - { - msg->msnslp_header.session_id = slpmsg->session_id; - msg->msnslp_header.ack_id = slpmsg->ack_id; - msg->msnslp_header.ack_size = slpmsg->ack_size; - msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id; - } - else if (slpmsg->flags == 0x20 || - slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) + else if (msn_p2p_msg_is_data(slpmsg->header->flags)) { MsnSlpCall *slpcall; slpcall = slpmsg->slpcall; g_return_if_fail(slpcall != NULL); - msg->msnslp_header.session_id = slpcall->session_id; - msg->msnslp_footer.value = slpcall->app_id; - msg->msnslp_header.ack_id = rand() % 0xFFFFFF00; - } - else if (slpmsg->flags == 0x100) - { - msg->msnslp_header.ack_id = slpmsg->ack_id; - msg->msnslp_header.ack_sub_id = slpmsg->ack_sub_id; - msg->msnslp_header.ack_size = slpmsg->ack_size; + slpmsg->header->session_id = slpcall->session_id; + slpmsg->footer->value = slpcall->app_id; + slpmsg->header->ack_id = rand() % 0xFFFFFF00; } - msg->msnslp_header.id = slpmsg->id; - msg->msnslp_header.flags = slpmsg->flags; + slpmsg->header->id = slpmsg->id; - msg->msnslp_header.total_size = slpmsg->size; - - passport = purple_normalize(slplink->session->account, slplink->remote_user); - msn_message_set_attr(msg, "P2P-Dest", passport); - - msg->ack_cb = msg_ack; - msg->nak_cb = msg_nak; - msg->ack_data = slpmsg; + slpmsg->header->total_size = slpmsg->size; msn_slplink_send_msgpart(slplink, slpmsg); - - msn_message_destroy(msg); } void msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg) { g_return_if_fail(slpmsg != NULL); slpmsg->id = slplink->slp_seq_id++; @@ -489,352 +395,238 @@ msn_slplink_send_queued_slpmsgs(MsnSlpLi /* Send the queued msgs in the order they were created */ while ((slpmsg = g_queue_pop_head(slplink->slp_msg_queue)) != NULL) { msn_slplink_release_slpmsg(slplink, slpmsg); } } static MsnSlpMessage * -msn_slplink_create_ack(MsnSlpLink *slplink, MsnSlpHeader *header) +msn_slplink_create_ack(MsnSlpLink *slplink, MsnP2PHeader *header) { MsnSlpMessage *slpmsg; - slpmsg = msn_slpmsg_new(slplink); - - slpmsg->session_id = header->session_id; - slpmsg->size = header->total_size; - slpmsg->flags = 0x02; - slpmsg->ack_id = header->id; - slpmsg->ack_sub_id = header->ack_id; - slpmsg->ack_size = header->total_size; - slpmsg->info = "SLP ACK"; + slpmsg = msn_slpmsg_ack_new(header); + msn_slpmsg_set_slplink(slpmsg, slplink); return slpmsg; } static void -msn_slplink_send_ack(MsnSlpLink *slplink, MsnSlpHeader *header) +msn_slplink_send_ack(MsnSlpLink *slplink, MsnP2PHeader *header) { MsnSlpMessage *slpmsg = msn_slplink_create_ack(slplink, header); msn_slplink_send_slpmsg(slplink, slpmsg); msn_slpmsg_destroy(slpmsg); } -static void -send_file_cb(MsnSlpCall *slpcall) -{ - MsnSlpMessage *slpmsg; - PurpleXfer *xfer; - - xfer = (PurpleXfer *)slpcall->xfer; - if (purple_xfer_get_status(xfer) >= PURPLE_XFER_STATUS_STARTED) - return; - - purple_xfer_ref(xfer); - purple_xfer_start(xfer, -1, NULL, 0); - if (purple_xfer_get_status(xfer) != PURPLE_XFER_STATUS_STARTED) { - purple_xfer_unref(xfer); - return; - } - purple_xfer_unref(xfer); - - slpmsg = msn_slpmsg_new(slpcall->slplink); - slpmsg->slpcall = slpcall; - slpmsg->flags = 0x1000030; - slpmsg->info = "SLP FILE"; - slpmsg->size = purple_xfer_get_size(xfer); - - msn_slplink_send_slpmsg(slpcall->slplink, slpmsg); -} - static MsnSlpMessage * msn_slplink_message_find(MsnSlpLink *slplink, long session_id, long id) { GList *e; for (e = slplink->slp_msgs; e != NULL; e = e->next) { MsnSlpMessage *slpmsg = e->data; - if ((slpmsg->session_id == session_id) && (slpmsg->id == id)) + if ((slpmsg->header->session_id == session_id) && (slpmsg->id == id)) return slpmsg; } return NULL; } -void -msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len) +static MsnSlpMessage * +init_first_msg(MsnSlpLink *slplink, MsnP2PHeader *header) { MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(slplink); + slpmsg->id = header->id; + slpmsg->header->session_id = header->session_id; + slpmsg->size = header->total_size; + slpmsg->header->flags = header->flags; + + if (slpmsg->header->session_id) + { + slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->header->session_id); + if (slpmsg->slpcall != NULL) + { + if (msn_p2p_msg_is_data(header->flags)) + { + PurpleXfer *xfer = slpmsg->slpcall->xfer; + if (xfer != NULL) + { + slpmsg->ft = TRUE; + slpmsg->slpcall->xfer_msg = slpmsg; + + purple_xfer_ref(xfer); + purple_xfer_start(xfer, -1, NULL, 0); + + if (xfer->data == NULL) { + purple_xfer_unref(xfer); + msn_slpmsg_destroy(slpmsg); + g_return_val_if_reached(NULL); + } else { + purple_xfer_unref(xfer); + } + } + } + } + } + if (!slpmsg->ft && slpmsg->size) + { + slpmsg->buffer = g_try_malloc(slpmsg->size); + if (slpmsg->buffer == NULL) + { + purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n"); + msn_slpmsg_destroy(slpmsg); + return NULL; + } + } + + return slpmsg; +} + +static void +process_complete_msg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg, MsnP2PHeader *header) +{ + MsnSlpCall *slpcall; + + slpcall = msn_slp_process_msg(slplink, slpmsg); + + if (slpcall == NULL) { + msn_slpmsg_destroy(slpmsg); + return; + } + + purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n"); + + if (/* !slpcall->wasted && */ slpmsg->header->flags == P2P_DC_HANDSHAKE) + { +#if 0 + MsnDirectConn *directconn; + + directconn = slplink->directconn; + if (!directconn->acked) + msn_directconn_send_handshake(directconn); +#endif + } + else if (slpmsg->header->flags == P2P_NO_FLAG || slpmsg->header->flags == P2P_WLM2009_COMP || + msn_p2p_msg_is_data(slpmsg->header->flags)) + { + /* Release all the messages and send the ACK */ + + if (slpcall->wait_for_socket) { + /* + * Save ack for later because we have to send + * a 200 OK message to the previous direct connect + * invitation before ACK but the listening socket isn't + * created yet. + */ + purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n"); + + slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header); + } else if (!slpcall->wasted) { + purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n"); + + msn_slplink_send_ack(slplink, header); + msn_slplink_send_queued_slpmsgs(slplink); + } + } + + msn_slpmsg_destroy(slpmsg); + + if (!slpcall->wait_for_socket && slpcall->wasted) + msn_slpcall_destroy(slpcall); +} + +static void +slpmsg_add_part(MsnSlpMessage *slpmsg, MsnSlpMessagePart *part) +{ + if (slpmsg->ft) { + slpmsg->slpcall->u.incoming_data = + g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)part->buffer, part->size); + purple_xfer_prpl_ready(slpmsg->slpcall->xfer); + } + else if (slpmsg->size && slpmsg->buffer) { + if (G_MAXSIZE - part->size < part->header->offset + || (part->header->offset + part->size) > slpmsg->size + || slpmsg->header->offset != part->header->offset) { + purple_debug_error("msn", + "Oversized slpmsg - msgsize=%lld offset=%" G_GUINT64_FORMAT " len=%" G_GSIZE_FORMAT "\n", + slpmsg->size, part->header->offset, part->size); + g_return_if_reached(); + } else { + memcpy(slpmsg->buffer + part->header->offset, part->buffer, part->size); + slpmsg->header->offset += part->size; + } + } +} + +void +msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpMessagePart *part) +{ + MsnSlpMessage *slpmsg; + MsnP2PHeader *header; guint64 offset; - PurpleXfer *xfer = NULL; + + header = part->header; if (header->total_size < header->length) { - purple_debug_error("msn", "This can't be good\n"); - g_return_if_reached(); + /* We seem to have received a bad header */ + purple_debug_warning("msn", "Total size listed in SLP binary header " + "was less than length of this particular message. This " + "should not happen. Dropping message.\n"); + return; } offset = header->offset; if (offset == 0) - { - slpmsg = msn_slpmsg_new(slplink); - slpmsg->id = header->id; - slpmsg->session_id = header->session_id; - slpmsg->size = header->total_size; - slpmsg->flags = header->flags; - - if (slpmsg->session_id) - { - if (slpmsg->slpcall == NULL) - slpmsg->slpcall = msn_slplink_find_slp_call_with_session_id(slplink, slpmsg->session_id); - - if (slpmsg->slpcall != NULL) - { - if (slpmsg->flags == 0x20 || - slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) - { - xfer = slpmsg->slpcall->xfer; - if (xfer != NULL) - { - slpmsg->ft = TRUE; - slpmsg->slpcall->xfer_msg = slpmsg; - - purple_xfer_ref(xfer); - purple_xfer_start(xfer, -1, NULL, 0); - - if (xfer->data == NULL) { - purple_xfer_unref(xfer); - msn_slpmsg_destroy(slpmsg); - g_return_if_reached(); - } else { - purple_xfer_unref(xfer); - } - } - } - } - } - if (!slpmsg->ft && slpmsg->size) - { - slpmsg->buffer = g_try_malloc(slpmsg->size); - if (slpmsg->buffer == NULL) - { - purple_debug_error("msn", "Failed to allocate buffer for slpmsg\n"); - msn_slpmsg_destroy(slpmsg); - return; - } - } - } - else - { + slpmsg = init_first_msg(slplink, header); + else { slpmsg = msn_slplink_message_find(slplink, header->session_id, header->id); if (slpmsg == NULL) { - /* Probably the transfer was canceled */ + /* Probably the transfer was cancelled */ purple_debug_error("msn", "Couldn't find slpmsg\n"); return; } } - if (slpmsg->ft) - { - xfer = slpmsg->slpcall->xfer; - slpmsg->slpcall->u.incoming_data = - g_byte_array_append(slpmsg->slpcall->u.incoming_data, (const guchar *)data, len); - purple_xfer_prpl_ready(xfer); - } - else if (slpmsg->size && slpmsg->buffer) - { - if (G_MAXSIZE - len < offset || (offset + len) > slpmsg->size || slpmsg->offset != offset) - { - purple_debug_error("msn", - "Oversized slpmsg - msgsize=%lld offset=%" G_GUINT64_FORMAT " len=%" G_GSIZE_FORMAT "\n", - slpmsg->size, offset, len); - g_return_if_reached(); - } else { - memcpy(slpmsg->buffer + offset, data, len); - slpmsg->offset += len; - } - } + slpmsg_add_part(slpmsg, part); - if ((slpmsg->flags == 0x20 || - slpmsg->flags == 0x1000020 || slpmsg->flags == 0x1000030) && + + if (msn_p2p_msg_is_data(slpmsg->header->flags) && (slpmsg->slpcall != NULL)) { slpmsg->slpcall->progress = TRUE; if (slpmsg->slpcall->progress_cb != NULL) { slpmsg->slpcall->progress_cb(slpmsg->slpcall, slpmsg->size, - len, offset); + part->size, offset); } } #if 0 if (slpmsg->buffer == NULL) return; #endif + /* All the pieces of the slpmsg have been received */ if (header->offset + header->length >= header->total_size) - { - /* All the pieces of the slpmsg have been received */ - MsnSlpCall *slpcall; + process_complete_msg(slplink, slpmsg, header); - slpcall = msn_slp_process_msg(slplink, slpmsg); - - if (slpcall == NULL) { - msn_slpmsg_destroy(slpmsg); - return; - } - - purple_debug_info("msn", "msn_slplink_process_msg: slpmsg complete\n"); - - if (/* !slpcall->wasted && */ slpmsg->flags == 0x100) - { -#if 0 - MsnDirectConn *directconn; - - directconn = slplink->directconn; - if (!directconn->acked) - msn_directconn_send_handshake(directconn); -#endif - } - else if (slpmsg->flags == 0x00 || slpmsg->flags == 0x1000000 || - slpmsg->flags == 0x20 || slpmsg->flags == 0x1000020 || - slpmsg->flags == 0x1000030) - { - /* Release all the messages and send the ACK */ - - if (slpcall->wait_for_socket) { - /* - * Save ack for later because we have to send - * a 200 OK message to the previous direct connect - * invitation before ACK but the listening socket isn't - * created yet. - */ - purple_debug_info("msn", "msn_slplink_process_msg: save ACK\n"); - - slpcall->slplink->dc->prev_ack = msn_slplink_create_ack(slplink, header); - } else if (!slpcall->wasted) { - purple_debug_info("msn", "msn_slplink_process_msg: send ACK\n"); - - msn_slplink_send_ack(slplink, header); - msn_slplink_send_queued_slpmsgs(slplink); - } - } - - msn_slpmsg_destroy(slpmsg); - - if (!slpcall->wait_for_socket && slpcall->wasted) - msn_slpcall_destroy(slpcall); - } -} - -static gchar * -gen_context(PurpleXfer *xfer, const char *file_name, const char *file_path) -{ - gsize size = 0; - MsnFileContext *header; - gchar *u8 = NULL; - gchar *ret; - gunichar2 *uni = NULL; - glong currentChar = 0; - glong len = 0; - const char *preview; - gsize preview_len; - - size = purple_xfer_get_size(xfer); - - purple_xfer_prepare_thumbnail(xfer, "png"); - - if (!file_name) { - gchar *basename = g_path_get_basename(file_path); - u8 = purple_utf8_try_convert(basename); - g_free(basename); - file_name = u8; - } - - uni = g_utf8_to_utf16(file_name, -1, NULL, &len, NULL); - - if (u8) { - g_free(u8); - file_name = NULL; - u8 = NULL; - } - - preview = purple_xfer_get_thumbnail(xfer, &preview_len); - header = g_malloc(sizeof(MsnFileContext) + preview_len); - - header->length = GUINT32_TO_LE(sizeof(MsnFileContext) - 1); - header->version = GUINT32_TO_LE(2); /* V.3 contains additional unnecessary data */ - header->file_size = GUINT64_TO_LE(size); - if (preview) - header->type = GUINT32_TO_LE(0); - else - header->type = GUINT32_TO_LE(1); - - len = MIN(len, MAX_FILE_NAME_LEN); - for (currentChar = 0; currentChar < len; currentChar++) { - header->file_name[currentChar] = GUINT16_TO_LE(uni[currentChar]); - } - memset(&header->file_name[currentChar], 0x00, (MAX_FILE_NAME_LEN - currentChar) * 2); - - memset(&header->unknown1, 0, sizeof(header->unknown1)); - header->unknown2 = GUINT32_TO_LE(0xffffffff); - if (preview) { - memcpy(&header->preview, preview, preview_len); - } - header->preview[preview_len] = '\0'; - - g_free(uni); - ret = purple_base64_encode((const guchar *)header, sizeof(MsnFileContext) + preview_len); - g_free(header); - return ret; -} - -void -msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer) -{ - MsnSlpCall *slpcall; - char *context; - const char *fn; - const char *fp; - - fn = purple_xfer_get_filename(xfer); - fp = purple_xfer_get_local_filename(xfer); - - g_return_if_fail(slplink != NULL); - g_return_if_fail(fp != NULL); - - slpcall = msn_slpcall_new(slplink); - msn_slpcall_init(slpcall, MSN_SLPCALL_DC); - - slpcall->session_init_cb = send_file_cb; - slpcall->end_cb = msn_xfer_end_cb; - slpcall->cb = msn_xfer_completed_cb; - slpcall->xfer = xfer; - purple_xfer_ref(slpcall->xfer); - - slpcall->pending = TRUE; - - purple_xfer_set_cancel_send_fnc(xfer, msn_xfer_cancel); - purple_xfer_set_read_fnc(xfer, msn_xfer_read); - purple_xfer_set_write_fnc(xfer, msn_xfer_write); - - xfer->data = slpcall; - - context = gen_context(xfer, fn, fp); - - msn_slpcall_invite(slpcall, MSN_FT_GUID, 2, context); - - g_free(context); + /* NOTE: The slpmsg will be destroyed in process_complete_msg or left in + the slplink until fully received. Don't free it here! + */ } void msn_slplink_request_object(MsnSlpLink *slplink, const char *info, MsnSlpCb cb, MsnSlpEndCb end_cb, const MsnObject *obj) @@ -852,12 +644,12 @@ msn_slplink_request_object(MsnSlpLink *s slpcall = msn_slpcall_new(slplink); msn_slpcall_init(slpcall, MSN_SLPCALL_ANY); slpcall->data_info = g_strdup(info); slpcall->cb = cb; slpcall->end_cb = end_cb; - msn_slpcall_invite(slpcall, MSN_OBJ_GUID, 1, msnobj_base64); + msn_slpcall_invite(slpcall, MSN_OBJ_GUID, P2P_APPID_OBJ, msnobj_base64); g_free(msnobj_base64); } diff --git a/purple/libpurple/protocols/msn/slplink.h b/purple/libpurple/protocols/msn/slplink.h --- a/purple/libpurple/protocols/msn/slplink.h +++ b/purple/libpurple/protocols/msn/slplink.h @@ -21,51 +21,47 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_SLPLINK_H #define MSN_SLPLINK_H typedef struct _MsnSlpLink MsnSlpLink; -#include "ft.h" - #include "directconn.h" #include "session.h" #include "slpcall.h" #include "slpmsg.h" #include "switchboard.h" typedef void (*MsnSlpCb)(MsnSlpCall *slpcall, const guchar *data, gsize size); typedef void (*MsnSlpEndCb)(MsnSlpCall *slpcall, MsnSession *session); struct _MsnSlpLink { MsnSession *session; MsnSwitchBoard *swboard; MsnDirectConn *dc; - int refs; + guint refs; char *remote_user; int slp_seq_id; GList *slp_calls; GList *slp_msgs; GQueue *slp_msg_queue; }; MsnSlpLink *msn_slplink_ref(MsnSlpLink *slplink); void msn_slplink_unref(MsnSlpLink *slplink); -void msn_slplink_destroy(MsnSlpLink *slplink); - /** * @return An MsnSlpLink for the given user, or NULL if there is no * existing MsnSlpLink. */ MsnSlpLink *msn_session_find_slplink(MsnSession *session, const char *who); /** @@ -78,20 +74,18 @@ void msn_slplink_add_slpcall(MsnSlpLink void msn_slplink_remove_slpcall(MsnSlpLink *slplink, MsnSlpCall *slpcall); MsnSlpCall *msn_slplink_find_slp_call(MsnSlpLink *slplink, const char *id); MsnSlpCall *msn_slplink_find_slp_call_with_session_id(MsnSlpLink *slplink, long id); void msn_slplink_queue_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); void msn_slplink_send_slpmsg(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); void msn_slplink_send_queued_slpmsgs(MsnSlpLink *slplink); -void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpHeader *header, const char *data, gsize len); -void msn_slplink_request_ft(MsnSlpLink *slplink, PurpleXfer *xfer); +void msn_slplink_process_msg(MsnSlpLink *slplink, MsnSlpMessagePart *part); -void msn_slplink_send_msg(MsnSlpLink *slplink, MsnMessage *msg); /* Only exported for msn_xfer_write */ void msn_slplink_send_msgpart(MsnSlpLink *slplink, MsnSlpMessage *slpmsg); void msn_slplink_request_object(MsnSlpLink *slplink, const char *info, MsnSlpCb cb, MsnSlpEndCb end_cb, const MsnObject *obj); diff --git a/purple/libpurple/protocols/msn/slpmsg.c b/purple/libpurple/protocols/msn/slpmsg.c --- a/purple/libpurple/protocols/msn/slpmsg.c +++ b/purple/libpurple/protocols/msn/slpmsg.c @@ -1,10 +1,10 @@ /** - * @file slpmsg.h SLP Message functions + * @file slpmsg.c SLP Message functions * * purple * * Purple is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * This program is free software; you can redistribute it and/or modify @@ -16,38 +16,45 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" + #include "slpmsg.h" +#include "slpmsg_part.h" #include "slplink.h" /************************************************************************** * SLP Message **************************************************************************/ MsnSlpMessage * msn_slpmsg_new(MsnSlpLink *slplink) { MsnSlpMessage *slpmsg; slpmsg = g_new0(MsnSlpMessage, 1); if (purple_debug_is_verbose()) purple_debug_info("msn", "slpmsg new (%p)\n", slpmsg); - slpmsg->slplink = slplink; + if (slplink) + msn_slpmsg_set_slplink(slpmsg, slplink); + else + slpmsg->slplink = NULL; - slplink->slp_msgs = - g_list_append(slplink->slp_msgs, slpmsg); + slpmsg->header = g_new0(MsnP2PHeader, 1); + slpmsg->footer = NULL; return slpmsg; } void msn_slpmsg_destroy(MsnSlpMessage *slpmsg) { MsnSlpLink *slplink; @@ -62,36 +69,51 @@ msn_slpmsg_destroy(MsnSlpMessage *slpmsg purple_imgstore_unref(slpmsg->img); /* We don't want to free the data of the PurpleStoredImage, * but to avoid code duplication, it's sharing buffer. */ if (slpmsg->img == NULL) g_free(slpmsg->buffer); - for (cur = slpmsg->msgs; cur != NULL; cur = g_list_delete_link(cur, cur)) + for (cur = slpmsg->parts; cur != NULL; cur = g_list_delete_link(cur, cur)) { /* Something is pointing to this slpmsg, so we should remove that * pointer to prevent a crash. */ /* Ex: a user goes offline and after that we receive an ACK */ - MsnMessage *msg = cur->data; + MsnSlpMessagePart *part = cur->data; - msg->ack_cb = NULL; - msg->nak_cb = NULL; - msg->ack_data = NULL; - msn_message_unref(msg); + part->ack_cb = NULL; + part->nak_cb = NULL; + part->ack_data = NULL; + msn_slpmsgpart_unref(part); } slplink->slp_msgs = g_list_remove(slplink->slp_msgs, slpmsg); + g_free(slpmsg->header); + g_free(slpmsg->footer); + g_free(slpmsg); } void +msn_slpmsg_set_slplink(MsnSlpMessage *slpmsg, MsnSlpLink *slplink) +{ + g_return_if_fail(slplink != NULL); + + slpmsg->slplink = slplink; + + slplink->slp_msgs = + g_list_append(slplink->slp_msgs, slpmsg); + +} + +void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body, long long size) { /* We can only have one data source at a time. */ g_return_if_fail(slpmsg->buffer == NULL); g_return_if_fail(slpmsg->img == NULL); g_return_if_fail(slpmsg->ft == FALSE); @@ -111,44 +133,16 @@ msn_slpmsg_set_image(MsnSlpMessage *slpm g_return_if_fail(slpmsg->img == NULL); g_return_if_fail(slpmsg->ft == FALSE); slpmsg->img = purple_imgstore_ref(img); slpmsg->buffer = (guchar *)purple_imgstore_get_data(img); slpmsg->size = purple_imgstore_get_size(img); } -void -msn_slpmsg_show(MsnMessage *msg) -{ - const char *info; - gboolean text; - guint32 flags; - - text = FALSE; - - flags = GUINT32_TO_LE(msg->msnslp_header.flags); - - switch (flags) - { - case 0x0: - info = "SLP CONTROL"; - text = TRUE; - break; - case 0x2: - info = "SLP ACK"; break; - case 0x20: - case 0x1000030: - info = "SLP DATA"; break; - default: - info = "SLP UNKNOWN"; break; - } - - msn_message_show_readable(msg, info, text); -} MsnSlpMessage * msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq, const char *header, const char *branch, const char *content_type, const char *content) { MsnSlpLink *slplink; PurpleAccount *account; @@ -201,8 +195,136 @@ msn_slpmsg_sip_new(MsnSlpCall *slpcall, slpmsg->sip = TRUE; slpmsg->slpcall = slpcall; g_free(body); return slpmsg; } + +MsnSlpMessage *msn_slpmsg_ack_new(MsnP2PHeader *header) +{ + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(NULL); + + slpmsg->header->session_id = header->session_id; + slpmsg->size = header->total_size; + slpmsg->header->flags = P2P_ACK; + slpmsg->header->ack_id = header->id; + slpmsg->header->ack_sub_id = header->ack_id; + slpmsg->header->ack_size = header->total_size; + slpmsg->info = "SLP ACK"; + + return slpmsg; +} + +MsnSlpMessage *msn_slpmsg_obj_new(MsnSlpCall *slpcall, PurpleStoredImage *img) +{ + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(NULL); + slpmsg->slpcall = slpcall; + slpmsg->header->flags = P2P_MSN_OBJ_DATA; + slpmsg->info = "SLP DATA"; + + msn_slpmsg_set_image(slpmsg, img); + + return slpmsg; +} + +MsnSlpMessage *msn_slpmsg_dataprep_new(MsnSlpCall *slpcall) +{ + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(NULL); + + slpmsg->slpcall = slpcall; + slpmsg->header->session_id = slpcall->session_id; + msn_slpmsg_set_body(slpmsg, NULL, 4); + slpmsg->info = "SLP DATA PREP"; + + return slpmsg; + +} + +MsnSlpMessage *msn_slpmsg_file_new(MsnSlpCall *slpcall, size_t size) +{ + MsnSlpMessage *slpmsg; + + slpmsg = msn_slpmsg_new(NULL); + + slpmsg->slpcall = slpcall; + slpmsg->header->flags = P2P_FILE_DATA; + slpmsg->info = "SLP FILE"; + slpmsg->size = size; + + return slpmsg; +} + +char *msn_slpmsg_serialize(MsnSlpMessage *slpmsg, size_t *ret_size) +{ + char *header; + char *footer; + char *base; + char *tmp; + size_t siz; + + base = g_malloc(P2P_PACKET_HEADER_SIZE + slpmsg->size + P2P_PACKET_FOOTER_SIZE); + tmp = base; + + header = msn_p2p_header_to_wire(slpmsg->header); + footer = msn_p2p_footer_to_wire(slpmsg->footer); + + siz = P2P_PACKET_HEADER_SIZE; + /* Copy header */ + memcpy(tmp, header, siz); + tmp += siz; + + /* Copy body */ + memcpy(tmp, slpmsg->buffer, slpmsg->size); + tmp += slpmsg->size; + + /* Copy footer */ + siz = P2P_PACKET_FOOTER_SIZE; + memcpy(tmp, footer, siz); + tmp += siz; + + *ret_size = tmp - base; + + g_free(header); + g_free(footer); + + return base; +} + +void msn_slpmsg_show_readable(MsnSlpMessage *slpmsg) +{ + GString *str; + + str = g_string_new(NULL); + + g_string_append_printf(str, "Session ID: %u\r\n", slpmsg->header->session_id); + g_string_append_printf(str, "ID: %u\r\n", slpmsg->header->id); + g_string_append_printf(str, "Offset: %" G_GUINT64_FORMAT "\r\n", slpmsg->header->offset); + g_string_append_printf(str, "Total size: %" G_GUINT64_FORMAT "\r\n", slpmsg->header->total_size); + g_string_append_printf(str, "Length: %u\r\n", slpmsg->header->length); + g_string_append_printf(str, "Flags: 0x%x\r\n", slpmsg->header->flags); + g_string_append_printf(str, "ACK ID: %u\r\n", slpmsg->header->ack_id); + g_string_append_printf(str, "SUB ID: %u\r\n", slpmsg->header->ack_sub_id); + g_string_append_printf(str, "ACK Size: %" G_GUINT64_FORMAT "\r\n", slpmsg->header->ack_size); + + if (purple_debug_is_verbose() && slpmsg->buffer != NULL) { + g_string_append_len(str, (gchar*)slpmsg->buffer, slpmsg->size); + + if (slpmsg->buffer[slpmsg->size - 1] == '\0') { + str->len--; + g_string_append(str, " 0x00"); + } + g_string_append(str, "\r\n"); + + } + + g_string_append_printf(str, "Footer: %u\r\n", slpmsg->footer->value); + + purple_debug_info("msn", "SlpMessage %s:\n{%s}\n", slpmsg->info, str->str); +} diff --git a/purple/libpurple/protocols/msn/slpmsg.h b/purple/libpurple/protocols/msn/slpmsg.h --- a/purple/libpurple/protocols/msn/slpmsg.h +++ b/purple/libpurple/protocols/msn/slpmsg.h @@ -26,87 +26,130 @@ typedef struct _MsnSlpMessage MsnSlpMessage; #include "imgstore.h" #include "slpcall.h" #include "slplink.h" #include "session.h" -#include "msg.h" +#include "p2p.h" #include "slp.h" /** * A SLP Message This contains everything that we will need to send a SLP * Message even if has to be sent in several parts. */ struct _MsnSlpMessage { MsnSlpCall *slpcall; /**< The slpcall to which this slp message belongs (if applicable). */ MsnSlpLink *slplink; /**< The slplink through which this slp message is being sent. */ MsnSession *session; - long session_id; + MsnP2PHeader *header; + MsnP2PFooter *footer; + long id; - long ack_id; - long ack_sub_id; - long long ack_size; gboolean sip; /**< A flag that states if this is a SIP slp message. */ - long flags; gboolean ft; PurpleStoredImage *img; guchar *buffer; /** - * For outgoing messages this is the number of bytes from buffer that - * have already been sent out. For incoming messages this is the - * number of bytes that have been written to buffer. - */ - long long offset; - - /** * This is the size of buffer, unless this is an outgoing file transfer, * in which case this is the size of the file. */ long long size; - GList *msgs; /**< The real messages. */ - -#if 1 - MsnMessage *msg; /**< The temporary real message that will be sent. */ -#endif + GList *parts; /**< A list with the SlpMsgParts */ const char *info; gboolean text_body; }; /** * Creates a new slp message * * @param slplink The slplink through which this slp message will be sent. + * If it's set to NULL, it is a temporary SlpMessage. * @return The created slp message. */ MsnSlpMessage *msn_slpmsg_new(MsnSlpLink *slplink); /** * Destroys a slp message * * @param slpmsg The slp message to destory. */ void msn_slpmsg_destroy(MsnSlpMessage *slpmsg); +/** + * Relate this SlpMessage with an existing SlpLink + * + * @param slplink The SlpLink that will send this message. + */ +void msn_slpmsg_set_slplink(MsnSlpMessage *slpmsg, MsnSlpLink *slplink); + void msn_slpmsg_set_body(MsnSlpMessage *slpmsg, const char *body, long long size); void msn_slpmsg_set_image(MsnSlpMessage *slpmsg, PurpleStoredImage *img); void msn_slpmsg_open_file(MsnSlpMessage *slpmsg, const char *file_name); MsnSlpMessage * msn_slpmsg_sip_new(MsnSlpCall *slpcall, int cseq, const char *header, const char *branch, const char *content_type, const char *content); -void msn_slpmsg_show(MsnMessage *msg); +/** + * Create a new SLP Ack message + * + * @param header the value of the header in this slpmsg. + * + * @return A new SlpMessage with ACK headers + */ +MsnSlpMessage *msn_slpmsg_ack_new(MsnP2PHeader *header); + +/** + * Create a new SLP message for MsnObject data. + * + * @param slpcall The slpcall that manages this message. + * @param img The image to be sent in this message. + * + * @return A new SlpMessage with MsnObject info. + */ +MsnSlpMessage *msn_slpmsg_obj_new(MsnSlpCall *slpcall, PurpleStoredImage *img); + +/** + * Create a new SLP message for data preparation. + * + * @param slpcall The slpcall that manages this message. + * + * @return A new SlpMessage with data preparation info. + */ +MsnSlpMessage *msn_slpmsg_dataprep_new(MsnSlpCall *slpcall); + +/** + * Create a new SLP message for File transfer. + * + * @param slpcall The slpcall that manages this message. + * @param size The size of the file being transsmited. + * + * @return A new SlpMessage with the file transfer info. + */ +MsnSlpMessage *msn_slpmsg_file_new(MsnSlpCall *slpcall, size_t size); + +/** + * Serialize the MsnSlpMessage in a way it can be used to be transmited + * + * @param slpmsg The MsnSlpMessage. + * @param ret_size The size of the buffer cointaining the message. + * + * @return a buffer with the serialized data. + */ +char *msn_slpmsg_serialize(MsnSlpMessage *slpmsg, size_t *ret_size); + +void msn_slpmsg_show_readable(MsnSlpMessage *slpmsg); #endif /* _MSN_SLPMSG_H_ */ diff --git a/purple/libpurple/protocols/msn/slpmsg_part.c b/purple/libpurple/protocols/msn/slpmsg_part.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/slpmsg_part.c @@ -0,0 +1,223 @@ +/** + * @file slpmsg_part.c MSNSLP Parts + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" + +#include "slpmsg.h" +#include "slpmsg_part.h" + +MsnSlpMessagePart *msn_slpmsgpart_new(MsnP2PHeader *header, MsnP2PFooter *footer) +{ + MsnSlpMessagePart *part; + + part = g_new0(MsnSlpMessagePart, 1); + + if (header) + part->header = g_memdup(header, P2P_PACKET_HEADER_SIZE); + if (footer) + part->footer = g_memdup(footer, P2P_PACKET_FOOTER_SIZE); + + part->ack_cb = msn_slpmsgpart_ack; + part->nak_cb = msn_slpmsgpart_nak; + + return msn_slpmsgpart_ref(part); +} + +MsnSlpMessagePart *msn_slpmsgpart_new_from_data(const char *data, size_t data_len) +{ + MsnSlpMessagePart *part; + int body_len; + + if (data_len < P2P_PACKET_HEADER_SIZE) { + return NULL; + } + + part = msn_slpmsgpart_new(NULL, NULL); + + /* Extract the binary SLP header */ + part->header = msn_p2p_header_from_wire(data); + data += P2P_PACKET_HEADER_SIZE; + + /* Extract the body */ + body_len = data_len - P2P_PACKET_HEADER_SIZE - P2P_PACKET_FOOTER_SIZE; + /* msg->body_len = msg->msnslp_header.length; */ + + if (body_len > 0) { + part->size = body_len; + part->buffer = g_malloc(body_len); + memcpy(part->buffer, data, body_len); + data += body_len; + } + + /* Extract the footer */ + if (body_len >= 0) + part->footer = msn_p2p_footer_from_wire(data); + + return part; +} + +static void msn_slpmsgpart_destroy(MsnSlpMessagePart *part) +{ + g_free(part->header); + g_free(part->footer); + + g_free(part); + +} + +MsnSlpMessagePart *msn_slpmsgpart_ref(MsnSlpMessagePart *part) +{ + g_return_val_if_fail(part != NULL, NULL); + part->ref_count++; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "part ref (%p)[%u]\n", part, part->ref_count); + + return part; +} + +void msn_slpmsgpart_unref(MsnSlpMessagePart *part) +{ + g_return_if_fail(part != NULL); + g_return_if_fail(part->ref_count > 0); + + part->ref_count--; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "part unref (%p)[%u]\n", part, part->ref_count); + + if (part->ref_count == 0) { + msn_slpmsgpart_destroy(part); + } +} + +void msn_slpmsgpart_set_bin_data(MsnSlpMessagePart *part, const void *data, size_t len) +{ + g_return_if_fail(part != NULL); + + g_free(part->buffer); + + if (data != NULL && len > 0) { + part->buffer = g_malloc(len + 1); + memcpy(part->buffer, data, len); + part->buffer[len] = '\0'; + part->size = len; + } else { + part->buffer = NULL; + part->size = 0; + } + +} + +char *msn_slpmsgpart_serialize(MsnSlpMessagePart *part, size_t *ret_size) +{ + char *header; + char *footer; + char *base; + char *tmp; + size_t siz; + + base = g_malloc(P2P_PACKET_HEADER_SIZE + part->size + P2P_PACKET_FOOTER_SIZE); + tmp = base; + + header = msn_p2p_header_to_wire(part->header); + footer = msn_p2p_footer_to_wire(part->footer); + + siz = P2P_PACKET_HEADER_SIZE; + /* Copy header */ + memcpy(tmp, header, siz); + tmp += siz; + + /* Copy body */ + memcpy(tmp, part->buffer, part->size); + tmp += part->size; + + /* Copy footer */ + siz = P2P_PACKET_FOOTER_SIZE; + memcpy(tmp, footer, siz); + tmp += siz; + + *ret_size = tmp - base; + + g_free(header); + g_free(footer); + + return base; +} +/* We have received the message ack */ +void +msn_slpmsgpart_ack(MsnSlpMessagePart *part, void *data) +{ + MsnSlpMessage *slpmsg; + long long real_size; + + slpmsg = data; + + real_size = (slpmsg->header->flags == P2P_ACK) ? 0 : slpmsg->size; + + slpmsg->header->offset += part->header->length; + + slpmsg->parts = g_list_remove(slpmsg->parts, part); + msn_slpmsgpart_unref(part); + + if (slpmsg->header->offset < real_size) + { + if (slpmsg->slpcall->xfer && purple_xfer_get_status(slpmsg->slpcall->xfer) == PURPLE_XFER_STATUS_STARTED) + { + slpmsg->slpcall->xfer_msg = slpmsg; + purple_xfer_prpl_ready(slpmsg->slpcall->xfer); + } + else + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + } + else + { + /* The whole message has been sent */ + if (msn_p2p_msg_is_data(slpmsg->header->flags)) + { + if (slpmsg->slpcall != NULL) + { + if (slpmsg->slpcall->cb) + slpmsg->slpcall->cb(slpmsg->slpcall, + NULL, 0); + } + } + } +} + +/* We have received the message nak. */ +void +msn_slpmsgpart_nak(MsnSlpMessagePart *part, void *data) +{ + MsnSlpMessage *slpmsg; + + slpmsg = data; + + msn_slplink_send_msgpart(slpmsg->slplink, slpmsg); + + slpmsg->parts = g_list_remove(slpmsg->parts, part); + msn_slpmsgpart_unref(part); +} + diff --git a/purple/libpurple/protocols/msn/slpmsg_part.h b/purple/libpurple/protocols/msn/slpmsg_part.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/slpmsg_part.h @@ -0,0 +1,63 @@ +/** + * @file slpmsg_part.h MSNSLP Parts + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef MSN_SLPMSG_PART_H +#define MSN_SLPMSG_PART_H + +#include "p2p.h" + +typedef struct _MsnSlpMessagePart MsnSlpMessagePart; +typedef void (*MsnSlpPartCb)(MsnSlpMessagePart *part, void *data); + +struct _MsnSlpMessagePart +{ + guint ref_count; + + MsnP2PHeader *header; + MsnP2PFooter *footer; + + MsnSlpPartCb ack_cb; + MsnSlpPartCb nak_cb; + void *ack_data; + + guchar *buffer; + size_t size; +}; + +MsnSlpMessagePart *msn_slpmsgpart_new(MsnP2PHeader *header, MsnP2PFooter *footer); + +MsnSlpMessagePart *msn_slpmsgpart_new_from_data(const char *data, size_t data_len); + +MsnSlpMessagePart *msn_slpmsgpart_ref(MsnSlpMessagePart *part); + +void msn_slpmsgpart_unref(MsnSlpMessagePart *part); + +void msn_slpmsgpart_set_bin_data(MsnSlpMessagePart *part, const void *data, size_t len); + +char *msn_slpmsgpart_serialize(MsnSlpMessagePart *part, size_t *ret_size); + +void msn_slpmsgpart_ack(MsnSlpMessagePart *part, void *data); + +void msn_slpmsgpart_nak(MsnSlpMessagePart *part, void *data); +#endif /* MSN_SLPMSG_PART_H */ diff --git a/purple/libpurple/protocols/msn/state.c b/purple/libpurple/protocols/msn/state.c --- a/purple/libpurple/protocols/msn/state.c +++ b/purple/libpurple/protocols/msn/state.c @@ -18,50 +18,47 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include "internal.h" +#include "debug.h" #include "core.h" -#include "msn.h" +#include "notification.h" #include "state.h" static const char *away_text[] = { N_("Available"), N_("Available"), N_("Busy"), N_("Idle"), N_("Be Right Back"), N_("Away From Computer"), N_("On The Phone"), N_("Out To Lunch"), N_("Available"), N_("Available") }; -/* Local Function Prototype*/ -static char *msn_build_psm(const char *psmstr,const char *mediastr, - const char *guidstr); - /* * WLM media PSM info build prcedure * * Result can like: * \0Music\01\0{0} - {1}\0Song Title\0Song Artist\0Song Album\0\0\ * \0Games\01\0Playing {0}\0Game Name\0\ * \0Office\01\0Office Message\0Office App Name\0" */ static char * -msn_build_psm(const char *psmstr,const char *mediastr, const char *guidstr) +msn_build_psm(const char *psmstr,const char *mediastr, const char *guidstr, guint protocol_ver) { xmlnode *dataNode,*psmNode,*mediaNode,*guidNode; char *result; int length; dataNode = xmlnode_new("Data"); psmNode = xmlnode_new("PSM"); @@ -77,70 +74,60 @@ msn_build_psm(const char *psmstr,const c xmlnode_insert_child(dataNode, mediaNode); guidNode = xmlnode_new("MachineGuid"); if(guidstr != NULL){ xmlnode_insert_data(guidNode, guidstr, -1); } xmlnode_insert_child(dataNode, guidNode); + if (protocol_ver >= 16) { + /* TODO: What is this for? */ + xmlnode *ddpNode = xmlnode_new("DDP"); + xmlnode_insert_child(dataNode, ddpNode); + } + result = xmlnode_to_str(dataNode, &length); xmlnode_free(dataNode); return result; } -/* get the CurrentMedia info from the XML string */ +/* get the CurrentMedia info from the XML node */ char * -msn_get_currentmedia(char *xml_str, gsize len) +msn_get_currentmedia(xmlnode *payloadNode) { - xmlnode *payloadNode, *currentmediaNode; + xmlnode *currentmediaNode; char *currentmedia; purple_debug_info("msn", "Get CurrentMedia\n"); - payloadNode = xmlnode_from_str(xml_str, len); - if (!payloadNode) { - purple_debug_error("msn", "PSM XML parse Error!\n"); - return NULL; - } currentmediaNode = xmlnode_get_child(payloadNode, "CurrentMedia"); if (currentmediaNode == NULL) { purple_debug_info("msn", "No CurrentMedia Node\n"); - xmlnode_free(payloadNode); return NULL; } currentmedia = xmlnode_get_data(currentmediaNode); - xmlnode_free(payloadNode); - return currentmedia; } -/*get the PSM info from the XML string*/ +/* Get the PSM info from the XML node */ char * -msn_get_psm(char *xml_str, gsize len) +msn_get_psm(xmlnode *payloadNode) { - xmlnode *payloadNode, *psmNode; + xmlnode *psmNode; char *psm; purple_debug_info("msn", "msn get PSM\n"); - payloadNode = xmlnode_from_str(xml_str, len); - if (!payloadNode) { - purple_debug_error("msn", "PSM XML parse Error!\n"); - return NULL; - } psmNode = xmlnode_get_child(payloadNode, "PSM"); if (psmNode == NULL) { purple_debug_info("msn", "No PSM status Node\n"); - xmlnode_free(payloadNode); return NULL; } psm = xmlnode_get_data(psmNode); - xmlnode_free(payloadNode); - return psm; } static char * create_media_string(PurplePresence *presence) { const char *title, *game, *office; char *ret; @@ -170,60 +157,56 @@ create_media_string(PurplePresence *pres ret = NULL; return ret; } /* set the MSN's PSM info,Currently Read from the status Line * Thanks for Cris Code */ -void +static void msn_set_psm(MsnSession *session) { PurpleAccount *account; PurplePresence *presence; PurpleStatus *status; - MsnCmdProc *cmdproc; - MsnTransaction *trans; char *payload; const char *statusline; gchar *statusline_stripped, *media = NULL; g_return_if_fail(session != NULL); g_return_if_fail(session->notification != NULL); account = session->account; - cmdproc = session->notification->cmdproc; /* Get the PSM string from Purple's Status Line */ presence = purple_account_get_presence(account); status = purple_presence_get_active_status(presence); statusline = purple_status_get_attr_string(status, "message"); /* MSN expects plain text, not HTML */ statusline_stripped = purple_markup_strip_html(statusline); media = create_media_string(presence); g_free(session->psm); - session->psm = msn_build_psm(statusline_stripped, media, NULL); + session->psm = msn_build_psm(statusline_stripped, media, session->protocol_ver >= 16 ? session->guid : NULL, session->protocol_ver); payload = session->psm; - purple_debug_misc("msn", "Sending UUX command with payload: %s\n", payload); - trans = msn_transaction_new(cmdproc, "UUX", "%" G_GSIZE_FORMAT, strlen(payload)); - msn_transaction_set_payload(trans, payload, strlen(payload)); - msn_cmdproc_send_trans(cmdproc, trans); + + msn_notification_send_uux(session, payload); g_free(statusline_stripped); g_free(media); } void msn_change_status(MsnSession *session) { PurpleAccount *account; MsnCmdProc *cmdproc; + MsnTransaction *trans; MsnUser *user; MsnObject *msnobj; const char *state_text; GHashTable *ui_info = purple_core_get_ui_info(); MsnClientCaps caps = MSN_CLIENT_ID; g_return_if_fail(session != NULL); g_return_if_fail(session->notification != NULL); @@ -251,34 +234,44 @@ msn_change_status(MsnSession *session) state_text = msn_state_get_text(msn_state_from_account(account)); /* If we're not logged in yet, don't send the status to the server, * it will be sent when login completes */ if (!session->logged_in) return; + msn_set_psm(session); + msnobj = msn_user_get_object(user); if (msnobj == NULL) { - msn_cmdproc_send(cmdproc, "CHG", "%s %u", state_text, caps); + if (session->protocol_ver >= 16) + trans = msn_transaction_new(cmdproc, "CHG", "%s %u:%02u 0", state_text, caps, MSN_CLIENT_ID_EXT_CAPS); + else + trans = msn_transaction_new(cmdproc, "CHG", "%s %u", state_text, caps); } else { char *msnobj_str; msnobj_str = msn_object_to_string(msnobj); - msn_cmdproc_send(cmdproc, "CHG", "%s %u %s", state_text, - caps, purple_url_encode(msnobj_str)); + if (session->protocol_ver >= 16) + trans = msn_transaction_new(cmdproc, "CHG", "%s %u:%02u %s", state_text, + caps, MSN_CLIENT_ID_EXT_CAPS, purple_url_encode(msnobj_str)); + else + trans = msn_transaction_new(cmdproc, "CHG", "%s %u %s", state_text, + caps, purple_url_encode(msnobj_str)); g_free(msnobj_str); } - msn_set_psm(session); + + msn_cmdproc_send_trans(cmdproc, trans); } const char * msn_away_get_text(MsnAwayType type) { g_return_val_if_fail(type <= MSN_HIDDEN, NULL); return _(away_text[type]); diff --git a/purple/libpurple/protocols/msn/state.h b/purple/libpurple/protocols/msn/state.h --- a/purple/libpurple/protocols/msn/state.h +++ b/purple/libpurple/protocols/msn/state.h @@ -53,19 +53,17 @@ void msn_change_status(MsnSession *sessi * @param type The away type. * * @return The string representation of the away type. */ const char *msn_away_get_text(MsnAwayType type); const char *msn_state_get_text(MsnAwayType state); -void msn_set_psm(MsnSession *session); +/* Get the CurrentMedia info from the XML node */ +char *msn_get_currentmedia(xmlnode *payloadNode); -/* Get the CurrentMedia info from the XML string */ -char *msn_get_currentmedia(char *xml_str, gsize len); - -/*get the PSM info from the XML string*/ -char *msn_get_psm(char *xml_str, gsize len); +/* Get the PSM info from the XML node */ +char *msn_get_psm(xmlnode *payloadNode); MsnAwayType msn_state_from_account(PurpleAccount *account); #endif /* MSN_STATE_H */ diff --git a/purple/libpurple/protocols/msn/switchboard.c b/purple/libpurple/protocols/msn/switchboard.c --- a/purple/libpurple/protocols/msn/switchboard.c +++ b/purple/libpurple/protocols/msn/switchboard.c @@ -16,29 +16,29 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" -#include "prefs.h" + +#include "internal.h" +#include "debug.h" + +#include "msnutils.h" #include "switchboard.h" -#include "notification.h" -#include "msnutils.h" - -#include "error.h" +#include "sbconn.h" +#include "slplink.h" +#include "user.h" +#include "userlist.h" static MsnTable *cbs_table; -static void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, - MsnMsgErrorType error); - /************************************************************************** * Main **************************************************************************/ MsnSwitchBoard * msn_switchboard_new(MsnSession *session) { MsnSwitchBoard *swboard; @@ -85,19 +85,21 @@ msn_switchboard_destroy(MsnSwitchBoard * if (swboard->reconn_timeout_h > 0) purple_timeout_remove(swboard->reconn_timeout_h); /* If it linked us is because its looking for trouble */ while (swboard->slplinks != NULL) { MsnSlpLink *slplink = swboard->slplinks->data; + swboard->slplinks = g_list_remove(swboard->slplinks, slplink); + /* Destroy only those slplinks which use the switchboard */ if (slplink->dc == NULL) - msn_slplink_destroy(slplink); + msn_slplink_unref(slplink); else { swboard->slplinks = g_list_remove(swboard->slplinks, slplink); slplink->swboard = NULL; } } /* Destroy the message queue */ while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) @@ -118,17 +120,17 @@ msn_switchboard_destroy(MsnSwitchBoard * while ((l = swboard->ack_list) != NULL) msg_error_helper(swboard->cmdproc, l->data, MSN_MSG_ERROR_SB); g_free(swboard->im_user); g_free(swboard->auth_key); g_free(swboard->session_id); for (; swboard->users; swboard->users = g_list_delete_link(swboard->users, swboard->users)) - g_free(swboard->users->data); + msn_user_unref(swboard->users->data); session = swboard->session; session->switches = g_list_remove(session->switches, swboard); for (l = session->slplinks; l; l = l->next) { MsnSlpLink *slplink = l->data; if (slplink->swboard == swboard) slplink->swboard = NULL; } @@ -218,46 +220,71 @@ send_clientcaps(MsnSwitchBoard *swboard) msg = msn_message_new(MSN_MSG_CAPS); msn_message_set_content_type(msg, "text/x-clientcaps"); msn_message_set_flag(msg, 'U'); msn_message_set_bin_data(msg, MSN_CLIENTINFO, strlen(MSN_CLIENTINFO)); msn_switchboard_send_msg(swboard, msg, TRUE); - msn_message_destroy(msg); + msn_message_unref(msg); } static void msn_switchboard_add_user(MsnSwitchBoard *swboard, const char *user) { MsnCmdProc *cmdproc; PurpleAccount *account; + MsnUserList *userlist; + MsnUser *msnuser; char *semicolon; char *passport; g_return_if_fail(swboard != NULL); cmdproc = swboard->cmdproc; account = cmdproc->session->account; semicolon = strchr(user, ';'); /* We don't really care about the machine ID. */ if (semicolon) passport = g_strndup(user, semicolon - user); else passport = g_strdup(user); + userlist = swboard->session->userlist; + msnuser = msn_userlist_find_user(userlist, passport); + /* Don't add multiple endpoints to the conversation. */ - if (g_list_find_custom(swboard->users, passport, (GCompareFunc)strcmp)) { + if (g_list_find_custom(swboard->users, passport, (GCompareFunc)msn_user_passport_cmp)) { g_free(passport); return; } - swboard->users = g_list_prepend(swboard->users, passport); + /* Don't add ourselves either... */ + if (g_str_equal(passport, purple_account_get_username(account))) { + g_free(passport); + return; + } + + /* Don't add ourselves either... */ + if (g_str_equal(passport, purple_account_get_username(account))) { + g_free(passport); + return; + } + + if (!msnuser) { + purple_debug_info("msn","User %s is not on our list.\n", passport); + msnuser = msn_user_new(userlist, passport, NULL); + } else + msn_user_ref(msnuser); + + g_free(passport); + + swboard->users = g_list_prepend(swboard->users, msnuser); swboard->current_users++; swboard->empty = FALSE; if (purple_debug_is_verbose()) purple_debug_info("msn", "user=[%s], total=%d\n", user, swboard->current_users); if (!(swboard->flag & MSN_SB_FLAG_IM) && (swboard->conv != NULL)) @@ -265,21 +292,21 @@ msn_switchboard_add_user(MsnSwitchBoard /* This is a helper switchboard. */ purple_debug_error("msn", "switchboard_add_user: conv != NULL\n"); return; } if ((swboard->conv != NULL) && (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) { - purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL, + purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), msnuser->passport, NULL, PURPLE_CBFLAGS_NONE, TRUE); msn_servconn_set_idle_timeout(swboard->servconn, 0); } - else if (swboard->current_users > 1 || swboard->total_users > 1) + else if (swboard->current_users > 1) { msn_servconn_set_idle_timeout(swboard->servconn, 0); if (swboard->conv == NULL || purple_conversation_get_type(swboard->conv) != PURPLE_CONV_TYPE_CHAT) { GList *l; #if 0 @@ -294,34 +321,34 @@ msn_switchboard_add_user(MsnSwitchBoard swboard->conv = serv_got_joined_chat(account->gc, swboard->chat_id, "MSN Chat"); for (l = swboard->users; l != NULL; l = l->next) { const char *tmp_user; - tmp_user = l->data; + tmp_user = ((MsnUser*)l->data)->passport; purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), tmp_user, NULL, PURPLE_CBFLAGS_NONE, TRUE); } purple_conv_chat_add_user(PURPLE_CONV_CHAT(swboard->conv), purple_account_get_username(account), NULL, PURPLE_CBFLAGS_NONE, TRUE); g_free(swboard->im_user); swboard->im_user = NULL; } } else if (swboard->conv == NULL) { swboard->conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, - user, account); + msnuser->passport, account); } else { purple_debug_warning("msn", "switchboard_add_user: This should not happen!\n"); } } static PurpleConversation * @@ -401,17 +428,17 @@ msg_resend_cb(gpointer data) purple_debug_info("msn", "unqueuing unsent message to %s\n", swboard->im_user); msn_switchboard_request(swboard); msn_switchboard_request_add_user(swboard, swboard->im_user); swboard->reconn_timeout_h = 0; return FALSE; } -static void +void msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error) { MsnSwitchBoard *swboard; g_return_if_fail(cmdproc != NULL); g_return_if_fail(msg != NULL); if ((error != MSN_MSG_ERROR_SB) && (msg->nak_cb != NULL)) @@ -509,17 +536,17 @@ msg_error_helper(MsnCmdProc *cmdproc, Ms str_reason = _("Message may have not been sent " "because an unknown error occurred:"); } body_str = msn_message_to_string(msg); body_enc = g_markup_escape_text(body_str, -1); g_free(body_str); - format = msn_message_get_attr(msg, "X-MMS-IM-Format"); + format = msn_message_get_header_value(msg, "X-MMS-IM-Format"); msn_parse_format(format, &pre, &post); body_str = g_strdup_printf("%s%s%s", pre ? pre : "", body_enc ? body_enc : "", post ? post : ""); g_free(body_enc); g_free(pre); g_free(post); msn_switchboard_report_user(swboard, PURPLE_MESSAGE_ERROR, @@ -538,176 +565,34 @@ msg_error_helper(MsnCmdProc *cmdproc, Ms msn_message_unref(msg); } } /************************************************************************** * Message Stuff **************************************************************************/ -/** Called when a message times out. */ -static void -msg_timeout(MsnCmdProc *cmdproc, MsnTransaction *trans) -{ - MsnMessage *msg; - - msg = trans->data; - - msg_error_helper(cmdproc, msg, MSN_MSG_ERROR_TIMEOUT); -} - /** Called when we receive an error of a message. */ static void msg_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) { msg_error_helper(cmdproc, trans->data, MSN_MSG_ERROR_UNKNOWN); } -#if 0 -/** Called when we receive an ack of a special message. */ -static void -msg_ack(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnMessage *msg; - - msg = cmd->trans->data; - - if (msg->ack_cb != NULL) - msg->ack_cb(msg->ack_data); - - msn_message_unref(msg); -} - -/** Called when we receive a nak of a special message. */ -static void -msg_nak(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnMessage *msg; - - msg = cmd->trans->data; - - msn_message_unref(msg); -} -#endif - -static void -release_msg(MsnSwitchBoard *swboard, MsnMessage *msg) -{ - MsnCmdProc *cmdproc; - MsnTransaction *trans; - char *payload; - gsize payload_len; - char flag; - - g_return_if_fail(swboard != NULL); - g_return_if_fail(msg != NULL); - - cmdproc = swboard->cmdproc; - - payload = msn_message_gen_payload(msg, &payload_len); - - if (purple_debug_is_verbose()) { - purple_debug_info("msn", "SB length:{%" G_GSIZE_FORMAT "}\n", payload_len); - msn_message_show_readable(msg, "SB SEND", FALSE); - } - - flag = msn_message_get_flag(msg); - trans = msn_transaction_new(cmdproc, "MSG", "%c %" G_GSIZE_FORMAT, - flag, payload_len); - - /* Data for callbacks */ - msn_transaction_set_data(trans, msg); - - if (flag != 'U') { - if (msg->type == MSN_MSG_TEXT) - { - msg->ack_ref = TRUE; - msn_message_ref(msg); - swboard->ack_list = g_list_append(swboard->ack_list, msg); - msn_transaction_set_timeout_cb(trans, msg_timeout); - } - else if (msg->type == MSN_MSG_SLP) - { - msg->ack_ref = TRUE; - msn_message_ref(msg); - swboard->ack_list = g_list_append(swboard->ack_list, msg); - msn_transaction_set_timeout_cb(trans, msg_timeout); -#if 0 - if (msg->ack_cb != NULL) - { - msn_transaction_add_cb(trans, "ACK", msg_ack); - msn_transaction_add_cb(trans, "NAK", msg_nak); - } -#endif - } - } - - trans->payload = payload; - trans->payload_len = payload_len; - - msg->trans = trans; - - msn_cmdproc_send_trans(cmdproc, trans); -} - -static void -queue_msg(MsnSwitchBoard *swboard, MsnMessage *msg) -{ - g_return_if_fail(swboard != NULL); - g_return_if_fail(msg != NULL); - - purple_debug_info("msn", "Appending message to queue.\n"); - - g_queue_push_tail(swboard->msg_queue, msg); - - msn_message_ref(msg); -} - -static void -process_queue(MsnSwitchBoard *swboard) -{ - MsnMessage *msg; - - g_return_if_fail(swboard != NULL); - - purple_debug_info("msn", "Processing queue\n"); - - while ((msg = g_queue_pop_head(swboard->msg_queue)) != NULL) - { - purple_debug_info("msn", "Sending message\n"); - release_msg(swboard, msg); - msn_message_unref(msg); - } -} - gboolean msn_switchboard_can_send(MsnSwitchBoard *swboard) { g_return_val_if_fail(swboard != NULL, FALSE); if (swboard->empty || !g_queue_is_empty(swboard->msg_queue)) return FALSE; return TRUE; } -void -msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, - gboolean queue) -{ - g_return_if_fail(swboard != NULL); - g_return_if_fail(msg != NULL); - - purple_debug_info("msn", "switchboard send msg..\n"); - if (msn_switchboard_can_send(swboard)) - release_msg(swboard, msg); - else if (queue) - queue_msg(swboard, msg); -} - /************************************************************************** * Switchboard Commands **************************************************************************/ static void ans_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSwitchBoard *swboard; @@ -740,64 +625,63 @@ bye_cmd(MsnCmdProc *cmdproc, MsnCommand else if ((swboard->current_users > 1) || (purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) { GList *passport; /* This is a switchboard used for a chat */ purple_conv_chat_remove_user(PURPLE_CONV_CHAT(swboard->conv), user, NULL); passport = g_list_find_custom(swboard->users, user, (GCompareFunc)strcmp); - g_free(passport->data); + if (passport) + g_free(passport->data); + else + purple_debug_warning("msn", "Can't find user %s in the switchboard\n", user); swboard->users = g_list_delete_link(swboard->users, passport); swboard->current_users--; if (swboard->current_users == 0) msn_switchboard_destroy(swboard); } else { /* This is a switchboard used for a im session */ msn_switchboard_destroy(swboard); } } static void iro_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { PurpleAccount *account; - PurpleConnection *gc; MsnSwitchBoard *swboard; account = cmdproc->session->account; - gc = account->gc; swboard = cmdproc->data; swboard->total_users = atoi(cmd->params[2]); msn_switchboard_add_user(swboard, cmd->params[3]); } static void joi_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSession *session; PurpleAccount *account; - PurpleConnection *gc; MsnSwitchBoard *swboard; const char *passport; passport = cmd->params[0]; session = cmdproc->session; account = session->account; - gc = account->gc; swboard = cmdproc->data; msn_switchboard_add_user(swboard, passport); - process_queue(swboard); + msn_sbconn_process_queue(swboard); if (!session->http_method) send_clientcaps(swboard); if (swboard->closed) msn_switchboard_close(swboard); } @@ -827,17 +711,20 @@ msg_cmd(MsnCmdProc *cmdproc, MsnCommand cmd->payload_len = atoi(cmd->params[2]); cmdproc->last_cmd->payload_cb = msg_cmd_post; } static void ubm_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { purple_debug_misc("msn", "get UBM...\n"); - cmd->payload_len = atoi(cmd->params[3]); + if (cmdproc->session->protocol_ver >= 16) + cmd->payload_len = atoi(cmd->params[5]); + else + cmd->payload_len = atoi(cmd->params[3]); cmdproc->last_cmd->payload_cb = msg_cmd_post; } static void nak_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnMessage *msg; @@ -850,18 +737,18 @@ nak_cmd(MsnCmdProc *cmdproc, MsnCommand static void ack_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) { MsnSwitchBoard *swboard; MsnMessage *msg; msg = cmd->trans->data; - if (msg->ack_cb != NULL) - msg->ack_cb(msg, msg->ack_data); + if (msg->part && msg->part->ack_cb != NULL) + msg->part->ack_cb(msg->part, msg->part->ack_data); swboard = cmdproc->data; if (swboard) swboard->ack_list = g_list_remove(swboard->ack_list, msg); msn_message_unref(msg); } static void @@ -973,42 +860,52 @@ ans_usr_error(MsnCmdProc *cmdproc, MsnTr static void connect_cb(MsnServConn *servconn) { MsnSwitchBoard *swboard; MsnTransaction *trans; MsnCmdProc *cmdproc; PurpleAccount *account; + char *username; cmdproc = servconn->cmdproc; g_return_if_fail(cmdproc != NULL); account = cmdproc->session->account; swboard = cmdproc->data; g_return_if_fail(swboard != NULL); + if (servconn->session->protocol_ver >= 16) + username = g_strdup_printf("%s;{%s}", + purple_account_get_username(account), + servconn->session->guid); + else + username = g_strdup(purple_account_get_username(account)); + if (msn_switchboard_is_invited(swboard)) { swboard->empty = FALSE; trans = msn_transaction_new(cmdproc, "ANS", "%s %s %s", - purple_account_get_username(account), + username, swboard->auth_key, swboard->session_id); } else { trans = msn_transaction_new(cmdproc, "USR", "%s %s", - purple_account_get_username(account), + username, swboard->auth_key); } msn_transaction_set_error_cb(trans, ans_usr_error); msn_transaction_set_data(trans, swboard); msn_cmdproc_send_trans(cmdproc, trans); + + g_free(username); } static void ans_usr_error(MsnCmdProc *cmdproc, MsnTransaction *trans, int error) { MsnSwitchBoard *swboard; char **params; char *passport; @@ -1216,18 +1113,21 @@ msn_switchboard_close(MsnSwitchBoard *sw if (swboard->error != MSN_SB_ERROR_NONE) { msn_switchboard_destroy(swboard); } else if (g_queue_is_empty(swboard->msg_queue) || !swboard->session->connected) { MsnCmdProc *cmdproc; + MsnTransaction *trans; cmdproc = swboard->cmdproc; - msn_cmdproc_send_quick(cmdproc, "OUT", NULL, NULL); + trans = msn_transaction_new(cmdproc, "OUT", NULL); + msn_transaction_set_saveable(trans, FALSE); + msn_cmdproc_send_trans(cmdproc, trans); msn_switchboard_destroy(swboard); } else { swboard->closed = TRUE; } } diff --git a/purple/libpurple/protocols/msn/switchboard.h b/purple/libpurple/protocols/msn/switchboard.h --- a/purple/libpurple/protocols/msn/switchboard.h +++ b/purple/libpurple/protocols/msn/switchboard.h @@ -45,22 +45,20 @@ typedef enum * A switchboard flag. */ typedef enum { MSN_SB_FLAG_IM = 0x01, /**< This switchboard is being used for a conversation. */ MSN_SB_FLAG_FT = 0x02 /**< This switchboard is being used for file transfer. */ } MsnSBFlag; -#include "conversation.h" - +#include "cmdproc.h" #include "msg.h" #include "servconn.h" -#include "slplink.h" -#include "user.h" +#include "session.h" /** * A switchboard. * * A place where a bunch of users send messages to the rest of the users. */ struct _MsnSwitchBoard { @@ -241,47 +239,26 @@ gboolean msn_switchboard_can_send(MsnSwi * @param queue A flag that states if we want this message to be queued (in * the case it cannot currently be sent). * * @return @c TRUE if a message can be sent, @c FALSE otherwise. */ void msn_switchboard_send_msg(MsnSwitchBoard *swboard, MsnMessage *msg, gboolean queue); +void +msg_error_helper(MsnCmdProc *cmdproc, MsnMessage *msg, MsnMsgErrorType error); + gboolean msn_switchboard_chat_leave(MsnSwitchBoard *swboard); gboolean msn_switchboard_chat_invite(MsnSwitchBoard *swboard, const char *who); void msn_switchboard_request(MsnSwitchBoard *swboard); void msn_switchboard_request_add_user(MsnSwitchBoard *swboard, const char *user); /** - * Processes peer to peer messages. - * - * @param cmdproc The command processor. - * @param msg The message. - */ -void msn_p2p_msg(MsnCmdProc *cmdproc, MsnMessage *msg); - -/** - * Processes emoticon messages. - * - * @param cmdproc The command processor. - * @param msg The message. - */ -void msn_emoticon_msg(MsnCmdProc *cmdproc, MsnMessage *msg); - -/** - * Processes INVITE messages. - * - * @param cmdproc The command processor. - * @param msg The message. - */ -void msn_invite_msg(MsnCmdProc *cmdproc, MsnMessage *msg); - -/** * Shows an ink message from this switchboard. * * @param swboard The switchboard. * @param passport The user that sent the ink. * @param data The ink data. */ void msn_switchboard_show_ink(MsnSwitchBoard *swboard, const char *passport, const char *data); diff --git a/purple/libpurple/protocols/msn/sync.c b/purple/libpurple/protocols/msn/sync.c deleted file mode 100644 --- a/purple/libpurple/protocols/msn/sync.c +++ /dev/null @@ -1,253 +0,0 @@ -/** - * @file sync.c MSN list synchronization functions - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ -#include "msn.h" -#include "sync.h" -#include "state.h" - -static MsnTable *cbs_table; - -static void -blp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - PurpleConnection *gc = cmdproc->session->account->gc; - const char *list_name; - - list_name = cmd->params[0]; - - if (!g_ascii_strcasecmp(list_name, "AL")) - { - /* - * If the current setting is AL, messages from users who - * are not in BL will be delivered. - * - * In other words, deny some. - */ - gc->account->perm_deny = PURPLE_PRIVACY_DENY_USERS; - } - else - { - /* If the current setting is BL, only messages from people - * who are in the AL will be delivered. - * - * In other words, permit some. - */ - gc->account->perm_deny = PURPLE_PRIVACY_ALLOW_USERS; - } -} - -static void -prp_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnSession *session = cmdproc->session; - const char *type, *value; - - type = cmd->params[0]; - value = cmd->params[1]; - - if (cmd->param_count == 2) - { - if (!strcmp(type, "PHH")) - msn_user_set_home_phone(session->user, purple_url_decode(value)); - else if (!strcmp(type, "PHW")) - msn_user_set_work_phone(session->user, purple_url_decode(value)); - else if (!strcmp(type, "PHM")) - msn_user_set_mobile_phone(session->user, purple_url_decode(value)); - } - else - { - if (!strcmp(type, "PHH")) - msn_user_set_home_phone(session->user, NULL); - else if (!strcmp(type, "PHW")) - msn_user_set_work_phone(session->user, NULL); - else if (!strcmp(type, "PHM")) - msn_user_set_mobile_phone(session->user, NULL); - } -} - -static void -lsg_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnSession *session = cmdproc->session; - const char *name; - const char *group_id; - - group_id = cmd->params[0]; - name = purple_url_decode(cmd->params[1]); - - msn_group_new(session->userlist, group_id, name); - - /* HACK */ - if (group_id == 0) - { - /* Group of ungroupped buddies */ - if (session->sync->total_users == 0) - { - cmdproc->cbs_table = session->sync->old_cbs_table; - - msn_session_finish_login(session); - - msn_sync_destroy(session->sync); - session->sync = NULL; - } - return; - } - - if ((purple_find_group(name)) == NULL) - { - PurpleGroup *g = purple_group_new(name); - purple_blist_add_group(g, NULL); - } -} - -static void -lst_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnSession *session = cmdproc->session; - char *passport = NULL; - const char *friend = NULL; - int list_op; - MsnUser *user; - - passport = cmd->params[0]; - friend = purple_url_decode(cmd->params[1]); - list_op = atoi(cmd->params[2]); - - user = msn_user_new(session->userlist, passport, friend); - - msn_userlist_add_user(session->userlist, user); - - session->sync->last_user = user; - - /* TODO: This can be improved */ - - if (list_op & MSN_LIST_FL_OP) - { - char **c; - char **tokens; - const char *group_nums; - GSList *group_ids; - - group_nums = cmd->params[3]; - - group_ids = NULL; - - tokens = g_strsplit(group_nums, ",", -1); - - for (c = tokens; *c != NULL; c++) - { - group_ids = g_slist_append(group_ids, *c); - } - - - msn_got_lst_user(session, user, list_op, group_ids); - - g_strfreev(tokens); - g_slist_free(group_ids); - } - else - { - msn_got_lst_user(session, user, list_op, NULL); - } - - session->sync->num_users++; - - if (session->sync->num_users == session->sync->total_users) - { - cmdproc->cbs_table = session->sync->old_cbs_table; - - msn_session_finish_login(session); - - msn_sync_destroy(session->sync); - session->sync = NULL; - } -} - -static void -bpr_cmd(MsnCmdProc *cmdproc, MsnCommand *cmd) -{ - MsnSync *sync = cmdproc->session->sync; - const char *type, *value; - MsnUser *user; - - user = sync->last_user; - - g_return_if_fail(user != NULL); - - type = cmd->params[0]; - value = cmd->params[1]; - - if (value) - { - if (!strcmp(type, "MOB")) - { - if (!strcmp(value, "Y")) - user->mobile = TRUE; - } - else if (!strcmp(type, "PHH")) - msn_user_set_home_phone(user, purple_url_decode(value)); - else if (!strcmp(type, "PHW")) - msn_user_set_work_phone(user, purple_url_decode(value)); - else if (!strcmp(type, "PHM")) - msn_user_set_mobile_phone(user, purple_url_decode(value)); - } -} - -void -msn_sync_init(void) -{ - cbs_table = msn_table_new(); - - /* Syncing */ - msn_table_add_cmd(cbs_table, NULL, "GTC", NULL); - msn_table_add_cmd(cbs_table, NULL, "BLP", blp_cmd); - msn_table_add_cmd(cbs_table, NULL, "PRP", prp_cmd); - msn_table_add_cmd(cbs_table, NULL, "LSG", lsg_cmd); - msn_table_add_cmd(cbs_table, NULL, "LST", lst_cmd); - msn_table_add_cmd(cbs_table, NULL, "BPR", bpr_cmd); -} - -void -msn_sync_end(void) -{ - msn_table_destroy(cbs_table); -} - -MsnSync * -msn_sync_new(MsnSession *session) -{ - MsnSync *sync; - - sync = g_new0(MsnSync, 1); - - sync->session = session; - sync->cbs_table = cbs_table; - - return sync; -} - -void -msn_sync_destroy(MsnSync *sync) -{ - g_free(sync); -} diff --git a/purple/libpurple/protocols/msn/sync.h b/purple/libpurple/protocols/msn/sync.h deleted file mode 100644 --- a/purple/libpurple/protocols/msn/sync.h +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @file sync.h MSN list synchronization functions - * - * purple - * - * Purple is the legal property of its developers, whose names are too numerous - * to list here. Please refer to the COPYRIGHT file distributed with this - * source distribution. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA - */ -#ifndef MSN_SYNC_H -#define MSN_SYNC_H - -typedef struct _MsnSync MsnSync; - -#include "session.h" -#include "table.h" -#include "user.h" - -struct _MsnSync -{ - MsnSession *session; - MsnTable *cbs_table; - - /* - * TODO: What is the intended purpose of old_cbs_table? Nothing - * sets it and it is only read in two places. - */ - MsnTable *old_cbs_table; - - int num_users; - int total_users; - int num_groups; - int total_groups; - MsnUser *last_user; -}; - -void msn_sync_init(void); -void msn_sync_end(void); - -MsnSync *msn_sync_new(MsnSession *session); -void msn_sync_destroy(MsnSync *sync); - -#endif /* MSN_SYNC_H */ diff --git a/purple/libpurple/protocols/msn/table.h b/purple/libpurple/protocols/msn/table.h --- a/purple/libpurple/protocols/msn/table.h +++ b/purple/libpurple/protocols/msn/table.h @@ -29,25 +29,65 @@ typedef struct _MsnTable MsnTable; #include "cmdproc.h" #include "transaction.h" #include "msg.h" typedef void (*MsnMsgTypeCb)(MsnCmdProc *cmdproc, MsnMessage *msg); struct _MsnTable { - GHashTable *cmds; - GHashTable *msgs; - GHashTable *errors; + GHashTable *cmds; /**< Callbacks that manage command response. */ + GHashTable *msgs; /**< Callbacks that manage incoming messages. */ + GHashTable *errors; /**< Callbacks that manage command errors. */ - GHashTable *async; - GHashTable *fallback; + GHashTable *async; /**< Callbacks that manage incoming asyncronous messages. */ + /* TODO: Does this one is really needed? */ + GHashTable *fallback; /**< Fallback callback. */ }; +/** + * Create a new instance of a MsnTable which map commands, errors and messages + * with callbacks that will handle it. + * + * @return A new MsnTable. + */ MsnTable *msn_table_new(void); + +/** + * Destroy a MsnTable. + * + * @param table The MsnTable to be destroyed. + */ void msn_table_destroy(MsnTable *table); +/** + * Relate an incomming command from server with a callback able to handle + * the event. + * + * @param table The MsnTable. + * @param command If NULL this add an incoming asyncronous command set in answer. + * Else, the command sent. + * @param answer The server answer to 'command'. If 'command' is NULL, + * the asyncronous command sent by the server. + * @param cb Callback to handle this event. + */ void msn_table_add_cmd(MsnTable *table, char *command, char *answer, MsnTransCb cb); + +/** + * Set a callback to handle incoming command errors. + * + * @param table The MsnTable. + * @param answer Incoming command with error. + * @param cb Callback to handle this error. + */ void msn_table_add_error(MsnTable *table, char *answer, MsnErrorCb cb); + +/** + * Relate a message Content-type with a callback able to handle it. + * + * @param table The MsnTable. + * @param type The Message Content-Type. + * @param cb Callback to handle this Content-type. + */ void msn_table_add_msg_type(MsnTable *table, char *type, MsnMsgTypeCb cb); #endif /* MSN_TABLE_H */ diff --git a/purple/libpurple/protocols/msn/tlv.c b/purple/libpurple/protocols/msn/tlv.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/tlv.c @@ -0,0 +1,420 @@ +/** + * @file tlv.c MSN TLV functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "tlv.h" +#include "msnutils.h" + +static msn_tlv_t * +createtlv(guint8 type, guint8 length, guint8 *value) +{ + msn_tlv_t *ret; + + ret = g_new(msn_tlv_t, 1); + ret->type = type; + ret->length = length; + ret->value = value; + + return ret; +} + +static void +freetlv(msn_tlv_t *oldtlv) +{ + g_free(oldtlv->value); + g_free(oldtlv); +} + +static GSList * +msn_tlv_read(GSList *list, char *bs, size_t *bs_len) +{ + guint8 type, length; + msn_tlv_t *tlv; + + type = msn_read8(bs); + length = msn_read8(bs); + *bs_len -= 2; + + if (length > *bs_len) { + msn_tlvlist_free(list); + return NULL; + } + + tlv = createtlv(type, length, NULL); + if (length > 0) { + tlv->value = g_memdup(bs, length); + if (!tlv->value) { + freetlv(tlv); + msn_tlvlist_free(list); + return NULL; + } + } + + *bs_len -= length; + + return g_slist_prepend(list, tlv); +} + +GSList * +msn_tlvlist_read(char *bs, size_t bs_len) +{ + GSList *list = NULL; + + while (bs_len > 0) { + list = msn_tlv_read(list, bs, &bs_len); + if (list == NULL) + return NULL; + } + + return g_slist_reverse(list); +} + +GSList *msn_tlvlist_copy(GSList *orig) +{ + GSList *new = NULL; + msn_tlv_t *tlv; + + while (orig != NULL) { + tlv = orig->data; + msn_tlvlist_add_raw(&new, tlv->type, tlv->length, (const char *)tlv->value); + orig = orig->next; + } + + return new; +} + +gboolean +msn_tlvlist_equal(GSList *one, GSList *two) +{ + while (one && two) { + msn_tlv_t *a = one->data; + msn_tlv_t *b = two->data; + + if (a->type != b->type) + return FALSE; + else if (a->length != b->length) + return FALSE; + else if (!a->value && b->value) + return FALSE; + else if (a->value && !b->value) + return FALSE; + else if (a->value && b->value && memcmp(a->value, b->value, a->length) != 0) + return FALSE; + + one = one->next; + two = two->next; + } + + return one == two; +} + +void +msn_tlvlist_free(GSList *list) +{ + while (list != NULL) { + freetlv(list->data); + list = g_slist_delete_link(list, list); + } +} + +int +msn_tlvlist_count(GSList *list) +{ + return g_slist_length(list); +} + +size_t +msn_tlvlist_size(GSList *list) +{ + int size; + + if (list == NULL) + return 0; + + for (size = 0; list; list = list->next) + size += (2 + ((msn_tlv_t *)list->data)->length); + + return size; +} + +int +msn_tlvlist_add_raw(GSList **list, const guint16 type, const guint16 length, const char *value) +{ + msn_tlv_t *tlv; + + if (list == NULL) + return 0; + + tlv = createtlv(type, length, NULL); + if (length > 0) + tlv->value = g_memdup(value, length); + + *list = g_slist_append(*list, tlv); + + return tlv->length; +} + +int +msn_tlvlist_add_8(GSList **list, const guint16 type, const guint8 value) +{ + char v8[1]; + + msn_write8(v8, value); + + return msn_tlvlist_add_raw(list, type, 1, v8); +} + +int +msn_tlvlist_add_16(GSList **list, const guint16 type, const guint16 value) +{ + char v16[2]; + + msn_write16be(v16, value); + + return msn_tlvlist_add_raw(list, type, 2, v16); +} + +int +msn_tlvlist_add_32(GSList **list, const guint16 type, const guint32 value) +{ + char v32[4]; + + msn_write32be(v32, value); + + return msn_tlvlist_add_raw(list, type, 4, v32); +} + +int +msn_tlvlist_add_str(GSList **list, const guint16 type, const char *value) +{ + return msn_tlvlist_add_raw(list, type, strlen(value), value); +} + +int +msn_tlvlist_add_empty(GSList **list, const guint16 type) +{ + return msn_tlvlist_add_raw(list, type, 0, NULL); +} + +int +msn_tlvlist_replace_raw(GSList **list, const guint16 type, const guint16 length, const char *value) +{ + GSList *cur; + msn_tlv_t *tlv; + + if (list == NULL) + return 0; + + for (cur = *list; cur != NULL; cur = cur->next) { + tlv = cur->data; + if (tlv->type == type) + break; + } + + if (cur == NULL) + /* TLV does not exist, so add a new one */ + return msn_tlvlist_add_raw(list, type, length, value); + + g_free(tlv->value); + tlv->length = length; + if (length > 0) { + tlv->value = g_memdup(value, length); + } else + tlv->value = NULL; + + return length; +} + +int +msn_tlvlist_replace_str(GSList **list, const guint16 type, const char *str) +{ + return msn_tlvlist_replace_raw(list, type, strlen(str), str); +} + +int +msn_tlvlist_replace_empty(GSList **list, const guint16 type) +{ + return msn_tlvlist_replace_raw(list, type, 0, NULL); +} + +int +msn_tlvlist_replace_8(GSList **list, const guint16 type, const guint8 value) +{ + char v8[1]; + + msn_write8(v8, value); + + return msn_tlvlist_replace_raw(list, type, 1, v8); +} + +int +msn_tlvlist_replace_32(GSList **list, const guint16 type, const guint32 value) +{ + char v32[4]; + + msn_write32be(v32, value); + + return msn_tlvlist_replace_raw(list, type, 4, v32); +} + +void +msn_tlvlist_remove(GSList **list, const guint16 type) +{ + GSList *cur, *next; + msn_tlv_t *tlv; + + if (list == NULL || *list == NULL) + return; + + cur = *list; + while (cur != NULL) { + tlv = cur->data; + next = cur->next; + + if (tlv->type == type) { + /* Delete this TLV */ + *list = g_slist_delete_link(*list, cur); + g_free(tlv->value); + g_free(tlv); + } + + cur = next; + } +} + +#if 0 +int +msn_tlvlist_write(ByteStream *bs, GSList **list) +{ + int goodbuflen; + GSList *cur; + msn_tlv_t *tlv; + + /* do an initial run to test total length */ + goodbuflen = msn_tlvlist_size(*list); + + if (goodbuflen > byte_stream_bytes_left(bs)) + return 0; /* not enough buffer */ + + /* do the real write-out */ + for (cur = *list; cur; cur = cur->next) { + tlv = cur->data; + byte_stream_put16(bs, tlv->type); + byte_stream_put16(bs, tlv->length); + if (tlv->length > 0) + byte_stream_putraw(bs, tlv->value, tlv->length); + } + + return 1; /* TODO: This is a nonsensical return */ +} +#endif + +msn_tlv_t * +msn_tlv_gettlv(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + int i; + + for (i = 0; list != NULL; list = list->next) { + tlv = list->data; + if (tlv->type == type) + i++; + if (i >= nth) + return tlv; + } + + return NULL; +} + +int +msn_tlv_getlength(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + + tlv = msn_tlv_gettlv(list, type, nth); + if (tlv == NULL) + return -1; + + return tlv->length; +} + +char * +msn_tlv_getvalue_as_string(msn_tlv_t *tlv) +{ + char *ret; + + ret = g_malloc(tlv->length + 1); + memcpy(ret, tlv->value, tlv->length); + ret[tlv->length] = '\0'; + + return ret; +} + +char * +msn_tlv_getstr(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + + tlv = msn_tlv_gettlv(list, type, nth); + if (tlv == NULL) + return NULL; + + return msn_tlv_getvalue_as_string(tlv); +} + +guint8 +msn_tlv_get8(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + + tlv = msn_tlv_gettlv(list, type, nth); + if (tlv == NULL) + return 0; /* erm */ + + return msn_read8((const char *)tlv->value); +} + +guint16 +msn_tlv_get16(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + + tlv = msn_tlv_gettlv(list, type, nth); + if (tlv == NULL) + return 0; /* erm */ + + return msn_read16be((const char *)tlv->value); +} + +guint32 +msn_tlv_get32(GSList *list, const guint16 type, const int nth) +{ + msn_tlv_t *tlv; + + tlv = msn_tlv_gettlv(list, type, nth); + if (tlv == NULL) + return 0; /* erm */ + + return msn_read32be((const char *)tlv->value); +} + diff --git a/purple/libpurple/protocols/msn/tlv.h b/purple/libpurple/protocols/msn/tlv.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/tlv.h @@ -0,0 +1,75 @@ +/** + * @file tlv.h MSN TLV functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef MSN_TLV_H +#define MSN_TLV_H + +#include "msn.h" + +/* TLV structure */ +typedef struct msn_tlv_s +{ + guint8 type; + guint8 length; + guint8 *value; +} msn_tlv_t; + +/* TLV handling functions */ +char *msn_tlv_getvalue_as_string(msn_tlv_t *tlv); + +msn_tlv_t *msn_tlv_gettlv(GSList *list, const guint16 type, const int nth); +int msn_tlv_getlength(GSList *list, const guint16 type, const int nth); +char *msn_tlv_getstr(GSList *list, const guint16 type, const int nth); +guint8 msn_tlv_get8(GSList *list, const guint16 type, const int nth); +guint16 msn_tlv_get16(GSList *list, const guint16 type, const int nth); +guint32 msn_tlv_get32(GSList *list, const guint16 type, const int nth); + +/* TLV list handling functions */ +GSList *msn_tlvlist_read(char *bs, size_t bs_len); +GSList *msn_tlvlist_copy(GSList *orig); + +int msn_tlvlist_count(GSList *list); +size_t msn_tlvlist_size(GSList *list); +gboolean msn_tlvlist_equal(GSList *one, GSList *two); +int msn_tlvlist_write(char *bs, size_t bs_len, GSList **list); +void msn_tlvlist_free(GSList *list); + +int msn_tlvlist_add_raw(GSList **list, const guint16 type, const guint16 length, const char *value); +int msn_tlvlist_add_empty(GSList **list, const guint16 type); +int msn_tlvlist_add_8(GSList **list, const guint16 type, const guint8 value); +int msn_tlvlist_add_16(GSList **list, const guint16 type, const guint16 value); +int msn_tlvlist_add_32(GSList **list, const guint16 type, const guint32 value); +int msn_tlvlist_add_str(GSList **list, const guint16 type, const char *value); + +int msn_tlvlist_replace_raw(GSList **list, const guint16 type, const guint16 lenth, const char *value); +int msn_tlvlist_replace_str(GSList **list, const guint16 type, const char *str); +int msn_tlvlist_replace_empty(GSList **list, const guint16 type); +int msn_tlvlist_replace_8(GSList **list, const guint16 type, const guint8 value); +int msn_tlvlist_replace_16(GSList **list, const guint16 type, const guint16 value); +int msn_tlvlist_replace_32(GSList **list, const guint16 type, const guint32 value); + +void msn_tlvlist_remove(GSList **list, const guint16 type); + +#endif /* MSN_TLV_H */ + diff --git a/purple/libpurple/protocols/msn/transaction.c b/purple/libpurple/protocols/msn/transaction.c --- a/purple/libpurple/protocols/msn/transaction.c +++ b/purple/libpurple/protocols/msn/transaction.c @@ -16,32 +16,37 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" +#include "debug.h" + #include "msn.h" #include "transaction.h" MsnTransaction * msn_transaction_new(MsnCmdProc *cmdproc, const char *command, const char *format, ...) { MsnTransaction *trans; va_list arg; g_return_val_if_fail(command != NULL, NULL); trans = g_new0(MsnTransaction, 1); trans->cmdproc = cmdproc; trans->command = g_strdup(command); + trans->saveable = TRUE; if (format != NULL) { va_start(arg, format); trans->params = g_strdup_vprintf(format, arg); va_end(arg); } @@ -91,18 +96,20 @@ char * msn_transaction_to_string(MsnTransaction *trans) { char *str; g_return_val_if_fail(trans != NULL, FALSE); if (trans->params != NULL) str = g_strdup_printf("%s %u %s\r\n", trans->command, trans->trId, trans->params); + else if (trans->saveable) + str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId); else - str = g_strdup_printf("%s %u\r\n", trans->command, trans->trId); + str = g_strdup_printf("%s\r\n", trans->command); return str; } void msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd) { purple_debug_info("msn", "queueing command.\n"); @@ -170,16 +177,24 @@ msn_transaction_set_data(MsnTransaction void msn_transaction_set_data_free(MsnTransaction *trans, GDestroyNotify fn) { g_return_if_fail(trans != NULL); trans->data_free = fn; } void +msn_transaction_set_saveable(MsnTransaction *trans, gboolean saveable) +{ + g_return_if_fail(trans != NULL); + + trans->saveable = saveable; +} + +void msn_transaction_add_cb(MsnTransaction *trans, char *answer, MsnTransCb cb) { g_return_if_fail(trans != NULL); g_return_if_fail(answer != NULL); if (trans->callbacks == NULL) { @@ -200,20 +215,21 @@ transaction_timeout(gpointer data) trans = data; g_return_val_if_fail(trans != NULL, FALSE); #if 0 purple_debug_info("msn", "timed out: %s %d %s\n", trans->command, trans->trId, trans->params); #endif + trans->timer = 0; + if (trans->timeout_cb != NULL) trans->timeout_cb(trans->cmdproc, trans); - trans->timer = 0; return FALSE; } void msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb) { if (trans->timer) { diff --git a/purple/libpurple/protocols/msn/transaction.h b/purple/libpurple/protocols/msn/transaction.h --- a/purple/libpurple/protocols/msn/transaction.h +++ b/purple/libpurple/protocols/msn/transaction.h @@ -19,33 +19,37 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #ifndef MSN_TRANSACTION_H #define MSN_TRANSACTION_H +#include "internal.h" + typedef struct _MsnTransaction MsnTransaction; #include "cmdproc.h" #include "command.h" typedef void (*MsnTransCb)(MsnCmdProc *cmdproc, MsnCommand *cmd); typedef void (*MsnTimeoutCb)(MsnCmdProc *cmdproc, MsnTransaction *trans); typedef void (*MsnErrorCb)(MsnCmdProc *cmdproc, MsnTransaction *trans, int error); /** * A transaction. A sending command that will initiate the transaction. */ struct _MsnTransaction { MsnCmdProc *cmdproc; - unsigned int trId; + + gboolean saveable; /**< Whether to save this transaction in the history */ + unsigned int trId; /**< The ID of this transaction, if it's being saved */ char *command; char *params; guint timer; void *data; /**< The data to be used on the different callbacks. */ GDestroyNotify data_free; /**< The function to free 'data', or @c NULL */ @@ -69,14 +73,15 @@ void msn_transaction_destroy(MsnTransact char *msn_transaction_to_string(MsnTransaction *trans); void msn_transaction_queue_cmd(MsnTransaction *trans, MsnCommand *cmd); void msn_transaction_unqueue_cmd(MsnTransaction *trans, MsnCmdProc *cmdproc); void msn_transaction_set_payload(MsnTransaction *trans, const char *payload, int payload_len); void msn_transaction_set_data(MsnTransaction *trans, void *data); void msn_transaction_set_data_free(MsnTransaction *trans, GDestroyNotify fn); +void msn_transaction_set_saveable(MsnTransaction *trans, gboolean saveable); void msn_transaction_add_cb(MsnTransaction *trans, char *answer, MsnTransCb cb); void msn_transaction_set_error_cb(MsnTransaction *trans, MsnErrorCb cb); void msn_transaction_set_timeout_cb(MsnTransaction *trans, MsnTimeoutCb cb); #endif /* MSN_TRANSACTION_H */ diff --git a/purple/libpurple/protocols/msn/user.c b/purple/libpurple/protocols/msn/user.c --- a/purple/libpurple/protocols/msn/user.c +++ b/purple/libpurple/protocols/msn/user.c @@ -16,42 +16,56 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ -#include "msn.h" + +#include "internal.h" +#include "debug.h" +#include "util.h" + #include "user.h" #include "slp.h" +static void free_user_endpoint(MsnUserEndpoint *data) +{ + g_free(data->id); + g_free(data->name); + g_free(data); +} + /*new a user object*/ MsnUser * msn_user_new(MsnUserList *userlist, const char *passport, const char *friendly_name) { MsnUser *user; user = g_new0(MsnUser, 1); user->userlist = userlist; msn_user_set_passport(user, passport); msn_user_set_friendly_name(user, friendly_name); - return user; + return msn_user_ref(user); } /*destroy a user object*/ -void +static void msn_user_destroy(MsnUser *user) { - g_return_if_fail(user != NULL); + while (user->endpoints != NULL) { + free_user_endpoint(user->endpoints->data); + user->endpoints = g_slist_delete_link(user->endpoints, user->endpoints); + } if (user->clientcaps != NULL) g_hash_table_destroy(user->clientcaps); if (user->group_ids != NULL) { GList *l; for (l = user->group_ids; l != NULL; l = l->next) @@ -77,16 +91,37 @@ msn_user_destroy(MsnUser *user) g_free(user->extinfo); } g_free(user->statusline); g_free(user->invite_message); g_free(user); } +MsnUser * +msn_user_ref(MsnUser *user) +{ + g_return_val_if_fail(user != NULL, NULL); + + user->refcount++; + + return user; +} + +void +msn_user_unref(MsnUser *user) +{ + g_return_if_fail(user != NULL); + + user->refcount--; + + if(user->refcount == 0) + msn_user_destroy(user); +} + void msn_user_update(MsnUser *user) { PurpleAccount *account; gboolean offline; g_return_if_fail(user != NULL); @@ -156,16 +191,18 @@ msn_user_set_state(MsnUser *user, const else if (!g_ascii_strcasecmp(state, "BRB")) status = "brb"; else if (!g_ascii_strcasecmp(state, "AWY")) status = "away"; else if (!g_ascii_strcasecmp(state, "PHN")) status = "phone"; else if (!g_ascii_strcasecmp(state, "LUN")) status = "lunch"; + else if (!g_ascii_strcasecmp(state, "HDN")) + status = NULL; else status = "available"; if (!g_ascii_strcasecmp(state, "IDL")) user->idle = TRUE; else user->idle = FALSE; @@ -212,16 +249,73 @@ msn_user_set_uid(MsnUser *user, const ch { g_return_if_fail(user != NULL); g_free(user->uid); user->uid = g_strdup(uid); } void +msn_user_set_endpoint_data(MsnUser *user, const char *input, MsnUserEndpoint *newep) +{ + MsnUserEndpoint *ep; + char *endpoint; + GSList *l; + + g_return_if_fail(user != NULL); + g_return_if_fail(input != NULL); + + endpoint = g_ascii_strdown(input, -1); + + for (l = user->endpoints; l; l = l->next) { + ep = l->data; + if (g_str_equal(ep->id, endpoint)) { + /* We have info about this endpoint! */ + + g_free(endpoint); + + if (newep == NULL) { + /* Delete it and exit */ + user->endpoints = g_slist_delete_link(user->endpoints, l); + free_user_endpoint(ep); + return; + } + + /* Break out of our loop and update it */ + break; + } + } + if (l == NULL) { + /* Need to add a new endpoint */ + ep = g_new0(MsnUserEndpoint, 1); + ep->id = endpoint; + user->endpoints = g_slist_prepend(user->endpoints, ep); + } + + ep->clientid = newep->clientid; + ep->extcaps = newep->extcaps; +} + +void +msn_user_clear_endpoints(MsnUser *user) +{ + MsnUserEndpoint *ep; + GSList *l; + + g_return_if_fail(user != NULL); + + for (l = user->endpoints; l; l = g_slist_delete_link(l, l)) { + ep = l->data; + free_user_endpoint(ep); + } + + user->endpoints = NULL; +} + +void msn_user_set_op(MsnUser *user, MsnListOp list_op) { g_return_if_fail(user != NULL); user->list_op |= list_op; } void @@ -401,35 +495,108 @@ void msn_user_set_clientid(MsnUser *user, guint clientid) { g_return_if_fail(user != NULL); user->clientid = clientid; } void +msn_user_set_extcaps(MsnUser *user, guint extcaps) +{ + g_return_if_fail(user != NULL); + + user->extcaps = extcaps; +} + +void msn_user_set_network(MsnUser *user, MsnNetwork network) { g_return_if_fail(user != NULL); user->networkid = network; } +static gboolean +buddy_icon_cached(PurpleConnection *gc, MsnObject *obj) +{ + PurpleAccount *account; + PurpleBuddy *buddy; + const char *old; + const char *new; + + g_return_val_if_fail(obj != NULL, FALSE); + + account = purple_connection_get_account(gc); + + buddy = purple_find_buddy(account, msn_object_get_creator(obj)); + if (buddy == NULL) + return FALSE; + + old = purple_buddy_icons_get_checksum_for_user(buddy); + new = msn_object_get_sha1(obj); + + if (new == NULL) + return FALSE; + + /* If the old and new checksums are the same, and the file actually exists, + * then return TRUE */ + if (old != NULL && !strcmp(old, new)) + return TRUE; + + return FALSE; +} + +static void +queue_buddy_icon_request(MsnUser *user) +{ + PurpleAccount *account; + MsnObject *obj; + GQueue *queue; + + g_return_if_fail(user != NULL); + + account = user->userlist->session->account; + + obj = msn_user_get_object(user); + + if (obj == NULL) { + purple_buddy_icons_set_for_user(account, user->passport, NULL, 0, NULL); + return; + } + + if (!buddy_icon_cached(account->gc, obj)) { + MsnUserList *userlist; + + userlist = user->userlist; + queue = userlist->buddy_icon_requests; + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "Queueing buddy icon request for %s (buddy_icon_window = %i)\n", + user->passport, userlist->buddy_icon_window); + + g_queue_push_tail(queue, user); + + if (userlist->buddy_icon_window > 0) + msn_release_buddy_icon_request(userlist); + } +} + void msn_user_set_object(MsnUser *user, MsnObject *obj) { g_return_if_fail(user != NULL); - if (user->msnobj != NULL) + if (user->msnobj != NULL && !msn_object_find_local(msn_object_get_sha1(obj))) msn_object_destroy(user->msnobj); user->msnobj = obj; if (user->list_op & MSN_LIST_FL_OP) - msn_queue_buddy_icon_request(user); + queue_buddy_icon_request(user); } void msn_user_set_client_caps(MsnUser *user, GHashTable *info) { g_return_if_fail(user != NULL); g_return_if_fail(info != NULL); @@ -491,16 +658,49 @@ msn_user_get_mobile_phone(const MsnUser guint msn_user_get_clientid(const MsnUser *user) { g_return_val_if_fail(user != NULL, 0); return user->clientid; } +guint +msn_user_get_extcaps(const MsnUser *user) +{ + g_return_val_if_fail(user != NULL, 0); + + return user->extcaps; +} + +MsnUserEndpoint * +msn_user_get_endpoint_data(MsnUser *user, const char *input) +{ + char *endpoint; + GSList *l; + MsnUserEndpoint *ep; + + g_return_val_if_fail(user != NULL, NULL); + g_return_val_if_fail(input != NULL, NULL); + + endpoint = g_ascii_strdown(input, -1); + + for (l = user->endpoints; l; l = l->next) { + ep = l->data; + if (g_str_equal(ep->id, endpoint)) { + g_free(endpoint); + return ep; + } + } + + g_free(endpoint); + + return NULL; +} + MsnObject * msn_user_get_object(const MsnUser *user) { g_return_val_if_fail(user != NULL, NULL); return user->msnobj; } @@ -515,8 +715,76 @@ msn_user_get_client_caps(const MsnUser * const char * msn_user_get_invite_message(const MsnUser *user) { g_return_val_if_fail(user != NULL, NULL); return user->invite_message; } +gboolean +msn_user_is_capable(MsnUser *user, char *endpoint, guint capability, guint extcap) +{ + g_return_val_if_fail(user != NULL, FALSE); + + if (endpoint != NULL) { + MsnUserEndpoint *ep = msn_user_get_endpoint_data(user, endpoint); + if (ep != NULL) + return (ep->clientid & capability) && (ep->extcaps & extcap); + else + return FALSE; + } + + return (user->clientid & capability) && (user->extcaps & extcap); +} + +/************************************************************************** + * Utility functions + **************************************************************************/ + +int +msn_user_passport_cmp(MsnUser *user, const char *passport) +{ + const char *str; + char *pass; + int result; + + str = purple_normalize_nocase(NULL, msn_user_get_passport(user)); + pass = g_strdup(str); + +#if 0 + result = g_strcmp0(pass, purple_normalize_nocase(NULL, passport)); +#else + str = purple_normalize_nocase(NULL, passport); + if (!pass) + result = -(pass != str); + else if (!str) + result = pass != str; + else + result = strcmp(pass, str); +#endif /* GLIB < 2.16.0 */ + + g_free(pass); + + return result; +} + +gboolean +msn_user_is_in_group(MsnUser *user, const char * group_id) +{ + if (user == NULL) + return FALSE; + + if (group_id == NULL) + return FALSE; + + return (g_list_find_custom(user->group_ids, group_id, (GCompareFunc)strcmp)) != NULL; +} + +gboolean +msn_user_is_in_list(MsnUser *user, MsnListId list_id) +{ + if (user == NULL) + return FALSE; + + return (user->list_op & (1 << list_id)); +} + diff --git a/purple/libpurple/protocols/msn/user.h b/purple/libpurple/protocols/msn/user.h --- a/purple/libpurple/protocols/msn/user.h +++ b/purple/libpurple/protocols/msn/user.h @@ -74,20 +74,23 @@ typedef struct _MsnUserExtendedInfo /** * A user. */ struct _MsnUser { MsnUserList *userlist; + guint8 refcount; /**< The reference count of this object */ + char *passport; /**< The passport account. */ char *friendly_name; /**< The friendly name. */ char *uid; /*< User ID */ + GSList *endpoints; /*< Endpoint-specific data */ const char *status; /**< The state of the user. */ char *statusline; /**< The state of the user. */ gboolean idle; /**< The idle state of the user. */ MsnUserExtendedInfo *extinfo; /**< Extended info for the user. */ @@ -97,30 +100,43 @@ struct _MsnUser GList *group_ids; /**< The group IDs. */ char *pending_group; /**< A pending group to add. */ MsnObject *msnobj; /**< The user's MSN Object. */ GHashTable *clientcaps; /**< The client's capabilities. */ guint clientid; /**< The client's ID */ + guint extcaps; /**< The client's extended capabilities */ MsnNetwork networkid; /**< The user's network */ MsnListOp list_op; /**< Which lists the user is in */ /** * The membershipId for this buddy on our pending list. Sent by * the contact's server */ guint member_id_on_pending_list; char *invite_message; /**< Invite message of user request */ }; +/** + * A specific user endpoint. + */ +typedef struct MsnUserEndpoint { + char *id; /**< The client's endpoint ID */ + char *name; /**< The client's endpoint's name */ + int type; /**< The client's endpoint type */ + guint clientid; /**< The client's ID */ + guint extcaps; /**< The client's extended capabilites */ + +} MsnUserEndpoint; + /************************************************************************** ** @name User API * **************************************************************************/ /*@{*/ /** * Creates a new user structure. * @@ -129,22 +145,30 @@ struct _MsnUser * @param stored_name The initial stored name. * * @return A new user structure. */ MsnUser *msn_user_new(MsnUserList *userlist, const char *passport, const char *friendly_name); /** - * Destroys a user structure. + * Increment the reference count. * - * @param user The user to destroy. + * @param user The user. + * + * @return user. */ -void msn_user_destroy(MsnUser *user); +MsnUser *msn_user_ref(MsnUser *user); +/** + * Decrement the reference count. + * + * @param user The user + */ +void msn_user_unref(MsnUser *user); /** * Updates the user. * * Communicates with the core to update the ui, etc. * * @param user The user to update. */ @@ -247,24 +271,50 @@ void msn_user_set_home_phone(MsnUser *us * @param user The user. * @param number The work phone number. */ void msn_user_set_work_phone(MsnUser *user, const char *number); void msn_user_set_uid(MsnUser *user, const char *uid); /** + * Sets endpoint data for a user. + * + * @param user The user. + * @param endpoint The endpoint. + * @param data The endpoint data. + */ +void +msn_user_set_endpoint_data(MsnUser *user, const char *endpoint, MsnUserEndpoint *data); + +/** + * Clears all endpoint data for a user. + * + * @param user The user. + */ +void +msn_user_clear_endpoints(MsnUser *user); + +/** * Sets the client id for a user. * * @param user The user. * @param clientid The client id. */ void msn_user_set_clientid(MsnUser *user, guint clientid); /** + * Sets the client id for a user. + * + * @param user The user. + * @param extcaps The client's extended capabilities. + */ +void msn_user_set_extcaps(MsnUser *user, guint extcaps); + +/** * Sets the network id for a user. * * @param user The user. * @param network The network id. */ void msn_user_set_network(MsnUser *user, MsnNetwork network); /** @@ -341,25 +391,69 @@ const char *msn_user_get_work_phone(cons * * @param user The user. * * @return The user's mobile phone number. */ const char *msn_user_get_mobile_phone(const MsnUser *user); /** + * Gets endpoint data for a user. + * + * @param user The user. + * @param endpoint The endpoint. + * + * @return The user's endpoint data. + */ +MsnUserEndpoint * +msn_user_get_endpoint_data(MsnUser *user, const char *endpoint); + +/** * Returns the client id for a user. * * @param user The user. * * @return The user's client id. */ guint msn_user_get_clientid(const MsnUser *user); /** + * Returns the extended capabilities for a user. + * + * @param user The user. + * + * @return The user's extended capabilities. + */ +guint msn_user_get_extcaps(const MsnUser *user); + +/************************************************************************** + * Utility functions + **************************************************************************/ + + +/** + * Check if the user is part of the group. + * + * @param user The user we are asking group membership. + * @param group_id The group where the user may be in. + * + * @return TRUE if user is part of the group. Otherwise, FALSE. + */ +gboolean msn_user_is_in_group(MsnUser *user, const char * group_id); + +/** + * Check if user is on list. + * + * @param user The user we are asking list membership. + * @param list_id The list where the user may be in. + * + * @return TRUE if the user is on the list, else FALSE. + */ +gboolean msn_user_is_in_list(MsnUser *user, MsnListId list_id); +/** * Returns the network id for a user. * * @param user The user. * * @return The user's network id. */ MsnNetwork msn_user_get_network(const MsnUser *user); @@ -393,16 +487,41 @@ const char *msn_user_get_invite_message( /** * check to see if user is online */ gboolean msn_user_is_online(PurpleAccount *account, const char *name); /** * check to see if user is Yahoo User */ -gboolean msn_user_is_yahoo(PurpleAccount *account ,const char *name); +gboolean msn_user_is_yahoo(PurpleAccount *account, const char *name); void msn_user_set_op(MsnUser *user, MsnListOp list_op); void msn_user_unset_op(MsnUser *user, MsnListOp list_op); +/** + * Compare the given passport with the one of the user + * + * @param user User to compare. + * @oaran passport Passport to compare. + * + * @return Zero if the passport match with the one of the user, otherwise + * a positive integer if the user passport is greather than the one given + * and a negative integer if it is less. + */ +int msn_user_passport_cmp(MsnUser *user, const char *passport); + +/** + * Checks whether a user is capable of some task. + * + * @param user The user. + * @param endpoint The endpoint. Can be @NULL to check overall capabilities. + * @param capability The capability (including client version). + * @param extcap The extended capability. + * + * @return Whether the user supports the capability. + */ +gboolean +msn_user_is_capable(MsnUser *user, char *endpoint, guint capability, guint extcap); + /*@}*/ #endif /* MSN_USER_H */ diff --git a/purple/libpurple/protocols/msn/userlist.c b/purple/libpurple/protocols/msn/userlist.c --- a/purple/libpurple/protocols/msn/userlist.c +++ b/purple/libpurple/protocols/msn/userlist.c @@ -16,17 +16,23 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ + +#include "internal.h" +#include "debug.h" +#include "request.h" + #include "msn.h" +#include "msnutils.h" #include "userlist.h" #include "contact.h" const char *lists[] = { "FL", "AL", "BL", "RL" }; typedef struct { @@ -102,41 +108,16 @@ got_new_entry(PurpleConnection *gc, cons acct = purple_connection_get_account(gc); purple_account_request_authorization(acct, passport, NULL, friendly, message, purple_find_buddy(acct, passport) != NULL, msn_accept_add_cb, msn_cancel_add_cb, pa); } /************************************************************************** - * Utility functions - **************************************************************************/ - -gboolean -msn_userlist_user_is_in_group(MsnUser *user, const char * group_id) -{ - if (user == NULL) - return FALSE; - - if (group_id == NULL) - return FALSE; - - return (g_list_find_custom(user->group_ids, group_id, (GCompareFunc)strcmp)) != NULL; -} - -gboolean -msn_userlist_user_is_in_list(MsnUser *user, MsnListId list_id) -{ - if (user == NULL) - return FALSE; - - return (user->list_op & (1 << list_id)); -} - -/************************************************************************** * Server functions **************************************************************************/ void msn_got_lst_user(MsnSession *session, MsnUser *user, MsnListOp list_op, GSList *group_ids) { PurpleConnection *gc; @@ -230,17 +211,17 @@ msn_userlist_new(MsnSession *session) void msn_userlist_destroy(MsnUserList *userlist) { GList *l; /*destroy userlist*/ for (l = userlist->users; l != NULL; l = l->next) { - msn_user_destroy(l->data); + msn_user_unref(l->data); } g_list_free(userlist->users); /*destroy group list*/ for (l = userlist->groups; l != NULL; l = l->next) { msn_group_destroy(l->data); } @@ -498,17 +479,17 @@ msn_userlist_rem_buddy_from_list(MsnUser MsnUser *user; const gchar *list; MsnListOp list_op = 1 << list_id; user = msn_userlist_find_user(userlist, who); g_return_if_fail(user != NULL); - if ( !msn_userlist_user_is_in_list(user, list_id)) { + if ( !msn_user_is_in_list(user, list_id)) { list = lists[list_id]; purple_debug_info("msn", "User %s is not in list %s, not removing.\n", who, list); return; } msn_user_unset_op(user, list_op); msn_notification_rem_buddy_from_list(userlist->session->notification, list_id, user); @@ -564,23 +545,23 @@ msn_userlist_add_buddy(MsnUserList *user } /* XXX: adding user here may not be correct (should add them in the * ACK to the ADL command), but for now we need to make sure they exist * early enough that the ILN command doesn't screw us up */ user = msn_userlist_find_add_user(userlist, who, who); - if ( msn_userlist_user_is_in_list(user, MSN_LIST_FL) ) { + if ( msn_user_is_in_list(user, MSN_LIST_FL) ) { purple_debug_info("msn", "User %s already exists\n", who); msn_userlist_rem_buddy_from_list(userlist, who, MSN_LIST_BL); - if (msn_userlist_user_is_in_group(user, group_id)) { + if (msn_user_is_in_group(user, group_id)) { purple_debug_info("msn", "User %s is already in group %s, returning\n", who, new_group_name); msn_callback_state_free(state); return; } } purple_debug_info("msn", "Adding user: %s to group id: %s\n", who, group_id); @@ -599,17 +580,17 @@ msn_userlist_add_buddy_to_list(MsnUserLi const gchar *list; MsnListOp list_op = 1 << list_id; g_return_if_fail(userlist != NULL); user = msn_userlist_find_add_user(userlist, who, who); /* First we're going to check if it's already there. */ - if (msn_userlist_user_is_in_list(user, list_id)) + if (msn_user_is_in_list(user, list_id)) { list = lists[list_id]; purple_debug_info("msn", "User '%s' is already in list: %s\n", who, list); return; } /* XXX: see XXX above, this should really be done when we get the response from the server */ @@ -701,16 +682,47 @@ msn_userlist_move_buddy(MsnUserList *use } /* add the contact to the new group, and remove it from the old one in * the callback */ msn_add_contact_to_group(userlist->session, state, who, new_group_id); } +void +msn_release_buddy_icon_request(MsnUserList *userlist) +{ + MsnUser *user; + + g_return_if_fail(userlist != NULL); + + if (purple_debug_is_verbose()) + purple_debug_info("msn", "Releasing buddy icon request\n"); + + if (userlist->buddy_icon_window > 0) { + GQueue *queue; + + queue = userlist->buddy_icon_requests; + + if (g_queue_is_empty(userlist->buddy_icon_requests)) + return; + + user = g_queue_pop_head(queue); + + userlist->buddy_icon_window--; + + msn_request_user_display(user); + + if (purple_debug_is_verbose()) + purple_debug_info("msn", + "msn_release_buddy_icon_request(): buddy_icon_window-- yields =%d\n", + userlist->buddy_icon_window); + } +} + /*load userlist from the Blist file cache*/ void msn_userlist_load(MsnSession *session) { PurpleAccount *account = session->account; PurpleConnection *gc = purple_account_get_connection(account); GSList *l; MsnUser * user; diff --git a/purple/libpurple/protocols/msn/userlist.h b/purple/libpurple/protocols/msn/userlist.h --- a/purple/libpurple/protocols/msn/userlist.h +++ b/purple/libpurple/protocols/msn/userlist.h @@ -30,16 +30,26 @@ typedef enum { MSN_LIST_FL, /**< Forward list */ MSN_LIST_AL, /**< Allow list */ MSN_LIST_BL, /**< Block list */ MSN_LIST_RL, /**< Reverse list */ MSN_LIST_PL /**< Pending list */ } MsnListId; +typedef enum +{ + MSN_LIST_FL_OP = 0x01, + MSN_LIST_AL_OP = 0x02, + MSN_LIST_BL_OP = 0x04, + MSN_LIST_RL_OP = 0x08, + MSN_LIST_PL_OP = 0x10 +} MsnListOp; +#define MSN_LIST_OP_MASK 0x07 + #include "group.h" #include "msn.h" #include "user.h" struct _MsnUserList { MsnSession *session; @@ -47,19 +57,16 @@ struct _MsnUserList GList *groups; /* Contains MsnGroups */ GQueue *buddy_icon_requests; int buddy_icon_window; guint buddy_icon_request_timer; }; -gboolean msn_userlist_user_is_in_group(MsnUser *user, const char * group_id); -gboolean msn_userlist_user_is_in_list(MsnUser *user, MsnListId list_id); - void msn_got_lst_user(MsnSession *session, MsnUser *user, MsnListOp list_op, GSList *group_ids); MsnUserList *msn_userlist_new(MsnSession *session); void msn_userlist_destroy(MsnUserList *userlist); void msn_userlist_add_user(MsnUserList *userlist, MsnUser *user); void msn_userlist_remove_user(MsnUserList *userlist, MsnUser *user); @@ -93,12 +100,13 @@ gboolean msn_userlist_add_buddy_to_group gboolean msn_userlist_rem_buddy_from_group(MsnUserList *userlist, const char *who, const char *group_name); void msn_userlist_add_buddy_to_list(MsnUserList *userlist, const char *who, MsnListId list_id); void msn_userlist_rem_buddy_from_list(MsnUserList *userlist, const char *who, MsnListId list_id); +void msn_release_buddy_icon_request(MsnUserList *userlist); void msn_userlist_load(MsnSession *session); #endif /* MSN_USERLIST_H */ diff --git a/purple/libpurple/protocols/msn/xfer.c b/purple/libpurple/protocols/msn/xfer.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/xfer.c @@ -0,0 +1,235 @@ +/** + * @file xfer.c MSN File Transfer functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include "internal.h" +#include "debug.h" + +#include "msnutils.h" +#include "sbconn.h" +#include "xfer.h" + +/************************************************************************** + * Xfer + **************************************************************************/ + +void +msn_xfer_init(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + /* MsnSlpLink *slplink; */ + char *content; + + purple_debug_info("msn", "xfer_init\n"); + + slpcall = xfer->data; + + /* Send Ok */ + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + msn_slp_send_ok(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + msn_slplink_send_queued_slpmsgs(slpcall->slplink); +} + +void +msn_xfer_cancel(PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + char *content; + + g_return_if_fail(xfer != NULL); + g_return_if_fail(xfer->data != NULL); + + slpcall = xfer->data; + + if (purple_xfer_get_status(xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) + { + if (slpcall->started) + { + msn_slpcall_close(slpcall); + } + else + { + content = g_strdup_printf("SessionID: %lu\r\n\r\n", + slpcall->session_id); + + msn_slp_send_decline(slpcall, slpcall->branch, "application/x-msnmsgr-sessionreqbody", + content); + + g_free(content); + msn_slplink_send_queued_slpmsgs(slpcall->slplink); + + if (purple_xfer_get_type(xfer) == PURPLE_XFER_SEND) + slpcall->wasted = TRUE; + else + msn_slpcall_destroy(slpcall); + } + } +} + +gssize +msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + g_return_val_if_fail(len > 0, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND, -1); + + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); + + g_return_val_if_fail(slpcall->xfer_msg != NULL, -1); + + slpcall->u.outgoing.len = len; + slpcall->u.outgoing.data = data; + msn_slplink_send_msgpart(slpcall->slplink, slpcall->xfer_msg); + + return MIN(MSN_SBCONN_MAX_SIZE, len); +} + +gssize +msn_xfer_read(guchar **data, PurpleXfer *xfer) +{ + MsnSlpCall *slpcall; + gsize len; + + g_return_val_if_fail(xfer != NULL, -1); + g_return_val_if_fail(data != NULL, -1); + + g_return_val_if_fail(purple_xfer_get_type(xfer) == PURPLE_XFER_RECEIVE, -1); + + slpcall = xfer->data; + /* Not sure I trust it'll be there */ + g_return_val_if_fail(slpcall != NULL, -1); + + /* Just pass up the whole GByteArray. We'll make another. */ + *data = slpcall->u.incoming_data->data; + len = slpcall->u.incoming_data->len; + + g_byte_array_free(slpcall->u.incoming_data, FALSE); + slpcall->u.incoming_data = g_byte_array_new(); + + return len; +} + +void +msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session) +{ + if ((purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_DONE) && + (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_REMOTE) && + (purple_xfer_get_status(slpcall->xfer) != PURPLE_XFER_STATUS_CANCEL_LOCAL)) + { + purple_xfer_cancel_remote(slpcall->xfer); + } +} + +void +msn_xfer_completed_cb(MsnSlpCall *slpcall, const guchar *body, + gsize size) +{ + PurpleXfer *xfer = slpcall->xfer; + + purple_xfer_set_completed(xfer, TRUE); + purple_xfer_end(xfer); +} + +gchar * +msn_file_context_to_wire(MsnFileContext *header) +{ + gchar *ret, *tmp; + + tmp = ret = g_new(gchar, MSN_FILE_CONTEXT_SIZE + header->preview_len + 1); + + msn_push32le(tmp, header->length); + msn_push32le(tmp, header->version); + msn_push64le(tmp, header->file_size); + msn_push32le(tmp, header->type); + memcpy(tmp, header->file_name, MAX_FILE_NAME_LEN * 2); + tmp += MAX_FILE_NAME_LEN * 2; + memcpy(tmp, header->unknown1, sizeof(header->unknown1)); + tmp += sizeof(header->unknown1); + msn_push32le(tmp, header->unknown2); + if (header->preview) { + memcpy(tmp, header->preview, header->preview_len); + } + tmp[header->preview_len] = '\0'; + + return ret; +} + +MsnFileContext * +msn_file_context_from_wire(const char *buf, gsize len) +{ + MsnFileContext *header; + + if (!buf || len < MSN_FILE_CONTEXT_SIZE) + return NULL; + + header = g_new(MsnFileContext, 1); + + header->length = msn_pop32le(buf); + header->version = msn_pop32le(buf); + if (header->version == 2) { + /* The length field is broken for this version. No check. */ + header->length = MSN_FILE_CONTEXT_SIZE; + } else if (header->version == 3) { + if (header->length != MSN_FILE_CONTEXT_SIZE + 63) { + g_free(header); + return NULL; + } else if (len < MSN_FILE_CONTEXT_SIZE + 63) { + g_free(header); + return NULL; + } + } else { + purple_debug_warning("msn", "Received MsnFileContext with unknown version: %d\n", header->version); + g_free(header); + return NULL; + } + + header->file_size = msn_pop64le(buf); + header->type = msn_pop32le(buf); + memcpy(header->file_name, buf, MAX_FILE_NAME_LEN * 2); + buf += MAX_FILE_NAME_LEN * 2; + memcpy(header->unknown1, buf, sizeof(header->unknown1)); + buf += sizeof(header->unknown1); + header->unknown2 = msn_pop32le(buf); + + if (header->type == 0 && len > header->length) { + header->preview_len = len - header->length; + header->preview = g_memdup(buf, header->preview_len); + } else { + header->preview_len = 0; + header->preview = NULL; + } + + return header; +} + diff --git a/purple/libpurple/protocols/msn/xfer.h b/purple/libpurple/protocols/msn/xfer.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/msn/xfer.h @@ -0,0 +1,63 @@ +/** + * @file xfer.h MSN File Transfer functions + * + * purple + * + * Purple is the legal property of its developers, whose names are too numerous + * to list here. Please refer to the COPYRIGHT file distributed with this + * source distribution. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + + +#include "slpcall.h" + +#define MAX_FILE_NAME_LEN 260 /* MAX_PATH in Windows */ + +/** + * The context data for a file transfer request + */ +typedef struct +{ + guint32 length; /*< Length of header */ + guint32 version; /*< MSN version */ + guint64 file_size; /*< Size of file */ + guint32 type; /*< Transfer type */ + gunichar2 file_name[MAX_FILE_NAME_LEN]; /*< Self-explanatory */ + gchar unknown1[30]; /*< Used somehow for background sharing */ + guint32 unknown2; /*< Possibly for background sharing as well */ + gchar *preview; /*< File preview data, 96x96 PNG */ + gsize preview_len; +} MsnFileContext; + +#define MSN_FILE_CONTEXT_SIZE (4*4 + 1*8 + 2*MAX_FILE_NAME_LEN + 30) + +void msn_xfer_init(PurpleXfer *xfer); +void msn_xfer_cancel(PurpleXfer *xfer); + +gssize msn_xfer_write(const guchar *data, gsize len, PurpleXfer *xfer); +gssize msn_xfer_read(guchar **data, PurpleXfer *xfer); + +void msn_xfer_completed_cb(MsnSlpCall *slpcall, + const guchar *body, gsize size); +void msn_xfer_end_cb(MsnSlpCall *slpcall, MsnSession *session); + +gchar * +msn_file_context_to_wire(MsnFileContext *header); + +MsnFileContext * +msn_file_context_from_wire(const char *buf, gsize len); + diff --git a/purple/libpurple/protocols/myspace/message.c b/purple/libpurple/protocols/myspace/message.c --- a/purple/libpurple/protocols/myspace/message.c +++ b/purple/libpurple/protocols/myspace/message.c @@ -1358,17 +1358,17 @@ msim_msg_get_binary_from_element(MsimMes * msim_msg_get_binary() sees MSIM_TYPE_STRING, base64 decodes, returns. * everything is fine. * But then, msim_send() is called on the incoming message, which has * a base64'd MSIM_TYPE_STRING that really is encoded binary. The values * will be escaped since strings are escaped, and / becomes /2; no good. * */ *binary_data = (gchar *)purple_base64_decode((const gchar *)elem->data, binary_length); - return TRUE; + return ((*binary_data) != NULL); case MSIM_TYPE_BINARY: gs = (GString *)elem->data; /* Duplicate data, so caller can g_free() it. */ *binary_data = g_memdup(gs->str, gs->len); *binary_length = gs->len; diff --git a/purple/libpurple/protocols/oscar/Makefile.in b/purple/libpurple/protocols/oscar/Makefile.in --- a/purple/libpurple/protocols/oscar/Makefile.in +++ b/purple/libpurple/protocols/oscar/Makefile.in @@ -38,48 +38,48 @@ DEPTH = ../../../.. topsrcdir = @top_srcdir@ srcdir = @srcdir@ VPATH = @srcdir@ PROTOCOL = oscar include $(srcdir)/../prpl.mk CSRCS = \ + authorization.c \ bstream.c \ clientlogin.c \ + encoding.c \ family_admin.c \ - family_advert.c \ family_alert.c \ family_auth.c \ family_bart.c \ family_bos.c \ family_buddy.c \ family_chat.c \ family_chatnav.c \ family_icq.c \ family_icbm.c \ - family_invite.c \ family_locate.c \ - family_odir.c \ family_oservice.c \ family_popup.c \ family_feedbag.c \ family_stats.c \ - family_translate.c \ family_userlookup.c \ flap_connection.c \ misc.c \ msgcookie.c \ odc.c \ oft.c \ oscar.c \ oscar_data.c \ peer.c \ peer_proxy.c \ rxhandlers.c \ snac.c \ tlv.c \ + userinfo.c \ util.c \ + visibility.c \ libaim.c \ libicq.c \ $(NULL) include $(srcdir)/../prpl-rules.mk diff --git a/purple/libpurple/protocols/oscar/authorization.c b/purple/libpurple/protocols/oscar/authorization.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/authorization.c @@ -0,0 +1,161 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +/* + * Everything related to OSCAR authorization requests. + */ + +#include "oscar.h" +#include "request.h" + +static void +oscar_auth_request(struct name_data *data, char *msg) +{ + PurpleConnection *gc; + OscarData *od; + PurpleAccount *account; + PurpleBuddy *buddy; + PurpleGroup *group; + const char *bname, *gname; + + gc = data->gc; + od = purple_connection_get_protocol_data(gc); + account = purple_connection_get_account(gc); + buddy = purple_find_buddy(account, data->name); + if (buddy != NULL) + group = purple_buddy_get_group(buddy); + else + group = NULL; + + if (group != NULL) + { + bname = purple_buddy_get_name(buddy); + gname = purple_group_get_name(group); + purple_debug_info("oscar", "ssi: adding buddy %s to group %s\n", + bname, gname); + aim_ssi_sendauthrequest(od, data->name, msg ? msg : _("Please authorize me so I can add you to my buddy list.")); + if (!aim_ssi_itemlist_finditem(od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY)) + { + aim_ssi_addbuddy(od, bname, gname, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, TRUE); + + /* Mobile users should always be online */ + if (bname[0] == '+') { + purple_prpl_got_user_status(account, + purple_buddy_get_name(buddy), + OSCAR_STATUS_ID_AVAILABLE, NULL); + purple_prpl_got_user_status(account, + purple_buddy_get_name(buddy), + OSCAR_STATUS_ID_MOBILE, NULL); + } + } + } + + oscar_free_name_data(data); +} + +static void +oscar_auth_grant(gpointer cbdata) +{ + struct name_data *data = cbdata; + PurpleConnection *gc = data->gc; + OscarData *od = purple_connection_get_protocol_data(gc); + + aim_ssi_sendauthreply(od, data->name, 0x01, NULL); + + oscar_free_name_data(data); +} + +static void +oscar_auth_dontgrant(struct name_data *data, char *msg) +{ + PurpleConnection *gc = data->gc; + OscarData *od = purple_connection_get_protocol_data(gc); + + aim_ssi_sendauthreply(od, data->name, 0x00, msg ? msg : _("No reason given.")); + + oscar_free_name_data(data); +} + +static void +oscar_auth_dontgrant_msgprompt(gpointer cbdata) +{ + struct name_data *data = cbdata; +#if 0 + purple_request_input(data->gc, NULL, _("Authorization Denied Message:"), + NULL, _("No reason given."), TRUE, FALSE, NULL, + _("_OK"), G_CALLBACK(oscar_auth_dontgrant), + _("_Cancel"), G_CALLBACK(oscar_free_name_data), + purple_connection_get_account(data->gc), data->name, NULL, + data); +#else + oscar_auth_dontgrant(data, NULL); +#endif +} + +/* When you ask other people for authorization */ +void +oscar_auth_sendrequest(PurpleConnection *gc, const char *name) +{ + struct name_data *data; + + data = g_new0(struct name_data, 1); + data->gc = gc; + data->name = g_strdup(name); + +#if 0 + purple_request_input(data->gc, NULL, _("Authorization Request Message:"), + NULL, _("Please authorize me!"), TRUE, FALSE, NULL, + _("_OK"), G_CALLBACK(oscar_auth_request), + _("_Cancel"), G_CALLBACK(oscar_free_name_data), + purple_connection_get_account(gc), name, NULL, + data); +#else + oscar_auth_request(data, NULL); +#endif +} + +void +oscar_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored) +{ + PurpleBuddy *buddy; + PurpleConnection *gc; + + g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); + + buddy = (PurpleBuddy *) node; + gc = purple_account_get_connection(purple_buddy_get_account(buddy)); + oscar_auth_sendrequest(gc, purple_buddy_get_name(buddy)); +} + +/* When other people ask you for authorization */ +void +oscar_auth_recvrequest(PurpleConnection *gc, gchar *name, gchar *nick, gchar *reason) +{ + PurpleAccount* account = purple_connection_get_account(gc); + struct name_data *data = g_new(struct name_data, 1); + + data->gc = gc; + data->name = name; + data->nick = nick; + + purple_account_request_authorization(account, data->name, NULL, data->nick, + reason, purple_find_buddy(account, data->name) != NULL, + oscar_auth_grant, oscar_auth_dontgrant_msgprompt, data); +} diff --git a/purple/libpurple/protocols/oscar/bstream.c b/purple/libpurple/protocols/oscar/bstream.c --- a/purple/libpurple/protocols/oscar/bstream.c +++ b/purple/libpurple/protocols/oscar/bstream.c @@ -19,296 +19,233 @@ */ /* * This file contains all functions needed to use bstreams. */ #include "oscar.h" -int byte_stream_new(ByteStream *bs, guint32 len) +int byte_stream_new(ByteStream *bs, size_t len) { if (bs == NULL) return -1; return byte_stream_init(bs, g_malloc(len), len); } -int byte_stream_init(ByteStream *bs, guint8 *data, int len) +int byte_stream_init(ByteStream *bs, guint8 *data, size_t len) { - if (bs == NULL) return -1; bs->data = data; bs->len = len; bs->offset = 0; return 0; } void byte_stream_destroy(ByteStream *bs) { g_free(bs->data); } -int byte_stream_empty(ByteStream *bs) +int byte_stream_bytes_left(ByteStream *bs) { return bs->len - bs->offset; } int byte_stream_curpos(ByteStream *bs) { return bs->offset; } -int byte_stream_setpos(ByteStream *bs, unsigned int off) +int byte_stream_setpos(ByteStream *bs, size_t off) { - - if (off > bs->len) - return -1; + g_return_val_if_fail(off <= bs->len, -1); bs->offset = off; - return off; } void byte_stream_rewind(ByteStream *bs) { - byte_stream_setpos(bs, 0); - - return; } /* * N can be negative, which can be used for going backwards - * in a bstream. I'm not sure if libfaim actually does - * this anywhere... + * in a bstream. */ int byte_stream_advance(ByteStream *bs, int n) { - - if ((byte_stream_curpos(bs) + n < 0) || (byte_stream_empty(bs) < n)) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_curpos(bs) + n >= 0, 0); + g_return_val_if_fail(n <= byte_stream_bytes_left(bs), 0); bs->offset += n; - return n; } guint8 byte_stream_get8(ByteStream *bs) { - - if (byte_stream_empty(bs) < 1) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 1, 0); bs->offset++; - return aimutil_get8(bs->data + bs->offset - 1); } guint16 byte_stream_get16(ByteStream *bs) { - - if (byte_stream_empty(bs) < 2) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 2, 0); bs->offset += 2; - return aimutil_get16(bs->data + bs->offset - 2); } guint32 byte_stream_get32(ByteStream *bs) { - - if (byte_stream_empty(bs) < 4) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 4, 0); bs->offset += 4; - return aimutil_get32(bs->data + bs->offset - 4); } guint8 byte_stream_getle8(ByteStream *bs) { - - if (byte_stream_empty(bs) < 1) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 1, 0); bs->offset++; - return aimutil_getle8(bs->data + bs->offset - 1); } guint16 byte_stream_getle16(ByteStream *bs) { - - if (byte_stream_empty(bs) < 2) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 2, 0); bs->offset += 2; - return aimutil_getle16(bs->data + bs->offset - 2); } guint32 byte_stream_getle32(ByteStream *bs) { - - if (byte_stream_empty(bs) < 4) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 4, 0); bs->offset += 4; - return aimutil_getle32(bs->data + bs->offset - 4); } -static void byte_stream_getrawbuf_nocheck(ByteStream *bs, guint8 *buf, int len) +static void byte_stream_getrawbuf_nocheck(ByteStream *bs, guint8 *buf, size_t len) { memcpy(buf, bs->data + bs->offset, len); bs->offset += len; } -int byte_stream_getrawbuf(ByteStream *bs, guint8 *buf, int len) +int byte_stream_getrawbuf(ByteStream *bs, guint8 *buf, size_t len) { - - if (byte_stream_empty(bs) < len) - return 0; + g_return_val_if_fail(byte_stream_bytes_left(bs) >= len, 0); byte_stream_getrawbuf_nocheck(bs, buf, len); return len; } -guint8 *byte_stream_getraw(ByteStream *bs, int len) +guint8 *byte_stream_getraw(ByteStream *bs, size_t len) { guint8 *ob; - if (byte_stream_empty(bs) < len) - return NULL; + g_return_val_if_fail(byte_stream_bytes_left(bs) >= len, NULL); ob = g_malloc(len); - byte_stream_getrawbuf_nocheck(bs, ob, len); - return ob; } -char *byte_stream_getstr(ByteStream *bs, int len) +char *byte_stream_getstr(ByteStream *bs, size_t len) { char *ob; - if (byte_stream_empty(bs) < len) - return NULL; + g_return_val_if_fail(byte_stream_bytes_left(bs) >= len, NULL); ob = g_malloc(len + 1); - byte_stream_getrawbuf_nocheck(bs, (guint8 *)ob, len); - ob[len] = '\0'; - return ob; } int byte_stream_put8(ByteStream *bs, guint8 v) { - - if (byte_stream_empty(bs) < 1) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 1, 0); bs->offset += aimutil_put8(bs->data + bs->offset, v); - return 1; } int byte_stream_put16(ByteStream *bs, guint16 v) { - - if (byte_stream_empty(bs) < 2) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 2, 0); bs->offset += aimutil_put16(bs->data + bs->offset, v); - return 2; } int byte_stream_put32(ByteStream *bs, guint32 v) { - - if (byte_stream_empty(bs) < 4) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 4, 0); bs->offset += aimutil_put32(bs->data + bs->offset, v); - return 1; } int byte_stream_putle8(ByteStream *bs, guint8 v) { - - if (byte_stream_empty(bs) < 1) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 1, 0); bs->offset += aimutil_putle8(bs->data + bs->offset, v); - return 1; } int byte_stream_putle16(ByteStream *bs, guint16 v) { - - if (byte_stream_empty(bs) < 2) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 2, 0); bs->offset += aimutil_putle16(bs->data + bs->offset, v); - return 2; } int byte_stream_putle32(ByteStream *bs, guint32 v) { - - if (byte_stream_empty(bs) < 4) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= 4, 0); bs->offset += aimutil_putle32(bs->data + bs->offset, v); - return 1; } -int byte_stream_putraw(ByteStream *bs, const guint8 *v, int len) +int byte_stream_putraw(ByteStream *bs, const guint8 *v, size_t len) { - - if (byte_stream_empty(bs) < len) - return 0; /* XXX throw an exception */ + g_return_val_if_fail(byte_stream_bytes_left(bs) >= len, 0); memcpy(bs->data + bs->offset, v, len); bs->offset += len; - return len; } int byte_stream_putstr(ByteStream *bs, const char *str) { return byte_stream_putraw(bs, (guint8 *)str, strlen(str)); } -int byte_stream_putbs(ByteStream *bs, ByteStream *srcbs, int len) +int byte_stream_putbs(ByteStream *bs, ByteStream *srcbs, size_t len) { - - if (byte_stream_empty(srcbs) < len) - return 0; /* XXX throw exception (underrun) */ - - if (byte_stream_empty(bs) < len) - return 0; /* XXX throw exception (overflow) */ + g_return_val_if_fail(byte_stream_bytes_left(srcbs) >= len, 0); + g_return_val_if_fail(byte_stream_bytes_left(bs) >= len, 0); memcpy(bs->data + bs->offset, srcbs->data + srcbs->offset, len); bs->offset += len; srcbs->offset += len; - return len; } int byte_stream_putuid(ByteStream *bs, OscarData *od) { PurpleAccount *account; account = purple_connection_get_account(od->gc); diff --git a/purple/libpurple/protocols/oscar/clientlogin.c b/purple/libpurple/protocols/oscar/clientlogin.c --- a/purple/libpurple/protocols/oscar/clientlogin.c +++ b/purple/libpurple/protocols/oscar/clientlogin.c @@ -37,18 +37,46 @@ */ #include "oscar.h" #include "oscarcommon.h" #include "cipher.h" #include "core.h" -#define URL_CLIENT_LOGIN "https://api.screenname.aol.com/auth/clientLogin" -#define URL_START_OSCAR_SESSION "https://api.oscar.aol.com/aim/startOSCARSession" +#define AIM_LOGIN_HOST "api.screenname.aol.com" +#define ICQ_LOGIN_HOST "api.login.icq.net" + +#define AIM_API_HOST "api.oscar.aol.com" +#define ICQ_API_HOST "api.icq.net" + +#define CLIENT_LOGIN_PAGE "/auth/clientLogin" +#define START_OSCAR_SESSION_PAGE "/aim/startOSCARSession" + +#define HTTPS_FORMAT_URL(host, page) "https://" host page + +static const gchar *client_login_urls[] = { + HTTPS_FORMAT_URL(AIM_LOGIN_HOST, CLIENT_LOGIN_PAGE), + HTTPS_FORMAT_URL(ICQ_LOGIN_HOST, CLIENT_LOGIN_PAGE), +}; + +static const gchar *start_oscar_session_urls[] = { + HTTPS_FORMAT_URL(AIM_API_HOST, START_OSCAR_SESSION_PAGE), + HTTPS_FORMAT_URL(ICQ_API_HOST, START_OSCAR_SESSION_PAGE), +}; + +static const gchar *get_client_login_url(OscarData *od) +{ + return client_login_urls[od->icq ? 1 : 0]; +} + +static const gchar *get_start_oscar_session_url(OscarData *od) +{ + return start_oscar_session_urls[od->icq ? 1 : 0]; +} /* * Using clientLogin requires a developer ID. This key is for libpurple. * It is the default key for all libpurple-based clients. AOL encourages * UIs (especially ones with lots of users) to override this with their * own key. This key is owned by the AIM account "markdoliner" * * Keys can be managed at http://developer.aim.com/manageKeys.jsp @@ -60,20 +88,32 @@ static const char *get_client_key(OscarD return oscar_get_ui_info_string( od->icq ? "prpl-icq-clientkey" : "prpl-aim-clientkey", DEFAULT_CLIENT_KEY); } static gchar *generate_error_message(xmlnode *resp, const char *url) { xmlnode *text; + xmlnode *status_code_node; + gchar *status_code; + gboolean have_error_code = TRUE; gchar *err = NULL; gchar *details = NULL; - if (resp && (text = xmlnode_get_child(resp, "statusText"))) { + status_code_node = xmlnode_get_child(resp, "statusCode"); + if (status_code_node) { + /* We can get 200 OK here if the server omitted something we think it shouldn't have (see #12783). + * No point in showing the "Ok" string to the user. + */ + if ((status_code = xmlnode_get_data_unescaped(status_code_node)) && strcmp(status_code, "200") == 0) { + have_error_code = FALSE; + } + } + if (have_error_code && resp && (text = xmlnode_get_child(resp, "statusText"))) { details = xmlnode_get_data(text); } if (details && *details) { err = g_strdup_printf(_("Received unexpected response from %s: %s"), url, details); } else { err = g_strdup_printf(_("Received unexpected response from %s"), url); } @@ -120,57 +160,55 @@ static gchar *generate_signature(const c signature = hmac_sha256(session_key, signature_base_string); g_free(signature_base_string); return signature; } static gboolean parse_start_oscar_session_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **host, unsigned short *port, char **cookie, char **tls_certname) { + OscarData *od = purple_connection_get_protocol_data(gc); xmlnode *response_node, *tmp_node, *data_node; xmlnode *host_node = NULL, *port_node = NULL, *cookie_node = NULL, *tls_node = NULL; - gboolean use_tls; char *tmp; guint code; - - use_tls = purple_account_get_bool(purple_connection_get_account(gc), "use_ssl", OSCAR_DEFAULT_USE_SSL); + const gchar *encryption_type = purple_account_get_string(purple_connection_get_account(gc), "encryption", OSCAR_DEFAULT_ENCRYPTION); /* Parse the response as XML */ response_node = xmlnode_from_str(response, response_len); if (response_node == NULL) { char *msg; purple_debug_error("oscar", "startOSCARSession could not parse " "response as XML: %s\n", response); /* Note to translators: %s in this string is a URL */ msg = generate_error_message(response_node, - URL_START_OSCAR_SESSION); + get_start_oscar_session_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); return FALSE; } /* Grab the necessary XML nodes */ tmp_node = xmlnode_get_child(response_node, "statusCode"); data_node = xmlnode_get_child(response_node, "data"); if (data_node != NULL) { host_node = xmlnode_get_child(data_node, "host"); port_node = xmlnode_get_child(data_node, "port"); cookie_node = xmlnode_get_child(data_node, "cookie"); - tls_node = xmlnode_get_child(data_node, "tlsCertName"); } /* Make sure we have a status code */ if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) { char *msg; purple_debug_error("oscar", "startOSCARSession response was " "missing statusCode: %s\n", response); msg = generate_error_message(response_node, - URL_START_OSCAR_SESSION); + get_start_oscar_session_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); xmlnode_free(response_node); return FALSE; } /* Make sure the status code was 200 */ @@ -198,61 +236,74 @@ static gboolean parse_start_oscar_sessio PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("You have been connecting and disconnecting too " "frequently. Wait ten minutes and try again. If " "you continue to try, you will need to wait even " "longer.")); else { char *msg; msg = generate_error_message(response_node, - URL_START_OSCAR_SESSION); + get_start_oscar_session_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg); g_free(msg); } g_free(tmp); xmlnode_free(response_node); return FALSE; } g_free(tmp); /* Make sure we have everything else */ - if (data_node == NULL || host_node == NULL || - port_node == NULL || cookie_node == NULL || - (use_tls && tls_node == NULL)) + if (data_node == NULL || host_node == NULL || port_node == NULL || cookie_node == NULL) { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " "something: %s\n", response); msg = generate_error_message(response_node, - URL_START_OSCAR_SESSION); + get_start_oscar_session_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); xmlnode_free(response_node); return FALSE; } + if (strcmp(encryption_type, OSCAR_NO_ENCRYPTION) != 0) { + tls_node = xmlnode_get_child(data_node, "tlsCertName"); + if (tls_node != NULL) { + *tls_certname = xmlnode_get_data_unescaped(tls_node); + } else { + if (strcmp(encryption_type, OSCAR_OPPORTUNISTIC_ENCRYPTION) == 0) { + purple_debug_warning("oscar", "We haven't received a tlsCertName to use. We will not do SSL to BOS.\n"); + } else { + purple_debug_error("oscar", "startOSCARSession was missing tlsCertName: %s\n", response); + purple_connection_error_reason( + gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("You required encryption in your account settings, but one of the servers doesn't support it.")); + xmlnode_free(response_node); + return FALSE; + } + } + } + /* Extract data from the XML */ *host = xmlnode_get_data_unescaped(host_node); tmp = xmlnode_get_data_unescaped(port_node); *cookie = xmlnode_get_data_unescaped(cookie_node); - if (use_tls) - *tls_certname = xmlnode_get_data_unescaped(tls_node); - - if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0' || - (use_tls && (*tls_certname == NULL || **tls_certname == '\0'))) + if (*host == NULL || **host == '\0' || tmp == NULL || *tmp == '\0' || *cookie == NULL || **cookie == '\0') { char *msg; purple_debug_error("oscar", "startOSCARSession response was missing " "something: %s\n", response); msg = generate_error_message(response_node, - URL_START_OSCAR_SESSION); + get_start_oscar_session_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); g_free(*host); g_free(tmp); g_free(*cookie); xmlnode_free(response_node); return FALSE; @@ -267,29 +318,30 @@ static gboolean parse_start_oscar_sessio static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { OscarData *od; PurpleConnection *gc; char *host, *cookie; char *tls_certname = NULL; unsigned short port; guint8 *cookiedata; - gsize cookiedata_len; + gsize cookiedata_len = 0; od = user_data; gc = od->gc; od->url_data = NULL; if (error_message != NULL || len == 0) { gchar *tmp; /* Note to translators: The first %s is a URL, the second is an error message. */ tmp = g_strdup_printf(_("Error requesting %s: %s"), - URL_START_OSCAR_SESSION, error_message); + get_start_oscar_session_url(od), error_message ? + error_message : _("The server returned an empty response")); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname)) return; @@ -301,39 +353,37 @@ static void start_oscar_session_cb(Purpl g_free(host); g_free(cookie); g_free(tls_certname); } static void send_start_oscar_session(OscarData *od, const char *token, const char *session_key, time_t hosttime) { char *query_string, *signature, *url; - PurpleAccount *account; - gboolean use_tls; - - account = purple_connection_get_account(od->gc); - use_tls = purple_account_get_bool(account, "use_ssl", OSCAR_DEFAULT_USE_SSL); + PurpleAccount *account = purple_connection_get_account(od->gc); + const gchar *encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION); /* * Construct the GET parameters. 0x00000611 is the distid given to * us by AOL for use as the default libpurple distid. */ query_string = g_strdup_printf("a=%s" "&distId=%d" "&f=xml" "&k=%s" "&ts=%" PURPLE_TIME_T_MODIFIER "&useTLS=%d", purple_url_encode(token), - oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" - : "prpl-aim-distid", 0x00000611), - get_client_key(od), hosttime, use_tls); - signature = generate_signature("GET", URL_START_OSCAR_SESSION, + oscar_get_ui_info_int(od->icq ? "prpl-icq-distid" : "prpl-aim-distid", 0x00000611), + get_client_key(od), + hosttime, + strcmp(encryption_type, OSCAR_NO_ENCRYPTION) != 0 ? 1 : 0); + signature = generate_signature("GET", get_start_oscar_session_url(od), query_string, session_key); - url = g_strdup_printf(URL_START_OSCAR_SESSION "?%s&sig_sha256=%s", + url = g_strdup_printf("%s?%s&sig_sha256=%s", get_start_oscar_session_url(od), query_string, signature); g_free(query_string); g_free(signature); /* Make the request */ od->url_data = purple_util_fetch_url_request_len_with_account(account, url, TRUE, NULL, FALSE, NULL, FALSE, -1, start_oscar_session_cb, od); @@ -362,29 +412,30 @@ static void send_start_oscar_session(Osc * the time on the OpenAuth Server in seconds since the * Unix epoch. On failure this value will be untouched. * * @return TRUE if the request was successful and we were able to * extract all info we need. Otherwise FALSE. */ static gboolean parse_client_login_response(PurpleConnection *gc, const gchar *response, gsize response_len, char **token, char **secret, time_t *hosttime) { + OscarData *od = purple_connection_get_protocol_data(gc); xmlnode *response_node, *tmp_node, *data_node; xmlnode *secret_node = NULL, *hosttime_node = NULL, *token_node = NULL, *tokena_node = NULL; char *tmp; /* Parse the response as XML */ response_node = xmlnode_from_str(response, response_len); if (response_node == NULL) { char *msg; purple_debug_error("oscar", "clientLogin could not parse " "response as XML: %s\n", response); msg = generate_error_message(response_node, - URL_CLIENT_LOGIN); + get_client_login_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); return FALSE; } /* Grab the necessary XML nodes */ tmp_node = xmlnode_get_child(response_node, "statusCode"); @@ -398,17 +449,17 @@ static gboolean parse_client_login_respo } /* Make sure we have a status code */ if (tmp_node == NULL || (tmp = xmlnode_get_data_unescaped(tmp_node)) == NULL) { char *msg; purple_debug_error("oscar", "clientLogin response was " "missing statusCode: %s\n", response); msg = generate_error_message(response_node, - URL_CLIENT_LOGIN); + get_client_login_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); xmlnode_free(response_node); return FALSE; } /* Make sure the status code was 200 */ @@ -441,17 +492,17 @@ static gboolean parse_client_login_respo "sign in, but this client does not currently support CAPTCHAs.")); } else if (status_code == 401 && status_detail_code == 3019) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, _("AOL does not allow your screen name to authenticate here")); } else { char *msg; msg = generate_error_message(response_node, - URL_CLIENT_LOGIN); + get_client_login_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, msg); g_free(msg); } xmlnode_free(response_node); return FALSE; } @@ -460,17 +511,17 @@ static gboolean parse_client_login_respo /* Make sure we have everything else */ if (data_node == NULL || secret_node == NULL || token_node == NULL || tokena_node == NULL) { char *msg; purple_debug_error("oscar", "clientLogin response was missing " "something: %s\n", response); msg = generate_error_message(response_node, - URL_CLIENT_LOGIN); + get_client_login_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); xmlnode_free(response_node); return FALSE; } /* Extract data from the XML */ @@ -478,17 +529,17 @@ static gboolean parse_client_login_respo *secret = xmlnode_get_data_unescaped(secret_node); tmp = xmlnode_get_data_unescaped(hosttime_node); if (*token == NULL || **token == '\0' || *secret == NULL || **secret == '\0' || tmp == NULL || *tmp == '\0') { char *msg; purple_debug_error("oscar", "clientLogin response was missing " "something: %s\n", response); msg = generate_error_message(response_node, - URL_CLIENT_LOGIN); + get_client_login_url(od)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, msg); g_free(msg); g_free(*token); g_free(*secret); g_free(tmp); xmlnode_free(response_node); return FALSE; @@ -513,22 +564,19 @@ static void client_login_cb(PurpleUtilFe od = user_data; gc = od->gc; od->url_data = NULL; if (error_message != NULL || len == 0) { gchar *tmp; - if (error_message != NULL) - tmp = g_strdup_printf(_("Error requesting %s: %s"), - URL_CLIENT_LOGIN, error_message); - else - tmp = g_strdup_printf(_("Error requesting %s"), - URL_CLIENT_LOGIN); + tmp = g_strdup_printf(_("Error requesting %s: %s"), + get_client_login_url(od), error_message ? + error_message : _("The server returned an empty response")); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } if (!parse_client_login_response(gc, url_text, len, &token, &secret, &hosttime)) return; @@ -594,13 +642,13 @@ void send_client_login(OscarData *od, co /* Tack on the body */ g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n"); g_string_append_printf(request, "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n", body->len); g_string_append_len(request, body->str, body->len); g_string_free(body, TRUE); /* Send the POST request */ od->url_data = purple_util_fetch_url_request_len_with_account( - purple_connection_get_account(gc), URL_CLIENT_LOGIN, + purple_connection_get_account(gc), get_client_login_url(od), TRUE, NULL, FALSE, request->str, FALSE, -1, client_login_cb, od); g_string_free(request, TRUE); } diff --git a/purple/libpurple/protocols/oscar/encoding.c b/purple/libpurple/protocols/oscar/encoding.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/encoding.c @@ -0,0 +1,234 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +#include "encoding.h" + +static gchar * +encoding_extract(const char *encoding) +{ + char *begin, *end; + + if (encoding == NULL) { + return NULL; + } + + if (!g_str_has_prefix(encoding, "text/aolrtf; charset=") && + !g_str_has_prefix(encoding, "text/x-aolrtf; charset=") && + !g_str_has_prefix(encoding, "text/plain; charset=")) { + return g_strdup(encoding); + } + + begin = strchr(encoding, '"'); + end = strrchr(encoding, '"'); + + if ((begin == NULL) || (end == NULL) || (begin >= end)) { + return g_strdup(encoding); + } + + return g_strndup(begin+1, (end-1) - begin); +} + +gchar * +oscar_encoding_to_utf8(const char *encoding, const char *text, int textlen) +{ + gchar *utf8 = NULL; + const gchar *glib_encoding = NULL; + gchar *extracted_encoding = encoding_extract(encoding); + + if (extracted_encoding == NULL || *extracted_encoding == '\0') { + purple_debug_info("oscar", "Empty encoding, assuming UTF-8\n"); + } else if (!g_ascii_strcasecmp(extracted_encoding, "iso-8859-1")) { + glib_encoding = "iso-8859-1"; + } else if (!g_ascii_strcasecmp(extracted_encoding, "ISO-8859-1-Windows-3.1-Latin-1") || !g_ascii_strcasecmp(extracted_encoding, "us-ascii")) { + glib_encoding = "Windows-1252"; + } else if (!g_ascii_strcasecmp(extracted_encoding, "unicode-2-0")) { + glib_encoding = "UTF-16BE"; + } else if (g_ascii_strcasecmp(extracted_encoding, "utf-8")) { + glib_encoding = extracted_encoding; + } + + if (glib_encoding != NULL) { + utf8 = g_convert(text, textlen, "UTF-8", glib_encoding, NULL, NULL, NULL); + } + + /* + * If utf8 is still NULL then either the encoding is utf-8 or + * we have been unable to convert the text to utf-8 from the encoding + * that was specified. So we check if the text is valid utf-8 then + * just copy it. + */ + if (utf8 == NULL) { + if (textlen != 0 && *text != '\0' && !g_utf8_validate(text, textlen, NULL)) + utf8 = g_strdup(_("(There was an error receiving this message. The buddy you are speaking with is probably using a different encoding than expected. If you know what encoding he is using, you can specify it in the advanced account options for your AIM/ICQ account.)")); + else + utf8 = g_strndup(text, textlen); + } + + g_free(extracted_encoding); + return utf8; +} + +gchar * +oscar_utf8_try_convert(PurpleAccount *account, OscarData *od, const gchar *msg) +{ + const char *charset = NULL; + char *ret = NULL; + + if (msg == NULL) + return NULL; + + if (g_utf8_validate(msg, -1, NULL)) + return g_strdup(msg); + + if (od->icq) + charset = purple_account_get_string(account, "encoding", NULL); + + if(charset && *charset) + ret = g_convert(msg, -1, "UTF-8", charset, NULL, NULL, NULL); + + if(!ret) + ret = purple_utf8_try_convert(msg); + + return ret; +} + +static gchar * +oscar_convert_to_utf8(const gchar *data, gsize datalen, const char *charsetstr, gboolean fallback) +{ + gchar *ret = NULL; + GError *err = NULL; + + if ((charsetstr == NULL) || (*charsetstr == '\0')) + return NULL; + + if (g_ascii_strcasecmp("UTF-8", charsetstr)) { + if (fallback) + ret = g_convert_with_fallback(data, datalen, "UTF-8", charsetstr, "?", NULL, NULL, &err); + else + ret = g_convert(data, datalen, "UTF-8", charsetstr, NULL, NULL, &err); + if (err != NULL) { + purple_debug_warning("oscar", "Conversion from %s failed: %s.\n", + charsetstr, err->message); + g_error_free(err); + } + } else { + if (g_utf8_validate(data, datalen, NULL)) + ret = g_strndup(data, datalen); + else + purple_debug_warning("oscar", "String is not valid UTF-8.\n"); + } + + return ret; +} + +gchar * +oscar_decode_im(PurpleAccount *account, const char *sourcebn, guint16 charset, const gchar *data, gsize datalen) +{ + gchar *ret = NULL; + /* charsetstr1 is always set to what the correct encoding should be. */ + const gchar *charsetstr1, *charsetstr2, *charsetstr3 = NULL; + + if ((datalen == 0) || (data == NULL)) + return NULL; + + if (charset == AIM_CHARSET_UNICODE) { + charsetstr1 = "UTF-16BE"; + charsetstr2 = "UTF-8"; + } else if (charset == AIM_CHARSET_LATIN_1) { + if ((sourcebn != NULL) && oscar_util_valid_name_icq(sourcebn)) + charsetstr1 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); + else + charsetstr1 = "ISO-8859-1"; + charsetstr2 = "UTF-8"; + } else if (charset == AIM_CHARSET_ASCII) { + /* Should just be "ASCII" */ + charsetstr1 = "ASCII"; + charsetstr2 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); + } else if (charset == 0x000d) { + /* iChat sending unicode over a Direct IM connection = UTF-8 */ + /* Mobile AIM client on multiple devices (including Blackberry Tour, Nokia 3100, and LG VX6000) = ISO-8859-1 */ + charsetstr1 = "UTF-8"; + charsetstr2 = "ISO-8859-1"; + charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); + } else { + /* Unknown, hope for valid UTF-8... */ + charsetstr1 = "UTF-8"; + charsetstr2 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); + } + + purple_debug_info("oscar", "Parsing IM, charset=0x%04hx, datalen=%" G_GSIZE_FORMAT ", choice1=%s, choice2=%s, choice3=%s\n", + charset, datalen, charsetstr1, charsetstr2, (charsetstr3 ? charsetstr3 : "")); + + ret = oscar_convert_to_utf8(data, datalen, charsetstr1, FALSE); + if (ret == NULL) { + if (charsetstr3 != NULL) { + /* Try charsetstr2 without allowing substitutions, then fall through to charsetstr3 if needed */ + ret = oscar_convert_to_utf8(data, datalen, charsetstr2, FALSE); + if (ret == NULL) + ret = oscar_convert_to_utf8(data, datalen, charsetstr3, TRUE); + } else { + /* Try charsetstr2, allowing substitutions */ + ret = oscar_convert_to_utf8(data, datalen, charsetstr2, TRUE); + } + } + if (ret == NULL) { + char *str, *salvage, *tmp; + + str = g_malloc(datalen + 1); + strncpy(str, data, datalen); + str[datalen] = '\0'; + salvage = purple_utf8_salvage(str); + tmp = g_strdup_printf(_("(There was an error receiving this message. Either you and %s have different encodings selected, or %s has a buggy client.)"), + sourcebn, sourcebn); + ret = g_strdup_printf("%s %s", salvage, tmp); + g_free(tmp); + g_free(str); + g_free(salvage); + } + + return ret; +} + +static guint16 +get_simplest_charset(const char *utf8) +{ + while (*utf8) + { + if ((unsigned char)(*utf8) > 0x7f) { + /* not ASCII! */ + return AIM_CHARSET_UNICODE; + } + utf8++; + } + return AIM_CHARSET_ASCII; +} + +gchar * +oscar_encode_im(const gchar *msg, gsize *result_len, guint16 *charset, gchar **charsetstr) +{ + guint16 msg_charset = get_simplest_charset(msg); + if (charset != NULL) { + *charset = msg_charset; + } + if (charsetstr != NULL) { + *charsetstr = msg_charset == AIM_CHARSET_ASCII ? "us-ascii" : "unicode-2-0"; + } + return g_convert(msg, -1, msg_charset == AIM_CHARSET_ASCII ? "ASCII" : "UTF-16BE", "UTF-8", NULL, result_len, NULL); +} diff --git a/purple/libpurple/protocols/oscar/encoding.h b/purple/libpurple/protocols/oscar/encoding.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/encoding.h @@ -0,0 +1,46 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +#ifndef _ENCODING_H_ +#define _ENCODING_H_ + +#include "oscar.h" +#include "oscarcommon.h" + +gchar * oscar_encoding_to_utf8(const char *encoding, const char *text, int textlen); +gchar * oscar_utf8_try_convert(PurpleAccount *account, OscarData *od, const gchar *msg); + +/** + * This attemps to decode an incoming IM into a UTF8 string. + * + * We try decoding using two different character sets. The charset + * specified in the IM determines the order in which we attempt to + * decode. We do this because there are lots of broken ICQ clients + * that don't correctly send non-ASCII messages. And if Purple isn't + * able to deal with that crap, then people complain like banshees. + */ +gchar * oscar_decode_im(PurpleAccount *account, const char *sourcebn, guint16 charset, const gchar *data, gsize datalen); + +/** + * Figure out what encoding to use when sending a given outgoing message. + */ +gchar * oscar_encode_im(const gchar *msg, gsize *result_len, guint16 *charset, gchar **charsetstr); + +#endif \ No newline at end of file diff --git a/purple/libpurple/protocols/oscar/family_admin.c b/purple/libpurple/protocols/oscar/family_admin.c --- a/purple/libpurple/protocols/oscar/family_admin.c +++ b/purple/libpurple/protocols/oscar/family_admin.c @@ -42,17 +42,17 @@ aim_admin_getinfo(OscarData *od, FlapCon aim_snacid_t snacid; byte_stream_new(&bs, 4); byte_stream_put16(&bs, info); byte_stream_put16(&bs, 0x0000); snacid = aim_cachesnac(od, SNAC_FAMILY_ADMIN, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0002, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtypes 0x0003 and 0x0005 - Parse account info. * * Called in reply to both an information request (subtype 0x0002) and @@ -63,17 +63,17 @@ infochange(OscarData *od, FlapConnection { aim_rxcallback_t userfunc; char *url=NULL, *sn=NULL, *email=NULL; guint16 perms, tlvcount, err=0; perms = byte_stream_get16(bs); tlvcount = byte_stream_get16(bs); - while (tlvcount && byte_stream_empty(bs)) { + while (tlvcount && byte_stream_bytes_left(bs)) { guint16 type, length; type = byte_stream_get16(bs); length = byte_stream_get16(bs); switch (type) { case 0x0001: { g_free(sn); @@ -122,17 +122,17 @@ aim_admin_setnick(OscarData *od, FlapCon byte_stream_new(&bs, 2+2+strlen(newnick)); aim_tlvlist_add_str(&tlvlist, 0x0001, newnick); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0004 - Change password. */ void @@ -149,17 +149,17 @@ aim_admin_changepasswd(OscarData *od, Fl /* current password TLV t(0012) */ aim_tlvlist_add_str(&tlvlist, 0x0012, curpw); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0004 - Change email address. */ void @@ -172,17 +172,17 @@ aim_admin_setemail(OscarData *od, FlapCo byte_stream_new(&bs, 2+2+strlen(newemail)); aim_tlvlist_add_str(&tlvlist, 0x0011, newemail); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ADMIN, 0x0004, snacid, &bs); byte_stream_destroy(&bs); } /* * Subtype 0x0006 - Request account confirmation. * * This will cause an email to be sent to the address associated with diff --git a/purple/libpurple/protocols/oscar/family_advert.c b/purple/libpurple/protocols/oscar/family_advert.c deleted file mode 100644 --- a/purple/libpurple/protocols/oscar/family_advert.c +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Purple's oscar protocol plugin - * This file is the legal property of its developers. - * Please see the AUTHORS file distributed alongside this file. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA -*/ - -/* - * Family 0x0005 - Advertisements. - * - */ - -#include "oscar.h" - -void -aim_ads_requestads(OscarData *od, FlapConnection *conn) -{ - aim_genericreq_n(od, conn, SNAC_FAMILY_ADVERT, 0x0002); -} - -static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *rx, aim_modsnac_t *snac, ByteStream *bs) -{ - return 0; -} - -int adverts_modfirst(OscarData *od, aim_module_t *mod) -{ - - mod->family = SNAC_FAMILY_ADVERT; - mod->version = 0x0001; - mod->toolid = 0x0001; - mod->toolversion = 0x0001; - mod->flags = 0; - strncpy(mod->name, "advert", sizeof(mod->name)); - mod->snachandler = snachandler; - - return 0; -} diff --git a/purple/libpurple/protocols/oscar/family_alert.c b/purple/libpurple/protocols/oscar/family_alert.c --- a/purple/libpurple/protocols/oscar/family_alert.c +++ b/purple/libpurple/protocols/oscar/family_alert.c @@ -68,17 +68,17 @@ aim_email_sendcookies(OscarData *od) byte_stream_put16(&bs, 0x0dba); byte_stream_put16(&bs, 0x11d5); byte_stream_put16(&bs, 0x9f8a); byte_stream_put16(&bs, 0x0060); byte_stream_put16(&bs, 0xb0ee); byte_stream_put16(&bs, 0x0631); snacid = aim_cachesnac(od, SNAC_FAMILY_ALERT, 0x0006, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ALERT, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ALERT, 0x0006, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** @@ -184,17 +184,17 @@ aim_email_activate(OscarData *od) /* ...but I really have no idea */ byte_stream_put8(&bs, 0x02); byte_stream_put32(&bs, 0x04000000); byte_stream_put32(&bs, 0x04000000); byte_stream_put32(&bs, 0x04000000); byte_stream_put32(&bs, 0x00000000); snacid = aim_cachesnac(od, SNAC_FAMILY_ALERT, 0x0016, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ALERT, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ALERT, 0x0006, snacid, &bs); byte_stream_destroy(&bs); return 0; } static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) diff --git a/purple/libpurple/protocols/oscar/family_auth.c b/purple/libpurple/protocols/oscar/family_auth.c --- a/purple/libpurple/protocols/oscar/family_auth.c +++ b/purple/libpurple/protocols/oscar/family_auth.c @@ -51,27 +51,20 @@ * * @param password Incoming password. * @param encoded Buffer to put encoded password. */ static int aim_encode_password(const char *password, guint8 *encoded) { guint8 encoding_table[] = { -#if 0 /* old v1 table */ - 0xf3, 0xb3, 0x6c, 0x99, - 0x95, 0x3f, 0xac, 0xb6, - 0xc5, 0xfa, 0x6b, 0x63, - 0x69, 0x6c, 0xc3, 0x9f -#else /* v2.1 table, also works for ICQ */ 0xf3, 0x26, 0x81, 0xc4, 0x39, 0x86, 0xdb, 0x92, 0x71, 0xa3, 0xb9, 0xe6, 0x53, 0x7a, 0x95, 0x7c -#endif }; unsigned int i; for (i = 0; i < strlen(password); i++) encoded[i] = (password[i] ^ encoding_table[i]); return 0; } @@ -229,17 +222,17 @@ aim_send_login(OscarData *od, FlapConnec /* If we're signing on an ICQ account then use the older, XOR login method */ if (aim_snvalid_icq(sn)) return goddamnicq2(od, conn, sn, password, ci); #endif frame = flap_frame_new(od, 0x02, 1152); snacid = aim_cachesnac(od, SNAC_FAMILY_AUTH, 0x0002, 0x0000, NULL, 0); - aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, 0x0002, 0x0000, snacid); + aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, 0x0002, snacid); aim_tlvlist_add_str(&tlvlist, 0x0001, sn); /* Truncate ICQ and AOL passwords, if necessary */ password_len = strlen(password); if (oscar_util_valid_name_icq(sn) && (password_len > MAXICQPASSLEN)) password_len = MAXICQPASSLEN; else if (truncate_pass && password_len > 8) @@ -380,52 +373,31 @@ parse(OscarData *od, FlapConnection *con info->latestbeta.build = aim_tlv_get32(tlvlist, 0x0040, 1); if (aim_tlv_gettlv(tlvlist, 0x0041, 1)) info->latestbeta.url = aim_tlv_getstr(tlvlist, 0x0041, 1); if (aim_tlv_gettlv(tlvlist, 0x0042, 1)) info->latestbeta.info = aim_tlv_getstr(tlvlist, 0x0042, 1); if (aim_tlv_gettlv(tlvlist, 0x0043, 1)) info->latestbeta.name = aim_tlv_getstr(tlvlist, 0x0043, 1); -#if 0 - if (aim_tlv_gettlv(tlvlist, 0x0048, 1)) { - /* beta serial */ - } -#endif - if (aim_tlv_gettlv(tlvlist, 0x0044, 1)) info->latestrelease.build = aim_tlv_get32(tlvlist, 0x0044, 1); if (aim_tlv_gettlv(tlvlist, 0x0045, 1)) info->latestrelease.url = aim_tlv_getstr(tlvlist, 0x0045, 1); if (aim_tlv_gettlv(tlvlist, 0x0046, 1)) info->latestrelease.info = aim_tlv_getstr(tlvlist, 0x0046, 1); if (aim_tlv_gettlv(tlvlist, 0x0047, 1)) info->latestrelease.name = aim_tlv_getstr(tlvlist, 0x0047, 1); -#if 0 - if (aim_tlv_gettlv(tlvlist, 0x0049, 1)) { - /* lastest release serial */ - } -#endif - /* * URL to change password. */ if (aim_tlv_gettlv(tlvlist, 0x0054, 1)) info->chpassurl = aim_tlv_getstr(tlvlist, 0x0054, 1); -#if 0 - /* - * Unknown. Seen on an @mac.com username with value of 0x003f - */ - if (aim_tlv_gettlv(tlvlist, 0x0055, 1)) { - /* Unhandled */ - } -#endif - od->authinfo = info; if ((userfunc = aim_callhandler(od, snac ? snac->family : SNAC_FAMILY_AUTH, snac ? snac->subtype : 0x0003))) ret = userfunc(od, conn, frame, info); aim_tlvlist_free(tlvlist); return ret; @@ -499,17 +471,17 @@ aim_request_login(OscarData *od, FlapCon #ifdef USE_XOR_FOR_ICQ if (aim_snvalid_icq(sn)) return goddamnicq(od, conn, sn); #endif frame = flap_frame_new(od, 0x02, 10+2+2+strlen(sn)+8); snacid = aim_cachesnac(od, SNAC_FAMILY_AUTH, 0x0006, 0x0000, NULL, 0); - aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, 0x0006, 0x0000, snacid); + aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, 0x0006, snacid); aim_tlvlist_add_str(&tlvlist, 0x0001, sn); /* Tell the server we support SecurID logins. */ aim_tlvlist_add_noval(&tlvlist, 0x004b); /* Unknown. Sent in recent WinAIM clients.*/ aim_tlvlist_add_noval(&tlvlist, 0x005a); @@ -597,17 +569,17 @@ aim_auth_securid_send(OscarData *od, con if (!od || !(conn = flap_connection_getbytype_all(od, SNAC_FAMILY_AUTH)) || !securid) return -EINVAL; len = strlen(securid); frame = flap_frame_new(od, 0x02, 10+2+len); snacid = aim_cachesnac(od, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_RESPONSE, 0x0000, NULL, 0); - aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_RESPONSE, 0x0000, 0); + aim_putsnac(&frame->data, SNAC_FAMILY_AUTH, SNAC_SUBTYPE_AUTH_SECURID_RESPONSE, 0); byte_stream_put16(&frame->data, len); byte_stream_putstr(&frame->data, securid); flap_connection_send(conn, frame); return 0; } diff --git a/purple/libpurple/protocols/oscar/family_bart.c b/purple/libpurple/protocols/oscar/family_bart.c --- a/purple/libpurple/protocols/oscar/family_bart.c +++ b/purple/libpurple/protocols/oscar/family_bart.c @@ -51,17 +51,17 @@ aim_bart_upload(OscarData *od, const gui /* The reference number for the icon */ byte_stream_put16(&bs, 1); /* The icon */ byte_stream_put16(&bs, iconlen); byte_stream_putraw(&bs, icon, iconlen); snacid = aim_cachesnac(od, SNAC_FAMILY_BART, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BART, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_BART, 0x0002, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Subtype 0x0003 - Acknowledgement for uploading a buddy icon. @@ -116,17 +116,17 @@ aim_bart_request(OscarData *od, const ch byte_stream_put16(&bs, 0x0001); byte_stream_put8(&bs, iconcsumtype); /* Icon string */ byte_stream_put8(&bs, iconcsumlen); byte_stream_putraw(&bs, iconcsum, iconcsumlen); snacid = aim_cachesnac(od, SNAC_FAMILY_BART, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BART, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_BART, 0x0004, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Subtype 0x0005 - Receive a buddy icon. diff --git a/purple/libpurple/protocols/oscar/family_bos.c b/purple/libpurple/protocols/oscar/family_bos.c --- a/purple/libpurple/protocols/oscar/family_bos.c +++ b/purple/libpurple/protocols/oscar/family_bos.c @@ -63,108 +63,16 @@ static int rights(OscarData *od, FlapCon if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, maxpermits, maxdenies); aim_tlvlist_free(tlvlist); return ret; } -/* - * Subtype 0x0004 - Set group permission mask. - * - * Normally 0x1f (all classes). - * - * The group permission mask allows you to keep users of a certain - * class or classes from talking to you. The mask should be - * a bitwise OR of all the user classes you want to see you. - * - */ -void -aim_bos_setgroupperm(OscarData *od, FlapConnection *conn, guint32 mask) -{ - aim_genericreq_l(od, conn, SNAC_FAMILY_BOS, 0x0004, &mask); -} - -/* - * Stubtypes 0x0005, 0x0006, 0x0007, and 0x0008 - Modify permit/deny lists. - * - * Changes your visibility depending on changetype: - * - * AIM_VISIBILITYCHANGE_PERMITADD: Lets provided list of names see you - * AIM_VISIBILITYCHANGE_PERMIDREMOVE: Removes listed names from permit list - * AIM_VISIBILITYCHANGE_DENYADD: Hides you from provided list of names - * AIM_VISIBILITYCHANGE_DENYREMOVE: Lets list see you again - * - * list should be a list of "Buddy Name One&BuddyNameTwo&" etc. - * - * Equivelents to options in WinAIM: - * - Allow all users to contact me: Send an AIM_VISIBILITYCHANGE_DENYADD - * with only your name on it. - * - Allow only users on my Buddy List: Send an - * AIM_VISIBILITYCHANGE_PERMITADD with the list the same as your - * buddy list - * - Allow only the uesrs below: Send an AIM_VISIBILITYCHANGE_PERMITADD - * with everyone listed that you want to see you. - * - Block all users: Send an AIM_VISIBILITYCHANGE_PERMITADD with only - * yourself in the list - * - Block the users below: Send an AIM_VISIBILITYCHANGE_DENYADD with - * the list of users to be blocked - * - * XXX ye gods. - */ -int aim_bos_changevisibility(OscarData *od, FlapConnection *conn, int changetype, const char *denylist) -{ - ByteStream bs; - int packlen = 0; - guint16 subtype; - char *localcpy = NULL, *tmpptr = NULL; - int i; - int listcount; - aim_snacid_t snacid; - - if (!denylist) - return -EINVAL; - - if (changetype == AIM_VISIBILITYCHANGE_PERMITADD) - subtype = 0x05; - else if (changetype == AIM_VISIBILITYCHANGE_PERMITREMOVE) - subtype = 0x06; - else if (changetype == AIM_VISIBILITYCHANGE_DENYADD) - subtype = 0x07; - else if (changetype == AIM_VISIBILITYCHANGE_DENYREMOVE) - subtype = 0x08; - else - return -EINVAL; - - localcpy = g_strdup(denylist); - - listcount = aimutil_itemcnt(localcpy, '&'); - packlen = aimutil_tokslen(localcpy, 99, '&') + listcount-1; - - byte_stream_new(&bs, packlen); - - for (i = 0; (i < (listcount - 1)) && (i < 99); i++) { - tmpptr = aimutil_itemindex(localcpy, i, '&'); - - byte_stream_put8(&bs, strlen(tmpptr)); - byte_stream_putstr(&bs, tmpptr); - - g_free(tmpptr); - } - g_free(localcpy); - - snacid = aim_cachesnac(od, SNAC_FAMILY_BOS, subtype, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BOS, subtype, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { if (snac->subtype == 0x0003) return rights(od, conn, mod, frame, snac, bs); return 0; } diff --git a/purple/libpurple/protocols/oscar/family_buddy.c b/purple/libpurple/protocols/oscar/family_buddy.c --- a/purple/libpurple/protocols/oscar/family_buddy.c +++ b/purple/libpurple/protocols/oscar/family_buddy.c @@ -83,127 +83,16 @@ rights(OscarData *od, FlapConnection *co ret = userfunc(od, conn, frame, maxbuddies, maxwatchers); aim_tlvlist_free(tlvlist); return ret; } /* - * Subtype 0x0004 (SNAC_SUBTYPE_BUDDY_ADDBUDDY) - Add buddy to list. - * - * Adds a single buddy to your buddy list after login. - * XXX This should just be an extension of setbuddylist() - * - */ -int -aim_buddylist_addbuddy(OscarData *od, FlapConnection *conn, const char *sn) -{ - ByteStream bs; - aim_snacid_t snacid; - - if (!sn || !strlen(sn)) - return -EINVAL; - - byte_stream_new(&bs, 1+strlen(sn)); - - byte_stream_put8(&bs, strlen(sn)); - byte_stream_putstr(&bs, sn); - - snacid = aim_cachesnac(od, SNAC_FAMILY_BUDDY, 0x0004, 0x0000, sn, strlen(sn)+1); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BUDDY, 0x0004, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * Subtype 0x0004 (SNAC_SUBTYPE_BUDDY_ADDBUDDY) - Add multiple buddies to your buddy list. - * - * This just builds the "set buddy list" command then queues it. - * - * buddy_list = "Buddy Name One&BuddyNameTwo&"; - * - * XXX Clean this up. - * - */ -int -aim_buddylist_set(OscarData *od, FlapConnection *conn, const char *buddy_list) -{ - ByteStream bs; - aim_snacid_t snacid; - int len = 0; - char *localcpy = NULL; - char *tmpptr = NULL; - - if (!buddy_list || !(localcpy = g_strdup(buddy_list))) - return -EINVAL; - - for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { - purple_debug_misc("oscar", "---adding: %s (%" G_GSIZE_FORMAT - ")\n", tmpptr, strlen(tmpptr)); - len += 1 + strlen(tmpptr); - tmpptr = strtok(NULL, "&"); - } - - byte_stream_new(&bs, len); - - strncpy(localcpy, buddy_list, strlen(buddy_list) + 1); - - for (tmpptr = strtok(localcpy, "&"); tmpptr; ) { - - purple_debug_misc("oscar", "---adding: %s (%" G_GSIZE_FORMAT - ")\n", tmpptr, strlen(tmpptr)); - - byte_stream_put8(&bs, strlen(tmpptr)); - byte_stream_putstr(&bs, tmpptr); - tmpptr = strtok(NULL, "&"); - } - - snacid = aim_cachesnac(od, SNAC_FAMILY_BUDDY, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BUDDY, 0x0004, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - g_free(localcpy); - - return 0; -} - -/* - * Subtype 0x0005 (SNAC_SUBTYPE_BUDDY_REMBUDDY) - Remove buddy from list. - * - * XXX generalise to support removing multiple buddies (basically, its - * the same as setbuddylist() but with a different snac subtype). - * - */ -int -aim_buddylist_removebuddy(OscarData *od, FlapConnection *conn, const char *sn) -{ - ByteStream bs; - aim_snacid_t snacid; - - if (!sn || !strlen(sn)) - return -EINVAL; - - byte_stream_new(&bs, 1 + strlen(sn)); - - byte_stream_put8(&bs, strlen(sn)); - byte_stream_putstr(&bs, sn); - - snacid = aim_cachesnac(od, SNAC_FAMILY_BUDDY, 0x0005, 0x0000, sn, strlen(sn)+1); - flap_connection_send_snac(od, conn, SNAC_FAMILY_BUDDY, 0x0005, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* * Subtypes 0x000b (SNAC_SUBTYPE_BUDDY_ONCOMING) and 0x000c (SNAC_SUBTYPE_BUDDY_OFFGOING) - Change in buddy status * * Oncoming Buddy notifications contain a subset of the * user information structure. It's close enough to run * through aim_info_extract() however. * * Although the offgoing notification contains no information, * it is still in a format parsable by aim_info_extract(). diff --git a/purple/libpurple/protocols/oscar/family_chat.c b/purple/libpurple/protocols/oscar/family_chat.c --- a/purple/libpurple/protocols/oscar/family_chat.c +++ b/purple/libpurple/protocols/oscar/family_chat.c @@ -42,294 +42,94 @@ flap_connection_destroy_chat(OscarData * if (ccp) g_free(ccp->name); g_free(ccp); return; } -char * -aim_chat_getname(FlapConnection *conn) -{ - struct chatconnpriv *ccp; - - if (!conn) - return NULL; - - if (conn->type != SNAC_FAMILY_CHAT) - return NULL; - - ccp = (struct chatconnpriv *)conn->internal; - - return ccp->name; -} - -/* XXX get this into conn.c -- evil!! */ -FlapConnection * -aim_chat_getconn(OscarData *od, const char *name) -{ - GSList *cur; - - for (cur = od->oscar_connections; cur; cur = cur->next) - { - FlapConnection *conn; - struct chatconnpriv *ccp; - - conn = cur->data; - ccp = (struct chatconnpriv *)conn->internal; - - if (conn->type != SNAC_FAMILY_CHAT) - continue; - if (!conn->internal) - { - purple_debug_misc("oscar", "%sfaim: chat: chat connection with no name! (fd = %d)\n", - conn->gsc ? "(ssl) " : "", conn->gsc ? conn->gsc->fd : conn->fd); - continue; - } - - if (strcmp(ccp->name, name) == 0) - return conn; - } - - return NULL; -} - -int -aim_chat_attachname(FlapConnection *conn, guint16 exchange, const char *roomname, guint16 instance) -{ - struct chatconnpriv *ccp; - - if (!conn || !roomname) - return -EINVAL; - - if (conn->internal) - g_free(conn->internal); - - ccp = g_new(struct chatconnpriv, 1); - - ccp->exchange = exchange; - ccp->name = g_strdup(roomname); - ccp->instance = instance; - - conn->internal = (void *)ccp; - - return 0; -} - int aim_chat_readroominfo(ByteStream *bs, struct aim_chat_roominfo *outinfo) { if (!bs || !outinfo) return 0; outinfo->exchange = byte_stream_get16(bs); outinfo->namelen = byte_stream_get8(bs); outinfo->name = (char *)byte_stream_getraw(bs, outinfo->namelen); outinfo->instance = byte_stream_get16(bs); return 0; } -int -aim_chat_leaveroom(OscarData *od, const char *name) -{ - FlapConnection *conn; - - if (!(conn = aim_chat_getconn(od, name))) - return -ENOENT; - - flap_connection_close(od, conn); - - return 0; -} - /* * Subtype 0x0002 - General room information. Lots of stuff. * * Values I know are in here but I haven't attached * them to any of the 'Unknown's: * - Language (English) * */ static int infoupdate(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - aim_userinfo_t *userinfo = NULL; aim_rxcallback_t userfunc; int ret = 0; - int usercount; guint8 detaillevel = 0; - char *roomname; struct aim_chat_roominfo roominfo; - guint16 tlvcount = 0; GSList *tlvlist; - aim_tlv_t *tlv; - char *roomdesc; - guint16 flags; - guint32 creationtime; guint16 maxmsglen, maxvisiblemsglen; - guint16 unknown_d2, unknown_d5; aim_chat_readroominfo(bs, &roominfo); detaillevel = byte_stream_get8(bs); if (detaillevel != 0x02) { purple_debug_misc("oscar", "faim: chat_roomupdateinfo: detail level %d not supported\n", detaillevel); return 1; } - tlvcount = byte_stream_get16(bs); + byte_stream_get16(bs); /* skip the TLV count */ /* * Everything else are TLVs. */ tlvlist = aim_tlvlist_read(bs); /* - * TLV type 0x006a is the room name in Human Readable Form. - */ - roomname = aim_tlv_getstr(tlvlist, 0x006a, 1); - - /* - * Type 0x006f: Number of occupants. - */ - usercount = aim_tlv_get16(tlvlist, 0x006f, 1); - - /* - * Type 0x0073: Occupant list. - */ - tlv = aim_tlv_gettlv(tlvlist, 0x0073, 1); - if (tlv != NULL) - { - int curoccupant = 0; - ByteStream occbs; - - /* Allocate enough userinfo structs for all occupants */ - userinfo = g_new0(aim_userinfo_t, usercount); - - byte_stream_init(&occbs, tlv->value, tlv->length); - - while (curoccupant < usercount) - aim_info_extract(od, &occbs, &userinfo[curoccupant++]); - } - - /* - * Type 0x00c9: Flags. (AIM_CHATROOM_FLAG) - */ - flags = aim_tlv_get16(tlvlist, 0x00c9, 1); - - /* - * Type 0x00ca: Creation time (4 bytes) - */ - creationtime = aim_tlv_get32(tlvlist, 0x00ca, 1); - - /* * Type 0x00d1: Maximum Message Length */ maxmsglen = aim_tlv_get16(tlvlist, 0x00d1, 1); /* - * Type 0x00d2: Unknown. (2 bytes) - */ - unknown_d2 = aim_tlv_get16(tlvlist, 0x00d2, 1); - - /* - * Type 0x00d3: Room Description - */ - roomdesc = aim_tlv_getstr(tlvlist, 0x00d3, 1); - -#if 0 - /* - * Type 0x000d4: Unknown (flag only) - */ - if (aim_tlv_gettlv(tlvlist, 0x000d4, 1)) { - /* Unhandled */ - } -#endif - - /* - * Type 0x00d5: Unknown. (1 byte) - */ - unknown_d5 = aim_tlv_get8(tlvlist, 0x00d5, 1); - -#if 0 - /* - * Type 0x00d6: Encoding 1 ("us-ascii") - */ - if (aim_tlv_gettlv(tlvlist, 0x000d6, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d7: Language 1 ("en") - */ - if (aim_tlv_gettlv(tlvlist, 0x000d7, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d8: Encoding 2 ("us-ascii") - */ - if (aim_tlv_gettlv(tlvlist, 0x000d8, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d9: Language 2 ("en") - */ - if (aim_tlv_gettlv(tlvlist, 0x000d9, 1)) { - /* Unhandled */ - } -#endif - - /* * Type 0x00da: Maximum visible message length */ maxvisiblemsglen = aim_tlv_get16(tlvlist, 0x00da, 1); if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) { - ret = userfunc(od, conn, - frame, - &roominfo, - roomname, - usercount, - userinfo, - roomdesc, - flags, - creationtime, - maxmsglen, - unknown_d2, - unknown_d5, - maxvisiblemsglen); + ret = userfunc(od, conn, frame, maxmsglen, maxvisiblemsglen); } g_free(roominfo.name); - while (usercount > 0) - aim_info_free(&userinfo[--usercount]); - - g_free(userinfo); - g_free(roomname); - g_free(roomdesc); aim_tlvlist_free(tlvlist); return ret; } /* Subtypes 0x0003 and 0x0004 */ static int userlistchange(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { aim_userinfo_t *userinfo = NULL; aim_rxcallback_t userfunc; int curcount = 0, ret = 0; - while (byte_stream_empty(bs)) { + while (byte_stream_bytes_left(bs)) { curcount++; userinfo = g_realloc(userinfo, curcount * sizeof(aim_userinfo_t)); aim_info_extract(od, bs, &userinfo[curcount-1]); } if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, curcount, userinfo); @@ -429,17 +229,17 @@ aim_chat_send_im(OscarData *od, FlapConn */ aim_tlvlist_add_frozentlvlist(&tlvlist, 0x0005, &inner_tlvlist); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_CHAT, 0x0005, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_CHAT, 0x0005, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0006 @@ -518,26 +318,16 @@ incomingim_ch3(OscarData *od, FlapConnec */ tlv = aim_tlv_gettlv(tlvlist, 0x0003, 1); if (tlv != NULL) { byte_stream_init(&tbs, tlv->value, tlv->length); aim_info_extract(od, &tbs, &userinfo); } -#if 0 - /* - * Type 0x0001: If present, it means it was a message to the - * room (as opposed to a whisper). - */ - if (aim_tlv_gettlv(tlvlist, 0x0001, 1)) { - /* Unhandled */ - } -#endif - /* * Type 0x0005: Message Block. Conains more TLVs. */ tlv = aim_tlv_gettlv(tlvlist, 0x0005, 1); if (tlv != NULL) { GSList *inner_tlvlist; aim_tlv_t *inner_tlv; diff --git a/purple/libpurple/protocols/oscar/family_chatnav.c b/purple/libpurple/protocols/oscar/family_chatnav.c --- a/purple/libpurple/protocols/oscar/family_chatnav.c +++ b/purple/libpurple/protocols/oscar/family_chatnav.c @@ -134,17 +134,17 @@ int aim_chatnav_createroom(OscarData *od aim_tlvlist_add_str(&tlvlist, 0x00d7, lang); /* tlvcount */ byte_stream_put16(&bs, aim_tlvlist_count(tlvlist)); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_CHATNAV, 0x0008, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_CHATNAV, 0x0008, snacid, &bs); byte_stream_destroy(&bs); return 0; } static int parseinfo_perms(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs, aim_snac_t *snac2) @@ -180,42 +180,16 @@ parseinfo_perms(OscarData *od, FlapConne curexchange++; exchanges = g_realloc(exchanges, curexchange * sizeof(struct aim_chat_exchangeinfo)); /* exchange number */ exchanges[curexchange-1].number = byte_stream_get16(&tbs); innerlist = aim_tlvlist_read(&tbs); -#if 0 - /* - * Type 0x000a: Unknown. - * - * Usually three bytes: 0x0114 (exchange 1) or 0x010f (others). - * - */ - if (aim_tlv_gettlv(innerlist, 0x000a, 1)) { - /* Unhandled */ - } - - /* - * Type 0x000d: Unknown. - */ - if (aim_tlv_gettlv(innerlist, 0x000d, 1)) { - /* Unhandled */ - } - - /* - * Type 0x0004: Unknown - */ - if (aim_tlv_gettlv(innerlist, 0x0004, 1)) { - /* Unhandled */ - } -#endif - /* * Type 0x0002: Unknown */ if (aim_tlv_gettlv(innerlist, 0x0002, 1)) { guint16 classperms; classperms = aim_tlv_get16(innerlist, 0x0002, 1); @@ -229,63 +203,24 @@ parseinfo_perms(OscarData *od, FlapConne * 2 Nav Only * 4 Instancing Allowed * 8 Occupant Peek Allowed * */ if (aim_tlv_gettlv(innerlist, 0x00c9, 1)) exchanges[curexchange-1].flags = aim_tlv_get16(innerlist, 0x00c9, 1); -#if 0 - /* - * Type 0x00ca: Creation Date - */ - if (aim_tlv_gettlv(innerlist, 0x00ca, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d0: Mandatory Channels? - */ - if (aim_tlv_gettlv(innerlist, 0x00d0, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d1: Maximum Message length - */ - if (aim_tlv_gettlv(innerlist, 0x00d1, 1)) { - /* Unhandled */ - } - - /* - * Type 0x00d2: Maximum Occupancy? - */ - if (aim_tlv_gettlv(innerlist, 0x00d2, 1)) { - /* Unhandled */ - } -#endif - /* * Type 0x00d3: Exchange Description */ if (aim_tlv_gettlv(innerlist, 0x00d3, 1)) exchanges[curexchange-1].name = aim_tlv_getstr(innerlist, 0x00d3, 1); else exchanges[curexchange-1].name = NULL; -#if 0 - /* - * Type 0x00d4: Exchange Description URL - */ - if (aim_tlv_gettlv(innerlist, 0x00d4, 1)) { - /* Unhandled */ - } -#endif - /* * Type 0x00d5: Creation Permissions * * 0 Creation not allowed * 1 Room creation allowed * 2 Exchange creation allowed * */ @@ -322,25 +257,16 @@ parseinfo_perms(OscarData *od, FlapConne /* * Type 0x00d9: Language (Second Time) */ if (aim_tlv_gettlv(innerlist, 0x00d9, 1)) exchanges[curexchange-1].lang2 = aim_tlv_getstr(innerlist, 0x00d9, 1); else exchanges[curexchange-1].lang2 = NULL; -#if 0 - /* - * Type 0x00da: Unknown - */ - if (aim_tlv_gettlv(innerlist, 0x00da, 1)) { - /* Unhandled */ - } -#endif - aim_tlvlist_free(innerlist); } /* * Call client. */ if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, snac2->type, maxrooms, curexchange, exchanges); diff --git a/purple/libpurple/protocols/oscar/family_feedbag.c b/purple/libpurple/protocols/oscar/family_feedbag.c --- a/purple/libpurple/protocols/oscar/family_feedbag.c +++ b/purple/libpurple/protocols/oscar/family_feedbag.c @@ -655,20 +655,18 @@ int aim_ssi_cleanlist(OscarData *od) /* Do the same for buddies that are in a non-existant group. */ /* This will kind of mess up if you hit the item limit, but this function isn't too critical */ cur = od->ssi.local; while (cur) { next = cur->next; if (!cur->name) { if (cur->type == AIM_SSI_TYPE_BUDDY) aim_ssi_delbuddy(od, NULL, NULL); - else if (cur->type == AIM_SSI_TYPE_PERMIT) - aim_ssi_delpermit(od, NULL); - else if (cur->type == AIM_SSI_TYPE_DENY) - aim_ssi_deldeny(od, NULL); + else if (cur->type == AIM_SSI_TYPE_PERMIT || cur->type == AIM_SSI_TYPE_DENY || cur->type == AIM_SSI_TYPE_ICQDENY) + aim_ssi_del_from_private_list(od, NULL, cur->type); } else if ((cur->type == AIM_SSI_TYPE_BUDDY) && ((cur->gid == 0x0000) || (!aim_ssi_itemlist_find(od->ssi.local, cur->gid, 0x0000)))) { char *alias = aim_ssi_getalias(od->ssi.local, NULL, cur->name); aim_ssi_addbuddy(od, cur->name, "orphans", NULL, alias, NULL, NULL, FALSE); aim_ssi_delbuddy(od, cur->name, NULL); g_free(alias); } cur = next; } @@ -743,61 +741,41 @@ int aim_ssi_addbuddy(OscarData *od, cons /* Modify the parent group */ aim_ssi_itemlist_rebuildgroup(od->ssi.local, group); /* Sync our local list with the server list */ return aim_ssi_sync(od); } -/** - * Add a permit buddy to the list. - * - * @param od The oscar odion. - * @param name The name of the item.. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_ssi_addpermit(OscarData *od, const char *name) +int +aim_ssi_add_to_private_list(OscarData *od, const char* name, guint16 list_type) { - if (!od || !name || !od->ssi.received_data) return -EINVAL; - /* Make sure the master group exists */ if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL) - aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL); + aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, list_type, NULL); - /* Add that bad boy */ - aim_ssi_itemlist_add(&od->ssi.local, name, 0x0000, 0xFFFF, AIM_SSI_TYPE_PERMIT, NULL); - - /* Sync our local list with the server list */ + aim_ssi_itemlist_add(&od->ssi.local, name, 0x0000, 0xFFFF, list_type, NULL); return aim_ssi_sync(od); } -/** - * Add a deny buddy to the list. - * - * @param od The oscar odion. - * @param name The name of the item.. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_ssi_adddeny(OscarData *od, const char *name) +int +aim_ssi_del_from_private_list(OscarData* od, const char* name, guint16 list_type) { + struct aim_ssi_item *del; - if (!od || !name || !od->ssi.received_data) + if (!od) return -EINVAL; - /* Make sure the master group exists */ - if (aim_ssi_itemlist_find(od->ssi.local, 0x0000, 0x0000) == NULL) - aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL); + if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, NULL, name, list_type))) + return -EINVAL; - /* Add that bad boy */ - aim_ssi_itemlist_add(&od->ssi.local, name, 0x0000, 0xFFFF, AIM_SSI_TYPE_DENY, NULL); - - /* Sync our local list with the server list */ + aim_ssi_itemlist_del(&od->ssi.local, del); return aim_ssi_sync(od); } /** * Deletes a buddy from the list. * * @param od The oscar odion. * @param name The name of the item, or NULL. @@ -855,66 +833,16 @@ int aim_ssi_delgroup(OscarData *od, cons /* Modify the parent group */ aim_ssi_itemlist_rebuildgroup(od->ssi.local, group); /* Sync our local list with the server list */ return aim_ssi_sync(od); } /** - * Deletes a permit buddy from the list. - * - * @param od The oscar odion. - * @param name The name of the item, or NULL. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_ssi_delpermit(OscarData *od, const char *name) -{ - struct aim_ssi_item *del; - - if (!od) - return -EINVAL; - - /* Find the item */ - if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, NULL, name, AIM_SSI_TYPE_PERMIT))) - return -EINVAL; - - /* Remove the item from the list */ - aim_ssi_itemlist_del(&od->ssi.local, del); - - /* Sync our local list with the server list */ - return aim_ssi_sync(od); -} - -/** - * Deletes a deny buddy from the list. - * - * @param od The oscar odion. - * @param name The name of the item, or NULL. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_ssi_deldeny(OscarData *od, const char *name) -{ - struct aim_ssi_item *del; - - if (!od) - return -EINVAL; - - /* Find the item */ - if (!(del = aim_ssi_itemlist_finditem(od->ssi.local, NULL, name, AIM_SSI_TYPE_DENY))) - return -EINVAL; - - /* Remove the item from the list */ - aim_ssi_itemlist_del(&od->ssi.local, del); - - /* Sync our local list with the server list */ - return aim_ssi_sync(od); -} - -/** * Move a buddy from one group to another group. This basically just deletes the * buddy and re-adds it. * * @param od The oscar odion. * @param oldgn The group that the buddy is currently in. * @param newgn The group that the buddy should be moved in to. * @param bn The name of the buddy to be moved. * @return Return 0 if no errors, otherwise return the error number. @@ -1025,27 +953,26 @@ int aim_ssi_rename_group(OscarData *od, /* Sync our local list with the server list */ return aim_ssi_sync(od); } /** * Stores your permit/deny setting on the server, and starts using it. * * @param od The oscar odion. - * @param permdeny Your permit/deny setting. Can be one of the following: + * @param permdeny Your permit/deny setting. For ICQ accounts, it actually affects your visibility + * and has nothing to do with blocking. Can be one of the following: * 1 - Allow all users * 2 - Block all users * 3 - Allow only the users below * 4 - Block only the users below * 5 - Allow only users on my buddy list - * @param vismask A bitmask of the class of users to whom you want to be - * visible. See the AIM_FLAG_BLEH #defines in oscar.h * @return Return 0 if no errors, otherwise return the error number. */ -int aim_ssi_setpermdeny(OscarData *od, guint8 permdeny, guint32 vismask) +int aim_ssi_setpermdeny(OscarData *od, guint8 permdeny) { struct aim_ssi_item *tmp; if (!od || !od->ssi.received_data) return -EINVAL; /* Find the PDINFO item, or add it if it does not exist */ if (!(tmp = aim_ssi_itemlist_finditem(od->ssi.local, NULL, NULL, AIM_SSI_TYPE_PDINFO))) { @@ -1054,19 +981,16 @@ int aim_ssi_setpermdeny(OscarData *od, g aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0x0000, AIM_SSI_TYPE_GROUP, NULL); tmp = aim_ssi_itemlist_add(&od->ssi.local, NULL, 0x0000, 0xFFFF, AIM_SSI_TYPE_PDINFO, NULL); } /* Need to add the 0x00ca TLV to the TLV chain */ aim_tlvlist_replace_8(&tmp->data, 0x00ca, permdeny); - /* Need to add the 0x00cb TLV to the TLV chain */ - aim_tlvlist_replace_32(&tmp->data, 0x00cb, vismask); - /* Sync our local list with the server list */ return aim_ssi_sync(od); } /** * Set buddy icon information * * @param od The oscar odion. @@ -1226,67 +1150,32 @@ int aim_ssi_reqdata(OscarData *od) aim_ssi_freelist(od); aim_genericreq_n_snacid(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_REQDATA); return 0; } /* - * Subtype 0x0005 - Request SSI Data when you have a timestamp and revision - * number. - * - * The data will only be sent if it is newer than the posted local - * timestamp and revision. - * - * Note that the client should never increment the revision, only the server. - * - */ -int aim_ssi_reqifchanged(OscarData *od, time_t timestamp, guint16 numitems) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG))) - return -EINVAL; - - byte_stream_new(&bs, 4+2); - - byte_stream_put32(&bs, timestamp); - byte_stream_put16(&bs, numitems); - - snacid = aim_cachesnac(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_REQIFCHANGED, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_REQIFCHANGED, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - /* Free any current data, just in case */ - aim_ssi_freelist(od); - - return 0; -} - -/* * Subtype 0x0006 - SSI Data. */ static int parsedata(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; guint8 fmtver; /* guess */ guint16 namelen, gid, bid, type; char *name; GSList *data; fmtver = byte_stream_get8(bs); /* Version of ssi data. Should be 0x00 */ od->ssi.numitems += byte_stream_get16(bs); /* # of items in this SSI SNAC */ /* Read in the list */ - while (byte_stream_empty(bs) > 4) { /* last four bytes are timestamp */ + while (byte_stream_bytes_left(bs) > 4) { /* last four bytes are timestamp */ if ((namelen = byte_stream_get16(bs))) name = byte_stream_getstr(bs, namelen); else name = NULL; gid = byte_stream_get16(bs); bid = byte_stream_get16(bs); type = byte_stream_get16(bs); data = aim_tlvlist_readlen(bs, byte_stream_get16(bs)); @@ -1373,17 +1262,17 @@ static int aim_ssi_addmoddel(OscarData * byte_stream_put16(&bs, cur->item->bid); byte_stream_put16(&bs, cur->item->type); byte_stream_put16(&bs, cur->item->data ? aim_tlvlist_size(cur->item->data) : 0); if (cur->item->data) aim_tlvlist_write(&bs, &cur->item->data); } snacid = aim_cachesnac(od, SNAC_FAMILY_FEEDBAG, od->ssi.pending->action, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, od->ssi.pending->action, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, od->ssi.pending->action, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0008 - Incoming SSI add. @@ -1394,17 +1283,17 @@ static int aim_ssi_addmoddel(OscarData * static int parseadd(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; char *name; guint16 len, gid, bid, type; GSList *data; - while (byte_stream_empty(bs)) { + while (byte_stream_bytes_left(bs)) { if ((len = byte_stream_get16(bs))) name = byte_stream_getstr(bs, len); else name = NULL; gid = byte_stream_get16(bs); bid = byte_stream_get16(bs); type = byte_stream_get16(bs); if ((len = byte_stream_get16(bs))) @@ -1432,17 +1321,17 @@ static int parsemod(OscarData *od, FlapC { int ret = 0; aim_rxcallback_t userfunc; char *name; guint16 len, gid, bid, type; GSList *data; struct aim_ssi_item *item; - while (byte_stream_empty(bs)) { + while (byte_stream_bytes_left(bs)) { if ((len = byte_stream_get16(bs))) name = byte_stream_getstr(bs, len); else name = NULL; gid = byte_stream_get16(bs); bid = byte_stream_get16(bs); type = byte_stream_get16(bs); if ((len = byte_stream_get16(bs))) @@ -1484,17 +1373,17 @@ static int parsemod(OscarData *od, FlapC */ static int parsedel(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 gid, bid; struct aim_ssi_item *del; - while (byte_stream_empty(bs)) { + while (byte_stream_bytes_left(bs)) { byte_stream_advance(bs, byte_stream_get16(bs)); gid = byte_stream_get16(bs); bid = byte_stream_get16(bs); byte_stream_get16(bs); byte_stream_advance(bs, byte_stream_get16(bs)); if ((del = aim_ssi_itemlist_find(od->ssi.local, gid, bid))) aim_ssi_itemlist_del(&od->ssi.local, del); @@ -1517,17 +1406,17 @@ static int parsedel(OscarData *od, FlapC static int parseack(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; struct aim_ssi_tmp *cur, *del; /* Read in the success/failure flags from the ack SNAC */ cur = od->ssi.pending; - while (cur && (byte_stream_empty(bs)>0)) { + while (cur && (byte_stream_bytes_left(bs)>0)) { cur->ack = byte_stream_get16(bs); cur = cur->next; } /* * If outcome is 0, then add the item to the item list, or replace the other item, * or remove the old item. If outcome is non-zero, then remove the item from the * local list, or unmodify it, or add it. @@ -1673,55 +1562,16 @@ int aim_ssi_modend(OscarData *od) return -EINVAL; aim_genericreq_n(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_EDITSTOP); return 0; } /* - * Subtype 0x0014 - Grant authorization - * - * Authorizes a contact so they can add you to their contact list. - * - */ -int aim_ssi_sendauth(OscarData *od, char *bn, char *msg) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_FEEDBAG)) || !bn) - return -EINVAL; - - byte_stream_new(&bs, 1+strlen(bn) + 2+(msg ? strlen(msg)+1 : 0) + 2); - - /* Username */ - byte_stream_put8(&bs, strlen(bn)); - byte_stream_putstr(&bs, bn); - - /* Message (null terminated) */ - byte_stream_put16(&bs, msg ? strlen(msg) : 0); - if (msg) { - byte_stream_putstr(&bs, msg); - byte_stream_put8(&bs, 0x00); - } - - /* Unknown */ - byte_stream_put16(&bs, 0x0000); - - snacid = aim_cachesnac(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTH, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTH, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* * Subtype 0x0015 - Receive an authorization grant */ static int receiveauthgrant(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 tmp; char *bn, *msg; @@ -1778,17 +1628,17 @@ int aim_ssi_sendauthrequest(OscarData *o byte_stream_putstr(&bs, msg); byte_stream_put8(&bs, 0x00); } /* Unknown */ byte_stream_put16(&bs, 0x0000); snacid = aim_cachesnac(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREQ, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREQ, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREQ, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0019 - Receive an authorization request @@ -1858,17 +1708,17 @@ int aim_ssi_sendauthreply(OscarData *od, byte_stream_putstr(&bs, msg); byte_stream_put8(&bs, 0x00); } /* Unknown */ byte_stream_put16(&bs, 0x0000); snacid = aim_cachesnac(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREP, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREP, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_SENDAUTHREP, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x001b - Receive an authorization reply @@ -1930,16 +1780,26 @@ static int receiveadded(OscarData *od, F if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, bn); g_free(bn); return ret; } +/* + * If we're on ICQ, then AIM_SSI_TYPE_DENY is used for the "permanently invisible" list. + * AIM_SSI_TYPE_ICQDENY is used for blocking users instead. + */ +guint16 +aim_ssi_getdenyentrytype(OscarData* od) +{ + return od->icq ? AIM_SSI_TYPE_ICQDENY : AIM_SSI_TYPE_DENY; +} + static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { if (snac->subtype == SNAC_SUBTYPE_FEEDBAG_RIGHTSINFO) return parserights(od, conn, mod, frame, snac, bs); else if (snac->subtype == SNAC_SUBTYPE_FEEDBAG_LIST) return parsedata(od, conn, mod, frame, snac, bs); else if (snac->subtype == SNAC_SUBTYPE_FEEDBAG_ADD) diff --git a/purple/libpurple/protocols/oscar/family_icbm.c b/purple/libpurple/protocols/oscar/family_icbm.c --- a/purple/libpurple/protocols/oscar/family_icbm.c +++ b/purple/libpurple/protocols/oscar/family_icbm.c @@ -39,16 +39,17 @@ * use of this field. * * TODO: Split this up into an im.c file an an icbm.c file. It * will be beautiful, you'll see. * * Make sure flap_connection_findbygroup is used by all functions. */ +#include "encoding.h" #include "oscar.h" #include "peer.h" #ifdef _WIN32 #include "win32dep.h" #endif #include "util.h" @@ -103,79 +104,16 @@ void aim_icbm_makecookie(guchar *cookie) /* Should be like "21CBF95" and null terminated */ for (i = 0; i < 7; i++) cookie[i] = 0x30 + ((guchar)rand() % 10); cookie[7] = '\0'; } /* - * Takes a msghdr (and a length) and returns a client type - * code. Note that this is *only a guess* and has a low likelihood - * of actually being accurate. - * - * Its based on experimental data, with the help of Eric Warmenhoven - * who seems to have collected a wide variety of different AIM clients. - * - * - * Heres the current collection: - * 0501 0003 0101 0101 01 AOL Mobile Communicator, WinAIM 1.0.414 - * 0501 0003 0101 0201 01 WinAIM 2.0.847, 2.1.1187, 3.0.1464, - * 4.3.2229, 4.4.2286 - * 0501 0004 0101 0102 0101 WinAIM 4.1.2010, libfaim (right here) - * 0501 0003 0101 02 WinAIM 5 - * 0501 0001 01 iChat x.x, mobile buddies - * 0501 0001 0101 01 AOL v6.0, CompuServe 2000 v6.0, any TOC client - * 0501 0002 0106 WinICQ 5.45.1.3777.85 - * - * Note that in this function, only the feature bytes are tested, since - * the rest will always be the same. - * - */ -guint16 aim_im_fingerprint(const guint8 *msghdr, int len) -{ - static const struct { - guint16 clientid; - int len; - guint8 data[10]; - } fingerprints[] = { - /* AOL Mobile Communicator, WinAIM 1.0.414 */ - { AIM_CLIENTTYPE_MC, - 3, {0x01, 0x01, 0x01}}, - - /* WinAIM 2.0.847, 2.1.1187, 3.0.1464, 4.3.2229, 4.4.2286 */ - { AIM_CLIENTTYPE_WINAIM, - 3, {0x01, 0x01, 0x02}}, - - /* WinAIM 4.1.2010, libfaim */ - { AIM_CLIENTTYPE_WINAIM41, - 4, {0x01, 0x01, 0x01, 0x02}}, - - /* AOL v6.0, CompuServe 2000 v6.0, any TOC client */ - { AIM_CLIENTTYPE_AOL_TOC, - 1, {0x01}}, - - { 0, 0, {0x00}} - }; - int i; - - if (!msghdr || (len <= 0)) - return AIM_CLIENTTYPE_UNKNOWN; - - for (i = 0; fingerprints[i].len; i++) { - if (fingerprints[i].len != len) - continue; - if (memcmp(fingerprints[i].data, msghdr, fingerprints[i].len) == 0) - return fingerprints[i].clientid; - } - - return AIM_CLIENTTYPE_UNKNOWN; -} - -/* * Subtype 0x0001 - Error */ static int error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { aim_snac_t *snac2; guint16 reason, errcode = 0; const char *bn; @@ -282,17 +220,17 @@ int aim_im_setparams(OscarData *od, stru /* These are all read-write */ byte_stream_put32(&bs, params->flags); byte_stream_put16(&bs, params->maxmsglen); byte_stream_put16(&bs, params->maxsenderwarn); byte_stream_put16(&bs, params->maxrecverwarn); byte_stream_put32(&bs, params->minmsginterval); snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0002, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Subtype 0x0004 - Request ICBM parameter information. @@ -340,158 +278,90 @@ static int aim_im_paraminfo(OscarData *o } /** * Subtype 0x0006 - Send an ICBM (instant message). * * * Possible flags: * AIM_IMFLAGS_AWAY -- Marks the message as an autoresponse - * AIM_IMFLAGS_ACK -- Requests that the server send an ack - * when the message is received (of type SNAC_FAMILY_ICBM/0x000c) * AIM_IMFLAGS_OFFLINE--If destination is offline, store it until they are * online (probably ICQ only). * - * Generally, you should use the lowest encoding possible to send - * your message. If you only use basic punctuation and the generic - * Latin alphabet, use ASCII7 (no flags). If you happen to use non-ASCII7 - * characters, but they are all clearly defined in ISO-8859-1, then - * use that. Keep in mind that not all characters in the PC ASCII8 - * character set are defined in the ISO standard. For those cases (most - * notably when the (r) symbol is used), you must use the full UNICODE - * encoding for your message. In UNICODE mode, _all_ characters must - * occupy 16bits, including ones that are not special. (Remember that - * the first 128 UNICODE symbols are equivalent to ASCII7, however they - * must be prefixed with a zero high order byte.) - * - * I strongly discourage the use of UNICODE mode, mainly because none - * of the clients I use can parse those messages (and besides that, - * wchars are difficult and non-portable to handle in most UNIX environments). - * If you really need to include special characters, use the HTML UNICODE - * entities. These are of the form ߪ where 2026 is the hex - * representation of the UNICODE index (in this case, UNICODE - * "Horizontal Ellipsis", or 133 in in ASCII8). - * * Implementation note: Since this is one of the most-used functions * in all of libfaim, it is written with performance in mind. As such, * it is not as clear as it could be in respect to how this message is * supposed to be layed out. Most obviously, tlvlists should be used * instead of writing out the bytes manually. - * - * XXX - more precise verification that we never send SNACs larger than 8192 - * XXX - check SNAC size for multipart - * */ int aim_im_sendch1_ext(OscarData *od, struct aim_sendimext_args *args) { FlapConnection *conn; aim_snacid_t snacid; ByteStream data; guchar cookie[8]; int msgtlvlen; - static const guint8 deffeatures[] = { 0x01, 0x01, 0x01, 0x02 }; if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM))) return -EINVAL; if (!args) return -EINVAL; - if (args->flags & AIM_IMFLAGS_MULTIPART) { - if (args->mpmsg->numparts == 0) - return -EINVAL; - } else { - if (!args->msg || (args->msglen <= 0)) - return -EINVAL; + if (!args->msg || (args->msglen <= 0)) + return -EINVAL; - if (args->msglen > MAXMSGLEN) - return -E2BIG; - } + if (args->msglen > MAXMSGLEN) + return -E2BIG; /* Painfully calculate the size of the message TLV */ msgtlvlen = 1 + 1; /* 0501 */ - - if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) - msgtlvlen += 2 + args->featureslen; - else - msgtlvlen += 2 + sizeof(deffeatures); - - if (args->flags & AIM_IMFLAGS_MULTIPART) { - aim_mpmsg_section_t *sec; - - for (sec = args->mpmsg->parts; sec; sec = sec->next) { - msgtlvlen += 2 /* 0101 */ + 2 /* block len */; - msgtlvlen += 4 /* charset */ + sec->datalen; - } - - } else { - msgtlvlen += 2 /* 0101 */ + 2 /* block len */; - msgtlvlen += 4 /* charset */ + args->msglen; - } + msgtlvlen += 2 + args->featureslen; + msgtlvlen += 2 /* 0101 */ + 2 /* block len */; + msgtlvlen += 4 /* charset */ + args->msglen; byte_stream_new(&data, msgtlvlen + 128); /* Generate an ICBM cookie */ aim_icbm_makecookie(cookie); /* ICBM header */ aim_im_puticbm(&data, cookie, 0x0001, args->destbn); /* Message TLV (type 0x0002) */ byte_stream_put16(&data, 0x0002); byte_stream_put16(&data, msgtlvlen); /* Features TLV (type 0x0501) */ byte_stream_put16(&data, 0x0501); - if (args->flags & AIM_IMFLAGS_CUSTOMFEATURES) { - byte_stream_put16(&data, args->featureslen); - byte_stream_putraw(&data, args->features, args->featureslen); - } else { - byte_stream_put16(&data, sizeof(deffeatures)); - byte_stream_putraw(&data, deffeatures, sizeof(deffeatures)); - } + byte_stream_put16(&data, args->featureslen); + byte_stream_putraw(&data, args->features, args->featureslen); - if (args->flags & AIM_IMFLAGS_MULTIPART) { - aim_mpmsg_section_t *sec; + /* Insert message text in a TLV (type 0x0101) */ + byte_stream_put16(&data, 0x0101); - /* Insert each message part in a TLV (type 0x0101) */ - for (sec = args->mpmsg->parts; sec; sec = sec->next) { - byte_stream_put16(&data, 0x0101); - byte_stream_put16(&data, sec->datalen + 4); - byte_stream_put16(&data, sec->charset); - byte_stream_put16(&data, sec->charsubset); - byte_stream_putraw(&data, (guchar *)sec->data, sec->datalen); - } + /* Message block length */ + byte_stream_put16(&data, args->msglen + 0x04); - } else { + /* Character set */ + byte_stream_put16(&data, args->charset); + /* Character subset -- we always use 0 here */ + byte_stream_put16(&data, 0x0); - /* Insert message text in a TLV (type 0x0101) */ - byte_stream_put16(&data, 0x0101); - - /* Message block length */ - byte_stream_put16(&data, args->msglen + 0x04); - - /* Character set */ - byte_stream_put16(&data, args->charset); - byte_stream_put16(&data, args->charsubset); - - /* Message. Not terminated */ - byte_stream_putraw(&data, (guchar *)args->msg, args->msglen); - } + /* Message. Not terminated */ + byte_stream_putraw(&data, (guchar *)args->msg, args->msglen); /* Set the Autoresponse flag */ if (args->flags & AIM_IMFLAGS_AWAY) { byte_stream_put16(&data, 0x0004); byte_stream_put16(&data, 0x0000); } else { - if (args->flags & AIM_IMFLAGS_ACK) { - /* Set the Request Acknowledge flag */ - byte_stream_put16(&data, 0x0003); - byte_stream_put16(&data, 0x0000); - } + /* Set the Request Acknowledge flag */ + byte_stream_put16(&data, 0x0003); + byte_stream_put16(&data, 0x0000); if (args->flags & AIM_IMFLAGS_OFFLINE) { /* Allow this message to be queued as an offline message */ byte_stream_put16(&data, 0x0006); byte_stream_put16(&data, 0x0000); } } @@ -516,53 +386,26 @@ int aim_im_sendch1_ext(OscarData *od, st if (args->flags & AIM_IMFLAGS_BUDDYREQ) { byte_stream_put16(&data, 0x0009); byte_stream_put16(&data, 0x0000); } /* XXX - should be optional */ snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0006, 0x0000, args->destbn, strlen(args->destbn)+1); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &data); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &data); byte_stream_destroy(&data); /* clean out SNACs over 60sec old */ aim_cleansnacs(od, 60); return 0; } /* - * Simple wrapper for aim_im_sendch1_ext() - * - * You cannot use aim_send_im if you need the HASICON flag. You must - * use aim_im_sendch1_ext directly for that. - * - * aim_send_im also cannot be used if you require UNICODE messages, because - * that requires an explicit message length. Use aim_im_sendch1_ext(). - * - */ -int aim_im_sendch1(OscarData *od, const char *bn, guint16 flags, const char *msg) -{ - struct aim_sendimext_args args; - - args.destbn = bn; - args.flags = flags; - args.msg = msg; - args.msglen = strlen(msg); - args.charset = 0x0000; - args.charsubset = 0x0000; - - /* Make these don't get set by accident -- they need aim_im_sendch1_ext */ - args.flags &= ~(AIM_IMFLAGS_CUSTOMFEATURES | AIM_IMFLAGS_HASICON | AIM_IMFLAGS_MULTIPART); - - return aim_im_sendch1_ext(od, &args); -} - -/* * Subtype 0x0006 - Send a chat invitation. */ int aim_im_sendch2_chatinvite(OscarData *od, const char *bn, const char *msg, guint16 exchange, const char *roomname, guint16 instance) { FlapConnection *conn; ByteStream bs; aim_snacid_t snacid; IcbmCookie *msgcookie; @@ -623,17 +466,17 @@ int aim_im_sendch2_chatinvite(OscarData aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Subtype 0x0006 - Send your icon to a given user. @@ -693,110 +536,17 @@ int aim_im_sendch2_icon(OscarData *od, c byte_stream_put32(&bs, stamp); byte_stream_putraw(&bs, icon, iconlen); byte_stream_putstr(&bs, AIM_ICONIDENT); /* TLV t(0003) */ byte_stream_put16(&bs, 0x0003); byte_stream_put16(&bs, 0x0000); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * Subtype 0x0006 - Send a rich text message. - * - * This only works for ICQ 2001b (thats 2001 not 2000). Better, only - * send it to clients advertising the RTF capability. In fact, if you send - * it to a client that doesn't support that capability, the server will gladly - * bounce it back to you. - * - * You'd think this would be in icq.c, but, well, I'm trying to stick with - * the one-group-per-file scheme as much as possible. This could easily - * be an exception, since Rendezvous IMs are external of the Oscar core, - * and therefore are undefined. Really I just need to think of a good way to - * make an interface similar to what AOL actually uses. But I'm not using COM. - * - */ -int aim_im_sendch2_rtfmsg(OscarData *od, struct aim_sendrtfmsg_args *args) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - guchar cookie[8]; - const char rtfcap[] = {"{97B12751-243C-4334-AD22-D6ABF73F1492}"}; /* OSCAR_CAPABILITY_ICQRTF capability in string form */ - int servdatalen; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM))) - return -EINVAL; - - if (!args || !args->destbn || !args->rtfmsg) - return -EINVAL; - - servdatalen = 2+2+16+2+4+1+2 + 2+2+4+4+4 + 2+4+2+strlen(args->rtfmsg)+1 + 4+4+4+strlen(rtfcap)+1; - - aim_icbm_makecookie(cookie); - - byte_stream_new(&bs, 128+servdatalen); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0006, 0x0000, NULL, 0); - - /* ICBM header */ - aim_im_puticbm(&bs, cookie, 0x0002, args->destbn); - - /* TLV t(0005) - Encompasses everything below. */ - byte_stream_put16(&bs, 0x0005); - byte_stream_put16(&bs, 2+8+16 + 2+2+2 + 2+2 + 2+2+servdatalen); - - byte_stream_put16(&bs, 0x0000); - byte_stream_putraw(&bs, cookie, 8); - byte_stream_putcaps(&bs, OSCAR_CAPABILITY_ICQSERVERRELAY); - - /* t(000a) l(0002) v(0001) */ - byte_stream_put16(&bs, 0x000a); - byte_stream_put16(&bs, 0x0002); - byte_stream_put16(&bs, 0x0001); - - /* t(000f) l(0000) v() */ - byte_stream_put16(&bs, 0x000f); - byte_stream_put16(&bs, 0x0000); - - /* Service Data TLV */ - byte_stream_put16(&bs, 0x2711); - byte_stream_put16(&bs, servdatalen); - - byte_stream_putle16(&bs, 11 + 16 /* 11 + (sizeof CLSID) */); - byte_stream_putle16(&bs, 9); - byte_stream_putcaps(&bs, OSCAR_CAPABILITY_EMPTY); - byte_stream_putle16(&bs, 0); - byte_stream_putle32(&bs, 0); - byte_stream_putle8(&bs, 0); - byte_stream_putle16(&bs, 0x03ea); /* trid1 */ - - byte_stream_putle16(&bs, 14); - byte_stream_putle16(&bs, 0x03eb); /* trid2 */ - byte_stream_putle32(&bs, 0); - byte_stream_putle32(&bs, 0); - byte_stream_putle32(&bs, 0); - - byte_stream_putle16(&bs, 0x0001); - byte_stream_putle32(&bs, 0); - byte_stream_putle16(&bs, strlen(args->rtfmsg)+1); - byte_stream_putraw(&bs, (const guint8 *)args->rtfmsg, strlen(args->rtfmsg)+1); - - byte_stream_putle32(&bs, args->fgcolor); - byte_stream_putle32(&bs, args->bgcolor); - byte_stream_putle32(&bs, strlen(rtfcap)+1); - byte_stream_putraw(&bs, (const guint8 *)rtfcap, strlen(rtfcap)+1); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Cancel a rendezvous invitation. It could be an invitation to @@ -839,17 +589,17 @@ aim_im_sendch2_cancel(PeerConnection *pe aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0006 - Send an "I accept and I've connected to * you" message. */ @@ -874,17 +624,17 @@ aim_im_sendch2_connected(PeerConnection aim_im_puticbm(&bs, peer_conn->cookie, 0x0002, peer_conn->bn); byte_stream_put16(&bs, 0x0005); byte_stream_put16(&bs, 0x001a); byte_stream_put16(&bs, AIM_RENDEZVOUS_CONNECTED); byte_stream_putraw(&bs, peer_conn->cookie, 8); byte_stream_putcaps(&bs, peer_conn->type); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0006 - Send a direct connect rendezvous ICBM. This * could have a number of meanings, depending on the content: * "I want you to connect to me" @@ -929,17 +679,17 @@ aim_im_sendch2_odc_requestdirect(OscarDa aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0006 - Send a direct connect rendezvous ICBM asking the * remote user to connect to us via a proxy server. */ @@ -992,17 +742,17 @@ aim_im_sendch2_odc_requestproxy(OscarDat aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0006 - Send an "I want to send you this file" message * */ @@ -1039,27 +789,16 @@ aim_im_sendch2_sendfile_requestdirect(Os aim_tlvlist_add_raw(&inner_tlvlist, 0x0002, 4, ip); aim_tlvlist_add_raw(&inner_tlvlist, 0x0003, 4, ip); aim_tlvlist_add_16(&inner_tlvlist, 0x0005, port); aim_tlvlist_add_16(&inner_tlvlist, 0x000a, requestnumber); aim_tlvlist_add_noval(&inner_tlvlist, 0x000f); /* TODO: Send 0x0016 and 0x0017 */ -#if 0 - /* TODO: If the following is ever enabled, ensure that it is - * not sent with a receive redirect or stage 3 proxy - * redirect for a file receive (same conditions for - * sending 0x000f above) - */ - aim_tlvlist_add_raw(&inner_tlvlist, 0x000e, 2, "en"); - aim_tlvlist_add_raw(&inner_tlvlist, 0x000d, 8, "us-ascii"); - aim_tlvlist_add_raw(&inner_tlvlist, 0x000c, 24, "Please accept this file."); -#endif - if (filename != NULL) { ByteStream inner_bs; /* Begin TLV t(2711) */ byte_stream_new(&inner_bs, 2+2+4+strlen(filename)+1); byte_stream_put16(&inner_bs, (numfiles > 1) ? 0x0002 : 0x0001); byte_stream_put16(&inner_bs, numfiles); @@ -1078,17 +817,17 @@ aim_im_sendch2_sendfile_requestdirect(Os aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } /** * Subtype 0x0006 - Send a sendfile connect rendezvous ICBM asking the * remote user to connect to us via a proxy server. */ @@ -1131,27 +870,16 @@ aim_im_sendch2_sendfile_requestproxy(Osc /* Send the bitwise complement of the port and ip. As a check? */ ip_comp[0] = ~ip[0]; ip_comp[1] = ~ip[1]; ip_comp[2] = ~ip[2]; ip_comp[3] = ~ip[3]; aim_tlvlist_add_raw(&inner_tlvlist, 0x0016, 4, ip_comp); aim_tlvlist_add_16(&inner_tlvlist, 0x0017, ~pin); -#if 0 - /* TODO: If the following is ever enabled, ensure that it is - * not sent with a receive redirect or stage 3 proxy - * redirect for a file receive (same conditions for - * sending 0x000f above) - */ - aim_tlvlist_add_raw(&inner_tlvlist, 0x000e, 2, "en"); - aim_tlvlist_add_raw(&inner_tlvlist, 0x000d, 8, "us-ascii"); - aim_tlvlist_add_raw(&inner_tlvlist, 0x000c, 24, "Please accept this file."); -#endif - if (filename != NULL) { ByteStream filename_bs; /* Begin TLV t(2711) */ byte_stream_new(&filename_bs, 2+2+4+strlen(filename)+1); byte_stream_put16(&filename_bs, (numfiles > 1) ? 0x0002 : 0x0001); byte_stream_put16(&filename_bs, numfiles); @@ -1171,635 +899,89 @@ aim_im_sendch2_sendfile_requestproxy(Osc aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&hdrbs), hdrbs.data); byte_stream_destroy(&hdrbs); aim_tlvlist_write(&bs, &outer_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_free(outer_tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, snacid, &bs); byte_stream_destroy(&bs); } -/** - * Subtype 0x0006 - Request the status message of the given ICQ user. - * - * @param od The oscar session. - * @param bn The UIN of the user of whom you wish to request info. - * @param type The type of info you wish to request. This should be the current - * state of the user, as one of the AIM_ICQ_STATE_* defines. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_im_sendch2_geticqaway(OscarData *od, const char *bn, int type) +static void +incomingim_ch1_parsemsg(OscarData *od, aim_userinfo_t *userinfo, ByteStream *message, struct aim_incomingim_ch1_args *args) { - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - guchar cookie[8]; + PurpleAccount *account = purple_connection_get_account(od->gc); + /* + * We're interested in the inner TLV 0x101, which contains precious, precious message. + */ + while (byte_stream_bytes_left(message) >= 4) { + guint16 type = byte_stream_get16(message); + guint16 length = byte_stream_get16(message); + if (type == 0x101) { + gchar *msg; + guint16 msglen = length - 4; /* charset + charsubset */ + guint16 charset = byte_stream_get16(message); + byte_stream_advance(message, 2); /* charsubset */ - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICBM)) || !bn) - return -EINVAL; - - aim_icbm_makecookie(cookie); - - byte_stream_new(&bs, 8+2+1+strlen(bn) + 4+0x5e + 4); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0006, 0x0000, NULL, 0); - - /* ICBM header */ - aim_im_puticbm(&bs, cookie, 0x0002, bn); - - /* TLV t(0005) - Encompasses almost everything below. */ - byte_stream_put16(&bs, 0x0005); /* T */ - byte_stream_put16(&bs, 0x005e); /* L */ - { /* V */ - byte_stream_put16(&bs, 0x0000); - - /* Cookie */ - byte_stream_putraw(&bs, cookie, 8); - - /* Put the 16 byte server relay capability */ - byte_stream_putcaps(&bs, OSCAR_CAPABILITY_ICQSERVERRELAY); - - /* TLV t(000a) */ - byte_stream_put16(&bs, 0x000a); - byte_stream_put16(&bs, 0x0002); - byte_stream_put16(&bs, 0x0001); - - /* TLV t(000f) */ - byte_stream_put16(&bs, 0x000f); - byte_stream_put16(&bs, 0x0000); - - /* TLV t(2711) */ - byte_stream_put16(&bs, 0x2711); - byte_stream_put16(&bs, 0x0036); - { /* V */ - byte_stream_putle16(&bs, 0x001b); /* L */ - byte_stream_putle16(&bs, 0x0009); /* Protocol version */ - byte_stream_putcaps(&bs, OSCAR_CAPABILITY_EMPTY); - byte_stream_putle16(&bs, 0x0000); /* Unknown */ - byte_stream_putle16(&bs, 0x0001); /* Client features? */ - byte_stream_putle16(&bs, 0x0000); /* Unknown */ - byte_stream_putle8(&bs, 0x00); /* Unkizown */ - byte_stream_putle16(&bs, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */ - - byte_stream_putle16(&bs, 0x000e); /* L */ - byte_stream_putle16(&bs, 0xffff); /* Sequence number? XXX - This should decrement by 1 with each request */ - byte_stream_putle32(&bs, 0x00000000); /* Unknown */ - byte_stream_putle32(&bs, 0x00000000); /* Unknown */ - byte_stream_putle32(&bs, 0x00000000); /* Unknown */ - - /* The type of status message being requested */ - if (type & AIM_ICQ_STATE_CHAT) - byte_stream_putle16(&bs, 0x03ec); - else if(type & AIM_ICQ_STATE_DND) - byte_stream_putle16(&bs, 0x03eb); - else if(type & AIM_ICQ_STATE_OUT) - byte_stream_putle16(&bs, 0x03ea); - else if(type & AIM_ICQ_STATE_BUSY) - byte_stream_putle16(&bs, 0x03e9); - else if(type & AIM_ICQ_STATE_AWAY) - byte_stream_putle16(&bs, 0x03e8); - - byte_stream_putle16(&bs, 0x0001); /* Status? */ - byte_stream_putle16(&bs, 0x0001); /* Priority of this message? */ - byte_stream_putle16(&bs, 0x0001); /* L */ - byte_stream_putle8(&bs, 0x00); /* String of length L */ - } /* End TLV t(2711) */ - } /* End TLV t(0005) */ - - /* TLV t(0003) */ - byte_stream_put16(&bs, 0x0003); - byte_stream_put16(&bs, 0x0000); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; + msg = byte_stream_getstr(message, msglen); + args->msg = oscar_decode_im(account, userinfo->bn, charset, msg, msglen); + } else { + byte_stream_advance(message, length); + } + } } -/** - * Subtype 0x0006 - Send an ICQ-esque ICBM. - * - * This can be used to send an ICQ authorization reply (deny or grant). It is the "old way." - * The new way is to use SSI. I like the new way a lot better. This seems like such a hack, - * mostly because it's in network byte order. Figuring this stuff out sometimes takes a while, - * but thats ok, because it gives me time to try to figure out what kind of drugs the AOL people - * were taking when they merged the two protocols. - * - * @param bn The destination buddy name. - * @param type The type of message. 0x0007 for authorization denied. 0x0008 for authorization granted. - * @param message The message you want to send, it should be null terminated. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_im_sendch4(OscarData *od, const char *bn, guint16 type, const char *message) +static int +incomingim_ch1(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, ByteStream *bs, guint8 *cookie) { - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - guchar cookie[8]; - - if (!od || !(conn = flap_connection_findbygroup(od, 0x0002))) - return -EINVAL; - - if (!bn || !type || !message) - return -EINVAL; - - byte_stream_new(&bs, 8+3+strlen(bn)+12+strlen(message)+1+4); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0006, 0x0000, NULL, 0); - - aim_icbm_makecookie(cookie); - - /* ICBM header */ - aim_im_puticbm(&bs, cookie, 0x0004, bn); - - /* - * TLV t(0005) - * - * ICQ data (the UIN and the message). - */ - byte_stream_put16(&bs, 0x0005); - byte_stream_put16(&bs, 4 + 2+2+strlen(message)+1); - - /* - * Your UIN - */ - byte_stream_putuid(&bs, od); - - /* - * TLV t(type) l(strlen(message)+1) v(message+NULL) - */ - byte_stream_putle16(&bs, type); - byte_stream_putle16(&bs, strlen(message)+1); - byte_stream_putraw(&bs, (const guint8 *)message, strlen(message)+1); - - /* - * TLV t(0006) l(0000) v() - */ - byte_stream_put16(&bs, 0x0006); - byte_stream_put16(&bs, 0x0000); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0006, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * XXX - I don't see when this would ever get called... - */ -static int outgoingim(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) -{ - int ret = 0; - aim_rxcallback_t userfunc; - guchar cookie[8]; - guint16 channel; - GSList *tlvlist; - char *bn; - int bnlen; - guint16 icbmflags = 0; - guint8 flag1 = 0, flag2 = 0; - gchar *msg = NULL; - aim_tlv_t *msgblock; - - /* ICBM Cookie. */ - aim_icbm_makecookie(cookie); - - /* Channel ID */ - channel = byte_stream_get16(bs); - - if (channel != 0x01) { - purple_debug_misc("oscar", "icbm: ICBM received on unsupported channel. Ignoring. (chan = %04x)\n", channel); - return 0; - } - - bnlen = byte_stream_get8(bs); - bn = byte_stream_getstr(bs, bnlen); - - tlvlist = aim_tlvlist_read(bs); - - if (aim_tlv_gettlv(tlvlist, 0x0003, 1)) - icbmflags |= AIM_IMFLAGS_ACK; - if (aim_tlv_gettlv(tlvlist, 0x0004, 1)) - icbmflags |= AIM_IMFLAGS_AWAY; - - if ((msgblock = aim_tlv_gettlv(tlvlist, 0x0002, 1))) { - ByteStream mbs; - int featurelen, msglen; - - byte_stream_init(&mbs, msgblock->value, msgblock->length); - - byte_stream_get8(&mbs); - byte_stream_get8(&mbs); - for (featurelen = byte_stream_get16(&mbs); featurelen; featurelen--) - byte_stream_get8(&mbs); - byte_stream_get8(&mbs); - byte_stream_get8(&mbs); - - msglen = byte_stream_get16(&mbs) - 4; /* final block length */ - - flag1 = byte_stream_get16(&mbs); - flag2 = byte_stream_get16(&mbs); - - msg = byte_stream_getstr(&mbs, msglen); - } - - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, channel, bn, msg, icbmflags, flag1, flag2); - - g_free(bn); - g_free(msg); - aim_tlvlist_free(tlvlist); - - return ret; -} - -/* - * Ahh, the joys of nearly ridiculous over-engineering. - * - * Not only do AIM ICBM's support multiple channels. Not only do they - * support multiple character sets. But they support multiple character - * sets / encodings within the same ICBM. - * - * These multipart messages allow for complex space savings techniques, which - * seem utterly unnecessary by today's standards. In fact, there is only - * one client still in popular use that still uses this method: AOL for the - * Macintosh, Version 5.0. Obscure, yes, I know. - * - * In modern (non-"legacy") clients, if the user tries to send a character - * that is not ISO-8859-1 or ASCII, the client will send the entire message - * as UNICODE, meaning that every character in the message will occupy the - * full 16 bit UNICODE field, even if the high order byte would be zero. - * Multipart messages prevent this wasted space by allowing the client to - * only send the characters in UNICODE that need to be sent that way, and - * the rest of the message can be sent in whatever the native character - * set is (probably ASCII). - * - * An important note is that sections will be displayed in the order that - * they appear in the ICBM. There is no facility for merging or rearranging - * sections at run time. So if you have, say, ASCII then UNICODE then ASCII, - * you must supply two ASCII sections with a UNICODE in the middle, and incur - * the associated overhead. - * - * Normally I would have laughed and given a firm 'no' to supporting this - * seldom-used feature, but something is attracting me to it. In the future, - * it may be possible to abuse this to send mixed-media messages to other - * open source clients (like encryption or something) -- see faimtest for - * examples of how to do this. - * - * I would definitely recommend avoiding this feature unless you really - * know what you are doing, and/or you have something neat to do with it. - * - */ -int aim_mpmsg_init(OscarData *od, aim_mpmsg_t *mpm) -{ - - memset(mpm, 0, sizeof(aim_mpmsg_t)); - - return 0; -} - -static int mpmsg_addsection(OscarData *od, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, gchar *data, guint16 datalen) -{ - aim_mpmsg_section_t *sec; - - sec = g_malloc(sizeof(aim_mpmsg_section_t)); - - sec->charset = charset; - sec->charsubset = charsubset; - sec->data = data; - sec->datalen = datalen; - sec->next = NULL; - - if (!mpm->parts) - mpm->parts = sec; - else { - aim_mpmsg_section_t *cur; - - for (cur = mpm->parts; cur->next; cur = cur->next) - ; - cur->next = sec; - } - - mpm->numparts++; - - return 0; -} - -int aim_mpmsg_addraw(OscarData *od, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const gchar *data, guint16 datalen) -{ - gchar *dup; - - dup = g_malloc(datalen); - memcpy(dup, data, datalen); - - if (mpmsg_addsection(od, mpm, charset, charsubset, dup, datalen) == -1) { - g_free(dup); - return -1; - } - - return 0; -} - -/* XXX - should provide a way of saying ISO-8859-1 specifically */ -int aim_mpmsg_addascii(OscarData *od, aim_mpmsg_t *mpm, const char *ascii) -{ - gchar *dup; - - if (!(dup = g_strdup(ascii))) - return -1; - - if (mpmsg_addsection(od, mpm, 0x0000, 0x0000, dup, strlen(ascii)) == -1) { - g_free(dup); - return -1; - } - - return 0; -} - -int aim_mpmsg_addunicode(OscarData *od, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen) -{ - gchar *buf; - ByteStream bs; - int i; - - buf = g_malloc(unicodelen * 2); - - byte_stream_init(&bs, (guchar *)buf, unicodelen * 2); - - /* We assume unicode is in /host/ byte order -- convert to network */ - for (i = 0; i < unicodelen; i++) - byte_stream_put16(&bs, unicode[i]); - - if (mpmsg_addsection(od, mpm, 0x0002, 0x0000, buf, byte_stream_curpos(&bs)) == -1) { - g_free(buf); - return -1; - } - - return 0; -} - -void aim_mpmsg_free(OscarData *od, aim_mpmsg_t *mpm) -{ - aim_mpmsg_section_t *cur; - - for (cur = mpm->parts; cur; ) { - aim_mpmsg_section_t *tmp; - - tmp = cur->next; - g_free(cur->data); - g_free(cur); - cur = tmp; - } - - mpm->numparts = 0; - mpm->parts = NULL; - - return; -} - -/* - * Start by building the multipart structures, then pick the first - * human-readable section and stuff it into args->msg so no one gets - * suspicious. - */ -static int incomingim_ch1_parsemsgs(OscarData *od, aim_userinfo_t *userinfo, guint8 *data, int len, struct aim_incomingim_ch1_args *args) -{ - /* Should this be ASCII -> UNICODE -> Custom */ - static const guint16 charsetpri[] = { - AIM_CHARSET_ASCII, /* ASCII first */ - AIM_CHARSET_LATIN_1, /* then ISO-8859-1 */ - AIM_CHARSET_UNICODE, /* UNICODE as last resort */ - }; - static const int charsetpricount = 3; - int i; - ByteStream mbs; - aim_mpmsg_section_t *sec; - - byte_stream_init(&mbs, data, len); - - while (byte_stream_empty(&mbs)) { - guint16 msglen, flag1, flag2; - gchar *msgbuf; - - byte_stream_get8(&mbs); /* 01 */ - byte_stream_get8(&mbs); /* 01 */ - - /* Message string length, including character set info. */ - msglen = byte_stream_get16(&mbs); - if (msglen > byte_stream_empty(&mbs)) - { - purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); - break; - } - - /* Character set info */ - flag1 = byte_stream_get16(&mbs); - flag2 = byte_stream_get16(&mbs); - - /* Message. */ - msglen -= 4; - - /* - * For now, we don't care what the encoding is. Just copy - * it into a multipart struct and deal with it later. However, - * always pad the ending with a NULL. This makes it easier - * to treat ASCII sections as strings. It won't matter for - * UNICODE or binary data, as you should never read past - * the specified data length, which will not include the pad. - * - * XXX - There's an API bug here. For sending, the UNICODE is - * given in host byte order (aim_mpmsg_addunicode), but here - * the received messages are given in network byte order. - * - */ - msgbuf = (gchar *)byte_stream_getraw(&mbs, msglen); - mpmsg_addsection(od, &args->mpmsg, flag1, flag2, msgbuf, msglen); - - } /* while */ - - args->icbmflags |= AIM_IMFLAGS_MULTIPART; /* always set */ - - /* - * Clients that support multiparts should never use args->msg, as it - * will point to an arbitrary section. - * - * Here, we attempt to provide clients that do not support multipart - * messages with something to look at -- hopefully a human-readable - * string. But, failing that, a UNICODE message, or nothing at all. - * - * Which means that even if args->msg is NULL, it does not mean the - * message was blank. - * - */ - for (i = 0; i < charsetpricount; i++) { - for (sec = args->mpmsg.parts; sec; sec = sec->next) { - - if (sec->charset != charsetpri[i]) - continue; - - /* Great. We found one. Fill it in. */ - args->charset = sec->charset; - args->charsubset = sec->charsubset; - - /* Set up the simple flags */ - switch (args->charsubset) - { - case 0x0000: - /* standard subencoding? */ - break; - case 0x000b: - args->icbmflags |= AIM_IMFLAGS_SUBENC_MACINTOSH; - break; - case 0xffff: - /* no subencoding */ - break; - default: - break; - } - - args->msg = sec->data; - args->msglen = sec->datalen; - - return 0; - } - } - - /* No human-readable sections found. Oh well. */ - args->charset = args->charsubset = 0xffff; - args->msg = NULL; - args->msglen = 0; - - return 0; -} - -static int incomingim_ch1(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, guint16 channel, aim_userinfo_t *userinfo, ByteStream *bs, guint8 *cookie) -{ - guint16 type, length, magic1, msglen = 0; + guint16 type, length; aim_rxcallback_t userfunc; int ret = 0; - int rev = 0; struct aim_incomingim_ch1_args args; unsigned int endpos; memset(&args, 0, sizeof(args)); - aim_mpmsg_init(od, &args.mpmsg); - /* * This used to be done using tlvchains. For performance reasons, * I've changed it to process the TLVs in-place. This avoids lots * of per-IM memory allocations. */ - while (byte_stream_empty(bs) >= 4) + while (byte_stream_bytes_left(bs) >= 4) { type = byte_stream_get16(bs); length = byte_stream_get16(bs); - if (length > byte_stream_empty(bs)) + if (length > byte_stream_bytes_left(bs)) { purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); break; } endpos = byte_stream_curpos(bs) + length; if (type == 0x0002) { /* Message Block */ - - /* - * This TLV consists of the following: - * - 0501 -- Unknown - * - Features: Don't know how to interpret these - * - 0101 -- Unknown - * - Message - * - * Slick and possible others reverse 'Features' and 'Messages' section. - * Thus, the TLV could have following layout: - * - 0101 -- Unknown (possibly magic for message section) - * - Message - * - 0501 -- Unknown (possibly magic for features section) - * - Features: Don't know how to interpret these - */ - - magic1 = byte_stream_get16(bs); /* 0501 or 0101 */ - if (magic1 == 0x101) /* Bad, message comes before attributes */ - { - /* Jump to the features section */ - msglen = byte_stream_get16(bs); - bs->offset += msglen; - rev = 1; - - magic1 = byte_stream_get16(bs); /* 0501 */ - } - - if (magic1 != 0x501) - { - purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); - break; - } - - args.featureslen = byte_stream_get16(bs); - if (args.featureslen > byte_stream_empty(bs)) - { - purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); - break; - } - if (args.featureslen == 0) - { - args.features = NULL; - } - else - { - args.features = byte_stream_getraw(bs, args.featureslen); - args.icbmflags |= AIM_IMFLAGS_CUSTOMFEATURES; - } - - if (rev) - { - /* Fix buffer back to message */ - bs->offset -= args.featureslen + 2 + 2 + msglen + 2 + 2; - } - - magic1 = byte_stream_get16(bs); /* 01 01 */ - if (magic1 != 0x101) /* Bad, message comes before attributes */ - { - purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); - break; - } - msglen = byte_stream_get16(bs); - - /* - * The rest of the TLV contains one or more message - * blocks... - */ - incomingim_ch1_parsemsgs(od, userinfo, bs->data + bs->offset - 2 - 2 /* XXX evil!!! */, msglen + 2 + 2, &args); - + ByteStream tlv02; + byte_stream_init(&tlv02, bs->data + bs->offset, length); + incomingim_ch1_parsemsg(od, userinfo, &tlv02, &args); } else if (type == 0x0003) { /* Server Ack Requested */ - args.icbmflags |= AIM_IMFLAGS_ACK; - } else if (type == 0x0004) { /* Message is Auto Response */ - args.icbmflags |= AIM_IMFLAGS_AWAY; - } else if (type == 0x0006) { /* Message was received offline. */ - /* * This flag is set on incoming offline messages for both * AIM and ICQ accounts. */ args.icbmflags |= AIM_IMFLAGS_OFFLINE; - } else if (type == 0x0008) { /* I-HAVE-A-REALLY-PURTY-ICON Flag */ - args.iconlen = byte_stream_get32(bs); byte_stream_get16(bs); /* 0x0001 */ args.iconsum = byte_stream_get16(bs); args.iconstamp = byte_stream_get32(bs); /* * This looks to be a client bug. MacAIM 4.3 will * send this tag, but with all zero values, in the @@ -1807,49 +989,26 @@ static int incomingim_ch1(OscarData *od, * sense whatsoever, so I'm going to say its a bug. * * You really shouldn't advertise a zero-length icon * anyway. * */ if (args.iconlen) args.icbmflags |= AIM_IMFLAGS_HASICON; - } else if (type == 0x0009) { - args.icbmflags |= AIM_IMFLAGS_BUDDYREQ; - } else if (type == 0x000b) { /* Non-direct connect typing notification */ - args.icbmflags |= AIM_IMFLAGS_TYPINGNOT; - } else if (type == 0x0016) { - /* * UTC timestamp for when the message was sent. Only * provided for offline messages. */ args.timestamp = byte_stream_get32(bs); - - } else if (type == 0x0017) { - - if (length > byte_stream_empty(bs)) - { - purple_debug_misc("oscar", "Received an IM containing an invalid message part from %s. They are probably trying to do something malicious.\n", userinfo->bn); - break; - } - g_free(args.extdata); - args.extdatalen = length; - if (args.extdatalen == 0) - args.extdata = NULL; - else - args.extdata = byte_stream_getraw(bs, args.extdatalen); - - } else { - purple_debug_misc("oscar", "incomingim_ch1: unknown TLV 0x%04x (len %d)\n", type, length); } /* * This is here to protect ourselves from ourselves. That * is, if something above doesn't completely parse its value * section, or, worse, overparses it, this will set the * stream where it needs to be in order to land on the next * TLV when the loop continues. @@ -1857,20 +1016,17 @@ static int incomingim_ch1(OscarData *od, */ byte_stream_setpos(bs, endpos); } if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, channel, userinfo, &args); - aim_mpmsg_free(od, &args.mpmsg); - g_free(args.features); - g_free(args.extdata); - + g_free(args.msg); return ret; } static void incomingim_ch2_buddylist(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, aim_userinfo_t *userinfo, IcbmArgsCh2 *args, ByteStream *servdata) { /* * This goes like this... @@ -1886,17 +1042,17 @@ incomingim_ch2_buddylist(OscarData *od, * group name length * group name * num of buddies in group * buddy name length * buddy name * ... * ... */ - while (byte_stream_empty(servdata)) + while (byte_stream_bytes_left(servdata)) { guint16 gnlen, numb; int i; char *gn; gnlen = byte_stream_get16(servdata); gn = byte_stream_getstr(servdata, gnlen); numb = byte_stream_get16(servdata); @@ -1958,57 +1114,58 @@ incomingim_ch2_chat(OscarData *od, FlapC aim_chat_readroominfo(servdata, &args->info.chat.roominfo); args->destructor = (void *)incomingim_ch2_chat_free; } static void incomingim_ch2_icqserverrelay_free(OscarData *od, IcbmArgsCh2 *args) { - g_free((char *)args->info.rtfmsg.rtfmsg); + g_free((char *)args->info.rtfmsg.msg); } /* * The relationship between OSCAR_CAPABILITY_ICQSERVERRELAY and OSCAR_CAPABILITY_ICQRTF is * kind of odd. This sends the client ICQRTF since that is all that I've seen * SERVERRELAY used for. * * Note that this is all little-endian. Cringe. * */ static void incomingim_ch2_icqserverrelay(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, aim_userinfo_t *userinfo, IcbmArgsCh2 *args, ByteStream *servdata) { - guint16 hdrlen, anslen, msglen; + guint16 hdrlen, msglen; - if (byte_stream_empty(servdata) < 24) - /* Someone sent us a short server relay ICBM. Weird. (Maybe?) */ - return; + args->destructor = (void *)incomingim_ch2_icqserverrelay_free; - hdrlen = byte_stream_getle16(servdata); +#define SKIP_HEADER(expected_hdrlen) \ + hdrlen = byte_stream_getle16(servdata); \ + if (hdrlen != expected_hdrlen) { \ + purple_debug_warning("oscar", "Expected to find a header with length " #expected_hdrlen "; ignoring message"); \ + return; \ + } \ byte_stream_advance(servdata, hdrlen); - hdrlen = byte_stream_getle16(servdata); - byte_stream_advance(servdata, hdrlen); + SKIP_HEADER(0x001b); + SKIP_HEADER(0x000e); - args->info.rtfmsg.msgtype = byte_stream_getle16(servdata); - - anslen = byte_stream_getle32(servdata); - byte_stream_advance(servdata, anslen); + args->info.rtfmsg.msgtype = byte_stream_get8(servdata); + /* + * Copied from http://iserverd.khstu.ru/oscar/message.html: + * xx byte message flags + * xx xx word (LE) status code + * xx xx word (LE) priority code + * + * We don't need any of these, so just skip them. + */ + byte_stream_advance(servdata, 1 + 2 + 2); msglen = byte_stream_getle16(servdata); - args->info.rtfmsg.rtfmsg = byte_stream_getstr(servdata, msglen); - - args->info.rtfmsg.fgcolor = byte_stream_getle32(servdata); - args->info.rtfmsg.bgcolor = byte_stream_getle32(servdata); - - hdrlen = byte_stream_getle32(servdata); - byte_stream_advance(servdata, hdrlen); - - args->destructor = (void *)incomingim_ch2_icqserverrelay_free; + args->info.rtfmsg.msg = byte_stream_getstr(servdata, msglen); } static void incomingim_ch2_sendfile_free(OscarData *od, IcbmArgsCh2 *args) { g_free(args->info.sendfile.filename); } @@ -2158,17 +1315,17 @@ static int incomingim_ch2(OscarData *od, * 0x0002 - "Reply request" for a stage 2 proxy (receiver wants to use proxy) * 0x0003 - A third request has been sent; applies only to stage 3 proxied transfers */ if (aim_tlv_gettlv(list2, 0x000a, 1)) args.requestnumber = aim_tlv_get16(list2, 0x000a, 1); /* * Terminate connection/error code. 0x0001 means the other user - * canceled the connection. + * cancelled the connection. */ if (aim_tlv_gettlv(list2, 0x000b, 1)) args.errorcode = aim_tlv_get16(list2, 0x000b, 1); /* * Invitation message / chat description. */ if (aim_tlv_gettlv(list2, 0x000c, 1)) { @@ -2183,30 +1340,16 @@ static int incomingim_ch2(OscarData *od, args.encoding = aim_tlv_getstr(list2, 0x000d, 1); /* * Language. */ if (aim_tlv_gettlv(list2, 0x000e, 1)) args.language = aim_tlv_getstr(list2, 0x000e, 1); -#if 0 - /* - * Unknown -- no value - * - * Maybe means we should connect directly to transfer the file? - * Also used in ICQ Lite Beta 4.0 URLs. Also empty. - */ - /* I don't think this indicates a direct transfer; this flag is - * also present in a stage 1 proxied file send request -- Jonathan */ - if (aim_tlv_gettlv(list2, 0x000f, 1)) { - /* Unhandled */ - } -#endif - /* * Flag meaning we should proxy the file transfer through an AIM server */ if (aim_tlv_gettlv(list2, 0x0010, 1)) args.use_proxy = TRUE; if (strlen(proxyip)) args.proxyip = (char *)proxyip; @@ -2391,57 +1534,25 @@ static int incomingim(OscarData *od, Fla } aim_info_free(&userinfo); g_free(cookie); return ret; } -/* - * Subtype 0x0008 - Send a warning to bn. - * - * Flags: - * AIM_WARN_ANON Send as an anonymous (doesn't count as much) - * - * returns -1 on error (couldn't alloc packet), 0 on success. - * - */ -int aim_im_warn(OscarData *od, FlapConnection *conn, const char *bn, guint32 flags) -{ - ByteStream bs; - aim_snacid_t snacid; - - if (!od || !conn || !bn) - return -EINVAL; - - byte_stream_new(&bs, strlen(bn)+3); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x0008, 0x0000, bn, strlen(bn)+1); - - byte_stream_put16(&bs, (flags & AIM_WARN_ANON) ? 0x0001 : 0x0000); - byte_stream_put8(&bs, strlen(bn)); - byte_stream_putstr(&bs, bn); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0008, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - /* Subtype 0x000a */ static int missedcall(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; guint16 channel, nummissed, reason; aim_userinfo_t userinfo; - while (byte_stream_empty(bs)) { + while (byte_stream_bytes_left(bs)) { channel = byte_stream_get16(bs); aim_info_extract(od, bs, &userinfo); nummissed = byte_stream_get16(bs); reason = byte_stream_get16(bs); if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, channel, &userinfo, nummissed, reason); @@ -2451,19 +1562,17 @@ static int missedcall(OscarData *od, Fla return ret; } /* * Subtype 0x000b * * Possible codes: - * AIM_TRANSFER_DENY_NOTSUPPORTED -- "client does not support" * AIM_TRANSFER_DENY_DECLINE -- "client has declined transfer" - * AIM_TRANSFER_DENY_NOTACCEPTING -- "client is not accepting transfers" * */ int aim_im_denytransfer(OscarData *od, const char *bn, const guchar *cookie, guint16 code) { FlapConnection *conn; ByteStream bs; aim_snacid_t snacid; GSList *tlvlist = NULL; @@ -2480,196 +1589,67 @@ int aim_im_denytransfer(OscarData *od, c byte_stream_put16(&bs, 0x0002); /* channel */ byte_stream_put8(&bs, strlen(bn)); byte_stream_putstr(&bs, bn); aim_tlvlist_add_16(&tlvlist, 0x0003, code); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x000b, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x000b, snacid, &bs); byte_stream_destroy(&bs); return 0; } -static void parse_status_note_text(OscarData *od, guchar *cookie, char *bn, ByteStream *bs) +/* + * Subtype 0x000b. + * Send confirmation for a channel 2 message (Miranda wants it by default). + */ +void +aim_im_send_icq_confirmation(OscarData *od, const char *bn, const guchar *cookie) { - struct aim_icq_info *info; - struct aim_icq_info *prev_info; - char *response; - char *encoding; - char *stripped_encoding; - char *status_note_title; - char *status_note_text; - char *stripped_status_note_text; - char *status_note; - guint32 length; - guint16 version; - guint32 capability; - guint8 message_type; - guint16 status_code; - guint16 text_length; - guint32 request_length; - guint32 response_length; - guint32 encoding_length; - PurpleAccount *account; - PurpleBuddy *buddy; - PurplePresence *presence; - PurpleStatus *status; + ByteStream bs; + aim_snacid_t snacid; + guint32 header_size, data_size; + guint16 cookie2 = (guint16)g_random_int(); - for (prev_info = NULL, info = od->icq_info; info != NULL; prev_info = info, info = info->next) - { - if (memcmp(&info->icbm_cookie, cookie, 8) == 0) - { - if (prev_info == NULL) - od->icq_info = info->next; - else - prev_info->next = info->next; + purple_debug_misc("oscar", "Sending message ack to %s\n", bn); - break; - } - } + header_size = 8 + 2 + 1 + strlen(bn) + 2; + data_size = 2 + 1 + 16 + 4*2 + 2*3 + 4*3 + 1*2 + 2*3 + 1; + byte_stream_new(&bs, header_size + data_size); - if (info == NULL) - return; + /* The message header. */ + aim_im_puticbm(&bs, cookie, 0x0002, bn); + byte_stream_put16(&bs, 0x0003); /* reason */ - status_note_title = info->status_note_title; - g_free(info); + /* The actual message. */ + byte_stream_putle16(&bs, 0x1b); /* subheader #1 length */ + byte_stream_put8(&bs, 0x08); /* protocol version */ + byte_stream_putcaps(&bs, OSCAR_CAPABILITY_EMPTY); + byte_stream_put32(&bs, 0x3); /* client features */ + byte_stream_put32(&bs, 0x0004); /* DC type */ + byte_stream_put16(&bs, cookie2); /* a cookie, chosen by fair dice roll */ + byte_stream_putle16(&bs, 0x0e); /* header #2 len? */ + byte_stream_put16(&bs, cookie2); /* the same cookie again */ + byte_stream_put32(&bs, 0); /* unknown */ + byte_stream_put32(&bs, 0); /* unknown */ + byte_stream_put32(&bs, 0); /* unknown */ + byte_stream_put8(&bs, 0x01); /* plain text message */ + byte_stream_put8(&bs, 0x00); /* no message flags */ + byte_stream_put16(&bs, 0x0000); /* no icq status */ + byte_stream_put16(&bs, 0x0100); /* priority */ + byte_stream_putle16(&bs, 1); /* query message len */ + byte_stream_put8(&bs, 0x00); /* empty query message */ - length = byte_stream_getle16(bs); - if (length != 27) { - purple_debug_misc("oscar", "clientautoresp: incorrect header " - "size; expected 27, received %u.\n", length); - g_free(status_note_title); - return; - } - - version = byte_stream_getle16(bs); - if (version != 9) { - purple_debug_misc("oscar", "clientautoresp: incorrect version; " - "expected 9, received %u.\n", version); - g_free(status_note_title); - return; - } - - capability = aim_locate_getcaps(od, bs, 0x10); - if (capability != OSCAR_CAPABILITY_EMPTY) { - purple_debug_misc("oscar", "clientautoresp: plugin ID is not null.\n"); - g_free(status_note_title); - return; - } - - byte_stream_advance(bs, 2); /* unknown */ - byte_stream_advance(bs, 4); /* client capabilities flags */ - byte_stream_advance(bs, 1); /* unknown */ - byte_stream_advance(bs, 2); /* downcouner? */ - - length = byte_stream_getle16(bs); - if (length != 14) { - purple_debug_misc("oscar", "clientautoresp: incorrect header " - "size; expected 14, received %u.\n", length); - g_free(status_note_title); - return; - } - - byte_stream_advance(bs, 2); /* downcounter? */ - byte_stream_advance(bs, 12); /* unknown */ - - message_type = byte_stream_get8(bs); - if (message_type != 0x1a) { - purple_debug_misc("oscar", "clientautoresp: incorrect message " - "type; expected 0x1a, received 0x%x.\n", message_type); - g_free(status_note_title); - return; - } - - byte_stream_advance(bs, 1); /* message flags */ - - status_code = byte_stream_getle16(bs); - if (status_code != 0) { - purple_debug_misc("oscar", "clientautoresp: incorrect status " - "code; expected 0, received %u.\n", status_code); - g_free(status_note_title); - return; - } - - byte_stream_advance(bs, 2); /* priority code */ - - text_length = byte_stream_getle16(bs); - byte_stream_advance(bs, text_length); /* text */ - - length = byte_stream_getle16(bs); - byte_stream_advance(bs, 18); /* unknown */ - - request_length = byte_stream_getle32(bs); - if (length != 18 + 4 + request_length + 17) { - purple_debug_misc("oscar", "clientautoresp: incorrect block; " - "expected length is %u, got %u.\n", - 18 + 4 + request_length + 17, length); - g_free(status_note_title); - return; - } - - byte_stream_advance(bs, request_length); /* x request */ - byte_stream_advance(bs, 17); /* unknown */ - - length = byte_stream_getle32(bs); - response_length = byte_stream_getle32(bs); - response = byte_stream_getstr(bs, response_length); - encoding_length = byte_stream_getle32(bs); - if (length != 4 + response_length + 4 + encoding_length) { - purple_debug_misc("oscar", "clientautoresp: incorrect block; " - "expected length is %u, got %u.\n", - 4 + response_length + 4 + encoding_length, length); - g_free(status_note_title); - g_free(response); - return; - } - - encoding = byte_stream_getstr(bs, encoding_length); - - account = purple_connection_get_account(od->gc); - - stripped_encoding = oscar_encoding_extract(encoding); - status_note_text = oscar_encoding_to_utf8(account, stripped_encoding, response, response_length); - stripped_status_note_text = purple_markup_strip_html(status_note_text); - - if (stripped_status_note_text != NULL && stripped_status_note_text[0] != 0) - status_note = g_strdup_printf("%s: %s", status_note_title, stripped_status_note_text); - else - status_note = g_strdup(status_note_title); - - g_free(status_note_title); - g_free(response); - g_free(encoding); - g_free(stripped_encoding); - g_free(status_note_text); - g_free(stripped_status_note_text); - - buddy = purple_find_buddy(account, bn); - if (buddy == NULL) - { - purple_debug_misc("oscar", "clientautoresp: buddy %s was not found.\n", bn); - g_free(status_note); - return; - } - - purple_debug_misc("oscar", "clientautoresp: setting status " - "message to \"%s\".\n", status_note); - - presence = purple_buddy_get_presence(buddy); - status = purple_presence_get_active_status(presence); - - purple_prpl_got_user_status(account, bn, - purple_status_get_id(status), - "message", status_note, NULL); - - g_free(status_note); + snacid = aim_cachesnac(od, SNAC_FAMILY_ICBM, 0x000b, 0x0000, NULL, 0); + flap_connection_send_snac(od, flap_connection_findbygroup(od, SNAC_FAMILY_ICBM), SNAC_FAMILY_ICBM, 0x000b, snacid, &bs); + byte_stream_destroy(&bs); } /* * Subtype 0x000b - Receive the response from an ICQ status message * request (in which case this contains the ICQ status message) or * a file transfer or direct IM request was declined. */ static int clientautoresp(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) @@ -2706,17 +1686,17 @@ static int clientautoresp(OscarData *od, if (num1 == 0x4f00 && num2 == 0x3b00) { byte_stream_advance(bs, 86); curpos = byte_stream_curpos(bs); xml = byte_stream_getstr(bs, bs->len - curpos); purple_debug_misc("oscar", "X-Status: Received XML reply\n"); if (xml) { GString *xstatus; - char *tmp1, *tmp2; + char *tmp1, *tmp2, *unescaped_xstatus; /* purple_debug_misc("oscar", "X-Status: XML reply: %s\n", xml); */ xstatus = g_string_new(NULL); tmp1 = strstr(xml, "<title>"); if (tmp1 != NULL) { tmp1 += 13; @@ -2724,39 +1704,42 @@ static int clientautoresp(OscarData *od, if (tmp2 != NULL) g_string_append_len(xstatus, tmp1, tmp2 - tmp1); } tmp1 = strstr(xml, "<desc>"); if (tmp1 != NULL) { tmp1 += 12; tmp2 = strstr(tmp1, "</desc>"); if (tmp2 != NULL) { - if (xstatus->len > 0) + if (xstatus->len > 0 && tmp2 > tmp1) g_string_append(xstatus, " - "); g_string_append_len(xstatus, tmp1, tmp2 - tmp1); } } - if (xstatus->len > 0) { - purple_debug_misc("oscar", "X-Status reply: %s\n", xstatus->str); + unescaped_xstatus = purple_unescape_text(xstatus->str); + g_string_free(xstatus, TRUE); + if (*unescaped_xstatus) { + purple_debug_misc("oscar", "X-Status reply: %s\n", unescaped_xstatus); account = purple_connection_get_account(od->gc); buddy = purple_find_buddy(account, bn); presence = purple_buddy_get_presence(buddy); - status = purple_presence_get_active_status(presence); - purple_prpl_got_user_status(account, bn, - purple_status_get_id(status), - "message", xstatus->str, NULL); + status = purple_presence_get_status(presence, "mood"); + if (status) { + purple_prpl_got_user_status(account, bn, + "mood", + PURPLE_MOOD_NAME, purple_status_get_attr_string(status, PURPLE_MOOD_NAME), + PURPLE_MOOD_COMMENT, unescaped_xstatus, NULL); + } } - g_string_free(xstatus, TRUE); + g_free(unescaped_xstatus); } else { purple_debug_misc("oscar", "X-Status: Can't get XML reply string\n"); } } else { purple_debug_misc("oscar", "X-Status: 0x0004, 0x000b not an xstatus reply\n"); - /* if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, channel, sn, reason); */ } } } else if (channel == 0x0004) { /* ICQ message */ switch (reason) { case 0x0003: { /* ICQ status message. Maybe other stuff too, you never know with these people. */ guint8 statusmsgtype, *msg; @@ -2814,37 +1797,30 @@ static int clientautoresp(OscarData *od, g_free(cookie); g_free(bn); g_free(xml); return ret; } /* - * Subtype 0x000c - Receive an ack after sending an ICBM. - * - * You have to have send the message with the AIM_IMFLAGS_ACK flag set - * (TLV t(0003)). The ack contains the ICBM header of the message you - * sent. - * + * Subtype 0x000c - Receive an ack after sending an ICBM. The ack contains the ICBM header of the message you sent. */ static int msgack(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - aim_rxcallback_t userfunc; guint16 ch; guchar *cookie; char *bn; int ret = 0; cookie = byte_stream_getraw(bs, 8); ch = byte_stream_get16(bs); bn = byte_stream_getstr(bs, byte_stream_get8(bs)); - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, ch, bn); + purple_debug_info("oscar", "Sent message to %s.\n", bn); g_free(bn); g_free(cookie); return ret; } /* @@ -2909,17 +1885,17 @@ int aim_im_sendmtn(OscarData *od, guint1 byte_stream_put8(&bs, strlen(bn)); byte_stream_putstr(&bs, bn); /* * Event (should be 0x0000, 0x0001, or 0x0002 for mtn) */ byte_stream_put16(&bs, event); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0014, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICBM, 0x0014, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0006 - Send eXtra Status request @@ -2958,20 +1934,19 @@ int icq_im_xstatus_request(OscarData *od return -EINVAL; if (!sn) return -EINVAL; fmt = "<Q><PluginID>srvMng</PluginID></Q><srv><id>cAwaySrv</id><req><id>AwayStat</id><trans>2</trans><senderId>%s</senderId></req></srv>\r\n"; account = purple_connection_get_account(od->gc); - xmllen = strlen(fmt) - 2 + strlen(account->username); - statxml = g_malloc(xmllen); - snprintf(statxml, xmllen, fmt, account->username); + statxml = g_strdup_printf(fmt, account->username); + xmllen = strlen(statxml); aim_icbm_makecookie(cookie); byte_stream_new(&bs, 10 + 8 + 2 + 1 + strlen(sn) + 2 + 2 + 2 + 8 + 16 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + sizeof(c_plugindata) + xmllen + 2 + 2); @@ -2984,30 +1959,30 @@ int icq_im_xstatus_request(OscarData *od byte_stream_putraw(&header, pluginid, sizeof(pluginid)); /* Plugin ID */ aim_tlvlist_add_16(&inner_tlvlist, 0x000a, 0x0001); aim_tlvlist_add_noval(&inner_tlvlist, 0x000f); /* Add Plugin Specific Data */ byte_stream_new(&plugindata, (sizeof(c_plugindata) + xmllen)); byte_stream_putraw(&plugindata, c_plugindata, sizeof(c_plugindata)); /* Content of TLV 0x2711 */ - byte_stream_putstr(&plugindata, statxml); + byte_stream_putraw(&plugindata, (const guint8*)statxml, xmllen); aim_tlvlist_add_raw(&inner_tlvlist, 0x2711, (sizeof(c_plugindata) + xmllen), plugindata.data); aim_tlvlist_write(&header, &inner_tlvlist); aim_tlvlist_free(inner_tlvlist); aim_tlvlist_add_raw(&outer_tlvlist, 0x0005, byte_stream_curpos(&header), header.data); aim_tlvlist_add_noval(&outer_tlvlist, 0x0003); /* Empty TLV 0x0003 */ aim_tlvlist_write(&bs, &outer_tlvlist); purple_debug_misc("oscar", "X-Status Request\n"); - flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x0006, 0x0000, snacid, &bs, TRUE); + flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x0006, snacid, &bs, TRUE); aim_tlvlist_free(outer_tlvlist); byte_stream_destroy(&header); byte_stream_destroy(&plugindata); byte_stream_destroy(&bs); g_free(statxml); return 0; @@ -3070,32 +2045,30 @@ int icq_relay_xstatus(OscarData *od, con formatted_msg = purple_status_get_attr_string(status, "message"); if (!formatted_msg) return -EINVAL; msg = purple_markup_strip_html(formatted_msg); if (!msg) return -EINVAL; - len = strlen(fmt) - 6 + strlen(account->username) + strlen(title) + strlen(msg); - statxml = g_malloc(len); - - snprintf(statxml, len, fmt, account->username, title, msg); + statxml = g_strdup_printf(fmt, account->username, title, msg); + len = strlen(statxml); purple_debug_misc("oscar", "X-Status AutoReply: %s, %s\n", formatted_msg, msg); - byte_stream_new(&bs, 10 + 8 + 2 + 1 + strlen(sn) + 2 + sizeof(plugindata) + strlen(statxml)); /* 16 extra */ + byte_stream_new(&bs, 10 + 8 + 2 + 1 + strlen(sn) + 2 + sizeof(plugindata) + len); /* 16 extra */ snacid = aim_cachesnac(od, 0x0004, 0x000b, 0x0000, NULL, 0); aim_im_puticbm(&bs, cookie, 0x0002, sn); byte_stream_put16(&bs, 0x0003); byte_stream_putraw(&bs, plugindata, sizeof(plugindata)); - byte_stream_putraw(&bs, (const guint8 *)statxml, strlen(statxml)); + byte_stream_putraw(&bs, (const guint8*)statxml, len); - flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x000b, 0x0000, snacid, &bs, TRUE); + flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x000b, snacid, &bs, TRUE); g_free(statxml); g_free(msg); byte_stream_destroy(&bs); return 0; } @@ -3130,18 +2103,16 @@ static int mtn_receive(OscarData *od, Fl static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { if (snac->subtype == 0x0001) return error(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x0005) return aim_im_paraminfo(od, conn, mod, frame, snac, bs); - else if (snac->subtype == 0x0006) - return outgoingim(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x0007) return incomingim(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x000a) return missedcall(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x000b) return clientautoresp(od, conn, mod, frame, snac, bs); else if (snac->subtype == 0x000c) return msgack(od, conn, mod, frame, snac, bs); diff --git a/purple/libpurple/protocols/oscar/family_icq.c b/purple/libpurple/protocols/oscar/family_icq.c --- a/purple/libpurple/protocols/oscar/family_icq.c +++ b/purple/libpurple/protocols/oscar/family_icq.c @@ -18,87 +18,114 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* * Family 0x0015 - Encapsulated ICQ. * */ +#include "encoding.h" #include "oscar.h" -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS -int aim_icq_reqofflinemsgs(OscarData *od) +#define AIM_ICQ_INFO_REQUEST 0x04b2 +#define AIM_ICQ_ALIAS_REQUEST 0x04ba + +static +int compare_icq_infos(gconstpointer a, gconstpointer b) { - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - int bslen; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) - return -EINVAL; - - purple_debug_info("oscar", "Requesting offline messages\n"); - - bslen = 2 + 4 + 2 + 2; - - byte_stream_new(&bs, 4 + bslen); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); - - /* For simplicity, don't bother using a tlvlist */ - byte_stream_put16(&bs, 0x0001); - byte_stream_put16(&bs, bslen); - - byte_stream_putle16(&bs, bslen - 2); - byte_stream_putuid(&bs, od); - byte_stream_putle16(&bs, 0x003c); /* I command thee. */ - byte_stream_putle16(&bs, snacid); /* eh. */ - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; + const struct aim_icq_info* aa = a; + const guint16* bb = b; + return aa->reqid - *bb; } -int aim_icq_ackofflinemsgs(OscarData *od) +static void aim_icq_freeinfo(struct aim_icq_info *info) { + int i; + + if (!info) + return; + g_free(info->nick); + g_free(info->first); + g_free(info->last); + g_free(info->email); + g_free(info->homecity); + g_free(info->homestate); + g_free(info->homephone); + g_free(info->homefax); + g_free(info->homeaddr); + g_free(info->mobile); + g_free(info->homezip); + g_free(info->personalwebpage); + if (info->email2) + for (i = 0; i < info->numaddresses; i++) + g_free(info->email2[i]); + g_free(info->email2); + g_free(info->workcity); + g_free(info->workstate); + g_free(info->workphone); + g_free(info->workfax); + g_free(info->workaddr); + g_free(info->workzip); + g_free(info->workcompany); + g_free(info->workdivision); + g_free(info->workposition); + g_free(info->workwebpage); + g_free(info->info); + g_free(info->status_note_title); + g_free(info->auth_request_reason); +} + +static +int error(OscarData *od, aim_modsnac_t *error_snac, ByteStream *bs) { - ByteStream bs; - FlapFrame *frame; - aim_snacid_t snacid; - int bslen; + aim_snac_t *original_snac = aim_remsnac(od, error_snac->id); + guint16 *request_type; + GSList *original_info_ptr; + struct aim_icq_info *original_info; + guint16 reason; + gchar *uin; - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) - return -EINVAL; + if (!original_snac || (original_snac->family != SNAC_FAMILY_ICQ) || !original_snac->data) { + purple_debug_misc("oscar", "icq: the original snac for the error packet was not found"); + g_free(original_snac); + return 0; + } + + request_type = original_snac->data; + original_info_ptr = g_slist_find_custom(od->icq_info, &original_snac->id, compare_icq_infos); + original_info = original_info_ptr->data; + + if (!original_info_ptr) { + purple_debug_misc("oscar", "icq: the request info for the error packet was not found"); + g_free(original_snac); + return 0; + } + + reason = byte_stream_get16(bs); + uin = g_strdup_printf("%u", original_info->uin); + switch (*request_type) { + case AIM_ICQ_INFO_REQUEST: + oscar_user_info_display_error(od, reason, uin); + break; + case AIM_ICQ_ALIAS_REQUEST: + /* Couldn't retrieve an alias for the buddy requesting authorization; have to make do with UIN only. */ + if (original_info->for_auth_request) + oscar_auth_recvrequest(od->gc, uin, NULL, original_info->auth_request_reason); + break; + default: + purple_debug_misc("oscar", "icq: got an error packet with unknown request type %u", *request_type); + break; + } - purple_debug_info("oscar", "Acknowledged receipt of offline messages\n"); - - bslen = 2 + 4 + 2 + 2; - - byte_stream_new(&bs, 4 + bslen); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); - - /* For simplicity, don't bother using a tlvlist */ - byte_stream_put16(&bs, 0x0001); - byte_stream_put16(&bs, bslen); - - byte_stream_putle16(&bs, bslen - 2); - byte_stream_putuid(&bs, od); - byte_stream_putle16(&bs, 0x003e); /* I command thee. */ - byte_stream_putle16(&bs, snacid); /* eh. */ - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; + aim_icq_freeinfo(original_info); + od->icq_info = g_slist_remove(od->icq_info, original_info_ptr); + g_free(original_snac->data); + g_free(original_snac); + return 1; } -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ int aim_icq_setsecurity(OscarData *od, gboolean auth_required, gboolean webaware) { FlapConnection *conn; ByteStream bs; aim_snacid_t snacid; int bslen; @@ -125,17 +152,17 @@ aim_icq_setsecurity(OscarData *od, gbool byte_stream_putle16(&bs, 0x0001); byte_stream_putle8(&bs, webaware); byte_stream_putle8(&bs, 0xf8); byte_stream_putle8(&bs, 0x02); byte_stream_putle8(&bs, 0x01); byte_stream_putle8(&bs, 0x00); byte_stream_putle8(&bs, !auth_required); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, snacid, &bs); byte_stream_destroy(&bs); return 0; } /** * Change your ICQ password. @@ -175,192 +202,117 @@ int aim_icq_changepasswd(OscarData *od, byte_stream_putuid(&bs, od); byte_stream_putle16(&bs, 0x07d0); /* I command thee. */ byte_stream_putle16(&bs, snacid); /* eh. */ byte_stream_putle16(&bs, 0x042e); /* shrug. */ byte_stream_putle16(&bs, passwdlen+1); byte_stream_putraw(&bs, (const guint8 *)passwd, passwdlen); byte_stream_putle8(&bs, '\0'); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, snacid, &bs); byte_stream_destroy(&bs); return 0; } int aim_icq_getallinfo(OscarData *od, const char *uin) { FlapConnection *conn; ByteStream bs; aim_snacid_t snacid; int bslen; struct aim_icq_info *info; + guint16 request_type = AIM_ICQ_INFO_REQUEST; if (!uin || uin[0] < '0' || uin[0] > '9') return -EINVAL; if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) return -EINVAL; bslen = 2 + 4 + 2 + 2 + 2 + 4; byte_stream_new(&bs, 4 + bslen); - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); + snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, &request_type, sizeof(request_type)); /* For simplicity, don't bother using a tlvlist */ byte_stream_put16(&bs, 0x0001); byte_stream_put16(&bs, bslen); byte_stream_putle16(&bs, bslen - 2); byte_stream_putuid(&bs, od); byte_stream_putle16(&bs, 0x07d0); /* I command thee. */ byte_stream_putle16(&bs, snacid); /* eh. */ - byte_stream_putle16(&bs, 0x04b2); /* shrug. */ + byte_stream_putle16(&bs, request_type); /* shrug. */ byte_stream_putle32(&bs, atoi(uin)); - flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE); + flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, snacid, &bs, FALSE); byte_stream_destroy(&bs); /* Keep track of this request and the ICQ number and request ID */ info = (struct aim_icq_info *)g_new0(struct aim_icq_info, 1); info->reqid = snacid; info->uin = atoi(uin); - info->next = od->icq_info; - od->icq_info = info; + od->icq_info = g_slist_prepend(od->icq_info, info); return 0; } -int aim_icq_getalias(OscarData *od, const char *uin) +int aim_icq_getalias(OscarData *od, const char *uin, gboolean for_auth_request, char *auth_request_reason) { FlapConnection *conn; ByteStream bs; aim_snacid_t snacid; int bslen; struct aim_icq_info *info; + guint16 request_type = AIM_ICQ_ALIAS_REQUEST; if (!uin || uin[0] < '0' || uin[0] > '9') return -EINVAL; if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) return -EINVAL; purple_debug_info("oscar", "Requesting ICQ alias for %s\n", uin); bslen = 2 + 4 + 2 + 2 + 2 + 4; byte_stream_new(&bs, 4 + bslen); - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); + snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, &request_type, sizeof(request_type)); /* For simplicity, don't bother using a tlvlist */ byte_stream_put16(&bs, 0x0001); byte_stream_put16(&bs, bslen); byte_stream_putle16(&bs, bslen - 2); byte_stream_putuid(&bs, od); byte_stream_putle16(&bs, 0x07d0); /* I command thee. */ byte_stream_putle16(&bs, snacid); /* eh. */ - byte_stream_putle16(&bs, 0x04ba); /* shrug. */ + byte_stream_putle16(&bs, request_type); /* shrug. */ byte_stream_putle32(&bs, atoi(uin)); - flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE); + flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, snacid, &bs, FALSE); byte_stream_destroy(&bs); /* Keep track of this request and the ICQ number and request ID */ info = (struct aim_icq_info *)g_new0(struct aim_icq_info, 1); info->reqid = snacid; info->uin = atoi(uin); - info->next = od->icq_info; - od->icq_info = info; + info->for_auth_request = for_auth_request; + info->auth_request_reason = g_strdup(auth_request_reason); + od->icq_info = g_slist_prepend(od->icq_info, info); return 0; } -int aim_icq_getsimpleinfo(OscarData *od, const char *uin) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - int bslen; - - if (!uin || uin[0] < '0' || uin[0] > '9') - return -EINVAL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) - return -EINVAL; - - bslen = 2 + 4 + 2 + 2 + 2 + 4; - - byte_stream_new(&bs, 4 + bslen); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); - - /* For simplicity, don't bother using a tlvlist */ - byte_stream_put16(&bs, 0x0001); - byte_stream_put16(&bs, bslen); - - byte_stream_putle16(&bs, bslen - 2); - byte_stream_putuid(&bs, od); - byte_stream_putle16(&bs, 0x07d0); /* I command thee. */ - byte_stream_putle16(&bs, snacid); /* eh. */ - byte_stream_putle16(&bs, 0x051f); /* shrug. */ - byte_stream_putle32(&bs, atoi(uin)); - - flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs, FALSE); - - byte_stream_destroy(&bs); - - return 0; -} - -#if 0 -int aim_icq_sendxmlreq(OscarData *od, const char *xml) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - int bslen; - - if (!xml || !strlen(xml)) - return -EINVAL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ICQ))) - return -EINVAL; - - bslen = 2 + 10 + 2 + strlen(xml) + 1; - - byte_stream_new(&bs, 4 + bslen); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ICQ, 0x0002, 0x0000, NULL, 0); - - /* For simplicity, don't bother using a tlvlist */ - byte_stream_put16(&bs, 0x0001); - byte_stream_put16(&bs, bslen); - - byte_stream_putle16(&bs, bslen - 2); - byte_stream_putuid(&bs, od); - byte_stream_putle16(&bs, 0x07d0); /* I command thee. */ - byte_stream_putle16(&bs, snacid); /* eh. */ - byte_stream_putle16(&bs, 0x0998); /* shrug. */ - byte_stream_putle16(&bs, strlen(xml) + 1); - byte_stream_putraw(&bs, (guint8 *)xml, strlen(xml) + 1); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} -#endif - /* * Send an SMS message. This is the non-US way. The US-way is to IM * their cell phone number (+19195551234). * * We basically construct and send an XML message. The format is: * * full_phone_without_leading_+ * message @@ -441,69 +393,55 @@ int aim_icq_sendsms(OscarData *od, const byte_stream_put32(&bs, 0x00000000); byte_stream_put32(&bs, 0x00000000); byte_stream_put16(&bs, 0x0000); byte_stream_put16(&bs, xmllen); byte_stream_putstr(&bs, xml); byte_stream_put8(&bs, 0x00); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_ICQ, 0x0002, snacid, &bs); byte_stream_destroy(&bs); g_free(xml); g_free(stripped); return 0; } -static void aim_icq_freeinfo(struct aim_icq_info *info) { - int i; +static void +gotalias(OscarData *od, struct aim_icq_info *info) +{ + PurpleConnection *gc = od->gc; + PurpleAccount *account = purple_connection_get_account(gc); + PurpleBuddy *b; + gchar *utf8 = oscar_utf8_try_convert(account, od, info->nick); - if (!info) - return; - g_free(info->nick); - g_free(info->first); - g_free(info->last); - g_free(info->email); - g_free(info->homecity); - g_free(info->homestate); - g_free(info->homephone); - g_free(info->homefax); - g_free(info->homeaddr); - g_free(info->mobile); - g_free(info->homezip); - g_free(info->personalwebpage); - if (info->email2) - for (i = 0; i < info->numaddresses; i++) - g_free(info->email2[i]); - g_free(info->email2); - g_free(info->workcity); - g_free(info->workstate); - g_free(info->workphone); - g_free(info->workfax); - g_free(info->workaddr); - g_free(info->workzip); - g_free(info->workcompany); - g_free(info->workdivision); - g_free(info->workposition); - g_free(info->workwebpage); - g_free(info->info); - g_free(info->status_note_title); - g_free(info); + if (info->for_auth_request) { + oscar_auth_recvrequest(gc, g_strdup_printf("%u", info->uin), utf8, info->auth_request_reason); + } else { + if (utf8 && *utf8) { + gchar who[16]; + g_snprintf(who, sizeof(who), "%u", info->uin); + serv_got_alias(gc, who, utf8); + if ((b = purple_find_buddy(account, who))) { + purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", utf8); + } + } + g_free(utf8); + } } /** * Subtype 0x0003 - Response to SNAC_FAMILY_ICQ/0x002, contains an ICQesque packet. */ static int -icqresponse(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) +icqresponse(OscarData *od, aim_modsnac_t *snac, ByteStream *bs) { - int ret = 0; GSList *tlvlist; aim_tlv_t *datatlv; ByteStream qbs; guint32 ouruin; guint16 cmdlen, cmd, reqid; if (!(tlvlist = aim_tlvlist_read(bs)) || !(datatlv = aim_tlv_gettlv(tlvlist, 0x0001, 1))) { aim_tlvlist_free(tlvlist); @@ -515,63 +453,33 @@ icqresponse(OscarData *od, FlapConnectio cmdlen = byte_stream_getle16(&qbs); ouruin = byte_stream_getle32(&qbs); cmd = byte_stream_getle16(&qbs); reqid = byte_stream_getle16(&qbs); purple_debug_misc("oscar", "icq response: %d bytes, %u, 0x%04x, 0x%04x\n", cmdlen, ouruin, cmd, reqid); - if (cmd == 0x0041) { /* offline message */ -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS - struct aim_icq_offlinemsg msg; - aim_rxcallback_t userfunc; - - memset(&msg, 0, sizeof(msg)); - - msg.sender = byte_stream_getle32(&qbs); - msg.year = byte_stream_getle16(&qbs); - msg.month = byte_stream_getle8(&qbs); - msg.day = byte_stream_getle8(&qbs); - msg.hour = byte_stream_getle8(&qbs); - msg.minute = byte_stream_getle8(&qbs); - msg.type = byte_stream_getle8(&qbs); - msg.flags = byte_stream_getle8(&qbs); - msg.msglen = byte_stream_getle16(&qbs); - msg.msg = byte_stream_getstr(&qbs, msg.msglen); - - if ((userfunc = aim_callhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_OFFLINEMSG))) - ret = userfunc(od, conn, frame, &msg); - - g_free(msg.msg); - - } else if (cmd == 0x0042) { - aim_rxcallback_t userfunc; - - if ((userfunc = aim_callhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_OFFLINEMSGCOMPLETE))) - ret = userfunc(od, conn, frame); -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ - - } else if (cmd == 0x07da) { /* information */ + if (cmd == 0x07da) { /* information */ guint16 subtype; + GSList *info_ptr; struct aim_icq_info *info; - aim_rxcallback_t userfunc; subtype = byte_stream_getle16(&qbs); byte_stream_advance(&qbs, 1); /* 0x0a */ /* find other data from the same request */ - for (info = od->icq_info; info && (info->reqid != reqid); info = info->next); - if (!info) { - info = (struct aim_icq_info *)g_new0(struct aim_icq_info, 1); - info->reqid = reqid; - info->next = od->icq_info; - od->icq_info = info; + info_ptr = g_slist_find_custom(od->icq_info, &reqid, compare_icq_infos); + if (!info_ptr) { + struct aim_icq_info *new_info = (struct aim_icq_info *)g_new0(struct aim_icq_info, 1); + new_info->reqid = reqid; + info_ptr = od->icq_info = g_slist_prepend(od->icq_info, new_info); } + info = info_ptr->data; switch (subtype) { case 0x00a0: { /* hide ip status */ /* nothing */ } break; case 0x00aa: { /* password change status */ /* nothing */ } break; @@ -813,77 +721,64 @@ icqresponse(OscarData *od, FlapConnectio byte_stream_put16(&bs, 0x0003); /* server ACK requested */ byte_stream_put16(&bs, 0x0000); info->uin = atoi(uin); info->status_note_title = status_note_title; memcpy(&info->icbm_cookie, cookie, 8); - info->next = od->icq_info; - od->icq_info = info; + od->icq_info = g_slist_prepend(od->icq_info, info); - flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x0006, 0x0000, snacid, &bs, FALSE); + flap_connection_send_snac_with_priority(od, conn, 0x0004, 0x0006, snacid, &bs, FALSE); byte_stream_destroy(&bs); } g_free(uin); } break; } /* End switch statement */ if (!(snac->flags & 0x0001)) { if (subtype != 0x0104) - if ((userfunc = aim_callhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_INFO))) - ret = userfunc(od, conn, frame, info); + oscar_user_info_display_icq(od, info); if (info->uin && info->nick) - if ((userfunc = aim_callhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_ALIAS))) - ret = userfunc(od, conn, frame, info); + gotalias(od, info); - if (od->icq_info == info) { - od->icq_info = info->next; - } else { - struct aim_icq_info *cur; - for (cur=od->icq_info; (cur->next && (cur->next!=info)); cur=cur->next); - if (cur->next) - cur->next = cur->next->next; - } aim_icq_freeinfo(info); + od->icq_info = g_slist_remove(od->icq_info, info); } } aim_tlvlist_free(tlvlist); - return ret; + return 1; } static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - if (snac->subtype == 0x0003) - return icqresponse(od, conn, mod, frame, snac, bs); + if (snac->subtype == 0x0001) + return error(od, snac, bs); + else if (snac->subtype == 0x0003) + return icqresponse(od, snac, bs); return 0; } static void icq_shutdown(OscarData *od, aim_module_t *mod) { - struct aim_icq_info *del; - - while (od->icq_info) { - del = od->icq_info; - od->icq_info = od->icq_info->next; - aim_icq_freeinfo(del); - } - - return; + GSList *cur; + for (cur = od->icq_info; cur; cur = cur->next) + aim_icq_freeinfo(cur->data); + g_slist_free(od->icq_info); } int icq_modfirst(OscarData *od, aim_module_t *mod) { mod->family = SNAC_FAMILY_ICQ; mod->version = 0x0001; mod->toolid = 0x0110; diff --git a/purple/libpurple/protocols/oscar/family_invite.c b/purple/libpurple/protocols/oscar/family_invite.c deleted file mode 100644 --- a/purple/libpurple/protocols/oscar/family_invite.c +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Purple's oscar protocol plugin - * This file is the legal property of its developers. - * Please see the AUTHORS file distributed alongside this file. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA -*/ - -/* - * Family 0x0006 - This isn't really ever used by anyone anymore. - * - * Once upon a time, there used to be a menu item in AIM clients that - * said something like "Invite a friend to use AIM..." and then it would - * ask for an email address and it would sent a mail to them saying - * how perfectly wonderful the AIM service is and why you should use it - * and click here if you hate the person who sent this to you and want to - * complain and yell at them in a small box with pretty fonts. - * - * I could've sworn libfaim had this implemented once, a long long time ago, - * but I can't find it. - * - * I'm mainly adding this so that I can keep advertising that we support - * group 6, even though we don't. - * - */ - -#include "oscar.h" - -int invite_modfirst(OscarData *od, aim_module_t *mod) -{ - - mod->family = SNAC_FAMILY_INVITE; - mod->version = 0x0001; - mod->toolid = 0x0110; - mod->toolversion = 0x0629; - mod->flags = 0; - strncpy(mod->name, "invite", sizeof(mod->name)); - mod->snachandler = NULL; - - return 0; -} diff --git a/purple/libpurple/protocols/oscar/family_locate.c b/purple/libpurple/protocols/oscar/family_locate.c --- a/purple/libpurple/protocols/oscar/family_locate.c +++ b/purple/libpurple/protocols/oscar/family_locate.c @@ -240,16 +240,20 @@ static const struct { {OSCAR_CAPABILITY_TRILLIANCRYPT, {0xf2, 0xe7, 0xc7, 0xf4, 0xfe, 0xad, 0x4d, 0xfb, 0xb2, 0x35, 0x36, 0x79, 0x8b, 0xdf, 0x00, 0x00}}, {OSCAR_CAPABILITY_EMPTY, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {OSCAR_CAPABILITY_HTML_MSGS, + {0x01, 0x38, 0xca, 0x7b, 0x76, 0x9a, 0x49, 0x15, + 0x88, 0xf2, 0x13, 0xfc, 0x00, 0x97, 0x9e, 0xa8}}, + {OSCAR_CAPABILITY_LAST, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, }; /* Keep this array synchronized with icq_purple_moods. */ static const struct { const char *mood; @@ -578,17 +582,17 @@ aim_userinfo_t *aim_locate_finduserinfo( } guint64 aim_locate_getcaps(OscarData *od, ByteStream *bs, int len) { guint64 flags = 0; int offset; - for (offset = 0; byte_stream_empty(bs) && (offset < len); offset += 0x10) { + for (offset = 0; byte_stream_bytes_left(bs) && (offset < len); offset += 0x10) { guint8 *cap; int i, identified; cap = byte_stream_getraw(bs, 0x10); for (i = 0, identified = 0; !(aim_caps[i].flag & OSCAR_CAPABILITY_LAST); i++) { if (memcmp(&aim_caps[i].data, cap, 0x10) == 0) { flags |= aim_caps[i].flag; @@ -612,17 +616,17 @@ aim_locate_getcaps(OscarData *od, ByteSt } static const char * aim_receive_custom_icon(OscarData *od, ByteStream *bs, int len) { int offset; const char *result = NULL; - for (offset = 0; byte_stream_empty(bs) && (offset < len); offset += 0x10) { + for (offset = 0; byte_stream_bytes_left(bs) && (offset < len); offset += 0x10) { /* check wheather this capability is a custom user icon */ guint8 *cap; int i; cap = byte_stream_getraw(bs, 0x10); for (i = 0; icq_custom_icons[i].mood; i++) { if (memcmp(&icq_custom_icons[i].data, cap, 0x10) == 0) { @@ -638,17 +642,17 @@ aim_receive_custom_icon(OscarData *od, B } guint64 aim_locate_getcaps_short(OscarData *od, ByteStream *bs, int len) { guint64 flags = 0; int offset; - for (offset = 0; byte_stream_empty(bs) && (offset < len); offset += 0x02) { + for (offset = 0; byte_stream_bytes_left(bs) && (offset < len); offset += 0x02) { guint8 *cap; int i, identified; cap = byte_stream_getraw(bs, 0x02); for (i = 0, identified = 0; !(aim_caps[i].flag & OSCAR_CAPABILITY_LAST); i++) { if (memcmp(&aim_caps[i].data[2], cap, 0x02) == 0) { flags |= aim_caps[i].flag; @@ -669,26 +673,23 @@ aim_locate_getcaps_short(OscarData *od, int byte_stream_putcaps(ByteStream *bs, guint64 caps) { int i; if (!bs) return -EINVAL; - for (i = 0; byte_stream_empty(bs); i++) { - + for (i = 0; byte_stream_bytes_left(bs); i++) { if (aim_caps[i].flag == OSCAR_CAPABILITY_LAST) break; if (caps & aim_caps[i].flag) byte_stream_putraw(bs, aim_caps[i].data, 0x10); - } - return 0; } #ifdef LOG_UNKNOWN_TLV static void dumptlv(OscarData *od, guint16 type, ByteStream *bs, guint8 len) { int i; @@ -799,17 +800,17 @@ aim_info_extract(OscarData *od, ByteStre for (curtlv = 0; curtlv < tlvcnt; curtlv++) { guint16 type, length; int endpos; int curpos; type = byte_stream_get16(bs); length = byte_stream_get16(bs); curpos = byte_stream_curpos(bs); - endpos = curpos + MIN(length, byte_stream_empty(bs)); + endpos = curpos + MIN(length, byte_stream_bytes_left(bs)); if (type == 0x0001) { /* * User flags * * Specified as any of the following ORed together: * 0x0001 Unconfirmed account * 0x0002 Unknown bit 2 @@ -1005,17 +1006,17 @@ aim_info_extract(OscarData *od, ByteStre * Continue looping as long as we're able to read type2, * number2, and length2. */ while (byte_stream_curpos(bs) + 4 <= endpos) { type2 = byte_stream_get16(bs); number2 = byte_stream_get8(bs); length2 = byte_stream_get8(bs); - endpos2 = byte_stream_curpos(bs) + MIN(length2, byte_stream_empty(bs)); + endpos2 = byte_stream_curpos(bs) + MIN(length2, byte_stream_bytes_left(bs)); switch (type2) { case 0x0000: { /* This is an official buddy icon? */ /* This is always 5 bytes of "0x02 01 d2 04 72"? */ } break; case 0x0001: { /* A buddy icon checksum */ if ((length2 > 0) && ((number2 == 0x00) || (number2 == 0x01))) { @@ -1160,78 +1161,22 @@ aim_info_extract(OscarData *od, ByteStre byte_stream_setpos(bs, endpos); } aim_locate_adduserinfo(od, outinfo); return 0; } -/* Apparently, this is never called. - * If you activate it, figure out a way to know what mood to pass to - * aim_tlvlist_add_caps() below. --rlaager */ -#if 0 -/* - * Inverse of aim_info_extract() - */ -int -aim_putuserinfo(ByteStream *bs, aim_userinfo_t *info) -{ - GSList *tlvlist = NULL; - - if (!bs || !info) - return -EINVAL; - - byte_stream_put8(bs, strlen(info->bn)); - byte_stream_putstr(bs, info->bn); - - byte_stream_put16(bs, info->warnlevel); - - if (info->present & AIM_USERINFO_PRESENT_FLAGS) - aim_tlvlist_add_16(&tlvlist, 0x0001, info->flags); - if (info->present & AIM_USERINFO_PRESENT_MEMBERSINCE) - aim_tlvlist_add_32(&tlvlist, 0x0002, info->membersince); - if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE) - aim_tlvlist_add_32(&tlvlist, 0x0003, info->onlinesince); - if (info->present & AIM_USERINFO_PRESENT_IDLE) - aim_tlvlist_add_16(&tlvlist, 0x0004, info->idletime); - -/* XXX - So, ICQ_OSCAR_SUPPORT is never defined anywhere... */ -#ifdef ICQ_OSCAR_SUPPORT - if (atoi(info->bn) != 0) { - if (info->present & AIM_USERINFO_PRESENT_ICQEXTSTATUS) - aim_tlvlist_add_16(&tlvlist, 0x0006, info->icqinfo.status); - if (info->present & AIM_USERINFO_PRESENT_ICQIPADDR) - aim_tlvlist_add_32(&tlvlist, 0x000a, info->icqinfo.ipaddr); - } -#endif - - if (info->present & AIM_USERINFO_PRESENT_CAPABILITIES) { - aim_tlvlist_add_caps(&tlvlist, 0x000d, info->capabilities, NULL); - } - - if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) - aim_tlvlist_add_32(&tlvlist, (guint16)((info->flags & AIM_FLAG_AOL) ? 0x0010 : 0x000f), info->sessionlen); - - byte_stream_put16(bs, aim_tlvlist_count(tlvlist)); - aim_tlvlist_write(bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - return 0; -} -#endif - /* * Subtype 0x0001 */ static int error(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - int ret = 0; - aim_rxcallback_t userfunc; aim_snac_t *snac2; guint16 reason; char *bn; snac2 = aim_remsnac(od, snac->id); if (!snac2) { purple_debug_misc("oscar", "locate error: received response from unknown request!\n"); return 0; @@ -1248,24 +1193,22 @@ error(OscarData *od, FlapConnection *con if (!bn) { purple_debug_misc("oscar", "locate error: received response from request without a buddy name!\n"); g_free(snac2); return 0; } reason = byte_stream_get16(bs); - /* Notify the user that we do not have info for this buddy */ - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, reason, bn); + oscar_user_info_display_error(od, reason, bn); g_free(snac2->data); g_free(snac2); - return ret; + return 1; } /* * Subtype 0x0002 * * Request Location services rights. * */ @@ -1385,17 +1328,17 @@ aim_locate_setprofile(OscarData *od, byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0004, 0x0000, NULL, 0); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0004, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0004 - Set your client's capabilities. @@ -1419,63 +1362,28 @@ aim_locate_setcaps(OscarData *od, guint6 byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0004, 0x0000, NULL, 0); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0004, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * Subtype 0x0005 - Request info of another AIM user. - * - * @param bn The buddy name whose info you wish to request. - * @param infotype The type of info you wish to request. - * 0x0001 - Info/profile - * 0x0003 - Away message - * 0x0004 - Capabilities - */ -int -aim_locate_getinfo(OscarData *od, const char *bn, guint16 infotype) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_LOCATE)) || !bn) - return -EINVAL; - - byte_stream_new(&bs, 2+1+strlen(bn)); - - snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0005, 0x0000, NULL, 0); - - byte_stream_put16(&bs, infotype); - byte_stream_put8(&bs, strlen(bn)); - byte_stream_putstr(&bs, bn); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0005, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0004, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* Subtype 0x0006 */ static int userinfo(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; - aim_rxcallback_t userfunc; aim_userinfo_t *userinfo, *userinfo2; GSList *tlvlist; aim_tlv_t *tlv = NULL; userinfo = (aim_userinfo_t *)g_malloc(sizeof(aim_userinfo_t)); aim_info_extract(od, bs, userinfo); tlvlist = aim_tlvlist_read(bs); @@ -1517,150 +1425,22 @@ userinfo(OscarData *od, FlapConnection * aim_tlvlist_free(tlvlist); aim_locate_adduserinfo(od, userinfo); userinfo2 = aim_locate_finduserinfo(od, userinfo->bn); aim_info_free(userinfo); g_free(userinfo); /* Show the info to the user */ - if (userinfo2 != NULL && ((userfunc = aim_callhandler(od, snac->family, snac->subtype)))) - ret = userfunc(od, conn, frame, userinfo2); + oscar_user_info_display_aim(od, userinfo2); return ret; } /* - * Subtype 0x0009 - Set directory profile data. - * - * This is not the same as aim_location_setprofile! - * privacy: 1 to allow searching, 0 to disallow. - * - */ -int aim_locate_setdirinfo(OscarData *od, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - GSList *tlvlist = NULL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_LOCATE))) - return -EINVAL; - - aim_tlvlist_add_16(&tlvlist, 0x000a, privacy); - - if (first) - aim_tlvlist_add_str(&tlvlist, 0x0001, first); - if (last) - aim_tlvlist_add_str(&tlvlist, 0x0002, last); - if (middle) - aim_tlvlist_add_str(&tlvlist, 0x0003, middle); - if (maiden) - aim_tlvlist_add_str(&tlvlist, 0x0004, maiden); - - if (state) - aim_tlvlist_add_str(&tlvlist, 0x0007, state); - if (city) - aim_tlvlist_add_str(&tlvlist, 0x0008, city); - - if (nickname) - aim_tlvlist_add_str(&tlvlist, 0x000c, nickname); - if (zip) - aim_tlvlist_add_str(&tlvlist, 0x000d, zip); - - if (street) - aim_tlvlist_add_str(&tlvlist, 0x0021, street); - - byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); - - snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0009, 0x0000, NULL, 0); - - aim_tlvlist_write(&bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x0009, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * Subtype 0x000b - Huh? What is this? - */ -int aim_locate_000b(OscarData *od, const char *bn) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - - return -EINVAL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_LOCATE)) || !bn) - return -EINVAL; - - byte_stream_new(&bs, 1+strlen(bn)); - - snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x000b, 0x0000, NULL, 0); - - byte_stream_put8(&bs, strlen(bn)); - byte_stream_putstr(&bs, bn); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x000b, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - -/* - * Subtype 0x000f - * - * XXX pass these in better - * - */ -int -aim_locate_setinterests(OscarData *od, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - GSList *tlvlist = NULL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_LOCATE))) - return -EINVAL; - - /* ?? privacy ?? */ - aim_tlvlist_add_16(&tlvlist, 0x000a, privacy); - - if (interest1) - aim_tlvlist_add_str(&tlvlist, 0x0000b, interest1); - if (interest2) - aim_tlvlist_add_str(&tlvlist, 0x0000b, interest2); - if (interest3) - aim_tlvlist_add_str(&tlvlist, 0x0000b, interest3); - if (interest4) - aim_tlvlist_add_str(&tlvlist, 0x0000b, interest4); - if (interest5) - aim_tlvlist_add_str(&tlvlist, 0x0000b, interest5); - - byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); - - snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x000f, 0x0000, NULL, 0); - - aim_tlvlist_write(&bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - flap_connection_send_snac(od, conn, SNAC_FAMILY_LOCATE, 0x000f, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - return 0; -} - -/* * Subtype 0x0015 - Request the info of a user using the short method. This is * what iChat uses. It normally is VERY leniently rate limited. * * @param bn The buddy name whose info you wish to request. * @param flags The bitmask which specifies the type of info you wish to request. * 0x00000001 - Info/profile. * 0x00000002 - Away message. * 0x00000004 - Capabilities. @@ -1678,17 +1458,17 @@ aim_locate_getinfoshort(OscarData *od, c return -EINVAL; byte_stream_new(&bs, 4 + 1 + strlen(bn)); byte_stream_put32(&bs, flags); byte_stream_put8(&bs, strlen(bn)); byte_stream_putstr(&bs, bn); snacid = aim_cachesnac(od, SNAC_FAMILY_LOCATE, 0x0015, 0x0000, bn, strlen(bn)+1); - flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_LOCATE, 0x0015, 0x0000, snacid, &bs, FALSE); + flap_connection_send_snac_with_priority(od, conn, SNAC_FAMILY_LOCATE, 0x0015, snacid, &bs, FALSE); byte_stream_destroy(&bs); return 0; } static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) @@ -1726,25 +1506,16 @@ locate_modfirst(OscarData *od, aim_modul mod->flags = 0; strncpy(mod->name, "locate", sizeof(mod->name)); mod->snachandler = snachandler; mod->shutdown = locate_shutdown; return 0; } -#if 0 //rlaager -const char* aim_get_custom_icon_mood(gint32 no) -{ - if (no >= G_N_ELEMENTS(aim_custom_icons) || no < 1) - return NULL; - return aim_custom_icons[no].mood.mood; -} -#endif - const char* icq_get_custom_icon_description(const char *mood) { int i; if (!(mood && *mood)) return NULL; diff --git a/purple/libpurple/protocols/oscar/family_odir.c b/purple/libpurple/protocols/oscar/family_odir.c deleted file mode 100644 --- a/purple/libpurple/protocols/oscar/family_odir.c +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Purple's oscar protocol plugin - * This file is the legal property of its developers. - * Please see the AUTHORS file distributed alongside this file. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA -*/ - -/* - * Family 0x000f - Newer Search Method - * - * Used for searching for other AIM users by email address, name, - * location, commmon interests, and a few other similar things. - * - */ - -#include "oscar.h" - -/** - * Subtype 0x0002 - Submit a User Search Request - * - * Search for an AIM buddy based on their email address. - * - * @param od The oscar session. - * @param region Should be "us-ascii" unless you know what you're doing. - * @param email The email address you want to search for. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_odir_email(OscarData *od, const char *region, const char *email) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - GSList *tlvlist = NULL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ODIR)) || !region || !email) - return -EINVAL; - - /* Create a TLV chain, write it to the outgoing frame, then free the chain */ - aim_tlvlist_add_str(&tlvlist, 0x001c, region); - aim_tlvlist_add_16(&tlvlist, 0x000a, 0x0001); /* Type of search */ - aim_tlvlist_add_str(&tlvlist, 0x0005, email); - - byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); - - aim_tlvlist_write(&bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ODIR, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ODIR, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - - -/** - * Subtype 0x0002 - Submit a User Search Request - * - * Search for an AIM buddy based on various info - * about the person. - * - * @param od The oscar session. - * @param region Should be "us-ascii" unless you know what you're doing. - * @param first The first name of the person you want to search for. - * @param middle The middle name of the person you want to search for. - * @param last The last name of the person you want to search for. - * @param maiden The maiden name of the person you want to search for. - * @param nick The nick name of the person you want to search for. - * @param city The city where the person you want to search for resides. - * @param state The state where the person you want to search for resides. - * @param country The country where the person you want to search for resides. - * @param zip The zip code where the person you want to search for resides. - * @param address The street address where the person you want to seach for resides. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_odir_name(OscarData *od, const char *region, const char *first, const char *middle, const char *last, const char *maiden, const char *nick, const char *city, const char *state, const char *country, const char *zip, const char *address) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - GSList *tlvlist = NULL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ODIR)) || !region) - return -EINVAL; - - /* Create a TLV chain, write it to the outgoing frame, then free the chain */ - aim_tlvlist_add_str(&tlvlist, 0x001c, region); - aim_tlvlist_add_16(&tlvlist, 0x000a, 0x0000); /* Type of search */ - if (first) - aim_tlvlist_add_str(&tlvlist, 0x0001, first); - if (last) - aim_tlvlist_add_str(&tlvlist, 0x0002, last); - if (middle) - aim_tlvlist_add_str(&tlvlist, 0x0003, middle); - if (maiden) - aim_tlvlist_add_str(&tlvlist, 0x0004, maiden); - if (country) - aim_tlvlist_add_str(&tlvlist, 0x0006, country); - if (state) - aim_tlvlist_add_str(&tlvlist, 0x0007, state); - if (city) - aim_tlvlist_add_str(&tlvlist, 0x0008, city); - if (nick) - aim_tlvlist_add_str(&tlvlist, 0x000c, nick); - if (zip) - aim_tlvlist_add_str(&tlvlist, 0x000d, zip); - if (address) - aim_tlvlist_add_str(&tlvlist, 0x0021, address); - - byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); - - aim_tlvlist_write(&bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ODIR, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ODIR, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - - -/** - * Subtype 0x0002 - Submit a User Search Request - * - * @param od The oscar session. - * @param interest1 An interest you want to search for. - * @return Return 0 if no errors, otherwise return the error number. - */ -int aim_odir_interest(OscarData *od, const char *region, const char *interest) -{ - FlapConnection *conn; - ByteStream bs; - aim_snacid_t snacid; - GSList *tlvlist = NULL; - - if (!od || !(conn = flap_connection_findbygroup(od, SNAC_FAMILY_ODIR)) || !region) - return -EINVAL; - - /* Create a TLV chain, write it to the outgoing frame, then free the chain */ - aim_tlvlist_add_str(&tlvlist, 0x001c, region); - aim_tlvlist_add_16(&tlvlist, 0x000a, 0x0001); /* Type of search */ - if (interest) - aim_tlvlist_add_str(&tlvlist, 0x0001, interest); - - byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); - - aim_tlvlist_write(&bs, &tlvlist); - aim_tlvlist_free(tlvlist); - - snacid = aim_cachesnac(od, SNAC_FAMILY_ODIR, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_ODIR, 0x0002, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); - - return 0; -} - - -/** - * Subtype 0x0003 - Receive Reply From a User Search - * - */ -static int parseresults(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) -{ - int ret = 0; - aim_rxcallback_t userfunc; - guint16 tmp, numresults; - struct aim_odir *results = NULL; - - tmp = byte_stream_get16(bs); /* Unknown */ - tmp = byte_stream_get16(bs); /* Unknown */ - byte_stream_advance(bs, tmp); - - numresults = byte_stream_get16(bs); /* Number of results to follow */ - - /* Allocate a linked list, 1 node per result */ - while (numresults) { - struct aim_odir *new; - GSList *tlvlist = aim_tlvlist_readnum(bs, byte_stream_get16(bs)); - new = (struct aim_odir *)g_malloc(sizeof(struct aim_odir)); - new->first = aim_tlv_getstr(tlvlist, 0x0001, 1); - new->last = aim_tlv_getstr(tlvlist, 0x0002, 1); - new->middle = aim_tlv_getstr(tlvlist, 0x0003, 1); - new->maiden = aim_tlv_getstr(tlvlist, 0x0004, 1); - new->email = aim_tlv_getstr(tlvlist, 0x0005, 1); - new->country = aim_tlv_getstr(tlvlist, 0x0006, 1); - new->state = aim_tlv_getstr(tlvlist, 0x0007, 1); - new->city = aim_tlv_getstr(tlvlist, 0x0008, 1); - new->bn = aim_tlv_getstr(tlvlist, 0x0009, 1); - new->interest = aim_tlv_getstr(tlvlist, 0x000b, 1); - new->nick = aim_tlv_getstr(tlvlist, 0x000c, 1); - new->zip = aim_tlv_getstr(tlvlist, 0x000d, 1); - new->region = aim_tlv_getstr(tlvlist, 0x001c, 1); - new->address = aim_tlv_getstr(tlvlist, 0x0021, 1); - new->next = results; - results = new; - numresults--; - } - - if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) - ret = userfunc(od, conn, frame, results); - - /* Now free everything from above */ - while (results) { - struct aim_odir *del = results; - results = results->next; - g_free(del->first); - g_free(del->last); - g_free(del->middle); - g_free(del->maiden); - g_free(del->email); - g_free(del->country); - g_free(del->state); - g_free(del->city); - g_free(del->bn); - g_free(del->interest); - g_free(del->nick); - g_free(del->zip); - g_free(del->region); - g_free(del->address); - g_free(del); - } - - return ret; -} - -static int -snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) -{ - if (snac->subtype == 0x0003) - return parseresults(od, conn, mod, frame, snac, bs); - - return 0; -} - -int -odir_modfirst(OscarData *od, aim_module_t *mod) -{ - mod->family = SNAC_FAMILY_ODIR; - mod->version = 0x0001; - mod->toolid = 0x0010; - mod->toolversion = 0x0629; - mod->flags = 0; - strncpy(mod->name, "odir", sizeof(mod->name)); - mod->snachandler = snachandler; - - return 0; -} diff --git a/purple/libpurple/protocols/oscar/family_oservice.c b/purple/libpurple/protocols/oscar/family_oservice.c --- a/purple/libpurple/protocols/oscar/family_oservice.c +++ b/purple/libpurple/protocols/oscar/family_oservice.c @@ -68,17 +68,17 @@ aim_srv_clientready(OscarData *od, FlapC byte_stream_put16(&bs, mod->family); byte_stream_put16(&bs, mod->version); byte_stream_put16(&bs, mod->toolid); byte_stream_put16(&bs, mod->toolversion); } } snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0002, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0002, snacid, &bs); byte_stream_destroy(&bs); } /* * Subtype 0x0003 - Host Online * * See comments in conn.c about how the group associations are supposed @@ -92,17 +92,17 @@ aim_srv_clientready(OscarData *od, FlapC * shortly after the rate information is acknowledged. * */ static int hostonline(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int group; - while (byte_stream_empty(bs)) + while (byte_stream_bytes_left(bs)) { group = byte_stream_get16(bs); conn->groups = g_slist_prepend(conn->groups, GUINT_TO_POINTER(group)); } /* * Next step is in the Host Versions handler. * @@ -136,17 +136,17 @@ aim_srv_requestnew(OscarData *od, guint1 if (od->use_ssl) /* Request SSL Connection */ aim_tlvlist_add_noval(&tlvlist, 0x008c); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, snacid, &bs); byte_stream_destroy(&bs); } /* * Join a room of name roomname. This is the first step to joining an * already created room. It's basically a Service Request for * family 0x000e, with a little added on to specify the exchange and room @@ -182,17 +182,17 @@ aim_chat_join(OscarData *od, guint16 exc if (od->use_ssl) /* Request SSL Connection */ aim_tlvlist_add_noval(&tlvlist, 0x008c); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, &csi, sizeof(csi)); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0004, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* Subtype 0x0005 - Redirect */ static int @@ -439,40 +439,17 @@ aim_srv_rates_addparam(OscarData *od, Fl for (tmp = conn->rateclasses; tmp != NULL; tmp = tmp->next) { struct rateclass *rateclass; rateclass = tmp->data; byte_stream_put16(&bs, rateclass->classid); } snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0008, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0008, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); -} - -/* Subtype 0x0009 - Delete Rate Parameter */ -void -aim_srv_rates_delparam(OscarData *od, FlapConnection *conn) -{ - ByteStream bs; - aim_snacid_t snacid; - GSList *tmp; - - byte_stream_new(&bs, 502); - - for (tmp = conn->rateclasses; tmp != NULL; tmp = tmp->next) - { - struct rateclass *rateclass; - rateclass = tmp->data; - byte_stream_put16(&bs, rateclass->classid); - } - - snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0009, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0009, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0008, snacid, &bs); byte_stream_destroy(&bs); } /* Subtype 0x000a - Rate Change */ static int ratechange(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { @@ -553,50 +530,16 @@ serverpause(OscarData *od, FlapConnectio aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame); return ret; } -/* - * Subtype 0x000c - Service Pause Acknowledgement - * - * It is rather important that aim_srv_sendpauseack() gets called for the exact - * same connection that the Server Pause callback was called for, since - * libfaim extracts the data for the SNAC from the connection structure. - * - * Of course, if you don't do that, more bad things happen than just what - * libfaim can cause. - * - */ -void -aim_srv_sendpauseack(OscarData *od, FlapConnection *conn) -{ - ByteStream bs; - aim_snacid_t snacid; - GSList *cur; - - byte_stream_new(&bs, 1014); - - /* - * This list should have all the groups that the original - * Host Online / Server Ready said this host supports. And - * we want them all back after the migration. - */ - for (cur = conn->groups; cur != NULL; cur = cur->next) - byte_stream_put16(&bs, GPOINTER_TO_UINT(cur->data)); - - snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x000c, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x000c, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); -} - /* Subtype 0x000d - Service Resume */ static int serverresume(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int ret = 0; aim_rxcallback_t userfunc; if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) @@ -638,17 +581,17 @@ evilnotify(OscarData *od, FlapConnection aim_rxcallback_t userfunc; guint16 newevil; aim_userinfo_t userinfo; memset(&userinfo, 0, sizeof(aim_userinfo_t)); newevil = byte_stream_get16(bs); - if (byte_stream_empty(bs)) + if (byte_stream_bytes_left(bs)) aim_info_extract(od, bs, &userinfo); if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, newevil, &userinfo); aim_info_free(&userinfo); return ret; @@ -765,46 +708,16 @@ motd(OscarData *od, FlapConnection *conn g_free(msg); aim_tlvlist_free(tlvlist); return ret; } /* - * Subtype 0x0014 - Set privacy flags - * - * Normally 0x03. - * - * Bit 1: Allows other AIM users to see how long you've been idle. - * Bit 2: Allows other AIM users to see how long you've been a member. - * - */ -void -aim_srv_setprivacyflags(OscarData *od, FlapConnection *conn, guint32 flags) -{ - aim_genericreq_l(od, conn, SNAC_FAMILY_OSERVICE, 0x0014, &flags); -} - -/* - * Subtype 0x0016 - No-op - * - * WinAIM sends these every 4min or so to keep the connection alive. Its not - * really necessary. - * - * Wha? No? Since when? I think WinAIM sends an empty channel 5 - * FLAP as a no-op... - */ -void -aim_srv_nop(OscarData *od, FlapConnection *conn) -{ - aim_genericreq_n(od, conn, SNAC_FAMILY_OSERVICE, 0x0016); -} - -/* * Subtype 0x0017 - Set client versions * * If you've seen the clientonline/clientready SNAC you're probably * wondering what the point of this one is. And that point seems to be * that the versions in the client online SNAC are sent too late for the * server to be able to use them to change the protocol for the earlier * login packets (client versions are sent right after Host Online is * received, but client online versions aren't sent until quite a bit later). @@ -832,31 +745,31 @@ aim_srv_setversions(OscarData *od, FlapC if ((mod = aim__findmodulebygroup(od, GPOINTER_TO_UINT(cur->data)))) { byte_stream_put16(&bs, mod->family); byte_stream_put16(&bs, mod->version); } } snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0017, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0017, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0017, snacid, &bs); byte_stream_destroy(&bs); } /* Subtype 0x0018 - Host versions */ static int hostversions(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { int vercount; guint8 *versions; /* This is frivolous. (Thank you SmarterChild.) */ - vercount = byte_stream_empty(bs)/4; - versions = byte_stream_getraw(bs, byte_stream_empty(bs)); + vercount = byte_stream_bytes_left(bs)/4; + versions = byte_stream_getraw(bs, byte_stream_bytes_left(bs)); g_free(versions); /* * Now request rates. */ aim_srv_reqrates(od, conn); return 1; @@ -894,26 +807,16 @@ aim_srv_setextrainfo(OscarData *od, return -EINVAL; if (seticqstatus) { aim_tlvlist_add_32(&tlvlist, 0x0006, icqstatus | AIM_ICQ_STATE_HIDEIP | AIM_ICQ_STATE_DIRECTREQUIREAUTH); } -#if 0 - if (other_stuff_that_isnt_implemented) - { - aim_tlvlist_add_raw(&tlvlist, 0x000c, 0x0025, - chunk_of_x25_bytes_with_ip_address_etc); - aim_tlvlist_add_raw(&tlvlist, 0x0011, 0x0005, unknown 0x01 61 10 f6 41); - aim_tlvlist_add_16(&tlvlist, 0x0012, unknown 0x00 00); - } -#endif - if (setstatusmsg) { size_t statusmsglen, itmsurllen; ByteStream tmpbs; statusmsglen = (statusmsg != NULL) ? strlen(statusmsg) : 0; itmsurllen = (itmsurl != NULL) ? strlen(itmsurl) : 0; @@ -927,23 +830,67 @@ aim_srv_setextrainfo(OscarData *od, } byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); aim_tlvlist_write(&bs, &tlvlist); aim_tlvlist_free(tlvlist); snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x001e, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x001e, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x001e, snacid, &bs); byte_stream_destroy(&bs); return 0; } +/* Send dummy DC (direct connect) information to the server. + * Direct connect is ICQ's counterpart for AIM's DirectIM, + * as far as I can tell. Anyway, we don't support it; + * the reason to send this packet is that some clients + * (Miranda, QIP) won't send us channel 2 ICBM messages + * unless we specify DC version >= 8. + * + * See #12044 for more information. + */ +void +aim_srv_set_dc_info(OscarData *od) +{ + ByteStream bs, tlv0c; + aim_snacid_t snacid; + GSList *tlvlist = NULL; + + /* http://iserverd.khstu.ru/oscar/snac_01_1e.html has a nice analysis of what goes in 0xc tlv. + * Kopete sends a dummy DC info, too, so I just copied the values from them. + */ + byte_stream_new(&tlv0c, 4*2 + 1 + 2 + 4*6 + 2); + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put8(&tlv0c, 0x0); /* We don't support DC */ + byte_stream_put16(&tlv0c, 8); /* DC version */ + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put32(&tlv0c, 0x50); + byte_stream_put32(&tlv0c, 0x3); + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put32(&tlv0c, 0x0); + byte_stream_put16(&tlv0c, 0x0); + aim_tlvlist_add_raw(&tlvlist, 0x000c, byte_stream_curpos(&tlv0c), tlv0c.data); + byte_stream_destroy(&tlv0c); + + byte_stream_new(&bs, aim_tlvlist_size(tlvlist)); + aim_tlvlist_write(&bs, &tlvlist); + aim_tlvlist_free(tlvlist); + + snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x001e, 0x0000, NULL, 0); + flap_connection_send_snac(od, flap_connection_findbygroup(od, SNAC_FAMILY_ICBM), SNAC_FAMILY_OSERVICE, 0x001e, snacid, &bs); + + byte_stream_destroy(&bs); +} + /** * Starting this past week (26 Mar 2001, say), AOL has started sending * this nice little extra SNAC. AFAIK, it has never been used until now. * * The request contains eight bytes. The first four are an offset, the * second four are a length. * * The offset is an offset into aim.exe when it is mapped during execution @@ -1072,90 +1019,72 @@ aim_sendmemblock(OscarData *od, FlapConn #endif } else purple_debug_warning("oscar", "sendmemblock: unknown hash request\n"); } snacid = aim_cachesnac(od, SNAC_FAMILY_OSERVICE, 0x0020, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0020, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_OSERVICE, 0x0020, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0021 - Receive our extended status * * This is used for iChat's "available" messages, and maybe ICQ extended * status messages? It's also used to tell the client whether or not it * needs to upload an SSI buddy icon... who engineers this stuff, anyway? */ static int aim_parse_extstatus(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { - guint16 type; - guint8 flags, length; + guint16 type = byte_stream_get16(bs); + if (type == 0x0000 || type == 0x0001) { + /* buddy icon checksum */ + /* not sure what the difference between 1 and 0 is */ + guint8 flags = byte_stream_get8(bs); + guint8 length = byte_stream_get8(bs); + guint8 *md5 = byte_stream_getraw(bs, length); - type = byte_stream_get16(bs); - flags = byte_stream_get8(bs); - length = byte_stream_get8(bs); - - /* - * A flag of 0x01 could mean "this is the checksum we have for you" - * A flag of 0x40 could mean "I don't have your icon, upload it" - */ - - switch (type) { - case 0x0000: - case 0x0001: { /* buddy icon checksum */ - /* not sure what the difference between 1 and 0 is */ - guint8 *md5 = byte_stream_getraw(bs, length); - - if ((flags == 0x00) || (flags == 0x41)) { - if (!flap_connection_getbytype(od, SNAC_FAMILY_BART) && !od->iconconnecting) { - od->iconconnecting = TRUE; - od->set_icon = TRUE; - aim_srv_requestnew(od, SNAC_FAMILY_BART); - } else { - PurpleAccount *account = purple_connection_get_account(od->gc); - PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account); - if (img == NULL) { - aim_ssi_delicon(od); - } else { - - purple_debug_info("oscar", - "Uploading icon to icon server\n"); - aim_bart_upload(od, purple_imgstore_get_data(img), - purple_imgstore_get_size(img)); - purple_imgstore_unref(img); - } - } - } else if (flags == 0x81) { + if ((flags == 0x00) || (flags == 0x41)) { + if (!flap_connection_getbytype(od, SNAC_FAMILY_BART) && !od->iconconnecting) { + od->iconconnecting = TRUE; + od->set_icon = TRUE; + aim_srv_requestnew(od, SNAC_FAMILY_BART); + } else { PurpleAccount *account = purple_connection_get_account(od->gc); PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account); - if (img == NULL) + if (img == NULL) { aim_ssi_delicon(od); - else { - aim_ssi_seticon(od, md5, length); + } else { + + purple_debug_info("oscar", + "Uploading icon to icon server\n"); + aim_bart_upload(od, purple_imgstore_get_data(img), + purple_imgstore_get_size(img)); purple_imgstore_unref(img); } } + } else if (flags == 0x81) { + PurpleAccount *account = purple_connection_get_account(od->gc); + PurpleStoredImage *img = purple_buddy_icons_find_account_icon(account); + if (img == NULL) + aim_ssi_delicon(od); + else { + aim_ssi_seticon(od, md5, length); + purple_imgstore_unref(img); + } + } - g_free(md5); - } break; - - case 0x0002: { - /* We just set an available message? */ - /* there is a second length that is just for the message */ - char *msg = byte_stream_getstr(bs, byte_stream_get16(bs)); - g_free(msg); - } break; + g_free(md5); } return 0; } static int snachandler(OscarData *od, FlapConnection *conn, aim_module_t *mod, FlapFrame *frame, aim_modsnac_t *snac, ByteStream *bs) { diff --git a/purple/libpurple/protocols/oscar/family_translate.c b/purple/libpurple/protocols/oscar/family_translate.c deleted file mode 100644 --- a/purple/libpurple/protocols/oscar/family_translate.c +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Purple's oscar protocol plugin - * This file is the legal property of its developers. - * Please see the AUTHORS file distributed alongside this file. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA -*/ - -/* - * Family 0x000c - Translation. - * - * I have no idea why this group was issued. I have never seen anything - * that uses it. From what I remember, the last time I tried to poke at - * the server with this group, it whined about not supporting it. - * - * But we advertise it anyway, because its fun. - * - */ - -#include "oscar.h" - -int translate_modfirst(OscarData *od, aim_module_t *mod) -{ - - mod->family = SNAC_FAMILY_TRANSLATE; - mod->version = 0x0001; - mod->toolid = 0x0104; - mod->toolversion = 0x0001; - mod->flags = 0; - strncpy(mod->name, "translate", sizeof(mod->name)); - mod->snachandler = NULL; - - return 0; -} diff --git a/purple/libpurple/protocols/oscar/family_userlookup.c b/purple/libpurple/protocols/oscar/family_userlookup.c --- a/purple/libpurple/protocols/oscar/family_userlookup.c +++ b/purple/libpurple/protocols/oscar/family_userlookup.c @@ -70,17 +70,17 @@ int aim_search_address(OscarData *od, co if (!conn || !address) return -EINVAL; byte_stream_new(&bs, strlen(address)); byte_stream_putstr(&bs, address); snacid = aim_cachesnac(od, SNAC_FAMILY_USERLOOKUP, 0x0002, 0x0000, address, strlen(address)+1); - flap_connection_send_snac(od, conn, SNAC_FAMILY_USERLOOKUP, 0x0002, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, SNAC_FAMILY_USERLOOKUP, 0x0002, snacid, &bs); byte_stream_destroy(&bs); return 0; } /* * Subtype 0x0003 diff --git a/purple/libpurple/protocols/oscar/flap_connection.c b/purple/libpurple/protocols/oscar/flap_connection.c --- a/purple/libpurple/protocols/oscar/flap_connection.c +++ b/purple/libpurple/protocols/oscar/flap_connection.c @@ -207,27 +207,27 @@ static gboolean flap_connection_send_que * * @param data The optional bytestream that makes up the data portion * of this SNAC. For empty SNACs this should be NULL. * @param high_priority If TRUE, the SNAC will be queued normally if * needed. If FALSE, it wil be queued separately, to be sent * only if all high priority SNACs have been sent. */ void -flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority) +flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data, gboolean high_priority) { FlapFrame *frame; guint32 length; gboolean enqueue = FALSE; struct rateclass *rateclass; length = data != NULL ? data->offset : 0; frame = flap_frame_new(od, 0x02, 10 + length); - aim_putsnac(&frame->data, family, subtype, flags, snacid); + aim_putsnac(&frame->data, family, subtype, snacid); if (length > 0) { byte_stream_rewind(data); byte_stream_putbs(&frame->data, data, length); } if (conn->queued_timeout != 0) @@ -279,19 +279,19 @@ flap_connection_send_snac_with_priority( return; } flap_connection_send(conn, frame); } void -flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data) +flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data) { - flap_connection_send_snac_with_priority(od, conn, family, subtype, flags, snacid, data, TRUE); + flap_connection_send_snac_with_priority(od, conn, family, subtype, snacid, data, TRUE); } /** * This sends an empty channel 4 FLAP. This is sent to signify * that we're logging off. This shouldn't really be necessary-- * usually the AIM server will detect that the TCP connection has * been destroyed--but it's good practice. */ @@ -430,21 +430,26 @@ static gboolean flap_connection_destroy_cb(gpointer data) { FlapConnection *conn; OscarData *od; PurpleAccount *account; aim_rxcallback_t userfunc; conn = data; + /* Explicitly added for debugging #5927. Don't re-order this, only + * consider removing it. + */ + purple_debug_info("oscar", "Destroying FLAP connection %p\n", conn); + od = conn->od; account = purple_connection_get_account(od->gc); - purple_debug_info("oscar", "Destroying oscar connection of " - "type 0x%04hx. Disconnect reason is %d\n", + purple_debug_info("oscar", "Destroying oscar connection (%p) of " + "type 0x%04hx. Disconnect reason is %d\n", conn, conn->type, conn->disconnect_reason); od->oscar_connections = g_slist_remove(od->oscar_connections, conn); if ((userfunc = aim_callhandler(od, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR))) userfunc(od, conn, NULL, conn->disconnect_code, conn->error_message); /* @@ -570,17 +575,17 @@ flap_connection_destroy(FlapConnection * void flap_connection_schedule_destroy(FlapConnection *conn, OscarDisconnectReason reason, const gchar *error_message) { if (conn->destroy_timeout != 0) /* Already taken care of */ return; purple_debug_info("oscar", "Scheduling destruction of FLAP " - "connection of type 0x%04hx\n", conn->type); + "connection %p of type 0x%04hx\n", conn, conn->type); conn->disconnect_reason = reason; g_free(conn->error_message); conn->error_message = g_strdup(error_message); conn->destroy_timeout = purple_timeout_add(0, flap_connection_destroy_cb, conn); } /** * In OSCAR, every connection has a set of SNAC groups associated @@ -728,17 +733,17 @@ flap_frame_new(OscarData *od, guint16 ch } static void parse_snac(OscarData *od, FlapConnection *conn, FlapFrame *frame) { aim_module_t *cur; aim_modsnac_t snac; - if (byte_stream_empty(&frame->data) < 10) + if (byte_stream_bytes_left(&frame->data) < 10) return; snac.family = byte_stream_get16(&frame->data); snac.subtype = byte_stream_get16(&frame->data); snac.flags = byte_stream_get16(&frame->data); snac.id = byte_stream_get32(&frame->data); /* SNAC flags are apparently uniform across all SNACs, so we handle them here */ @@ -795,17 +800,17 @@ parse_fakesnac(OscarData *od, FlapConnec } static void parse_flap_ch4(OscarData *od, FlapConnection *conn, FlapFrame *frame) { GSList *tlvlist; char *msg = NULL; - if (byte_stream_empty(&frame->data) == 0) { + if (byte_stream_bytes_left(&frame->data) == 0) { /* XXX should do something with this */ return; } /* An ICQ account is logging in */ if (conn->type == SNAC_FAMILY_AUTH) { parse_fakesnac(od, conn, frame, 0x0017, 0x0003); @@ -926,28 +931,16 @@ flap_connection_recv(FlapConnection *con /* All FLAP frames must start with the byte 0x2a */ if (aimutil_get8(&conn->header[0]) != 0x2a) { flap_connection_schedule_destroy(conn, OSCAR_DISCONNECT_INVALID_DATA, NULL); break; } - /* Verify the sequence number sent by the server. */ -#if 0 - /* TODO: Need to initialize conn->seqnum_in somewhere before we can use this. */ - if (aimutil_get16(&conn->header[1]) != conn->seqnum_in++) - { - /* Received an out-of-order FLAP! */ - flap_connection_schedule_destroy(conn, - OSCAR_DISCONNECT_INVALID_DATA, NULL); - break; - } -#endif - /* Initialize a new temporary FlapFrame for incoming data */ conn->buffer_incoming.channel = aimutil_get8(&conn->header[1]); conn->buffer_incoming.seqnum = aimutil_get16(&conn->header[2]); conn->buffer_incoming.data.len = aimutil_get16(&conn->header[4]); conn->buffer_incoming.data.data = g_new(guint8, conn->buffer_incoming.data.len); conn->buffer_incoming.data.offset = 0; } @@ -1069,18 +1062,18 @@ send_cb(gpointer data, gint source, Purp static void flap_connection_send_byte_stream(ByteStream *bs, FlapConnection *conn, size_t count) { if (conn == NULL) return; /* Make sure we don't send past the end of the bs */ - if (count > byte_stream_empty(bs)) - count = byte_stream_empty(bs); /* truncate to remaining space */ + if (count > byte_stream_bytes_left(bs)) + count = byte_stream_bytes_left(bs); /* truncate to remaining space */ if (count == 0) return; /* Add everything to our outgoing buffer */ purple_circ_buffer_append(conn->buffer_outgoing, bs->data, count); /* If we haven't already started writing stuff, then start the cycle */ diff --git a/purple/libpurple/protocols/oscar/libaim.c b/purple/libpurple/protocols/oscar/libaim.c --- a/purple/libpurple/protocols/oscar/libaim.c +++ b/purple/libpurple/protocols/oscar/libaim.c @@ -20,16 +20,17 @@ * */ /* libaim is the AIM protocol plugin. It is linked against liboscar, * which contains all the shared implementation code with libicq */ #include "oscarcommon.h" +#include "oscar.h" static PurplePluginProtocolInfo prpl_info = { OPT_PROTO_MAIL_CHECK | OPT_PROTO_IM_IMAGE, NULL, /* user_splits */ NULL, /* protocol_options */ {"gif,jpeg,bmp,ico", 0, 0, 100, 100, 7168, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */ oscar_list_icon_aim, /* list_icon */ @@ -52,17 +53,17 @@ static PurplePluginProtocolInfo prpl_inf oscar_add_buddy, /* add_buddy */ NULL, /* add_buddies */ oscar_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ oscar_add_permit, /* add_permit */ oscar_add_deny, /* add_deny */ oscar_rem_permit, /* rem_permit */ oscar_rem_deny, /* rem_deny */ - oscar_set_permit_deny, /* set_permit_deny */ + oscar_set_aim_permdeny, /* set_permit_deny */ oscar_join_chat, /* join_chat */ NULL, /* reject_chat */ oscar_get_chat_name, /* get_chat_name */ oscar_chat_invite, /* chat_invite */ oscar_chat_leave, /* chat_leave */ NULL, /* chat_whisper */ oscar_send_chat, /* chat_send */ oscar_keepalive, /* keepalive */ @@ -139,13 +140,13 @@ static PurplePluginInfo info = NULL }; gboolean purple_init_icq_plugin(void); static void init_plugin(PurplePlugin *plugin) { - oscar_init(plugin); + oscar_init(plugin, FALSE); purple_init_icq_plugin(); } PURPLE_INIT_PLUGIN(oscar, init_plugin, info) diff --git a/purple/libpurple/protocols/oscar/libicq.c b/purple/libpurple/protocols/oscar/libicq.c --- a/purple/libpurple/protocols/oscar/libicq.c +++ b/purple/libpurple/protocols/oscar/libicq.c @@ -60,21 +60,21 @@ static PurplePluginProtocolInfo prpl_inf oscar_get_info, /* get_info */ oscar_set_status, /* set_status */ oscar_set_idle, /* set_idle */ oscar_change_passwd, /* change_passwd */ oscar_add_buddy, /* add_buddy */ NULL, /* add_buddies */ oscar_remove_buddy, /* remove_buddy */ NULL, /* remove_buddies */ - oscar_add_permit, /* add_permit */ + NULL, /* add_permit */ oscar_add_deny, /* add_deny */ - oscar_rem_permit, /* rem_permit */ + NULL, /* rem_permit */ oscar_rem_deny, /* rem_deny */ - oscar_set_permit_deny, /* set_permit_deny */ + NULL, /* set_permit_deny */ oscar_join_chat, /* join_chat */ NULL, /* reject_chat */ oscar_get_chat_name, /* get_chat_name */ oscar_chat_invite, /* chat_invite */ oscar_chat_leave, /* chat_leave */ NULL, /* chat_whisper */ oscar_send_chat, /* chat_send */ oscar_keepalive, /* keepalive */ @@ -152,15 +152,15 @@ static PurplePluginInfo info = NULL }; static void init_plugin(PurplePlugin *plugin) { PurpleAccountOption *option; - oscar_init(plugin); + oscar_init(plugin, TRUE); option = purple_account_option_string_new(_("Encoding"), "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); } PURPLE_INIT_PLUGIN(icq, init_plugin, info); diff --git a/purple/libpurple/protocols/oscar/misc.c b/purple/libpurple/protocols/oscar/misc.c --- a/purple/libpurple/protocols/oscar/misc.c +++ b/purple/libpurple/protocols/oscar/misc.c @@ -36,27 +36,27 @@ * back to the single. I don't see any advantage to doing it either way. * */ void aim_genericreq_n(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype) { aim_snacid_t snacid = 0x00000000; - flap_connection_send_snac(od, conn, family, subtype, 0x0000, snacid, NULL); + flap_connection_send_snac(od, conn, family, subtype, snacid, NULL); } void aim_genericreq_n_snacid(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype) { aim_snacid_t snacid; snacid = aim_cachesnac(od, family, subtype, 0x0000, NULL, 0); - flap_connection_send_snac(od, conn, family, subtype, 0x0000, snacid, NULL); + flap_connection_send_snac(od, conn, family, subtype, snacid, NULL); } void aim_genericreq_l(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype, guint32 *longdata) { ByteStream bs; aim_snacid_t snacid; @@ -67,40 +67,17 @@ aim_genericreq_l(OscarData *od, FlapConn } byte_stream_new(&bs, 4); snacid = aim_cachesnac(od, family, subtype, 0x0000, NULL, 0); byte_stream_put32(&bs, *longdata); - flap_connection_send_snac(od, conn, family, subtype, 0x0000, snacid, &bs); - - byte_stream_destroy(&bs); -} - -void -aim_genericreq_s(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype, guint16 *shortdata) -{ - ByteStream bs; - aim_snacid_t snacid; - - if (!shortdata) - { - aim_genericreq_n(od, conn, family, subtype); - return; - } - - byte_stream_new(&bs, 2); - - snacid = aim_cachesnac(od, family, subtype, 0x0000, NULL, 0); - - byte_stream_put16(&bs, *shortdata); - - flap_connection_send_snac(od, conn, family, subtype, 0x0000, snacid, &bs); + flap_connection_send_snac(od, conn, family, subtype, snacid, &bs); byte_stream_destroy(&bs); } /* * Should be generic enough to handle the errors for all groups. * */ @@ -109,17 +86,17 @@ generror(OscarData *od, FlapConnection * { int ret = 0; int error = 0; aim_rxcallback_t userfunc; aim_snac_t *snac2; snac2 = aim_remsnac(od, snac->id); - if (byte_stream_empty(bs)) + if (byte_stream_bytes_left(bs)) error = byte_stream_get16(bs); if ((userfunc = aim_callhandler(od, snac->family, snac->subtype))) ret = userfunc(od, conn, frame, error, snac2 ? snac2->data : NULL); if (snac2) { g_free(snac2->data); g_free(snac2); diff --git a/purple/libpurple/protocols/oscar/msgcookie.c b/purple/libpurple/protocols/oscar/msgcookie.c --- a/purple/libpurple/protocols/oscar/msgcookie.c +++ b/purple/libpurple/protocols/oscar/msgcookie.c @@ -172,23 +172,8 @@ int aim_cookie_free(OscarData *od, IcbmC prev = &cur->next; } g_free(cookie->data); g_free(cookie); return 0; } - -/* XXX I hate switch */ -int aim_msgcookie_gettype(guint64 type) -{ - /* XXX: hokey-assed. needs fixed. */ - switch(type) { - case OSCAR_CAPABILITY_BUDDYICON: return AIM_COOKIETYPE_OFTICON; - case OSCAR_CAPABILITY_TALK: return AIM_COOKIETYPE_OFTVOICE; - case OSCAR_CAPABILITY_DIRECTIM: return AIM_COOKIETYPE_OFTIMAGE; - case OSCAR_CAPABILITY_CHAT: return AIM_COOKIETYPE_CHAT; - case OSCAR_CAPABILITY_GETFILE: return AIM_COOKIETYPE_OFTGET; - case OSCAR_CAPABILITY_SENDFILE: return AIM_COOKIETYPE_OFTSEND; - default: return AIM_COOKIETYPE_UNKNOWN; - } -} diff --git a/purple/libpurple/protocols/oscar/odc.c b/purple/libpurple/protocols/oscar/odc.c --- a/purple/libpurple/protocols/oscar/odc.c +++ b/purple/libpurple/protocols/oscar/odc.c @@ -14,16 +14,17 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ /* From the oscar PRPL */ +#include "encoding.h" #include "oscar.h" #include "peer.h" /* From Purple */ #include "conversation.h" #include "imgstore.h" #include "util.h" @@ -84,17 +85,17 @@ static void peer_odc_send(PeerConnection *conn, OdcFrame *frame) { PurpleAccount *account; const char *username; size_t length; ByteStream bs; purple_debug_info("oscar", "Outgoing ODC frame to %s with " - "type=0x%04x, flags=0x%04x, payload length=%u\n", + "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT "\n", conn->bn, frame->type, frame->flags, frame->payload.len); account = purple_connection_get_account(conn->od->gc); username = purple_account_get_username(account); memcpy(frame->bn, username, strlen(username)); memcpy(frame->cookie, conn->cookie, 8); length = 76; @@ -361,18 +362,17 @@ peer_odc_handle_payload(PeerConnection * images = g_slist_append(images, GINT_TO_POINTER(imgid)); } } /* Delete the attribute list */ g_datalist_clear(&attributes); /* Append the message up to the tag */ - utf8 = purple_plugin_oscar_decode_im_part(account, conn->bn, - encoding, 0x0000, tmp, start - tmp); + utf8 = oscar_decode_im(account, conn->bn, encoding, tmp, start - tmp); if (utf8 != NULL) { g_string_append(newmsg, utf8); g_free(utf8); } if (imgid != 0) { /* Write the new image tag */ @@ -381,18 +381,17 @@ peer_odc_handle_payload(PeerConnection * /* Continue from the end of the tag */ tmp = end + 1; } /* Append any remaining message data */ if (tmp <= msgend) { - utf8 = purple_plugin_oscar_decode_im_part(account, conn->bn, - encoding, 0x0000, tmp, msgend - tmp); + utf8 = oscar_decode_im(account, conn->bn, encoding, tmp, msgend - tmp); if (utf8 != NULL) { g_string_append(newmsg, utf8); g_free(utf8); } } /* Display the message we received */ imflags = 0; @@ -501,17 +500,17 @@ peer_odc_recv_frame(PeerConnection *conn frame->payload.len = byte_stream_get32(bs); frame->encoding = byte_stream_get16(bs); byte_stream_advance(bs, 4); frame->flags = byte_stream_get16(bs); byte_stream_advance(bs, 4); byte_stream_getrawbuf(bs, frame->bn, 32); purple_debug_info("oscar", "Incoming ODC frame from %s with " - "type=0x%04x, flags=0x%04x, payload length=%u\n", + "type=0x%04x, flags=0x%04x, payload length=%" G_GSIZE_FORMAT "\n", frame->bn, frame->type, frame->flags, frame->payload.len); if (!conn->ready) { /* * We need to verify the cookie so that we know we are * connected to our friend and not a malicious middle man. */ diff --git a/purple/libpurple/protocols/oscar/oft.c b/purple/libpurple/protocols/oscar/oft.c --- a/purple/libpurple/protocols/oscar/oft.c +++ b/purple/libpurple/protocols/oscar/oft.c @@ -202,17 +202,17 @@ peer_oft_checksum_file(PeerConnection *c purple_xfer_get_local_filename(xfer)); checksum_data = g_malloc0(sizeof(ChecksumData)); checksum_data->conn = conn; checksum_data->xfer = xfer; checksum_data->callback = callback; checksum_data->size = size; checksum_data->checksum = 0xffff0000; - checksum_data->file = fopen(purple_xfer_get_local_filename(xfer), "rb"); + checksum_data->file = g_fopen(purple_xfer_get_local_filename(xfer), "rb"); if (checksum_data->file == NULL) { purple_debug_error("oscar", "Unable to open %s for checksumming: %s\n", purple_xfer_get_local_filename(xfer), g_strerror(errno)); callback(checksum_data); g_free(checksum_data); } @@ -235,17 +235,17 @@ peer_oft_copy_xfer_data(PeerConnection * /** * Free any OFT related data. */ void peer_oft_close(PeerConnection *conn) { /* - * If canceled by local user, and we're receiving a file, and + * If cancelled by local user, and we're receiving a file, and * we're not connected/ready then send an ICBM cancel message. */ if ((purple_xfer_get_status(conn->xfer) == PURPLE_XFER_STATUS_CANCEL_LOCAL) && !conn->ready) { aim_im_sendch2_cancel(conn); } diff --git a/purple/libpurple/protocols/oscar/oscar.c b/purple/libpurple/protocols/oscar/oscar.c --- a/purple/libpurple/protocols/oscar/oscar.c +++ b/purple/libpurple/protocols/oscar/oscar.c @@ -32,720 +32,130 @@ #include "account.h" #include "accountopt.h" #include "buddyicon.h" #include "cipher.h" #include "conversation.h" #include "core.h" #include "debug.h" +#include "encoding.h" #include "imgstore.h" #include "network.h" #include "notify.h" #include "privacy.h" #include "prpl.h" #include "proxy.h" #include "request.h" #include "util.h" #include "version.h" +#include "visibility.h" #include "oscarcommon.h" #include "oscar.h" #include "peer.h" -#define OSCAR_STATUS_ID_INVISIBLE "invisible" -#define OSCAR_STATUS_ID_OFFLINE "offline" -#define OSCAR_STATUS_ID_AVAILABLE "available" -#define OSCAR_STATUS_ID_AWAY "away" -#define OSCAR_STATUS_ID_DND "dnd" -#define OSCAR_STATUS_ID_NA "na" -#define OSCAR_STATUS_ID_OCCUPIED "occupied" -#define OSCAR_STATUS_ID_FREE4CHAT "free4chat" -#define OSCAR_STATUS_ID_CUSTOM "custom" -#define OSCAR_STATUS_ID_MOBILE "mobile" -#define OSCAR_STATUS_ID_EVIL "evil" -#define OSCAR_STATUS_ID_DEPRESSION "depression" -#define OSCAR_STATUS_ID_ATHOME "athome" -#define OSCAR_STATUS_ID_ATWORK "atwork" -#define OSCAR_STATUS_ID_LUNCH "lunch" - #define AIMHASHDATA "http://pidgin.im/aim_data.php3" #define OSCAR_CONNECT_STEPS 6 static guint64 purple_caps = OSCAR_CAPABILITY_CHAT | OSCAR_CAPABILITY_BUDDYICON | OSCAR_CAPABILITY_DIRECTIM | OSCAR_CAPABILITY_SENDFILE | OSCAR_CAPABILITY_UNICODE | OSCAR_CAPABILITY_INTEROPERATE | OSCAR_CAPABILITY_SHORTCAPS | OSCAR_CAPABILITY_TYPING | OSCAR_CAPABILITY_ICQSERVERRELAY | OSCAR_CAPABILITY_NEWCAPS - | OSCAR_CAPABILITY_XTRAZ; + | OSCAR_CAPABILITY_XTRAZ + | OSCAR_CAPABILITY_HTML_MSGS; static guint8 features_aim[] = {0x01, 0x01, 0x01, 0x02}; static guint8 features_icq[] = {0x01}; -static guint8 ck[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; struct create_room { char *name; int exchange; }; struct oscar_ask_directim_data { OscarData *od; char *who; }; -/* - * Various PRPL-specific buddy info that we want to keep track of - * Some other info is maintained by locate.c, and I'd like to move - * the rest of this to libfaim, mostly im.c - * - * TODO: More of this should use the status API. - */ -struct buddyinfo { - gboolean typingnot; - guint32 ipaddr; - - unsigned long ico_me_len; - unsigned long ico_me_csum; - time_t ico_me_time; - gboolean ico_informed; - - unsigned long ico_len; - unsigned long ico_csum; - time_t ico_time; - gboolean ico_need; - gboolean ico_sent; -}; - -struct name_data { - PurpleConnection *gc; - gchar *name; - gchar *nick; -}; - /* All the libfaim->purple callback functions */ /* Only used when connecting with the old-style BUCP login */ static int purple_parse_auth_resp (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_login (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_auth_securid_request(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_handle_redirect (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_info_change (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_account_confirm (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_oncoming (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_offgoing (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_incoming_im(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_misses (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_clientauto (OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_parse_userinfo (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_motd (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_chatnav_info (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_join (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_leave (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_info_update (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_conv_chat_incoming_msg(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_email_parseupdate(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_icon_parseicon (OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_parse_msgack (OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_parse_evilnotify (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_searcherror(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_searchreply(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_bosrights (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_connerr (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_mtn (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_locaterights(OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_buddyrights(OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_parse_locerr (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_parse_genericerr (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_memrequest (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_selfinfo (OscarData *, FlapConnection *, FlapFrame *, ...); -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS -static int purple_offlinemsg (OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_offlinemsgdone (OscarData *, FlapConnection *, FlapFrame *, ...); -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ -static int purple_icqalias (OscarData *, FlapConnection *, FlapFrame *, ...); -static int purple_icqinfo (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_popup (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_parseerr (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_parserights (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_parselist (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_parseack (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_parseaddmod (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_authgiven (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_authrequest (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_authreply (OscarData *, FlapConnection *, FlapFrame *, ...); static int purple_ssi_gotadded (OscarData *, FlapConnection *, FlapFrame *, ...); static void purple_icons_fetch(PurpleConnection *gc); void oscar_set_info(PurpleConnection *gc, const char *info); static void oscar_set_info_and_status(PurpleAccount *account, gboolean setinfo, const char *rawinfo, gboolean setstatus, PurpleStatus *status); -static void oscar_set_extendedstatus(PurpleConnection *gc); +static void oscar_set_extended_status(PurpleConnection *gc); static gboolean purple_ssi_rerequestdata(gpointer data); -static void oscar_free_name_data(struct name_data *data) { +void oscar_free_name_data(struct name_data *data) { g_free(data->name); g_free(data->nick); g_free(data); } #ifdef _WIN32 const char *oscar_get_locale_charset(void) { static const char *charset = NULL; if (charset == NULL) g_get_charset(&charset); return charset; } #endif -/** - * Determine how we can send this message. Per the warnings elsewhere - * in this file, these little checks determine the simplest encoding - * we can use for a given message send using it. - */ -static guint32 -oscar_charset_check(const char *utf8) -{ - int i = 0; - int charset = AIM_CHARSET_ASCII; - - /* - * Can we get away with using our custom encoding? - */ - while (utf8[i]) - { - if ((unsigned char)utf8[i] > 0x7f) { - /* not ASCII! */ - charset = AIM_CHARSET_LATIN_1; - break; - } - i++; - } - - /* - * Must we send this message as UNICODE (in the UTF-16BE encoding)? - */ - while (utf8[i]) - { - /* ISO-8859-1 is 0x00-0xbf in the first byte - * followed by 0xc0-0xc3 in the second */ - if ((unsigned char)utf8[i] < 0x80) { - i++; - continue; - } else if (((unsigned char)utf8[i] & 0xfc) == 0xc0 && - ((unsigned char)utf8[i + 1] & 0xc0) == 0x80) { - i += 2; - continue; - } - charset = AIM_CHARSET_UNICODE; - break; - } - - return charset; -} - -/** - * Take a string of the form charset="bleh" where bleh is - * one of us-ascii, utf-8, iso-8859-1, or unicode-2-0, and - * return a newly allocated string containing bleh. - */ -gchar * -oscar_encoding_extract(const char *encoding) -{ - gchar *ret = NULL; - char *begin, *end; - - g_return_val_if_fail(encoding != NULL, NULL); - - /* Make sure encoding begins with charset= */ - if (strncmp(encoding, "text/aolrtf; charset=", 21) && - strncmp(encoding, "text/x-aolrtf; charset=", 23) && - strncmp(encoding, "text/plain; charset=", 20)) - { - return NULL; - } - - begin = strchr(encoding, '"'); - end = strrchr(encoding, '"'); - - if ((begin == NULL) || (end == NULL) || (begin >= end)) - return NULL; - - ret = g_strndup(begin+1, (end-1) - begin); - - return ret; -} - -gchar * -oscar_encoding_to_utf8(PurpleAccount *account, const char *encoding, const char *text, int textlen) -{ - gchar *utf8 = NULL; - - if ((encoding == NULL) || encoding[0] == '\0') { - purple_debug_info("oscar", "Empty encoding, assuming UTF-8\n"); - } else if (!g_ascii_strcasecmp(encoding, "iso-8859-1")) { - utf8 = g_convert(text, textlen, "UTF-8", "iso-8859-1", NULL, NULL, NULL); - } else if (!g_ascii_strcasecmp(encoding, "ISO-8859-1-Windows-3.1-Latin-1") || - !g_ascii_strcasecmp(encoding, "us-ascii")) - { - utf8 = g_convert(text, textlen, "UTF-8", "Windows-1252", NULL, NULL, NULL); - } else if (!g_ascii_strcasecmp(encoding, "unicode-2-0")) { - /* Some official ICQ clients are apparently total crack, - * and have been known to save a UTF-8 string converted - * from the locale character set to UTF-16 (not from UTF-8 - * to UTF-16!) in the away message. This hack should find - * and do something (un)reasonable with that, and not - * mess up too much else. */ - const gchar *charset = purple_account_get_string(account, "encoding", NULL); - if (charset) { - gsize len; - utf8 = g_convert(text, textlen, charset, "UTF-16BE", &len, NULL, NULL); - if (!utf8 || len != textlen || !g_utf8_validate(utf8, -1, NULL)) { - g_free(utf8); - utf8 = NULL; - } else { - purple_debug_info("oscar", "Used broken ICQ fallback encoding\n"); - } - } - if (!utf8) - utf8 = g_convert(text, textlen, "UTF-8", "UTF-16BE", NULL, NULL, NULL); - } else if (g_ascii_strcasecmp(encoding, "utf-8")) { - purple_debug_warning("oscar", "Unrecognized character encoding \"%s\", " - "attempting to convert to UTF-8 anyway\n", encoding); - utf8 = g_convert(text, textlen, "UTF-8", encoding, NULL, NULL, NULL); - } - - /* - * If utf8 is still NULL then either the encoding is utf-8 or - * we have been unable to convert the text to utf-8 from the encoding - * that was specified. So we check if the text is valid utf-8 then - * just copy it. - */ - if (utf8 == NULL) { - if (textlen != 0 && *text != '\0' - && !g_utf8_validate(text, textlen, NULL)) - utf8 = g_strdup(_("(There was an error receiving this message. The buddy you are speaking with is probably using a different encoding than expected. If you know what encoding he is using, you can specify it in the advanced account options for your AIM/ICQ account.)")); - else - utf8 = g_strndup(text, textlen); - } - - return utf8; -} - -static gchar * -oscar_utf8_try_convert(PurpleAccount *account, OscarData *od, const gchar *msg) -{ - const char *charset = NULL; - char *ret = NULL; - - if (od->icq) - charset = purple_account_get_string(account, "encoding", NULL); - - if(charset && *charset) - ret = g_convert(msg, -1, "UTF-8", charset, NULL, NULL, NULL); - - if(!ret) - ret = purple_utf8_try_convert(msg); - - return ret; -} - -static gchar * -purple_plugin_oscar_convert_to_utf8(const gchar *data, gsize datalen, const char *charsetstr, gboolean fallback) -{ - gchar *ret = NULL; - GError *err = NULL; - - if ((charsetstr == NULL) || (*charsetstr == '\0')) - return NULL; - - if (g_ascii_strcasecmp("UTF-8", charsetstr)) { - if (fallback) - ret = g_convert_with_fallback(data, datalen, "UTF-8", charsetstr, "?", NULL, NULL, &err); - else - ret = g_convert(data, datalen, "UTF-8", charsetstr, NULL, NULL, &err); - if (err != NULL) { - purple_debug_warning("oscar", "Conversion from %s failed: %s.\n", - charsetstr, err->message); - g_error_free(err); - } - } else { - if (g_utf8_validate(data, datalen, NULL)) - ret = g_strndup(data, datalen); - else - purple_debug_warning("oscar", "String is not valid UTF-8.\n"); - } - - return ret; -} - -/** - * This attemps to decode an incoming IM into a UTF8 string. - * - * We try decoding using two different character sets. The charset - * specified in the IM determines the order in which we attempt to - * decode. We do this because there are lots of broken ICQ clients - * that don't correctly send non-ASCII messages. And if Purple isn't - * able to deal with that crap, then people complain like banshees. - * charsetstr1 is always set to what the correct encoding should be. - */ -gchar * -purple_plugin_oscar_decode_im_part(PurpleAccount *account, const char *sourcebn, guint16 charset, guint16 charsubset, const gchar *data, gsize datalen) -{ - gchar *ret = NULL; - const gchar *charsetstr1, *charsetstr2, *charsetstr3 = NULL; - - if ((datalen == 0) || (data == NULL)) - return NULL; - - if (charset == AIM_CHARSET_UNICODE) { - charsetstr1 = "UTF-16BE"; - charsetstr2 = "UTF-8"; - } else if (charset == AIM_CHARSET_LATIN_1) { - if ((sourcebn != NULL) && oscar_util_valid_name_icq(sourcebn)) - charsetstr1 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); - else - charsetstr1 = "ISO-8859-1"; - charsetstr2 = "UTF-8"; - } else if (charset == AIM_CHARSET_ASCII) { - /* Should just be "ASCII" */ - charsetstr1 = "ASCII"; - charsetstr2 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); - } else if (charset == 0x000d) { - /* iChat sending unicode over a Direct IM connection = UTF-8 */ - /* Mobile AIM client on multiple devices (including Blackberry Tour, Nokia 3100, and LG VX6000) = ISO-8859-1 */ - charsetstr1 = "UTF-8"; - charsetstr2 = "ISO-8859-1"; - charsetstr3 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); - } else { - /* Unknown, hope for valid UTF-8... */ - charsetstr1 = "UTF-8"; - charsetstr2 = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); - } - - purple_debug_info("oscar", "Parsing IM part, charset=0x%04hx, charsubset=0x%04hx, datalen=%" G_GSIZE_FORMAT ", choice1=%s, choice2=%s, choice3=%s\n", - charset, charsubset, datalen, charsetstr1, charsetstr2, (charsetstr3 ? charsetstr3 : "")); - - ret = purple_plugin_oscar_convert_to_utf8(data, datalen, charsetstr1, FALSE); - if (ret == NULL) { - if (charsetstr3 != NULL) { - /* Try charsetstr2 without allowing substitutions, then fall through to charsetstr3 if needed */ - ret = purple_plugin_oscar_convert_to_utf8(data, datalen, charsetstr2, FALSE); - if (ret == NULL) - ret = purple_plugin_oscar_convert_to_utf8(data, datalen, charsetstr3, TRUE); - } else { - /* Try charsetstr2, allowing substitutions */ - ret = purple_plugin_oscar_convert_to_utf8(data, datalen, charsetstr2, TRUE); - } - } - if (ret == NULL) { - char *str, *salvage, *tmp; - - str = g_malloc(datalen + 1); - strncpy(str, data, datalen); - str[datalen] = '\0'; - salvage = purple_utf8_salvage(str); - tmp = g_strdup_printf(_("(There was an error receiving this message. Either you and %s have different encodings selected, or %s has a buggy client.)"), - sourcebn, sourcebn); - ret = g_strdup_printf("%s %s", salvage, tmp); - g_free(tmp); - g_free(str); - g_free(salvage); - } - - return ret; -} - -/** - * Figure out what encoding to use when sending a given outgoing message. - */ -static void -purple_plugin_oscar_convert_to_best_encoding(PurpleConnection *gc, - const char *destbn, const gchar *from, - gchar **msg, int *msglen_int, - guint16 *charset, guint16 *charsubset) -{ - OscarData *od = purple_connection_get_protocol_data(gc); - PurpleAccount *account = purple_connection_get_account(gc); - GError *err = NULL; - aim_userinfo_t *userinfo = NULL; - const gchar *charsetstr; - gsize msglen; - - /* Attempt to send as ASCII */ - if (oscar_charset_check(from) == AIM_CHARSET_ASCII) { - *msg = g_convert(from, -1, "ASCII", "UTF-8", NULL, &msglen, &err); - if (*msg != NULL) { - *charset = AIM_CHARSET_ASCII; - *charsubset = 0x0000; - *msglen_int = msglen; - return; - } - purple_debug_error("oscar", "Conversion from UTF-8 to ASCII failed: %s.\n", - err->message); - g_error_free(err); - err = NULL; - *msg = g_strdup(from); - } - - /* - * If we're sending to an ICQ user, and they are in our - * buddy list, and they are advertising the Unicode - * capability, and they are online, then attempt to send - * as UTF-16BE. - */ - if ((destbn != NULL) && oscar_util_valid_name_icq(destbn)) - userinfo = aim_locate_finduserinfo(od, destbn); - - if ((userinfo != NULL) && (userinfo->capabilities & OSCAR_CAPABILITY_UNICODE)) - { - PurpleBuddy *b; - b = purple_find_buddy(account, destbn); - if ((b != NULL) && (PURPLE_BUDDY_IS_ONLINE(b))) - { - *msg = g_convert(from, -1, "UTF-16BE", "UTF-8", NULL, &msglen, &err); - if (*msg != NULL) - { - *charset = AIM_CHARSET_UNICODE; - *charsubset = 0x0000; - *msglen_int = msglen; - return; - } - - purple_debug_error("oscar", "Conversion from UTF-8 to UTF-16BE failed: %s.\n", - err->message); - g_error_free(err); - err = NULL; - } - } - - /* - * If this is AIM then attempt to send as ISO-8859-1. If this is - * ICQ then attempt to send as the user specified character encoding. - */ - charsetstr = "ISO-8859-1"; - if ((destbn != NULL) && oscar_util_valid_name_icq(destbn)) - charsetstr = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); - - /* - * XXX - We need a way to only attempt to convert if we KNOW "from" - * can be converted to "charsetstr" - */ - *msg = g_convert(from, -1, charsetstr, "UTF-8", NULL, &msglen, &err); - if (*msg != NULL) { - *charset = AIM_CHARSET_LATIN_1; - *charsubset = 0x0000; - *msglen_int = msglen; - return; - } - - purple_debug_info("oscar", "Conversion from UTF-8 to %s failed (%s). Falling back to unicode.\n", - charsetstr, err->message); - g_error_free(err); - err = NULL; - - /* - * Nothing else worked, so send as UTF-16BE. - */ - *msg = g_convert(from, -1, "UTF-16BE", "UTF-8", NULL, &msglen, &err); - if (*msg != NULL) { - *charset = AIM_CHARSET_UNICODE; - *charsubset = 0x0000; - *msglen_int = msglen; - return; - } - - purple_debug_error("oscar", "Error converting a Unicode message: %s\n", err->message); - g_error_free(err); - err = NULL; - - purple_debug_error("oscar", "This should NEVER happen! Sending UTF-8 text flagged as ASCII.\n"); - *msg = g_strdup(from); - *msglen_int = strlen(*msg); - *charset = AIM_CHARSET_ASCII; - *charsubset = 0x0000; - return; -} - -/** - * Looks for %n, %d, or %t in a string, and replaces them with the - * specified name, date, and time, respectively. - * - * @param str The string that may contain the special variables. - * @param name The sender name. - * - * @return A newly allocated string where the special variables are - * expanded. This should be g_free'd by the caller. - */ -static gchar * -purple_str_sub_away_formatters(const char *str, const char *name) -{ - char *c; - GString *cpy; - time_t t; - struct tm *tme; - - g_return_val_if_fail(str != NULL, NULL); - g_return_val_if_fail(name != NULL, NULL); - - /* Create an empty GString that is hopefully big enough for most messages */ - cpy = g_string_sized_new(1024); - - t = time(NULL); - tme = localtime(&t); - - c = (char *)str; - while (*c) { - switch (*c) { - case '%': - if (*(c + 1)) { - switch (*(c + 1)) { - case 'n': - /* append name */ - g_string_append(cpy, name); - c++; - break; - case 'd': - /* append date */ - g_string_append(cpy, purple_date_format_short(tme)); - c++; - break; - case 't': - /* append time */ - g_string_append(cpy, purple_time_format(tme)); - c++; - break; - default: - g_string_append_c(cpy, *c); - } - } else { - g_string_append_c(cpy, *c); - } - break; - default: - g_string_append_c(cpy, *c); - } - c++; - } - - return g_string_free(cpy, FALSE); -} - -static gchar *oscar_caps_to_string(guint64 caps) -{ - GString *str; - const gchar *tmp; - guint64 bit = 1; - - str = g_string_new(""); - - if (!caps) { - return NULL; - } else while (bit <= OSCAR_CAPABILITY_LAST) { - if (bit & caps) { - switch (bit) { - case OSCAR_CAPABILITY_BUDDYICON: - tmp = _("Buddy Icon"); - break; - case OSCAR_CAPABILITY_TALK: - tmp = _("Voice"); - break; - case OSCAR_CAPABILITY_DIRECTIM: - tmp = _("AIM Direct IM"); - break; - case OSCAR_CAPABILITY_CHAT: - tmp = _("Chat"); - break; - case OSCAR_CAPABILITY_GETFILE: - tmp = _("Get File"); - break; - case OSCAR_CAPABILITY_SENDFILE: - tmp = _("Send File"); - break; - case OSCAR_CAPABILITY_GAMES: - case OSCAR_CAPABILITY_GAMES2: - tmp = _("Games"); - break; - case OSCAR_CAPABILITY_XTRAZ: - case OSCAR_CAPABILITY_NEWCAPS: - tmp = _("ICQ Xtraz"); - break; - case OSCAR_CAPABILITY_ADDINS: - tmp = _("Add-Ins"); - break; - case OSCAR_CAPABILITY_SENDBUDDYLIST: - tmp = _("Send Buddy List"); - break; - case OSCAR_CAPABILITY_ICQ_DIRECT: - tmp = _("ICQ Direct Connect"); - break; - case OSCAR_CAPABILITY_APINFO: - tmp = _("AP User"); - break; - case OSCAR_CAPABILITY_ICQRTF: - tmp = _("ICQ RTF"); - break; - case OSCAR_CAPABILITY_EMPTY: - tmp = _("Nihilist"); - break; - case OSCAR_CAPABILITY_ICQSERVERRELAY: - tmp = _("ICQ Server Relay"); - break; - case OSCAR_CAPABILITY_UNICODEOLD: - tmp = _("Old ICQ UTF8"); - break; - case OSCAR_CAPABILITY_TRILLIANCRYPT: - tmp = _("Trillian Encryption"); - break; - case OSCAR_CAPABILITY_UNICODE: - tmp = _("ICQ UTF8"); - break; - case OSCAR_CAPABILITY_HIPTOP: - tmp = _("Hiptop"); - break; - case OSCAR_CAPABILITY_SECUREIM: - tmp = _("Security Enabled"); - break; - case OSCAR_CAPABILITY_VIDEO: - tmp = _("Video Chat"); - break; - /* Not actually sure about this one... WinAIM doesn't show anything */ - case OSCAR_CAPABILITY_ICHATAV: - tmp = _("iChat AV"); - break; - case OSCAR_CAPABILITY_LIVEVIDEO: - tmp = _("Live Video"); - break; - case OSCAR_CAPABILITY_CAMERA: - tmp = _("Camera"); - break; - case OSCAR_CAPABILITY_ICHAT_SCREENSHARE: - tmp = _("Screen Sharing"); - break; - default: - tmp = NULL; - break; - } - if (tmp) - g_string_append_printf(str, "%s%s", (*(str->str) == '\0' ? "" : ", "), tmp); - } - bit <<= 1; - } - - return g_string_free(str, FALSE); -} - static char *oscar_icqstatus(int state) { /* Make a cute little string that shows the status of the dude or dudet */ if (state & AIM_ICQ_STATE_CHAT) return g_strdup(_("Free For Chat")); else if (state & AIM_ICQ_STATE_DND) return g_strdup(_("Do Not Disturb")); else if (state & AIM_ICQ_STATE_OUT) return g_strdup(_("Not Available")); @@ -766,265 +176,16 @@ static char *oscar_icqstatus(int state) else if (state & AIM_ICQ_STATE_ATWORK) return g_strdup(_("At work")); else if (state & AIM_ICQ_STATE_LUNCH) return g_strdup(_("At lunch")); else return g_strdup(_("Online")); } -static void -oscar_user_info_add_pair(PurpleNotifyUserInfo *user_info, const char *name, const char *value) -{ - if (value && value[0]) { - purple_notify_user_info_add_pair(user_info, name, value); - } -} - -static void -oscar_user_info_convert_and_add_pair(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, - const char *name, const char *value) -{ - gchar *utf8; - - if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { - purple_notify_user_info_add_pair(user_info, name, utf8); - g_free(utf8); - } -} - -static void -oscar_user_info_convert_and_add(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, - const char *name, const char *value) -{ - gchar *utf8; - - if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { - purple_notify_user_info_add_pair(user_info, name, utf8); - g_free(utf8); - } -} - -/** - * @brief Append the status information to a user_info struct - * - * The returned information is HTML-ready, appropriately escaped, as all information in a user_info struct should be HTML. - * - * @param gc The PurpleConnection - * @param user_info A PurpleNotifyUserInfo object to which status information will be added - * @param b The PurpleBuddy whose status is desired. This or the aim_userinfo_t (or both) must be passed to oscar_user_info_append_status(). - * @param userinfo The aim_userinfo_t of the buddy whose status is desired. This or the PurpleBuddy (or both) must be passed to oscar_user_info_append_status(). - * @param strip_html_tags If strip_html_tags is TRUE, tags embedded in the status message will be stripped, returning a non-formatted string. The string will still be HTML escaped. - */ -static void oscar_user_info_append_status(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo, gboolean strip_html_tags) -{ - PurpleAccount *account = purple_connection_get_account(gc); - OscarData *od; - PurplePresence *presence = NULL; - PurpleStatus *status = NULL; - gchar *message = NULL, *itmsurl = NULL, *tmp; - gboolean is_away; - - od = purple_connection_get_protocol_data(gc); - - if (b == NULL && userinfo == NULL) - return; - - if (b == NULL) - b = purple_find_buddy(purple_connection_get_account(gc), userinfo->bn); - else - userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b)); - - if (b) { - presence = purple_buddy_get_presence(b); - status = purple_presence_get_active_status(presence); - } - - /* If we have both b and userinfo we favor userinfo, because if we're - viewing someone's profile then we want the HTML away message, and - the "message" attribute of the status contains only the plaintext - message. */ - if (userinfo) { - if ((userinfo->flags & AIM_FLAG_AWAY) - && userinfo->away_len > 0 - && userinfo->away != NULL - && userinfo->away_encoding != NULL) - { - /* Away message */ - tmp = oscar_encoding_extract(userinfo->away_encoding); - message = oscar_encoding_to_utf8(account, - tmp, userinfo->away, userinfo->away_len); - g_free(tmp); - } else { - /* - * Available message or non-HTML away message (because that's - * all we have right now. - */ - if ((userinfo->status != NULL) && userinfo->status[0] != '\0') { - message = oscar_encoding_to_utf8(account, - userinfo->status_encoding, userinfo->status, - userinfo->status_len); - } -#if defined (_WIN32) || defined (__APPLE__) - if (userinfo->itmsurl && (userinfo->itmsurl[0] != '\0')) - itmsurl = oscar_encoding_to_utf8(account, userinfo->itmsurl_encoding, - userinfo->itmsurl, userinfo->itmsurl_len); -#endif - } - } else { - message = g_strdup(purple_status_get_attr_string(status, "message")); - itmsurl = g_strdup(purple_status_get_attr_string(status, "itmsurl")); - } - - is_away = ((status && !purple_status_is_available(status)) || - (userinfo && (userinfo->flags & AIM_FLAG_AWAY))); - - if (strip_html_tags) { - /* Away messages are HTML, but available messages were originally plain text. - * We therefore need to strip away messages but not available messages if we're asked to remove HTML tags. - */ - /* - * It seems like the above comment no longer applies. All messages need - * to be escaped. - */ - if (message) { - gchar *tmp2; - tmp = purple_markup_strip_html(message); - g_free(message); - tmp2 = g_markup_escape_text(tmp, -1); - g_free(tmp); - message = tmp2; - } - - } else { - if (itmsurl) { - tmp = g_strdup_printf("%s", - itmsurl, message); - g_free(message); - message = tmp; - } - } - g_free(itmsurl); - - if (message) { - tmp = purple_str_sub_away_formatters(message, purple_account_get_username(account)); - g_free(message); - message = tmp; - } - - if (b) { - if (purple_presence_is_online(presence)) { - if (oscar_util_valid_name_icq(purple_buddy_get_name(b)) || is_away || !message || !(*message)) { - /* Append the status name for online ICQ statuses, away AIM statuses, and for all buddies with no message. - * If the status name and the message are the same, only show one. */ - const char *status_name = purple_status_get_name(status); - if (status_name && message && !strcmp(status_name, message)) - status_name = NULL; - - tmp = g_strdup_printf("%s%s%s", - status_name ? status_name : "", - ((status_name && message) && *message) ? ": " : "", - (message && *message) ? message : ""); - g_free(message); - message = tmp; - } - - } else if (aim_ssi_waitingforauth(od->ssi.local, - aim_ssi_itemlist_findparentname(od->ssi.local, purple_buddy_get_name(b)), - purple_buddy_get_name(b))) - { - /* Note if an offline buddy is not authorized */ - tmp = g_strdup_printf("%s%s%s", - _("Not Authorized"), - (message && *message) ? ": " : "", - (message && *message) ? message : ""); - g_free(message); - message = tmp; - } else { - g_free(message); - message = g_strdup(_("Offline")); - } - } - - if (presence) { - const char *mood; - const char *description; - status = purple_presence_get_status(presence, "mood"); - mood = purple_status_get_attr_string(status, PURPLE_MOOD_NAME); - description = icq_get_custom_icon_description(mood); - if (description && *description) - purple_notify_user_info_add_pair(user_info, _("Mood"), _(description)); - } - - purple_notify_user_info_add_pair(user_info, _("Status"), message); - g_free(message); -} - -static void oscar_user_info_append_extra_info(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo) -{ - OscarData *od; - PurpleAccount *account; - PurplePresence *presence = NULL; - PurpleStatus *status = NULL; - PurpleGroup *g = NULL; - struct buddyinfo *bi = NULL; - char *tmp; - const char *bname = NULL, *gname = NULL; - - od = purple_connection_get_protocol_data(gc); - account = purple_connection_get_account(gc); - - if ((user_info == NULL) || ((b == NULL) && (userinfo == NULL))) - return; - - if (userinfo == NULL) - userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b)); - - if (b == NULL) - b = purple_find_buddy(account, userinfo->bn); - - if (b != NULL) { - bname = purple_buddy_get_name(b); - g = purple_buddy_get_group(b); - gname = purple_group_get_name(g); - presence = purple_buddy_get_presence(b); - status = purple_presence_get_active_status(presence); - } - - if (userinfo != NULL) - bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, userinfo->bn)); - - if ((bi != NULL) && (bi->ipaddr != 0)) { - tmp = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", - (bi->ipaddr & 0xff000000) >> 24, - (bi->ipaddr & 0x00ff0000) >> 16, - (bi->ipaddr & 0x0000ff00) >> 8, - (bi->ipaddr & 0x000000ff)); - oscar_user_info_add_pair(user_info, _("IP Address"), tmp); - g_free(tmp); - } - - if ((userinfo != NULL) && (userinfo->warnlevel != 0)) { - tmp = g_strdup_printf("%d", (int)(userinfo->warnlevel/10.0 + .5)); - oscar_user_info_add_pair(user_info, _("Warning Level"), tmp); - g_free(tmp); - } - - if ((b != NULL) && (bname != NULL) && (g != NULL) && (gname != NULL)) { - tmp = aim_ssi_getcomment(od->ssi.local, gname, bname); - if (tmp != NULL) { - char *tmp2 = g_markup_escape_text(tmp, strlen(tmp)); - g_free(tmp); - - oscar_user_info_convert_and_add_pair(account, od, user_info, _("Buddy Comment"), tmp2); - g_free(tmp2); - } - } -} - static char *extract_name(const char *name) { char *tmp, *x; int i, j; if (!name) return NULL; x = strchr(name, '-'); @@ -1117,17 +278,16 @@ oscar_chat_kill(PurpleConnection *gc, st { OscarData *od = purple_connection_get_protocol_data(gc); /* Notify the conversation window that we've left the chat */ serv_got_chat_left(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(cc->conv))); /* Destroy the chat_connection */ od->oscar_chats = g_slist_remove(od->oscar_chats, cc); - flap_connection_schedule_destroy(cc->conn, OSCAR_DISCONNECT_DONE, NULL); oscar_chat_destroy(cc); } /** * This is called from the callback functions for establishing * a TCP connection with an oscar host if an error occurred. */ static void @@ -1209,22 +369,20 @@ connection_common_established_cb(FlapCon } if (conn->type == SNAC_FAMILY_AUTH) { /* This only happens when connecting with the old-style BUCP login */ aim_request_login(od, conn, purple_account_get_username(account)); purple_debug_info("oscar", "Username sent, waiting for response\n"); purple_connection_update_progress(gc, _("Username sent"), 1, OSCAR_CONNECT_STEPS); - ck[1] = 0x65; } else if (conn->type == SNAC_FAMILY_LOCATE) { purple_connection_update_progress(gc, _("Connection established, cookie sent"), 4, OSCAR_CONNECT_STEPS); - ck[4] = 0x61; } else if (conn->type == SNAC_FAMILY_CHAT) { od->oscar_chats = g_slist_prepend(od->oscar_chats, conn->new_conn_data); conn->new_conn_data = NULL; } } @@ -1409,23 +567,25 @@ flap_connection_established(OscarData *o } static void idle_reporting_pref_cb(const char *name, PurplePrefType type, gconstpointer value, gpointer data) { PurpleConnection *gc; OscarData *od; + gboolean report_idle; guint32 presence; gc = data; od = purple_connection_get_protocol_data(gc); + report_idle = GPOINTER_TO_INT(value); presence = aim_ssi_getpresence(od->ssi.local); - if (value) + if (report_idle) aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); else aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); } /** * Should probably make a "Use recent buddies group" account preference * so that this option is surfaced to the user. @@ -1443,21 +603,63 @@ recent_buddies_pref_cb(const char *name, presence = aim_ssi_getpresence(od->ssi.local); if (value) aim_ssi_setpresence(od, presence & ~AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES); else aim_ssi_setpresence(od, presence | AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES); } +static const gchar *login_servers[] = { + AIM_DEFAULT_LOGIN_SERVER, + AIM_DEFAULT_SSL_LOGIN_SERVER, + ICQ_DEFAULT_LOGIN_SERVER, + ICQ_DEFAULT_SSL_LOGIN_SERVER, +}; + +static const gchar * +get_login_server(gboolean is_icq, gboolean use_ssl) +{ + return login_servers[(is_icq ? 2 : 0) + (use_ssl ? 1 : 0)]; +} + +static gint +compare_handlers(gconstpointer a, gconstpointer b) +{ + guint aa = GPOINTER_TO_UINT(a); + guint bb = GPOINTER_TO_UINT(b); + guint family1 = aa >> 16; + guint family2 = bb >> 16; + guint subtype1 = aa & 0xFFFF; + guint subtype2 = bb & 0xFFFF; + if (family1 != family2) { + return family1 - family2; + } + return subtype1 - subtype2; +} + +#if !GLIB_CHECK_VERSION(2,14,0) +static void hash_table_get_list_of_keys(gpointer key, gpointer value, gpointer user_data) +{ + GList **handlers = (GList **)user_data; + + *handlers = g_list_prepend(*handlers, key); +} +#endif /* GLIB < 2.14.0 */ + void oscar_login(PurpleAccount *account) { PurpleConnection *gc; OscarData *od; + const gchar *encryption_type; + GList *handlers; + GList *sorted_handlers; + GList *cur; + GString *msg = g_string_new(""); gc = purple_account_get_connection(account); od = oscar_data_new(); od->gc = gc; purple_connection_set_protocol_data(gc, od); oscar_data_addhandler(od, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNERR, purple_connerr, 0); oscar_data_addhandler(od, AIM_CB_FAM_SPECIAL, AIM_CB_SPECIAL_CONNINITDONE, flap_connection_established, 0); @@ -1496,36 +698,43 @@ oscar_login(PurpleAccount *account) oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_RECVAUTH, purple_ssi_authgiven, 0); oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_RECVAUTHREQ, purple_ssi_authrequest, 0); oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_RECVAUTHREP, purple_ssi_authreply, 0); oscar_data_addhandler(od, SNAC_FAMILY_FEEDBAG, SNAC_SUBTYPE_FEEDBAG_ADDED, purple_ssi_gotadded, 0); oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_INCOMING, purple_parse_incoming_im, 0); oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_MISSEDCALL, purple_parse_misses, 0); oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_CLIENTAUTORESP, purple_parse_clientauto, 0); oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_MTN, purple_parse_mtn, 0); - oscar_data_addhandler(od, SNAC_FAMILY_ICBM, SNAC_SUBTYPE_ICBM_ACK, purple_parse_msgack, 0); -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS - oscar_data_addhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_OFFLINEMSG, purple_offlinemsg, 0); - oscar_data_addhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_OFFLINEMSGCOMPLETE, purple_offlinemsgdone, 0); -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ - oscar_data_addhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_ALIAS, purple_icqalias, 0); - oscar_data_addhandler(od, SNAC_FAMILY_ICQ, SNAC_SUBTYPE_ICQ_INFO, purple_icqinfo, 0); oscar_data_addhandler(od, SNAC_FAMILY_LOCATE, SNAC_SUBTYPE_LOCATE_RIGHTSINFO, purple_parse_locaterights, 0); - oscar_data_addhandler(od, SNAC_FAMILY_LOCATE, SNAC_SUBTYPE_LOCATE_USERINFO, purple_parse_userinfo, 0); - oscar_data_addhandler(od, SNAC_FAMILY_LOCATE, SNAC_SUBTYPE_LOCATE_ERROR, purple_parse_locerr, 0); oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, 0x0001, purple_parse_genericerr, 0); oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, 0x000f, purple_selfinfo, 0); oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, 0x001f, purple_memrequest, 0); oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, SNAC_SUBTYPE_OSERVICE_REDIRECT, purple_handle_redirect, 0); oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, SNAC_SUBTYPE_OSERVICE_MOTD, purple_parse_motd, 0); - oscar_data_addhandler(od, SNAC_FAMILY_OSERVICE, SNAC_SUBTYPE_OSERVICE_EVIL, purple_parse_evilnotify, 0); oscar_data_addhandler(od, SNAC_FAMILY_POPUP, 0x0002, purple_popup, 0); oscar_data_addhandler(od, SNAC_FAMILY_USERLOOKUP, SNAC_SUBTYPE_USERLOOKUP_ERROR, purple_parse_searcherror, 0); oscar_data_addhandler(od, SNAC_FAMILY_USERLOOKUP, 0x0003, purple_parse_searchreply, 0); + g_string_append(msg, "Registered handlers: "); +#if GLIB_CHECK_VERSION(2,14,0) + handlers = g_hash_table_get_keys(od->handlerlist); +#else + handlers = NULL; + g_hash_table_foreach(od->handlerlist, hash_table_get_list_of_keys, &handlers); +#endif /* GLIB < 2.14.0 */ + sorted_handlers = g_list_sort(g_list_copy(handlers), compare_handlers); + for (cur = sorted_handlers; cur; cur = cur->next) { + guint x = GPOINTER_TO_UINT(cur->data); + g_string_append_printf(msg, "%04x/%04x, ", x >> 16, x & 0xFFFF); + } + g_list_free(sorted_handlers); + g_list_free(handlers); + purple_debug_misc("oscar", "%s\n", msg->str); + g_string_free(msg, TRUE); + purple_debug_misc("oscar", "oscar_login: gc = %p\n", gc); if (!oscar_util_valid_name(purple_account_get_username(account))) { gchar *buf; buf = g_strdup_printf(_("Unable to sign on as %s because the username is invalid. Usernames must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers."), purple_account_get_username(account)); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_INVALID_SETTINGS, buf); g_free(buf); return; @@ -1535,17 +744,26 @@ oscar_login(PurpleAccount *account) if (oscar_util_valid_name_icq((purple_account_get_username(account)))) { od->icq = TRUE; gc->flags |= PURPLE_CONNECTION_SUPPORT_MOODS; } else { gc->flags |= PURPLE_CONNECTION_AUTO_RESP; } od->default_port = purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT); - od->use_ssl = purple_account_get_bool(account, "use_ssl", OSCAR_DEFAULT_USE_SSL); + + encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION); + if (!purple_ssl_is_supported() && strcmp(encryption_type, OSCAR_REQUIRE_ENCRYPTION) == 0) { + purple_connection_error_reason( + gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("You required encryption in your account settings, but encryption is not supported by your system.")); + return; + } + od->use_ssl = purple_ssl_is_supported() && strcmp(encryption_type, OSCAR_NO_ENCRYPTION) != 0; /* Connect to core Purple signals */ purple_prefs_connect_callback(gc, "/messenger/status/reportIdle", idle_reporting_pref_cb, gc); purple_prefs_connect_callback(gc, "/plugins/prpl/oscar/recent_buddies", recent_buddies_pref_cb, gc); /* * On 2008-03-05 AOL released some documentation on the OSCAR protocol * which includes a new login method called clientLogin. It is similar @@ -1560,68 +778,61 @@ oscar_login(PurpleAccount *account) send_client_login(od, purple_account_get_username(account)); } else { FlapConnection *newconn; const char *server; newconn = flap_connection_new(od, SNAC_FAMILY_AUTH); if (od->use_ssl) { - if (!purple_ssl_is_supported()) { - purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, - _("SSL support unavailable")); - return; - } - - server = purple_account_get_string(account, "server", OSCAR_DEFAULT_SSL_LOGIN_SERVER); + server = purple_account_get_string(account, "server", get_login_server(od->icq, TRUE)); /* * If the account's server is what the oscar prpl has offered as * the default login server through the vast eons (all two of * said default options, AFAIK) and the user wants SSL, we'll * do what we know is best for them and change the setting out * from under them to the SSL login server. */ - if (!strcmp(server, OSCAR_DEFAULT_LOGIN_SERVER) || !strcmp(server, OSCAR_OLD_LOGIN_SERVER)) { + if (!strcmp(server, get_login_server(od->icq, FALSE)) || !strcmp(server, AIM_ALT_LOGIN_SERVER)) { purple_debug_info("oscar", "Account uses SSL, so changing server to default SSL server\n"); - purple_account_set_string(account, "server", OSCAR_DEFAULT_SSL_LOGIN_SERVER); - server = OSCAR_DEFAULT_SSL_LOGIN_SERVER; + purple_account_set_string(account, "server", get_login_server(od->icq, TRUE)); + server = get_login_server(od->icq, TRUE); } newconn->gsc = purple_ssl_connect(account, server, purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT), ssl_connection_established_cb, ssl_connection_error_cb, newconn); } else { - server = purple_account_get_string(account, "server", OSCAR_DEFAULT_LOGIN_SERVER); + server = purple_account_get_string(account, "server", get_login_server(od->icq, FALSE)); /* * See the comment above. We do the reverse here. If they don't want * SSL but their server is set to OSCAR_DEFAULT_SSL_LOGIN_SERVER, * set it back to the default. */ - if (!strcmp(server, OSCAR_DEFAULT_SSL_LOGIN_SERVER)) { + if (!strcmp(server, get_login_server(od->icq, TRUE))) { purple_debug_info("oscar", "Account does not use SSL, so changing server back to non-SSL\n"); - purple_account_set_string(account, "server", OSCAR_DEFAULT_LOGIN_SERVER); - server = OSCAR_DEFAULT_LOGIN_SERVER; + purple_account_set_string(account, "server", get_login_server(od->icq, FALSE)); + server = get_login_server(od->icq, FALSE); } newconn->connect_data = purple_proxy_connect(NULL, account, server, purple_account_get_int(account, "port", OSCAR_DEFAULT_LOGIN_PORT), connection_established_cb, newconn); } if (newconn->gsc == NULL && newconn->connect_data == NULL) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return; } } purple_connection_update_progress(gc, _("Connecting"), 0, OSCAR_CONNECT_STEPS); - ck[0] = 0x5a; } void oscar_close(PurpleConnection *gc) { OscarData *od; od = purple_connection_get_protocol_data(gc); @@ -1771,44 +982,16 @@ static int purple_memrequest(OscarData * offset, len, (modname ? modname : "aim.exe")); if (len == 0) { purple_debug_misc("oscar", "len is 0, hashing NULL\n"); aim_sendmemblock(od, conn, offset, len, NULL, AIM_SENDMEMBLOCK_FLAG_ISREQUEST); return 1; } - /* uncomment this when you're convinced it's right. remember, it's been wrong before. */ -#if 0 - if (offset > AIM_MAX_FILE_SIZE || len > AIM_MAX_FILE_SIZE) { - char *buf; - int i = 8; - if (modname) - i += strlen(modname); - buf = g_malloc(i); - i = 0; - if (modname) { - memcpy(buf, modname, strlen(modname)); - i += strlen(modname); - } - buf[i++] = offset & 0xff; - buf[i++] = (offset >> 8) & 0xff; - buf[i++] = (offset >> 16) & 0xff; - buf[i++] = (offset >> 24) & 0xff; - buf[i++] = len & 0xff; - buf[i++] = (len >> 8) & 0xff; - buf[i++] = (len >> 16) & 0xff; - buf[i++] = (len >> 24) & 0xff; - purple_debug_misc("oscar", "len + offset is invalid, " - "hashing request\n"); - aim_sendmemblock(od, command->conn, offset, i, buf, AIM_SENDMEMBLOCK_FLAG_ISREQUEST); - g_free(buf); - return 1; - } -#endif pos = g_new0(struct pieceofcrap, 1); pos->gc = od->gc; pos->conn = conn; pos->offset = offset; pos->len = len; pos->modname = g_strdup(modname); @@ -1838,18 +1021,18 @@ int oscar_connect_to_bos(PurpleConnectio account = purple_connection_get_account(gc); conn = flap_connection_new(od, SNAC_FAMILY_LOCATE); conn->cookielen = cookielen; conn->cookie = g_memdup(cookie, cookielen); /* - * tls_certname is only set (and must be set if we get this far) if - * SSL is enabled. + * Use TLS only if the server provided us with a tls_certname. The server might not specify a tls_certname even if we requested to use TLS, + * and that is something we should be prepared to. */ if (tls_certname) { conn->gsc = purple_ssl_connect_with_ssl_cn(account, host, port, ssl_connection_established_cb, ssl_connection_error_cb, tls_certname, conn); } else @@ -1863,17 +1046,16 @@ int oscar_connect_to_bos(PurpleConnectio { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return 0; } od->default_port = port; purple_connection_update_progress(gc, _("Received authorization"), 3, OSCAR_CONNECT_STEPS); - ck[3] = 0x64; return 1; } /** * Only used when connecting with the old-style BUCP login. */ static int @@ -1983,17 +1165,16 @@ purple_parse_auth_resp(OscarData *od, Fl g_free(host); if (newconn->gsc == NULL && newconn->connect_data == NULL) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Unable to connect")); return 0; } purple_connection_update_progress(gc, _("Received authorization"), 3, OSCAR_CONNECT_STEPS); - ck[3] = 0x64; return 1; } /** * Only used when connecting with the old-style BUCP login. */ static void @@ -2067,17 +1248,16 @@ purple_parse_login(OscarData *od, FlapCo va_end(ap); aim_send_login(od, conn, purple_account_get_username(account), purple_connection_get_password(gc), truncate_pass, od->icq ? &icqinfo : &aiminfo, key, purple_account_get_bool(account, "allow_multiple_logins", OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS)); purple_connection_update_progress(gc, _("Password sent"), 2, OSCAR_CONNECT_STEPS); - ck[2] = 0x6c; return 1; } static int purple_handle_redirect(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; @@ -2097,31 +1277,42 @@ purple_handle_redirect(OscarData *od, Fl if (separator != NULL) { host = g_strndup(redir->ip, separator - redir->ip); port = atoi(separator + 1); } else host = g_strdup(redir->ip); + if (!redir->use_ssl) { + const gchar *encryption_type = purple_account_get_string(account, "encryption", OSCAR_DEFAULT_ENCRYPTION); + if (strcmp(encryption_type, OSCAR_OPPORTUNISTIC_ENCRYPTION) == 0) { + purple_debug_warning("oscar", "We won't use SSL for FLAP type 0x%04hx.\n", redir->group); + } else if (strcmp(encryption_type, OSCAR_REQUIRE_ENCRYPTION) == 0) { + purple_debug_error("oscar", "FLAP server %s:%d of type 0x%04hx doesn't support encryption.", host, port, redir->group); + purple_connection_error_reason( + gc, + PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, + _("You required encryption in your account settings, but one of the servers doesn't support it.")); + return 0; + } + } + /* * These FLAP servers advertise SSL (type "0x02"), but SSL connections to these hosts * die a painful death. iChat and Miranda, when using SSL, still do these in plaintext. */ if (redir->use_ssl && (redir->group == SNAC_FAMILY_ADMIN || redir->group == SNAC_FAMILY_BART)) { - purple_debug_info("oscar", "Ignoring broken SSL for FLAP type 0x%04hx.\n", - redir->group); + purple_debug_info("oscar", "Ignoring broken SSL for FLAP type 0x%04hx.\n", redir->group); redir->use_ssl = 0; } - purple_debug_info("oscar", "Connecting to FLAP server %s:%d of type 0x%04hx%s\n", - host, port, redir->group, - od->use_ssl && !redir->use_ssl ? " without SSL, despite main stream encryption" : ""); + purple_debug_info("oscar", "Connecting to FLAP server %s:%d of type 0x%04hx\n", host, port, redir->group); newconn = flap_connection_new(od, redir->group); newconn->cookielen = redir->cookielen; newconn->cookie = g_memdup(redir->cookie, redir->cookielen); if (newconn->type == SNAC_FAMILY_CHAT) { struct chat_connection *cc; cc = g_new0(struct chat_connection, 1); @@ -2161,38 +1352,43 @@ purple_handle_redirect(OscarData *od, Fl return 1; } static int purple_parse_oncoming(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc; PurpleAccount *account; + PurpleBuddy *buddy = NULL; + PurpleStatus *previous_status = NULL; struct buddyinfo *bi; time_t time_idle = 0, signon = 0; int type = 0; gboolean buddy_is_away = FALSE; const char *status_id; va_list ap; aim_userinfo_t *info; - char *message = NULL; + char *message; char *itmsurl = NULL; - char *tmp; - const char *tmp2; gc = od->gc; account = purple_connection_get_account(gc); va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); va_end(ap); g_return_val_if_fail(info != NULL, 1); g_return_val_if_fail(info->bn != NULL, 1); + buddy = purple_find_buddy(account, info->bn); + if (buddy) { + previous_status = purple_presence_get_active_status(purple_buddy_get_presence(buddy)); + } + /* * If this is an AIM buddy and their name has formatting, set their * server alias. */ if (!oscar_util_valid_name_icq(info->bn)) { gboolean bn_has_formatting = FALSE; char *c; for (c = info->bn; *c != '\0'; c++) { @@ -2246,50 +1442,40 @@ static int purple_parse_oncoming(OscarDa if (type & AIM_ICQ_STATE_INVISIBLE) status_id = OSCAR_STATUS_ID_INVISIBLE; else if (buddy_is_away) status_id = OSCAR_STATUS_ID_AWAY; else status_id = OSCAR_STATUS_ID_AVAILABLE; } - if (info->flags & AIM_FLAG_WIRELESS) - { + if (info->flags & AIM_FLAG_WIRELESS) { purple_prpl_got_user_status(account, info->bn, OSCAR_STATUS_ID_MOBILE, NULL); } else { purple_prpl_got_user_status_deactive(account, info->bn, OSCAR_STATUS_ID_MOBILE); } - if (info->status != NULL && info->status[0] != '\0') - /* Grab the available message */ - message = oscar_encoding_to_utf8(account, info->status_encoding, - info->status, info->status_len); - - tmp2 = tmp = (message ? purple_markup_escape_text(message, -1) : NULL); + message = (info->status && info->status_len > 0) + ? oscar_encoding_to_utf8(info->status_encoding, info->status, info->status_len) + : NULL; if (strcmp(status_id, OSCAR_STATUS_ID_AVAILABLE) == 0) { - if (info->itmsurl_encoding && info->itmsurl && info->itmsurl_len) - /* Grab the iTunes Music Store URL */ - itmsurl = oscar_encoding_to_utf8(account, info->itmsurl_encoding, - info->itmsurl, info->itmsurl_len); - - if (tmp2 == NULL && itmsurl != NULL) - /* - * The message can't be NULL because NULL means it was the - * last attribute, so the itmsurl would get ignored below. - */ - tmp2 = ""; - - purple_prpl_got_user_status(account, info->bn, status_id, - "message", tmp2, "itmsurl", itmsurl, NULL); + /* TODO: If itmsurl is NULL, does that mean the URL has been + cleared? Or does it mean the URL should remain unchanged? */ + if (info->itmsurl != NULL) { + itmsurl = (info->itmsurl_len > 0) ? oscar_encoding_to_utf8(info->itmsurl_encoding, info->itmsurl, info->itmsurl_len) : NULL; + } else if (previous_status != NULL && purple_status_is_available(previous_status)) { + itmsurl = g_strdup(purple_status_get_attr_string(previous_status, "itmsurl")); + } + purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s', itmsurl = '%s'\n", status_id, info->bn, message, itmsurl); + purple_prpl_got_user_status(account, info->bn, status_id, "message", message, "itmsurl", itmsurl, NULL); + } else { + purple_debug_info("oscar", "Activating status '%s' for buddy %s, message = '%s'\n", status_id, info->bn, message); + purple_prpl_got_user_status(account, info->bn, status_id, "message", message, NULL); } - else - purple_prpl_got_user_status(account, info->bn, status_id, "message", tmp2, NULL); - - g_free(tmp); g_free(message); g_free(itmsurl); /* Login time stuff */ if (info->present & AIM_USERINFO_PRESENT_ONLINESINCE) signon = info->onlinesince; else if (info->present & AIM_USERINFO_PRESENT_SESSIONLEN) @@ -2340,23 +1526,16 @@ static int purple_parse_oncoming(OscarDa } } g_free(b16); } return 1; } -static void purple_check_comment(OscarData *od, const char *str) { - if ((str == NULL) || strcmp(str, (const char *)ck)) - aim_locate_setcaps(od, purple_caps); - else - aim_locate_setcaps(od, purple_caps | OSCAR_CAPABILITY_SECUREIM); -} - static int purple_parse_offgoing(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; PurpleAccount *account = purple_connection_get_account(gc); va_list ap; aim_userinfo_t *info; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); @@ -2370,27 +1549,21 @@ static int purple_parse_offgoing(OscarDa } static int incomingim_chan1(OscarData *od, FlapConnection *conn, aim_userinfo_t *userinfo, struct aim_incomingim_ch1_args *args) { PurpleConnection *gc = od->gc; PurpleAccount *account = purple_connection_get_account(gc); PurpleMessageFlags flags = 0; struct buddyinfo *bi; PurpleStoredImage *img; - GString *message; gchar *tmp; - aim_mpmsg_section_t *curpart; const char *start, *end; GData *attribs; - purple_debug_misc("oscar", "Received IM from %s with %d parts\n", - userinfo->bn, args->mpmsg.numparts); - - if (args->mpmsg.numparts == 0) - return 1; + purple_debug_misc("oscar", "Received IM from %s\n", userinfo->bn); bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, userinfo->bn)); if (!bi) { bi = g_new0(struct buddyinfo, 1); g_hash_table_insert(od->buddyinfo, g_strdup(purple_normalize(account, userinfo->bn)), bi); } if (args->icbmflags & AIM_IMFLAGS_AWAY) @@ -2420,29 +1593,17 @@ static int incomingim_chan1(OscarData *o "Sending buddy icon to %s (%" G_GSIZE_FORMAT " bytes)\n", userinfo->bn, len); aim_im_sendch2_icon(od, userinfo->bn, data, len, purple_buddy_icons_get_account_icon_timestamp(account), aimutil_iconsum(data, len)); } purple_imgstore_unref(img); - message = g_string_new(""); - curpart = args->mpmsg.parts; - while (curpart != NULL) { - tmp = purple_plugin_oscar_decode_im_part(account, userinfo->bn, curpart->charset, - curpart->charsubset, curpart->data, curpart->datalen); - if (tmp != NULL) { - g_string_append(message, tmp); - g_free(tmp); - } - - curpart = curpart->next; - } - tmp = g_string_free(message, FALSE); + tmp = g_strdup(args->msg); /* * Convert iChat color tags to normal font tags. */ if (purple_markup_find_tag("body", tmp, &start, &end, &attribs)) { int len; char *tmp2, *body; @@ -2516,18 +1677,17 @@ static int incomingim_chan1(OscarData *o g_datalist_clear(&attribs); len = start - tmp; tmp2 = g_strdup_printf("%.*s%s", len, tmp, end + 1); g_free(tmp); tmp = tmp2; } - serv_got_im(gc, userinfo->bn, tmp, flags, - (args->icbmflags & AIM_IMFLAGS_OFFLINE) ? args->timestamp : time(NULL)); + serv_got_im(gc, userinfo->bn, tmp, flags, (args->icbmflags & AIM_IMFLAGS_OFFLINE) ? args->timestamp : time(NULL)); g_free(tmp); return 1; } static int incomingim_chan2(OscarData *od, FlapConnection *conn, aim_userinfo_t *userinfo, IcbmArgsCh2 *args) { @@ -2545,45 +1705,30 @@ incomingim_chan2(OscarData *od, FlapConn if (args == NULL) return 0; purple_debug_misc("oscar", "Incoming rendezvous message of type %" G_GUINT64_FORMAT ", user %s, status %hu\n", args->type, userinfo->bn, args->status); - if (args->msg != NULL) - { - if (args->encoding != NULL) - { - char *encoding = NULL; - encoding = oscar_encoding_extract(args->encoding); - message = oscar_encoding_to_utf8(account, encoding, args->msg, - args->msglen); - g_free(encoding); - } else { - if (g_utf8_validate(args->msg, args->msglen, NULL)) - message = g_strdup(args->msg); - } + if (args->msg != NULL) { + message = oscar_encoding_to_utf8(args->encoding, args->msg, args->msglen); } if (args->type & OSCAR_CAPABILITY_CHAT) { - char *encoding, *utf8name, *tmp; + char *utf8name, *tmp; GHashTable *components; if (!args->info.chat.roominfo.name || !args->info.chat.roominfo.exchange) { g_free(message); return 1; } - encoding = args->encoding ? oscar_encoding_extract(args->encoding) : NULL; - utf8name = oscar_encoding_to_utf8(account, encoding, - args->info.chat.roominfo.name, - args->info.chat.roominfo.namelen); - g_free(encoding); + utf8name = oscar_encoding_to_utf8(args->encoding, args->info.chat.roominfo.name, args->info.chat.roominfo.namelen); tmp = extract_name(utf8name); if (tmp != NULL) { g_free(utf8name); utf8name = tmp; } @@ -2594,26 +1739,25 @@ incomingim_chan2(OscarData *od, FlapConn g_strdup_printf("%d", args->info.chat.roominfo.exchange)); serv_got_chat_invite(gc, utf8name, userinfo->bn, message, components); } - else if ((args->type & OSCAR_CAPABILITY_SENDFILE) || - (args->type & OSCAR_CAPABILITY_DIRECTIM)) + else if ((args->type & OSCAR_CAPABILITY_SENDFILE) || (args->type & OSCAR_CAPABILITY_DIRECTIM)) { if (args->status == AIM_RENDEZVOUS_PROPOSE) { peer_connection_got_proposition(od, userinfo->bn, message, args); } else if (args->status == AIM_RENDEZVOUS_CANCEL) { - /* The other user canceled a peer request */ + /* The other user cancelled a peer request */ PeerConnection *conn; conn = peer_connection_find_by_cookie(od, userinfo->bn, args->cookie); /* * If conn is NULL it means we haven't tried to create * a connection with that user. They may be trying to * do something malicious. */ @@ -2648,34 +1792,46 @@ incomingim_chan2(OscarData *od, FlapConn NULL); } else if (args->type & OSCAR_CAPABILITY_ICQSERVERRELAY) { purple_debug_info("oscar", "Got an ICQ Server Relay message of " "type %d\n", args->info.rtfmsg.msgtype); - if (args->info.rtfmsg.msgtype == 1) - { - if (args->info.rtfmsg.rtfmsg != NULL) - { - char *rtfmsg = NULL; - if (args->encoding != NULL) { - char *encoding = oscar_encoding_extract(args->encoding); - rtfmsg = oscar_encoding_to_utf8(account, encoding, - args->info.rtfmsg.rtfmsg, strlen(args->info.rtfmsg.rtfmsg)); - g_free(encoding); - } else { - if (g_utf8_validate(args->info.rtfmsg.rtfmsg, strlen(args->info.rtfmsg.rtfmsg), NULL)) - rtfmsg = g_strdup(args->info.rtfmsg.rtfmsg); + if (args->info.rtfmsg.msgtype == 1) { + if (args->info.rtfmsg.msg != NULL) { + char *rtfmsg; + const char *encoding = args->encoding; + size_t len = strlen(args->info.rtfmsg.msg); + char *tmp, *tmp2; + + if (encoding == NULL && !g_utf8_validate(args->info.rtfmsg.msg, len, NULL)) { + /* Yet another wonderful Miranda-related hack. If their user disables the "Send Unicode messages" setting, + * Miranda sends us ch2 messages in whatever Windows codepage is set as default on their user's system (instead of UTF-8). + * Of course, they don't bother to specify that codepage. Let's just fallback to the encoding OUR users can + * specify in account options as a last resort. + */ + encoding = purple_account_get_string(account, "encoding", OSCAR_DEFAULT_CUSTOM_ENCODING); + purple_debug_info("oscar", "Miranda, is that you? Using '%s' as encoding\n", encoding); } - if (rtfmsg) { - serv_got_im(gc, userinfo->bn, rtfmsg, flags, time(NULL)); - g_free(rtfmsg); - } + + rtfmsg = oscar_encoding_to_utf8(encoding, args->info.rtfmsg.msg, len); + + /* Channel 2 messages are supposed to be plain-text (never mind the name "rtfmsg", even + * the official client doesn't parse them as RTF). Therefore, we should escape them before + * showing to the user. */ + tmp = g_markup_escape_text(rtfmsg, -1); + g_free(rtfmsg); + tmp2 = purple_strreplace(tmp, "\r\n", "
"); + g_free(tmp); + + serv_got_im(gc, userinfo->bn, tmp2, flags, time(NULL)); + aim_im_send_icq_confirmation(od, userinfo->bn, args->cookie); + g_free(tmp2); } } else if (args->info.rtfmsg.msgtype == 26) { purple_debug_info("oscar", "Sending X-Status Reply\n"); icq_relay_xstatus(od, userinfo->bn, args->cookie); } } else { @@ -2683,140 +1839,16 @@ incomingim_chan2(OscarData *od, FlapConn G_GUINT64_FORMAT "\n", args->type); } g_free(message); return 1; } -/* - * Authorization Functions - * Most of these are callbacks from dialogs. They're used by both - * methods of authorization (SSI and old-school channel 4 ICBM) - */ -/* When you ask other people for authorization */ -static void -purple_auth_request(struct name_data *data, char *msg) -{ - PurpleConnection *gc; - OscarData *od; - PurpleAccount *account; - PurpleBuddy *buddy; - PurpleGroup *group; - const char *bname, *gname; - - gc = data->gc; - od = purple_connection_get_protocol_data(gc); - account = purple_connection_get_account(gc); - buddy = purple_find_buddy(account, data->name); - if (buddy != NULL) - group = purple_buddy_get_group(buddy); - else - group = NULL; - - if (group != NULL) - { - bname = purple_buddy_get_name(buddy); - gname = purple_group_get_name(group); - purple_debug_info("oscar", "ssi: adding buddy %s to group %s\n", - bname, gname); - aim_ssi_sendauthrequest(od, data->name, msg ? msg : _("Please authorize me so I can add you to my buddy list.")); - if (!aim_ssi_itemlist_finditem(od->ssi.local, gname, bname, AIM_SSI_TYPE_BUDDY)) - { - aim_ssi_addbuddy(od, bname, gname, NULL, purple_buddy_get_alias_only(buddy), NULL, NULL, TRUE); - - /* Mobile users should always be online */ - if (bname[0] == '+') { - purple_prpl_got_user_status(account, - purple_buddy_get_name(buddy), - OSCAR_STATUS_ID_AVAILABLE, NULL); - purple_prpl_got_user_status(account, - purple_buddy_get_name(buddy), - OSCAR_STATUS_ID_MOBILE, NULL); - } - } - } - - oscar_free_name_data(data); -} - -static void -purple_auth_sendrequest(PurpleConnection *gc, const char *name) -{ - struct name_data *data; - - data = g_new0(struct name_data, 1); - data->gc = gc; - data->name = g_strdup(name); - -#if 0 - purple_request_input(data->gc, NULL, _("Authorization Request Message:"), - NULL, _("Please authorize me!"), TRUE, FALSE, NULL, - _("_OK"), G_CALLBACK(purple_auth_request), - _("_Cancel"), G_CALLBACK(oscar_free_name_data), - purple_connection_get_account(gc), name, NULL, - data); -#else - purple_auth_request(data, NULL); -#endif -} - -static void -purple_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored) -{ - PurpleBuddy *buddy; - PurpleConnection *gc; - - g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); - - buddy = (PurpleBuddy *) node; - gc = purple_account_get_connection(purple_buddy_get_account(buddy)); - purple_auth_sendrequest(gc, purple_buddy_get_name(buddy)); -} - -/* When other people ask you for authorization */ -static void -purple_auth_grant(gpointer cbdata) -{ - struct name_data *data = cbdata; - PurpleConnection *gc = data->gc; - OscarData *od = purple_connection_get_protocol_data(gc); - - aim_ssi_sendauthreply(od, data->name, 0x01, NULL); - - oscar_free_name_data(data); -} - -/* When other people ask you for authorization */ -static void -purple_auth_dontgrant(struct name_data *data, char *msg) -{ - PurpleConnection *gc = data->gc; - OscarData *od = purple_connection_get_protocol_data(gc); - - aim_ssi_sendauthreply(od, data->name, 0x00, msg ? msg : _("No reason given.")); -} - -static void -purple_auth_dontgrant_msgprompt(gpointer cbdata) -{ - struct name_data *data = cbdata; -#if 0 - purple_request_input(data->gc, NULL, _("Authorization Denied Message:"), - NULL, _("No reason given."), TRUE, FALSE, NULL, - _("_OK"), G_CALLBACK(purple_auth_dontgrant), - _("_Cancel"), G_CALLBACK(oscar_free_name_data), - purple_connection_get_account(data->gc), data->name, NULL, - data); -#else - purple_auth_dontgrant(data, NULL); -#endif -} - /* When someone sends you buddies */ static void purple_icq_buddyadd(struct name_data *data) { PurpleConnection *gc = data->gc; purple_blist_request_add_buddy(purple_connection_get_account(gc), data->name, NULL, data->nick); @@ -2850,17 +1882,17 @@ incomingim_chan4(OscarData *od, FlapConn msg1 = g_strsplit(args->msg, "\376", (args->type == 0x01 ? 1 : 0)); for (numtoks=0; msg1[numtoks]; numtoks++); msg2 = (gchar **)g_malloc((numtoks+1)*sizeof(gchar *)); for (i=0; msg1[i]; i++) { gchar *uin = g_strdup_printf("%u", args->uin); purple_str_strip_char(msg1[i], '\r'); /* TODO: Should use an encoding other than ASCII? */ - msg2[i] = purple_plugin_oscar_decode_im_part(account, uin, AIM_CHARSET_ASCII, 0x0000, msg1[i], strlen(msg1[i])); + msg2[i] = oscar_decode_im(account, uin, AIM_CHARSET_ASCII, msg1[i], strlen(msg1[i])); g_free(uin); } msg2[i] = NULL; switch (args->type) { case 0x01: { /* MacICQ message or basic offline message */ if (i >= 1) { gchar *uin = g_strdup_printf("%u", args->uin); @@ -2903,34 +1935,27 @@ incomingim_chan4(OscarData *od, FlapConn g_free(uin); g_free(message); } } } break; case 0x06: { /* Someone requested authorization */ if (i >= 6) { - struct name_data *data = g_new(struct name_data, 1); gchar *bn = g_strdup_printf("%u", args->uin); gchar *reason = NULL; if (msg2[5] != NULL) - reason = purple_plugin_oscar_decode_im_part(account, bn, AIM_CHARSET_LATIN_1, 0x0000, msg2[5], strlen(msg2[5])); + reason = oscar_decode_im(account, bn, AIM_CHARSET_LATIN_1, msg2[5], strlen(msg2[5])); purple_debug_info("oscar", "Received an authorization request from UIN %u\n", args->uin); - data->gc = gc; - data->name = bn; - data->nick = NULL; - - purple_account_request_authorization(account, bn, NULL, NULL, - reason, purple_find_buddy(account, bn) != NULL, - purple_auth_grant, - purple_auth_dontgrant_msgprompt, data); + aim_icq_getalias(od, bn, TRUE, reason); + g_free(bn); g_free(reason); } } break; case 0x07: { /* Someone has denied you authorization */ if (i >= 1) { gchar *dialog_msg = g_strdup_printf(_("The user %u has denied your request to add them to your buddy list for the following reason:\n%s"), args->uin, msg2[0] ? msg2[0] : _("No reason given.")); purple_notify_info(gc, NULL, _("ICQ authorization denied."), @@ -3384,115 +2409,16 @@ static int purple_parse_mtn(OscarData *o "notification message from %s. Channel is 0x%04x " "and event is 0x%04hx.\n", bn, channel, event); } break; } return 1; } -/* - * We get this error when there was an error in the locate family. This - * happens when you request info of someone who is offline. - */ -static int purple_parse_locerr(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - gchar *buf; - va_list ap; - guint16 reason; - char *destn; - PurpleNotifyUserInfo *user_info; - - va_start(ap, fr); - reason = (guint16) va_arg(ap, unsigned int); - destn = va_arg(ap, char *); - va_end(ap); - - if (destn == NULL) - return 1; - - user_info = purple_notify_user_info_new(); - buf = g_strdup_printf(_("User information not available: %s"), oscar_get_msgerr_reason(reason)); - purple_notify_user_info_add_pair(user_info, NULL, buf); - purple_notify_userinfo(od->gc, destn, user_info, NULL, NULL); - purple_notify_user_info_destroy(user_info); - purple_conv_present_error(destn, purple_connection_get_account(od->gc), buf); - g_free(buf); - - return 1; -} - -static int purple_parse_userinfo(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - PurpleConnection *gc = od->gc; - PurpleAccount *account = purple_connection_get_account(gc); - PurpleNotifyUserInfo *user_info; - gchar *tmp = NULL, *info_utf8 = NULL, *base_profile_url = NULL; - va_list ap; - aim_userinfo_t *userinfo; - - va_start(ap, fr); - userinfo = va_arg(ap, aim_userinfo_t *); - va_end(ap); - - user_info = purple_notify_user_info_new(); - - oscar_user_info_append_status(gc, user_info, /* PurpleBuddy */ NULL, userinfo, /* strip_html_tags */ FALSE); - - if ((userinfo->present & AIM_USERINFO_PRESENT_IDLE) && userinfo->idletime != 0) { - tmp = purple_str_seconds_to_string(userinfo->idletime*60); - oscar_user_info_add_pair(user_info, _("Idle"), tmp); - g_free(tmp); - } - - oscar_user_info_append_extra_info(gc, user_info, NULL, userinfo); - - if ((userinfo->present & AIM_USERINFO_PRESENT_ONLINESINCE) && !oscar_util_valid_name_sms(userinfo->bn)) { - /* An SMS contact is always online; its Online Since value is not useful */ - time_t t = userinfo->onlinesince; - oscar_user_info_add_pair(user_info, _("Online Since"), purple_date_format_full(localtime(&t))); - } - - if (userinfo->present & AIM_USERINFO_PRESENT_MEMBERSINCE) { - time_t t = userinfo->membersince; - oscar_user_info_add_pair(user_info, _("Member Since"), purple_date_format_full(localtime(&t))); - } - - if (userinfo->capabilities != 0) { - tmp = oscar_caps_to_string(userinfo->capabilities); - oscar_user_info_add_pair(user_info, _("Capabilities"), tmp); - g_free(tmp); - } - - /* Info */ - if ((userinfo->info_len > 0) && (userinfo->info != NULL) && (userinfo->info_encoding != NULL)) { - tmp = oscar_encoding_extract(userinfo->info_encoding); - info_utf8 = oscar_encoding_to_utf8(account, tmp, userinfo->info, - userinfo->info_len); - g_free(tmp); - if (info_utf8 != NULL) { - tmp = purple_str_sub_away_formatters(info_utf8, purple_account_get_username(account)); - purple_notify_user_info_add_section_break(user_info); - oscar_user_info_add_pair(user_info, _("Profile"), tmp); - g_free(tmp); - g_free(info_utf8); - } - } - - purple_notify_user_info_add_section_break(user_info); - base_profile_url = oscar_util_valid_name_icq(userinfo->bn) ? "http://www.icq.com/people" : "http://profiles.aim.com"; - tmp = g_strdup_printf("%s", - base_profile_url, purple_normalize(account, userinfo->bn), _("View web profile")); - purple_notify_user_info_add_pair(user_info, NULL, tmp); - g_free(tmp); - - purple_notify_userinfo(gc, userinfo->bn, user_info, NULL, NULL); - purple_notify_user_info_destroy(user_info); - - return 1; -} - static int purple_parse_motd(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { char *msg; guint16 id; va_list ap; va_start(ap, fr); id = (guint16) va_arg(ap, unsigned int); @@ -3512,33 +2438,36 @@ static int purple_chatnav_info(OscarData va_list ap; guint16 type; va_start(ap, fr); type = (guint16) va_arg(ap, unsigned int); switch(type) { case 0x0002: { + GString *msg = g_string_new(""); guint8 maxrooms; struct aim_chat_exchangeinfo *exchanges; int exchangecount, i; maxrooms = (guint8) va_arg(ap, unsigned int); exchangecount = va_arg(ap, int); exchanges = va_arg(ap, struct aim_chat_exchangeinfo *); - purple_debug_misc("oscar", "chat info: Chat Rights:\n"); - purple_debug_misc("oscar", - "chat info: \tMax Concurrent Rooms: %hhd\n", maxrooms); - purple_debug_misc("oscar", - "chat info: \tExchange List: (%d total)\n", exchangecount); - for (i = 0; i < exchangecount; i++) - purple_debug_misc("oscar", - "chat info: \t\t%hu %s\n", - exchanges[i].number, exchanges[i].name ? exchanges[i].name : ""); + g_string_append_printf(msg, "chat info: Max Concurrent Rooms: %hhd, Exchange List (%d total): ", maxrooms, exchangecount); + for (i = 0; i < exchangecount; i++) { + g_string_append_printf(msg, "%hu", exchanges[i].number); + if (exchanges[i].name) { + g_string_append_printf(msg, " %s", exchanges[i].name); + } + g_string_append(msg, ", "); + } + purple_debug_misc("oscar", "%s\n", msg->str); + g_string_free(msg, TRUE); + while (od->create_rooms) { struct create_room *cr = od->create_rooms->data; purple_debug_info("oscar", "creating room %s\n", cr->name); aim_chatnav_createroom(od, conn, cr->name, cr->exchange); g_free(cr->name); od->create_rooms = g_slist_remove(od->create_rooms, cr); g_free(cr); @@ -3625,56 +2554,40 @@ static int purple_conv_chat_leave(OscarD for (i = 0; i < count; i++) purple_conv_chat_remove_user(PURPLE_CONV_CHAT(c->conv), info[i].bn, NULL); return 1; } static int purple_conv_chat_info_update(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { va_list ap; - aim_userinfo_t *userinfo; - struct aim_chat_roominfo *roominfo; - char *roomname; - int usercount; - char *roomdesc; - guint16 unknown_c9, unknown_d2, unknown_d5, maxmsglen, maxvisiblemsglen; - guint32 creationtime; + guint16 maxmsglen, maxvisiblemsglen; PurpleConnection *gc = od->gc; struct chat_connection *ccon = find_oscar_chat_by_conn(gc, conn); if (!ccon) return 1; va_start(ap, fr); - roominfo = va_arg(ap, struct aim_chat_roominfo *); - roomname = va_arg(ap, char *); - usercount= va_arg(ap, int); - userinfo = va_arg(ap, aim_userinfo_t *); - roomdesc = va_arg(ap, char *); - unknown_c9 = (guint16)va_arg(ap, unsigned int); - creationtime = va_arg(ap, guint32); maxmsglen = (guint16)va_arg(ap, unsigned int); - unknown_d2 = (guint16)va_arg(ap, unsigned int); - unknown_d5 = (guint16)va_arg(ap, unsigned int); maxvisiblemsglen = (guint16)va_arg(ap, unsigned int); va_end(ap); purple_debug_misc("oscar", "inside chat_info_update (maxmsglen = %hu, maxvislen = %hu)\n", maxmsglen, maxvisiblemsglen); ccon->maxlen = maxmsglen; ccon->maxvis = maxvisiblemsglen; return 1; } static int purple_conv_chat_incoming_msg(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; - PurpleAccount *account = purple_connection_get_account(gc); struct chat_connection *ccon = find_oscar_chat_by_conn(gc, conn); gchar *utf8; va_list ap; aim_userinfo_t *info; int len; char *msg; char *charset; @@ -3683,20 +2596,17 @@ static int purple_conv_chat_incoming_msg va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); len = va_arg(ap, int); msg = va_arg(ap, char *); charset = va_arg(ap, char *); va_end(ap); - utf8 = oscar_encoding_to_utf8(account, charset, msg, len); - if (utf8 == NULL) - /* The conversion failed! */ - utf8 = g_strdup(_("[Unable to display a message from this user because it contained invalid characters.]")); + utf8 = oscar_encoding_to_utf8(charset, msg, len); serv_got_chat_in(gc, ccon->id, info->bn, 0, utf8, time(NULL)); g_free(utf8); return 1; } static int purple_email_parseupdate(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { va_list ap; @@ -3804,51 +2714,16 @@ purple_icons_fetch(PurpleConnection *gc) g_free(od->requesticon->data); od->requesticon = g_slist_delete_link(od->requesticon, od->requesticon); } purple_debug_misc("oscar", "no more icons to request\n"); } -/* - * Received in response to an IM sent with the AIM_IMFLAGS_ACK option. - */ -static int purple_parse_msgack(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - va_list ap; - guint16 type; - char *bn; - - va_start(ap, fr); - type = (guint16) va_arg(ap, unsigned int); - bn = va_arg(ap, char *); - va_end(ap); - - purple_debug_info("oscar", "Sent message to %s.\n", bn); - - return 1; -} - -static int purple_parse_evilnotify(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { -#ifdef CRAZY_WARNING - va_list ap; - guint16 newevil; - aim_userinfo_t *userinfo; - - va_start(ap, fr); - newevil = (guint16) va_arg(ap, unsigned int); - userinfo = va_arg(ap, aim_userinfo_t *); - va_end(ap); - - purple_prpl_got_account_warning_level(account, (userinfo && userinfo->bn) ? userinfo->bn : NULL, (newevil/10.0) + 0.5); -#endif - - return 1; -} - static int purple_selfinfo(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { int warning_level; va_list ap; aim_userinfo_t *info; va_start(ap, fr); info = va_arg(ap, aim_userinfo_t *); va_end(ap); @@ -3859,20 +2734,16 @@ static int purple_selfinfo(OscarData *od * What's with the + 0.5? * The 0.5 is basically poor-man's rounding. Normally * casting "13.7" to an int will truncate to "13," but * with 13.7 + 0.5 = 14.2, which becomes "14" when * truncated. */ warning_level = info->warnlevel/10.0 + 0.5; -#ifdef CRAZY_WARNING - purple_presence_set_warning_level(presence, warning_level); -#endif - return 1; } static int purple_connerr(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; va_list ap; guint16 code; char *msg; @@ -4001,26 +2872,24 @@ static int purple_bosrights(OscarData *o is_available = purple_status_is_available(status); if (is_available) message = purple_status_get_attr_string(status, "message"); else message = NULL; tmp = purple_markup_strip_html(message); itmsurl = purple_status_get_attr_string(status, "itmsurl"); aim_srv_setextrainfo(od, FALSE, 0, is_available, tmp, itmsurl); + aim_srv_set_dc_info(od); g_free(tmp); presence = purple_status_get_presence(status); aim_srv_setidle(od, !purple_presence_is_idle(presence) ? 0 : time(NULL) - purple_presence_get_idle_time(presence)); if (od->icq) { -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS - aim_icq_reqofflinemsgs(od); -#endif - oscar_set_extendedstatus(gc); + oscar_set_extended_status(gc); aim_icq_setsecurity(od, purple_account_get_bool(account, "authorization", OSCAR_DEFAULT_AUTHORIZATION), purple_account_get_bool(account, "web_aware", OSCAR_DEFAULT_WEB_AWARE)); } aim_srv_requestnew(od, SNAC_FAMILY_ALERT); aim_srv_requestnew(od, SNAC_FAMILY_CHATNAV); @@ -4042,216 +2911,16 @@ static int purple_bosrights(OscarData *o aim_im_reqofflinemsgs(od); purple_connection_set_state(gc, PURPLE_CONNECTED); } return 1; } -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS -static int purple_offlinemsg(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - va_list ap; - struct aim_icq_offlinemsg *msg; - struct aim_incomingim_ch4_args args; - time_t t; - - va_start(ap, fr); - msg = va_arg(ap, struct aim_icq_offlinemsg *); - va_end(ap); - - purple_debug_info("oscar", - "Received offline message. Converting to channel 4 ICBM...\n"); - args.uin = msg->sender; - args.type = msg->type; - args.flags = msg->flags; - args.msglen = msg->msglen; - args.msg = msg->msg; - t = purple_time_build(msg->year, msg->month, msg->day, msg->hour, msg->minute, 0); - incomingim_chan4(od, conn, NULL, &args, t); - - return 1; -} - -static int purple_offlinemsgdone(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) -{ - aim_icq_ackofflinemsgs(od); - return 1; -} -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ - -static int purple_icqinfo(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) -{ - PurpleConnection *gc; - PurpleAccount *account; - PurpleBuddy *buddy; - struct buddyinfo *bi; - gchar who[16]; - PurpleNotifyUserInfo *user_info; - gchar *utf8; - gchar *buf; - const gchar *alias; - va_list ap; - struct aim_icq_info *info; - - gc = od->gc; - account = purple_connection_get_account(gc); - - va_start(ap, fr); - info = va_arg(ap, struct aim_icq_info *); - va_end(ap); - - if (!info->uin) - return 0; - - user_info = purple_notify_user_info_new(); - - g_snprintf(who, sizeof(who), "%u", info->uin); - buddy = purple_find_buddy(account, who); - if (buddy != NULL) - bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, purple_buddy_get_name(buddy))); - else - bi = NULL; - - purple_notify_user_info_add_pair(user_info, _("UIN"), who); - oscar_user_info_convert_and_add(account, od, user_info, _("Nick"), info->nick); - if ((bi != NULL) && (bi->ipaddr != 0)) { - char *tstr = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", - (bi->ipaddr & 0xff000000) >> 24, - (bi->ipaddr & 0x00ff0000) >> 16, - (bi->ipaddr & 0x0000ff00) >> 8, - (bi->ipaddr & 0x000000ff)); - purple_notify_user_info_add_pair(user_info, _("IP Address"), tstr); - g_free(tstr); - } - oscar_user_info_convert_and_add(account, od, user_info, _("First Name"), info->first); - oscar_user_info_convert_and_add(account, od, user_info, _("Last Name"), info->last); - if (info->email && info->email[0] && (utf8 = oscar_utf8_try_convert(account, od, info->email))) { - buf = g_strdup_printf("%s", utf8, utf8); - purple_notify_user_info_add_pair(user_info, _("Email Address"), buf); - g_free(buf); - g_free(utf8); - } - if (info->numaddresses && info->email2) { - int i; - for (i = 0; i < info->numaddresses; i++) { - if (info->email2[i] && info->email2[i][0] && (utf8 = oscar_utf8_try_convert(account, od, info->email2[i]))) { - buf = g_strdup_printf("%s", utf8, utf8); - purple_notify_user_info_add_pair(user_info, _("Email Address"), buf); - g_free(buf); - g_free(utf8); - } - } - } - oscar_user_info_convert_and_add(account, od, user_info, _("Mobile Phone"), info->mobile); - - if (info->gender != 0) - purple_notify_user_info_add_pair(user_info, _("Gender"), (info->gender == 1 ? _("Female") : _("Male"))); - - if ((info->birthyear > 1900) && (info->birthmonth > 0) && (info->birthday > 0)) { - /* Initialize the struct properly or strftime() will crash - * under some conditions (e.g. Debian sarge w/ LANG=en_HK). */ - time_t t = time(NULL); - struct tm *tm = localtime(&t); - - tm->tm_mday = (int)info->birthday; - tm->tm_mon = (int)info->birthmonth - 1; - tm->tm_year = (int)info->birthyear - 1900; - - /* To be 100% sure that the fields are re-normalized. - * If you're sure strftime() ALWAYS does this EVERYWHERE, - * feel free to remove it. --rlaager */ - mktime(tm); - - oscar_user_info_convert_and_add(account, od, user_info, _("Birthday"), purple_date_format_short(tm)); - } - if ((info->age > 0) && (info->age < 255)) { - char age[5]; - snprintf(age, sizeof(age), "%hhd", info->age); - purple_notify_user_info_add_pair(user_info, _("Age"), age); - } - if (info->personalwebpage && info->personalwebpage[0] && (utf8 = oscar_utf8_try_convert(account, od, info->personalwebpage))) { - buf = g_strdup_printf("%s", utf8, utf8); - purple_notify_user_info_add_pair(user_info, _("Personal Web Page"), buf); - g_free(buf); - g_free(utf8); - } - - if (buddy != NULL) - oscar_user_info_append_status(gc, user_info, buddy, /* aim_userinfo_t */ NULL, /* strip_html_tags */ FALSE); - - oscar_user_info_convert_and_add(account, od, user_info, _("Additional Information"), info->info); - purple_notify_user_info_add_section_break(user_info); - - if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { - purple_notify_user_info_add_section_header(user_info, _("Home Address")); - - oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->homeaddr); - oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->homecity); - oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->homestate); - oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->homezip); - } - if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { - purple_notify_user_info_add_section_header(user_info, _("Work Address")); - - oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->workaddr); - oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->workcity); - oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->workstate); - oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->workzip); - } - if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { - purple_notify_user_info_add_section_header(user_info, _("Work Information")); - - oscar_user_info_convert_and_add(account, od, user_info, _("Company"), info->workcompany); - oscar_user_info_convert_and_add(account, od, user_info, _("Division"), info->workdivision); - oscar_user_info_convert_and_add(account, od, user_info, _("Position"), info->workposition); - - if (info->workwebpage && info->workwebpage[0] && (utf8 = oscar_utf8_try_convert(account, od, info->workwebpage))) { - char *webpage = g_strdup_printf("%s", utf8, utf8); - purple_notify_user_info_add_pair(user_info, _("Web Page"), webpage); - g_free(webpage); - g_free(utf8); - } - } - - if (buddy != NULL) - alias = purple_buddy_get_alias(buddy); - else - alias = who; - purple_notify_userinfo(gc, who, user_info, NULL, NULL); - purple_notify_user_info_destroy(user_info); - - return 1; -} - -static int purple_icqalias(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) -{ - PurpleConnection *gc = od->gc; - PurpleAccount *account = purple_connection_get_account(gc); - gchar who[16], *utf8; - PurpleBuddy *b; - va_list ap; - struct aim_icq_info *info; - - va_start(ap, fr); - info = va_arg(ap, struct aim_icq_info *); - va_end(ap); - - if (info->uin && info->nick && info->nick[0] && (utf8 = oscar_utf8_try_convert(account, od, info->nick))) { - g_snprintf(who, sizeof(who), "%u", info->uin); - serv_got_alias(gc, who, utf8); - if ((b = purple_find_buddy(account, who))) { - purple_blist_node_set_string((PurpleBlistNode*)b, "servernick", utf8); - } - g_free(utf8); - } - - return 1; -} - static int purple_popup(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; gchar *text; va_list ap; char *msg, *url; guint16 wid, hei, delay; @@ -4466,18 +3135,18 @@ oscar_send_typing(PurpleConnection *gc, /* TODO: Move this into odc.c! */ static void purple_odc_send_im(PeerConnection *conn, const char *message, PurpleMessageFlags imflags) { GString *msg; GString *data; gchar *tmp; - int tmplen; - guint16 charset, charsubset; + gsize tmplen; + guint16 charset; GData *attribs; const char *start, *end, *last; int oscar_id = 0; msg = g_string_new(""); data = g_string_new(""); last = message; @@ -4528,18 +3197,17 @@ purple_odc_send_im(PeerConnection *conn, /* append any remaining message data */ if (last && *last) g_string_append(msg, last); g_string_append(msg, ""); /* Convert the message to a good encoding */ - purple_plugin_oscar_convert_to_best_encoding(conn->od->gc, - conn->bn, msg->str, &tmp, &tmplen, &charset, &charsubset); + tmp = oscar_encode_im(msg->str, &tmplen, &charset, NULL); g_string_free(msg, TRUE); msg = g_string_new_len(tmp, tmplen); g_free(tmp); /* Append any binary data that we may have */ if (oscar_id) { msg = g_string_append_len(msg, data->str, data->len); msg = g_string_append(msg, ""); @@ -4576,17 +3244,17 @@ oscar_send_im(PurpleConnection *gc, cons */ int ret; purple_debug_info("oscar", "Sending SMS to %s.\n", name); ret = aim_icq_sendsms(od, name, message, purple_account_get_username(account)); return (ret >= 0 ? 1 : ret); } if (imflags & PURPLE_MESSAGE_AUTO_RESP) - tmp1 = purple_str_sub_away_formatters(message, name); + tmp1 = oscar_util_format_string(message, name); else tmp1 = g_strdup(message); conn = peer_connection_find_by_type(od, name, OSCAR_CAPABILITY_DIRECTIM); if ((conn != NULL) && (conn->ready)) { /* If we're directly connected, send a direct IM */ purple_debug_info("oscar", "Sending direct IM with flags %i\n", imflags); @@ -4609,17 +3277,17 @@ oscar_send_im(PurpleConnection *gc, cons buddy = purple_find_buddy(account, name); bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, name)); if (!bi) { bi = g_new0(struct buddyinfo, 1); g_hash_table_insert(od->buddyinfo, g_strdup(purple_normalize(account, name)), bi); } - args.flags = AIM_IMFLAGS_ACK | AIM_IMFLAGS_CUSTOMFEATURES; + args.flags = 0; if (!is_sms && (!buddy || !PURPLE_BUDDY_IS_ONLINE(buddy))) args.flags |= AIM_IMFLAGS_OFFLINE; if (od->icq) { args.features = features_icq; args.featureslen = sizeof(features_icq); } else { @@ -4678,41 +3346,39 @@ oscar_send_im(PurpleConnection *gc, cons } else { /* ICQ 6 wants its HTML wrapped in these tags. Oblige it. */ tmp2 = g_strdup_printf("%s", tmp1); is_html = TRUE; } g_free(tmp1); tmp1 = tmp2; - purple_plugin_oscar_convert_to_best_encoding(gc, name, tmp1, (char **)&args.msg, &args.msglen, &args.charset, &args.charsubset); + args.msg = oscar_encode_im(tmp1, &args.msglen, &args.charset, NULL); if (is_html && (args.msglen > MAXMSGLEN)) { /* If the length was too long, try stripping the HTML and then running it back through * purple_strdup_withhtml() and the encoding process. The result may be shorter. */ g_free((char *)args.msg); tmp2 = purple_markup_strip_html(tmp1); g_free(tmp1); /* re-escape the entities */ tmp1 = g_markup_escape_text(tmp2, -1); g_free(tmp2); tmp2 = purple_strdup_withhtml(tmp1); g_free(tmp1); tmp1 = tmp2; - purple_plugin_oscar_convert_to_best_encoding(gc, name, tmp1, (char **)&args.msg, &args.msglen, &args.charset, &args.charsubset); - + args.msg = oscar_encode_im(tmp1, &args.msglen, &args.charset, NULL); purple_debug_info("oscar", "Sending %s as %s because the original was too long.\n", message, (char *)args.msg); } - purple_debug_info("oscar", "Sending IM, charset=0x%04hx, charsubset=0x%04hx, length=%d\n", - args.charset, args.charsubset, args.msglen); + purple_debug_info("oscar", "Sending IM, charset=0x%04hx, length=%" G_GSIZE_FORMAT "\n", args.charset, args.msglen); ret = aim_im_sendch1_ext(od, &args); g_free((char *)args.msg); } g_free(tmp1); if (ret >= 0) return 1; @@ -4729,66 +3395,34 @@ void oscar_get_info(PurpleConnection *gc OscarData *od = purple_connection_get_protocol_data(gc); if (od->icq && oscar_util_valid_name_icq(name)) aim_icq_getallinfo(od, name); else aim_locate_getinfoshort(od, name, 0x00000003); } -#if 0 -static void oscar_set_dir(PurpleConnection *gc, const char *first, const char *middle, const char *last, - const char *maiden, const char *city, const char *state, const char *country, int web) { - /* XXX - some of these things are wrong, but i'm lazy */ - OscarData *od = purple_connection_get_protocol_data(gc); - aim_locate_setdirinfo(od, first, middle, last, - maiden, NULL, NULL, city, state, NULL, 0, web); -} -#endif - void oscar_set_idle(PurpleConnection *gc, int time) { OscarData *od = purple_connection_get_protocol_data(gc); aim_srv_setidle(od, time); } -static -gchar *purple_prpl_oscar_convert_to_infotext(const gchar *str, gsize *ret_len, char **encoding) -{ - int charset = 0; - char *encoded = NULL; - - charset = oscar_charset_check(str); - if (charset == AIM_CHARSET_UNICODE) { - encoded = g_convert(str, -1, "UTF-16BE", "UTF-8", NULL, ret_len, NULL); - *encoding = "unicode-2-0"; - } else if (charset == AIM_CHARSET_LATIN_1) { - encoded = g_convert(str, -1, "ISO-8859-1", "UTF-8", NULL, ret_len, NULL); - *encoding = "iso-8859-1"; - } else { - encoded = g_strdup(str); - *ret_len = strlen(str); - *encoding = "us-ascii"; - } - - return encoded; -} - void oscar_set_info(PurpleConnection *gc, const char *rawinfo) { PurpleAccount *account; PurpleStatus *status; account = purple_connection_get_account(gc); status = purple_account_get_active_status(account); oscar_set_info_and_status(account, TRUE, rawinfo, FALSE, status); } -static void -oscar_set_extendedstatus(PurpleConnection *gc) +static guint32 +oscar_get_extended_status(PurpleConnection *gc) { OscarData *od; PurpleAccount *account; PurpleStatus *status; const gchar *status_id; guint32 data = 0x00000000; od = purple_connection_get_protocol_data(gc); @@ -4822,17 +3456,23 @@ oscar_set_extendedstatus(PurpleConnectio data |= AIM_ICQ_STATE_ATWORK; else if (!strcmp(status_id, OSCAR_STATUS_ID_ATHOME)) data |= AIM_ICQ_STATE_ATHOME; else if (!strcmp(status_id, OSCAR_STATUS_ID_LUNCH)) data |= AIM_ICQ_STATE_LUNCH; else if (!strcmp(status_id, OSCAR_STATUS_ID_CUSTOM)) data |= AIM_ICQ_STATE_OUT | AIM_ICQ_STATE_AWAY; - aim_srv_setextrainfo(od, TRUE, data, FALSE, NULL, NULL); + return data; +} + +static void +oscar_set_extended_status(PurpleConnection *gc) +{ + aim_srv_setextrainfo(purple_connection_get_protocol_data(gc), TRUE, oscar_get_extended_status(gc), FALSE, NULL, NULL); } static void oscar_set_info_and_status(PurpleAccount *account, gboolean setinfo, const char *rawinfo, gboolean setstatus, PurpleStatus *status) { PurpleConnection *gc = purple_account_get_connection(account); OscarData *od = purple_connection_get_protocol_data(gc); @@ -4863,17 +3503,17 @@ oscar_set_info_and_status(PurpleAccount _("You have probably requested to set your " "profile before the login procedure completed. " "Your profile remains unset; try setting it " "again when you are fully connected.")); } else if (rawinfo != NULL) { char *htmlinfo = purple_strdup_withhtml(rawinfo); - info = purple_prpl_oscar_convert_to_infotext(htmlinfo, &infolen, &info_encoding); + info = oscar_encode_im(htmlinfo, &infolen, NULL, &info_encoding); g_free(htmlinfo); if (infolen > od->rights.maxsiglen) { gchar *errstr; errstr = g_strdup_printf(dngettext(PACKAGE, "The maximum profile length of %d byte " "has been exceeded. It has been truncated for you.", "The maximum profile length of %d bytes " @@ -4896,17 +3536,17 @@ oscar_set_info_and_status(PurpleAccount away = g_strdup(""); } else { gchar *linkified; /* We do this for icq too so that they work for old third party clients */ linkified = purple_markup_linkify(status_html); - away = purple_prpl_oscar_convert_to_infotext(linkified, &awaylen, &away_encoding); + away = oscar_encode_im(linkified, &awaylen, NULL, &away_encoding); g_free(linkified); if (awaylen > od->rights.maxawaymsglen) { gchar *errstr; errstr = g_strdup_printf(dngettext(PACKAGE, "The maximum away message length of %d byte " "has been exceeded. It has been truncated for you.", @@ -4925,52 +3565,49 @@ oscar_set_info_and_status(PurpleAccount g_free(info); g_free(away); if (setstatus) { const char *status_html; status_html = purple_status_get_attr_string(status, "message"); - if (od->icq && (status_html == NULL || status_html[0] == '\0')) - status_html = purple_status_type_get_name(status_type); if (status_html != NULL) { status_text = purple_markup_strip_html(status_html); /* If the status_text is longer than 251 characters then truncate it */ if (strlen(status_text) > MAXAVAILMSGLEN) { char *tmp = g_utf8_find_prev_char(status_text, &status_text[MAXAVAILMSGLEN - 2]); strcpy(tmp, "..."); } } itmsurl = purple_status_get_attr_string(status, "itmsurl"); - - /* TODO: Combine these two calls! */ - aim_srv_setextrainfo(od, FALSE, 0, TRUE, status_text, itmsurl); - oscar_set_extendedstatus(gc); + + aim_srv_setextrainfo(od, TRUE, oscar_get_extended_status(gc), TRUE, status_text, itmsurl); g_free(status_text); } } static void -oscar_set_status_icq(PurpleAccount *account) +oscar_set_icq_permdeny(PurpleAccount *account) { PurpleConnection *gc = purple_account_get_connection(account); - - /* Our permit/deny setting affects our invisibility */ - oscar_set_permit_deny(gc); + OscarData *od = purple_connection_get_protocol_data(gc); + gboolean invisible = purple_account_is_status_active(account, OSCAR_STATUS_ID_INVISIBLE); /* - * TODO: I guess we should probably wait and do this after we get - * confirmation from the above SSI call? Right now other people - * see our status blip to "invisible" before we appear offline. + * For ICQ the permit/deny setting controls who can see you + * online. Mimicking the official client's behavior, we use PURPLE_PRIVACY_ALLOW_USERS + * when our status is "invisible" and PURPLE_PRIVACY_DENY_USERS otherwise. + * In the former case, we are visible only to buddies on our "permanently visible" list. + * In the latter, we are invisible only to buddies on our "permanently invisible" list. */ - oscar_set_extendedstatus(gc); + aim_ssi_setpermdeny(od, invisible ? PURPLE_PRIVACY_ALLOW_USERS : PURPLE_PRIVACY_DENY_USERS); } void oscar_set_status(PurpleAccount *account, PurpleStatus *status) { PurpleConnection *pc; OscarData *od; @@ -4986,32 +3623,25 @@ oscar_set_status(PurpleAccount *account, od = purple_connection_get_protocol_data(pc); /* There's no need to do the stuff below for mood updates. */ if (purple_status_type_get_primitive(purple_status_get_type(status)) == PURPLE_STATUS_MOOD) { aim_locate_setcaps(od, purple_caps); return; } + if (od->icq) { + /* Set visibility */ + oscar_set_icq_permdeny(account); + } + /* Set the AIM-style away message for both AIM and ICQ accounts */ oscar_set_info_and_status(account, FALSE, NULL, TRUE, status); - - /* Set the ICQ status for ICQ accounts only */ - if (od->icq) - oscar_set_status_icq(account); } -#ifdef CRAZY_WARN -void -oscar_warn(PurpleConnection *gc, const char *name, gboolean anonymous) { - OscarData *od = purple_connection_get_protocol_data(gc); - aim_im_warn(od, od->conn, name, anonymous ? AIM_WARN_ANON : 0); -} -#endif - void oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { OscarData *od; PurpleAccount *account; const char *bname, *gname; od = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); @@ -5043,23 +3673,23 @@ oscar_add_buddy(PurpleConnection *gc, Pu OSCAR_STATUS_ID_AVAILABLE, NULL); purple_prpl_got_user_status(account, bname, OSCAR_STATUS_ID_MOBILE, NULL); } } else if (aim_ssi_waitingforauth(od->ssi.local, aim_ssi_itemlist_findparentname(od->ssi.local, bname), bname)) { /* Not authorized -- Re-request authorization */ - purple_auth_sendrequest(gc, bname); + oscar_auth_sendrequest(gc, bname); } } /* XXX - Should this be done from AIM accounts, as well? */ if (od->icq) - aim_icq_getalias(od, bname); + aim_icq_getalias(od, bname, FALSE, NULL); } void oscar_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group) { OscarData *od = purple_connection_get_protocol_data(gc); if (od->ssi.received_data) { const char *gname = purple_group_get_name(group); const char *bname = purple_buddy_get_name(buddy); @@ -5159,18 +3789,16 @@ static int purple_ssi_parseerr(OscarData _("Unable to Retrieve Buddy List"), _("The AIM servers were temporarily unable to send " "your buddy list. Your buddy list is not lost, and " "will probably become available in a few minutes.")); od->getblisttimer = purple_timeout_add_seconds(30, purple_ssi_rerequestdata, od); return 1; } - oscar_set_status_icq(purple_connection_get_account(gc)); - return 1; } static int purple_ssi_parserights(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { int i; va_list ap; int numtypes; guint16 *maxitems; @@ -5201,189 +3829,169 @@ static int purple_ssi_parserights(OscarD } static int purple_ssi_parselist(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc; PurpleAccount *account; PurpleGroup *g; PurpleBuddy *b; + GSList *cur, *next, *buddies; struct aim_ssi_item *curitem; guint32 tmp; PurpleStoredImage *img; va_list ap; guint16 fmtver, numitems; guint32 timestamp; + guint16 deny_entry_type = aim_ssi_getdenyentrytype(od); gc = od->gc; od = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); va_start(ap, fr); fmtver = (guint16)va_arg(ap, int); numitems = (guint16)va_arg(ap, int); timestamp = va_arg(ap, guint32); va_end(ap); /* Don't attempt to re-request our buddy list later */ - if (od->getblisttimer != 0) + if (od->getblisttimer != 0) { purple_timeout_remove(od->getblisttimer); - od->getblisttimer = 0; - - purple_debug_info("oscar", - "ssi: syncing local list and server list\n"); + od->getblisttimer = 0; + } + + purple_debug_info("oscar", "ssi: syncing local list and server list\n"); /* Clean the buddy list */ aim_ssi_cleanlist(od); - { /* If not in server list then prune from local list */ - GSList *cur, *next; - GSList *buddies = purple_find_buddies(account, NULL); - - /* Buddies */ - cur = NULL; - - while(buddies) { - PurpleGroup *g; - const char *gname; - const char *bname; - - b = buddies->data; - g = purple_buddy_get_group(b); - gname = purple_group_get_name(g); - bname = purple_buddy_get_name(b); - - if (aim_ssi_itemlist_exists(od->ssi.local, bname)) { - /* If the buddy is an ICQ user then load his nickname */ - const char *servernick = purple_blist_node_get_string((PurpleBlistNode*)b, "servernick"); - char *alias; - const char *balias; - if (servernick) - serv_got_alias(gc, bname, servernick); - - /* Store local alias on server */ - alias = aim_ssi_getalias(od->ssi.local, gname, bname); - balias = purple_buddy_get_local_buddy_alias(b); - if (!alias && balias && *balias) - aim_ssi_aliasbuddy(od, gname, bname, balias); - g_free(alias); - } else { + /*** Begin code for pruning buddies from local list if they're not in server list ***/ + + /* Buddies */ + cur = NULL; + for (buddies = purple_find_buddies(account, NULL); + buddies; + buddies = g_slist_delete_link(buddies, buddies)) + { + PurpleGroup *g; + const char *gname; + const char *bname; + + b = buddies->data; + g = purple_buddy_get_group(b); + gname = purple_group_get_name(g); + bname = purple_buddy_get_name(b); + + if (aim_ssi_itemlist_exists(od->ssi.local, bname)) { + /* If the buddy is an ICQ user then load his nickname */ + const char *servernick = purple_blist_node_get_string((PurpleBlistNode*)b, "servernick"); + char *alias; + const char *balias; + if (servernick) + serv_got_alias(gc, bname, servernick); + + /* Store local alias on server */ + alias = aim_ssi_getalias(od->ssi.local, gname, bname); + balias = purple_buddy_get_local_buddy_alias(b); + if (!alias && balias && *balias) + aim_ssi_aliasbuddy(od, gname, bname, balias); + g_free(alias); + } else { + purple_debug_info("oscar", + "ssi: removing buddy %s from local list\n", bname); + /* Queue the buddy for removal from the local list */ + cur = g_slist_prepend(cur, b); + } + } + while (cur != NULL) { + purple_blist_remove_buddy(cur->data); + cur = g_slist_delete_link(cur, cur); + } + + /* Permit list (ICQ doesn't have one) */ + if (!od->icq) { + next = account->permit; + while (next != NULL) { + cur = next; + next = next->next; + if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, AIM_SSI_TYPE_PERMIT)) { purple_debug_info("oscar", - "ssi: removing buddy %s from local list\n", bname); - /* We can't actually remove now because it will screw up our looping */ - cur = g_slist_prepend(cur, b); - } - buddies = g_slist_delete_link(buddies, buddies); - } - - while (cur != NULL) { - b = cur->data; - cur = g_slist_remove(cur, b); - purple_blist_remove_buddy(b); - } - - /* Permit list */ - if (account->permit) { - next = account->permit; - while (next != NULL) { - cur = next; - next = next->next; - if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, AIM_SSI_TYPE_PERMIT)) { - purple_debug_info("oscar", - "ssi: removing permit %s from local list\n", (const char *)cur->data); - purple_privacy_permit_remove(account, cur->data, TRUE); - } + "ssi: removing permit %s from local list\n", (const char *)cur->data); + purple_privacy_permit_remove(account, cur->data, TRUE); } } - - /* Deny list */ - if (account->deny) { - next = account->deny; - while (next != NULL) { - cur = next; - next = next->next; - if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, AIM_SSI_TYPE_DENY)) { - purple_debug_info("oscar", - "ssi: removing deny %s from local list\n", (const char *)cur->data); - purple_privacy_deny_remove(account, cur->data, TRUE); - } - } + } + + /* Deny list */ + next = account->deny; + while (next != NULL) { + cur = next; + next = next->next; + if (!aim_ssi_itemlist_finditem(od->ssi.local, NULL, cur->data, deny_entry_type)) { + purple_debug_info("oscar", + "ssi: removing deny %s from local list\n", (const char *)cur->data); + purple_privacy_deny_remove(account, cur->data, TRUE); } - /* Presence settings (idle time visibility) */ - tmp = aim_ssi_getpresence(od->ssi.local); - if (tmp != 0xFFFFFFFF) { - const char *idle_reporting_pref; - gboolean report_idle; - - report_idle = purple_prefs_get_bool("/messenger/status/reportIdle"); - - if (report_idle) - aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); - else - aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); - } - - - } /* end pruning buddies from local list */ - - /* Add from server list to local list */ + } + + /* Presence settings (idle time visibility) */ + tmp = aim_ssi_getpresence(od->ssi.local); + if (tmp != 0xFFFFFFFF) { + const char *idle_reporting_pref; + gboolean report_idle; + + report_idle = purple_prefs_get_bool("/messenger/status/reportIdle"); + + if (report_idle) + aim_ssi_setpresence(od, tmp | AIM_SSI_PRESENCE_FLAG_SHOWIDLE); + else + aim_ssi_setpresence(od, tmp & ~AIM_SSI_PRESENCE_FLAG_SHOWIDLE); + } + + /*** End code for pruning buddies from local list ***/ + + /*** Begin code for adding from server list to local list ***/ + for (curitem=od->ssi.local; curitem; curitem=curitem->next) { - if ((curitem->name == NULL) || (g_utf8_validate(curitem->name, -1, NULL))) + if (curitem->name && !g_utf8_validate(curitem->name, -1, NULL)) + /* Got node with invalid UTF-8 in the name. Skip it. */ + break; + switch (curitem->type) { case AIM_SSI_TYPE_BUDDY: { /* Buddy */ if (curitem->name) { struct aim_ssi_item *groupitem; char *gname, *gname_utf8, *alias, *alias_utf8; groupitem = aim_ssi_itemlist_find(od->ssi.local, curitem->gid, 0x0000); gname = groupitem ? groupitem->name : NULL; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, od, gname); - } else - gname_utf8 = NULL; + gname_utf8 = oscar_utf8_try_convert(account, od, gname); g = purple_find_group(gname_utf8 ? gname_utf8 : _("Orphans")); if (g == NULL) { g = purple_group_new(gname_utf8 ? gname_utf8 : _("Orphans")); purple_blist_add_group(g, NULL); } alias = aim_ssi_getalias(od->ssi.local, gname, curitem->name); - if (alias != NULL) { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, od, alias); - g_free(alias); - } else - alias_utf8 = NULL; + alias_utf8 = oscar_utf8_try_convert(account, od, alias); b = purple_find_buddy_in_group(account, curitem->name, g); if (b) { /* Get server stored alias */ if (!purple_strequal(curitem->name, alias_utf8)) purple_blist_alias_buddy(b, alias_utf8); } else { b = purple_buddy_new(account, curitem->name, alias_utf8); purple_debug_info("oscar", "ssi: adding buddy %s to group %s to local list\n", curitem->name, gname); purple_blist_add_buddy(b, NULL, g, NULL); } - if (!oscar_util_name_compare(curitem->name, purple_account_get_username(account))) { - char *comment = aim_ssi_getcomment(od->ssi.local, gname, curitem->name); - if (comment != NULL) - { - purple_check_comment(od, comment); - g_free(comment); - } - } /* Mobile users should always be online */ if (curitem->name[0] == '+') { purple_prpl_got_user_status(account, purple_buddy_get_name(b), OSCAR_STATUS_ID_AVAILABLE, NULL); purple_prpl_got_user_status(account, purple_buddy_get_name(b), @@ -5395,49 +4003,41 @@ static int purple_ssi_parselist(OscarDat } } break; case AIM_SSI_TYPE_GROUP: { /* Group */ char *gname; char *gname_utf8; gname = curitem->name; - if (gname != NULL) { - if (g_utf8_validate(gname, -1, NULL)) - gname_utf8 = g_strdup(gname); - else - gname_utf8 = oscar_utf8_try_convert(account, od, gname); - } else - gname_utf8 = NULL; + gname_utf8 = oscar_utf8_try_convert(account, od, gname); if (gname_utf8 != NULL && purple_find_group(gname_utf8) == NULL) { g = purple_group_new(gname_utf8); purple_blist_add_group(g, NULL); } g_free(gname_utf8); } break; - case AIM_SSI_TYPE_PERMIT: { /* Permit buddy */ - if (curitem->name) { - /* if (!find_permdeny_by_name(gc->permit, curitem->name)) { AAA */ - GSList *list; - for (list=account->permit; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next); - if (!list) { + case AIM_SSI_TYPE_PERMIT: { /* Permit buddy (unless we're on ICQ) */ + if (!od->icq && curitem->name) { + for (cur = account->permit; (cur && oscar_util_name_compare(curitem->name, cur->data)); cur = cur->next); + if (!cur) { purple_debug_info("oscar", "ssi: adding permit buddy %s to local list\n", curitem->name); purple_privacy_permit_add(account, curitem->name, TRUE); } } } break; + case AIM_SSI_TYPE_ICQDENY: case AIM_SSI_TYPE_DENY: { /* Deny buddy */ - if (curitem->name) { - GSList *list; - for (list=account->deny; (list && oscar_util_name_compare(curitem->name, list->data)); list=list->next); - if (!list) { + if (curitem->type == deny_entry_type && curitem->name) { + for (cur = account->deny; (cur && oscar_util_name_compare(curitem->name, cur->data)); cur = cur->next); + if (!cur) { purple_debug_info("oscar", "ssi: adding deny buddy %s to local list\n", curitem->name); purple_privacy_deny_add(account, curitem->name, TRUE); } } } break; case AIM_SSI_TYPE_PDINFO: { /* Permit/deny setting */ @@ -5459,17 +4059,23 @@ static int purple_ssi_parselist(OscarDat } break; case AIM_SSI_TYPE_PRESENCEPREFS: { /* Presence setting */ /* We don't want to change Purple's setting because it applies to all accounts */ } break; } /* End of switch on curitem->type */ } /* End of for loop */ - oscar_set_status_icq(account); + /*** End code for adding from server list to local list ***/ + + if (od->icq) { + oscar_set_icq_permdeny(account); + } else { + oscar_set_aim_permdeny(gc); + } /* Activate SSI */ /* Sending the enable causes other people to be able to see you, and you to see them */ /* Make sure your privacy setting/invisibility is set how you want it before this! */ purple_debug_info("oscar", "ssi: activating server-stored buddy list\n"); aim_ssi_enable(od); @@ -5521,17 +4127,17 @@ static int purple_ssi_parseack(OscarData buf = g_strdup_printf(_("Unable to add the buddy %s because you have too many buddies in your buddy list. Please remove one and try again."), (retval->name ? retval->name : _("(no name)"))); if ((retval->name != NULL) && !purple_conv_present_error(retval->name, purple_connection_get_account(gc), buf)) purple_notify_error(gc, NULL, _("Unable to Add"), buf); g_free(buf); } case 0x000e: { /* buddy requires authorization */ if ((retval->action == SNAC_SUBTYPE_FEEDBAG_ADD) && (retval->name)) - purple_auth_sendrequest(gc, retval->name); + oscar_auth_sendrequest(gc, retval->name); } break; default: { /* La la la */ gchar *buf; purple_debug_error("oscar", "ssi: Action 0x%04hx was unsuccessful with error 0x%04hx\n", retval->action, retval->ack); buf = g_strdup_printf(_("Unable to add the buddy %s for an unknown reason."), (retval->name ? retval->name : _("(no name)"))); if ((retval->name != NULL) && !purple_conv_present_error(retval->name, purple_connection_get_account(gc), buf)) @@ -5570,25 +4176,17 @@ purple_ssi_parseaddmod(OscarData *od, Fl if ((type != 0x0000) || (name == NULL)) return 1; gname = aim_ssi_itemlist_findparentname(od->ssi.local, name); gname_utf8 = gname ? oscar_utf8_try_convert(account, od, gname) : NULL; alias = aim_ssi_getalias(od->ssi.local, gname, name); - if (alias != NULL) - { - if (g_utf8_validate(alias, -1, NULL)) - alias_utf8 = g_strdup(alias); - else - alias_utf8 = oscar_utf8_try_convert(account, od, alias); - } - else - alias_utf8 = NULL; + alias_utf8 = oscar_utf8_try_convert(account, od, alias); g_free(alias); b = purple_find_buddy(account, name); if (b) { /* * You're logged in somewhere else and you aliased one * of your buddies, so update our local buddy list with * the person's new alias. @@ -5673,53 +4271,38 @@ static int purple_ssi_authgiven(OscarDat G_CALLBACK(oscar_free_name_data)); g_free(dialog_msg); return 1; } static int purple_ssi_authrequest(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { - PurpleConnection *gc = od->gc; va_list ap; const char *bn; - const char *msg; - PurpleAccount *account = purple_connection_get_account(gc); - struct name_data *data; - PurpleBuddy *buddy; + char *msg; va_start(ap, fr); bn = va_arg(ap, const char *); - msg = va_arg(ap, const char *); + msg = va_arg(ap, char *); va_end(ap); purple_debug_info("oscar", "ssi: received authorization request from %s\n", bn); - buddy = purple_find_buddy(account, bn); - if (!msg) { purple_debug_warning("oscar", "Received auth request from %s with " "empty message\n", bn); } else if (!g_utf8_validate(msg, -1, NULL)) { purple_debug_warning("oscar", "Received auth request from %s with " "invalid UTF-8 message\n", bn); msg = NULL; } - data = g_new(struct name_data, 1); - data->gc = gc; - data->name = g_strdup(bn); - data->nick = (buddy ? g_strdup(purple_buddy_get_alias_only(buddy)) : NULL); - - purple_account_request_authorization(account, bn, NULL, - (buddy ? purple_buddy_get_alias_only(buddy) : NULL), - msg, buddy != NULL, purple_auth_grant, - purple_auth_dontgrant_msgprompt, data); - + aim_icq_getalias(od, bn, TRUE, msg); return 1; } static int purple_ssi_authreply(OscarData *od, FlapConnection *conn, FlapFrame *fr, ...) { PurpleConnection *gc = od->gc; va_list ap; char *bn, *msg; gchar *dialog_msg, *nombre; @@ -5873,79 +4456,75 @@ oscar_chat_leave(PurpleConnection *gc, i conv = purple_find_chat(gc, id); g_return_if_fail(conv != NULL); purple_debug_info("oscar", "Leaving chat room %s\n", purple_conversation_get_name(conv)); cc = find_oscar_chat(gc, purple_conv_chat_get_id(PURPLE_CONV_CHAT(conv))); + flap_connection_schedule_destroy(cc->conn, OSCAR_DISCONNECT_DONE, NULL); oscar_chat_kill(gc, cc); } int oscar_send_chat(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags) { OscarData *od = purple_connection_get_protocol_data(gc); PurpleConversation *conv = NULL; struct chat_connection *c = NULL; char *buf, *buf2, *buf3; - guint16 charset, charsubset; - char *charsetstr = NULL; - int len; + guint16 charset; + char *charsetstr; + gsize len; if (!(conv = purple_find_chat(gc, id))) return -EINVAL; if (!(c = find_oscar_chat_by_conv(gc, conv))) return -EINVAL; buf = purple_strdup_withhtml(message); if (strstr(buf, " c->maxlen) || (len > c->maxvis)) { /* If the length was too long, try stripping the HTML and then running it back through * purple_strdup_withhtml() and the encoding process. The result may be shorter. */ g_free(buf2); buf3 = purple_markup_strip_html(buf); g_free(buf); buf = purple_strdup_withhtml(buf3); g_free(buf3); - purple_plugin_oscar_convert_to_best_encoding(gc, NULL, buf, &buf2, &len, &charset, &charsubset); + buf2 = oscar_encode_im(buf, &len, &charset, &charsetstr); if ((len > c->maxlen) || (len > c->maxvis)) { - purple_debug_warning("oscar", "Could not send %s because (%i > maxlen %i) or (%i > maxvis %i)\n", + purple_debug_warning("oscar", + "Could not send %s because (%" G_GSIZE_FORMAT " > maxlen %i) or (%" G_GSIZE_FORMAT " > maxvis %i)\n", buf2, len, c->maxlen, len, c->maxvis); g_free(buf); g_free(buf2); return -E2BIG; } purple_debug_info("oscar", "Sending %s as %s because the original was too long.\n", message, buf2); } - if (charset == AIM_CHARSET_ASCII) - charsetstr = "us-ascii"; - else if (charset == AIM_CHARSET_UNICODE) - charsetstr = "unicode-2-0"; - else if (charset == AIM_CHARSET_LATIN_1) - charsetstr = "iso-8859-1"; aim_chat_send_im(od, c->conn, 0, buf2, len, charsetstr, "en"); g_free(buf2); g_free(buf); return 0; } PurpleMood* oscar_get_purple_moods(PurpleAccount *account) @@ -6048,17 +4627,17 @@ void oscar_tooltip_text(PurpleBuddy *b, if (!PURPLE_BUDDY_IS_ONLINE(b)) return; account = purple_buddy_get_account(b); gc = purple_account_get_connection(account); od = purple_connection_get_protocol_data(gc); userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b)); - oscar_user_info_append_status(gc, user_info, b, userinfo, /* strip_html_tags */ TRUE); + oscar_user_info_append_status(gc, user_info, b, userinfo, /* use_html_status */ FALSE); if (full) oscar_user_info_append_extra_info(gc, user_info, b, userinfo); } char *oscar_status_text(PurpleBuddy *b) { PurpleConnection *gc; @@ -6086,98 +4665,67 @@ char *oscar_status_text(PurpleBuddy *b) else ret = g_strdup(_("Offline")); } else { message = purple_status_get_attr_string(status, "message"); if (message != NULL) { - gchar *tmp1, *tmp2; - tmp1 = purple_markup_strip_html(message); - purple_util_chrreplace(tmp1, '\n', ' '); - tmp2 = g_markup_escape_text(tmp1, -1); - ret = purple_str_sub_away_formatters(tmp2, purple_account_get_username(account)); - g_free(tmp1); - g_free(tmp2); + gchar *tmp = oscar_util_format_string(message, purple_account_get_username(account)); + ret = purple_markup_escape_text(tmp, -1); + g_free(tmp); } else if (purple_status_is_available(status)) { /* Don't show "Available" as status message in case buddy doesn't have a status message */ } else { ret = g_strdup(purple_status_get_name(status)); } } return ret; } -void oscar_set_permit_deny(PurpleConnection *gc) { +void oscar_set_aim_permdeny(PurpleConnection *gc) { PurpleAccount *account = purple_connection_get_account(gc); OscarData *od = purple_connection_get_protocol_data(gc); - PurplePrivacyType perm_deny; /* - * For ICQ the permit/deny setting controls who you can see you - * online when you set your status to "invisible." If we're ICQ - * and we're invisible then we need to use one of - * PURPLE_PRIVACY_ALLOW_USERS or PURPLE_PRIVACY_ALLOW_BUDDYLIST or - * PURPLE_PRIVACY_DENY_USERS if we actually want to be invisible - * to anyone. - * - * These three permit/deny settings correspond to: - * 1. Invisible to everyone except the people on my "permit" list - * 2. Invisible to everyone except the people on my buddy list - * 3. Invisible only to the people on my "deny" list - * - * It would be nice to allow cases 2 and 3, but our UI doesn't have - * a nice way to do it. For now we just force case 1. + * Conveniently there is a one-to-one mapping between the + * values of libpurple's PurplePrivacyType and the values used + * by the oscar protocol. */ - if (od->icq && purple_account_is_status_active(account, OSCAR_STATUS_ID_INVISIBLE)) - perm_deny = PURPLE_PRIVACY_ALLOW_USERS; - else - perm_deny = account->perm_deny; - - if (od->ssi.received_data) - /* - * Conveniently there is a one-to-one mapping between the - * values of libpurple's PurplePrivacyType and the values used - * by the oscar protocol. - */ - aim_ssi_setpermdeny(od, perm_deny, 0xffffffff); + aim_ssi_setpermdeny(od, account->perm_deny); } void oscar_add_permit(PurpleConnection *gc, const char *who) { OscarData *od = purple_connection_get_protocol_data(gc); purple_debug_info("oscar", "ssi: About to add a permit\n"); - if (od->ssi.received_data) - aim_ssi_addpermit(od, who); + aim_ssi_add_to_private_list(od, who, AIM_SSI_TYPE_PERMIT); } void oscar_add_deny(PurpleConnection *gc, const char *who) { OscarData *od = purple_connection_get_protocol_data(gc); purple_debug_info("oscar", "ssi: About to add a deny\n"); - if (od->ssi.received_data) - aim_ssi_adddeny(od, who); + aim_ssi_add_to_private_list(od, who, aim_ssi_getdenyentrytype(od)); } void oscar_rem_permit(PurpleConnection *gc, const char *who) { OscarData *od = purple_connection_get_protocol_data(gc); purple_debug_info("oscar", "ssi: About to delete a permit\n"); - if (od->ssi.received_data) - aim_ssi_delpermit(od, who); + aim_ssi_del_from_private_list(od, who, AIM_SSI_TYPE_PERMIT); } void oscar_rem_deny(PurpleConnection *gc, const char *who) { OscarData *od = purple_connection_get_protocol_data(gc); purple_debug_info("oscar", "ssi: About to delete a deny\n"); - if (od->ssi.received_data) - aim_ssi_deldeny(od, who); + aim_ssi_del_from_private_list(od, who, aim_ssi_getdenyentrytype(od)); } GList * oscar_status_types(PurpleAccount *account) { gboolean is_icq; GList *status_types = NULL; PurpleStatusType *type; @@ -6303,17 +4851,16 @@ oscar_status_types(PurpleAccount *accoun } static void oscar_ssi_editcomment(struct name_data *data, const char *text) { PurpleConnection *gc; PurpleAccount *account; OscarData *od; PurpleBuddy *b; PurpleGroup *g; - const char *username; gc = data->gc; od = purple_connection_get_protocol_data(gc); account = purple_connection_get_account(gc); b = purple_find_buddy(account, data->name); if (b == NULL) { oscar_free_name_data(data); @@ -6322,21 +4869,16 @@ static void oscar_ssi_editcomment(struct g = purple_buddy_get_group(b); if (g == NULL) { oscar_free_name_data(data); return; } aim_ssi_editcomment(od, purple_group_get_name(g), data->name, text); - - username = purple_account_get_username(account); - if (!oscar_util_name_compare(data->name, username)) - purple_check_comment(od, text); - oscar_free_name_data(data); } static void oscar_buddycb_edit_comment(PurpleBlistNode *node, gpointer ignore) { PurpleBuddy *buddy; PurpleConnection *gc; OscarData *od; @@ -6459,17 +5001,17 @@ oscar_close_directim(gpointer object, gp if (conn != NULL) { if (!conn->ready) aim_im_sendch2_cancel(conn); peer_connection_destroy(conn, OSCAR_DISCONNECT_LOCAL_CLOSED, NULL); /* OSCAR_DISCONNECT_LOCAL_CLOSED doesn't write anything to the convo - * window. Let the user know that we canceled the Direct IM. */ + * window. Let the user know that we cancelled the Direct IM. */ conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, name); purple_conversation_write(conv, NULL, _("You closed the connection."), PURPLE_MESSAGE_SYSTEM, time(NULL)); } } static void oscar_get_icqxstatusmsg(PurpleBlistNode *node, gpointer ignore) { @@ -6499,17 +5041,16 @@ oscar_get_aim_info_cb(PurpleBlistNode *n gc = purple_account_get_connection(purple_buddy_get_account(buddy)); aim_locate_getinfoshort(purple_connection_get_protocol_data(gc), purple_buddy_get_name(buddy), 0x00000003); } static GList * oscar_buddy_menu(PurpleBuddy *buddy) { - PurpleConnection *gc; OscarData *od; GList *menu; PurpleMenuAction *act; aim_userinfo_t *userinfo; PurpleAccount *account; const char *bname = purple_buddy_get_name(buddy); @@ -6537,16 +5078,17 @@ oscar_buddy_menu(PurpleBuddy *buddy) { } if (od->icq) { act = purple_menu_action_new(_("Get X-Status Msg"), PURPLE_CALLBACK(oscar_get_icqxstatusmsg), NULL, NULL); menu = g_list_prepend(menu, act); + menu = g_list_prepend(menu, create_visibility_menu_item(od, bname)); } if (userinfo && oscar_util_name_compare(purple_account_get_username(account), bname) && PURPLE_BUDDY_IS_ONLINE(buddy)) { PeerConnection *conn; conn = peer_connection_find_by_type(od, bname, OSCAR_CAPABILITY_DIRECTIM); @@ -6562,39 +5104,30 @@ oscar_buddy_menu(PurpleBuddy *buddy) { else { act = purple_menu_action_new(_("Direct IM"), PURPLE_CALLBACK(oscar_ask_directim), NULL, NULL); } menu = g_list_prepend(menu, act); } -#if 0 - /* TODO: This menu item should be added by the core */ - if (userinfo->capabilities & OSCAR_CAPABILITY_GETFILE) { - act = purple_menu_action_new(_("Get File"), - PURPLE_CALLBACK(oscar_ask_getfile), - NULL, NULL); - menu = g_list_prepend(menu, act); - } -#endif } if (od->ssi.received_data && purple_buddy_get_group(buddy) != NULL) { /* * We only do this if the user is in our buddy list and we're * waiting for authorization. */ char *gname; gname = aim_ssi_itemlist_findparentname(od->ssi.local, bname); if (gname && aim_ssi_waitingforauth(od->ssi.local, gname, bname)) { act = purple_menu_action_new(_("Re-request Authorization"), - PURPLE_CALLBACK(purple_auth_sendrequest_menu), + PURPLE_CALLBACK(oscar_auth_sendrequest_menu), NULL, NULL); menu = g_list_prepend(menu, act); } } menu = g_list_reverse(menu); return menu; @@ -6621,17 +5154,17 @@ oscar_icq_privacy_opts(PurpleConnection auth = purple_request_field_bool_get_value(f); f = purple_request_fields_get_field(fields, "web_aware"); web_aware = purple_request_field_bool_get_value(f); purple_account_set_bool(account, "authorization", auth); purple_account_set_bool(account, "web_aware", web_aware); - oscar_set_extendedstatus(gc); + oscar_set_extended_status(gc); aim_icq_setsecurity(od, auth, web_aware); } static void oscar_show_icq_privacy_opts(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; PurpleAccount *account = purple_connection_get_account(gc); @@ -6736,51 +5269,39 @@ static void oscar_show_change_email(Purp purple_connection_get_account(gc), NULL, NULL, gc); } static void oscar_show_awaitingauth(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; OscarData *od = purple_connection_get_protocol_data(gc); - gchar *text, *tmp; - GSList *buddies; - PurpleAccount *account; - int num=0; - - text = g_strdup(""); - account = purple_connection_get_account(gc); + PurpleAccount *account = purple_connection_get_account(gc); + GSList *buddies, *filtered_buddies, *cur; + gchar *text; buddies = purple_find_buddies(account, NULL); - while (buddies) { + filtered_buddies = NULL; + for (cur = buddies; cur != NULL; cur = cur->next) { PurpleBuddy *buddy; const gchar *bname, *gname; - buddy = buddies->data; + buddy = cur->data; bname = purple_buddy_get_name(buddy); gname = purple_group_get_name(purple_buddy_get_group(buddy)); if (aim_ssi_waitingforauth(od->ssi.local, gname, bname)) { - const gchar *alias = purple_buddy_get_alias_only(buddy); - if (alias) - tmp = g_strdup_printf("%s %s (%s)
", text, bname, alias); - else - tmp = g_strdup_printf("%s %s
", text, bname); - g_free(text); - text = tmp; - - num++; + filtered_buddies = g_slist_prepend(filtered_buddies, buddy); } - - buddies = g_slist_delete_link(buddies, buddies); } - if (!num) { - g_free(text); - text = g_strdup(_("you are not waiting for authorization")); - } + g_slist_free(buddies); + + filtered_buddies = g_slist_reverse(filtered_buddies); + text = oscar_format_buddies(filtered_buddies, _("you are not waiting for authorization")); + g_slist_free(filtered_buddies); purple_notify_formatted(gc, NULL, _("You are awaiting authorization from " "the following buddies"), _("You can re-request " "authorization from these buddies by " "right-clicking on them and selecting " "\"Re-request Authorization.\""), text, NULL, NULL); g_free(text); } @@ -6985,16 +5506,22 @@ oscar_actions(PurplePlugin *plugin, gpoi menu = g_list_prepend(menu, NULL); if (od->icq) { /* ICQ actions */ act = purple_plugin_action_new(_("Set Privacy Options..."), oscar_show_icq_privacy_opts); menu = g_list_prepend(menu, act); + + act = purple_plugin_action_new(_("Show Visible List"), oscar_show_visible_list); + menu = g_list_prepend(menu, act); + + act = purple_plugin_action_new(_("Show Invisible List"), oscar_show_invisible_list); + menu = g_list_prepend(menu, act); } else { /* AIM actions */ act = purple_plugin_action_new(_("Confirm Account"), oscar_confirm_account); menu = g_list_prepend(menu, act); @@ -7014,22 +5541,16 @@ oscar_actions(PurplePlugin *plugin, gpoi menu = g_list_prepend(menu, act); menu = g_list_prepend(menu, NULL); act = purple_plugin_action_new(_("Search for Buddy by Email Address..."), oscar_show_find_email); menu = g_list_prepend(menu, act); -#if 0 - act = purple_plugin_action_new(_("Search for Buddy by Information"), - show_find_info); - menu = g_list_prepend(menu, act); -#endif - menu = g_list_reverse(menu); return menu; } void oscar_change_passwd(PurpleConnection *gc, const char *old, const char *new) { OscarData *od = purple_connection_get_protocol_data(gc); @@ -7193,30 +5714,49 @@ static gboolean oscar_uri_handler(const char *gname = g_hash_table_lookup(params, "groupname"); purple_blist_request_add_buddy(acct, bname, gname, NULL); return TRUE; } return FALSE; } -void oscar_init(PurplePlugin *plugin) +void oscar_init(PurplePlugin *plugin, gboolean is_icq) { PurplePluginProtocolInfo *prpl_info = PURPLE_PLUGIN_PROTOCOL_INFO(plugin); PurpleAccountOption *option; static gboolean init = FALSE; - - option = purple_account_option_string_new(_("Server"), "server", OSCAR_DEFAULT_LOGIN_SERVER); + static const gchar *encryption_keys[] = { + N_("Use encryption if available"), + N_("Require encryption"), + N_("Don't use encryption"), + NULL + }; + static const gchar *encryption_values[] = { + OSCAR_OPPORTUNISTIC_ENCRYPTION, + OSCAR_REQUIRE_ENCRYPTION, + OSCAR_NO_ENCRYPTION, + NULL + }; + GList *encryption_options = NULL; + int i; + + option = purple_account_option_string_new(_("Server"), "server", get_login_server(is_icq, TRUE)); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); option = purple_account_option_int_new(_("Port"), "port", OSCAR_DEFAULT_LOGIN_PORT); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); - option = purple_account_option_bool_new(_("Use SSL"), "use_ssl", - OSCAR_DEFAULT_USE_SSL); + for (i = 0; encryption_keys[i]; i++) { + PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); + kvp->key = g_strdup(_(encryption_keys[i])); + kvp->value = g_strdup(encryption_values[i]); + encryption_options = g_list_append(encryption_options, kvp); + } + option = purple_account_option_list_new(_("Connection security"), "encryption", encryption_options); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); option = purple_account_option_bool_new(_("Use clientLogin"), "use_clientlogin", OSCAR_DEFAULT_USE_CLIENTLOGIN); prpl_info->protocol_options = g_list_append(prpl_info->protocol_options, option); option = purple_account_option_bool_new( _("Always use AIM/ICQ proxy server for\nfile transfers and direct IM (slower,\nbut does not reveal your IP address)"), "always_use_rv_proxy", diff --git a/purple/libpurple/protocols/oscar/oscar.h b/purple/libpurple/protocols/oscar/oscar.h --- a/purple/libpurple/protocols/oscar/oscar.h +++ b/purple/libpurple/protocols/oscar/oscar.h @@ -98,26 +98,16 @@ extern "C" { /* * Maximum size of a Buddy Icon. */ #define MAXICONLEN 7168 #define AIM_ICONIDENT "AVT1picture.id" /* - * Current Maximum Length for Chat Room Messages - * - * This is actually defined by the protocol to be - * dynamic, but I have yet to see due cause to - * define it dynamically here. Maybe later. - * - */ -#define MAXCHATMSGLEN 512 - -/* * Found by trial and error. */ #define MAXAVAILMSGLEN 251 /** * Maximum length for the password of an ICQ account */ #define MAXICQPASSLEN 8 @@ -138,177 +128,16 @@ struct _ClientInfo guint16 minor; guint16 point; guint16 build; guint32 distrib; const char *country; /* two-letter abbrev */ const char *lang; /* two-letter abbrev */ }; -/* Needs to be checked */ -#define CLIENTINFO_AIM_3_5_1670 { \ - "AOL Instant Messenger (SM), version 3.5.1670/WIN32", \ - 0x0004, \ - 0x0003, 0x0005, \ - 0x0000, 0x0686, \ - 0x0000002a, \ - "us", "en", \ -} - -/* Needs to be checked */ -/* Latest winaim without ssi */ -#define CLIENTINFO_AIM_4_1_2010 { \ - "AOL Instant Messenger (SM), version 4.1.2010/WIN32", \ - 0x0004, \ - 0x0004, 0x0001, \ - 0x0000, 0x07da, \ - 0x0000004b, \ - "us", "en", \ -} - -/* Needs to be checked */ -#define CLIENTINFO_AIM_4_3_2188 { \ - "AOL Instant Messenger (SM), version 4.3.2188/WIN32", \ - 0x0109, \ - 0x0400, 0x0003, \ - 0x0000, 0x088c, \ - 0x00000086, \ - "us", "en", \ -} - -/* Needs to be checked */ -#define CLIENTINFO_AIM_4_8_2540 { \ - "AOL Instant Messenger (SM), version 4.8.2540/WIN32", \ - 0x0109, \ - 0x0004, 0x0008, \ - 0x0000, 0x09ec, \ - 0x000000af, \ - "us", "en", \ -} - -/* Needs to be checked */ -#define CLIENTINFO_AIM_5_0_2938 { \ - "AOL Instant Messenger, version 5.0.2938/WIN32", \ - 0x0109, \ - 0x0005, 0x0000, \ - 0x0000, 0x0b7a, \ - 0x00000000, \ - "us", "en", \ -} - -#define CLIENTINFO_AIM_5_1_3036 { \ - "AOL Instant Messenger, version 5.1.3036/WIN32", \ - 0x0109, \ - 0x0005, 0x0001, \ - 0x0000, 0x0bdc, \ - 0x000000d2, \ - "us", "en", \ -} - -#define CLIENTINFO_AIM_5_5_3415 { \ - "AOL Instant Messenger, version 5.5.3415/WIN32", \ - 0x0109, \ - 0x0005, 0x0005, \ - 0x0000, 0x0057, \ - 0x000000ef, \ - "us", "en", \ -} - -#define CLIENTINFO_AIM_5_9_3702 { \ - "AOL Instant Messenger, version 5.9.3702/WIN32", \ - 0x0109, \ - 0x0005, 0x0009, \ - 0x0000, 0x0e76, \ - 0x00000111, \ - "us", "en", \ -} - -#define CLIENTINFO_ICHAT_1_0 { \ - "Apple iChat", \ - 0x311a, \ - 0x0001, 0x0000, \ - 0x0000, 0x003c, \ - 0x000000c6, \ - "us", "en", \ -} - -/* Needs to be checked */ -#define CLIENTINFO_ICQ_4_65_3281 { \ - "ICQ Inc. - Product of ICQ (TM) 2000b.4.65.1.3281.85", \ - 0x010a, \ - 0x0004, 0x0041, \ - 0x0001, 0x0cd1, \ - 0x00000055, \ - "us", "en", \ -} - -/* Needs to be checked */ -#define CLIENTINFO_ICQ_5_34_3728 { \ - "ICQ Inc. - Product of ICQ (TM).2002a.5.34.1.3728.85", \ - 0x010a, \ - 0x0005, 0x0022, \ - 0x0001, 0x0e8f, \ - 0x00000055, \ - "us", "en", \ -} - -#define CLIENTINFO_ICQ_5_45_3777 { \ - "ICQ Inc. - Product of ICQ (TM).2003a.5.45.1.3777.85", \ - 0x010a, \ - 0x0005, 0x002d, \ - 0x0001, 0x0ec1, \ - 0x00000055, \ - "us", "en", \ -} - -#define CLIENTINFO_ICQ6_6_0_6059 { \ - "ICQ Client", \ - 0x010a, \ - 0x0006, 0x0000, \ - 0x0000, 0x17ab, \ - 0x00007535, \ - "us", "en", \ -} - -#define CLIENTINFO_ICQBASIC_14_3_1068 { \ - "ICQBasic", \ - 0x010a, \ - 0x0014, 0x0003, \ - 0x0000, 0x042c, \ - 0x0000043d, \ - "us", "en", \ -} - -#define CLIENTINFO_ICQBASIC_14_34_3000 { \ - "ICQBasic", \ - 0x010a, \ - 0x0014, 0x0034, \ - 0x0000, 0x0bb8, \ - 0x0000043d, \ - "us", "en", \ -} - -#define CLIENTINFO_ICQBASIC_14_34_3096 { \ - "ICQBasic", \ - 0x010a, \ - 0x0014, 0x0034, \ - 0x0000, 0x0c18, \ - 0x0000043d, \ - "us", "en", \ -} - -#define CLIENTINFO_NETSCAPE_7_0_1 { \ - "Netscape 2000 an approved user of AOL Instant Messenger (SM)", \ - 0x1d0d, \ - 0x0007, 0x0000, \ - 0x0001, 0x0000, \ - 0x00000058, \ - "us", "en", \ -} - /* * We need to use the major-minor-micro versions from the official * AIM and ICQ programs here or AOL won't let us use certain features. * * 0x00000611 is the distid given to us by AOL for use as the default * libpurple distid. */ #define CLIENTINFO_PURPLE_AIM { \ @@ -324,19 +153,16 @@ struct _ClientInfo NULL, \ 0x010a, \ 0x0014, 0x0034, \ 0x0000, 0x0c18, \ 0x00000611, \ "us", "en", \ } -#define CLIENTINFO_AIM_KNOWNGOOD CLIENTINFO_AIM_5_1_3036 -#define CLIENTINFO_ICQ_KNOWNGOOD CLIENTINFO_ICQBASIC_14_34_3096 - typedef enum { OSCAR_DISCONNECT_DONE, /* not considered an error */ OSCAR_DISCONNECT_LOCAL_CLOSED, /* peer connections only, not considered an error */ OSCAR_DISCONNECT_REMOTE_CLOSED, OSCAR_DISCONNECT_REMOTE_REFUSED, /* peer connections only */ OSCAR_DISCONNECT_LOST_CONNECTION, OSCAR_DISCONNECT_INVALID_DATA, @@ -371,17 +197,34 @@ typedef enum #define OSCAR_CAPABILITY_ICHATAV 0x0000000001000000LL #define OSCAR_CAPABILITY_LIVEVIDEO 0x0000000002000000LL #define OSCAR_CAPABILITY_CAMERA 0x0000000004000000LL #define OSCAR_CAPABILITY_ICHAT_SCREENSHARE 0x0000000008000000LL #define OSCAR_CAPABILITY_TYPING 0x0000000010000000LL #define OSCAR_CAPABILITY_NEWCAPS 0x0000000020000000LL #define OSCAR_CAPABILITY_XTRAZ 0x0000000040000000LL #define OSCAR_CAPABILITY_GENERICUNKNOWN 0x0000000080000000LL -#define OSCAR_CAPABILITY_LAST 0x0000000100000000LL +#define OSCAR_CAPABILITY_HTML_MSGS 0x0000000100000000LL +#define OSCAR_CAPABILITY_LAST 0x0000000200000000LL + +#define OSCAR_STATUS_ID_INVISIBLE "invisible" +#define OSCAR_STATUS_ID_OFFLINE "offline" +#define OSCAR_STATUS_ID_AVAILABLE "available" +#define OSCAR_STATUS_ID_AWAY "away" +#define OSCAR_STATUS_ID_DND "dnd" +#define OSCAR_STATUS_ID_NA "na" +#define OSCAR_STATUS_ID_OCCUPIED "occupied" +#define OSCAR_STATUS_ID_FREE4CHAT "free4chat" +#define OSCAR_STATUS_ID_CUSTOM "custom" +#define OSCAR_STATUS_ID_MOBILE "mobile" +#define OSCAR_STATUS_ID_EVIL "evil" +#define OSCAR_STATUS_ID_DEPRESSION "depression" +#define OSCAR_STATUS_ID_ATHOME "athome" +#define OSCAR_STATUS_ID_ATWORK "atwork" +#define OSCAR_STATUS_ID_LUNCH "lunch" /* * Byte Stream type. Sort of. * * Use of this type serves a couple purposes: * - Buffer/buflen pairs are passed all around everywhere. This turns * that into one value, as well as abstracting it slightly. * - Through the abstraction, it is possible to enable bounds checking @@ -390,18 +233,18 @@ typedef enum * - I like having variables named "bs". * * Don't touch the insides of this struct. Or I'll have to kill you. * */ struct _ByteStream { guint8 *data; - guint32 len; - guint32 offset; + size_t len; + size_t offset; }; struct _QueuedSnac { guint16 family; guint16 subtype; FlapFrame *frame; }; @@ -521,17 +364,17 @@ struct _OscarData /* * TODO: Data specific to a certain family should go into a * hashtable and the core parts of libfaim shouldn't * need to know about them. */ IcbmCookie *msgcookies; - struct aim_icq_info *icq_info; + GSList *icq_info; /** Only used when connecting with the old-style BUCP login. */ struct aim_authresp_info *authinfo; struct aim_emailinfo *emailinfo; struct { struct aim_userinfo_s *userinfo; } locate; @@ -575,20 +418,18 @@ struct _OscarData #define AIM_ICQ_STATE_DEPRESSION 0x00004000 #define AIM_ICQ_STATE_ATHOME 0x00005000 #define AIM_ICQ_STATE_ATWORK 0x00006000 #define AIM_ICQ_STATE_LUNCH 0x00002001 #define AIM_ICQ_STATE_EVIL 0x00003000 #define AIM_ICQ_STATE_WEBAWARE 0x00010000 #define AIM_ICQ_STATE_HIDEIP 0x00020000 #define AIM_ICQ_STATE_BIRTHDAY 0x00080000 -#define AIM_ICQ_STATE_DIRECTDISABLED 0x00100000 #define AIM_ICQ_STATE_ICQHOMEPAGE 0x00200000 #define AIM_ICQ_STATE_DIRECTREQUIREAUTH 0x10000000 -#define AIM_ICQ_STATE_DIRECTCONTACTLIST 0x20000000 /** * Only used when connecting with the old-style BUCP login. */ struct aim_clientrelease { char *name; guint32 build; @@ -664,87 +505,49 @@ FlapConnection *flap_connection_getbytyp FlapConnection *flap_connection_getbytype_all(OscarData *, int type); void flap_connection_recv_cb(gpointer data, gint source, PurpleInputCondition cond); void flap_connection_recv_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond); void flap_connection_send(FlapConnection *conn, FlapFrame *frame); void flap_connection_send_version(OscarData *od, FlapConnection *conn); void flap_connection_send_version_with_cookie(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy); void flap_connection_send_version_with_cookie_and_clientinfo(OscarData *od, FlapConnection *conn, guint16 length, const guint8 *chipsahoy, ClientInfo *ci, gboolean allow_multiple_login); -void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data); -void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, guint16 flags, aim_snacid_t snacid, ByteStream *data, gboolean high_priority); +void flap_connection_send_snac(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data); +void flap_connection_send_snac_with_priority(OscarData *od, FlapConnection *conn, guint16 family, const guint16 subtype, aim_snacid_t snacid, ByteStream *data, gboolean high_priority); void flap_connection_send_keepalive(OscarData *od, FlapConnection *conn); FlapFrame *flap_frame_new(OscarData *od, guint16 channel, int datalen); /* oscar_data.c */ typedef int (*aim_rxcallback_t)(OscarData *od, FlapConnection *conn, FlapFrame *frame, ...); OscarData *oscar_data_new(void); void oscar_data_destroy(OscarData *); void oscar_data_addhandler(OscarData *od, guint16 family, guint16 subtype, aim_rxcallback_t newhandler, guint16 flags); aim_rxcallback_t aim_callhandler(OscarData *od, guint16 family, guint16 subtype); -/* misc.c */ -#define AIM_VISIBILITYCHANGE_PERMITADD 0x05 -#define AIM_VISIBILITYCHANGE_PERMITREMOVE 0x06 -#define AIM_VISIBILITYCHANGE_DENYADD 0x07 -#define AIM_VISIBILITYCHANGE_DENYREMOVE 0x08 - -#define AIM_PRIVFLAGS_ALLOWIDLE 0x01 -#define AIM_PRIVFLAGS_ALLOWMEMBERSINCE 0x02 - -#define AIM_WARN_ANON 0x01 - - - /* 0x0001 - family_oservice.c */ /* 0x0002 */ void aim_srv_clientready(OscarData *od, FlapConnection *conn); /* 0x0004 */ void aim_srv_requestnew(OscarData *od, guint16 serviceid); /* 0x0006 */ void aim_srv_reqrates(OscarData *od, FlapConnection *conn); /* 0x0008 */ void aim_srv_rates_addparam(OscarData *od, FlapConnection *conn); -/* 0x0009 */ void aim_srv_rates_delparam(OscarData *od, FlapConnection *conn); -/* 0x000c */ void aim_srv_sendpauseack(OscarData *od, FlapConnection *conn); /* 0x000e */ void aim_srv_reqpersonalinfo(OscarData *od, FlapConnection *conn); /* 0x0011 */ void aim_srv_setidle(OscarData *od, guint32 idletime); -/* 0x0014 */ void aim_srv_setprivacyflags(OscarData *od, FlapConnection *conn, guint32); -/* 0x0016 */ void aim_srv_nop(OscarData *od, FlapConnection *conn); /* 0x0017 */ void aim_srv_setversions(OscarData *od, FlapConnection *conn); /* 0x001e */ int aim_srv_setextrainfo(OscarData *od, gboolean seticqstatus, guint32 icqstatus, gboolean setstatusmsg, const char *statusmsg, const char *itmsurl); +void aim_srv_set_dc_info(OscarData *od); void aim_bos_reqrights(OscarData *od, FlapConnection *conn); -int aim_bos_changevisibility(OscarData *od, FlapConnection *conn, int, const char *); -void aim_bos_setgroupperm(OscarData *od, FlapConnection *conn, guint32 mask); - - -#define AIM_CLIENTTYPE_UNKNOWN 0x0000 -#define AIM_CLIENTTYPE_MC 0x0001 -#define AIM_CLIENTTYPE_WINAIM 0x0002 -#define AIM_CLIENTTYPE_WINAIM41 0x0003 -#define AIM_CLIENTTYPE_AOL_TOC 0x0004 -guint16 aim_im_fingerprint(const guint8 *msghdr, int len); - -#define AIM_RATE_CODE_CHANGE 0x0001 -#define AIM_RATE_CODE_WARNING 0x0002 #define AIM_RATE_CODE_LIMIT 0x0003 -#define AIM_RATE_CODE_CLEARLIMIT 0x0004 -void aim_ads_requestads(OscarData *od, FlapConnection *conn); - - /* family_icbm.c */ -#define AIM_OFT_SUBTYPE_SEND_FILE 0x0001 #define AIM_OFT_SUBTYPE_SEND_DIR 0x0002 -#define AIM_OFT_SUBTYPE_GET_FILE 0x0011 -#define AIM_OFT_SUBTYPE_GET_LIST 0x0012 -#define AIM_TRANSFER_DENY_NOTSUPPORTED 0x0000 #define AIM_TRANSFER_DENY_DECLINE 0x0001 -#define AIM_TRANSFER_DENY_NOTACCEPTING 0x0002 #define AIM_IMPARAM_FLAG_CHANNEL_MSGS_ALLOWED 0x00000001 #define AIM_IMPARAM_FLAG_MISSED_CALLS_ENABLED 0x00000002 #define AIM_IMPARAM_FLAG_EVENTS_ALLOWED 0x00000008 #define AIM_IMPARAM_FLAG_SMS_SUPPORTED 0x00000010 #define AIM_IMPARAM_FLAG_OFFLINE_MSGS_ALLOWED 0x00000100 /** @@ -756,36 +559,16 @@ void aim_ads_requestads(OscarData *od, F * If we send an HTML message to an old client that doesn't support * HTML messages, then the oscar servers will merrily strip the HTML * for us. * * All incoming IMs are treated as HTML. */ #define AIM_IMPARAM_FLAG_USE_HTML_FOR_ICQ 0x00000400 -/* This is what the server will give you if you don't set them yourself. */ -/* This is probably out of date. */ -#define AIM_IMPARAM_DEFAULTS { \ - 0, \ - AIM_IMPARAM_FLAG_CHANNEL_MSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSED_CALLS_ENABLED, \ - 512, /* !! Note how small this is. */ \ - (99.9)*10, (99.9)*10, \ - 1000 /* !! And how large this is. */ \ -} - -/* This is what most AIM versions use. */ -/* This is probably out of date. */ -#define AIM_IMPARAM_REASONABLE { \ - 0, \ - AIM_IMPARAM_FLAG_CHANNEL_MSGS_ALLOWED | AIM_IMPARAM_FLAG_MISSED_CALLS_ENABLED, \ - 8000, \ - (99.9)*10, (99.9)*10, \ - 0 \ -} - struct aim_icbmparameters { guint16 maxchan; guint32 flags; /* AIM_IMPARAM_FLAG_ */ guint16 maxmsglen; /* message size that you will accept */ guint16 maxsenderwarn; /* this and below are *10 (999=99.9%) */ guint16 maxrecverwarn; guint32 minmsginterval; /* in milliseconds? */ @@ -822,136 +605,64 @@ struct chat_connection void oscar_chat_destroy(struct chat_connection *cc); #define AIM_IMFLAGS_AWAY 0x0001 /* mark as an autoreply */ #define AIM_IMFLAGS_ACK 0x0002 /* request a receipt notice */ #define AIM_IMFLAGS_BUDDYREQ 0x0010 /* buddy icon requested */ #define AIM_IMFLAGS_HASICON 0x0020 /* already has icon */ #define AIM_IMFLAGS_SUBENC_MACINTOSH 0x0040 /* damn that Steve Jobs! */ #define AIM_IMFLAGS_CUSTOMFEATURES 0x0080 /* features field present */ -#define AIM_IMFLAGS_EXTDATA 0x0100 -#define AIM_IMFLAGS_X 0x0200 -#define AIM_IMFLAGS_MULTIPART 0x0400 /* ->mpmsg section valid */ #define AIM_IMFLAGS_OFFLINE 0x0800 /* send to offline user */ #define AIM_IMFLAGS_TYPINGNOT 0x1000 /* typing notification */ #define AIM_CHARSET_ASCII 0x0000 /* ISO 646 */ #define AIM_CHARSET_UNICODE 0x0002 /* ISO 10646 (UTF-16/UCS-2BE) */ #define AIM_CHARSET_LATIN_1 0x0003 /* ISO 8859-1 */ /* - * Multipart message structures. - */ -typedef struct aim_mpmsg_section_s -{ - guint16 charset; - guint16 charsubset; - gchar *data; - guint16 datalen; - struct aim_mpmsg_section_s *next; -} aim_mpmsg_section_t; - -typedef struct aim_mpmsg_s -{ - unsigned int numparts; - aim_mpmsg_section_t *parts; -} aim_mpmsg_t; - -int aim_mpmsg_init(OscarData *od, aim_mpmsg_t *mpm); -int aim_mpmsg_addraw(OscarData *od, aim_mpmsg_t *mpm, guint16 charset, guint16 charsubset, const gchar *data, guint16 datalen); -int aim_mpmsg_addascii(OscarData *od, aim_mpmsg_t *mpm, const char *ascii); -int aim_mpmsg_addunicode(OscarData *od, aim_mpmsg_t *mpm, const guint16 *unicode, guint16 unicodelen); -void aim_mpmsg_free(OscarData *od, aim_mpmsg_t *mpm); - -/* * Arguments to aim_send_im_ext(). * * This is really complicated. But immensely versatile. * */ struct aim_sendimext_args { - /* These are _required_ */ const char *destbn; guint32 flags; /* often 0 */ - /* Only required if not using multipart messages */ const char *msg; - int msglen; - - /* Required if ->msg is not provided */ - aim_mpmsg_t *mpmsg; + gsize msglen; /* Only used if AIM_IMFLAGS_HASICON is set */ guint32 iconlen; time_t iconstamp; guint32 iconsum; - /* Only used if AIM_IMFLAGS_CUSTOMFEATURES is set */ guint16 featureslen; guint8 *features; - /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set and mpmsg not used */ guint16 charset; - guint16 charsubset; -}; - -/* - * Arguments to aim_send_rtfmsg(). - */ -struct aim_sendrtfmsg_args -{ - const char *destbn; - guint32 fgcolor; - guint32 bgcolor; - const char *rtfmsg; /* must be in RTF */ }; /* * This information is provided in the Incoming ICBM callback for * Channel 1 ICBM's. - * - * Note that although CUSTOMFEATURES and CUSTOMCHARSET say they - * are optional, both are always set by the current libfaim code. - * That may or may not change in the future. It is mainly for - * consistency with aim_sendimext_args. - * - * Multipart messages require some explanation. If you want to use them, - * I suggest you read all the comments in family_icbm.c. - * */ struct aim_incomingim_ch1_args { - - /* Always provided */ - aim_mpmsg_t mpmsg; guint32 icbmflags; /* some flags apply only to ->msg, not all mpmsg */ time_t timestamp; /* Only set for offline messages */ - /* Only provided if message has a human-readable section */ gchar *msg; - int msglen; /* Only provided if AIM_IMFLAGS_HASICON is set */ time_t iconstamp; guint32 iconlen; guint16 iconsum; - - /* Only provided if AIM_IMFLAGS_CUSTOMFEATURES is set */ - guint8 *features; - guint8 featureslen; - - /* Only provided if AIM_IMFLAGS_EXTDATA is set */ - guint8 extdatalen; - guint8 *extdata; - - /* Only used if AIM_IMFLAGS_CUSTOMCHARSET is set */ - guint16 charset; - guint16 charsubset; }; /* Valid values for channel 2 args->status */ #define AIM_RENDEZVOUS_PROPOSE 0x0000 #define AIM_RENDEZVOUS_CANCEL 0x0001 #define AIM_RENDEZVOUS_CONNECTED 0x0002 struct _IcbmArgsCh2 @@ -976,93 +687,70 @@ struct _IcbmArgsCh2 guint32 length; time_t timestamp; guint8 *icon; } icon; struct { struct aim_chat_roominfo roominfo; } chat; struct { - guint16 msgtype; - guint32 fgcolor; - guint32 bgcolor; - const char *rtfmsg; + guint8 msgtype; + const char *msg; } rtfmsg; struct { guint16 subtype; guint16 totfiles; guint32 totsize; char *filename; } sendfile; } info; void *destructor; /* used internally only */ }; -/* Valid values for channel 4 args->type */ -#define AIM_ICQMSG_AUTHREQUEST 0x0006 -#define AIM_ICQMSG_AUTHDENIED 0x0007 -#define AIM_ICQMSG_AUTHGRANTED 0x0008 - struct aim_incomingim_ch4_args { guint32 uin; /* Of the sender of the ICBM */ guint8 type; guint8 flags; gchar *msg; /* Reason for auth request, deny, or accept */ int msglen; }; /* SNAC sending functions */ /* 0x0002 */ int aim_im_setparams(OscarData *od, struct aim_icbmparameters *params); /* 0x0004 */ int aim_im_reqparams(OscarData *od); /* 0x0006 */ int aim_im_sendch1_ext(OscarData *od, struct aim_sendimext_args *args); /* 0x0006 */ int aim_im_sendch1(OscarData *, const char *destbn, guint16 flags, const char *msg); /* 0x0006 */ int aim_im_sendch2_chatinvite(OscarData *od, const char *bn, const char *msg, guint16 exchange, const char *roomname, guint16 instance); /* 0x0006 */ int aim_im_sendch2_icon(OscarData *od, const char *bn, const guint8 *icon, int iconlen, time_t stamp, guint16 iconsum); -/* 0x0006 */ int aim_im_sendch2_rtfmsg(OscarData *od, struct aim_sendrtfmsg_args *args); /* 0x0006 */ void aim_im_sendch2_cancel(PeerConnection *peer_conn); /* 0x0006 */ void aim_im_sendch2_connected(PeerConnection *peer_conn); /* 0x0006 */ void aim_im_sendch2_odc_requestdirect(OscarData *od, guchar *cookie, const char *bn, const guint8 *ip, guint16 port, guint16 requestnumber); /* 0x0006 */ void aim_im_sendch2_odc_requestproxy(OscarData *od, guchar *cookie, const char *bn, const guint8 *ip, guint16 pin, guint16 requestnumber); /* 0x0006 */ void aim_im_sendch2_sendfile_requestdirect(OscarData *od, guchar *cookie, const char *bn, const guint8 *ip, guint16 port, guint16 requestnumber, const gchar *filename, guint32 size, guint16 numfiles); /* 0x0006 */ void aim_im_sendch2_sendfile_requestproxy(OscarData *od, guchar *cookie, const char *bn, const guint8 *ip, guint16 pin, guint16 requestnumber, const gchar *filename, guint32 size, guint16 numfiles); -/* 0x0006 */ int aim_im_sendch2_geticqaway(OscarData *od, const char *bn, int type); -/* 0x0006 */ int aim_im_sendch4(OscarData *od, const char *bn, guint16 type, const char *message); -/* 0x0008 */ int aim_im_warn(OscarData *od, FlapConnection *conn, const char *destbn, guint32 flags); /* 0x000b */ int aim_im_denytransfer(OscarData *od, const char *bn, const guchar *cookie, guint16 code); /* 0x0010 */ int aim_im_reqofflinemsgs(OscarData *od); /* 0x0014 */ int aim_im_sendmtn(OscarData *od, guint16 type1, const char *bn, guint16 type2); /* 0x000b */ int icq_relay_xstatus (OscarData *od, const char *sn, const guchar* cookie); void aim_icbm_makecookie(guchar* cookie); -gchar *oscar_encoding_extract(const char *encoding); -gchar *oscar_encoding_to_utf8(PurpleAccount *account, const char *encoding, const char *text, int textlen); -gchar *purple_plugin_oscar_decode_im_part(PurpleAccount *account, const char *sourcebn, guint16 charset, guint16 charsubset, const gchar *data, gsize datalen); - +void aim_im_send_icq_confirmation(OscarData *od, const char *bn, const guchar *cookie); /* 0x0002 - family_locate.c */ /* * AIM User Info, Standard Form. */ -#define AIM_FLAG_UNCONFIRMED 0x0001 /* "damned transients" */ #define AIM_FLAG_ADMINISTRATOR 0x0002 #define AIM_FLAG_AOL 0x0004 -#define AIM_FLAG_OSCAR_PAY 0x0008 -#define AIM_FLAG_FREE 0x0010 #define AIM_FLAG_AWAY 0x0020 +#define AIM_FLAG_WIRELESS 0x0080 #define AIM_FLAG_ICQ 0x0040 -#define AIM_FLAG_WIRELESS 0x0080 -#define AIM_FLAG_UNKNOWN100 0x0100 -#define AIM_FLAG_IMFORWARDING 0x0200 #define AIM_FLAG_ACTIVEBUDDY 0x0400 -#define AIM_FLAG_UNKNOWN800 0x0800 -#define AIM_FLAG_ONEWAYWIRELESS 0x1000 -#define AIM_FLAG_NOKNOCKKNOCK 0x00040000 -#define AIM_FLAG_FORWARD_MOBILE 0x00080000 #define AIM_USERINFO_PRESENT_FLAGS 0x00000001 #define AIM_USERINFO_PRESENT_MEMBERSINCE 0x00000002 #define AIM_USERINFO_PRESENT_ONLINESINCE 0x00000004 #define AIM_USERINFO_PRESENT_IDLE 0x00000008 #define AIM_USERINFO_PRESENT_ICQEXTSTATUS 0x00000010 #define AIM_USERINFO_PRESENT_ICQIPADDR 0x00000020 #define AIM_USERINFO_PRESENT_ICQDATA 0x00000040 @@ -1125,150 +813,81 @@ int aim_sendmemblock(OscarData *od, Flap struct aim_invite_priv { char *bn; char *roomname; guint16 exchange; guint16 instance; }; -#define AIM_COOKIETYPE_UNKNOWN 0x00 -#define AIM_COOKIETYPE_ICBM 0x01 -#define AIM_COOKIETYPE_ADS 0x02 -#define AIM_COOKIETYPE_BOS 0x03 -#define AIM_COOKIETYPE_IM 0x04 -#define AIM_COOKIETYPE_CHAT 0x05 -#define AIM_COOKIETYPE_CHATNAV 0x06 -#define AIM_COOKIETYPE_INVITE 0x07 -/* we'll move OFT up a bit to give breathing room. not like it really - * matters. */ -#define AIM_COOKIETYPE_OFTIM 0x10 -#define AIM_COOKIETYPE_OFTGET 0x11 -#define AIM_COOKIETYPE_OFTSEND 0x12 -#define AIM_COOKIETYPE_OFTVOICE 0x13 -#define AIM_COOKIETYPE_OFTIMAGE 0x14 -#define AIM_COOKIETYPE_OFTICON 0x15 +#define AIM_COOKIETYPE_CHAT 0x01 +#define AIM_COOKIETYPE_INVITE 0x02 aim_userinfo_t *aim_locate_finduserinfo(OscarData *od, const char *bn); void aim_locate_dorequest(OscarData *od); /* 0x0002 */ int aim_locate_reqrights(OscarData *od); /* 0x0004 */ int aim_locate_setcaps(OscarData *od, guint64 caps); /* 0x0004 */ int aim_locate_setprofile(OscarData *od, const char *profile_encoding, const gchar *profile, const int profile_len, const char *awaymsg_encoding, const gchar *awaymsg, const int awaymsg_len); -/* 0x0005 */ int aim_locate_getinfo(OscarData *od, const char *, guint16); -/* 0x0009 */ int aim_locate_setdirinfo(OscarData *od, const char *first, const char *middle, const char *last, const char *maiden, const char *nickname, const char *street, const char *city, const char *state, const char *zip, int country, guint16 privacy); -/* 0x000b */ int aim_locate_000b(OscarData *od, const char *bn); -/* 0x000f */ int aim_locate_setinterests(OscarData *od, const char *interest1, const char *interest2, const char *interest3, const char *interest4, const char *interest5, guint16 privacy); /* 0x0015 */ int aim_locate_getinfoshort(OscarData *od, const char *bn, guint32 flags); guint64 aim_locate_getcaps(OscarData *od, ByteStream *bs, int len); guint64 aim_locate_getcaps_short(OscarData *od, ByteStream *bs, int len); void aim_info_free(aim_userinfo_t *); int aim_info_extract(OscarData *od, ByteStream *bs, aim_userinfo_t *); int aim_putuserinfo(ByteStream *bs, aim_userinfo_t *info); PurpleMood* icq_get_purple_moods(PurpleAccount *account); const char* icq_get_custom_icon_description(const char *mood); guint8* icq_get_custom_icon_data(const char *mood); int icq_im_xstatus_request(OscarData *od, const char *sn); /* 0x0003 - family_buddy.c */ /* 0x0002 */ void aim_buddylist_reqrights(OscarData *, FlapConnection *); -/* 0x0004 */ int aim_buddylist_set(OscarData *, FlapConnection *, const char *); -/* 0x0004 */ int aim_buddylist_addbuddy(OscarData *, FlapConnection *, const char *); -/* 0x0005 */ int aim_buddylist_removebuddy(OscarData *, FlapConnection *, const char *); - /* 0x000a - family_userlookup.c */ int aim_search_address(OscarData *, const char *); - - -/* 0x000d - family_chatnav.c */ -/* 0x000e - family_chat.c */ -/* These apply to exchanges as well. */ -#define AIM_CHATROOM_FLAG_EVILABLE 0x0001 -#define AIM_CHATROOM_FLAG_NAV_ONLY 0x0002 -#define AIM_CHATROOM_FLAG_INSTANCING_ALLOWED 0x0004 -#define AIM_CHATROOM_FLAG_OCCUPANT_PEEK_ALLOWED 0x0008 - struct aim_chat_exchangeinfo { guint16 number; guint16 flags; char *name; char *charset1; char *lang1; char *charset2; char *lang2; }; #define AIM_CHATFLAGS_NOREFLECT 0x0001 #define AIM_CHATFLAGS_AWAY 0x0002 int aim_chat_send_im(OscarData *od, FlapConnection *conn, guint16 flags, const gchar *msg, int msglen, const char *encoding, const char *language); int aim_chat_join(OscarData *od, guint16 exchange, const char *roomname, guint16 instance); -int aim_chat_attachname(FlapConnection *conn, guint16 exchange, const char *roomname, guint16 instance); -char *aim_chat_getname(FlapConnection *conn); -FlapConnection *aim_chat_getconn(OscarData *, const char *name); void aim_chatnav_reqrights(OscarData *od, FlapConnection *conn); int aim_chatnav_createroom(OscarData *od, FlapConnection *conn, const char *name, guint16 exchange); -int aim_chat_leaveroom(OscarData *od, const char *name); - - - -/* 0x000f - family_odir.c */ -struct aim_odir -{ - char *first; - char *last; - char *middle; - char *maiden; - char *email; - char *country; - char *state; - char *city; - char *bn; - char *interest; - char *nick; - char *zip; - char *region; - char *address; - struct aim_odir *next; -}; - -int aim_odir_email(OscarData *, const char *, const char *); -int aim_odir_name(OscarData *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *, const char *); -int aim_odir_interest(OscarData *, const char *, const char *); - /* 0x0010 - family_bart.c */ int aim_bart_upload(OscarData *od, const guint8 *icon, guint16 iconlen); int aim_bart_request(OscarData *od, const char *bn, guint8 iconcsumtype, const guint8 *iconstr, guint16 iconstrlen); /* 0x0013 - family_feedbag.c */ #define AIM_SSI_TYPE_BUDDY 0x0000 #define AIM_SSI_TYPE_GROUP 0x0001 #define AIM_SSI_TYPE_PERMIT 0x0002 #define AIM_SSI_TYPE_DENY 0x0003 #define AIM_SSI_TYPE_PDINFO 0x0004 #define AIM_SSI_TYPE_PRESENCEPREFS 0x0005 +#define AIM_SSI_TYPE_ICQDENY 0x000e #define AIM_SSI_TYPE_ICONINFO 0x0014 -#define AIM_SSI_ACK_SUCCESS 0x0000 -#define AIM_SSI_ACK_ITEMNOTFOUND 0x0002 -#define AIM_SSI_ACK_IDNUMINUSE 0x000a -#define AIM_SSI_ACK_ATMAX 0x000c -#define AIM_SSI_ACK_INVALIDNAME 0x000d -#define AIM_SSI_ACK_AUTHREQUIRED 0x000e - /* These flags are set in the 0x00c9 TLV of SSI type 0x0005 */ #define AIM_SSI_PRESENCE_FLAG_SHOWIDLE 0x00000400 #define AIM_SSI_PRESENCE_FLAG_NORECENTBUDDIES 0x00020000 struct aim_ssi_item { char *name; guint16 gid; @@ -1285,80 +904,51 @@ struct aim_ssi_tmp char *name; struct aim_ssi_item *item; struct aim_ssi_tmp *next; }; /* These build the actual SNACs and queue them to be sent */ /* 0x0002 */ int aim_ssi_reqrights(OscarData *od); /* 0x0004 */ int aim_ssi_reqdata(OscarData *od); -/* 0x0005 */ int aim_ssi_reqifchanged(OscarData *od, time_t localstamp, guint16 localrev); /* 0x0007 */ int aim_ssi_enable(OscarData *od); /* 0x0011 */ int aim_ssi_modbegin(OscarData *od); /* 0x0012 */ int aim_ssi_modend(OscarData *od); -/* 0x0014 */ int aim_ssi_sendauth(OscarData *od, char *bn, char *msg); /* 0x0018 */ int aim_ssi_sendauthrequest(OscarData *od, char *bn, const char *msg); /* 0x001a */ int aim_ssi_sendauthreply(OscarData *od, char *bn, guint8 reply, const char *msg); /* Client functions for retrieving SSI data */ struct aim_ssi_item *aim_ssi_itemlist_find(struct aim_ssi_item *list, guint16 gid, guint16 bid); struct aim_ssi_item *aim_ssi_itemlist_finditem(struct aim_ssi_item *list, const char *gn, const char *bn, guint16 type); struct aim_ssi_item *aim_ssi_itemlist_exists(struct aim_ssi_item *list, const char *bn); char *aim_ssi_itemlist_findparentname(struct aim_ssi_item *list, const char *bn); int aim_ssi_getpermdeny(struct aim_ssi_item *list); guint32 aim_ssi_getpresence(struct aim_ssi_item *list); char *aim_ssi_getalias(struct aim_ssi_item *list, const char *gn, const char *bn); char *aim_ssi_getcomment(struct aim_ssi_item *list, const char *gn, const char *bn); gboolean aim_ssi_waitingforauth(struct aim_ssi_item *list, const char *gn, const char *bn); /* Client functions for changing SSI data */ int aim_ssi_addbuddy(OscarData *od, const char *name, const char *group, GSList *tlvlist, const char *alias, const char *comment, const char *smsnum, gboolean needauth); -int aim_ssi_addpermit(OscarData *od, const char *name); -int aim_ssi_adddeny(OscarData *od, const char *name); int aim_ssi_delbuddy(OscarData *od, const char *name, const char *group); int aim_ssi_delgroup(OscarData *od, const char *group); -int aim_ssi_delpermit(OscarData *od, const char *name); -int aim_ssi_deldeny(OscarData *od, const char *name); int aim_ssi_movebuddy(OscarData *od, const char *oldgn, const char *newgn, const char *bn); int aim_ssi_aliasbuddy(OscarData *od, const char *gn, const char *bn, const char *alias); int aim_ssi_editcomment(OscarData *od, const char *gn, const char *bn, const char *alias); int aim_ssi_rename_group(OscarData *od, const char *oldgn, const char *newgn); int aim_ssi_cleanlist(OscarData *od); int aim_ssi_deletelist(OscarData *od); -int aim_ssi_setpermdeny(OscarData *od, guint8 permdeny, guint32 vismask); +int aim_ssi_setpermdeny(OscarData *od, guint8 permdeny); int aim_ssi_setpresence(OscarData *od, guint32 presence); int aim_ssi_seticon(OscarData *od, const guint8 *iconsum, guint8 iconsumlen); int aim_ssi_delicon(OscarData *od); +int aim_ssi_add_to_private_list(OscarData *od, const char* name, guint16 list_type); +int aim_ssi_del_from_private_list(OscarData* od, const char* name, guint16 list_type); - - -/* 0x0015 - family_icq.c */ -#define AIM_ICQ_INFO_SIMPLE 0x001 -#define AIM_ICQ_INFO_SUMMARY 0x002 -#define AIM_ICQ_INFO_EMAIL 0x004 -#define AIM_ICQ_INFO_PERSONAL 0x008 -#define AIM_ICQ_INFO_ADDITIONAL 0x010 -#define AIM_ICQ_INFO_WORK 0x020 -#define AIM_ICQ_INFO_INTERESTS 0x040 -#define AIM_ICQ_INFO_ORGS 0x080 -#define AIM_ICQ_INFO_UNKNOWN 0x100 -#define AIM_ICQ_INFO_HAVEALL 0x1ff - -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS -struct aim_icq_offlinemsg -{ - guint32 sender; - guint16 year; - guint8 month, day, hour, minute; - guint8 type; - guint8 flags; - char *msg; - int msglen; -}; -#endif /* OLDSTYLE_ICQ_OFFLINEMSGS */ +guint16 aim_ssi_getdenyentrytype(OscarData* od); struct aim_icq_info { guint16 reqid; /* simple */ guint32 uin; @@ -1405,32 +995,27 @@ struct aim_icq_info /* additional personal information (0x00e6) */ char *info; /* email (0x00eb) */ guint16 numaddresses; char **email2; - /* we keep track of these in a linked list because we're 1337 */ - struct aim_icq_info *next; - /* status note info */ guint8 icbm_cookie[8]; char *status_note_title; + + gboolean for_auth_request; + char *auth_request_reason; }; -#ifdef OLDSTYLE_ICQ_OFFLINEMSGS -int aim_icq_reqofflinemsgs(OscarData *od); -int aim_icq_ackofflinemsgs(OscarData *od); -#endif int aim_icq_setsecurity(OscarData *od, gboolean auth_required, gboolean webaware); int aim_icq_changepasswd(OscarData *od, const char *passwd); -int aim_icq_getsimpleinfo(OscarData *od, const char *uin); -int aim_icq_getalias(OscarData *od, const char *uin); +int aim_icq_getalias(OscarData *od, const char *uin, gboolean for_auth_request, char *auth_request_reason); int aim_icq_getallinfo(OscarData *od, const char *uin); int aim_icq_sendsms(OscarData *od, const char *name, const char *msg, const char *alias); /* 0x0017 - family_auth.c */ void aim_sendcookie(OscarData *, FlapConnection *, const guint16 length, const guint8 *); void aim_admin_changepasswd(OscarData *, FlapConnection *, const char *newpw, const char *curpw); void aim_admin_reqconfirm(OscarData *od, FlapConnection *conn); @@ -1560,27 +1145,23 @@ void aim_tlvlist_remove(GSList **list, c (((*((buf)+3)) << 24) & 0xff000000)) const char *oscar_get_msgerr_reason(size_t reason); int oscar_get_ui_info_int(const char *str, int default_value); const char *oscar_get_ui_info_string(const char *str, const char *default_value); gchar *oscar_get_clientstring(void); guint16 aimutil_iconsum(const guint8 *buf, int buflen); -int aimutil_tokslen(char *toSearch, int theindex, char dl); -int aimutil_itemcnt(char *toSearch, char dl); -char *aimutil_itemindex(char *toSearch, int theindex, char dl); gboolean oscar_util_valid_name(const char *bn); gboolean oscar_util_valid_name_icq(const char *bn); gboolean oscar_util_valid_name_sms(const char *bn); int oscar_util_name_compare(const char *bn1, const char *bn2); - - - +gchar *oscar_util_format_string(const char *str, const char *name); +gchar *oscar_format_buddies(GSList *buddies, const gchar *no_buddies_message); typedef struct { guint16 family; guint16 subtype; guint16 flags; guint32 id; } aim_modsnac_t; @@ -1612,58 +1193,53 @@ int search_modfirst(OscarData *od, aim_m int stats_modfirst(OscarData *od, aim_module_t *mod); int auth_modfirst(OscarData *od, aim_module_t *mod); int msg_modfirst(OscarData *od, aim_module_t *mod); int misc_modfirst(OscarData *od, aim_module_t *mod); int chatnav_modfirst(OscarData *od, aim_module_t *mod); int chat_modfirst(OscarData *od, aim_module_t *mod); int locate_modfirst(OscarData *od, aim_module_t *mod); int service_modfirst(OscarData *od, aim_module_t *mod); -int invite_modfirst(OscarData *od, aim_module_t *mod); -int translate_modfirst(OscarData *od, aim_module_t *mod); int popups_modfirst(OscarData *od, aim_module_t *mod); -int adverts_modfirst(OscarData *od, aim_module_t *mod); -int odir_modfirst(OscarData *od, aim_module_t *mod); int bart_modfirst(OscarData *od, aim_module_t *mod); int ssi_modfirst(OscarData *od, aim_module_t *mod); int icq_modfirst(OscarData *od, aim_module_t *mod); int email_modfirst(OscarData *od, aim_module_t *mod); void aim_genericreq_n(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype); void aim_genericreq_n_snacid(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype); void aim_genericreq_l(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype, guint32 *); -void aim_genericreq_s(OscarData *od, FlapConnection *conn, guint16 family, guint16 subtype, guint16 *); /* bstream.c */ -int byte_stream_new(ByteStream *bs, guint32 len); -int byte_stream_init(ByteStream *bs, guint8 *data, int len); +int byte_stream_new(ByteStream *bs, size_t len); +int byte_stream_init(ByteStream *bs, guint8 *data, size_t len); void byte_stream_destroy(ByteStream *bs); -int byte_stream_empty(ByteStream *bs); +int byte_stream_bytes_left(ByteStream *bs); int byte_stream_curpos(ByteStream *bs); -int byte_stream_setpos(ByteStream *bs, unsigned int off); +int byte_stream_setpos(ByteStream *bs, size_t off); void byte_stream_rewind(ByteStream *bs); int byte_stream_advance(ByteStream *bs, int n); guint8 byte_stream_get8(ByteStream *bs); guint16 byte_stream_get16(ByteStream *bs); guint32 byte_stream_get32(ByteStream *bs); guint8 byte_stream_getle8(ByteStream *bs); guint16 byte_stream_getle16(ByteStream *bs); guint32 byte_stream_getle32(ByteStream *bs); -int byte_stream_getrawbuf(ByteStream *bs, guint8 *buf, int len); -guint8 *byte_stream_getraw(ByteStream *bs, int len); -char *byte_stream_getstr(ByteStream *bs, int len); +int byte_stream_getrawbuf(ByteStream *bs, guint8 *buf, size_t len); +guint8 *byte_stream_getraw(ByteStream *bs, size_t len); +char *byte_stream_getstr(ByteStream *bs, size_t len); int byte_stream_put8(ByteStream *bs, guint8 v); int byte_stream_put16(ByteStream *bs, guint16 v); int byte_stream_put32(ByteStream *bs, guint32 v); int byte_stream_putle8(ByteStream *bs, guint8 v); int byte_stream_putle16(ByteStream *bs, guint16 v); int byte_stream_putle32(ByteStream *bs, guint32 v); -int byte_stream_putraw(ByteStream *bs, const guint8 *v, int len); +int byte_stream_putraw(ByteStream *bs, const guint8 *v, size_t len); int byte_stream_putstr(ByteStream *bs, const char *str); -int byte_stream_putbs(ByteStream *bs, ByteStream *srcbs, int len); +int byte_stream_putbs(ByteStream *bs, ByteStream *srcbs, size_t len); int byte_stream_putuid(ByteStream *bs, OscarData *od); int byte_stream_putcaps(ByteStream *bs, guint64 caps); /** * Inserts a BART asset block into the given byte stream. The flags * and length are set appropriately based on the value of data. */ void byte_stream_put_bart_asset(ByteStream *bs, guint16 type, ByteStream *data); @@ -1688,17 +1264,17 @@ typedef struct aim_snac_s { } aim_snac_t; /* snac.c */ void aim_initsnachash(OscarData *od); aim_snacid_t aim_newsnac(OscarData *, aim_snac_t *newsnac); aim_snacid_t aim_cachesnac(OscarData *od, const guint16 family, const guint16 type, const guint16 flags, const void *data, const int datalen); aim_snac_t *aim_remsnac(OscarData *, aim_snacid_t id); void aim_cleansnacs(OscarData *, int maxage); -int aim_putsnac(ByteStream *, guint16 family, guint16 type, guint16 flags, aim_snacid_t id); +int aim_putsnac(ByteStream *, guint16 family, guint16 type, aim_snacid_t id); struct chatsnacinfo { guint16 exchange; char name[128]; guint16 instance; }; struct rateclass { @@ -1715,20 +1291,60 @@ struct rateclass { struct timeval last; /**< The time when we last sent a SNAC of this rate class. */ }; int aim_cachecookie(OscarData *od, IcbmCookie *cookie); IcbmCookie *aim_uncachecookie(OscarData *od, guint8 *cookie, int type); IcbmCookie *aim_mkcookie(guint8 *, int, void *); IcbmCookie *aim_checkcookie(OscarData *, const unsigned char *, const int); int aim_freecookie(OscarData *od, IcbmCookie *cookie); -int aim_msgcookie_gettype(guint64 type); int aim_cookie_free(OscarData *od, IcbmCookie *cookie); int aim_chat_readroominfo(ByteStream *bs, struct aim_chat_roominfo *outinfo); void flap_connection_destroy_chat(OscarData *od, FlapConnection *conn); +/* userinfo.c - displaying user information */ + +void oscar_user_info_append_status(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo, gboolean use_html_status); +void oscar_user_info_append_extra_info(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo); +void oscar_user_info_display_error(OscarData *od, guint16 error_reason, char *buddy); +void oscar_user_info_display_icq(OscarData *od, struct aim_icq_info *info); +void oscar_user_info_display_aim(OscarData *od, aim_userinfo_t *userinfo); + +/* authorization.c - OSCAR authorization requests */ +void oscar_auth_sendrequest(PurpleConnection *gc, const char *name); +void oscar_auth_sendrequest_menu(PurpleBlistNode *node, gpointer ignored); +void oscar_auth_recvrequest(PurpleConnection *gc, gchar *name, gchar *nick, gchar *reason); + +void oscar_set_aim_permdeny(PurpleConnection *gc); + +struct buddyinfo +{ + gboolean typingnot; + guint32 ipaddr; + + unsigned long ico_me_len; + unsigned long ico_me_csum; + time_t ico_me_time; + gboolean ico_informed; + + unsigned long ico_len; + unsigned long ico_csum; + time_t ico_time; + gboolean ico_need; + gboolean ico_sent; +}; + +struct name_data +{ + PurpleConnection *gc; + gchar *name; + gchar *nick; +}; + +void oscar_free_name_data(struct name_data *data); + #ifdef __cplusplus } #endif #endif /* _OSCAR_H_ */ diff --git a/purple/libpurple/protocols/oscar/oscar_data.c b/purple/libpurple/protocols/oscar/oscar_data.c --- a/purple/libpurple/protocols/oscar/oscar_data.c +++ b/purple/libpurple/protocols/oscar/oscar_data.c @@ -32,53 +32,65 @@ struct _SnacHandler /** * Allocates a new OscarData and initializes it with default values. */ OscarData * oscar_data_new(void) { OscarData *od; + aim_module_t *cur; + GString *msg; od = g_new0(OscarData, 1); aim_initsnachash(od); od->snacid_next = 0x00000001; od->buddyinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); od->handlerlist = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free); /* * Register all the modules for this session... */ aim__registermodule(od, misc_modfirst); /* load the catch-all first */ aim__registermodule(od, service_modfirst); aim__registermodule(od, locate_modfirst); aim__registermodule(od, buddylist_modfirst); aim__registermodule(od, msg_modfirst); - /* aim__registermodule(od, adverts_modfirst); */ - /* aim__registermodule(od, invite_modfirst); */ aim__registermodule(od, admin_modfirst); aim__registermodule(od, popups_modfirst); aim__registermodule(od, bos_modfirst); aim__registermodule(od, search_modfirst); aim__registermodule(od, stats_modfirst); - /* aim__registermodule(od, translate_modfirst); */ aim__registermodule(od, chatnav_modfirst); aim__registermodule(od, chat_modfirst); - aim__registermodule(od, odir_modfirst); aim__registermodule(od, bart_modfirst); /* missing 0x11 - 0x12 */ aim__registermodule(od, ssi_modfirst); /* missing 0x14 */ aim__registermodule(od, icq_modfirst); /* missing 0x16 */ /* auth_modfirst is only needed if we're connecting with the old-style BUCP login */ aim__registermodule(od, auth_modfirst); aim__registermodule(od, email_modfirst); + msg = g_string_new("Registered modules: "); + for (cur = od->modlistv; cur; cur = cur->next) { + g_string_append_printf( + msg, + "%s (family=0x%04x, version=0x%04x, toolid=0x%04x, toolversion=0x%04x), ", + cur->name, + cur->family, + cur->version, + cur->toolid, + cur->toolversion); + } + purple_debug_misc("oscar", "%s\n", msg->str); + g_string_free(msg, TRUE); + return od; } /** * Logoff and deallocate a session. * * @param od Session to kill */ @@ -117,18 +129,16 @@ oscar_data_destroy(OscarData *od) g_free(od); } void oscar_data_addhandler(OscarData *od, guint16 family, guint16 subtype, aim_rxcallback_t newhandler, guint16 flags) { SnacHandler *snac_handler; - purple_debug_misc("oscar", "Adding handler for %04x/%04x\n", family, subtype); - snac_handler = g_new0(SnacHandler, 1); snac_handler->family = family; snac_handler->subtype = subtype; snac_handler->flags = flags; snac_handler->handler = newhandler; g_hash_table_insert(od->handlerlist, diff --git a/purple/libpurple/protocols/oscar/oscarcommon.h b/purple/libpurple/protocols/oscar/oscarcommon.h --- a/purple/libpurple/protocols/oscar/oscarcommon.h +++ b/purple/libpurple/protocols/oscar/oscarcommon.h @@ -27,32 +27,40 @@ #include "internal.h" #include "accountopt.h" #include "prpl.h" #include "version.h" #include "notify.h" #include "status.h" -#define OSCAR_DEFAULT_LOGIN_SERVER "login.messaging.aol.com" +#define AIM_DEFAULT_LOGIN_SERVER "login.oscar.aol.com" +#define AIM_ALT_LOGIN_SERVER "login.messaging.aol.com" +#define AIM_DEFAULT_SSL_LOGIN_SERVER "slogin.oscar.aol.com" +#define ICQ_DEFAULT_LOGIN_SERVER "login.icq.com" +#define ICQ_DEFAULT_SSL_LOGIN_SERVER "slogin.icq.com" + #define OSCAR_DEFAULT_LOGIN_PORT 5190 -#define OSCAR_DEFAULT_SSL_LOGIN_SERVER "slogin.oscar.aol.com" -#define OSCAR_OLD_LOGIN_SERVER "login.oscar.aol.com" + +#define OSCAR_OPPORTUNISTIC_ENCRYPTION "opportunistic_encryption" +#define OSCAR_REQUIRE_ENCRYPTION "require_encryption" +#define OSCAR_NO_ENCRYPTION "no_encryption" + #ifndef _WIN32 #define OSCAR_DEFAULT_CUSTOM_ENCODING "ISO-8859-1" #else #define OSCAR_DEFAULT_CUSTOM_ENCODING oscar_get_locale_charset() #endif #define OSCAR_DEFAULT_AUTHORIZATION TRUE #define OSCAR_DEFAULT_HIDE_IP TRUE #define OSCAR_DEFAULT_WEB_AWARE FALSE #define OSCAR_DEFAULT_ALWAYS_USE_RV_PROXY FALSE #define OSCAR_DEFAULT_ALLOW_MULTIPLE_LOGINS TRUE -#define OSCAR_DEFAULT_USE_SSL TRUE #define OSCAR_DEFAULT_USE_CLIENTLOGIN FALSE +#define OSCAR_DEFAULT_ENCRYPTION OSCAR_OPPORTUNISTIC_ENCRYPTION #ifdef _WIN32 const char *oscar_get_locale_charset(void); #endif PurpleMood* oscar_get_purple_moods(PurpleAccount *account); const char *oscar_list_icon_icq(PurpleAccount *a, PurpleBuddy *b); const char *oscar_list_icon_aim(PurpleAccount *a, PurpleBuddy *b); const char* oscar_list_emblem(PurpleBuddy *b); @@ -72,17 +80,16 @@ void oscar_set_status(PurpleAccount *acc void oscar_set_idle(PurpleConnection *gc, int time); void oscar_change_passwd(PurpleConnection *gc, const char *old, const char *new); void oscar_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); void oscar_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group); void oscar_add_permit(PurpleConnection *gc, const char *who); void oscar_add_deny(PurpleConnection *gc, const char *who); void oscar_rem_permit(PurpleConnection *gc, const char *who); void oscar_rem_deny(PurpleConnection *gc, const char *who); -void oscar_set_permit_deny(PurpleConnection *gc); void oscar_join_chat(PurpleConnection *gc, GHashTable *data); char *oscar_get_chat_name(GHashTable *data); void oscar_chat_invite(PurpleConnection *gc, int id, const char *message, const char *name); void oscar_chat_leave(PurpleConnection *gc, int id); int oscar_send_chat(PurpleConnection *gc, int id, const char *message, PurpleMessageFlags flags); void oscar_keepalive(PurpleConnection *gc); void oscar_alias_buddy(PurpleConnection *gc, const char *name, const char *alias); void oscar_move_buddy(PurpleConnection *gc, const char *name, const char *old_group, const char *new_group); @@ -92,9 +99,9 @@ const char *oscar_normalize(const Purple void oscar_set_icon(PurpleConnection *gc, PurpleStoredImage *img); void oscar_remove_group(PurpleConnection *gc, PurpleGroup *group); gboolean oscar_can_receive_file(PurpleConnection *gc, const char *who); void oscar_send_file(PurpleConnection *gc, const char *who, const char *file); PurpleXfer *oscar_new_xfer(PurpleConnection *gc, const char *who); gboolean oscar_offline_message(const PurpleBuddy *buddy); void oscar_format_username(PurpleConnection *gc, const char *nick); GList *oscar_actions(PurplePlugin *plugin, gpointer context); -void oscar_init(PurplePlugin *plugin); +void oscar_init(PurplePlugin *plugin, gboolean is_icq); diff --git a/purple/libpurple/protocols/oscar/peer.c b/purple/libpurple/protocols/oscar/peer.c --- a/purple/libpurple/protocols/oscar/peer.c +++ b/purple/libpurple/protocols/oscar/peer.c @@ -874,17 +874,19 @@ peer_connection_trynext(PeerConnection * tmp = g_strdup(_("Attempting to connect via proxy server.")); conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, conn->bn); purple_conversation_write(conv, NULL, tmp, PURPLE_MESSAGE_SYSTEM, time(NULL)); g_free(tmp); } conn->verified_connect_data = purple_proxy_connect(NULL, account, - (conn->proxyip != NULL) ? conn->proxyip : PEER_PROXY_SERVER, + (conn->proxyip != NULL) + ? conn->proxyip + : (conn->od->icq ? ICQ_PEER_PROXY_SERVER : AIM_PEER_PROXY_SERVER), PEER_PROXY_PORT, peer_proxy_connection_established_cb, conn); if (conn->verified_connect_data != NULL) { /* Connecting... */ return; } } diff --git a/purple/libpurple/protocols/oscar/peer.h b/purple/libpurple/protocols/oscar/peer.h --- a/purple/libpurple/protocols/oscar/peer.h +++ b/purple/libpurple/protocols/oscar/peer.h @@ -53,17 +53,18 @@ typedef struct _PeerConnection Pe #define PEER_TYPE_GETFILE_RECEIVELISTING 0x1209 /* "Yes, please send me your listing.txt file" */ #define PEER_TYPE_GETFILE_RECEIVEDLISTING 0x120a /* received corrupt listing.txt file? I'm just guessing about this one... */ #define PEER_TYPE_GETFILE_ACKLISTING 0x120b /* "I received the listing.txt file successfully" */ #define PEER_TYPE_GETFILE_REQUESTFILE 0x120c /* "Please send me this file" */ /* * For peer proxying */ -#define PEER_PROXY_SERVER "ars.oscar.aol.com" +#define AIM_PEER_PROXY_SERVER "ars.oscar.aol.com" +#define ICQ_PEER_PROXY_SERVER "ars.icq.com" #define PEER_PROXY_PORT 5190 /* The port we should always connect to */ #define PEER_PROXY_PACKET_VERSION 0x044a /* Thanks to Keith Lea and the Joust project for documenting these */ #define PEER_PROXY_TYPE_ERROR 0x0001 #define PEER_PROXY_TYPE_CREATE 0x0002 #define PEER_PROXY_TYPE_CREATED 0x0003 #define PEER_PROXY_TYPE_JOIN 0x0004 diff --git a/purple/libpurple/protocols/oscar/peer_proxy.c b/purple/libpurple/protocols/oscar/peer_proxy.c --- a/purple/libpurple/protocols/oscar/peer_proxy.c +++ b/purple/libpurple/protocols/oscar/peer_proxy.c @@ -27,18 +27,18 @@ static void peer_proxy_send(PeerConnection *conn, ProxyFrame *frame) { size_t length; ByteStream bs; purple_debug_info("oscar", "Outgoing peer proxy frame with " - "type=0x%04hx, unknown=0x%08x, " - "flags=0x%04hx, and payload length=%hd\n", + "type=0x%04hx, unknown=0x%08x, flags=0x%04hx, and " + "payload length=%" G_GSIZE_FORMAT "\n", frame->type, frame->unknown, frame->flags, frame->payload.len); length = 12 + frame->payload.len; byte_stream_new(&bs, length); byte_stream_put16(&bs, length - 2); byte_stream_put16(&bs, PEER_PROXY_PACKET_VERSION); byte_stream_put16(&bs, frame->type); @@ -124,18 +124,18 @@ peer_proxy_send_join_existing_conn(PeerC /** * Handle an incoming peer proxy negotiation frame. */ static void peer_proxy_recv_frame(PeerConnection *conn, ProxyFrame *frame) { purple_debug_info("oscar", "Incoming peer proxy frame with " - "type=0x%04hx, unknown=0x%08x, " - "flags=0x%04hx, and payload length=%hd\n", frame->type, + "type=0x%04hx, unknown=0x%08x, flags=0x%04hx, and " + "payload length=%" G_GSIZE_FORMAT "\n", frame->type, frame->unknown, frame->flags, frame->payload.len); if (frame->type == PEER_PROXY_TYPE_CREATED) { /* * Read in 2 byte port then 4 byte IP and tell the * remote user to connect to it by sending an ICBM. */ @@ -163,17 +163,17 @@ peer_proxy_recv_frame(PeerConnection *co { purple_input_remove(conn->watcher_incoming); conn->watcher_incoming = 0; peer_connection_finalize_connection(conn); } else if (frame->type == PEER_PROXY_TYPE_ERROR) { - if (byte_stream_empty(&frame->payload) >= 2) + if (byte_stream_bytes_left(&frame->payload) >= 2) { guint16 error; const char *msg; error = byte_stream_get16(&frame->payload); if (error == 0x000d) msg = "bad request"; else if (error == 0x0010) msg = "initial request timed out"; diff --git a/purple/libpurple/protocols/oscar/rxhandlers.c b/purple/libpurple/protocols/oscar/rxhandlers.c --- a/purple/libpurple/protocols/oscar/rxhandlers.c +++ b/purple/libpurple/protocols/oscar/rxhandlers.c @@ -64,18 +64,16 @@ int aim__registermodule(OscarData *od, i mod->shutdown(od, mod); g_free(mod); return -1; } mod->next = (aim_module_t *)od->modlistv; od->modlistv = mod; - purple_debug_misc("oscar", "registered module %s (family 0x%04x, version = 0x%04x, tool 0x%04x, tool version 0x%04x)\n", mod->name, mod->family, mod->version, mod->toolid, mod->toolversion); - return 0; } void aim__shutdownmodules(OscarData *od) { aim_module_t *cur; for (cur = (aim_module_t *)od->modlistv; cur; ) { @@ -90,199 +88,8 @@ void aim__shutdownmodules(OscarData *od) cur = tmp; } od->modlistv = NULL; return; } - -#if 0 -/* - * Bleck functions get called when there's no non-bleck functions - * around to cleanup the mess... - */ -static int bleck(OscarData *od, FlapFrame *frame, ...) -{ - guint16 family, subtype; - guint16 maxf, maxs; - - static const char *channels[6] = { - "Invalid (0)", - "FLAP Version", - "SNAC", - "Invalid (3)", - "Negotiation", - "FLAP NOP" - }; - static const int maxchannels = 5; - - /* XXX: this is ugly. and big just for debugging. */ - static const char *literals[14][25] = { - {"Invalid", - NULL - }, - {"General", - "Invalid", - "Error", - "Client Ready", - "Server Ready", - "Service Request", - "Redirect", - "Rate Information Request", - "Rate Information", - "Rate Information Ack", - NULL, - "Rate Information Change", - "Server Pause", - NULL, - "Server Resume", - "Request Personal User Information", - "Personal User Information", - "Evil Notification", - NULL, - "Migration notice", - "Message of the Day", - "Set Privacy Flags", - "Well Known URL", - "NOP" - }, - {"Location", - "Invalid", - "Error", - "Request Rights", - "Rights Information", - "Set user information", - "Request User Information", - "User Information", - "Watcher Sub Request", - "Watcher Notification" - }, - {"Buddy List Management", - "Invalid", - "Error", - "Request Rights", - "Rights Information", - "Add Buddy", - "Remove Buddy", - "Watcher List Query", - "Watcher List Response", - "Watcher SubRequest", - "Watcher Notification", - "Reject Notification", - "Oncoming Buddy", - "Offgoing Buddy" - }, - {"Messeging", - "Invalid", - "Error", - "Add ICBM Parameter", - "Remove ICBM Parameter", - "Request Parameter Information", - "Parameter Information", - "Outgoing Message", - "Incoming Message", - "Evil Request", - "Evil Reply", - "Missed Calls", - "Message Error", - "Host Ack" - }, - {"Advertisements", - "Invalid", - "Error", - "Request Ad", - "Ad Data (GIFs)" - }, - {"Invitation / Client-to-Client", - "Invalid", - "Error", - "Invite a Friend", - "Invitation Ack" - }, - {"Administrative", - "Invalid", - "Error", - "Information Request", - "Information Reply", - "Information Change Request", - "Information Chat Reply", - "Account Confirm Request", - "Account Confirm Reply", - "Account Delete Request", - "Account Delete Reply" - }, - {"Popups", - "Invalid", - "Error", - "Display Popup" - }, - {"BOS", - "Invalid", - "Error", - "Request Rights", - "Rights Response", - "Set group permission mask", - "Add permission list entries", - "Delete permission list entries", - "Add deny list entries", - "Delete deny list entries", - "Server Error" - }, - {"User Lookup", - "Invalid", - "Error", - "Search Request", - "Search Response" - }, - {"Stats", - "Invalid", - "Error", - "Set minimum report interval", - "Report Events" - }, - {"Translate", - "Invalid", - "Error", - "Translate Request", - "Translate Reply", - }, - {"Chat Navigation", - "Invalid", - "Error", - "Request rights", - "Request Exchange Information", - "Request Room Information", - "Request Occupant List", - "Search for Room", - "Outgoing Message", - "Incoming Message", - "Evil Request", - "Evil Reply", - "Chat Error", - } - }; - - maxf = sizeof(literals) / sizeof(literals[0]); - maxs = sizeof(literals[0]) / sizeof(literals[0][0]); - - if (frame->channel == 0x02) { - - family = byte_stream_get16(&frame->data); - subtype = byte_stream_get16(&frame->data); - - if ((family < maxf) && (subtype+1 < maxs) && (literals[family][subtype] != NULL)) - purple_debug_misc("oscar", "bleck: channel %s: null handler for %04x/%04x (%s)\n", channels[frame->channel], family, subtype, literals[family][subtype+1]); - else - purple_debug_misc("oscar", "bleck: channel %s: null handler for %04x/%04x (no literal)\n", channels[frame->channel], family, subtype); - } else { - - if (frame->channel <= maxchannels) - purple_debug_misc("oscar", "bleck: channel %s (0x%02x)\n", channels[frame->channel], frame->channel); - else - purple_debug_misc("oscar", "bleck: unknown channel 0x%02x\n", frame->channel); - - } - - return 1; -} -#endif diff --git a/purple/libpurple/protocols/oscar/snac.c b/purple/libpurple/protocols/oscar/snac.c --- a/purple/libpurple/protocols/oscar/snac.c +++ b/purple/libpurple/protocols/oscar/snac.c @@ -146,18 +146,18 @@ void aim_cleansnacs(OscarData *od, int m } else prev = &cur->next; } } return; } -int aim_putsnac(ByteStream *bs, guint16 family, guint16 subtype, guint16 flags, aim_snacid_t snacid) +int aim_putsnac(ByteStream *bs, guint16 family, guint16 subtype, aim_snacid_t snacid) { byte_stream_put16(bs, family); byte_stream_put16(bs, subtype); - byte_stream_put16(bs, flags); + byte_stream_put16(bs, 0x0000); byte_stream_put32(bs, snacid); return 10; } diff --git a/purple/libpurple/protocols/oscar/tlv.c b/purple/libpurple/protocols/oscar/tlv.c --- a/purple/libpurple/protocols/oscar/tlv.c +++ b/purple/libpurple/protocols/oscar/tlv.c @@ -44,37 +44,17 @@ static GSList * aim_tlv_read(GSList *list, ByteStream *bs) { guint16 type, length; aim_tlv_t *tlv; type = byte_stream_get16(bs); length = byte_stream_get16(bs); -#if 0 - /* - * This code hasn't been needed in years. It's been commented - * out since 2003, at the latest. It seems likely that it was - * just a bug in their server code that has since been fixed. - * In any case, here's the orignal comment, kept for historical - * purposes: - * - * Okay, so now AOL has decided that any TLV of - * type 0x0013 can only be two bytes, despite - * what the actual given length is. So here - * we dump any invalid TLVs of that sort. Hopefully - * there's no special cases to this special case. - * - mid (30jun2000) - */ - if ((type == 0x0013) && (length != 0x0002)) { - length = 0x0002; - return list; - } -#endif - if (length > byte_stream_empty(bs)) { + if (length > byte_stream_bytes_left(bs)) { aim_tlvlist_free(list); return NULL; } tlv = createtlv(type, length, NULL); if (tlv->length > 0) { tlv->value = byte_stream_getraw(bs, length); if (!tlv->value) { @@ -103,17 +83,17 @@ aim_tlv_read(GSList *list, ByteStream *b * * @param bs Input bstream * @return Return the TLV chain read */ GSList *aim_tlvlist_read(ByteStream *bs) { GSList *list = NULL; - while (byte_stream_empty(bs) > 0) { + while (byte_stream_bytes_left(bs) > 0) { list = aim_tlv_read(list, bs); if (list == NULL) return NULL; } return g_slist_reverse(list); } @@ -137,17 +117,17 @@ GSList *aim_tlvlist_read(ByteStream *bs) * but the chain is not at the end of the SNAC, and the chain is * preceded by the number of TLVs. So you can limit that with this. * @return Return the TLV chain read */ GSList *aim_tlvlist_readnum(ByteStream *bs, guint16 num) { GSList *list = NULL; - while ((byte_stream_empty(bs) > 0) && (num != 0)) { + while ((byte_stream_bytes_left(bs) > 0) && (num != 0)) { list = aim_tlv_read(list, bs); if (list == NULL) return NULL; num--; } return g_slist_reverse(list); } @@ -172,17 +152,17 @@ GSList *aim_tlvlist_readnum(ByteStream * * but the chain is not at the end of the SNAC, and the chain is * preceded by the length of the TLVs. So you can limit that with this. * @return Return the TLV chain read */ GSList *aim_tlvlist_readlen(ByteStream *bs, guint16 len) { GSList *list = NULL; - while ((byte_stream_empty(bs) > 0) && (len > 0)) { + while ((byte_stream_bytes_left(bs) > 0) && (len > 0)) { list = aim_tlv_read(list, bs); if (list == NULL) return NULL; len -= 2 + 2 + ((aim_tlv_t *)list->data)->length; } return g_slist_reverse(list); @@ -386,16 +366,27 @@ int aim_tlvlist_add_32(GSList **list, co * @param value Value to add. * @return The size of the value added. */ int aim_tlvlist_add_str(GSList **list, const guint16 type, const char *value) { return aim_tlvlist_add_raw(list, type, strlen(value), (guint8 *)value); } +static int +count_caps(guint64 caps) +{ + int set_bits = 0; + while (caps) { + set_bits += caps & 1; + caps >>= 1; + } + return set_bits; +} + /** * Adds a block of capability blocks to a TLV chain. The bitfield * passed in should be a bitwise %OR of any of the %AIM_CAPS constants: * * %OSCAR_CAPABILITY_BUDDYICON Supports Buddy Icons * %OSCAR_CAPABILITY_TALK Supports Voice Chat * %OSCAR_CAPABILITY_IMIMAGE Supports DirectIM/IMImage * %OSCAR_CAPABILITY_CHAT Supports Chat @@ -404,33 +395,34 @@ int aim_tlvlist_add_str(GSList **list, c * * @param list Destination chain * @param type TLV type to add * @param caps Bitfield of capability flags to send * @return The size of the value added. */ int aim_tlvlist_add_caps(GSList **list, const guint16 type, const guint64 caps, const char *mood) { - guint8 buf[256]; /* TODO: Don't use a fixed length buffer */ ByteStream bs; + guint32 bs_size; guint8 *data; if (caps == 0) return 0; /* nothing there anyway */ - byte_stream_init(&bs, buf, sizeof(buf)); + data = icq_get_custom_icon_data(mood); + bs_size = 16*(count_caps(caps) + (data != NULL ? 1 : 0)); + byte_stream_new(&bs, bs_size); byte_stream_putcaps(&bs, caps); - + /* adding of custom icon GUID */ - data = icq_get_custom_icon_data(mood); if (data != NULL) byte_stream_putraw(&bs, data, 16); - return aim_tlvlist_add_raw(list, type, byte_stream_curpos(&bs), buf); + return aim_tlvlist_add_raw(list, type, byte_stream_curpos(&bs), bs.data); } /** * Adds the given chatroom info to a TLV chain. * * @param list Destination chain. * @param type TLV type to add. * @param roomname The name of the chat. @@ -663,17 +655,17 @@ int aim_tlvlist_write(ByteStream *bs, GS { int goodbuflen; GSList *cur; aim_tlv_t *tlv; /* do an initial run to test total length */ goodbuflen = aim_tlvlist_size(*list); - if (goodbuflen > byte_stream_empty(bs)) + if (goodbuflen > byte_stream_bytes_left(bs)) return 0; /* not enough buffer */ /* do the real write-out */ for (cur = *list; cur; cur = cur->next) { tlv = cur->data; byte_stream_put16(bs, tlv->type); byte_stream_put16(bs, tlv->length); if (tlv->length > 0) diff --git a/purple/libpurple/protocols/oscar/userinfo.c b/purple/libpurple/protocols/oscar/userinfo.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/userinfo.c @@ -0,0 +1,546 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +/* + * Displaying various information about buddies. + */ + +#include "encoding.h" +#include "oscar.h" + +static gchar * +oscar_caps_to_string(guint64 caps) +{ + GString *str; + const gchar *tmp; + guint64 bit = 1; + + str = g_string_new(""); + + if (!caps) { + return NULL; + } else while (bit <= OSCAR_CAPABILITY_LAST) { + if (bit & caps) { + switch (bit) { + case OSCAR_CAPABILITY_BUDDYICON: + tmp = _("Buddy Icon"); + break; + case OSCAR_CAPABILITY_TALK: + tmp = _("Voice"); + break; + case OSCAR_CAPABILITY_DIRECTIM: + tmp = _("AIM Direct IM"); + break; + case OSCAR_CAPABILITY_CHAT: + tmp = _("Chat"); + break; + case OSCAR_CAPABILITY_GETFILE: + tmp = _("Get File"); + break; + case OSCAR_CAPABILITY_SENDFILE: + tmp = _("Send File"); + break; + case OSCAR_CAPABILITY_GAMES: + case OSCAR_CAPABILITY_GAMES2: + tmp = _("Games"); + break; + case OSCAR_CAPABILITY_XTRAZ: + case OSCAR_CAPABILITY_NEWCAPS: + tmp = _("ICQ Xtraz"); + break; + case OSCAR_CAPABILITY_ADDINS: + tmp = _("Add-Ins"); + break; + case OSCAR_CAPABILITY_SENDBUDDYLIST: + tmp = _("Send Buddy List"); + break; + case OSCAR_CAPABILITY_ICQ_DIRECT: + tmp = _("ICQ Direct Connect"); + break; + case OSCAR_CAPABILITY_APINFO: + tmp = _("AP User"); + break; + case OSCAR_CAPABILITY_ICQRTF: + tmp = _("ICQ RTF"); + break; + case OSCAR_CAPABILITY_EMPTY: + tmp = _("Nihilist"); + break; + case OSCAR_CAPABILITY_ICQSERVERRELAY: + tmp = _("ICQ Server Relay"); + break; + case OSCAR_CAPABILITY_UNICODEOLD: + tmp = _("Old ICQ UTF8"); + break; + case OSCAR_CAPABILITY_TRILLIANCRYPT: + tmp = _("Trillian Encryption"); + break; + case OSCAR_CAPABILITY_UNICODE: + tmp = _("ICQ UTF8"); + break; + case OSCAR_CAPABILITY_HIPTOP: + tmp = _("Hiptop"); + break; + case OSCAR_CAPABILITY_SECUREIM: + tmp = _("Security Enabled"); + break; + case OSCAR_CAPABILITY_VIDEO: + tmp = _("Video Chat"); + break; + /* Not actually sure about this one... WinAIM doesn't show anything */ + case OSCAR_CAPABILITY_ICHATAV: + tmp = _("iChat AV"); + break; + case OSCAR_CAPABILITY_LIVEVIDEO: + tmp = _("Live Video"); + break; + case OSCAR_CAPABILITY_CAMERA: + tmp = _("Camera"); + break; + case OSCAR_CAPABILITY_ICHAT_SCREENSHARE: + tmp = _("Screen Sharing"); + break; + default: + tmp = NULL; + break; + } + if (tmp) + g_string_append_printf(str, "%s%s", (*(str->str) == '\0' ? "" : ", "), tmp); + } + bit <<= 1; + } + + return g_string_free(str, FALSE); +} + +static void +oscar_user_info_add_pair(PurpleNotifyUserInfo *user_info, const char *name, const char *value) +{ + if (value && value[0]) { + purple_notify_user_info_add_pair(user_info, name, value); + } +} + +static void +oscar_user_info_convert_and_add(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, + const char *name, const char *value) +{ + gchar *utf8; + + if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { + purple_notify_user_info_add_pair(user_info, name, utf8); + g_free(utf8); + } +} + +static void +oscar_user_info_convert_and_add_hyperlink(PurpleAccount *account, OscarData *od, PurpleNotifyUserInfo *user_info, + const char *name, const char *value, const char *url_prefix) +{ + gchar *utf8; + + if (value && value[0] && (utf8 = oscar_utf8_try_convert(account, od, value))) { + gchar *tmp = g_strdup_printf("%s", url_prefix, utf8, utf8); + purple_notify_user_info_add_pair(user_info, name, tmp); + g_free(utf8); + g_free(tmp); + } +} + +/** + * @brief Append the status information to a user_info struct + * + * The returned information is HTML-ready, appropriately escaped, as all information in a user_info struct should be HTML. + * + * @param gc The PurpleConnection + * @param user_info A PurpleNotifyUserInfo object to which status information will be added + * @param b The PurpleBuddy whose status is desired. This or the aim_userinfo_t (or both) must be passed to oscar_user_info_append_status(). + * @param userinfo The aim_userinfo_t of the buddy whose status is desired. This or the PurpleBuddy (or both) must be passed to oscar_user_info_append_status(). + * @param use_html_status If TRUE, prefer HTML-formatted away message over plaintext available message. + */ +void +oscar_user_info_append_status(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo, gboolean use_html_status) +{ + PurpleAccount *account = purple_connection_get_account(gc); + OscarData *od; + PurplePresence *presence = NULL; + PurpleStatus *status = NULL; + gchar *message = NULL, *itmsurl = NULL, *tmp; + gboolean escaping_needed = TRUE; + + od = purple_connection_get_protocol_data(gc); + + if (b == NULL && userinfo == NULL) + return; + + if (b == NULL) + b = purple_find_buddy(purple_connection_get_account(gc), userinfo->bn); + else + userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b)); + + if (b) { + presence = purple_buddy_get_presence(b); + status = purple_presence_get_active_status(presence); + } + + /* If we have both b and userinfo we favor userinfo, because if we're + viewing someone's profile then we want the HTML away message, and + the "message" attribute of the status contains only the plaintext + message. */ + if (userinfo) { + if ((userinfo->flags & AIM_FLAG_AWAY) && use_html_status && userinfo->away_len > 0 && userinfo->away != NULL && userinfo->away_encoding != NULL) { + /* Away message */ + message = oscar_encoding_to_utf8(userinfo->away_encoding, userinfo->away, userinfo->away_len); + escaping_needed = FALSE; + } else { + /* + * Available message or non-HTML away message (because that's + * all we have right now. + */ + if ((userinfo->status != NULL) && userinfo->status[0] != '\0') { + message = oscar_encoding_to_utf8(userinfo->status_encoding, userinfo->status, userinfo->status_len); + } +#if defined (_WIN32) || defined (__APPLE__) + if (userinfo->itmsurl && (userinfo->itmsurl[0] != '\0')) { + itmsurl = oscar_encoding_to_utf8(userinfo->itmsurl_encoding, userinfo->itmsurl, userinfo->itmsurl_len); + } +#endif + } + } else { + message = g_strdup(purple_status_get_attr_string(status, "message")); + itmsurl = g_strdup(purple_status_get_attr_string(status, "itmsurl")); + } + + if (message) { + tmp = oscar_util_format_string(message, purple_account_get_username(account)); + g_free(message); + message = tmp; + if (escaping_needed) { + tmp = purple_markup_escape_text(message, -1); + g_free(message); + message = tmp; + } + } + + if (use_html_status && itmsurl) { + tmp = g_strdup_printf("%s", itmsurl, message); + g_free(message); + message = tmp; + } + + if (b) { + if (purple_presence_is_online(presence)) { + gboolean is_away = ((status && !purple_status_is_available(status)) || (userinfo && (userinfo->flags & AIM_FLAG_AWAY))); + if (oscar_util_valid_name_icq(purple_buddy_get_name(b)) || is_away || !message || !(*message)) { + /* Append the status name for online ICQ statuses, away AIM statuses, and for all buddies with no message. + * If the status name and the message are the same, only show one. */ + const char *status_name = purple_status_get_name(status); + if (status_name && message && !strcmp(status_name, message)) + status_name = NULL; + + tmp = g_strdup_printf("%s%s%s", + status_name ? status_name : "", + ((status_name && message) && *message) ? ": " : "", + (message && *message) ? message : ""); + g_free(message); + message = tmp; + } + + } else if (aim_ssi_waitingforauth(od->ssi.local, + aim_ssi_itemlist_findparentname(od->ssi.local, purple_buddy_get_name(b)), + purple_buddy_get_name(b))) + { + /* Note if an offline buddy is not authorized */ + tmp = g_strdup_printf("%s%s%s", + _("Not Authorized"), + (message && *message) ? ": " : "", + (message && *message) ? message : ""); + g_free(message); + message = tmp; + } else { + g_free(message); + message = g_strdup(_("Offline")); + } + } + + if (presence) { + const char *mood; + const char *comment; + char *description; + status = purple_presence_get_status(presence, "mood"); + mood = icq_get_custom_icon_description(purple_status_get_attr_string(status, PURPLE_MOOD_NAME)); + if (mood) { + comment = purple_status_get_attr_string(status, PURPLE_MOOD_COMMENT); + if (comment) { + char *escaped_comment = purple_markup_escape_text(comment, -1); + description = g_strdup_printf("%s (%s)", _(mood), escaped_comment); + g_free(escaped_comment); + } else { + description = g_strdup(_(mood)); + } + purple_notify_user_info_add_pair(user_info, _("Mood"), description); + g_free(description); + } + } + + purple_notify_user_info_add_pair(user_info, _("Status"), message); + g_free(message); +} + +void +oscar_user_info_append_extra_info(PurpleConnection *gc, PurpleNotifyUserInfo *user_info, PurpleBuddy *b, aim_userinfo_t *userinfo) +{ + OscarData *od; + PurpleAccount *account; + PurplePresence *presence = NULL; + PurpleStatus *status = NULL; + PurpleGroup *g = NULL; + struct buddyinfo *bi = NULL; + char *tmp; + const char *bname = NULL, *gname = NULL; + + od = purple_connection_get_protocol_data(gc); + account = purple_connection_get_account(gc); + + if ((user_info == NULL) || ((b == NULL) && (userinfo == NULL))) + return; + + if (userinfo == NULL) + userinfo = aim_locate_finduserinfo(od, purple_buddy_get_name(b)); + + if (b == NULL) + b = purple_find_buddy(account, userinfo->bn); + + if (b != NULL) { + bname = purple_buddy_get_name(b); + g = purple_buddy_get_group(b); + gname = purple_group_get_name(g); + presence = purple_buddy_get_presence(b); + status = purple_presence_get_active_status(presence); + } + + if (userinfo != NULL) + bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, userinfo->bn)); + + if ((bi != NULL) && (bi->ipaddr != 0)) { + tmp = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", + (bi->ipaddr & 0xff000000) >> 24, + (bi->ipaddr & 0x00ff0000) >> 16, + (bi->ipaddr & 0x0000ff00) >> 8, + (bi->ipaddr & 0x000000ff)); + oscar_user_info_add_pair(user_info, _("IP Address"), tmp); + g_free(tmp); + } + + if ((userinfo != NULL) && (userinfo->warnlevel != 0)) { + tmp = g_strdup_printf("%d", (int)(userinfo->warnlevel/10.0 + .5)); + oscar_user_info_add_pair(user_info, _("Warning Level"), tmp); + g_free(tmp); + } + + if ((b != NULL) && (bname != NULL) && (g != NULL) && (gname != NULL)) { + tmp = aim_ssi_getcomment(od->ssi.local, gname, bname); + if (tmp != NULL) { + char *tmp2 = g_markup_escape_text(tmp, strlen(tmp)); + g_free(tmp); + + oscar_user_info_convert_and_add(account, od, user_info, _("Buddy Comment"), tmp2); + g_free(tmp2); + } + } +} + +void +oscar_user_info_display_error(OscarData *od, guint16 error_reason, gchar *buddy) +{ + PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); + gchar *buf = g_strdup_printf(_("User information not available: %s"), oscar_get_msgerr_reason(error_reason)); + purple_notify_user_info_add_pair(user_info, NULL, buf); + purple_notify_userinfo(od->gc, buddy, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); + purple_conv_present_error(buddy, purple_connection_get_account(od->gc), buf); + g_free(buf); +} + +void +oscar_user_info_display_icq(OscarData *od, struct aim_icq_info *info) +{ + PurpleConnection *gc = od->gc; + PurpleAccount *account = purple_connection_get_account(gc); + PurpleBuddy *buddy; + struct buddyinfo *bi; + gchar who[16]; + PurpleNotifyUserInfo *user_info; + const gchar *alias; + + if (!info->uin) + return; + + user_info = purple_notify_user_info_new(); + + g_snprintf(who, sizeof(who), "%u", info->uin); + buddy = purple_find_buddy(account, who); + if (buddy != NULL) + bi = g_hash_table_lookup(od->buddyinfo, purple_normalize(account, purple_buddy_get_name(buddy))); + else + bi = NULL; + + purple_notify_user_info_add_pair(user_info, _("UIN"), who); + oscar_user_info_convert_and_add(account, od, user_info, _("Nick"), info->nick); + if ((bi != NULL) && (bi->ipaddr != 0)) { + char *tstr = g_strdup_printf("%hhu.%hhu.%hhu.%hhu", + (bi->ipaddr & 0xff000000) >> 24, + (bi->ipaddr & 0x00ff0000) >> 16, + (bi->ipaddr & 0x0000ff00) >> 8, + (bi->ipaddr & 0x000000ff)); + purple_notify_user_info_add_pair(user_info, _("IP Address"), tstr); + g_free(tstr); + } + oscar_user_info_convert_and_add(account, od, user_info, _("First Name"), info->first); + oscar_user_info_convert_and_add(account, od, user_info, _("Last Name"), info->last); + oscar_user_info_convert_and_add_hyperlink(account, od, user_info, _("Email Address"), info->email, "mailto:"); + if (info->numaddresses && info->email2) { + int i; + for (i = 0; i < info->numaddresses; i++) { + oscar_user_info_convert_and_add_hyperlink(account, od, user_info, _("Email Address"), info->email2[i], "mailto:"); + } + } + oscar_user_info_convert_and_add(account, od, user_info, _("Mobile Phone"), info->mobile); + + if (info->gender != 0) + purple_notify_user_info_add_pair(user_info, _("Gender"), (info->gender == 1 ? _("Female") : _("Male"))); + + if ((info->birthyear > 1900) && (info->birthmonth > 0) && (info->birthday > 0)) { + /* Initialize the struct properly or strftime() will crash + * under some conditions (e.g. Debian sarge w/ LANG=en_HK). */ + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + tm->tm_mday = (int)info->birthday; + tm->tm_mon = (int)info->birthmonth - 1; + tm->tm_year = (int)info->birthyear - 1900; + + /* To be 100% sure that the fields are re-normalized. + * If you're sure strftime() ALWAYS does this EVERYWHERE, + * feel free to remove it. --rlaager */ + mktime(tm); + + oscar_user_info_convert_and_add(account, od, user_info, _("Birthday"), purple_date_format_short(tm)); + } + if ((info->age > 0) && (info->age < 255)) { + char age[5]; + snprintf(age, sizeof(age), "%hhd", info->age); + purple_notify_user_info_add_pair(user_info, _("Age"), age); + } + oscar_user_info_convert_and_add_hyperlink(account, od, user_info, _("Personal Web Page"), info->email, ""); + if (buddy != NULL) + oscar_user_info_append_status(gc, user_info, buddy, /* aim_userinfo_t */ NULL, /* use_html_status */ TRUE); + + oscar_user_info_convert_and_add(account, od, user_info, _("Additional Information"), info->info); + purple_notify_user_info_add_section_break(user_info); + + if ((info->homeaddr && (info->homeaddr[0])) || (info->homecity && info->homecity[0]) || (info->homestate && info->homestate[0]) || (info->homezip && info->homezip[0])) { + purple_notify_user_info_add_section_header(user_info, _("Home Address")); + + oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->homeaddr); + oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->homecity); + oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->homestate); + oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->homezip); + } + if ((info->workaddr && info->workaddr[0]) || (info->workcity && info->workcity[0]) || (info->workstate && info->workstate[0]) || (info->workzip && info->workzip[0])) { + purple_notify_user_info_add_section_header(user_info, _("Work Address")); + + oscar_user_info_convert_and_add(account, od, user_info, _("Address"), info->workaddr); + oscar_user_info_convert_and_add(account, od, user_info, _("City"), info->workcity); + oscar_user_info_convert_and_add(account, od, user_info, _("State"), info->workstate); + oscar_user_info_convert_and_add(account, od, user_info, _("Zip Code"), info->workzip); + } + if ((info->workcompany && info->workcompany[0]) || (info->workdivision && info->workdivision[0]) || (info->workposition && info->workposition[0]) || (info->workwebpage && info->workwebpage[0])) { + purple_notify_user_info_add_section_header(user_info, _("Work Information")); + + oscar_user_info_convert_and_add(account, od, user_info, _("Company"), info->workcompany); + oscar_user_info_convert_and_add(account, od, user_info, _("Division"), info->workdivision); + oscar_user_info_convert_and_add(account, od, user_info, _("Position"), info->workposition); + oscar_user_info_convert_and_add_hyperlink(account, od, user_info, _("Web Page"), info->email, ""); + } + + if (buddy != NULL) + alias = purple_buddy_get_alias(buddy); + else + alias = who; + purple_notify_userinfo(gc, who, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); +} + +void +oscar_user_info_display_aim(OscarData *od, aim_userinfo_t *userinfo) +{ + PurpleConnection *gc = od->gc; + PurpleAccount *account = purple_connection_get_account(gc); + PurpleNotifyUserInfo *user_info = purple_notify_user_info_new(); + gchar *tmp = NULL, *info_utf8 = NULL, *base_profile_url = NULL; + + oscar_user_info_append_status(gc, user_info, /* PurpleBuddy */ NULL, userinfo, /* use_html_status */ TRUE); + + if ((userinfo->present & AIM_USERINFO_PRESENT_IDLE) && userinfo->idletime != 0) { + tmp = purple_str_seconds_to_string(userinfo->idletime*60); + oscar_user_info_add_pair(user_info, _("Idle"), tmp); + g_free(tmp); + } + + oscar_user_info_append_extra_info(gc, user_info, NULL, userinfo); + + if ((userinfo->present & AIM_USERINFO_PRESENT_ONLINESINCE) && !oscar_util_valid_name_sms(userinfo->bn)) { + /* An SMS contact is always online; its Online Since value is not useful */ + time_t t = userinfo->onlinesince; + oscar_user_info_add_pair(user_info, _("Online Since"), purple_date_format_full(localtime(&t))); + } + + if (userinfo->present & AIM_USERINFO_PRESENT_MEMBERSINCE) { + time_t t = userinfo->membersince; + oscar_user_info_add_pair(user_info, _("Member Since"), purple_date_format_full(localtime(&t))); + } + + if (userinfo->capabilities != 0) { + tmp = oscar_caps_to_string(userinfo->capabilities); + oscar_user_info_add_pair(user_info, _("Capabilities"), tmp); + g_free(tmp); + } + + /* Info */ + if ((userinfo->info_len > 0) && (userinfo->info != NULL) && (userinfo->info_encoding != NULL)) { + info_utf8 = oscar_encoding_to_utf8(userinfo->info_encoding, userinfo->info, userinfo->info_len); + tmp = oscar_util_format_string(info_utf8, purple_account_get_username(account)); + purple_notify_user_info_add_section_break(user_info); + oscar_user_info_add_pair(user_info, _("Profile"), tmp); + g_free(tmp); + g_free(info_utf8); + } + + purple_notify_user_info_add_section_break(user_info); + base_profile_url = oscar_util_valid_name_icq(userinfo->bn) ? "http://www.icq.com/people" : "http://profiles.aim.com"; + tmp = g_strdup_printf("%s", + base_profile_url, purple_normalize(account, userinfo->bn), _("View web profile")); + purple_notify_user_info_add_pair(user_info, NULL, tmp); + g_free(tmp); + + purple_notify_userinfo(gc, userinfo->bn, user_info, NULL, NULL); + purple_notify_user_info_destroy(user_info); +} \ No newline at end of file diff --git a/purple/libpurple/protocols/oscar/util.c b/purple/libpurple/protocols/oscar/util.c --- a/purple/libpurple/protocols/oscar/util.c +++ b/purple/libpurple/protocols/oscar/util.c @@ -102,101 +102,16 @@ gchar *oscar_get_clientstring(void) const char *name, *version; name = oscar_get_ui_info_string("name", "Purple"); version = oscar_get_ui_info_string("version", VERSION); return g_strdup_printf("%s/%s", name, version);; } -/* - * Tokenizing functions. Used to portably replace strtok/sep. - * -- DMP. - * - */ -/* TODO: Get rid of this and use glib functions */ -int -aimutil_tokslen(char *toSearch, int theindex, char dl) -{ - int curCount = 1; - char *next; - char *last; - int toReturn; - - last = toSearch; - next = strchr(toSearch, dl); - - while(curCount < theindex && next != NULL) { - curCount++; - last = next + 1; - next = strchr(last, dl); - } - - if ((curCount < theindex) || (next == NULL)) - toReturn = strlen(toSearch) - (curCount - 1); - else - toReturn = next - toSearch - (curCount - 1); - - return toReturn; -} - -int -aimutil_itemcnt(char *toSearch, char dl) -{ - int curCount; - char *next; - - curCount = 1; - - next = strchr(toSearch, dl); - - while(next != NULL) { - curCount++; - next = strchr(next + 1, dl); - } - - return curCount; -} - -char * -aimutil_itemindex(char *toSearch, int theindex, char dl) -{ - int curCount; - char *next; - char *last; - char *toReturn; - - curCount = 0; - - last = toSearch; - next = strchr(toSearch, dl); - - while (curCount < theindex && next != NULL) { - curCount++; - last = next + 1; - next = strchr(last, dl); - } - next = strchr(last, dl); - - if (curCount < theindex) { - toReturn = g_malloc(sizeof(char)); - *toReturn = '\0'; - } else { - if (next == NULL) { - toReturn = g_malloc((strlen(last) + 1) * sizeof(char)); - strcpy(toReturn, last); - } else { - toReturn = g_malloc((next - last + 1) * sizeof(char)); - memcpy(toReturn, last, (next - last)); - toReturn[next - last] = '\0'; - } - } - return toReturn; -} - /** * Calculate the checksum of a given icon. */ guint16 aimutil_iconsum(const guint8 *buf, int buflen) { guint32 sum; int i; @@ -318,8 +233,94 @@ oscar_util_name_compare(const char *name while (*name1 == ' ') name1++; if (toupper(*name1) != toupper(*name2)) return 1; } while ((*name1 != '\0') && name1++ && name2++); return 0; } + +/** + * Looks for %n, %d, or %t in a string, and replaces them with the + * specified name, date, and time, respectively. + * + * @param str The string that may contain the special variables. + * @param name The sender name. + * + * @return A newly allocated string where the special variables are + * expanded. This should be g_free'd by the caller. + */ +gchar * +oscar_util_format_string(const char *str, const char *name) +{ + char *c; + GString *cpy; + time_t t; + struct tm *tme; + + g_return_val_if_fail(str != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + /* Create an empty GString that is hopefully big enough for most messages */ + cpy = g_string_sized_new(1024); + + t = time(NULL); + tme = localtime(&t); + + c = (char *)str; + while (*c) { + switch (*c) { + case '%': + if (*(c + 1)) { + switch (*(c + 1)) { + case 'n': + /* append name */ + g_string_append(cpy, name); + c++; + break; + case 'd': + /* append date */ + g_string_append(cpy, purple_date_format_short(tme)); + c++; + break; + case 't': + /* append time */ + g_string_append(cpy, purple_time_format(tme)); + c++; + break; + default: + g_string_append_c(cpy, *c); + } + } else { + g_string_append_c(cpy, *c); + } + break; + default: + g_string_append_c(cpy, *c); + } + c++; + } + + return g_string_free(cpy, FALSE); +} + +gchar * +oscar_format_buddies(GSList *buddies, const gchar *no_buddies_message) +{ + GSList *cur; + GString *result; + if (!buddies) { + return g_strdup_printf("%s", no_buddies_message); + } + result = g_string_new(""); + for (cur = buddies; cur != NULL; cur = cur->next) { + PurpleBuddy *buddy = cur->data; + const gchar *bname = purple_buddy_get_name(buddy); + const gchar *alias = purple_buddy_get_alias_only(buddy); + g_string_append(result, bname); + if (alias) { + g_string_append_printf(result, " (%s)", alias); + } + g_string_append(result, "
"); + } + return g_string_free(result, FALSE); +} diff --git a/purple/libpurple/protocols/oscar/visibility.c b/purple/libpurple/protocols/oscar/visibility.c new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/visibility.c @@ -0,0 +1,140 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +#include "visibility.h" + +/* Translators: This string is a menu option that, if selected, will cause + you to appear online to the chosen user even when your status is set to + Invisible. */ +#define APPEAR_ONLINE N_("Appear Online") + +/* Translators: This string is a menu option that, if selected, will cause + you to appear offline to the chosen user when your status is set to + Invisible (this is the default). */ +#define DONT_APPEAR_ONLINE N_("Don't Appear Online") + +/* Translators: This string is a menu option that, if selected, will cause + you to always appear offline to the chosen user (even when your status + isn't Invisible). */ +#define APPEAR_OFFLINE N_("Appear Offline") + +/* Translators: This string is a menu option that, if selected, will cause + you to appear offline to the chosen user if you are invisible, and + appear online to the chosen user if you are not invisible (this is the + default). */ +#define DONT_APPEAR_OFFLINE N_("Don't Appear Offline") + +static guint16 +get_buddy_list_type(OscarData *od) +{ + PurpleAccount *account = purple_connection_get_account(od->gc); + return purple_account_is_status_active(account, OSCAR_STATUS_ID_INVISIBLE) ? AIM_SSI_TYPE_PERMIT : AIM_SSI_TYPE_DENY; +} + +static gboolean +is_buddy_on_list(OscarData *od, const char *bname) +{ + return aim_ssi_itemlist_finditem(od->ssi.local, NULL, bname, get_buddy_list_type(od)) != NULL; +} + +static void +visibility_cb(PurpleBlistNode *node, gpointer whatever) +{ + PurpleBuddy *buddy = PURPLE_BUDDY(node); + const char* bname = purple_buddy_get_name(buddy); + OscarData *od = purple_connection_get_protocol_data(purple_account_get_connection(purple_buddy_get_account(buddy))); + guint16 list_type = get_buddy_list_type(od); + + if (!is_buddy_on_list(od, bname)) { + aim_ssi_add_to_private_list(od, bname, list_type); + } else { + aim_ssi_del_from_private_list(od, bname, list_type); + } +} + +PurpleMenuAction * +create_visibility_menu_item(OscarData *od, const char *bname) +{ + PurpleAccount *account = purple_connection_get_account(od->gc); + gboolean invisible = purple_account_is_status_active(account, OSCAR_STATUS_ID_INVISIBLE); + gboolean on_list = is_buddy_on_list(od, bname); + const gchar *label; + + if (invisible) { + label = on_list ? _(DONT_APPEAR_ONLINE) : _(APPEAR_ONLINE); + } else { + label = on_list ? _(DONT_APPEAR_OFFLINE) : _(APPEAR_OFFLINE); + } + return purple_menu_action_new(label, PURPLE_CALLBACK(visibility_cb), NULL, NULL); +} + +static void +show_private_list(PurplePluginAction *action, guint16 list_type, const gchar *title, const gchar *list_description, const gchar *menu_action_name) +{ + PurpleConnection *gc = (PurpleConnection *) action->context; + OscarData *od = purple_connection_get_protocol_data(gc); + PurpleAccount *account = purple_connection_get_account(gc); + GSList *buddies, *filtered_buddies, *cur; + gchar *text, *secondary; + + buddies = purple_find_buddies(account, NULL); + filtered_buddies = NULL; + for (cur = buddies; cur != NULL; cur = cur->next) { + PurpleBuddy *buddy; + const gchar *bname; + + buddy = cur->data; + bname = purple_buddy_get_name(buddy); + if (aim_ssi_itemlist_finditem(od->ssi.local, NULL, bname, list_type)) { + filtered_buddies = g_slist_prepend(filtered_buddies, buddy); + } + } + + g_slist_free(buddies); + + filtered_buddies = g_slist_reverse(filtered_buddies); + text = oscar_format_buddies(filtered_buddies, _("you have no buddies on this list")); + g_slist_free(filtered_buddies); + + secondary = g_strdup_printf(_("You can add a buddy to this list " + "by right-clicking on them and " + "selecting \"%s\""), menu_action_name); + purple_notify_formatted(gc, title, list_description, secondary, text, NULL, NULL); + g_free(secondary); + g_free(text); +} + +void +oscar_show_visible_list(PurplePluginAction *action) +{ + show_private_list(action, AIM_SSI_TYPE_PERMIT, _("Visible List"), + _("These buddies will see " + "your status when you switch " + "to \"Invisible\""), + _(APPEAR_ONLINE)); +} + +void +oscar_show_invisible_list(PurplePluginAction *action) +{ + show_private_list(action, AIM_SSI_TYPE_DENY, _("Invisible List"), + _("These buddies will always see you as offline"), + _(APPEAR_OFFLINE)); +} diff --git a/purple/libpurple/protocols/oscar/visibility.h b/purple/libpurple/protocols/oscar/visibility.h new file mode 100644 --- /dev/null +++ b/purple/libpurple/protocols/oscar/visibility.h @@ -0,0 +1,32 @@ +/* + * Purple's oscar protocol plugin + * This file is the legal property of its developers. + * Please see the AUTHORS file distributed alongside this file. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA +*/ + +#ifndef _VISIBILITY_H_ +#define _VISIBILITY_H_ + +#include "oscar.h" +#include "plugin.h" +#include "util.h" + +PurpleMenuAction * create_visibility_menu_item(OscarData *od, const char *bname); +void oscar_show_visible_list(PurplePluginAction *action); +void oscar_show_invisible_list(PurplePluginAction *action); + +#endif \ No newline at end of file diff --git a/purple/libpurple/protocols/qq/buddy_info.c b/purple/libpurple/protocols/qq/buddy_info.c --- a/purple/libpurple/protocols/qq/buddy_info.c +++ b/purple/libpurple/protocols/qq/buddy_info.c @@ -219,22 +219,20 @@ static void info_display_only(PurpleConn purple_notify_user_info_destroy(user_info); g_strfreev(segments); } void qq_request_buddy_info(PurpleConnection *gc, guint32 uid, guint32 update_class, int action) { - qq_data *qd; gchar raw_data[16] = {0}; g_return_if_fail(uid != 0); - qd = (qq_data *) gc->proto_data; g_snprintf(raw_data, sizeof(raw_data), "%u", uid); qq_send_cmd_mess(gc, QQ_CMD_GET_BUDDY_INFO, (guint8 *) raw_data, strlen(raw_data), update_class, action); } /* send packet to modify personal information */ static void request_change_info(PurpleConnection *gc, gchar **segments) { @@ -266,26 +264,24 @@ static void info_modify_cancel_cb(modify g_strfreev(info_request->segments); g_free(info_request); } /* parse fields and send info packet */ static void info_modify_ok_cb(modify_info_request *info_request, PurpleRequestFields *fields) { PurpleConnection *gc; - qq_data *qd; gchar **segments; int index; const char *utf8_str; gchar *value; int choice_num; gc = info_request->gc; - g_return_if_fail(gc != NULL && info_request->gc); - qd = (qq_data *) gc->proto_data; + g_return_if_fail(gc != NULL); segments = info_request->segments; g_return_if_fail(segments != NULL); for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) { if (field_infos[index].iclass == QQ_FIELD_UNUSED) { continue; } if (!purple_request_fields_exists(fields, field_infos[index].id)) { @@ -385,24 +381,22 @@ static void field_request_new(PurpleRequ field = purple_request_field_label_new(field_infos[index].id, segments[index]); purple_request_field_group_add_field(group, field); break; } } static void info_modify_dialogue(PurpleConnection *gc, gchar **segments, int iclass) { - qq_data *qd; PurpleRequestFieldGroup *group; PurpleRequestFields *fields; modify_info_request *info_request; gchar *utf8_title, *utf8_prim; int index; - qd = (qq_data *) gc->proto_data; /* Keep one dialog once a time */ purple_request_close_with_handle(gc); fields = purple_request_fields_new(); group = purple_request_field_group_new(NULL); purple_request_fields_add_group(fields, group); for (index = 1; segments[index] != NULL && index < QQ_INFO_LAST; index++) { @@ -411,27 +405,30 @@ static void info_modify_dialogue(PurpleC } field_request_new(group, index, segments); } switch (iclass) { case QQ_FIELD_CONTACT: utf8_title = g_strdup(_("Modify Contact")); utf8_prim = g_strdup_printf("%s for %s", _("Modify Contact"), segments[0]); + break; case QQ_FIELD_ADDR: utf8_title = g_strdup(_("Modify Address")); utf8_prim = g_strdup_printf("%s for %s", _("Modify Address"), segments[0]); + break; case QQ_FIELD_EXT: utf8_title = g_strdup(_("Modify Extended Information")); utf8_prim = g_strdup_printf("%s for %s", _("Modify Extended Information"), segments[0]); break; case QQ_FIELD_BASE: default: utf8_title = g_strdup(_("Modify Information")); utf8_prim = g_strdup_printf("%s for %s", _("Modify Information"), segments[0]); + break; } info_request = g_new0(modify_info_request, 1); info_request->gc = gc; info_request->iclass = iclass; info_request->segments = segments; purple_request_fields(gc, diff --git a/purple/libpurple/protocols/qq/buddy_list.c b/purple/libpurple/protocols/qq/buddy_list.c --- a/purple/libpurple/protocols/qq/buddy_list.c +++ b/purple/libpurple/protocols/qq/buddy_list.c @@ -50,21 +50,19 @@ typedef struct _qq_buddy_online { guint8 comm_flag; guint16 unknown2; guint8 ending; /* 0x00 */ } qq_buddy_online; /* get a list of online_buddies */ void qq_request_get_buddies_online(PurpleConnection *gc, guint8 position, guint32 update_class) { - qq_data *qd; guint8 *raw_data; gint bytes = 0; - qd = (qq_data *) gc->proto_data; raw_data = g_newa(guint8, 5); /* 000-000 get online friends cmd * only 0x02 and 0x03 returns info from server, other valuse all return 0xff * I can also only send the first byte (0x02, or 0x03) * and the result is the same */ bytes += qq_put8(raw_data + bytes, QQ_GET_ONLINE_BUDDY_02); /* 001-001 seems it supports 255 online buddies at most */ @@ -355,29 +353,26 @@ guint16 qq_process_get_buddies(guint8 *d purple_debug_info("QQ", "Received %d buddies, nextposition=%u\n", count, (guint) position); return position; } guint32 qq_process_get_buddies_and_rooms(guint8 *data, gint data_len, PurpleConnection *gc) { - qq_data *qd; gint i, j; gint bytes; guint8 sub_cmd, reply_code; guint32 unknown, position; guint32 uid; guint8 type; qq_room_data *rmd; g_return_val_if_fail(data != NULL && data_len != 0, -1); - qd = (qq_data *) gc->proto_data; - bytes = 0; bytes += qq_get8(&sub_cmd, data + bytes); g_return_val_if_fail(sub_cmd == 0x01, -1); bytes += qq_get8(&reply_code, data + bytes); if(0 != reply_code) { purple_debug_warning("QQ", "qq_process_get_buddies_and_rooms, %d\n", reply_code); } @@ -463,21 +458,16 @@ static guint8 get_status_from_purple(Pu void qq_request_change_status(PurpleConnection *gc, guint32 update_class) { qq_data *qd; guint8 raw_data[16] = {0}; gint bytes = 0; guint8 away_cmd; guint32 misc_status; gboolean fake_video; - PurpleAccount *account; - PurplePresence *presence; - - account = purple_connection_get_account(gc); - presence = purple_account_get_presence(account); qd = (qq_data *) gc->proto_data; if (!qd->is_login) return; away_cmd = get_status_from_purple(gc); misc_status = 0x00000000; @@ -591,24 +581,23 @@ void qq_process_buddy_change_status(guin } } } /*TODO: maybe this should be qq_update_buddy_status() ?*/ void qq_update_buddy_status(PurpleConnection *gc, guint32 uid, guint8 status, guint8 flag) { gchar *who; - gchar *status_id; + const gchar *status_id; g_return_if_fail(uid != 0); /* purple supports signon and idle time * but it is not much use for QQ, I do not use them */ /* serv_got_update(gc, name, online, 0, q_bud->signon, q_bud->idle, bud->uc); */ - status_id = "available"; switch(status) { case QQ_BUDDY_OFFLINE: status_id = "offline"; break; case QQ_BUDDY_ONLINE_NORMAL: status_id = "available"; break; case QQ_BUDDY_CHANGE_TO_OFFLINE: @@ -672,23 +661,20 @@ void qq_update_buddyies_status(PurpleCon bd->status = QQ_BUDDY_CHANGE_TO_OFFLINE; bd->last_update = time(NULL); qq_update_buddy_status(gc, bd->uid, bd->status, bd->comm_flag); } } void qq_buddy_data_free_all(PurpleConnection *gc) { - qq_data *qd; PurpleBuddy *buddy; GSList *buddies, *it; gint count = 0; - qd = (qq_data *)purple_connection_get_protocol_data(gc); - buddies = purple_find_buddies(purple_connection_get_account(gc), NULL); for (it = buddies; it; it = it->next) { qq_buddy_data *qbd = NULL; buddy = it->data; if (buddy == NULL) continue; qbd = purple_buddy_get_protocol_data(buddy); diff --git a/purple/libpurple/protocols/qq/buddy_opt.c b/purple/libpurple/protocols/qq/buddy_opt.c --- a/purple/libpurple/protocols/qq/buddy_opt.c +++ b/purple/libpurple/protocols/qq/buddy_opt.c @@ -257,28 +257,25 @@ void qq_request_auth_code(PurpleConnecti bytes += qq_put16(raw_data + bytes, sub_cmd); bytes += qq_put32(raw_data + bytes, uid); qq_send_cmd_mess(gc, QQ_CMD_AUTH_CODE, raw_data, bytes, 0, uid); } void qq_process_auth_code(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) { - qq_data *qd; gint bytes; guint8 cmd, reply; guint16 sub_cmd; guint8 *code = NULL; guint16 code_len = 0; g_return_if_fail(data != NULL && data_len != 0); g_return_if_fail(uid != 0); - qd = (qq_data *) gc->proto_data; - qq_show_packet("qq_process_auth_code", data, data_len); bytes = 0; bytes += qq_get8(&cmd, data + bytes); bytes += qq_get16(&sub_cmd, data + bytes); bytes += qq_get8(&reply, data + bytes); g_return_if_fail(bytes + 2 <= data_len); bytes += qq_get16(&code_len, data + bytes); @@ -319,17 +316,17 @@ static void add_buddy_question_input(Pur add_req = g_new0(qq_buddy_req, 1); add_req->gc = gc; add_req->uid = uid; add_req->auth = NULL; add_req->auth_len = 0; who = uid_to_purple_name(uid); - msg = g_strdup_printf(_("%u requires verification"), uid); + msg = g_strdup_printf(_("%u requires verification: %s"), uid, question); purple_request_input(gc, _("Add buddy question"), msg, _("Enter answer here"), NULL, TRUE, FALSE, NULL, _("Send"), G_CALLBACK(add_buddy_question_cb), _("Cancel"), G_CALLBACK(buddy_req_cancel_cb), purple_connection_get_account(gc), who, NULL, add_req); @@ -395,27 +392,24 @@ static void request_add_buddy_by_questio bytes += qq_put8(raw_data + bytes, 1); /* ALLOW ADD ME FLAG */ bytes += qq_put8(raw_data + bytes, 0); /* group number? */ qq_send_cmd(gc, QQ_CMD_ADD_BUDDY_AUTH_EX, raw_data, bytes); } void qq_process_question(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) { - qq_data *qd; gint bytes; guint8 cmd, reply; gchar *question, *answer; guint16 code_len; guint8 *code; g_return_if_fail(data != NULL && data_len != 0); - qd = (qq_data *) gc->proto_data; - qq_show_packet("qq_process_question", data, data_len); bytes = 0; bytes += qq_get8(&cmd, data + bytes); if (cmd == QQ_QUESTION_GET) { bytes += qq_get_vstr(&question, QQ_CHARSET_DEFAULT, data + bytes); bytes += qq_get_vstr(&answer, QQ_CHARSET_DEFAULT, data + bytes); purple_debug_info("QQ", "Get buddy adding Q&A:\n%s\n%s\n", question, answer); g_free(question); @@ -715,23 +709,20 @@ void qq_add_buddy(PurpleConnection *gc, purple_debug_info("QQ", "Remove buddy with invalid QQ number %u\n", uid); qq_buddy_free(buddy); } /* process reply to add_buddy_auth request */ void qq_process_add_buddy_auth(guint8 *data, gint data_len, PurpleConnection *gc) { - qq_data *qd; gchar **segments, *msg_utf8; g_return_if_fail(data != NULL && data_len != 0); - qd = (qq_data *) gc->proto_data; - if (data[0] == '0') { purple_debug_info("QQ", "Reply OK for sending authorize\n"); return; } if (NULL == (segments = split_data(data, data_len, "\x1f", 2))) { purple_notify_error(gc, _("QQ Buddy"), _("Failed sending authorize"), NULL); return; @@ -762,21 +753,19 @@ void qq_process_remove_buddy(PurpleConne if (buddy != NULL) { qq_buddy_free(buddy); } } /* process the server reply for my request to remove myself from a buddy */ void qq_process_buddy_remove_me(PurpleConnection *gc, guint8 *data, gint data_len, guint32 uid) { - qq_data *qd; gchar *msg; g_return_if_fail(data != NULL && data_len != 0); - qd = (qq_data *) gc->proto_data; if (data[0] == 0) { purple_debug_info("QQ", "Reply OK for removing me from %u's buddy list\n", uid); return; } msg = g_strdup_printf(_("Failed removing me from %d's buddy list"), uid); purple_notify_info(gc, _("QQ Buddy"), msg, NULL); g_free(msg); @@ -999,27 +988,24 @@ static void server_buddy_add_request(Pur } buddy_add_input(gc, uid, reason); g_free(reason); } void qq_process_buddy_check_code(PurpleConnection *gc, guint8 *data, gint data_len) { - qq_data *qd; gint bytes; guint8 cmd; guint8 reply; guint32 uid; guint16 flag1, flag2; g_return_if_fail(data != NULL && data_len >= 5); - qd = (qq_data *) gc->proto_data; - qq_show_packet("buddy_check_code", data, data_len); bytes = 0; bytes += qq_get8(&cmd, data + bytes); /* 0x03 */ bytes += qq_get8(&reply, data + bytes); if (reply == 0) { purple_debug_info("QQ", "Failed checking code\n"); diff --git a/purple/libpurple/protocols/qq/char_conv.c b/purple/libpurple/protocols/qq/char_conv.c --- a/purple/libpurple/protocols/qq/char_conv.c +++ b/purple/libpurple/protocols/qq/char_conv.c @@ -32,27 +32,29 @@ #define UTF8 "UTF-8" #define QQ_CHARSET_ZH_CN "GB18030" #define QQ_CHARSET_ENG "ISO-8859-1" #define QQ_NULL_MSG "(NULL)" /* return this if conversion fails */ /* convert a string from from_charset to to_charset, using g_convert */ /* Warning: do not return NULL */ -static gchar *do_convert(const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset) +static gchar *do_convert(const gchar *str, gssize len, guint8 *out_len, const gchar *to_charset, const gchar *from_charset) { GError *error = NULL; gchar *ret; gsize byte_read, byte_write; g_return_val_if_fail(str != NULL && to_charset != NULL && from_charset != NULL, g_strdup(QQ_NULL_MSG)); ret = g_convert(str, len, to_charset, from_charset, &byte_read, &byte_write, &error); if (error == NULL) { + if (out_len) + *out_len = byte_write; return ret; /* convert is OK */ } /* convert error */ purple_debug_error("QQ_CONVERT", "%s\n", error->message); qq_show_packet("Dump failed text", (guint8 *) str, (len == -1) ? strlen(str) : len); g_error_free(error); @@ -62,52 +64,52 @@ static gchar *do_convert(const gchar *st /* * take the input as a pascal string and return a converted c-string in UTF-8 * returns the number of bytes read, return -1 if fatal error * the converted UTF-8 will be saved in ret * Return: *ret != NULL */ gint qq_get_vstr(gchar **ret, const gchar *from_charset, guint8 *data) { - guint8 len; + gssize len; + guint8 out_len; g_return_val_if_fail(data != NULL && from_charset != NULL, -1); len = data[0]; if (len == 0) { *ret = g_strdup(""); return 1; } - *ret = do_convert((gchar *) (data + 1), (gssize) len, UTF8, from_charset); + *ret = do_convert((gchar *) (data + 1), len, &out_len, UTF8, from_charset); - return len + 1; + return out_len + 1; } gint qq_put_vstr(guint8 *buf, const gchar *str_utf8, const gchar *to_charset) { gchar *str; guint8 len; - if (str_utf8 == NULL || (len = strlen(str_utf8)) == 0) { + if (str_utf8 == NULL || str_utf8[0] == '\0') { buf[0] = 0; return 1; } - str = do_convert(str_utf8, -1, to_charset, UTF8); - len = strlen(str_utf8); + str = do_convert(str_utf8, -1, &len, to_charset, UTF8); buf[0] = len; if (len > 0) { memcpy(buf + 1, str, len); } return 1 + len; } /* Warning: do not return NULL */ gchar *utf8_to_qq(const gchar *str, const gchar *to_charset) { - return do_convert(str, -1, to_charset, UTF8); + return do_convert(str, -1, NULL, to_charset, UTF8); } /* Warning: do not return NULL */ gchar *qq_to_utf8(const gchar *str, const gchar *from_charset) { - return do_convert(str, -1, UTF8, from_charset); + return do_convert(str, -1, NULL, UTF8, from_charset); } diff --git a/purple/libpurple/protocols/qq/file_trans.c b/purple/libpurple/protocols/qq/file_trans.c --- a/purple/libpurple/protocols/qq/file_trans.c +++ b/purple/libpurple/protocols/qq/file_trans.c @@ -79,17 +79,17 @@ static void _fill_file_md5(const gchar * size_t wc; const gint QQ_MAX_FILE_MD5_LENGTH = 10002432; g_return_if_fail(filename != NULL && md5 != NULL); if (filelen > QQ_MAX_FILE_MD5_LENGTH) filelen = QQ_MAX_FILE_MD5_LENGTH; - fp = fopen(filename, "rb"); + fp = g_fopen(filename, "rb"); g_return_if_fail(fp != NULL); buffer = g_newa(guint8, filelen); g_return_if_fail(buffer != NULL); wc = fread(buffer, filelen, 1, fp); fclose(fp); if (wc != 1) { purple_debug_error("qq", "Unable to read file: %s\n", filename); @@ -197,17 +197,17 @@ void qq_xfer_close_file(PurpleXfer *xfer ft_info *info = xfer->data; if (info->buffer) munmap(info->buffer, purple_xfer_get_size(xfer)); } #else static int _qq_xfer_open_file(const gchar *filename, const gchar *method, PurpleXfer *xfer) { ft_info *info = xfer->data; - info->dest_fp = fopen(purple_xfer_get_local_filename(xfer), method); + info->dest_fp = g_fopen(purple_xfer_get_local_filename(xfer), method); if (info->dest_fp == NULL) { return -1; } return 0; } static gint _qq_xfer_read_file(guint8 *buffer, guint index, guint len, PurpleXfer *xfer) { @@ -233,22 +233,19 @@ void qq_xfer_close_file(PurpleXfer *xfer #endif static gint _qq_send_file(PurpleConnection *gc, guint8 *data, gint len, guint16 packet_type, guint32 to_uid) { guint8 *raw_data; gint bytes = 0; guint32 file_key; qq_data *qd; - ft_info *info; qd = (qq_data *) gc->proto_data; - info = (ft_info *) qd->xfer->data; - raw_data = g_newa(guint8, MAX_PACKET_SIZE); file_key = _gen_file_key(); bytes += qq_put8(raw_data + bytes, packet_type); bytes += qq_put16(raw_data + bytes, qd->client_tag); bytes += qq_put8(raw_data + bytes, file_key & 0xff); bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(qd->uid, file_key)); bytes += qq_put32(raw_data + bytes, _encrypt_qq_uid(to_uid, file_key)); @@ -800,19 +797,16 @@ static void _qq_process_recv_file_data(P break; } } void qq_process_recv_file(PurpleConnection *gc, guint8 *data, gint len) { gint bytes; guint8 tag; - qq_data *qd; - - qd = (qq_data *) gc->proto_data; bytes = 0; bytes += qq_get8(&tag, data + bytes); switch (tag) { case QQ_FILE_CONTROL_PACKET_TAG: _qq_process_recv_file_ctl_packet(gc, data + bytes, len - bytes); break; diff --git a/purple/libpurple/protocols/qq/group.c b/purple/libpurple/protocols/qq/group.c --- a/purple/libpurple/protocols/qq/group.c +++ b/purple/libpurple/protocols/qq/group.c @@ -114,18 +114,16 @@ PurpleRoomlist *qq_roomlist_get_list(Pur gc); return qd->roomlist; } /* free roomlist space, I have no idea when this one is called... */ void qq_roomlist_cancel(PurpleRoomlist *list) { - qq_data *qd; PurpleConnection *gc; g_return_if_fail(list != NULL); gc = purple_account_get_connection(list->account); - qd = (qq_data *) gc->proto_data; purple_roomlist_set_in_progress(list, FALSE); purple_roomlist_unref(list); } diff --git a/purple/libpurple/protocols/qq/group_im.c b/purple/libpurple/protocols/qq/group_im.c --- a/purple/libpurple/protocols/qq/group_im.c +++ b/purple/libpurple/protocols/qq/group_im.c @@ -43,22 +43,20 @@ #include "qq_network.h" #include "qq_process.h" #include "utils.h" /* show group conversation window */ PurpleConversation *qq_room_conv_open(PurpleConnection *gc, qq_room_data *rmd) { PurpleConversation *conv; - qq_data *qd; gchar *topic_utf8; g_return_val_if_fail(rmd != NULL, NULL); g_return_val_if_fail(rmd->title_utf8, NULL); - qd = (qq_data *) gc->proto_data; conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_CHAT, rmd->title_utf8, purple_connection_get_account(gc)); if (conv != NULL) { /* show only one conversation per room */ return conv; } @@ -202,17 +200,16 @@ void qq_room_got_chat_in(PurpleConnectio } serv_got_chat_in(gc, room_id, from, 0, msg, in_time); g_free(from); } /* recv an IM from a group chat */ void qq_process_room_im(guint8 *data, gint data_len, guint32 id, PurpleConnection *gc, guint16 msg_type) { - qq_data *qd; gchar *msg_smiley, *msg_fmt, *msg_utf8; gint bytes, tail_len; struct { guint32 ext_id; guint8 type8; guint32 member_uid; guint16 unknown; guint16 msg_seq; @@ -224,17 +221,16 @@ void qq_process_room_im(guint8 *data, gi guint32 temp_id; guint16 content_type; guint8 frag_count, frag_index; guint16 msg_id; qq_im_format *fmt = NULL; /* at least include im_text.msg_len */ g_return_if_fail(data != NULL && data_len > 23); - qd = (qq_data *) gc->proto_data; /* qq_show_packet("ROOM_IM", data, data_len); */ memset(&im_text, 0, sizeof(im_text)); bytes = 0; bytes += qq_get32(&(im_text.ext_id), data + bytes); bytes += qq_get8(&(im_text.type8), data + bytes); if(QQ_MSG_TEMP_QUN_IM == msg_type) { @@ -371,29 +367,25 @@ int qq_chat_send(PurpleConnection *gc, i { qq_data *qd; qq_im_format *fmt; gchar *msg_stripped, *tmp; GSList *segments, *it; gint msg_len; const gchar *start_invalid; gboolean is_smiley_none; - guint8 frag_count, frag_index; g_return_val_if_fail(NULL != gc && NULL != gc->proto_data, -1); g_return_val_if_fail(id != 0 && what != NULL, -1); qd = (qq_data *) gc->proto_data; purple_debug_info("QQ", "Send chat IM to %u, len %" G_GSIZE_FORMAT ":\n%s\n", id, strlen(what), what); /* qq_show_packet("chat IM UTF8", (guint8 *)what, strlen(what)); */ - fmt = qq_im_fmt_new_by_purple(what); - is_smiley_none = qq_im_smiley_none(what); - msg_stripped = purple_markup_strip_html(what); g_return_val_if_fail(msg_stripped != NULL, -1); /* qq_show_packet("IM Stripped", (guint8 *)what, strlen(what)); */ /* Check and valid utf8 string */ msg_len = strlen(msg_stripped); if (!g_utf8_validate(msg_stripped, msg_len, &start_invalid)) { if (start_invalid > msg_stripped) { @@ -412,31 +404,15 @@ int qq_chat_send(PurpleConnection *gc, i g_free(msg_stripped); if (segments == NULL) { return -1; } qd->send_im_id++; fmt = qq_im_fmt_new_by_purple(what); - frag_count = g_slist_length(segments); - frag_index = 0; -/* - if (frag_count <= 1) { -*/ - for (it = segments; it; it = it->next) { - request_room_send_im(gc, id, fmt, (gchar *)it->data); - g_free(it->data); - } -/* - } else { - for (it = segments; it; it = it->next) { - request_room_send_im_ex(gc, id, fmt, (gchar *)it->data, - qd->send_im_id, frag_count, frag_index); - g_free(it->data); - frag_index++; - } + for (it = segments; it; it = g_slist_delete_link(it, it)) { + request_room_send_im(gc, id, fmt, (gchar *)it->data); + g_free(it->data); } -*/ qq_im_fmt_free(fmt); - g_slist_free(segments); return 1; } diff --git a/purple/libpurple/protocols/qq/group_join.c b/purple/libpurple/protocols/qq/group_join.c --- a/purple/libpurple/protocols/qq/group_join.c +++ b/purple/libpurple/protocols/qq/group_join.c @@ -173,22 +173,20 @@ void qq_send_cmd_group_auth(PurpleConnec bytes += qq_put_vstr(raw_data + bytes, reason_utf8, QQ_CHARSET_DEFAULT); qq_send_room_cmd(gc, QQ_ROOM_CMD_AUTH, rmd->id, raw_data, bytes); } /* If comes here, cmd is OK already */ void qq_process_group_cmd_exit_group(guint8 *data, gint len, PurpleConnection *gc) { - qq_data *qd; gint bytes; guint32 id; g_return_if_fail(data != NULL && len > 0); - qd = (qq_data *) gc->proto_data; if (len < 4) { purple_debug_error("QQ", "Invalid exit group reply, expect %d bytes, read %d bytes\n", 4, len); return; } bytes = 0; bytes += qq_get32(&id, data + bytes); @@ -196,22 +194,20 @@ void qq_process_group_cmd_exit_group(gui qq_room_remove(gc, id); } /* Process the reply to group_auth subcmd */ void qq_process_group_cmd_join_group_auth(guint8 *data, gint len, PurpleConnection *gc) { gint bytes; guint32 id; - qq_data *qd; qq_room_data *rmd; gchar *msg; g_return_if_fail(data != NULL && len > 0); - qd = (qq_data *) gc->proto_data; if (len < 4) { purple_debug_error("QQ", "Invalid join room reply, expect %d bytes, read %d bytes\n", 4, len); return; } bytes = 0; bytes += qq_get32(&id, data + bytes); @@ -278,25 +274,23 @@ void qq_process_group_cmd_join_group(gui purple_notify_info(gc, _("QQ Qun Operation"), _("Failed:"), _("Join Qun, Unknown Reply")); } } /* Attempt to join a group without auth */ void qq_group_join(PurpleConnection *gc, GHashTable *data) { - qq_data *qd; gchar *ext_id_str; gchar *id_str; guint32 ext_id; guint32 id; qq_room_data *rmd; g_return_if_fail(data != NULL); - qd = (qq_data *) gc->proto_data; ext_id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_EXTERNAL_ID); id_str = g_hash_table_lookup(data, QQ_ROOM_KEY_INTERNAL_ID); purple_debug_info("QQ", "Join room %s, extend id %s\n", id_str, ext_id_str); if (id_str != NULL) { id = strtoul(id_str, NULL, 10); if (id != 0) { diff --git a/purple/libpurple/protocols/qq/group_opt.c b/purple/libpurple/protocols/qq/group_opt.c --- a/purple/libpurple/protocols/qq/group_opt.c +++ b/purple/libpurple/protocols/qq/group_opt.c @@ -129,22 +129,20 @@ static void member_join_deny_cb(gpointer add_req); g_free(who); } void qq_group_modify_members(PurpleConnection *gc, qq_room_data *rmd, guint32 *new_members) { guint32 *old_members, *del_members, *add_members; qq_buddy_data *bd; - qq_data *qd; gint i = 0, old = 0, new = 0, del = 0, add = 0; GList *list; g_return_if_fail(rmd != NULL); - qd = (qq_data *) gc->proto_data; if (new_members[0] == 0xffffffff) return; old_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); del_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); add_members = g_newa(guint32, QQ_QUN_MEMBER_MAX); /* construct the old member list */ diff --git a/purple/libpurple/protocols/qq/im.c b/purple/libpurple/protocols/qq/im.c --- a/purple/libpurple/protocols/qq/im.c +++ b/purple/libpurple/protocols/qq/im.c @@ -546,17 +546,16 @@ qq_im_format *qq_im_fmt_new(void) } qq_im_format *qq_im_fmt_new_by_purple(const gchar *msg) { qq_im_format *fmt; const gchar *start, *end, *last; GData *attribs; gchar *tmp; - unsigned char *rgb; g_return_val_if_fail(msg != NULL, NULL); fmt = qq_im_fmt_new(); last = msg; while (purple_markup_find_tag("font", last, &start, &end, &attribs)) { tmp = g_datalist_get_data(&attribs, "face"); @@ -569,18 +568,21 @@ qq_im_format *qq_im_fmt_new_by_purple(co tmp = g_datalist_get_data(&attribs, "size"); if (tmp) { fmt->attr = atoi(tmp) * 3 + 1; fmt->attr &= 0x0f; } tmp = g_datalist_get_data(&attribs, "color"); if (tmp && strlen(tmp) > 1) { - rgb = purple_base16_decode(tmp + 1, NULL); - g_memmove(fmt->rgb, rgb, 3); + unsigned char *rgb; + gsize rgb_len; + rgb = purple_base16_decode(tmp + 1, &rgb_len); + if (rgb != NULL && rgb_len >= 3) + g_memmove(fmt->rgb, rgb, 3); g_free(rgb); } g_datalist_clear(&attribs); last = end + 1; } if (purple_markup_find_tag("b", msg, &start, &end, &attribs)) { @@ -724,17 +726,16 @@ void qq_got_message(PurpleConnection *gc from = uid_to_purple_name(qd->uid); serv_got_im(gc, from, msg, PURPLE_MESSAGE_SYSTEM, now); g_free(from); } /* process received normal text IM */ static void process_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header) { - qq_data *qd; guint16 purple_msg_type; gchar *who; gchar *msg_smiley, *msg_fmt, *msg_utf8; PurpleBuddy *buddy; qq_buddy_data *bd; gint bytes, tail_len; qq_im_format *fmt = NULL; @@ -748,20 +749,19 @@ static void process_im_text(PurpleConnec guint8 fragment_count; guint8 fragment_index; guint8 msg_id; guint8 unknown2; guint8 msg_type; gchar *msg; /* no fixed length, ends with 0x00 */ } im_text; - g_return_if_fail (data != NULL && len > 0); + g_return_if_fail(data != NULL && len > 0); g_return_if_fail(im_header != NULL); - qd = (qq_data *) gc->proto_data; memset(&im_text, 0, sizeof(im_text)); /* qq_show_packet("IM text", data, len); */ bytes = 0; bytes += qq_get16(&(im_text.msg_seq), data + bytes); bytes += qq_get32(&(im_text.send_time), data + bytes); bytes += qq_get16(&(im_text.sender_icon), data + bytes); bytes += qq_getdata(im_text.unknown1, sizeof(im_text.unknown1), data + bytes); /* 0x(00 00 00)*/ @@ -822,17 +822,16 @@ static void process_im_text(PurpleConnec g_free(msg_utf8); g_free(who); g_free(im_text.msg); } /* process received extended (2007) text IM */ static void process_extend_im_text(PurpleConnection *gc, guint8 *data, gint len, qq_im_header *im_header) { - qq_data *qd; guint16 purple_msg_type; gchar *who; gchar *msg_smiley, *msg_fmt, *msg_utf8; PurpleBuddy *buddy; qq_buddy_data *bd; gint bytes, tail_len; qq_im_format *fmt = NULL; @@ -847,20 +846,19 @@ static void process_extend_im_text(Purpl guint8 fragment_index; guint8 msg_id; guint8 unknown2; guint8 msg_type; gchar *msg; /* no fixed length, ends with 0x00 */ guint8 fromMobileQQ; } im_text; - g_return_if_fail (data != NULL && len > 0); + g_return_if_fail(data != NULL && len > 0); g_return_if_fail(im_header != NULL); - qd = (qq_data *) gc->proto_data; memset(&im_text, 0, sizeof(im_text)); /* qq_show_packet("Extend IM text", data, len); */ bytes = 0; bytes += qq_get16(&(im_text.msg_seq), data + bytes); bytes += qq_get32(&(im_text.send_time), data + bytes); bytes += qq_get16(&(im_text.sender_icon), data + bytes); bytes += qq_get32(&(im_text.has_font_attr), data + bytes); @@ -1042,22 +1040,20 @@ void qq_process_extend_im(PurpleConnecti } /* send an IM to uid_to */ static void request_send_im(PurpleConnection *gc, guint32 uid_to, gint type, qq_im_format *fmt, gchar *msg, guint8 id, guint8 frag_count, guint8 frag_index) { qq_data *qd; guint8 raw_data[MAX_PACKET_SIZE - 16]; - guint16 im_type; gint bytes; time_t now; qd = (qq_data *) gc->proto_data; - im_type = QQ_NORMAL_IM_TEXT; /* purple_debug_info("QQ", "Send IM %d-%d\n", frag_count, frag_index); */ bytes = 0; /* 000-003: receiver uid */ bytes += qq_put32(raw_data + bytes, qd->uid); /* 004-007: sender uid */ bytes += qq_put32(raw_data + bytes, uid_to); /* 008-009: sender client version */ @@ -1117,23 +1113,22 @@ static void im_convert_and_merge(GString } GSList *qq_im_get_segments(gchar *msg_stripped, gboolean is_smiley_none) { GSList *string_list = NULL; GString *new_string; GString *append_utf8; gchar *start, *p; - gint count, len; + gint len; qq_emoticon *emoticon; g_return_val_if_fail(msg_stripped != NULL, NULL); start = msg_stripped; - count = 0; new_string = g_string_new(""); append_utf8 = g_string_new(""); while (*start) { p = start; /* Convert emoticon */ if (!is_smiley_none && *p == '/') { if (new_string->len + append_utf8->len + 2 > QQ_MSG_IM_MAX) { diff --git a/purple/libpurple/protocols/qq/qq.c b/purple/libpurple/protocols/qq/qq.c --- a/purple/libpurple/protocols/qq/qq.c +++ b/purple/libpurple/protocols/qq/qq.c @@ -84,25 +84,22 @@ static GList *server_list_build(gchar se } return list; } static void server_list_create(PurpleAccount *account) { PurpleConnection *gc; qq_data *qd; - PurpleProxyInfo *gpi; const gchar *custom_server; gc = purple_account_get_connection(account); g_return_if_fail(gc != NULL && gc->proto_data != NULL); qd = gc->proto_data; - gpi = purple_proxy_get_setup(account); - qd->use_tcp = purple_account_get_bool(account, "use_tcp", TRUE); custom_server = purple_account_get_string(account, "server", NULL); if (custom_server != NULL) { purple_debug_info("QQ", "Select server '%s'\n", custom_server); if (*custom_server != '\0' && g_ascii_strcasecmp(custom_server, "auto") != 0) { qd->servers = g_list_append(qd->servers, g_strdup(custom_server)); @@ -376,23 +373,20 @@ static void qq_tooltip_text(PurpleBuddy g_free(tmp); #endif } /* we can show tiny icons on the four corners of buddy icon, */ static const char *qq_list_emblem(PurpleBuddy *b) { PurpleAccount *account; - PurpleConnection *gc; - qq_data *qd; qq_buddy_data *buddy; if (!b || !(account = purple_buddy_get_account(b)) || - !(gc = purple_account_get_connection(account)) || - !(qd = purple_connection_get_protocol_data(gc))) + !purple_account_get_connection(account)) return NULL; buddy = purple_buddy_get_protocol_data(b); if (!buddy) { return "not-authorized"; } if (buddy->comm_flag & QQ_COMM_FLAG_MOBILE) @@ -619,22 +613,20 @@ static void action_show_account_info(Pur purple_notify_formatted(gc, NULL, _("Login Information"), NULL, info->str, NULL, NULL); g_string_free(info, TRUE); } static void action_about_openq(PurplePluginAction *action) { PurpleConnection *gc = (PurpleConnection *) action->context; - qq_data *qd; GString *info; gchar *title; - g_return_if_fail(NULL != gc && NULL != gc->proto_data); - qd = (qq_data *) gc->proto_data; + g_return_if_fail(NULL != gc); info = g_string_new(""); g_string_append(info, _("

Original Author:
\n")); g_string_append(info, "puzzlebird
\n"); g_string_append(info, "
\n"); g_string_append(info, _("

Code Contributors:
\n")); g_string_append(info, "gfhuang(poppyer) : patches for libpurple 2.0.0beta2, maintainer
\n"); diff --git a/purple/libpurple/protocols/qq/qq_base.c b/purple/libpurple/protocols/qq/qq_base.c --- a/purple/libpurple/protocols/qq/qq_base.c +++ b/purple/libpurple/protocols/qq/qq_base.c @@ -389,26 +389,23 @@ static const guint8 login_53_68[16] = { 0x82, 0x2a, 0x91, 0xfd, 0xa5, 0xca, 0x67, 0x4c, 0xac, 0x81, 0x1f, 0x6f, 0x52, 0x05, 0xa7, 0xbf }; */ /* process the login reply packet */ guint8 qq_process_login( PurpleConnection *gc, guint8 *data, gint data_len) { - qq_data *qd; guint8 ret = data[0]; gchar *msg, *msg_utf8; gchar *error; PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED; g_return_val_if_fail(data != NULL && data_len != 0, QQ_LOGIN_REPLY_ERR); - qd = (qq_data *) gc->proto_data; - switch (ret) { case QQ_LOGIN_REPLY_OK: purple_debug_info("QQ", "Login OK\n"); return process_login_ok(gc, data, data_len); case QQ_LOGIN_REPLY_REDIRECT: purple_debug_info("QQ", "Redirect new server\n"); return process_login_redirect(gc, data, data_len); diff --git a/purple/libpurple/protocols/qq/qq_network.c b/purple/libpurple/protocols/qq/qq_network.c --- a/purple/libpurple/protocols/qq/qq_network.c +++ b/purple/libpurple/protocols/qq/qq_network.c @@ -477,23 +477,21 @@ static void tcp_pending(gpointer data, g break; } } } static void udp_pending(gpointer data, gint source, PurpleInputCondition cond) { PurpleConnection *gc = NULL; - qq_data *qd; guint8 *buf; gint buf_len; gc = (PurpleConnection *) data; - g_return_if_fail(gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; + g_return_if_fail(gc != NULL); if(cond != PURPLE_INPUT_READ) { purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, _("Socket error")); return; } @@ -743,24 +741,22 @@ static void set_all_keys(PurpleConnectio } /* the callback function after socket is built * we setup the qq protocol related configuration here */ static void connect_cb(gpointer data, gint source, const gchar *error_message) { PurpleConnection *gc; qq_data *qd; - PurpleAccount *account ; qq_connection *conn; gc = (PurpleConnection *) data; g_return_if_fail(gc != NULL && gc->proto_data != NULL); qd = (qq_data *) gc->proto_data; - account = purple_connection_get_account(gc); /* conn_data will be destoryed */ qd->conn_data = NULL; if (!PURPLE_CONNECTION_IS_VALID(gc)) { purple_debug_info("QQ_CONN", "Invalid connection\n"); close(source); return; diff --git a/purple/libpurple/protocols/qq/qq_process.c b/purple/libpurple/protocols/qq/qq_process.c --- a/purple/libpurple/protocols/qq/qq_process.c +++ b/purple/libpurple/protocols/qq/qq_process.c @@ -53,44 +53,37 @@ enum { QQ_ROOM_CMD_REPLY_OK = 0x00, QQ_ROOM_CMD_REPLY_SEARCH_ERROR = 0x02, QQ_ROOM_CMD_REPLY_NOT_MEMBER = 0x0a }; /* default process, decrypt and dump */ static void process_unknow_cmd(PurpleConnection *gc,const gchar *title, guint8 *data, gint data_len, guint16 cmd, guint16 seq) { - qq_data *qd; gchar *msg; g_return_if_fail(data != NULL && data_len != 0); qq_show_packet(title, data, data_len); - qd = (qq_data *) gc->proto_data; - qq_hex_dump(PURPLE_DEBUG_WARNING, "QQ", data, data_len, ">>> [%d] %s -> [default] decrypt and dump", seq, qq_get_cmd_desc(cmd)); msg = g_strdup_printf("Unknow command 0x%02X, %s", cmd, qq_get_cmd_desc(cmd)); purple_notify_info(gc, _("QQ Error"), title, msg); g_free(msg); } /* parse the reply to send_im */ static void do_im_ack(guint8 *data, gint data_len, PurpleConnection *gc) { - qq_data *qd; - g_return_if_fail(data != NULL && data_len != 0); - qd = gc->proto_data; - if (data[0] != 0) { purple_debug_warning("QQ", "Failed sent IM\n"); purple_notify_error(gc, _("Error"), _("Unable to send message."), NULL); return; } purple_debug_info("QQ", "OK sent IM\n"); } @@ -375,24 +368,21 @@ static void process_private_msg(guint8 * } break; } } /* Send ACK if the sys message needs an ACK */ static void request_server_ack(PurpleConnection *gc, gchar *funct_str, gchar *from, guint16 seq) { - qq_data *qd; guint8 *raw_data; gint bytes; guint8 bar; g_return_if_fail(funct_str != NULL && from != NULL); - qd = (qq_data *) gc->proto_data; - bar = 0x1e; raw_data = g_newa(guint8, strlen(funct_str) + strlen(from) + 16); bytes = 0; bytes += qq_putdata(raw_data + bytes, (guint8 *)funct_str, strlen(funct_str)); bytes += qq_put8(raw_data + bytes, bar); bytes += qq_putdata(raw_data + bytes, (guint8 *)from, strlen(from)); @@ -563,21 +553,19 @@ static void process_room_cmd_notify(Purp purple_notify_error(gc, _("QQ Qun Command"), prim, msg_utf8); g_free(prim); g_free(msg_utf8); } void qq_update_room(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { - qq_data *qd; gint ret; - g_return_if_fail (gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; + g_return_if_fail (gc != NULL); switch (room_cmd) { case 0: qq_send_room_cmd_mess(gc, QQ_ROOM_CMD_GET_INFO, room_id, NULL, 0, QQ_CMD_CLASS_UPDATE_ROOM, 0); break; case QQ_ROOM_CMD_GET_INFO: ret = qq_request_room_get_buddies(gc, room_id, QQ_CMD_CLASS_UPDATE_ROOM); @@ -594,22 +582,20 @@ void qq_update_room(PurpleConnection *gc /* last command */ default: break; } } void qq_update_all_rooms(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { - qq_data *qd; gboolean is_new_turn = FALSE; guint32 next_id; - g_return_if_fail (gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; + g_return_if_fail(gc != NULL); next_id = qq_room_get_next(gc, room_id); purple_debug_info("QQ", "Update rooms, next id %u, prev id %u\n", next_id, room_id); if (next_id <= 0) { if (room_id > 0) { is_new_turn = TRUE; next_id = qq_room_get_next(gc, 0); @@ -684,21 +670,19 @@ void qq_update_all(PurpleConnection *gc, default: break; } qd->online_last_update = time(NULL); } static void update_all_rooms_online(PurpleConnection *gc, guint8 room_cmd, guint32 room_id) { - qq_data *qd; guint32 next_id; - g_return_if_fail (gc != NULL && gc->proto_data != NULL); - qd = (qq_data *) gc->proto_data; + g_return_if_fail (gc != NULL); next_id = qq_room_get_next_conv(gc, room_id); if (next_id <= 0 && room_id <= 0) { purple_debug_info("QQ", "No room in conversation, no update online buddies\n"); return; } if (next_id <= 0 ) { purple_debug_info("QQ", "finished update rooms' online buddies\n"); diff --git a/purple/libpurple/protocols/qq/qq_trans.c b/purple/libpurple/protocols/qq/qq_trans.c --- a/purple/libpurple/protocols/qq/qq_trans.c +++ b/purple/libpurple/protocols/qq/qq_trans.c @@ -104,21 +104,19 @@ guint32 qq_trans_get_ship(qq_transaction { g_return_val_if_fail(trans != NULL, 0); return trans->ship32; } static qq_transaction *trans_create(PurpleConnection *gc, gint fd, guint16 cmd, guint16 seq, guint8 *data, gint data_len, guint32 update_class, guint32 ship32) { - qq_data *qd; qq_transaction *trans; - g_return_val_if_fail(gc != NULL && gc->proto_data != NULL, NULL); - qd = (qq_data *) gc->proto_data; + g_return_val_if_fail(gc != NULL, NULL); trans = g_new0(qq_transaction, 1); memset(trans, 0, sizeof(qq_transaction)); trans->fd = fd; trans->cmd = cmd; trans->seq = seq; @@ -133,20 +131,21 @@ static qq_transaction *trans_create(Purp trans->update_class = update_class; trans->ship32 = ship32; return trans; } /* Remove a packet with seq from send trans */ static void trans_remove(PurpleConnection *gc, qq_transaction *trans) { - qq_data *qd = (qq_data *)gc->proto_data; + qq_data *qd; - g_return_if_fail(gc != NULL && gc->proto_data != NULL); + g_return_if_fail(gc != NULL); qd = (qq_data *) gc->proto_data; + g_return_if_fail(qd != NULL); g_return_if_fail(trans != NULL); #if 0 purple_debug_info("QQ_TRANS", "Remove [%s%05d] retry %d rcved %d scan %d %s\n", (trans->flag & QQ_TRANS_IS_SERVER) ? "SRV-" : "", trans->seq, trans->send_retries, trans->rcved_times, trans->scan_times, diff --git a/purple/libpurple/protocols/qq/send_file.c b/purple/libpurple/protocols/qq/send_file.c --- a/purple/libpurple/protocols/qq/send_file.c +++ b/purple/libpurple/protocols/qq/send_file.c @@ -636,20 +636,18 @@ _qq_xfer_init (PurpleXfer * xfer) g_free(base_filename); } /* cancel the transfer of receiving files */ static void _qq_xfer_cancel(PurpleXfer *xfer) { PurpleConnection *gc; PurpleAccount *account; - guint16 *seq; g_return_if_fail (xfer != NULL); - seq = (guint16 *) xfer->data; account = purple_xfer_get_account(xfer); gc = purple_account_get_connection(account); switch (purple_xfer_get_status(xfer)) { case PURPLE_XFER_STATUS_CANCEL_LOCAL: _qq_send_packet_file_cancel(gc, purple_name_to_uid(xfer->who)); break; case PURPLE_XFER_STATUS_CANCEL_REMOTE: @@ -669,20 +667,18 @@ static void _qq_xfer_cancel(PurpleXfer * } } /* init the transfer of receiving files */ static void _qq_xfer_recv_init(PurpleXfer *xfer) { PurpleConnection *gc; PurpleAccount *account; - ft_info *info; - g_return_if_fail (xfer != NULL && xfer->data != NULL); - info = (ft_info *) xfer->data; + g_return_if_fail(xfer != NULL); account = purple_xfer_get_account(xfer); gc = purple_account_get_connection(account); _qq_send_packet_file_accept(gc, purple_name_to_uid(xfer->who)); } /* process reject im for file transfer request */ void qq_process_recv_file_reject (guint8 *data, gint data_len, @@ -751,26 +747,26 @@ void qq_process_recv_file_accept(guint8 qq_data *qd; gint bytes; ft_info *info; PurpleXfer *xfer; g_return_if_fail (data != NULL && data_len != 0); qd = (qq_data *) gc->proto_data; xfer = qd->xfer; - info = (ft_info *) qd->xfer->data; + info = (ft_info *) xfer->data; if (data_len <= 30 + QQ_CONN_INFO_LEN) { purple_debug_warning("QQ", "Received file reject message is empty\n"); return; } bytes = 18 + 12; /* skip 30 bytes */ qq_get_conn_info(info, data + bytes); - _qq_xfer_init_socket(qd->xfer); + _qq_xfer_init_socket(xfer); _qq_xfer_init_udp_channel(info); _qq_send_packet_file_notifyip(gc, sender_uid); } /* process request from buddy's im for file transfer request */ void qq_process_recv_file_request(guint8 *data, gint data_len, guint32 sender_uid, PurpleConnection * gc) { diff --git a/purple/libpurple/protocols/yahoo/libymsg.c b/purple/libpurple/protocols/yahoo/libymsg.c --- a/purple/libpurple/protocols/yahoo/libymsg.c +++ b/purple/libpurple/protocols/yahoo/libymsg.c @@ -316,17 +316,17 @@ static void yahoo_process_status(PurpleC case 197: /* Avatars */ { guchar *decoded; char *tmp; gsize len; if (pair->value) { decoded = purple_base64_decode(pair->value, &len); - if (len) { + if (decoded && len > 0) { tmp = purple_str_binary_to_ascii(decoded, len); purple_debug_info("yahoo", "Got key 197, value = %s\n", tmp); g_free(tmp); } g_free(decoded); } break; } @@ -501,18 +501,16 @@ static void yahoo_process_list_15(Purple PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = gc->proto_data; GHashTable *ht; char *norm_bud = NULL; char *temp = NULL; YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */ /* But what if you had no friends? */ - PurpleBuddy *b; - PurpleGroup *g; YahooFederation fed = YAHOO_FEDERATION_NONE; int stealth = 0; ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free); while (l) { struct yahoo_pair *pair = l->data; l = l->next; @@ -548,17 +546,19 @@ static void yahoo_process_list_15(Purple break; case YAHOO_FEDERATION_NONE: norm_bud = g_strdup(temp); break; } if (yd->current_list15_grp) { /* This buddy is in a group */ f = yahoo_friend_find_or_new(gc, norm_bud); - if (!(b = purple_find_buddy(account, norm_bud))) { + if (!purple_find_buddy(account, norm_bud)) { + PurpleBuddy *b; + PurpleGroup *g; if (!(g = purple_find_group(yd->current_list15_grp))) { g = purple_group_new(yd->current_list15_grp); purple_blist_add_group(g, NULL); } b = purple_buddy_new(account, norm_bud, NULL); purple_blist_add_buddy(b, NULL, g, NULL); } yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp); @@ -635,18 +635,16 @@ static void yahoo_process_list_15(Purple g_free(temp); } static void yahoo_process_list(PurpleConnection *gc, struct yahoo_packet *pkt) { GSList *l = pkt->hash; gboolean export = FALSE; gboolean got_serv_list = FALSE; - PurpleBuddy *b; - PurpleGroup *g; YahooFriend *f = NULL; PurpleAccount *account = purple_connection_get_account(gc); YahooData *yd = gc->proto_data; GHashTable *ht; char **lines; char **split; char **buddies; @@ -704,17 +702,19 @@ static void yahoo_process_list(PurpleCon continue; } grp = yahoo_string_decode(gc, split[0], FALSE); buddies = g_strsplit(split[1], ",", -1); for (bud = buddies; bud && *bud; bud++) { norm_bud = g_strdup(purple_normalize(account, *bud)); f = yahoo_friend_find_or_new(gc, norm_bud); - if (!(b = purple_find_buddy(account, norm_bud))) { + if (!purple_find_buddy(account, norm_bud)) { + PurpleBuddy *b; + PurpleGroup *g; if (!(g = purple_find_group(grp))) { g = purple_group_new(grp); purple_blist_add_group(g, NULL); } b = purple_buddy_new(account, norm_bud, NULL); purple_blist_add_buddy(b, NULL, g, NULL); export = TRUE; } @@ -2865,25 +2865,27 @@ static void yahoo_process_p2p(PurpleConn } l = l->next; } if (base64) { guint32 ip; YahooFriend *f; - char *host_ip; + char *host_ip, *tmp; struct yahoo_p2p_data *p2p_data; decoded = purple_base64_decode(base64, &len); - if (len) { - char *tmp = purple_str_binary_to_ascii(decoded, len); - purple_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp); - g_free(tmp); + if (decoded == NULL) { + purple_debug_info("yahoo","p2p: Unable to decode base64 IP (%s) \n", base64); + return; } + tmp = purple_str_binary_to_ascii(decoded, len); + purple_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp); + g_free(tmp); ip = strtol((gchar *)decoded, NULL, 10); g_free(decoded); host_ip = g_strdup_printf("%u.%u.%u.%u", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff); f = yahoo_friend_find(gc, who); if (f) yahoo_friend_set_ip(f, host_ip); @@ -3808,23 +3810,22 @@ const char *yahoo_list_icon(PurpleAccoun { return "yahoo"; } const char *yahoo_list_emblem(PurpleBuddy *b) { PurpleAccount *account; PurpleConnection *gc; - YahooData *yd; YahooFriend *f; PurplePresence *presence; if (!b || !(account = purple_buddy_get_account(b)) || !(gc = purple_account_get_connection(account)) || - !(yd = gc->proto_data)) + !gc->proto_data) return NULL; f = yahoo_friend_find(gc, purple_buddy_get_name(b)); if (!f) { return "not-authorized"; } presence = purple_buddy_get_presence(b); @@ -3909,28 +3910,26 @@ static void yahoo_presence_settings(Purp yahoo_friend_update_presence(gc, purple_buddy_get_name(buddy), presence_val); } static void yahoo_game(PurpleBlistNode *node, gpointer data) { PurpleBuddy *buddy; PurpleConnection *gc; - YahooData *yd; const char *game; char *game2; char *t; char url[256]; YahooFriend *f; g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node)); buddy = (PurpleBuddy *) node; gc = purple_account_get_connection(purple_buddy_get_account(buddy)); - yd = (YahooData *) gc->proto_data; f = yahoo_friend_find(gc, purple_buddy_get_name(buddy)); if (!f) return; game = yahoo_friend_get_game(f); if (!game) return; @@ -4944,29 +4943,27 @@ void yahoo_keepalive(PurpleConnection *g } void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *g) { YahooData *yd = (YahooData *)gc->proto_data; struct yahoo_packet *pkt; const char *group = NULL; char *group2; - YahooFriend *f; const char *bname; const char *fed_bname; YahooFederation fed = YAHOO_FEDERATION_NONE; if (!yd->logged_in) return; fed_bname = bname = purple_buddy_get_name(buddy); if (!purple_privacy_check(purple_connection_get_account(gc), bname)) return; - f = yahoo_friend_find(gc, bname); fed = yahoo_get_federation_from_name(bname); if (fed != YAHOO_FEDERATION_NONE) fed_bname += 4; g = purple_buddy_get_group(buddy); if (g) group = purple_group_get_name(g); else @@ -5219,25 +5216,21 @@ yahoopurple_cmd_buzz(PurpleConversation } PurpleCmdRet yahoopurple_cmd_chat_join(PurpleConversation *conv, const char *cmd, char **args, char **error, void *data) { GHashTable *comp; PurpleConnection *gc; - YahooData *yd; - int id; if (!args || !args[0]) return PURPLE_CMD_RET_FAILED; gc = purple_conversation_get_gc(conv); - yd = gc->proto_data; - id = yd->conf_id; purple_debug_info("yahoo", "Trying to join %s \n", args[0]); comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); g_hash_table_replace(comp, g_strdup("room"), g_ascii_strdown(args[0], -1)); g_hash_table_replace(comp, g_strdup("type"), g_strdup("Chat")); yahoo_c_join(gc, comp); diff --git a/purple/libpurple/protocols/yahoo/libymsg.h b/purple/libpurple/protocols/yahoo/libymsg.h --- a/purple/libpurple/protocols/yahoo/libymsg.h +++ b/purple/libpurple/protocols/yahoo/libymsg.h @@ -59,17 +59,17 @@ #define YAHOOJP_XFER_RELAY_PORT 80 #define YAHOOJP_ROOMLIST_URL "http://insider.msg.yahoo.co.jp/ycontent/" #define YAHOOJP_ROOMLIST_LOCALE "ja" #define YAHOO_AUDIBLE_URL "http://us.dl1.yimg.com/download.yahoo.com/dl/aud" #define WEBMESSENGER_URL "http://login.yahoo.com/config/login?.src=pg" -#define YAHOO_SMS_CARRIER_URL "http://lookup.msg.vip.mud.yahoo.com" +#define YAHOO_SMS_CARRIER_URL "http://validate.msg.yahoo.com" #define YAHOO_USERINFO_URL "http://address.yahoo.com/yab/us?v=XM&sync=1&tags=short&useutf8=1&noclear=1&legenc=codepage-1252" #define YAHOOJP_USERINFO_URL "http://address.yahoo.co.jp/yab/jp?v=XM&sync=1&tags=short&useutf8=1&noclear=1&legenc=codepage-1252" #define YAHOO_PICURL_SETTING "picture_url" #define YAHOO_PICCKSUM_SETTING "picture_checksum" #define YAHOO_PICEXPIRE_SETTING "picture_expire" diff --git a/purple/libpurple/protocols/yahoo/util.c b/purple/libpurple/protocols/yahoo/util.c --- a/purple/libpurple/protocols/yahoo/util.c +++ b/purple/libpurple/protocols/yahoo/util.c @@ -38,17 +38,17 @@ yahoo_account_use_http_proxy(PurpleConne PurpleAccount *account = purple_connection_get_account(pc); PurpleProxyInfo *ppi = NULL; PurpleProxyType type = PURPLE_PROXY_NONE; gboolean proxy_ssl = purple_account_get_bool(account, "proxy_ssl", FALSE); if(proxy_ssl) ppi = purple_proxy_get_setup(account); else - ppi = purple_global_proxy_get_info(); + ppi = purple_proxy_get_setup(NULL); type = purple_proxy_info_get_type(ppi); return (type == PURPLE_PROXY_HTTP || type == PURPLE_PROXY_USE_ENVVAR); } /* * Returns cookies formatted as a null terminated string for the given connection. diff --git a/purple/libpurple/protocols/yahoo/yahoo_aliases.c b/purple/libpurple/protocols/yahoo/yahoo_aliases.c --- a/purple/libpurple/protocols/yahoo/yahoo_aliases.c +++ b/purple/libpurple/protocols/yahoo/yahoo_aliases.c @@ -140,17 +140,17 @@ yahoo_fetch_aliases_cb(PurpleUtilFetchUr if (f != NULL && b != NULL) { const char *buddy_alias = purple_buddy_get_alias(b); yahoo_friend_set_alias_id(f, id); /* Finally, if we received an alias, we better update the buddy list */ if (alias != NULL) { serv_got_alias(gc, yid, alias); purple_debug_info("yahoo", "Fetched alias '%s' (%s)\n", alias, id); - } else if (buddy_alias != NULL && strcmp(buddy_alias, "") != 0) { + } else if (buddy_alias && *buddy_alias && !g_str_equal(buddy_alias, yid)) { /* Or if we have an alias that Yahoo doesn't, send it up */ yahoo_update_alias(gc, yid, buddy_alias); purple_debug_info("yahoo", "Sent updated alias '%s'\n", buddy_alias); } } if (f != NULL) ypd = &f->ypd; diff --git a/purple/libpurple/protocols/yahoo/yahoo_doodle.c b/purple/libpurple/protocols/yahoo/yahoo_doodle.c --- a/purple/libpurple/protocols/yahoo/yahoo_doodle.c +++ b/purple/libpurple/protocols/yahoo/yahoo_doodle.c @@ -367,17 +367,17 @@ void yahoo_doodle_command_got_shutdown(P */ wb = purple_whiteboard_get_session(account, from); if(wb == NULL) return; /* TODO Ask if user wants to save picture before the session is closed */ - wb->state = DOODLE_STATE_CANCELED; + wb->state = DOODLE_STATE_CANCELLED; purple_whiteboard_destroy(wb); } static void yahoo_doodle_command_send_generic(const char *type, PurpleConnection *gc, const char *to, const char *message, int command, @@ -455,17 +455,17 @@ void yahoo_doodle_start(PurpleWhiteboard void yahoo_doodle_end(PurpleWhiteboard *wb) { PurpleConnection *gc = purple_account_get_connection(wb->account); doodle_session *ds = wb->proto_data; /* g_debug_debug("yahoo", "doodle: yahoo_doodle_end()\n"); */ - if (gc && wb->state != DOODLE_STATE_CANCELED) + if (gc && wb->state != DOODLE_STATE_CANCELLED) yahoo_doodle_command_send_shutdown(gc, wb->who); g_free(ds->imv_key); g_free(wb->proto_data); } void yahoo_doodle_get_dimensions(const PurpleWhiteboard *wb, int *width, int *height) { diff --git a/purple/libpurple/protocols/yahoo/yahoo_doodle.h b/purple/libpurple/protocols/yahoo/yahoo_doodle.h --- a/purple/libpurple/protocols/yahoo/yahoo_doodle.h +++ b/purple/libpurple/protocols/yahoo/yahoo_doodle.h @@ -51,17 +51,17 @@ #define DOODLE_EXTRA_TICTACTOE "\"3\"" #define DOODLE_EXTRA_DOTS "\"2\"" /* Doodle session states */ /* TODO: Should be an enum. */ #define DOODLE_STATE_REQUESTING 0 #define DOODLE_STATE_REQUESTED 1 #define DOODLE_STATE_ESTABLISHED 2 -#define DOODLE_STATE_CANCELED 3 +#define DOODLE_STATE_CANCELLED 3 /* Doodle canvas dimensions */ #define DOODLE_CANVAS_WIDTH 368 #define DOODLE_CANVAS_HEIGHT 256 /* Doodle color codes (most likely RGB) */ /* TODO: Should be an enum and sorted by color name. */ #define DOODLE_COLOR_RED 13369344 diff --git a/purple/libpurple/protocols/yahoo/yahoo_filexfer.c b/purple/libpurple/protocols/yahoo/yahoo_filexfer.c --- a/purple/libpurple/protocols/yahoo/yahoo_filexfer.c +++ b/purple/libpurple/protocols/yahoo/yahoo_filexfer.c @@ -1230,35 +1230,42 @@ static void yahoo_xfer_send_cb_15(gpoint } } static void yahoo_xfer_connected_15(gpointer data, gint source, const gchar *error_message) { PurpleXfer *xfer; struct yahoo_xfer_data *xd; PurpleAccount *account; - YahooData* yd; + PurpleConnection *gc; if (!(xfer = data)) return; if (!(xd = xfer->data)) return; - yd = xd->gc->proto_data; - account = purple_connection_get_account(xd->gc); + gc = xd->gc; + account = purple_connection_get_account(gc); if ((source < 0) || (xd->path == NULL) || (xd->host == NULL)) { purple_xfer_error(PURPLE_XFER_RECEIVE, purple_xfer_get_account(xfer), xfer->who, _("Unable to connect.")); purple_xfer_cancel_remote(xfer); return; } /* The first time we get here, assemble the tx buffer */ if (xd->txbuflen == 0) { gchar* cookies; - cookies = yahoo_get_cookies(xd->gc); + YahooData *yd = gc->proto_data; + + /* cookies = yahoo_get_cookies(gc); + * This doesn't seem to be working. The function is returning NULL, which yahoo servers don't like + * For now let us not use this function */ + + cookies = g_strdup_printf("Y=%s; T=%s", yd->cookie_y, yd->cookie_t); + if(purple_xfer_get_type(xfer) == PURPLE_XFER_SEND && xd->status_15 == ACCEPTED) { if(xd->info_val_249 == 2) { /* sending file via p2p, we are connected as client */ xd->txbuf = g_strdup_printf("POST /%s HTTP/1.1\r\n" "User-Agent: " YAHOO_CLIENT_USERAGENT "\r\n" "Host: %s\r\n" diff --git a/purple/libpurple/protocols/yahoo/yahoochat.c b/purple/libpurple/protocols/yahoo/yahoochat.c --- a/purple/libpurple/protocols/yahoo/yahoochat.c +++ b/purple/libpurple/protocols/yahoo/yahoochat.c @@ -116,29 +116,28 @@ void yahoo_process_conference_invite(Pur { PurpleAccount *account; GSList *l; char *room = NULL; char *who = NULL; char *msg = NULL; GString *members = NULL; GHashTable *components; - PurpleConversation *c = NULL; if ( (pkt->status == 2) || (pkt->status == 11) ) return; /* Status is 11 when we are being notified about invitation being sent to someone else */ account = purple_connection_get_account(gc); for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 57) { room = yahoo_string_decode(gc, pair->value, FALSE); - if((c = yahoo_find_conference(gc, room))) + if (yahoo_find_conference(gc, room) != NULL) { /* Looks like we got invited to an already open conference. */ /* Laters: Should we accept this conference rather than ignoring the invitation ? */ purple_debug_info("yahoo","Ignoring invitation for an already existing chat, room:%s\n",room); g_free(room); return; } } @@ -613,19 +612,16 @@ void yahoo_process_chat_join(PurpleConne g_free(topic); } void yahoo_process_chat_exit(PurpleConnection *gc, struct yahoo_packet *pkt) { char *who = NULL; char *room = NULL; GSList *l; - YahooData *yd; - - yd = gc->proto_data; for (l = pkt->hash; l; l = l->next) { struct yahoo_pair *pair = l->data; if (pair->key == 104) { g_free(room); room = yahoo_string_decode(gc, pair->value, TRUE); } @@ -634,18 +630,17 @@ void yahoo_process_chat_exit(PurpleConne } if (who && room) { PurpleConversation *c = purple_find_chat(gc, YAHOO_CHAT_ID); if (c && !purple_utf8_strcasecmp(purple_conversation_get_name(c), room)) purple_conv_chat_remove_user(PURPLE_CONV_CHAT(c), who, NULL); } - if (room) - g_free(room); + g_free(room); } void yahoo_process_chat_message(PurpleConnection *gc, struct yahoo_packet *pkt) { char *room = NULL, *who = NULL, *msg = NULL, *msg2; int msgtype = 1, utf8 = 1; /* default to utf8 */ PurpleConversation *c = NULL; GSList *l; @@ -875,17 +870,16 @@ static void yahoo_conf_invite(PurpleConn /* * Functions dealing with chats */ static void yahoo_chat_leave(PurpleConnection *gc, const char *room, const char *dn, gboolean logout) { YahooData *yd = gc->proto_data; struct yahoo_packet *pkt; - PurpleConversation *c; char *eroom; gboolean utf8 = 1; if (yd->wm) { g_return_if_fail(yd->ycht != NULL); ycht_chat_leave(yd->ycht, room, logout); @@ -900,17 +894,17 @@ static void yahoo_chat_leave(PurpleConne yahoo_packet_send_and_free(pkt, yd); yd->in_chat = 0; if (yd->chat_name) { g_free(yd->chat_name); yd->chat_name = NULL; } - if ((c = purple_find_chat(gc, YAHOO_CHAT_ID))) + if (purple_find_chat(gc, YAHOO_CHAT_ID) != NULL) serv_got_chat_left(gc, YAHOO_CHAT_ID); if (!logout) return; pkt = yahoo_packet_new(YAHOO_SERVICE_CHATLOGOUT, YAHOO_STATUS_AVAILABLE, yd->session_id); yahoo_packet_hash_str(pkt, 1, dn); diff --git a/purple/libpurple/proxy.c b/purple/libpurple/proxy.c --- a/purple/libpurple/proxy.c +++ b/purple/libpurple/proxy.c @@ -1020,17 +1020,17 @@ http_canread(gpointer data, gint source, "Proxy-Authorization: NTLM %s\r\n" "Proxy-Connection: Keep-Alive\r\n\r\n", connect_data->host, connect_data->port, connect_data->host, connect_data->port, response); g_free(response); - } else if((header = g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic"))) { + } else if (g_strrstr((const char *)connect_data->read_buffer, "Proxy-Authenticate: Basic") != NULL) { gchar *t1, *t2; const char *username, *password; username = purple_proxy_info_get_username(connect_data->gpi); password = purple_proxy_info_get_password(connect_data->gpi); t1 = g_strdup_printf("%s:%s", username ? username : "", diff --git a/purple/libpurple/request.c b/purple/libpurple/request.c --- a/purple/libpurple/request.c +++ b/purple/libpurple/request.c @@ -1405,19 +1405,22 @@ purple_request_action_with_icon_varg(voi info->ui_handle = ops->request_action_with_icon(title, primary, secondary, default_action, account, who, conv, icon_data, icon_size, user_data, action_count, actions); handles = g_list_append(handles, info); return info->ui_handle; + } else { + /* Fall back on the non-icon request if the UI doesn't support icon + requests */ + return purple_request_action_varg(handle, title, primary, secondary, + default_action, account, who, conv, user_data, action_count, actions); } - else - PURPLE_REQUEST_NO_OPS("fields", title, primary); return NULL; } void * purple_request_fields(void *handle, const char *title, const char *primary, const char *secondary, PurpleRequestFields *fields, diff --git a/purple/libpurple/stun.c b/purple/libpurple/stun.c --- a/purple/libpurple/stun.c +++ b/purple/libpurple/stun.c @@ -100,21 +100,21 @@ static void close_stun_conn(struct stun_ if (sc->fd) close(sc->fd); g_free(sc); } static void do_callbacks(void) { - while(callbacks) { + while (callbacks) { StunCallback cb = callbacks->data; - if(cb) + if (cb) cb(&nattype); - callbacks = g_slist_remove(callbacks, cb); + callbacks = g_slist_delete_link(callbacks, callbacks); } } static gboolean timeoutfunc(gpointer data) { struct stun_conn *sc = data; if(sc->retry >= 2) { purple_debug_warning("stun", "request timed out, giving up.\n"); if(sc->test == 2) @@ -275,17 +275,16 @@ static void reply_cb(gpointer data, gint } } static void hbn_listen_cb(int fd, gpointer data) { GSList *hosts = data; struct stun_conn *sc; static struct stun_header hdr_data; - int ret; if(fd < 0) { nattype.status = PURPLE_STUN_STATUS_UNKNOWN; nattype.lookup_time = time(NULL); do_callbacks(); return; } @@ -293,25 +292,24 @@ static void hbn_listen_cb(int fd, gpoint sc->fd = fd; sc->addr.sin_family = AF_INET; sc->addr.sin_port = htons(purple_network_get_port_from_fd(fd)); sc->addr.sin_addr.s_addr = INADDR_ANY; sc->incb = purple_input_add(fd, PURPLE_INPUT_READ, reply_cb, sc); - ret = GPOINTER_TO_INT(hosts->data); - hosts = g_slist_remove(hosts, hosts->data); + hosts = g_slist_delete_link(hosts, hosts); memcpy(&(sc->addr), hosts->data, sizeof(struct sockaddr_in)); g_free(hosts->data); - hosts = g_slist_remove(hosts, hosts->data); - while(hosts) { - hosts = g_slist_remove(hosts, hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + while (hosts) { + hosts = g_slist_delete_link(hosts, hosts); g_free(hosts->data); - hosts = g_slist_remove(hosts, hosts->data); + hosts = g_slist_delete_link(hosts, hosts); } hdr_data.type = htons(MSGTYPE_BINDINGREQUEST); hdr_data.len = 0; hdr_data.transid[0] = rand(); hdr_data.transid[1] = ntohl(((int)'g' << 24) + ((int)'a' << 16) + ((int)'i' << 8) + (int)'m'); hdr_data.transid[2] = rand(); hdr_data.transid[3] = rand(); @@ -336,20 +334,20 @@ static void hbn_cb(GSList *hosts, gpoint if(!hosts || !hosts->data) { nattype.status = PURPLE_STUN_STATUS_UNDISCOVERED; nattype.lookup_time = time(NULL); do_callbacks(); return; } if (!purple_network_listen_range(12108, 12208, SOCK_DGRAM, hbn_listen_cb, hosts)) { - while(hosts) { - hosts = g_slist_remove(hosts, hosts->data); + while (hosts) { + hosts = g_slist_delete_link(hosts, hosts); g_free(hosts->data); - hosts = g_slist_remove(hosts, hosts->data); + hosts = g_slist_delete_link(hosts, hosts); } nattype.status = PURPLE_STUN_STATUS_UNKNOWN; nattype.lookup_time = time(NULL); do_callbacks(); return; } diff --git a/purple/libpurple/upnp.c b/purple/libpurple/upnp.c --- a/purple/libpurple/upnp.c +++ b/purple/libpurple/upnp.c @@ -157,19 +157,19 @@ static void lookup_public_ip(void); static void lookup_internal_ip(void); static void fire_discovery_callbacks(gboolean success) { while(discovery_callbacks) { gpointer data; PurpleUPnPCallback cb = discovery_callbacks->data; - discovery_callbacks = g_slist_remove(discovery_callbacks, cb); + discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks); data = discovery_callbacks->data; - discovery_callbacks = g_slist_remove(discovery_callbacks, data); + discovery_callbacks = g_slist_delete_link(discovery_callbacks, discovery_callbacks); cb(success, data); } } static gboolean purple_upnp_compare_device(const xmlnode* device, const gchar* deviceType) { xmlnode* deviceTypeNode = xmlnode_get_child(device, "deviceType"); diff --git a/purple/libpurple/util.c b/purple/libpurple/util.c --- a/purple/libpurple/util.c +++ b/purple/libpurple/util.c @@ -2808,16 +2808,20 @@ purple_program_is_valid(const char *prog if (argv == NULL) { return FALSE; } progname = g_find_program_in_path(argv[0]); is_valid = (progname != NULL); + if(purple_debug_is_verbose()) + purple_debug_info("program_is_valid", "Tested program %s. %s.\n", program, + is_valid ? "Valid" : "Invalid"); + g_strfreev(argv); g_free(progname); return is_valid; } #endif gboolean @@ -3111,17 +3115,17 @@ purple_str_strip_char(char *text, char t int i, j; g_return_if_fail(text != NULL); for (i = 0, j = 0; text[i]; i++) if (text[i] != thechar) text[j++] = text[i]; - text[j++] = '\0'; + text[j] = '\0'; } void purple_util_chrreplace(char *string, char delimiter, char replacement) { int i = 0; @@ -3790,75 +3794,72 @@ url_fetch_recv_cb(gpointer url_data, gin gfud->len += len; memcpy(data_cursor, buf, len); gfud->webdata[gfud->len] = '\0'; if(!gfud->got_headers) { - char *tmp; + char *end_of_headers; /* See if we've reached the end of the headers yet */ - if((tmp = strstr(gfud->webdata, "\r\n\r\n"))) { - char * new_data; - guint header_len = (tmp + 4 - gfud->webdata); + end_of_headers = strstr(gfud->webdata, "\r\n\r\n"); + if (end_of_headers) { + char *new_data; + guint header_len = (end_of_headers + 4 - gfud->webdata); size_t content_len; purple_debug_misc("util", "Response headers: '%.*s'\n", header_len, gfud->webdata); /* See if we can find a redirect. */ if(parse_redirect(gfud->webdata, header_len, gfud)) return; gfud->got_headers = TRUE; /* No redirect. See if we can find a content length. */ content_len = parse_content_len(gfud->webdata, header_len); gfud->chunked = content_is_chunked(gfud->webdata, header_len); - if(content_len == 0) { + if (content_len == 0) { /* We'll stick with an initial 8192 */ content_len = 8192; } else { gfud->has_explicit_data_len = TRUE; } /* If we're returning the headers too, we don't need to clean them out */ - if(gfud->include_headers) { + if (gfud->include_headers) { gfud->data_len = content_len + header_len; gfud->webdata = g_realloc(gfud->webdata, gfud->data_len); } else { - size_t body_len = 0; - - if(gfud->len > (header_len + 1)) - body_len = (gfud->len - header_len); + size_t body_len = gfud->len - header_len; content_len = MAX(content_len, body_len); new_data = g_try_malloc(content_len); - if(new_data == NULL) { + if (new_data == NULL) { purple_debug_error("util", "Failed to allocate %" G_GSIZE_FORMAT " bytes: %s\n", content_len, g_strerror(errno)); purple_util_fetch_url_error(gfud, _("Unable to allocate enough memory to hold " "the contents from %s. The web server may " "be trying something malicious."), gfud->website.address); return; } /* We may have read part of the body when reading the headers, don't lose it */ - if(body_len > 0) { - tmp += 4; - memcpy(new_data, tmp, body_len); + if (body_len > 0) { + memcpy(new_data, end_of_headers + 4, body_len); } /* Out with the old... */ g_free(gfud->webdata); /* In with the new. */ gfud->len = body_len; gfud->data_len = content_len; diff --git a/purple/libpurple/win32/libc_interface.c b/purple/libpurple/win32/libc_interface.c --- a/purple/libpurple/win32/libc_interface.c +++ b/purple/libpurple/win32/libc_interface.c @@ -311,36 +311,36 @@ struct hostent* wpurple_gethostbyname(co return hp; } /* string.h */ char* wpurple_strerror(int errornum) { if (errornum > WSABASEERR) { switch(errornum) { case WSAECONNABORTED: /* 10053 */ - g_snprintf(errbuf, sizeof(errbuf), _("Connection interrupted by other software on your computer.")); + g_snprintf(errbuf, sizeof(errbuf), "%s", _("Connection interrupted by other software on your computer.")); break; case WSAECONNRESET: /* 10054 */ - g_snprintf(errbuf, sizeof(errbuf), _("Remote host closed connection.")); + g_snprintf(errbuf, sizeof(errbuf), "%s", _("Remote host closed connection.")); break; case WSAETIMEDOUT: /* 10060 */ - g_snprintf(errbuf, sizeof(errbuf), _("Connection timed out.")); + g_snprintf(errbuf, sizeof(errbuf), "%s", _("Connection timed out.")); break; case WSAECONNREFUSED: /* 10061 */ - g_snprintf(errbuf, sizeof(errbuf), _("Connection refused.")); + g_snprintf(errbuf, sizeof(errbuf), "%s", _("Connection refused.")); break; case WSAEADDRINUSE: /* 10048 */ - g_snprintf(errbuf, sizeof(errbuf), _("Address already in use.")); + g_snprintf(errbuf, sizeof(errbuf), "%s", _("Address already in use.")); break; default: g_snprintf(errbuf, sizeof(errbuf), "Windows socket error #%d", errornum); } } else { const char *tmp = g_strerror(errornum); - g_snprintf(errbuf, sizeof(errbuf), tmp); + g_snprintf(errbuf, sizeof(errbuf), "%s", tmp); } return errbuf; } /* unistd.h */ /* * We need to figure out whether fd is a file or socket handle. diff --git a/purple/libpurple/win32/win32dep.c b/purple/libpurple/win32/win32dep.c --- a/purple/libpurple/win32/win32dep.c +++ b/purple/libpurple/win32/win32dep.c @@ -33,17 +33,17 @@ #include "notify.h" /* * LOCALS */ static char *app_data_dir = NULL, *install_dir = NULL, *lib_dir = NULL, *locale_dir = NULL; -static HINSTANCE libpurpledll_hInstance = 0; +static HINSTANCE libpurpledll_hInstance = NULL; /* * PUBLIC CODE */ /* Escape windows dir separators. This is needed when paths are saved, and on being read back have their '\' chars used as an escape char. Returns an allocated string which needs to be freed. @@ -75,30 +75,33 @@ char *wpurple_escape_dirsep(const char * /* Determine whether the specified dll contains the specified procedure. If so, load it (if not already loaded). */ FARPROC wpurple_find_and_loadproc(const char *dllname, const char *procedure) { HMODULE hmod; BOOL did_load = FALSE; FARPROC proc = 0; -#ifdef WINCE - return NULL; /* FIXME: ansi/wide mismatch, unlikely to work */ -#endif + wchar_t *wc_dllname = g_utf8_to_utf16(dllname, -1, NULL, NULL, NULL); - if(!(hmod = GetModuleHandle(dllname))) { + if(!(hmod = GetModuleHandleW(wc_dllname))) { purple_debug_warning("wpurple", "%s not already loaded; loading it...\n", dllname); - if(!(hmod = LoadLibrary(dllname))) { - purple_debug_error("wpurple", "Could not load: %s\n", dllname); + if(!(hmod = LoadLibraryW(wc_dllname))) { + purple_debug_error("wpurple", "Could not load: %s (%s)\n", dllname, + g_win32_error_message(GetLastError())); + g_free(wc_dllname); return NULL; } else did_load = TRUE; } + g_free(wc_dllname); + wc_dllname = NULL; + if((proc = GetProcAddress(hmod, procedure))) { purple_debug_info("wpurple", "This version of %s contains %s\n", dllname, procedure); return proc; } else { purple_debug_warning("wpurple", "Function %s not found in dll %s\n", procedure, dllname); @@ -127,17 +130,17 @@ gchar *wpurple_get_special_folder(int fo } const char *wpurple_install_dir(void) { static gboolean initialized = FALSE; if (!initialized) { char *tmp = NULL; wchar_t winstall_dir[MAXPATHLEN]; - if (GetModuleFileNameW(NULL, winstall_dir, + if (GetModuleFileNameW(libpurpledll_hInstance, winstall_dir, MAXPATHLEN) > 0) { tmp = g_utf16_to_utf8(winstall_dir, -1, NULL, NULL, NULL); } if (tmp == NULL) { tmp = g_win32_error_message(GetLastError()); purple_debug_error("wpurple", diff --git a/purple/locales/en-US/jabber.properties b/purple/locales/en-US/jabber.properties --- a/purple/locales/en-US/jabber.properties +++ b/purple/locales/en-US/jabber.properties @@ -296,18 +296,20 @@ msgLtUserGtLtMessageGt=msg <user> pingLtJidGtPingAUser=ping <jid>: Ping a user/component/server. buzzBuzzAUserToGetTheir=buzz: Buzz a user to get their attention moodSetCurrentUserMood=mood: Set current user mood extendedAway=Extended Away xmppProtocolPlugin=XMPP Protocol Plugin # Translators: 'domain' is used here in the context of Internet domains, e.g. pidgin.im domain=Domain -requireSslTls=Require SSL/TLS -forceOldPort5223Ssl=Force old (port 5223) SSL +requireEncryption=Require encryption +useEncryptionIfAvailable=Use encryption if available +useOldStyleSsl=Use old-style SSL +connectionSecurity=Connection security allowPlaintextAuthOverUnencryptedStreams=Allow plaintext auth over unencrypted streams connectPort=Connect port connectServer=Connect server fileTransferProxies=File transfer proxies boshUrl=BOSH URL showCustomSmileys=Show Custom Smileys hasLeftTheConversation=%s has left the conversation. messageFrom=Message from %s @@ -326,19 +328,16 @@ errorInChat=Error in chat %s createNewRoom=Create New Room youAreCreatingANewRoomWould=You are creating a new room. Would you like to configure it, or accept the default settings? _configureRoom=_Configure Room _acceptDefaults=_Accept Defaults noReason=No reason youHaveBeenKicked=You have been kicked: (%s) kicked=Kicked (%s) unknownErrorInPresence=Unknown Error in presence -anErrorOccurredOnTheInBand=An error occurred on the in-band bytestream transfer -transferWasClosed=Transfer was closed. -failedToOpenInBandBytestream=Failed to open in-band bytestream unableToSendFileToUserDoes=Unable to send file to %s, user does not support file transfers fileSendFailed=File Send Failed unableToSendFileToInvalidJid=Unable to send file to %s, invalid JID unableToSendFileToUserIs=Unable to send file to %s, user is not online unableToSendFileToNotSubscribed=Unable to send file to %s, not subscribed to user presence pleaseSelectTheResourceOfToWhich=Please select the resource of %s to which you would like to send a file sendFile=Send File afraid=Afraid diff --git a/purple/locales/en-US/msn.properties b/purple/locales/en-US/msn.properties --- a/purple/locales/en-US/msn.properties +++ b/purple/locales/en-US/msn.properties @@ -22,21 +22,16 @@ authenticating=Authenticating nowListening=Now Listening tuneArtist=Tune Artist tuneTitle=Tune Title tuneAlbum=Tune Album connectionTimeout=Connection Timeout unableToAdd27fe1719=Unable to add "%s". buddyAddError=Buddy Add error theUsernameSpecifiedDoesNotExist=The username specified does not exist. -buddyListSynchronizationIssueIn=Buddy list synchronization issue in %s (%s) -onTheLocalListIsInsideThe=%s on the local list is inside the group "%s" but not on the server list. Do you want this buddy to be added? -isOnTheLocalListButNot=%s is on the local list but not on the server list. Do you want this buddy to be added? -yes=Yes -no=No unableToParseMessage=Unable to parse message syntaxErrorProbablyAClientBug=Syntax Error (probably a client bug) invalidEmailAddress=Invalid email address userDoesNotExist=User does not exist fullyQualifiedDomainNameMissing=Fully qualified domain name missing alreadyLoggedIn=Already logged in invalidUsername=Invalid username invalidFriendlyName=Invalid friendly name @@ -83,38 +78,50 @@ authenticationFailed=Authentication fail notAllowedWhenOffline=Not allowed when offline notAcceptingNewUsers=Not accepting new users kidsPassportWithoutParentalConsent=Kids Passport without parental consent passportAccountNotYetVerified=Passport account not yet verified passportAccountSuspended=Passport account suspended badTicket=Bad ticket unknownErrorCode=Unknown Error Code %d msnError=MSN Error: %s +buddyListSynchronizationIssueIn=Buddy list synchronization issue in %s (%s) +onTheLocalListIsInsideThe=%s on the local list is inside the group "%s" but not on the server list. Do you want this buddy to be added? +isOnTheLocalListButNot=%s is on the local list but not on the server list. Do you want this buddy to be added? +yes=Yes +no=No otherContacts=Other Contacts nonImContacts=Non-IM Contacts sentAWinkAHrefMsnWink=%s sent a wink. Click here to play it sentAWinkButItCouldNot=%s sent a wink, but it could not be saved sentAVoiceClipAHrefAudio=%s sent a voice clip. Click here to play it sentAVoiceClipButItCould=%s sent a voice clip, but it could not be saved sentYouAVoiceChatInviteWhich=%s sent you a voice chat invite, which is not yet supported. nudge=Nudge hasNudgedYou=%s has nudged you! nudging=Nudging %s… emailAddress=Email Address… yourNewMsnFriendlyNameIsToo=Your new MSN friendly name is too long. setFriendlyNameFor=Set friendly name for %s. -setYourFriendlyName=Set your friendly name. +setFriendlyName8607c582=Set Friendly Name thisIsTheNameThatOtherMsn=This is the name that other MSN buddies will see you as. +thisLocation=This Location +thisIsTheNameThatIdentifiesThis=This is the name that identifies this location +otherLocations=Other Locations +youCanSignOutFromOtherLocations=You can sign out from other locations here +youAreNotSignedInFromAny=You are not signed in from any other locations. +allowMultipleLogins=Allow multiple logins? +doYouWantToAllowOrDisallow05d7d442=Do you want to allow or disallow connecting from multiple locations simultaneously? +allow=Allow +disallow=Disallow setYourHomePhoneNumber=Set your home phone number. setYourWorkPhoneNumber=Set your work phone number. setYourMobilePhoneNumber=Set your mobile phone number. allowMsnMobilePages=Allow MSN Mobile pages? -doYouWantToAllowOrDisallow=Do you want to allow or disallow people on your buddy list to send you MSN Mobile pages to your cell phone or other mobile device? -allow=Allow -disallow=Disallow +doYouWantToAllowOrDisallowdb0ca609=Do you want to allow or disallow people on your buddy list to send you MSN Mobile pages to your cell phone or other mobile device? blockedTextFor=Blocked Text for %s noTextIsBlockedForThisAccount=No text is blocked for this account. msnServersAreCurrentlyBlockingTheFollowing=MSN servers are currently blocking the following regular expressions:
%s thisAccountDoesNotHaveEmailEnabled=This account does not have email enabled. sendAMobileMessage=Send a mobile message. page=Page close=Close playingAGame=Playing a game @@ -124,21 +131,23 @@ homePhoneNumber=Home Phone Number workPhoneNumber=Work Phone Number mobilePhoneNumber=Mobile Phone Number beRightBack=Be Right Back busy=Busy onThePhone9eb07896=On the Phone outToLunch99343157=Out to Lunch gameTitle=Game Title officeTitle=Office Title -setFriendlyName=Set Friendly Name… +setFriendlyNamed9e39644=Set Friendly Name… +viewLocations=View Locations… setHomePhoneNumber=Set Home Phone Number… setWorkPhoneNumber=Set Work Phone Number… setMobilePhoneNumber=Set Mobile Phone Number… enableDisableMobileDevices=Enable/Disable Mobile Devices… +allowDisallowMultipleLogins=Allow/Disallow Multiple Logins… allowDisallowMobilePages=Allow/Disallow Mobile Pages… viewBlockedText=View Blocked Text… openHotmailInbox=Open Hotmail Inbox sendToMobile=Send to Mobile sslSupportIsNeededForMsnPlease=SSL support is needed for MSN. Please install a supported SSL library. unableToAddTheBuddyBecauseThe=Unable to add the buddy %s because the username is invalid. Usernames must be valid email addresses. unableToAddba21e7fb=Unable to Add authorizationRequestMessage=Authorization Request Message: @@ -194,16 +203,17 @@ theUserHasNotCreatedAPublic=The user has msnReportedNotBeingAbleToFind=MSN reported not being able to find the user's profile. This either means that the user does not exist, or that the user exists but has not created a public profile. couldNotFindAnyInformationInThe=Could not find any information in the user's profile. The user most likely does not exist. viewWebProfile=View web profile windowsLiveMessengerProtocolPlugin=Windows Live Messenger Protocol Plugin useHttpMethod=Use HTTP Method httpMethodServer=HTTP Method Server showCustomSmileys=Show custom smileys allowDirectConnections=Allow direct connections +allowConnectingFromMultipleLocations=Allow connecting from multiple locations nudgeNudgeAUserToGetTheir=nudge: nudge a user to get their attention windowsLiveIdAuthenticationUnableToConnect=Windows Live ID authentication:Unable to connect windowsLiveIdAuthenticationInvalidResponse=Windows Live ID authentication:Invalid response theFollowingUsersAreMissingFromYour=The following users are missing from your addressbook unknownError7b2a485c=Unknown error (%d): %s unableToAddUser=Unable to add user unknownError97a3ad42=Unknown error (%d) unableToRemoveUser=Unable to remove user diff --git a/purple/locales/en-US/oscar.properties b/purple/locales/en-US/oscar.properties --- a/purple/locales/en-US/oscar.properties +++ b/purple/locales/en-US/oscar.properties @@ -11,38 +11,39 @@ offline=Offline doNotDisturb=Do Not Disturb away=Away status=Status uin=UIN firstName=First Name unableToDisplayTheSearchResults=Unable to display the search results. connecting=Connecting lostConnectionWithServer=Lost connection with server: %s -sslSupportUnavailable=SSL support unavailable unableToConnect5d04a002=Unable to connect unableToConnectb0a9a86e=Unable to connect: %s serverClosedTheConnection=Server closed the connection server=Server port=Port username=Username -useSsl=Use SSL nick=Nick birthday=Birthday idle=Idle address=Address lastName=Last Name emailAddress=Email Address _room=_Room: state=State notAuthorized=Not Authorized mood=Mood moodName=Mood Name moodComment=Mood Comment setUserInfo=Set User Info… changePassword=Change Password… +requireEncryption=Require encryption +useEncryptionIfAvailable=Use encryption if available +connectionSecurity=Connection security sendFile=Send File angry=Angry inLove=In love sick=Sick sleepy=Sleepy notLoggedIn=Not logged in working=Working busy=Busy @@ -53,23 +54,29 @@ _ok=_OK _cancel=_Cancel age=Age homeAddress=Home Address company=Company workAddress=Work Address viewWebProfile=View web profile youHaveSignedOnFromAnotherLocation=You have signed on from another location incorrectPassword=Incorrect password +pleaseAuthorizeMeSoICanAdd=Please authorize me so I can add you to my buddy list. +noReasonGiven=No reason given. +authorizationDeniedMessage=Authorization Denied Message: receivedUnexpectedResponseFrom894db08f=Received unexpected response from %s: %s receivedUnexpectedResponseFrom84a8f810=Received unexpected response from %s youHaveBeenConnectingAndDisconnectingToo=You have been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer. -errorRequestinga16f5839=Error requesting %s: %s +youRequiredEncryptionInYourAccountSettings70b9410a=You required encryption in your account settings, but one of the servers doesn't support it. +errorRequesting=Error requesting %s: %s +theServerReturnedAnEmptyResponse=The server returned an empty response serverRequestedThatYouFillOutA=Server requested that you fill out a CAPTCHA in order to sign in, but this client does not currently support CAPTCHAs. aolDoesNotAllowYourScreenName=AOL does not allow your screen name to authenticate here -errorRequesting196ebfaf=Error requesting %s +thereWasAnErrorReceivingThisMessage3a6dbb62=(There was an error receiving this message. The buddy you are speaking with is probably using a different encoding than expected. If you know what encoding he is using, you can specify it in the advanced account options for your AIM/ICQ account.) +thereWasAnErrorReceivingThisMessage18152e8a=(There was an error receiving this message. Either you and %s have different encodings selected, or %s has a buggy client.) couldNotJoinChatRoom=Could not join chat room invalidChatRoomName=Invalid chat room name invalidError=Invalid error cannotReceiveImDueToParentalControls=Cannot receive IM due to parental controls cannotSendSmsWithoutAcceptingTerms=Cannot send SMS without accepting terms cannotSendSms=Cannot send SMS cannotSendSmsToThisCountry=Cannot send SMS to this country cannotSendSmsToUnknownCountry=Cannot send SMS to unknown country @@ -119,112 +126,67 @@ encoding=Encoding theRemoteUserHasClosedTheConnection=The remote user has closed the connection. theRemoteUserHasDeclinedYourRequest=The remote user has declined your request. lostConnectionWithTheRemoteUserBr=Lost connection with the remote user:
%s receivedInvalidDataOnConnectionWithRemote=Received invalid data on connection with remote user. unableToEstablishAConnectionWithThe=Unable to establish a connection with the remote user. directImEstablished=Direct IM established triedToSendYouAFileBut=%s tried to send you a %s file, but we only allow files up to %s over Direct IM. Try using file transfer instead. fileIsWhichIsLargerThanThe=File %s is %s, which is larger than the maximum size of %s. -thereWasAnErrorReceivingThisMessage3a6dbb62=(There was an error receiving this message. The buddy you are speaking with is probably using a different encoding than expected. If you know what encoding he is using, you can specify it in the advanced account options for your AIM/ICQ account.) -thereWasAnErrorReceivingThisMessage18152e8a=(There was an error receiving this message. Either you and %s have different encodings selected, or %s has a buggy client.) -buddyIcon=Buddy Icon -voice=Voice -aimDirectIm=AIM Direct IM -chat=Chat -getFile=Get File -games=Games -icqXtraz=ICQ Xtraz -addIns=Add-Ins -sendBuddyList=Send Buddy List -icqDirectConnect=ICQ Direct Connect -apUser=AP User -icqRtf=ICQ RTF -nihilist=Nihilist -icqServerRelay=ICQ Server Relay -oldIcqUtf8=Old ICQ UTF8 -trillianEncryption=Trillian Encryption -icqUtf8=ICQ UTF8 -hiptop=Hiptop -securityEnabled=Security Enabled -videoChat=Video Chat -ichatAv=iChat AV -liveVideo=Live Video -camera=Camera -screenSharing=Screen Sharing freeForChat=Free For Chat notAvailable=Not Available occupied=Occupied webAware=Web Aware invisible=Invisible evil=Evil depression=Depression atHome=At home atWork=At work atLunch=At lunch online=Online -ipAddress=IP Address -warningLevel=Warning Level -buddyComment55df1136=Buddy Comment unableToConnectToAuthenticationServer=Unable to connect to authentication server: %s unableToConnectToBosServer=Unable to connect to BOS server: %s usernameSent=Username sent connectionEstablishedCookieSent=Connection established, cookie sent finalizingConnection=Finalizing connection unableToSignOnAsBecauseThe=Unable to sign on as %s because the username is invalid. Usernames must be a valid email address, or start with a letter and contain only letters, numbers and spaces, or contain only numbers. +youRequiredEncryptionInYourAccountSettingsd3d4d297=You required encryption in your account settings, but encryption is not supported by your system. youMayBeDisconnectedShortlyIfSo=You may be disconnected shortly. If so, check %s for updates. unableToGetAValidAimLogin=Unable to get a valid AIM login hash. unableToGetAValidLoginHash=Unable to get a valid login hash. receivedAuthorization=Received authorization usernameDoesNotExist=Username does not exist yourAccountIsCurrentlySuspended=Your account is currently suspended theAolInstantMessengerServiceIsTemporarily=The AOL Instant Messenger service is temporarily unavailable. yourUsernameHasBeenConnectingAndDisconnecting=Your username has been connecting and disconnecting too frequently. Wait ten minutes and try again. If you continue to try, you will need to wait even longer. theClientVersionYouAreUsingIs=The client version you are using is too old. Please upgrade at %s yourIpAddressHasBeenConnectingAnd=Your IP address has been connecting and disconnecting too frequently. Wait a minute and try again. If you continue to try, you will need to wait even longer. theSecuridKeyEnteredIsInvalid=The SecurID key entered is invalid enterSecurid=Enter SecurID enterThe6DigitNumberFromThe=Enter the 6 digit number from the digital display. passwordSent=Password sent unableToInitializeConnection=Unable to initialize connection -pleaseAuthorizeMeSoICanAdd=Please authorize me so I can add you to my buddy list. -noReasonGiven=No reason given. -authorizationDeniedMessage=Authorization Denied Message: theUserHasDeniedYourRequestTo18086e13=The user %u has denied your request to add them to your buddy list for the following reason:\n%s icqAuthorizationDenied=ICQ authorization denied. theUserHasGrantedYourRequestTo86eb99b5=The user %u has granted your request to add them to your buddy list. youHaveReceivedASpecialMessageFrom=You have received a special message\n\nFrom: %s [%s]\n%s youHaveReceivedAnIcqPageFrom=You have received an ICQ page\n\nFrom: %s [%s]\n%s youHaveReceivedAnIcqEmailFrom=You have received an ICQ email from %s [%s]\n\nMessage is:\n%s icqUserHasSentYouABuddy=ICQ user %u has sent you a buddy: %s (%s) doYouWantToAddThisBuddy=Do you want to add this buddy to your buddy list? _add=_Add _decline=_Decline youMissedMessageFromBecauseItWas1dfe3b44=You missed %hu message from %s because it was invalid.;You missed %hu messages from %s because they were invalid. youMissedMessageFromBecauseItWasfa8d85ec=You missed %hu message from %s because it was too large.;You missed %hu messages from %s because they were too large. youMissedMessageFromBecauseTheRate=You missed %hu message from %s because the rate limit has been exceeded.;You missed %hu messages from %s because the rate limit has been exceeded. youMissedMessageFromBecauseHisHer=You missed %hu message from %s because his/her warning level is too high.;You missed %hu messages from %s because his/her warning level is too high. youMissedMessageFromBecauseYourWarning=You missed %hu message from %s because your warning level is too high.;You missed %hu messages from %s because your warning level is too high. youMissedMessageFromForAnUnknown=You missed %hu message from %s for an unknown reason.;You missed %hu messages from %s for an unknown reason. -userInformationNotAvailable=User information not available: %s -onlineSince=Online Since -memberSince=Member Since -capabilities=Capabilities -profile=Profile yourAimConnectionMayBeLost=Your AIM connection may be lost. -unableToDisplayAMessageFromThis=[Unable to display a message from this user because it contained invalid characters.] youHaveBeenDisconnectedFromChatRoom=You have been disconnected from chat room %s. -mobilePhone=Mobile Phone -personalWebPage=Personal Web Page -additionalInformation=Additional Information -zipCode=Zip Code -workInformation=Work Information -division=Division -position=Position -webPage=Web Page popUpMessage=Pop-Up Message theFollowingUsernameIsAssociatedWith=The following username is associated with %s;The following usernames are associated with %s noResultsFoundForEmailAddress=No results found for email address %s youShouldReceiveAnEmailAskingTo=You should receive an email asking to confirm %s. accountConfirmationRequested=Account Confirmation Requested errorUnableToFormatUsernameBecauseThe1c3d047d=Error 0x%04x: Unable to format username because the requested name differs from the original. errorUnableToFormatUsernameBecauseIt=Error 0x%04x: Unable to format username because it is invalid. errorUnableToFormatUsernameBecauseThe7447f420=Error 0x%04x: Unable to format username because the requested name is too long. @@ -272,41 +234,83 @@ endDirectImSession=End Direct IM Session directIm=Direct IM reRequestAuthorization=Re-request Authorization requireAuthorization=Require authorization webAwareEnablingThisWillCauseYou=Web aware (enabling this will cause you to receive SPAM!) icqPrivacyOptions=ICQ Privacy Options theNewFormattingIsInvalid=The new formatting is invalid. usernameFormattingCanChangeOnlyCapitalizationAnd=Username formatting can change only capitalization and whitespace. changeAddressTo=Change Address To: -iYouAreNotWaitingForAuthorization=you are not waiting for authorization +youAreNotWaitingForAuthorization=you are not waiting for authorization youAreAwaitingAuthorizationFromTheFollowing=You are awaiting authorization from the following buddies youCanReRequestAuthorizationFromThese=You can re-request authorization from these buddies by right-clicking on them and selecting "Re-request Authorization." findBuddyByEmail=Find Buddy by Email searchForABuddyByEmailAddress=Search for a buddy by email address typeTheEmailAddressOfTheBuddy=Type the email address of the buddy you are searching for. _search=_Search setUserInfoWeb=Set User Info (web)… changePasswordWeb=Change Password (web) configureImForwardingWeb=Configure IM Forwarding (web) setPrivacyOptions=Set Privacy Options… +showVisibleList=Show Visible List +showInvisibleList=Show Invisible List confirmAccount=Confirm Account displayCurrentlyRegisteredEmailAddress=Display Currently Registered Email Address changeCurrentlyRegisteredEmailAddress=Change Currently Registered Email Address… showBuddiesAwaitingAuthorization=Show Buddies Awaiting Authorization searchForBuddyByEmailAddress=Search for Buddy by Email Address… -searchForBuddyByInformation=Search for Buddy by Information +donTUseEncryption=Don't use encryption useClientlogin=Use clientLogin alwaysUseAimIcqProxyServerFor=Always use AIM/ICQ proxy server for\nfile transfers and direct IM (slower,\nbut does not reveal your IP address) allowMultipleSimultaneousLogins=Allow multiple simultaneous logins askingToConnectToUsAtFor=Asking %s to connect to us at %s:%hu for Direct IM. attemptingToConnectTo=Attempting to connect to %s:%hu. attemptingToConnectViaProxyServer=Attempting to connect via proxy server. hasJustAskedToDirectlyConnectTo=%s has just asked to directly connect to %s thisRequiresADirectConnectionBetweenThe=This requires a direct connection between the two computers and is necessary for IM Images. Because your IP address will be revealed, this may be considered a privacy risk. +buddyIcon=Buddy Icon +voice=Voice +aimDirectIm=AIM Direct IM +chat=Chat +getFile=Get File +games=Games +icqXtraz=ICQ Xtraz +addIns=Add-Ins +sendBuddyList=Send Buddy List +icqDirectConnect=ICQ Direct Connect +apUser=AP User +icqRtf=ICQ RTF +nihilist=Nihilist +icqServerRelay=ICQ Server Relay +oldIcqUtf8=Old ICQ UTF8 +trillianEncryption=Trillian Encryption +icqUtf8=ICQ UTF8 +hiptop=Hiptop +securityEnabled=Security Enabled +videoChat=Video Chat +ichatAv=iChat AV +liveVideo=Live Video +camera=Camera +screenSharing=Screen Sharing +ipAddress=IP Address +warningLevel=Warning Level +buddyComment55df1136=Buddy Comment +userInformationNotAvailable=User information not available: %s +mobilePhone=Mobile Phone +personalWebPage=Personal Web Page +additionalInformation=Additional Information +zipCode=Zip Code +workInformation=Work Information +division=Division +position=Position +webPage=Web Page +onlineSince=Online Since +memberSince=Member Since +capabilities=Capabilities +profile=Profile invalidSnac=Invalid SNAC serverRateLimitExceeded=Server rate limit exceeded clientRateLimitExceeded=Client rate limit exceeded serviceUnavailable=Service unavailable serviceNotDefined=Service not defined obsoleteSnac=Obsolete SNAC notSupportedByHost=Not supported by host notSupportedByClient=Not supported by client @@ -320,8 +324,18 @@ inLocalPermitDeny=In local permit/deny warningLevelTooHighSender=Warning level too high (sender) warningLevelTooHighReceiver=Warning level too high (receiver) userTemporarilyUnavailable=User temporarily unavailable noMatch=No match listOverflow=List overflow requestAmbiguous=Request ambiguous queueFull=Queue full notWhileOnAol=Not while on AOL +appearOnline=Appear Online +donTAppearOnline=Don't Appear Online +appearOffline=Appear Offline +donTAppearOffline=Don't Appear Offline +youHaveNoBuddiesOnThisList=you have no buddies on this list +youCanAddABuddyToThis=You can add a buddy to this list by right-clicking on them and selecting "%s" +visibleList=Visible List +theseBuddiesWillSeeYourStatusWhen=These buddies will see your status when you switch to "Invisible" +invisibleList=Invisible List +theseBuddiesWillAlwaysSeeYouAs=These buddies will always see you as offline diff --git a/purple/locales/en-US/qq.properties b/purple/locales/en-US/qq.properties --- a/purple/locales/en-US/qq.properties +++ b/purple/locales/en-US/qq.properties @@ -91,17 +91,17 @@ couldNotChangeBuddyInformation=Could not note=Note buddyMemo=Buddy Memo changeHisHerMemoAsYouLike=Change his/her memo as you like _modify=_Modify memoModify=Memo Modify serverSays=Server says: yourRequestWasAccepted=Your request was accepted. yourRequestWasRejected=Your request was rejected. -requiresVerification=%u requires verification +requiresVerification=%u requires verification: %s addBuddyQuestion=Add buddy question enterAnswerHere=Enter answer here send=Send addBuddy2739ada9=Add Buddy invalidAnswer=Invalid answer. authorizationDeniedMessage=Authorization denied message: sorryYouReNotMyStyle=Sorry, you're not my style. needsAuthorization=%u needs authorization diff --git a/purple/locales/en-US/yahoo.properties b/purple/locales/en-US/yahoo.properties --- a/purple/locales/en-US/yahoo.properties +++ b/purple/locales/en-US/yahoo.properties @@ -44,19 +44,21 @@ location=Location maritalStatus=Marital Status favoriteQuote=Favorite Quote viewWebProfile=View web profile unknownError97a3ad42=Unknown error (%d) youHaveSignedOnFromAnotherLocation=You have signed on from another location incorrectPassword=Incorrect password encoding=Encoding invisible=Invisible +usernameDoesNotExist=Username does not exist ipAddress=IP Address -usernameDoesNotExist=Username does not exist memberSince=Member Since +appearOnline=Appear Online +appearOffline=Appear Offline addBuddy=Add Buddy joinLtRoomGtJoinAChat=join <room>: Join a chat room on the Yahoo network listListRoomsOnTheYahooNetwork=list: List rooms on the Yahoo network doodleRequestUserToStartADoodle=doodle: Request user to start a Doodle session yahooIdbc474661=Yahoo ID… yahooProtocolPlugin=Yahoo! Protocol Plugin pagerPort=Pager port fileTransferServer=File transfer server @@ -97,20 +99,18 @@ unableToEstablishAConnectionWith=Unable unableToConnectTheServerReturnedAn=Unable to connect: The server returned an empty response. unableToConnectTheServerSResponse=Unable to connect: The server's response did not contain the necessary information notAtHome=Not at Home notAtDesk=Not at Desk notInOffice=Not in Office onVacation=On Vacation steppedOut=Stepped Out notOnServerList=Not on server list -appearOnline=Appear Online appearPermanentlyOffline=Appear Permanently Offline presence=Presence -appearOffline=Appear Offline donTAppearPermanentlyOffline=Don't Appear Permanently Offline joinInChat=Join in Chat initiateConference=Initiate Conference presenceSettings=Presence Settings startDoodling=Start Doodling selectTheIdYouWantToActivate=Select the ID you want to activate joinWhomInChat=Join whom in chat? activateId=Activate ID…