diff options
-rw-r--r-- | skypeweb/Makefile | 17 | ||||
-rw-r--r-- | skypeweb/Makefile.eion | 54 | ||||
-rw-r--r-- | skypeweb/libjson-glib-1.0.dll | bin | 0 -> 278906 bytes | |||
-rw-r--r-- | skypeweb/libskypeweb.c | 380 | ||||
-rw-r--r-- | skypeweb/libskypeweb.h | 127 | ||||
-rw-r--r-- | skypeweb/skypeweb_connection.c | 772 | ||||
-rw-r--r-- | skypeweb/skypeweb_connection.h | 95 | ||||
-rw-r--r-- | skypeweb/skypeweb_contacts.c | 462 | ||||
-rw-r--r-- | skypeweb/skypeweb_contacts.h | 21 | ||||
-rw-r--r-- | skypeweb/skypeweb_login.c | 108 | ||||
-rw-r--r-- | skypeweb/skypeweb_login.h | 13 | ||||
-rw-r--r-- | skypeweb/skypeweb_messages.c | 325 | ||||
-rw-r--r-- | skypeweb/skypeweb_messages.h | 14 | ||||
-rw-r--r-- | skypeweb/skypeweb_util.c | 170 | ||||
-rw-r--r-- | skypeweb/skypeweb_util.h | 9 |
15 files changed, 2567 insertions, 0 deletions
diff --git a/skypeweb/Makefile b/skypeweb/Makefile new file mode 100644 index 0000000..853f107 --- /dev/null +++ b/skypeweb/Makefile @@ -0,0 +1,17 @@ + +COMPILER = gcc + +LIBPURPLE_CFLAGS += $(shell pkg-config --cflags glib-2.0 json-glib-1.0 purple nss gnome-keyring-1) +LIBPURPLE_LIBS += $(shell pkg-config --libs glib-2.0 json-glib-1.0 purple nss) + +STEAM_SOURCES = \ + steam_connection.c \ + libsteam.c + +.PHONY: all clean install +all: libsteam.so +clean: + rm -f libsteam.so + +libsteam.so: ${STEAM_SOURCES} + ${COMPILER} -Wall -I. -g -O2 -fPIC -pipe ${STEAM_SOURCES} -o $@ ${LIBPURPLE_CFLAGS} ${LIBPURPLE_LIBS} -shared diff --git a/skypeweb/Makefile.eion b/skypeweb/Makefile.eion new file mode 100644 index 0000000..0113e75 --- /dev/null +++ b/skypeweb/Makefile.eion @@ -0,0 +1,54 @@ +#Customisable stuff here +LINUX32_COMPILER = i686-pc-linux-gnu-gcc +LINUX64_COMPILER = x86_64-pc-linux-gnu-gcc +#WIN32_COMPILER = /usr/bin/i586-mingw32-gcc +WIN32_COMPILER = gcc.exe +WIN32_WINDRES = i586-mingw32-windres +#WIN32_OBJCOPY = i586-mingw32-objcopy +WIN32_OBJCOPY = objcopy.exe +#LINUX_ARM_COMPILER = arm-pc-linux-gnu-gcc +LINUX_ARM_COMPILER = arm-none-linux-gnueabi-gcc +LINUX_PPC_COMPILER = powerpc-unknown-linux-gnu-gcc +FREEBSD60_COMPILER = i686-pc-freebsd6.0-gcc +MACPORT_COMPILER = i686-apple-darwin10-gcc-4.0.1 + +LIBPURPLE_CFLAGS = -I/usr/include/libpurple -I/usr/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB +GLIB_CFLAGS = -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include -I/usr/local/include/glib-2.0 -I/usr/local/lib/glib-2.0/include -I/usr/local/include -I/usr/include/json-glib-1.0 -ljson-glib-1.0 +WIN32_DEV_DIR = C:/cygwin/home/Eion/win32-dev +WIN32_PIDGIN_DIR = C:/cygwin/home/Eion/pidgin-2.10.4 +WIN32_CFLAGS = -I${WIN32_DEV_DIR}/gtk_2_0-2.14/include/glib-2.0 -I${WIN32_PIDGIN_DIR}/libpurple/win32 -I${WIN32_DEV_DIR}/gtk_2_0-2.14/include -I${WIN32_DEV_DIR}/gtk_2_0-2.14/include/glib-2.0 -I${WIN32_DEV_DIR}/gtk_2_0-2.14/lib/glib-2.0/include -I${WIN32_DEV_DIR}/json-glib-0.8.0 -Wno-format -I${WIN32_PIDGIN_DIR}/libpurple -I${WIN32_DEV_DIR}/nss-3.12.5-nspr-4.8.2/include +WIN32_LIBS = -L${WIN32_DEV_DIR}/gtk_2_0-2.14/lib -L${WIN32_PIDGIN_DIR}/libpurple -L${WIN32_DEV_DIR}/nss-3.12.5-nspr-4.8.2/lib -lglib-2.0 -lgobject-2.0 -lintl -lpurple -lws2_32 -L. -ljson-glib-1.0 -lz -lnss3 +MACPORT_CFLAGS = -I/opt/local/include/libpurple -DPURPLE_PLUGINS -DENABLE_NLS -DHAVE_ZLIB -I/opt/local/include/glib-2.0 -I/opt/local/lib/glib-2.0/include -I/opt/local/include -I/opt/local/include/json-glib-1.0 -arch i386 -arch ppc -dynamiclib -L/opt/local/lib -ljson-glib-1.0 -lpurple -lglib-2.0 -lgobject-2.0 -lintl -lz -isysroot /Developer/SDKs/MacOSX10.4u.sdk -mmacosx-version-min=10.4 + +DEB_PACKAGE_DIR = ./debdir + +STEAM_SOURCES = \ + skypeweb_connection.c \ + skypeweb_contacts.c \ + skypeweb_login.c \ + skypeweb_messages.c \ + skypeweb_util.c \ + libskypeweb.c + + +#Standard stuff here +.PHONY: all clean install sourcepackage + +all: libskypeweb.dll + +install: + +installers: + +clean: + rm -f libskypeweb.dll + +libskypeweb.dll: ${STEAM_SOURCES} + ${WIN32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall -I. -g -O2 -pipe ${STEAM_SOURCES} -o $@ -shared -mno-cygwin ${WIN32_CFLAGS} ${WIN32_LIBS} + ${WIN32_OBJCOPY} --only-keep-debug $@ $@.dbg + ${WIN32_OBJCOPY} --strip-debug $@ + ${WIN32_OBJCOPY} --add-gnu-debuglink=$@.dbg $@ + upx libskypeweb.dll + +libskypeweb-debug.dll: ${STEAM_SOURCES} + ${WIN32_COMPILER} ${LIBPURPLE_CFLAGS} -Wall -I. -g -O2 -pipe ${STEAM_SOURCES} -o $@ -shared -mno-cygwin ${WIN32_CFLAGS} ${WIN32_LIBS} diff --git a/skypeweb/libjson-glib-1.0.dll b/skypeweb/libjson-glib-1.0.dll Binary files differnew file mode 100644 index 0000000..d36628f --- /dev/null +++ b/skypeweb/libjson-glib-1.0.dll diff --git a/skypeweb/libskypeweb.c b/skypeweb/libskypeweb.c new file mode 100644 index 0000000..f7177b7 --- /dev/null +++ b/skypeweb/libskypeweb.c @@ -0,0 +1,380 @@ +#include "libskypeweb.h"
+#include "skypeweb_connection.h"
+#include "skypeweb_contacts.h"
+#include "skypeweb_login.h"
+#include "skypeweb_messages.h"
+#include "skypeweb_util.h"
+
+void
+skypeweb_do_all_the_things(SkypeWebAccount *sa)
+{
+ if (sa->registration_token) {
+ purple_connection_set_state(sa->pc, PURPLE_CONNECTED);
+
+ if (sa->authcheck_timeout)
+ purple_timeout_remove(sa->authcheck_timeout);
+ skypeweb_check_authrequests(sa);
+ sa->authcheck_timeout = purple_timeout_add_seconds(120, (GSourceFunc)skypeweb_check_authrequests, sa);
+
+ skypeweb_get_friend_list(sa);
+ skypeweb_poll(sa);
+ } else {
+ //Too soon!
+ skypeweb_get_registration_token(sa);
+ }
+}
+
+
+/******************************************************************************/
+/* PRPL functions */
+/******************************************************************************/
+
+static const char *skypeweb_list_icon(PurpleAccount *account, PurpleBuddy *buddy)
+{
+ return "skype";
+}
+
+static gchar *skypeweb_status_text(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy = buddy->proto_data;
+
+ if (sbuddy && (sbuddy->mood || sbuddy->rich_mood))
+ {
+ if (sbuddy->rich_mood)
+ {
+ return sbuddy->rich_mood;
+ } else {
+ return g_markup_printf_escaped("%s", sbuddy->mood);
+ }
+ }
+
+ return NULL;
+}
+
+void
+skypeweb_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
+{
+ SkypeWebBuddy *sbuddy = buddy->proto_data;
+
+ if (sbuddy)
+ {
+ PurplePresence *presence;
+ PurpleStatus *status;
+ SkypeWebBuddy *sbuddy = buddy->proto_data;
+
+ presence = purple_buddy_get_presence(buddy);
+ status = purple_presence_get_active_status(presence);
+ purple_notify_user_info_add_pair_html(user_info, _("Status"), purple_status_get_name(status));
+ if (sbuddy->mood && *sbuddy->mood)
+ purple_notify_user_info_add_pair_html(user_info, _("Message"), sbuddy->mood);
+
+ if (sbuddy->display_name && *sbuddy->display_name)
+ purple_notify_user_info_add_pair_html(user_info, "Alias", sbuddy->display_name);
+ if (sbuddy->fullname && *sbuddy->fullname)
+ purple_notify_user_info_add_pair_html(user_info, "Full Name", sbuddy->fullname);
+ }
+}
+
+const gchar *
+skypeweb_list_emblem(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy = buddy->proto_data;
+
+ return NULL;
+}
+
+GList *
+skypeweb_status_types(PurpleAccount *account)
+{
+ GList *types = NULL;
+ PurpleStatusType *status;
+
+ purple_debug_info("skypeweb", "status_types\n");
+
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, "Online", NULL, TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_OFFLINE, "Offline", NULL, TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_UNAVAILABLE, "Idle", NULL, TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_AWAY, "Away", NULL, TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+ status = purple_status_type_new_with_attrs(PURPLE_STATUS_EXTENDED_AWAY, "Hidden", NULL, TRUE, TRUE, FALSE, "message", "Mood", purple_value_new(PURPLE_TYPE_STRING), NULL);
+ types = g_list_append(types, status);
+
+ return types;
+}
+
+
+static void skypeweb_buddy_free(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy = buddy->proto_data;
+ if (sbuddy != NULL)
+ {
+ buddy->proto_data = NULL;
+
+ g_free(sbuddy->skypename);
+ g_free(sbuddy->fullname);
+ g_free(sbuddy->display_name);
+ g_free(sbuddy->avatar_url);
+ g_free(sbuddy->mood);
+ g_free(sbuddy->rich_mood);
+ g_free(sbuddy->country);
+ g_free(sbuddy->city);
+
+ g_free(sbuddy);
+ }
+}
+
+void
+skypeweb_fake_group_buddy(PurpleConnection *pc, const char *who, const char *old_group, const char *new_group)
+{
+ // Do nothing to stop the remove+add behaviour
+}
+void
+skypeweb_fake_group_rename(PurpleConnection *pc, const char *old_name, PurpleGroup *group, GList *moved_buddies)
+{
+ // Do nothing to stop the remove+add behaviour
+}
+
+
+static void
+skypeweb_login(PurpleAccount *account)
+{
+ PurpleConnection *pc = purple_account_get_connection(account);
+ SkypeWebAccount *sa = g_new0(SkypeWebAccount, 1);
+
+ pc->proto_data = sa;
+
+ if (!purple_ssl_is_supported()) {
+ purple_connection_error (pc,
+ PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT,
+ _("Server requires TLS/SSL for login. No TLS/SSL support found."));
+ return;
+ }
+
+ sa->account = account;
+ sa->pc = pc;
+ sa->cookie_table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ sa->hostname_ip_cache = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
+ sa->sent_messages_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+ sa->waiting_conns = g_queue_new();
+
+ skypeweb_begin_web_login(sa);
+}
+
+static void skypeweb_close(PurpleConnection *pc)
+{
+ SkypeWebAccount *sa;
+ GString *post;
+
+ g_return_if_fail(pc != NULL);
+ g_return_if_fail(pc->proto_data != NULL);
+
+ sa = pc->proto_data;
+
+ skypeweb_logout(sa);
+
+ purple_timeout_remove(sa->authcheck_timeout);
+ purple_timeout_remove(sa->poll_timeout);
+ purple_timeout_remove(sa->watchdog_timeout);
+
+ purple_debug_info("skypeweb", "destroying %d waiting connections\n",
+ g_queue_get_length(sa->waiting_conns));
+
+ while (!g_queue_is_empty(sa->waiting_conns))
+ skypeweb_connection_destroy(g_queue_pop_tail(sa->waiting_conns));
+ g_queue_free(sa->waiting_conns);
+
+ purple_debug_info("skypeweb", "destroying %d incomplete connections\n",
+ g_slist_length(sa->conns));
+
+ while (sa->conns != NULL)
+ skypeweb_connection_destroy(sa->conns->data);
+
+ while (sa->dns_queries != NULL) {
+ PurpleDnsQueryData *dns_query = sa->dns_queries->data;
+ purple_debug_info("skypeweb", "canceling dns query for %s\n",
+ purple_dnsquery_get_host(dns_query));
+ sa->dns_queries = g_slist_remove(sa->dns_queries, dns_query);
+ purple_dnsquery_destroy(dns_query);
+ }
+
+ g_hash_table_destroy(sa->sent_messages_hash);
+ g_hash_table_destroy(sa->cookie_table);
+ g_hash_table_destroy(sa->hostname_ip_cache);
+
+ g_free(sa->skype_token);
+ g_free(sa->registration_token);
+ g_free(sa);
+}
+
+/******************************************************************************/
+/* Plugin functions */
+/******************************************************************************/
+
+static gboolean plugin_load(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static gboolean plugin_unload(PurplePlugin *plugin)
+{
+ return TRUE;
+}
+
+static GList *skypeweb_actions(PurplePlugin *plugin, gpointer context)
+{
+ GList *m = NULL;
+ PurplePluginAction *act;
+
+ act = purple_plugin_action_new(_("Search for friends..."),
+ skypeweb_search_users);
+ m = g_list_append(m, act);
+
+ return m;
+}
+
+static void plugin_init(PurplePlugin *plugin)
+{
+ PurpleAccountOption *option;
+ PurplePluginInfo *info = plugin->info;
+ PurplePluginProtocolInfo *prpl_info = info->extra_info;
+
+
+ /*option = purple_account_option_bool_new(
+ _("Always use HTTPS"),
+ "always_use_https", FALSE);
+ prpl_info->protocol_options = g_list_append(
+ prpl_info->protocol_options, option);*/
+
+}
+
+static PurplePluginProtocolInfo prpl_info = {
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
+#endif
+
+ /* options */
+ OPT_PROTO_MAIL_CHECK,
+
+ NULL, /* user_splits */
+ NULL, /* protocol_options */
+ /* NO_BUDDY_ICONS */ /* icon_spec */
+ {"jpeg", 0, 0, 96, 96, 0, PURPLE_ICON_SCALE_DISPLAY}, /* icon_spec */
+ skypeweb_list_icon, /* list_icon */
+ skypeweb_list_emblem, /* list_emblems */
+ skypeweb_status_text, /* status_text */
+ skypeweb_tooltip_text, /* tooltip_text */
+ skypeweb_status_types, /* status_types */
+ NULL,//skypeweb_node_menu, /* blist_node_menu */
+ NULL,//skypeweb_chat_info, /* chat_info */
+ NULL,//skypeweb_chat_info_defaults, /* chat_info_defaults */
+ skypeweb_login, /* login */
+ skypeweb_close, /* close */
+ skypeweb_send_im, /* send_im */
+ NULL, /* set_info */
+ skypeweb_send_typing, /* send_typing */
+ NULL,//skypeweb_get_info, /* get_info */
+ skypeweb_set_status, /* set_status */
+ skypeweb_set_idle, /* set_idle */
+ NULL, /* change_passwd */
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ skypeweb_add_buddy, /* add_buddy */
+#else
+ skypeweb_add_buddy_with_invite,
+#endif
+ NULL, /* add_buddies */
+ skypeweb_buddy_remove, /* remove_buddy */
+ NULL, /* remove_buddies */
+ NULL, /* add_permit */
+ NULL, /* add_deny */
+ NULL, /* rem_permit */
+ NULL, /* rem_deny */
+ NULL, /* set_permit_deny */
+ NULL,//skypeweb_fake_join_chat, /* join_chat */
+ NULL, /* reject chat invite */
+ NULL,//skypeweb_get_chat_name, /* get_chat_name */
+ NULL, /* chat_invite */
+ NULL,//skypeweb_chat_fake_leave, /* chat_leave */
+ NULL, /* chat_whisper */
+ NULL,//skypeweb_chat_send, /* chat_send */
+ NULL, /* keepalive */
+ NULL, /* register_user */
+ NULL, /* get_cb_info */
+#if !PURPLE_VERSION_CHECK(3, 0, 0)
+ NULL, /* get_cb_away */
+#endif
+ NULL, /* alias_buddy */
+ skypeweb_fake_group_buddy, /* group_buddy */
+ skypeweb_fake_group_rename, /* rename_group */
+ skypeweb_buddy_free, /* buddy_free */
+ NULL, /* convo_closed */
+ purple_normalize_nocase, /* normalize */
+ NULL, /* set_buddy_icon */
+ NULL, /* remove_group */
+ NULL, /* get_cb_real_name */
+ NULL, /* set_chat_topic */
+ NULL, /* find_blist_chat */
+ NULL, /* roomlist_get_list */
+ NULL, /* roomlist_cancel */
+ NULL, /* roomlist_expand_category */
+ NULL, /* can_receive_file */
+ NULL, /* send_file */
+ NULL, /* new_xfer */
+ NULL, /* offline_message */
+ NULL, /* whiteboard_prpl_ops */
+ NULL, /* send_raw */
+ NULL, /* roomlist_room_serialize */
+ NULL, /* unregister_user */
+ NULL, /* send_attention */
+ NULL, /* attention_types */
+#if (PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 5) || PURPLE_MAJOR_VERSION > 2
+#if PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 5
+ sizeof(PurplePluginProtocolInfo), /* struct_size */
+#endif
+ NULL, // skypeweb_get_account_text_table, /* get_account_text_table */
+ NULL, /* initiate_media */
+ NULL, /* can_do_media */
+ NULL, /* get_moods */
+ NULL, /* set_public_alias */
+ NULL /* get_public_alias */
+#if PURPLE_MAJOR_VERSION == 2 && PURPLE_MINOR_VERSION >= 8
+, skypeweb_add_buddy_with_invite, /* add_buddy_with_invite */
+ NULL /* add_buddies_with_invite */
+#endif
+#endif
+};
+
+static PurplePluginInfo info = {
+ PURPLE_PLUGIN_MAGIC,
+ PURPLE_MAJOR_VERSION, /* major_version */
+ PURPLE_MINOR_VERSION, /* minor version */
+ PURPLE_PLUGIN_PROTOCOL, /* type */
+ NULL, /* ui_requirement */
+ 0, /* flags */
+ NULL, /* dependencies */
+ PURPLE_PRIORITY_DEFAULT, /* priority */
+ SKYPEWEB_PLUGIN_ID, /* id */
+ "Skype (HTTP)", /* name */
+ SKYPEWEB_PLUGIN_VERSION, /* version */
+ N_("Skype for Web Protocol Plugin"), /* summary */
+ N_("Skype for Web Protocol Plugin"), /* description */
+ "Eion Robb <eionrobb@gmail.com>", /* author */
+ "http://skype4pidgin.googlecode.com/", /* homepage */
+ plugin_load, /* load */
+ plugin_unload, /* unload */
+ NULL, /* destroy */
+ NULL, /* ui_info */
+ &prpl_info, /* extra_info */
+ NULL, /* prefs_info */
+ skypeweb_actions, /* actions */
+
+ /* padding */
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PURPLE_INIT_PLUGIN(skypeweb, plugin_init, info);
diff --git a/skypeweb/libskypeweb.h b/skypeweb/libskypeweb.h new file mode 100644 index 0000000..4676be8 --- /dev/null +++ b/skypeweb/libskypeweb.h @@ -0,0 +1,127 @@ + + +#ifndef LIBSKYPEWEB_H +#define LIBSKYPEWEB_H + +/* Maximum number of simultaneous connections to a server */ +#define SKYPEWEB_MAX_CONNECTIONS 16 + +#include <glib.h> + +#include <errno.h> +#include <string.h> +#include <glib/gi18n.h> +#include <sys/types.h> +#ifdef __GNUC__ + #include <unistd.h> +#endif + +#ifndef G_GNUC_NULL_TERMINATED +# if __GNUC__ >= 4 +# define G_GNUC_NULL_TERMINATED __attribute__((__sentinel__)) +# else +# define G_GNUC_NULL_TERMINATED +# endif /* __GNUC__ >= 4 */ +#endif /* G_GNUC_NULL_TERMINATED */ + +#ifdef _WIN32 +# include "win32dep.h" +# define dlopen(a,b) LoadLibrary(a) +# define RTLD_LAZY +# define dlsym(a,b) GetProcAddress(a,b) +# define dlclose(a) FreeLibrary(a) +#else +# include <arpa/inet.h> +# include <dlfcn.h> +# include <netinet/in.h> +# include <sys/socket.h> +#endif + +#include <json-glib/json-glib.h> + +#ifndef PURPLE_PLUGINS +# define PURPLE_PLUGINS +#endif + +#include "accountopt.h" +#include "blist.h" +#include "core.h" +#include "connection.h" +#include "debug.h" +#include "dnsquery.h" +#include "proxy.h" +#include "prpl.h" +#include "request.h" +#include "savedstatuses.h" +#include "sslconn.h" +#include "util.h" +#include "version.h" + +#if GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 12 +# define atoll(a) g_ascii_strtoll(a, NULL, 0) +#endif + +#if !PURPLE_VERSION_CHECK(3, 0, 0) + #define purple_connection_error purple_connection_error_reason + #define purple_notify_user_info_add_pair_html purple_notify_user_info_add_pair +#endif + +#define SKYPEWEB_MAX_MSG_RETRY 2 + +#define SKYPEWEB_PLUGIN_ID "prpl-skypeweb" +#define SKYPEWEB_PLUGIN_VERSION "0.1" + +#define SKYPEWEB_LOCKANDKEY_APPID "msmsgs@msnmsgr.com" +#define SKYPEWEB_LOCKANDKEY_SECRET "Q1P7W2E4J9R8U3S5" + +#define SKYPEWEB_CONTACTS_HOST "api.skype.com" +#define SKYPEWEB_MESSAGES_HOST "client-s.gateway.messenger.live.com" +#define SKYPEWEB_LOGIN_HOST "login.skype.com" + +typedef struct _SkypeWebAccount SkypeWebAccount; +typedef struct _SkypeWebBuddy SkypeWebBuddy; + +typedef void (*SkypeWebFunc)(SkypeWebAccount *swa); + +struct _SkypeWebAccount { + PurpleAccount *account; + PurpleConnection *pc; + GSList *conns; /**< A list of all active SkypeWebConnections */ + GQueue *waiting_conns; /**< A list of all SkypeWebConnections waiting to process */ + GSList *dns_queries; + GHashTable *cookie_table; + GHashTable *hostname_ip_cache; + + GHashTable *sent_messages_hash; + guint poll_timeout; + guint watchdog_timeout; + + guint authcheck_timeout; + + gchar *skype_token; + gchar *registration_token; +}; + +struct _SkypeWebBuddy { + SkypeWebAccount *sa; + PurpleBuddy *buddy; + + /** Contact info */ + gchar *skypename; + gchar *fullname; + gchar *display_name; + gboolean authorized; + gboolean blocked; + + /** Profile info */ + gchar *avatar_url; + gchar *mood; + gchar *rich_mood; + gchar *country; + gchar *city; +}; + + +void skypeweb_do_all_the_things(SkypeWebAccount *sa); + +#endif /* LIBSKYPEWEB_H */ diff --git a/skypeweb/skypeweb_connection.c b/skypeweb/skypeweb_connection.c new file mode 100644 index 0000000..0a68adb --- /dev/null +++ b/skypeweb/skypeweb_connection.c @@ -0,0 +1,772 @@ + + +#include "skypeweb_connection.h" + +#if !PURPLE_VERSION_CHECK(3, 0, 0) + #define purple_connection_error purple_connection_error_reason +#endif + +#if !GLIB_CHECK_VERSION (2, 22, 0) +#define g_hostname_is_ip_address(hostname) (g_ascii_isdigit(hostname[0]) && g_strstr_len(hostname, 4, ".")) +#endif + +static void skypeweb_attempt_connection(SkypeWebConnection *); +static void skypeweb_next_connection(SkypeWebAccount *sa); + +#include <zlib.h> + +static gchar *skypeweb_gunzip(const guchar *gzip_data, gssize *len_ptr) +{ + gsize gzip_data_len = *len_ptr; + z_stream zstr; + int gzip_err = 0; + gchar *data_buffer; + gulong gzip_len = G_MAXUINT16; + GString *output_string = NULL; + + data_buffer = g_new0(gchar, gzip_len); + + zstr.next_in = NULL; + zstr.avail_in = 0; + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + zstr.opaque = 0; + gzip_err = inflateInit2(&zstr, MAX_WBITS+32); + if (gzip_err != Z_OK) + { + g_free(data_buffer); + purple_debug_error("skypeweb", "no built-in gzip support in zlib\n"); + return NULL; + } + + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + + if (gzip_err == Z_DATA_ERROR) + { + inflateEnd(&zstr); + inflateInit2(&zstr, -MAX_WBITS); + if (gzip_err != Z_OK) + { + g_free(data_buffer); + purple_debug_error("skypeweb", "Cannot decode gzip header\n"); + return NULL; + } + zstr.next_in = (Bytef *)gzip_data; + zstr.avail_in = gzip_data_len; + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + } + output_string = g_string_new(""); + while (gzip_err == Z_OK) + { + //append data to buffer + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + //reset buffer pointer + zstr.next_out = (Bytef *)data_buffer; + zstr.avail_out = gzip_len; + gzip_err = inflate(&zstr, Z_SYNC_FLUSH); + } + if (gzip_err == Z_STREAM_END) + { + output_string = g_string_append_len(output_string, data_buffer, gzip_len - zstr.avail_out); + } else { + purple_debug_error("skypeweb", "gzip inflate error\n"); + } + inflateEnd(&zstr); + + g_free(data_buffer); + + if (len_ptr) + *len_ptr = output_string->len; + + return g_string_free(output_string, FALSE); +} + +void +skypeweb_connection_close(SkypeWebConnection *skypewebcon) +{ + skypewebcon->sa->conns = g_slist_remove(skypewebcon->sa->conns, skypewebcon); + + if (skypewebcon->connect_data != NULL) { + purple_proxy_connect_cancel(skypewebcon->connect_data); + skypewebcon->connect_data = NULL; + } + + if (skypewebcon->ssl_conn != NULL) { + purple_ssl_close(skypewebcon->ssl_conn); + skypewebcon->ssl_conn = NULL; + } + + if (skypewebcon->fd >= 0) { + close(skypewebcon->fd); + skypewebcon->fd = -1; + } + + if (skypewebcon->input_watcher > 0) { + purple_input_remove(skypewebcon->input_watcher); + skypewebcon->input_watcher = 0; + } + + purple_timeout_remove(skypewebcon->timeout_watcher); + + g_free(skypewebcon->rx_buf); + skypewebcon->rx_buf = NULL; + skypewebcon->rx_len = 0; +} + +void skypeweb_connection_destroy(SkypeWebConnection *skypewebcon) +{ + skypeweb_connection_close(skypewebcon); + + if (skypewebcon->request != NULL) + g_string_free(skypewebcon->request, TRUE); + + g_free(skypewebcon->url); + g_free(skypewebcon->hostname); + g_free(skypewebcon); +} + +static void skypeweb_update_cookies(SkypeWebAccount *sa, const gchar *headers) +{ + const gchar *cookie_start; + const gchar *cookie_end; + gchar *cookie_name; + gchar *cookie_value; + int header_len; + + g_return_if_fail(headers != NULL); + + header_len = strlen(headers); + + /* look for the next "Set-Cookie: " */ + /* grab the data up until ';' */ + cookie_start = headers; + while ((cookie_start = strstr(cookie_start, "\r\nSet-Cookie: ")) && + (cookie_start - headers) < header_len) + { + cookie_start += 14; + cookie_end = strchr(cookie_start, '='); + cookie_name = g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end + 1; + cookie_end = strchr(cookie_start, ';'); + cookie_value= g_strndup(cookie_start, cookie_end-cookie_start); + cookie_start = cookie_end; + + g_hash_table_replace(sa->cookie_table, cookie_name, + cookie_value); + } +} + +static void skypeweb_connection_process_data(SkypeWebConnection *skypewebcon) +{ + gssize len; + gchar *tmp; + + len = skypewebcon->rx_len; + tmp = g_strstr_len(skypewebcon->rx_buf, len, "\r\n\r\n"); + if (tmp == NULL) { + /* This is a corner case that occurs when the connection is + * prematurely closed either on the client or the server. + * This can either be no data at all or a partial set of + * headers. We pass along the data to be good, but don't + * do any fancy massaging. In all likelihood the result will + * be tossed by the connection callback func anyways + */ + tmp = g_strndup(skypewebcon->rx_buf, len); + } else { + tmp += 4; + len -= g_strstr_len(skypewebcon->rx_buf, len, "\r\n\r\n") - + skypewebcon->rx_buf + 4; + tmp = g_memdup(tmp, len + 1); + tmp[len] = '\0'; + skypewebcon->rx_buf[skypewebcon->rx_len - len] = '\0'; + skypeweb_update_cookies(skypewebcon->sa, skypewebcon->rx_buf); + + if (strstr(skypewebcon->rx_buf, "Content-Encoding: gzip")) + { + /* we've received compressed gzip data, decompress */ + gchar *gunzipped; + gunzipped = skypeweb_gunzip((const guchar *)tmp, &len); + g_free(tmp); + tmp = gunzipped; + } + } + + g_free(skypewebcon->rx_buf); + skypewebcon->rx_buf = NULL; + + if (skypewebcon->callback != NULL) { + if (!len) + { + purple_debug_error("skypeweb", "No data in response\n"); + //skypewebcon->callback(skypewebcon->sa, NULL, skypewebcon->user_data); + } else { + JsonParser *parser = json_parser_new(); + if (!json_parser_load_from_data(parser, tmp, len, NULL)) + { + if (skypewebcon->error_callback != NULL) { + skypewebcon->error_callback(skypewebcon->sa, tmp, len, skypewebcon->user_data); + } else { + purple_debug_error("skypeweb", "Error parsing response: %s\n", tmp); + } + } else { + JsonNode *root = json_parser_get_root(parser); + + //TODO + purple_debug_info("skypeweb", "Got response: %s\n", tmp); + purple_debug_info("skypeweb", "executing callback for %s\n", skypewebcon->url); + skypewebcon->callback(skypewebcon->sa, root, skypewebcon->user_data); + } + g_object_unref(parser); + } + } + + g_free(tmp); +} + +static void skypeweb_fatal_connection_cb(SkypeWebConnection *skypewebcon) +{ + PurpleConnection *pc = skypewebcon->sa->pc; + + purple_debug_error("skypeweb", "fatal connection error\n"); + + skypeweb_connection_destroy(skypewebcon); + + /* We died. Do not pass Go. Do not collect $200 */ + /* In all seriousness, don't attempt to call the normal callback here. + * That may lead to the wrong error message being displayed */ + purple_connection_error(pc, + PURPLE_CONNECTION_ERROR_NETWORK_ERROR, + _("Server closed the connection.")); + +} + +static void skypeweb_post_or_get_readdata_cb(gpointer data, gint source, + PurpleInputCondition cond) +{ + SkypeWebConnection *skypewebcon; + SkypeWebAccount *sa; + gchar buf[4096]; + gssize len; + + skypewebcon = data; + sa = skypewebcon->sa; + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL) { + len = purple_ssl_read(skypewebcon->ssl_conn, + buf, sizeof(buf) - 1); + } else { + len = recv(skypewebcon->fd, buf, sizeof(buf) - 1, 0); + } + + if (len < 0) + { + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + /* Try again later */ + return; + } + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL && skypewebcon->rx_len > 0) { + /* + * This is a slightly hacky workaround for a bug in either + * GNU TLS or in the SSL implementation on skypeweb's web + * servers. The sequence of events is: + * 1. We attempt to read the first time and successfully read + * the server's response. + * 2. We attempt to read a second time and libpurple's call + * to gnutls_record_recv() returns the error + * GNUTLS_E_UNEXPECTED_PACKET_LENGTH, or + * "A TLS packet with unexpected length was received." + * + * Normally the server would have closed the connection + * cleanly and this second read() request would have returned + * 0. Or maybe it's normal for SSL connections to be severed + * in this manner? In any case, this differs from the behavior + * of the standard recv() system call. + */ + purple_debug_warning("skypeweb", + "ssl error, but data received. attempting to continue\n"); + } else { + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_fatal_connection_cb(skypewebcon); + } + return; + } + } + + if (len > 0) + { + buf[len] = '\0'; + + skypewebcon->rx_buf = g_realloc(skypewebcon->rx_buf, + skypewebcon->rx_len + len + 1); + memcpy(skypewebcon->rx_buf + skypewebcon->rx_len, buf, len + 1); + skypewebcon->rx_len += len; + + /* Wait for more data before processing */ + return; + } + + /* The server closed the connection, let's parse the data */ + skypeweb_connection_process_data(skypewebcon); + + skypeweb_connection_destroy(skypewebcon); + + skypeweb_next_connection(sa); +} + +static void skypeweb_post_or_get_ssl_readdata_cb (gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) +{ + skypeweb_post_or_get_readdata_cb(data, -1, cond); +} + +static void skypeweb_post_or_get_connect_cb(gpointer data, gint source, + const gchar *error_message) +{ + SkypeWebConnection *skypewebcon; + gssize len; + + skypewebcon = data; + skypewebcon->connect_data = NULL; + + if (error_message) + { + purple_debug_error("skypeweb", "post_or_get_connect failure to %s\n", skypewebcon->url); + purple_debug_error("skypeweb", "post_or_get_connect_cb %s\n", + error_message); + skypeweb_fatal_connection_cb(skypewebcon); + return; + } + + skypewebcon->fd = source; + + /* TODO: Check the return value of write() */ + len = write(skypewebcon->fd, skypewebcon->request->str, + skypewebcon->request->len); + skypewebcon->input_watcher = purple_input_add(skypewebcon->fd, + PURPLE_INPUT_READ, + skypeweb_post_or_get_readdata_cb, skypewebcon); +} + +static void skypeweb_post_or_get_ssl_connect_cb(gpointer data, + PurpleSslConnection *ssl, PurpleInputCondition cond) +{ + SkypeWebConnection *skypewebcon; + gssize len; + + skypewebcon = data; + + purple_debug_info("skypeweb", "post_or_get_ssl_connect_cb\n"); + + /* TODO: Check the return value of write() */ + len = purple_ssl_write(skypewebcon->ssl_conn, + skypewebcon->request->str, skypewebcon->request->len); + purple_ssl_input_add(skypewebcon->ssl_conn, + skypeweb_post_or_get_ssl_readdata_cb, skypewebcon); +} + +static void skypeweb_host_lookup_cb(GSList *hosts, gpointer data, + const char *error_message) +{ + GSList *host_lookup_list; + struct sockaddr_in *addr; + gchar *hostname; + gchar *ip_address; + SkypeWebAccount *sa; + PurpleDnsQueryData *query; + + /* Extract variables */ + host_lookup_list = data; + + sa = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + hostname = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + query = host_lookup_list->data; + host_lookup_list = + g_slist_delete_link(host_lookup_list, host_lookup_list); + + /* The callback has executed, so we no longer need to keep track of + * the original query. This always needs to run when the cb is + * executed. */ + sa->dns_queries = g_slist_remove(sa->dns_queries, query); + + /* Any problems, capt'n? */ + if (error_message != NULL) + { + purple_debug_warning("skypeweb", + "Error doing host lookup: %s\n", error_message); + return; + } + + if (hosts == NULL) + { + purple_debug_warning("skypeweb", + "Could not resolve host name\n"); + return; + } + + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Copy the address then free it... */ + addr = hosts->data; + ip_address = g_strdup(inet_ntoa(addr->sin_addr)); + g_free(addr); + hosts = g_slist_delete_link(hosts, hosts); + + /* + * DNS lookups can return a list of IP addresses, but we only cache + * the first one. So free the rest. + */ + while (hosts != NULL) + { + /* Discard the length... */ + hosts = g_slist_delete_link(hosts, hosts); + /* Free the address... */ + g_free(hosts->data); + hosts = g_slist_delete_link(hosts, hosts); + } + + g_hash_table_insert(sa->hostname_ip_cache, hostname, ip_address); +} + +static void skypeweb_cookie_foreach_cb(gchar *cookie_name, + gchar *cookie_value, GString *str) +{ + /* TODO: Need to escape name and value? */ + g_string_append_printf(str, "%s=%s;", cookie_name, cookie_value); +} + +/** + * Serialize the sa->cookie_table hash table to a string. + */ +gchar *skypeweb_cookies_to_string(SkypeWebAccount *sa) +{ + GString *str; + + str = g_string_new(NULL); + + g_hash_table_foreach(sa->cookie_table, + (GHFunc)skypeweb_cookie_foreach_cb, str); + + return g_string_free(str, FALSE); +} + +static void skypeweb_ssl_connection_error(PurpleSslConnection *ssl, + PurpleSslErrorType errortype, gpointer data) +{ + SkypeWebConnection *skypewebcon = data; + SkypeWebAccount *sa = skypewebcon->sa; + PurpleConnection *pc = sa->pc; + + skypewebcon->ssl_conn = NULL; + + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_connection_destroy(skypewebcon); + purple_connection_ssl_error(pc, errortype); + } +} + +SkypeWebConnection * +skypeweb_post_or_get(SkypeWebAccount *sa, SkypeWebMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + SkypeWebProxyCallbackFunc callback_func, gpointer user_data, + gboolean keepalive) +{ + GString *request; + gchar *cookies; + SkypeWebConnection *skypewebcon; + gchar *real_url; + gboolean is_proxy = FALSE; + const gchar *user_agent; + const gchar* const *languages; + gchar *language_names; + PurpleProxyInfo *proxy_info = NULL; + gchar *proxy_auth; + gchar *proxy_auth_base64; + + /* TODO: Fix keepalive and use it as much as possible */ + keepalive = FALSE; + + if (host == NULL) + host = "api.skype.com"; + + if (sa && sa->account) + { + if (purple_account_get_bool(sa->account, "use-https", TRUE)) + method |= SKYPEWEB_METHOD_SSL; + } + + if (sa && sa->account && !(method & SKYPEWEB_METHOD_SSL)) + { + proxy_info = purple_proxy_get_setup(sa->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + { + is_proxy = TRUE; + } + } + if (is_proxy == TRUE) + { + real_url = g_strdup_printf("http://%s%s", host, url); + } else { + real_url = g_strdup(url); + } + + cookies = skypeweb_cookies_to_string(sa); + //user_agent = purple_account_get_string(sa->account, "user-agent", "SkypeWeb 1.2.0 / iPhone"); + + if (method & SKYPEWEB_METHOD_POST && !postdata) + postdata = ""; + + /* Build the request */ + request = g_string_new(NULL); + g_string_append_printf(request, "%s %s HTTP/1.0\r\n", + ((method & SKYPEWEB_METHOD_POST) ? "POST" : ((method & SKYPEWEB_METHOD_PUT) ? "PUT" : ((method & SKYPEWEB_METHOD_DELETE) ? "DELETE" : "GET"))), + real_url); + if (is_proxy == FALSE) + g_string_append_printf(request, "Host: %s\r\n", host); + g_string_append_printf(request, "Connection: %s\r\n", + (keepalive ? "Keep-Alive" : "close")); + //g_string_append_printf(request, "User-Agent: %s\r\n", user_agent); + if (method & SKYPEWEB_METHOD_POST) { + + if (postdata && postdata[0] == '[' || postdata[0] == '{') { + g_string_append(request, "Content-Type: application/json\r\n"); // hax + } else { + g_string_append_printf(request, "Content-Type: application/x-www-form-urlencoded\r\n"); + } + g_string_append_printf(request, "Content-length: %zu\r\n", strlen(postdata)); + } + g_string_append_printf(request, "Accept: */*\r\n"); + + if (g_str_equal(host, SKYPEWEB_CONTACTS_HOST)) { + g_string_append_printf(request, "X-Skypetoken: %s\r\n", sa->skype_token); + g_string_append(request, "X-Stratus-Caller: swx-skype.com\r\n"); + g_string_append(request, "X-Stratus-Request: abcd1234\r\n"); + g_string_append(request, "Origin: https://web.skype.com\r\n"); + g_string_append(request, "Referer: https://web.skype.com/main\r\n"); + g_string_append(request, "Accept: application/json; ver=1.0;\r\n"); + } else if (g_str_equal(host, SKYPEWEB_MESSAGES_HOST)) { + g_string_append_printf(request, "RegistrationToken: %s\r\n", sa->registration_token); + g_string_append(request, "Referer: https://web.skype.com/main\r\n"); + g_string_append(request, "Accept: application/json; ver=1.0;\r\n"); + g_string_append(request, "ClientInfo: os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=swx-skype.com; clientVer=908/1.0.0.20\r\n"); + } + + //g_string_append_printf(request, "Cookie: %s\r\n", cookies); + g_string_append_printf(request, "Accept-Encoding: gzip\r\n"); + if (is_proxy == TRUE) + { + if (purple_proxy_info_get_username(proxy_info) && + purple_proxy_info_get_password(proxy_info)) + { + proxy_auth = g_strdup_printf("%s:%s", purple_proxy_info_get_username(proxy_info), purple_proxy_info_get_password(proxy_info)); + proxy_auth_base64 = purple_base64_encode((guchar *)proxy_auth, strlen(proxy_auth)); + g_string_append_printf(request, "Proxy-Authorization: Basic %s\r\n", proxy_auth_base64); + g_free(proxy_auth_base64); + g_free(proxy_auth); + } + } + + /* Tell the server what language we accept, so that we get error messages in our language (rather than our IP's) */ + languages = g_get_language_names(); + language_names = g_strjoinv(", ", (gchar **)languages); + purple_util_chrreplace(language_names, '_', '-'); + g_string_append_printf(request, "Accept-Language: %s\r\n", language_names); + g_free(language_names); + + purple_debug_info("skypeweb", "getting url %s\n", url); + + g_string_append_printf(request, "\r\n"); + if (method & SKYPEWEB_METHOD_POST) + g_string_append_printf(request, "%s", postdata); + + /* If it needs to go over a SSL connection, we probably shouldn't print + * it in the debug log. Without this condition a user's password is + * printed in the debug log */ + if (method == SKYPEWEB_METHOD_POST) + purple_debug_info("skypeweb", "sending request data:\n%s\n", postdata); + + purple_debug_misc("skypeweb", "sending headers:\n%s\n", request->str); + + g_free(cookies); + + skypewebcon = g_new0(SkypeWebConnection, 1); + skypewebcon->sa = sa; + skypewebcon->url = real_url; + skypewebcon->method = method; + skypewebcon->hostname = g_strdup(host); + skypewebcon->request = request; + skypewebcon->callback = callback_func; + skypewebcon->user_data = user_data; + skypewebcon->fd = -1; + skypewebcon->connection_keepalive = keepalive; + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + + return skypewebcon; +} + +static void skypeweb_next_connection(SkypeWebAccount *sa) +{ + SkypeWebConnection *skypewebcon; + + g_return_if_fail(sa != NULL); + + if (!g_queue_is_empty(sa->waiting_conns)) + { + if(g_slist_length(sa->conns) < SKYPEWEB_MAX_CONNECTIONS) + { + skypewebcon = g_queue_pop_tail(sa->waiting_conns); + skypeweb_attempt_connection(skypewebcon); + } + } +} + + +static gboolean +skypeweb_connection_timedout(gpointer userdata) +{ + SkypeWebConnection *skypewebcon = userdata; + SkypeWebAccount *sa = skypewebcon->sa; + + /* Try resend the request */ + skypewebcon->retry_count++; + if (skypewebcon->retry_count < 3) { + skypeweb_connection_close(skypewebcon); + skypewebcon->request_time = time(NULL); + + g_queue_push_head(sa->waiting_conns, skypewebcon); + skypeweb_next_connection(sa); + } else { + skypeweb_fatal_connection_cb(skypewebcon); + } + + return FALSE; +} + +static void skypeweb_attempt_connection(SkypeWebConnection *skypewebcon) +{ + gboolean is_proxy = FALSE; + SkypeWebAccount *sa = skypewebcon->sa; + PurpleProxyInfo *proxy_info = NULL; + + if (sa && sa->account && !(skypewebcon->method & SKYPEWEB_METHOD_SSL)) + { + proxy_info = purple_proxy_get_setup(sa->account); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_USE_GLOBAL) + proxy_info = purple_global_proxy_get_info(); + if (purple_proxy_info_get_type(proxy_info) == PURPLE_PROXY_HTTP) + { + is_proxy = TRUE; + } + } + +#if 0 + /* Connection to attempt retries. This code doesn't work perfectly, but + * remains here for future reference if needed */ + if (time(NULL) - skypewebcon->request_time > 5) { + /* We've continuously tried to remake this connection for a + * bit now. It isn't happening, sadly. Time to die. */ + purple_debug_error("skypeweb", "could not connect after retries\n"); + skypeweb_fatal_connection_cb(skypewebcon); + return; + } + + purple_debug_info("skypeweb", "making connection attempt\n"); + + /* TODO: If we're retrying the connection, consider clearing the cached + * DNS value. This will require some juggling with the hostname param */ + /* TODO/FIXME: This retries almost instantenously, which in some cases + * runs at blinding speed. Slow it down. */ + /* TODO/FIXME: this doesn't retry properly on non-ssl connections */ +#endif + + sa->conns = g_slist_prepend(sa->conns, skypewebcon); + + /* + * Do a separate DNS lookup for the given host name and cache it + * for next time. + * + * TODO: It would be better if we did this before we call + * purple_proxy_connect(), so we could re-use the result. + * Or even better: Use persistent HTTP connections for servers + * that we access continually. + * + * TODO: This cache of the hostname<-->IP address does not respect + * the TTL returned by the DNS server. We should expire things + * from the cache after some amount of time. + */ + if (!is_proxy && !(skypewebcon->method & SKYPEWEB_METHOD_SSL) && !g_hostname_is_ip_address(skypewebcon->hostname)) + { + /* Don't do this for proxy connections, since proxies do the DNS lookup */ + gchar *host_ip; + + host_ip = g_hash_table_lookup(sa->hostname_ip_cache, skypewebcon->hostname); + if (host_ip != NULL) { + g_free(skypewebcon->hostname); + skypewebcon->hostname = g_strdup(host_ip); + } else if (sa->account && !sa->account->disconnecting) { + GSList *host_lookup_list = NULL; + PurpleDnsQueryData *query; + + host_lookup_list = g_slist_prepend( + host_lookup_list, g_strdup(skypewebcon->hostname)); + host_lookup_list = g_slist_prepend( + host_lookup_list, sa); + + query = purple_dnsquery_a( +#if PURPLE_VERSION_CHECK(3, 0, 0) + skypewebcon->sa->account, +#endif + skypewebcon->hostname, 80, + skypeweb_host_lookup_cb, host_lookup_list); + sa->dns_queries = g_slist_prepend(sa->dns_queries, query); + host_lookup_list = g_slist_append(host_lookup_list, query); + } + } + + if (skypewebcon->method & SKYPEWEB_METHOD_SSL) { + skypewebcon->ssl_conn = purple_ssl_connect(sa->account, skypewebcon->hostname, + 443, skypeweb_post_or_get_ssl_connect_cb, + skypeweb_ssl_connection_error, skypewebcon); + } else { + skypewebcon->connect_data = purple_proxy_connect(NULL, sa->account, + skypewebcon->hostname, 80, skypeweb_post_or_get_connect_cb, skypewebcon); + } + + skypewebcon->timeout_watcher = purple_timeout_add_seconds(120, skypeweb_connection_timedout, skypewebcon); + + return; +} + diff --git a/skypeweb/skypeweb_connection.h b/skypeweb/skypeweb_connection.h new file mode 100644 index 0000000..c49da65 --- /dev/null +++ b/skypeweb/skypeweb_connection.h @@ -0,0 +1,95 @@ + +#ifndef SKYPEWEB_CONNECTION_H +#define SKYPEWEB_CONNECTION_H + +#include "libskypeweb.h" + +typedef void (*SkypeWebProxyCallbackFunc)(SkypeWebAccount *sa, JsonNode *node, gpointer user_data); +typedef void (*SkypeWebProxyCallbackErrorFunc)(SkypeWebAccount *sa, const gchar *data, gssize data_len, gpointer user_data); + +/* + * This is a bitmask. + */ +typedef enum +{ + SKYPEWEB_METHOD_GET = 0x0001, + SKYPEWEB_METHOD_POST = 0x0002, + SKYPEWEB_METHOD_PUT = 0x0004, + SKYPEWEB_METHOD_DELETE = 0x0008, + SKYPEWEB_METHOD_SSL = 0x1000, +} SkypeWebMethod; + +typedef struct _SkypeWebConnection SkypeWebConnection; +struct _SkypeWebConnection { + SkypeWebAccount *sa; + SkypeWebMethod method; + gchar *hostname; + gchar *url; + GString *request; + SkypeWebProxyCallbackFunc callback; + gpointer user_data; + char *rx_buf; + size_t rx_len; + PurpleProxyConnectData *connect_data; + PurpleSslConnection *ssl_conn; + int fd; + guint input_watcher; + gboolean connection_keepalive; + time_t request_time; + guint retry_count; + guint timeout_watcher; + SkypeWebProxyCallbackErrorFunc error_callback; +}; + +void skypeweb_connection_destroy(SkypeWebConnection *skypewebcon); +void skypeweb_connection_close(SkypeWebConnection *skypewebcon); +SkypeWebConnection *skypeweb_post_or_get(SkypeWebAccount *sa, SkypeWebMethod method, + const gchar *host, const gchar *url, const gchar *postdata, + SkypeWebProxyCallbackFunc callback_func, gpointer user_data, + gboolean keepalive); +gchar *skypeweb_cookies_to_string(SkypeWebAccount *sa); + + + +/* Hack needed to stop redirect */ +struct _PurpleUtilFetchUrlData +{ + PurpleUtilFetchUrlCallback callback; + void *user_data; + + struct + { + char *user; + char *passwd; + char *address; + int port; + char *page; + + } website; + + char *url; + int num_times_redirected; + gboolean full; + char *user_agent; + gboolean http11; + char *request; + gsize request_written; + gboolean include_headers; + + gboolean is_ssl; + PurpleSslConnection *ssl_connection; + PurpleProxyConnectData *connect_data; + int fd; + guint inpa; + + gboolean got_headers; + gboolean has_explicit_data_len; + char *webdata; + gsize len; + unsigned long data_len; + gssize max_len; + gboolean chunked; + PurpleAccount *account; +}; + +#endif /* SKYPEWEB_CONNECTION_H */ diff --git a/skypeweb/skypeweb_contacts.c b/skypeweb/skypeweb_contacts.c new file mode 100644 index 0000000..e4be388 --- /dev/null +++ b/skypeweb/skypeweb_contacts.c @@ -0,0 +1,462 @@ +
+
+#include "skypeweb_contacts.h"
+#include "skypeweb_connection.h"
+#include "skypeweb_util.h"
+
+static guint active_icon_downloads = 0;
+
+static void
+skypeweb_get_icon_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+ PurpleBuddy *buddy = user_data;
+ SkypeWebBuddy *sbuddy;
+
+ if (!buddy || !buddy->proto_data)
+ return;
+
+ sbuddy = buddy->proto_data;
+
+ purple_buddy_icons_set_for_user(buddy->account, buddy->name, g_memdup(url_text, len), len, sbuddy->avatar_url);
+
+ active_icon_downloads--;
+}
+
+static void
+skypeweb_get_icon_now(PurpleBuddy *buddy)
+{
+ SkypeWebBuddy *sbuddy;
+ gchar *url;
+
+ purple_debug_info("skypeweb", "getting new buddy icon for %s\n", buddy->name);
+
+ sbuddy = buddy->proto_data;
+ if (sbuddy->avatar_url) {
+ url = g_strdup(sbuddy->avatar_url);
+ } else {
+ url = g_strdup_printf("https://api.skype.com/users/%s/profile/avatar", purple_url_encode(buddy->name));
+ }
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ purple_util_fetch_url_request(buddy->account, url, TRUE, NULL, FALSE, NULL, FALSE, -1, skypeweb_get_icon_cb, buddy);
+#else
+ purple_util_fetch_url_request(url, TRUE, NULL, FALSE, NULL, FALSE, skypeweb_get_icon_cb, buddy);
+#endif
+
+ g_free(url);
+
+ active_icon_downloads++;
+}
+
+static gboolean
+skypeweb_get_icon_queuepop(gpointer data)
+{
+ PurpleBuddy *buddy = data;
+
+ // Only allow 4 simultaneous downloads
+ if (active_icon_downloads > 4)
+ return TRUE;
+
+ skypeweb_get_icon_now(buddy);
+ return FALSE;
+}
+
+void
+skypeweb_get_icon(PurpleBuddy *buddy)
+{
+ if (!buddy) return;
+
+ purple_timeout_add(100, skypeweb_get_icon_queuepop, (gpointer)buddy);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+void
+skypeweb_search_results_add_buddy(PurpleConnection *pc, GList *row, void *user_data)
+{
+ PurpleAccount *account = purple_connection_get_account(pc);
+
+ if (!purple_find_buddy(account, g_list_nth_data(row, 0)))
+ purple_blist_request_add_buddy(account, g_list_nth_data(row, 0), "Skype", g_list_nth_data(row, 1));
+}
+
+void
+skypeweb_search_users_text_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *resultsarray = NULL;
+ guint index, length;
+ GString *userids;
+ gchar *search_term = user_data;
+
+ PurpleNotifySearchResults *results;
+ PurpleNotifySearchColumn *column;
+
+
+ length = json_array_get_length(resultsarray);
+
+ if (length == 0)
+ {
+ gchar *primary_text = g_strdup_printf("Your search for the user \"%s\" returned no results", search_term);
+ purple_notify_warning(sa->pc, "No users found", primary_text, "");
+ g_free(primary_text);
+ g_free(search_term);
+ return;
+ }
+
+ userids = g_string_new("");
+
+ resultsarray = json_node_get_array(node);
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *result = json_array_get_object_element(resultsarray, index);
+ g_string_append_printf(userids, "%s,", json_object_get_string_member(result, "skypewebid"));
+ }
+
+
+ results = purple_notify_searchresults_new();
+ if (results == NULL)
+ {
+ g_free(search_term);
+ return;
+ }
+
+ /* columns: Friend ID, Name, Network */
+ column = purple_notify_searchresults_column_new(_("Skype Name"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("Display Name"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("City"));
+ purple_notify_searchresults_column_add(results, column);
+ column = purple_notify_searchresults_column_new(_("Country"));
+ purple_notify_searchresults_column_add(results, column);
+
+ purple_notify_searchresults_button_add(results,
+ PURPLE_NOTIFY_BUTTON_ADD,
+ skypeweb_search_results_add_buddy);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *contact = json_array_get_object_element(resultsarray, index);
+ JsonObject *contactcards = json_object_get_object_member(contact, "ContactCards");
+ JsonObject *skypecontact = json_object_get_object_member(contactcards, "Skype");
+ JsonObject *currentlocation = json_object_get_object_member(contactcards, "CurrentLocation");
+
+ /* the row in the search results table */
+ /* prepend to it backwards then reverse to speed up adds */
+ GList *row = NULL;
+
+ row = g_list_prepend(row, g_strdup(json_object_get_string_member(skypecontact, "SkypeName")));
+ row = g_list_prepend(row, g_strdup(json_object_get_string_member(skypecontact, "DisplayName")));
+ row = g_list_prepend(row, g_strdup(json_object_get_string_member(currentlocation, "City")));
+ row = g_list_prepend(row, g_strdup(json_object_get_string_member(currentlocation, "Country")));
+
+ row = g_list_reverse(row);
+
+ purple_notify_searchresults_row_add(results, row);
+ }
+
+ purple_notify_searchresults(sa->pc, NULL, search_term, NULL,
+ results, NULL, NULL);
+}
+
+void
+skypeweb_search_users_text(gpointer user_data, const gchar *text)
+{
+ SkypeWebAccount *sa = user_data;
+ GString *url = g_string_new("/search/users/any?");
+
+ g_string_append_printf(url, "keyWord=%s&", purple_url_encode(text));
+ g_string_append(url, "contactTypes[]=skype&");
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url->str, NULL, skypeweb_search_users_text_cb, g_strdup(text), FALSE);
+
+ g_string_free(url, TRUE);
+}
+
+void
+skypeweb_search_users(PurplePluginAction *action)
+{
+ PurpleConnection *pc = (PurpleConnection *) action->context;
+ SkypeWebAccount *sa = pc->proto_data;
+
+ purple_request_input(pc, "Search for Skype Friends",
+ "Search for Skype Friends",
+ NULL,
+ NULL, FALSE, FALSE, NULL,
+ _("_Search"), G_CALLBACK(skypeweb_search_users_text),
+ _("_Cancel"), NULL,
+ purple_connection_get_account(pc), NULL, NULL,
+ sa);
+
+}
+
+
+
+
+static void
+skypeweb_got_friend_profiles(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *contacts;
+ PurpleBuddy *buddy;
+ SkypeWebBuddy *sbuddy;
+ guint index;
+ guint length;
+
+ contacts = json_node_get_array(node);
+ length = json_array_get_length(contacts);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *contact = json_array_get_object_element(contacts, index);
+
+ const gchar *username = json_object_get_string_member(contact, "username");
+ const gchar *new_avatar;
+
+ buddy = purple_find_buddy(sa->account, username);
+ if (!buddy)
+ continue;
+ sbuddy = buddy->proto_data;
+ if (sbuddy == NULL)
+ {
+ sbuddy = g_new0(SkypeWebBuddy, 1);
+ buddy->proto_data = sbuddy;
+ sbuddy->skypename = g_strdup(username);
+ }
+
+ g_free(sbuddy->display_name); sbuddy->display_name = g_strdup(json_object_get_string_member(contact, "displayname"));
+ serv_got_alias(sa->pc, username, sbuddy->display_name);
+
+ new_avatar = json_object_get_string_member(contact, "avatarUrl");
+ if (new_avatar && (!sbuddy->avatar_url || !g_str_equal(sbuddy->avatar_url, new_avatar))) {
+ g_free(sbuddy->avatar_url);
+ sbuddy->avatar_url = g_strdup(new_avatar);
+ skypeweb_get_icon(buddy);
+ }
+
+ g_free(sbuddy->mood); sbuddy->mood = g_strdup(json_object_get_string_member(contact, "mood"));
+ g_free(sbuddy->rich_mood); sbuddy->rich_mood = g_strdup(json_object_get_string_member(contact, "richMood"));
+ g_free(sbuddy->country); sbuddy->country = g_strdup(json_object_get_string_member(contact, "country"));
+ g_free(sbuddy->city); sbuddy->city = g_strdup(json_object_get_string_member(contact, "city"));
+ }
+}
+
+void
+skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts)
+{
+ const gchar *profiles_url = "/users/SELF/contacts/profiles"; //TODO: Might need to be username instead of SELF
+ GString *postdata;
+ GSList *cur = contacts;
+
+ postdata = g_string_new("");
+
+ do {
+ g_string_append_printf(postdata, "&contacts[]=", purple_url_encode(cur->data));
+ } while((cur = g_slist_next(cur)));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, profiles_url, NULL, skypeweb_got_friend_profiles, NULL, TRUE);
+
+ g_string_free(postdata, TRUE);
+}
+
+void
+skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who)
+{
+ GSList *contacts = NULL;
+ gchar *whodup;
+
+ g_return_if_fail(sa && who && *who);
+
+ whodup = g_strdup(who);
+ contacts = g_slist_prepend(contacts, whodup);
+
+ skypeweb_get_friend_profiles(sa, contacts);
+
+ g_free(contacts);
+ g_free(whodup);
+}
+
+static void
+skypeweb_get_friend_list_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *friends;
+ PurpleGroup *group = NULL;
+ GSList *users_to_fetch = NULL;
+ guint index, length;
+
+ /* {
+ "skypename": "eionrobb",
+ "fullname": "Eion Robb",
+ "authorized": true,
+ "blocked": false,
+ "display_name": "eionrobb",
+ "pstn_number": null,
+ "phone1": null,
+ "phone1_label": null,
+ "phone2": null,
+ "phone2_label": null,
+ "phone3": null,
+ "phone3_label": null
+} */
+
+ friends = json_node_get_array(node);
+ length = json_array_get_length(friends);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *friend = json_array_get_object_element(friends, index);
+ const gchar *skypename = json_object_get_string_member(friend, "skypename");
+ const gchar *fullname = json_object_get_string_member(friend, "fullname");
+ const gchar *display_name = json_object_get_string_member(friend, "display_name");
+ gboolean authorized = json_object_get_boolean_member(friend, "authorized");
+ gboolean blocked = json_object_get_boolean_member(friend, "blocked");
+
+ if (!purple_find_buddy(sa->account, skypename))
+ {
+ if (!group)
+ {
+ group = purple_find_group("Skype");
+ if (!group)
+ {
+ group = purple_group_new("Skype");
+ purple_blist_add_group(group, NULL);
+ }
+ }
+ purple_blist_add_buddy(purple_buddy_new(sa->account, skypename, NULL), NULL, group, NULL);
+ }
+
+ SkypeWebBuddy *sbuddy = g_new0(SkypeWebBuddy, 1);
+ sbuddy->skypename = g_strdup(skypename);
+ sbuddy->fullname = g_strdup(fullname);
+ sbuddy->display_name = g_strdup(display_name);
+ sbuddy->authorized = authorized;
+ sbuddy->blocked = blocked;
+
+ serv_got_alias(sa->pc, skypename, sbuddy->display_name); //purple_blist_server_alias_buddy(buddy, string_parts[3]);
+
+ users_to_fetch = g_slist_prepend(users_to_fetch, (gpointer) skypename);
+ }
+
+ if (users_to_fetch)
+ {
+ skypeweb_get_friend_profiles(sa, users_to_fetch);
+ }
+ g_free(users_to_fetch);
+}
+
+void
+skypeweb_get_friend_list(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/contacts", NULL, skypeweb_get_friend_list_cb, NULL, TRUE);
+}
+
+
+
+void
+skypeweb_auth_accept_cb(gpointer sender)
+{
+ PurpleBuddy *buddy = sender;
+ SkypeWebAccount *sa;
+ gchar *url = NULL;
+
+ sa = buddy->account->gc->proto_data;
+
+ url = g_strdup_printf("/users/self/contacts/auth-request/%s/accept", purple_url_encode(buddy->name));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+}
+
+void
+skypeweb_auth_reject_cb(gpointer sender)
+{
+ PurpleBuddy *buddy = sender;
+ SkypeWebAccount *sa;
+ gchar *url = NULL;
+
+ sa = buddy->account->gc->proto_data;
+
+ url = g_strdup_printf("/users/self/contacts/auth-request/%s/reject", purple_url_encode(buddy->name));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, NULL, NULL, NULL, TRUE);
+
+ g_free(url);
+}
+
+static void
+skypeweb_got_authrequests(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *requests;
+ guint index, length;
+
+ requests = json_node_get_array(node);
+ /* [{
+ "event_time": "2014-10-21 18:53:53.763883",
+ "sender": "gretike1984",
+ "greeting": "Kedves Eion Robb! Szeretn\u00e9lek felvenni a partnerlist\u00e1mra."
+ }] */
+ length = json_array_get_length(requests);
+
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *request = json_array_get_object_element(requests, index);
+ const gchar *event_time = json_object_get_string_member(request, "event_time");
+ const gchar *sender = json_object_get_string_member(request, "sender");
+ const gchar *greeting = json_object_get_string_member(request, "greeting");
+
+ purple_account_request_authorization(
+ sa->account, sender, NULL,
+ NULL, greeting, FALSE,
+ skypeweb_auth_accept_cb, skypeweb_auth_reject_cb, purple_buddy_new(sa->account, sender, NULL));
+
+ }
+}
+
+gboolean
+skypeweb_check_authrequests(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, "/users/self/contacts/auth-request", NULL, skypeweb_got_authrequests, NULL, TRUE);
+ return TRUE;
+}
+
+
+void
+skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char* message)
+{
+ SkypeWebAccount *sa = pc->proto_data;
+ gchar *url, *postdata;
+
+ url = g_strdup_printf("/users/self/contacts/auth-request/%s", purple_url_encode(buddy->name));
+ postdata = g_strdup_printf("greeting=%s", message ? purple_url_encode(message) : "");
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_CONTACTS_HOST, url, postdata, NULL, NULL, TRUE);
+
+ g_free(postdata);
+ g_free(url);
+}
+
+void
+skypeweb_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ skypeweb_add_buddy_with_invite(gc, buddy, group, _("Please authorize me so I can add you to my buddy list."));
+}
+
+void
+skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group)
+{
+ SkypeWebAccount *sa = pc->proto_data;
+
+}
\ No newline at end of file diff --git a/skypeweb/skypeweb_contacts.h b/skypeweb/skypeweb_contacts.h new file mode 100644 index 0000000..c2e8095 --- /dev/null +++ b/skypeweb/skypeweb_contacts.h @@ -0,0 +1,21 @@ + +#ifndef SKYPEWEB_CONTACTS_H +#define SKYPEWEB_CONTACTS_H + +#include "libskypeweb.h" + +void skypeweb_get_icon(PurpleBuddy *buddy); +void skypeweb_search_users(PurplePluginAction *action); + +void skypeweb_get_friend_profiles(SkypeWebAccount *sa, GSList *contacts); +void skypeweb_get_friend_profile(SkypeWebAccount *sa, const gchar *who); + +void skypeweb_get_friend_list(SkypeWebAccount *sa); + +void skypeweb_buddy_remove(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group); +void skypeweb_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char* message); +void skypeweb_add_buddy(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group); + +gboolean skypeweb_check_authrequests(SkypeWebAccount *sa); + +#endif /* SKYPEWEB_CONTACTS_H */ diff --git a/skypeweb/skypeweb_login.c b/skypeweb/skypeweb_login.c new file mode 100644 index 0000000..fe42d50 --- /dev/null +++ b/skypeweb/skypeweb_login.c @@ -0,0 +1,108 @@ +#include "skypeweb_login.h"
+#include "skypeweb_util.h"
+
+static void
+skypeweb_login_did_auth(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+ gchar *refresh_token;
+ SkypeWebAccount *sa = user_data;
+
+ if (url_text == NULL) {
+ url_text = url_data->webdata;
+ len = url_data->data_len;
+ }
+
+ refresh_token = skypeweb_string_get_chunk(url_text, len, "Set-Cookie: refresh-token=", ";");
+ if (refresh_token == NULL) {
+ purple_debug_info("skypeweb", "login response was %s\r\n", url_text);
+ purple_connection_error (sa->pc,
+ PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+ _("Failed getting Skype Token"));
+ return;
+ }
+
+ sa->skype_token = refresh_token;
+
+ skypeweb_do_all_the_things(sa);
+}
+
+static void
+skypeweb_login_got_pie(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+ SkypeWebAccount *sa = user_data;
+ PurpleAccount *account = sa->account;
+ gchar *pie;
+ gchar *etm;
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login?message=signin_continue";
+ GString *postdata;
+ gchar *request;
+ struct timezone tz;
+ guint tzhours, tzminutes;
+ PurpleUtilFetchUrlData *requestdata;
+
+ gettimeofday(NULL, &tz);
+ tzminutes = tz.tz_minuteswest;
+ if (tzminutes < 0) tzminutes = -tzminutes;
+ tzhours = tzminutes / 60;
+ tzminutes -= tzhours * 60;
+
+ pie = skypeweb_string_get_chunk(url_text, len, "=\"pie\" value=\"", "\"");
+ if (!pie) {
+ return;
+ }
+
+ etm = skypeweb_string_get_chunk(url_text, len, "=\"etm\" value=\"", "\"");
+ if (!etm) {
+ return;
+ }
+
+
+ postdata = g_string_new("");
+ g_string_append_printf(postdata, "username=%s&", purple_url_encode(purple_account_get_username(account)));
+ g_string_append_printf(postdata, "password=%s&", purple_url_encode(purple_account_get_password(account)));
+ g_string_append_printf(postdata, "timezone_field=%c|%d|%d&", (tz.tz_minuteswest < 0 ? '+' : '-'), tzhours, tzminutes);
+ g_string_append_printf(postdata, "pie=%s&", purple_url_encode(pie));
+ g_string_append_printf(postdata, "etm=%s&", purple_url_encode(etm));
+ g_string_append_printf(postdata, "js_time=%d&", time(NULL));
+ g_string_append(postdata, "return_url=https://web.skype.com/");
+
+ request = g_strdup_printf("POST /login?message=signin_continue HTTP/1.0\r\n"
+ "Connection: close\r\n"
+ "Accept: */*\r\n"
+ "BehaviorOverride: redirectAs404\r\n"
+ "Host: " SKYPEWEB_LOGIN_HOST "\r\n"
+ "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n"
+ "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
+ strlen(postdata->str), postdata->str);
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ requestdata = purple_util_fetch_url_request(sa->account, login_url, TRUE, NULL, FALSE, request, TRUE, -1, skypeweb_login_did_auth, sa);
+#else
+ requestdata = purple_util_fetch_url_request(login_url, TRUE, NULL, FALSE, request, TRUE, skypeweb_login_did_auth, sa);
+#endif
+ requestdata->num_times_redirected = 10; /* Prevent following redirects */
+
+ g_string_free(postdata, TRUE);
+ g_free(request);
+}
+
+void
+skypeweb_begin_web_login(SkypeWebAccount *sa)
+{
+ const gchar *login_url = "https://" SKYPEWEB_LOGIN_HOST "/login";
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ purple_util_fetch_url_request(sa->account, login_url, TRUE, NULL, FALSE, NULL, FALSE, -1, skypeweb_login_got_pie, sa);
+#else
+ purple_util_fetch_url_request(login_url, TRUE, NULL, FALSE, NULL, FALSE, skypeweb_login_got_pie, sa);
+#endif
+
+ purple_connection_set_state(sa->pc, PURPLE_CONNECTING);
+ purple_connection_update_progress(sa->pc, _("Connecting"), 1, 3);
+}
+
+void
+skypeweb_logout(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_GET | SKYPEWEB_METHOD_SSL, SKYPEWEB_LOGIN_HOST, "/logout", NULL, NULL, NULL, TRUE);
+}
diff --git a/skypeweb/skypeweb_login.h b/skypeweb/skypeweb_login.h new file mode 100644 index 0000000..f7812e5 --- /dev/null +++ b/skypeweb/skypeweb_login.h @@ -0,0 +1,13 @@ + +#ifndef SKYPEWEB_LOGIN_H +#define SKYPEWEB_LOGIN_H + +#include "libskypeweb.h" +#include "skypeweb_connection.h" + +#include <util.h> + +void skypeweb_logout(SkypeWebAccount *sa); +void skypeweb_begin_web_login(SkypeWebAccount *sa); + +#endif /* SKYPEWEB_LOGIN_H */ diff --git a/skypeweb/skypeweb_messages.c b/skypeweb/skypeweb_messages.c new file mode 100644 index 0000000..2f1575b --- /dev/null +++ b/skypeweb/skypeweb_messages.c @@ -0,0 +1,325 @@ +#include "skypeweb_messages.h"
+#include "skypeweb_util.h"
+#include "skypeweb_connection.h"
+
+static void
+process_userpresence_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ const gchar *selfLink = json_object_get_string_member(resource, "selfLink");
+ const gchar *status = json_object_get_string_member(resource, "status");
+ const gchar *from;
+
+ from = skypeweb_contact_url_to_name(selfLink);
+
+ purple_prpl_got_user_status(sa->account, from, status, NULL);
+}
+
+static void
+process_message_resource(SkypeWebAccount *sa, JsonObject *resource)
+{
+ const gchar *messagetype = json_object_get_string_member(resource, "messagetype");
+ const gchar *from = json_object_get_string_member(resource, "from");
+ const gchar *content = json_object_get_string_member(resource, "content");
+ const gchar *composetime = json_object_get_string_member(resource, "composetime");
+ const gchar *conversationLink = json_object_get_string_member(resource, "conversationLink");
+ time_t composetimestamp = purple_str_to_time(composetime, TRUE, NULL, NULL, NULL);
+ gchar **messagetype_parts;
+
+ messagetype_parts = g_strsplit(messagetype, "/", -1);
+
+ from = skypeweb_contact_url_to_name(from);
+
+ if (json_object_has_member(resource, "threadtopic"))
+ return; //TODO multi user chat threads's
+
+ if (g_str_equal(messagetype_parts[0], "Control")) {
+ if (g_str_equal(messagetype_parts[1], "ClearTyping")) {
+ serv_got_typing(sa->pc, from, 20, PURPLE_NOT_TYPING);
+ } else if (g_str_equal(messagetype_parts[1], "Typing")) {
+ serv_got_typing(sa->pc, from, 20, PURPLE_TYPING);
+ }
+ } else if (g_str_equal(messagetype, "RichText") || g_str_equal(messagetype, "Text")) {
+ gchar *html;
+ if (g_str_equal(messagetype, "Text")) {
+ html = purple_markup_escape_text(content, -1);
+ } else {
+ html = g_strdup(content);
+ }
+ if (g_str_equal(sa->account->username, from)) {
+ from = skypeweb_contact_url_to_name(conversationLink);
+ PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, sa->account);
+ if (conv == NULL)
+ {
+ conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, sa->account, from);
+ }
+ purple_conversation_write(conv, from, html, PURPLE_MESSAGE_SEND, composetimestamp);
+ } else {
+ serv_got_im(sa->pc, from, html, PURPLE_MESSAGE_RECV, composetimestamp);
+ }
+ g_free(html);
+ } else {
+ purple_debug_warning("skypeweb", "Unknown message resource messagetype '%s'\n", messagetype);
+ }
+
+ g_strfreev(messagetype_parts);
+}
+
+gboolean
+skypeweb_timeout(gpointer userdata)
+{
+ SkypeWebAccount *sa = userdata;
+ skypeweb_poll(sa);
+
+ // If no response within 3 minutes, assume connection lost and try again
+ purple_timeout_remove(sa->watchdog_timeout);
+ sa->watchdog_timeout = purple_timeout_add_seconds(3 * 60, skypeweb_timeout, sa);
+
+ return FALSE;
+}
+
+static void
+skypeweb_poll_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ JsonArray *messages = NULL;
+ guint index, length;
+ JsonObject *obj;
+
+ obj = json_node_get_object(node);
+
+ if (json_object_has_member(obj, "eventMessages"))
+ messages = json_object_get_array_member(obj, "eventMessages");
+
+ if (messages != NULL) {
+ length = json_array_get_length(messages);
+ for(index = 0; index < length; index++)
+ {
+ JsonObject *message = json_array_get_object_element(messages, index);
+ const gchar *resourceType = json_object_get_string_member(message, "resourceType");
+ JsonObject *resource = json_object_get_object_member(message, "resource");
+
+ if (g_str_equal(resourceType, "NewMessage"))
+ {
+ process_message_resource(sa, resource);
+ } else if (g_str_equal(resourceType, "UserPresence"))
+ {
+ process_userpresence_resource(sa, resource);
+ } else if (g_str_equal(resourceType, "EndpointPresence"))
+ {
+ //process_endpointpresence_resource(sa, resource);
+ } else if (g_str_equal(resourceType, "ConversationUpdate"))
+ {
+ //process_conversation_resource(sa, resource);
+ } else if (g_str_equal(resourceType, "ThreadUpdate"))
+ {
+ //process_thread_resource(sa, resource);
+ }
+ }
+ }
+
+ //TODO record id of highest recieved id to make sure we dont process the same id twice
+
+ sa->poll_timeout = purple_timeout_add_seconds(1, skypeweb_timeout, sa);
+
+}
+
+void
+skypeweb_poll(SkypeWebAccount *sa)
+{
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_MESSAGES_HOST, "/v1/users/ME/endpoints/SELF/subscriptions/0/poll", NULL, skypeweb_poll_cb, NULL, TRUE);
+}
+
+
+static void
+skypeweb_subscribe_cb(SkypeWebAccount *sa, JsonNode *node, gpointer user_data)
+{
+ //{"id":"messagingService","type":"EndpointPresenceDoc","selfLink":"uri","privateInfo":{"epname":"skype"},"publicInfo":{"capabilities":"video|audio","type":1,"skypeNameVersion":"908/1.0.20/swx-skype.com","nodeInfo":"xx","version":"908/1.0.20"}}
+ // /v1/users/ME/endpoints/%7B60da4fa8-79f0-445b-846f-1f1e0116ecb5%7D/presenceDocs/messagingService
+
+ skypeweb_do_all_the_things(sa);
+}
+
+static void
+skypeweb_subscribe(SkypeWebAccount *sa)
+{
+ JsonObject *obj;
+ JsonArray *interested;
+ gchar *post;
+
+ interested = json_array_new();
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties");
+ json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/messages");
+ json_array_add_string_element(interested, "/v1/users/ME/contacts/ALL");
+ //json_array_add_string_element(interested, "/v1/users/ME/conversations/ALL/properties"); //TODO
+
+ obj = json_object_new();
+ json_object_set_array_member(obj, "interestedResources", interested);
+ json_object_set_string_member(obj, "template", "raw");
+ json_object_set_string_member(obj, "channelType", "httpLongPoll");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_MESSAGES_HOST, "/v1/users/ME/endpoints/SELF/subscriptions", post, skypeweb_subscribe_cb, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ json_array_unref(interested);
+}
+
+static void
+skypeweb_got_registration_token(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message)
+{
+ gchar *registration_token;
+ //gchar *endpointId;
+ SkypeWebAccount *sa = user_data;
+
+ if (url_text == NULL) {
+ url_text = url_data->webdata;
+ len = url_data->data_len;
+ }
+
+ registration_token = skypeweb_string_get_chunk(url_text, len, "Set-RegistrationToken: ", ";");
+ //endpointId = skypeweb_string_get_chunk(url_text, len, "endpointId=", "\r\n");
+
+ if (registration_token == NULL) {
+ purple_connection_error (sa->pc,
+ PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED,
+ _("Failed getting Registration Token"));
+ return;
+ }
+ purple_debug_info("skypeweb", "New RegistrationToken is %s\n", registration_token);
+
+ sa->registration_token = registration_token;
+
+ skypeweb_subscribe(sa);
+}
+
+void
+skypeweb_get_registration_token(SkypeWebAccount *sa)
+{
+ const gchar *messages_url = "https://" SKYPEWEB_MESSAGES_HOST "/v1/users/ME/endpoints";
+ gchar *request;
+ gchar *curtime;
+ gchar *response;
+ PurpleUtilFetchUrlData *requestdata;
+
+ g_free(sa->registration_token);
+ sa->registration_token = NULL;
+
+ curtime = g_strdup_printf("%d", (int) time(NULL));
+ response = skypeweb_hmac_sha256(curtime);
+
+ request = g_strdup_printf("POST /v1/users/ME/endpoints HTTP/1.0\r\n"
+ "Connection: close\r\n"
+ "Accept: */*\r\n"
+ "LockAndKey: appId=" SKYPEWEB_LOCKANDKEY_APPID "; time=%s; lockAndKeyResponse=%s\r\n"
+ "ClientInfo: os=Windows; osVer=8.1; proc=Win32; lcid=en-us; deviceType=1; country=n/a; clientName=swx-skype.com; clientVer=908/1.0.0.20\r\n"
+ "Host: " SKYPEWEB_MESSAGES_HOST "\r\n"
+ "Content-Type: application/json\r\n"
+ "Authentication: skypetoken=%s\r\n"
+ "Content-Length: 2\r\n\r\n{}",
+ curtime, response, sa->skype_token);
+
+ purple_debug_info("skypeweb", "reg token request is %s\n", request);
+
+#if PURPLE_VERSION_CHECK(3, 0, 0)
+ requestdata = purple_util_fetch_url_request(sa->account, messages_url, TRUE, NULL, FALSE, request, TRUE, -1, skypeweb_got_registration_token, sa);
+#else
+ requestdata = purple_util_fetch_url_request(messages_url, TRUE, NULL, FALSE, request, TRUE, skypeweb_got_registration_token, sa);
+#endif
+ requestdata->num_times_redirected = 10; /* Prevent following redirects */
+
+ g_free(request);
+ g_free(curtime);
+ g_free(response);
+}
+
+
+
+
+
+guint
+skypeweb_send_typing(PurpleConnection *pc, const gchar *name, PurpleTypingState state)
+{
+ SkypeWebAccount *sa = pc->proto_data;
+ gchar *post, *url;
+ JsonObject *obj;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/8:%s/messages", purple_url_encode(name));
+
+ obj = json_object_new();
+ json_object_set_int_member(obj, "clientmessageid", time(NULL));
+ json_object_set_string_member(obj, "content", "");
+ json_object_set_string_member(obj, "messagetype", state == PURPLE_TYPING ? "Control/Typing" : "Control/ClearTyping");
+ json_object_set_string_member(obj, "contenttype", "text");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_MESSAGES_HOST, url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(url);
+
+ return 20;
+}
+
+
+
+
+void
+skypeweb_set_status(PurpleAccount *account, PurpleStatus *status)
+{
+ PurpleConnection *pc = purple_account_get_connection(account);
+ SkypeWebAccount *sa = pc->proto_data;
+ gchar *post;
+
+ post = g_strdup_printf("{\"status\":\"%s\"}", purple_status_get_id(status));
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_PUT | SKYPEWEB_METHOD_SSL, SKYPEWEB_MESSAGES_HOST, "/v1/users/ME/presenceDocs/messagingService", post, NULL, NULL, TRUE);
+
+ g_free(post);
+}
+
+void
+skypeweb_set_idle(PurpleConnection *pc, int time)
+{
+ SkypeWebAccount *sa = pc->proto_data;
+
+ if (time < 30) {
+ //skypeweb_set_status(pc->account, active status);
+ } else {
+ //skypeweb_set_status(pc->account, idle status);
+ }
+}
+
+
+
+
+
+
+gint
+skypeweb_send_im(PurpleConnection *pc, const gchar *who, const gchar *msg,
+ PurpleMessageFlags flags)
+{
+ SkypeWebAccount *sa = pc->proto_data;
+ gchar *post, *url;
+ JsonObject *obj;
+
+ url = g_strdup_printf("/v1/users/ME/conversations/8:%s/messages", purple_url_encode(who));
+
+ obj = json_object_new();
+ json_object_set_int_member(obj, "clientmessageid", g_random_int_range(100000, 999999));
+ json_object_set_string_member(obj, "content", msg);
+ json_object_set_string_member(obj, "messagetype", "RichText");
+ json_object_set_string_member(obj, "contenttype", "text");
+
+ post = skypeweb_jsonobj_to_string(obj);
+
+ skypeweb_post_or_get(sa, SKYPEWEB_METHOD_POST | SKYPEWEB_METHOD_SSL, SKYPEWEB_MESSAGES_HOST, url, post, NULL, NULL, TRUE);
+
+ g_free(post);
+ json_object_unref(obj);
+ g_free(url);
+
+ return 1;
+}
\ No newline at end of file diff --git a/skypeweb/skypeweb_messages.h b/skypeweb/skypeweb_messages.h new file mode 100644 index 0000000..2fd39e0 --- /dev/null +++ b/skypeweb/skypeweb_messages.h @@ -0,0 +1,14 @@ + +#ifndef SKYPEWEB_MESSAGES_H +#define SKYPEWEB_MESSAGES_H + +#include "libskypeweb.h" + +gint skypeweb_send_im(PurpleConnection *pc, const gchar *who, const gchar *msg, PurpleMessageFlags flags); +void skypeweb_set_idle(PurpleConnection *pc, int time); +void skypeweb_set_status(PurpleAccount *account, PurpleStatus *status); +guint skypeweb_send_typing(PurpleConnection *pc, const gchar *name, PurpleTypingState state); +void skypeweb_poll(SkypeWebAccount *sa); +void skypeweb_get_registration_token(SkypeWebAccount *sa); + +#endif /* SKYPEWEB_MESSAGES_H */ diff --git a/skypeweb/skypeweb_util.c b/skypeweb/skypeweb_util.c new file mode 100644 index 0000000..3c53502 --- /dev/null +++ b/skypeweb/skypeweb_util.c @@ -0,0 +1,170 @@ +#include "skypeweb_util.h"
+
+#include <cipher.h>
+
+gchar *
+skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end)
+{
+ const gchar *chunk_start, *chunk_end;
+ g_return_val_if_fail(haystack && start && end, NULL);
+
+ if (len > 0) {
+ chunk_start = g_strstr_len(haystack, len, start);
+ } else {
+ chunk_start = strstr(haystack, start);
+ }
+ g_return_val_if_fail(chunk_start, NULL);
+ chunk_start += strlen(start);
+
+ if (len > 0) {
+ chunk_end = g_strstr_len(chunk_start, len - (chunk_start - haystack), end);
+ } else {
+ chunk_end = strstr(chunk_start, end);
+ }
+ g_return_val_if_fail(chunk_end, NULL);
+
+ return g_strndup(chunk_start, chunk_end - chunk_start);
+}
+
+gchar *
+skypeweb_jsonobj_to_string(JsonObject *jsonobj)
+{
+ JsonGenerator *generator;
+ JsonNode *root;
+ gchar *string;
+
+ root = json_node_new(JSON_NODE_OBJECT);
+ json_node_set_object(root, jsonobj);
+
+ generator = json_generator_new();
+ json_generator_set_root(generator, root);
+
+ string = json_generator_to_data(generator, NULL);
+
+ g_object_unref(generator);
+ json_node_free(root);
+
+ return string;
+}
+
+/** turn https://bay-client-s.gateway.messenger.live.com/v1/users/ME/contacts/8:eionrobb
+ or https://bay-client-s.gateway.messenger.live.com/v1/users/8:eionrobb/presenceDocs/messagingService
+ into eionrobb
+*/
+const gchar *
+skypeweb_contact_url_to_name(const gchar *url)
+{
+ static gchar *tempname = NULL;
+ const gchar *start, *end;
+
+ start = g_strrstr(url, "/8:");
+ if (!start) return NULL;
+ start = start + 3;
+
+ if ((end = strchr(start, '/'))) {
+ g_free(tempname);
+ tempname = g_strndup(start, end - start);
+ return tempname;
+ }
+
+ return start;
+}
+
+/** Blatantly stolen from MSN prpl, with super-secret SHA256 change! */
+#define BUFSIZE 256
+char *
+skypeweb_hmac_sha256(char *input)
+{
+ PurpleCipher *cipher;
+ PurpleCipherContext *context;
+ const guchar productKey[] = SKYPEWEB_LOCKANDKEY_SECRET;
+ const guchar productID[] = SKYPEWEB_LOCKANDKEY_APPID;
+ const char hexChars[] = "0123456789abcdef";
+ char buf[BUFSIZE];
+ unsigned char sha256Hash[32];
+ unsigned char *newHash;
+ unsigned int *sha256Parts;
+ unsigned int *chlStringParts;
+ unsigned int newHashParts[5];
+ gchar *output;
+
+ long long nHigh = 0, nLow = 0;
+
+ int len;
+ int i;
+
+ /* Create the SHA256 hash by using Purple SHA256 algorithm */
+ cipher = purple_ciphers_find_cipher("sha256");
+ context = purple_cipher_context_new(cipher, NULL);
+
+ purple_cipher_context_append(context, (guchar *)input, strlen(input));
+ purple_cipher_context_append(context, productKey, sizeof(productKey) - 1);
+ purple_cipher_context_digest(context, sizeof(sha256Hash), sha256Hash, NULL);
+ purple_cipher_context_destroy(context);
+
+ /* Split it into four integers */
+ sha256Parts = (unsigned int *)sha256Hash;
+ for (i = 0; i < 4; i++) {
+ /* adjust endianess */
+ sha256Parts[i] = GUINT_TO_LE(sha256Parts[i]);
+
+ /* & each integer with 0x7FFFFFFF */
+ /* and save one unmodified array for later */
+ newHashParts[i] = sha256Parts[i];
+ sha256Parts[i] &= 0x7FFFFFFF;
+ }
+
+ /* make a new string and pad with '0' to length that's a multiple of 8 */
+ snprintf(buf, BUFSIZE - 5, "%s%s", input, productID);
+ len = strlen(buf);
+ if ((len % 8) != 0) {
+ int fix = 8 - (len % 8);
+ memset(&buf[len], '0', fix);
+ buf[len + fix] = '\0';
+ len += fix;
+ }
+
+ /* split into integers */
+ chlStringParts = (unsigned int *)buf;
+
+ /* this is magic */
+ 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 = (sha256Parts[0] * (temp + nLow) + sha256Parts[1]) % 0x7FFFFFFF;
+ nHigh += temp;
+
+ temp = ((long long)chlStringParts[i + 1] + temp) % 0x7FFFFFFF;
+ nLow = (sha256Parts[2] * temp + sha256Parts[3]) % 0x7FFFFFFF;
+ nHigh += nLow;
+ }
+ nLow = (nLow + sha256Parts[1]) % 0x7FFFFFFF;
+ nHigh = (nHigh + sha256Parts[3]) % 0x7FFFFFFF;
+
+ newHashParts[0] ^= nLow;
+ newHashParts[1] ^= nHigh;
+ newHashParts[2] ^= nLow;
+ newHashParts[3] ^= nHigh;
+
+ /* adjust endianness */
+ for(i = 0; i < 4; i++)
+ newHashParts[i] = GUINT_TO_LE(newHashParts[i]);
+
+ /* make a string of the parts */
+ newHash = (unsigned char *)newHashParts;
+
+ /* convert to hexadecimal */
+ output = g_new0(gchar, 33);
+ for (i = 0; i < 16; i++)
+ {
+ output[i * 2] = hexChars[(newHash[i] >> 4) & 0xF];
+ output[(i * 2) + 1] = hexChars[newHash[i] & 0xF];
+ }
+ output[32] = '\0';
+
+ return output;
+}
diff --git a/skypeweb/skypeweb_util.h b/skypeweb/skypeweb_util.h new file mode 100644 index 0000000..a9a4af5 --- /dev/null +++ b/skypeweb/skypeweb_util.h @@ -0,0 +1,9 @@ +#include "libskypeweb.h"
+
+gchar *skypeweb_string_get_chunk(const gchar *haystack, gsize len, const gchar *start, const gchar *end);
+
+gchar *skypeweb_jsonobj_to_string(JsonObject *jsonobj);
+
+const gchar *skypeweb_contact_url_to_name(const gchar *url);
+
+gchar *skypeweb_hmac_sha256(gchar *input);
\ No newline at end of file |