Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/api.c =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/api.c +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/api.c @@ -33,6 +33,7 @@ #include "util.h" typedef struct _FbApiData FbApiData; +typedef struct _FbApiPreloginData FbApiPreloginData; enum { @@ -44,6 +45,8 @@ enum PROP_STOKEN, PROP_TOKEN, PROP_UID, + PROP_TWEAK, + PROP_WORK, PROP_N }; @@ -69,6 +72,11 @@ struct _FbApiPrivate guint unread; FbId lastmid; gchar *contacts_delta; + int tweak; + gboolean is_work; + gboolean need_work_switch; + gchar *sso_verifier; + FbId work_community_id; }; struct _FbApiData @@ -77,6 +85,13 @@ struct _FbApiData GDestroyNotify func; }; +struct _FbApiPreloginData +{ + FbApi *api; + const gchar *user; + const gchar *pass; +}; + static void fb_api_attach(FbApi *api, FbId aid, const gchar *msgid, FbApiMessage *msg); @@ -94,6 +109,24 @@ fb_api_contacts_delta(FbApi *api, const G_DEFINE_TYPE_WITH_CODE(FbApi, fb_api, G_TYPE_OBJECT, G_ADD_PRIVATE(FbApi)); +static const gchar *agents[] = { + FB_API_AGENT, + NULL, +}; + +static const gchar * +fb_api_get_agent_string(int tweak, gboolean mqtt) +{ + gboolean http_only = tweak & 4; + gboolean mqtt_only = tweak & 8; + + if (tweak <= 0 || tweak > 15 || (http_only && mqtt) || (mqtt_only && !mqtt)) { + return agents[0]; + } + + return agents[tweak & 3]; +} + static void fb_api_set_property(GObject *obj, guint prop, const GValue *val, GParamSpec *pspec) @@ -123,6 +156,12 @@ fb_api_set_property(GObject *obj, guint case PROP_UID: priv->uid = g_value_get_int64(val); break; + case PROP_TWEAK: + priv->tweak = g_value_get_int(val); + break; + case PROP_WORK: + priv->is_work = g_value_get_boolean(val); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -154,6 +193,12 @@ fb_api_get_property(GObject *obj, guint case PROP_UID: g_value_set_int64(val, priv->uid); break; + case PROP_TWEAK: + g_value_set_int(val, priv->tweak); + break; + case PROP_WORK: + g_value_set_boolean(val, priv->is_work); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, prop, pspec); @@ -190,6 +235,7 @@ fb_api_dispose(GObject *obj) g_free(priv->stoken); g_free(priv->token); g_free(priv->contacts_delta); + g_free(priv->sso_verifier); } static void @@ -279,6 +325,26 @@ fb_api_class_init(FbApiClass *klass) "User identifier", 0, G_MAXINT64, 0, G_PARAM_READWRITE); + + /** + * FbApi:tweak: + */ + props[PROP_TWEAK] = g_param_spec_int( + "tweak", + "Tweak", + "", + 0, G_MAXINT, 0, + G_PARAM_READWRITE); + + /** + * FbApi:work: + */ + props[PROP_WORK] = g_param_spec_boolean( + "work", + "Work", + "", + FALSE, + G_PARAM_READWRITE); g_object_class_install_properties(gklass, PROP_N, props); /** @@ -517,6 +583,22 @@ fb_api_class_init(FbApiClass *klass) fb_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * FbApi::work-sso-login: + * @api: The #FbApi. + * + * Emitted when user interaction is required to continue SAML SSO login + */ + + g_signal_new("work-sso-login", + G_TYPE_FROM_CLASS(klass), + G_SIGNAL_ACTION, + 0, + NULL, NULL, + fb_marshal_VOID__VOID, + G_TYPE_NONE, + 0); } static void @@ -760,7 +842,8 @@ fb_api_http_req(FbApi *api, const gchar PurpleHttpConnection *ret; PurpleHttpRequest *req; - fb_http_params_set_str(params, "api_key", FB_API_KEY); + fb_http_params_set_str(params, "api_key", + priv->is_work ? FB_WORK_API_KEY : FB_API_KEY); fb_http_params_set_str(params, "device_id", priv->did); fb_http_params_set_str(params, "fb_api_req_friendly_name", name); fb_http_params_set_str(params, "format", "json"); @@ -787,7 +870,7 @@ fb_api_http_req(FbApi *api, const gchar g_string_append_printf(gstr, "%s=%s", key, val); } - g_string_append(gstr, FB_API_SECRET); + g_string_append(gstr, priv->is_work ? FB_WORK_API_SECRET : FB_API_SECRET); data = g_compute_checksum_for_string(G_CHECKSUM_MD5, gstr->str, gstr->len); fb_http_params_set_str(params, "sig", data); @@ -929,7 +1012,9 @@ fb_api_cb_mqtt_open(FbMqtt *mqtt, gpoint /* Write the information string */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_STRING, 2, 1); - fb_thrift_write_str(thft, FB_API_MQTT_AGENT); + fb_thrift_write_str(thft, (priv->tweak != 0) + ? fb_api_get_agent_string(priv->tweak, 1) + : FB_API_MQTT_AGENT); /* Write the UNKNOWN ("cp"?) */ fb_thrift_write_field(thft, FB_THRIFT_TYPE_I64, 3, 2); @@ -2128,6 +2213,54 @@ fb_api_attach(FbApi *api, FbId aid, cons } static void +fb_api_cb_work_peek(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) +{ + FbApi *api = data; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *community = NULL; + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + /* The work_users[0] explicitly only handles the first user. + * If more than one user is ever needed, this is what you want to change, + * but as far as I know this feature (linked work accounts) is deprecated + * and most users can detach their work accounts from their personal + * accounts by assigning a password to the work account. */ + community = fb_json_node_get_str(root, + "$.data.viewer.work_users[0].community.login_identifier", &err); + + FB_API_ERROR_EMIT(api, err, + g_free(community); + json_node_free(root); + return; + ); + + priv->work_community_id = FB_ID_FROM_STR(community); + + fb_api_auth(api, "X", "X", "personal_to_work_switch"); + + g_free(community); + json_node_free(root); +} + +static PurpleHttpConnection * +fb_api_work_peek(FbApi *api) +{ + FbHttpParams *prms; + + prms = fb_http_params_new(); + fb_http_params_set_int(prms, "doc_id", FB_API_WORK_COMMUNITY_PEEK); + + return fb_api_http_req(api, FB_API_URL_GQL, "WorkCommunityPeekQuery", + "post", prms, fb_api_cb_work_peek); +} + + +static void fb_api_cb_auth(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) { @@ -2143,7 +2276,14 @@ fb_api_cb_auth(PurpleHttpConnection *con values = fb_json_values_new(root); fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.access_token"); - fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + + /* extremely silly difference */ + if (priv->is_work) { + fb_json_values_add(values, FB_JSON_TYPE_STR, TRUE, "$.uid"); + } else { + fb_json_values_add(values, FB_JSON_TYPE_INT, TRUE, "$.uid"); + } + fb_json_values_update(values, &err); FB_API_ERROR_EMIT(api, err, @@ -2154,25 +2294,218 @@ fb_api_cb_auth(PurpleHttpConnection *con g_free(priv->token); priv->token = fb_json_values_next_str_dup(values, NULL); - priv->uid = fb_json_values_next_int(values, 0); - g_signal_emit_by_name(api, "auth"); + if (priv->is_work) { + priv->uid = FB_ID_FROM_STR(fb_json_values_next_str(values, "0")); + } else { + priv->uid = fb_json_values_next_int(values, 0); + } + + if (priv->need_work_switch) { + fb_api_work_peek(api); + priv->need_work_switch = FALSE; + } else { + g_signal_emit_by_name(api, "auth"); + } + g_object_unref(values); json_node_free(root); } void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass) +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type) { + FbApiPrivate *priv = api->priv; FbHttpParams *prms; prms = fb_http_params_new(); fb_http_params_set_str(prms, "email", user); fb_http_params_set_str(prms, "password", pass); + + if (credentials_type) { + fb_http_params_set_str(prms, "credentials_type", credentials_type); + } + + if (priv->sso_verifier) { + fb_http_params_set_str(prms, "code_verifier", priv->sso_verifier); + g_free(priv->sso_verifier); + priv->sso_verifier = NULL; + } + + if (priv->work_community_id) { + fb_http_params_set_int(prms, "community_id", priv->work_community_id); + } + + if (priv->is_work && priv->token) { + fb_http_params_set_str(prms, "access_token", priv->token); + } + fb_api_http_req(api, FB_API_URL_AUTH, "authenticate", "auth.login", prms, fb_api_cb_auth); } +static void +fb_api_cb_work_prelogin(PurpleHttpConnection *con, PurpleHttpResponse *res, gpointer data) +{ + FbApiPreloginData *pata = data; + FbApi *api = pata->api; + FbApiPrivate *priv = api->priv; + GError *err = NULL; + JsonNode *root; + gchar *status; + const gchar *user = pata->user; + const gchar *pass = pata->pass; + + g_free(pata); + + if (!fb_api_http_chk(api, con, res, &root)) { + return; + } + + status = fb_json_node_get_str(root, "$.status", &err); + + FB_API_ERROR_EMIT(api, err, + json_node_free(root); + return; + ); + + if (g_strcmp0(status, "can_login_password") == 0) { + fb_api_auth(api, user, pass, "work_account_password"); + + } else if (g_strcmp0(status, "can_login_via_linked_account") == 0) { + fb_api_auth(api, user, pass, "personal_account_password_with_work_username"); + priv->need_work_switch = TRUE; + + } else if (g_strcmp0(status, "can_login_sso") == 0) { + g_signal_emit_by_name(api, "work-sso-login"); + + } else if (g_strcmp0(status, "cannot_login") == 0) { + char *reason = fb_json_node_get_str(root, "$.cannot_login_reason", NULL); + + if (g_strcmp0(reason, "non_business_email") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, + "Cannot login with non-business email. " + "Change the 'username' setting or disable 'work'"); + } else { + char *title = fb_json_node_get_str(root, "$.error_title", NULL); + char *body = fb_json_node_get_str(root, "$.error_body", NULL); + + fb_api_error(api, FB_API_ERROR_AUTH, + "Work prelogin failed (%s - %s)", title, body); + + g_free(title); + g_free(body); + } + + g_free(reason); + + } else if (g_strcmp0(status, "can_self_invite") == 0) { + fb_api_error(api, FB_API_ERROR_AUTH, "Unknown email. " + "Change the 'username' setting or disable 'work'"); + } + + g_free(status); + json_node_free(root); +} + +void +fb_api_work_login(FbApi *api, const gchar *user, const gchar *pass) +{ + FbApiPrivate *priv = api->priv; + FbApiPreloginData *pata = g_new0(FbApiPreloginData, 1); + FbHttpParams *prms; + gchar *data; + PurpleHttpConnection *ret; + PurpleHttpRequest *req; + const char *url = FB_API_URL_WORK_PRELOGIN; + + pata->api = api; + pata->user = user; + pata->pass = pass; + + priv->is_work = TRUE; + + // see mark k + req = purple_http_request_new(url); + purple_http_request_set_max_len(req, -1); + purple_http_request_set_method(req, "POST"); + + purple_http_request_header_set(req, "Authorization", "OAuth null"); + purple_http_request_header_set(req, "User-Agent", FB_API_AGENT); + purple_http_request_header_set(req, "Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); + + prms = fb_http_params_new(); + fb_http_params_set_str(prms, "email", user); + fb_http_params_set_str(prms, "access_token", + FB_WORK_API_KEY "|" FB_WORK_API_SECRET); + + data = fb_http_params_close(prms, NULL); + purple_http_request_set_contents(req, data, -1); + ret = purple_http_request(priv->gc, req, fb_api_cb_work_prelogin, pata); + fb_http_conns_add(priv->cons, ret); + purple_http_request_unref(req); + + fb_util_debug(FB_UTIL_DEBUG_INFO, "HTTP Request (%p):", ret); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request URL: %s", url); + fb_util_debug(FB_UTIL_DEBUG_INFO, " Request Data: %s", data); + + g_free(data); +} + +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user) +{ + FbApiPrivate *priv = api->priv; + gchar *challenge, *verifier, *req_id, *email; + gchar *ret; + + fb_util_gen_sso_verifier(&challenge, &verifier, &req_id); + + email = g_uri_escape_string(user, NULL, FALSE); + + ret = g_strdup_printf(FB_API_SSO_URL, req_id, challenge, email); + + g_free(req_id); + g_free(challenge); + g_free(email); + + g_free(priv->sso_verifier); + priv->sso_verifier = verifier; + + return ret; +} + +void +fb_api_work_got_nonce(FbApi *api, const gchar *url) +{ + gchar **split; + gchar *uid = NULL; + gchar *nonce = NULL; + int i; + + if (!g_str_has_prefix(url, "fb-workchat-sso://sso/?")) { + return; + } + + split = g_strsplit(strchr(url, '?'), "&", -1); + + for (i = 0; split[i]; i++) { + gchar *eq = strchr(split[i], '='); + + if (g_str_has_prefix(split[i], "uid=")) { + uid = g_strstrip(eq + 1); + } else if (g_str_has_prefix(split[i], "nonce=")) { + nonce = g_strstrip(eq + 1); + } + } + + if (uid && nonce) { + fb_api_auth(api, uid, nonce, "work_sso_nonce"); + } + + g_strfreev(split); +} + static gchar * fb_api_user_icon_checksum(gchar *icon) { @@ -2277,6 +2610,8 @@ fb_api_cb_contacts_nodes(FbApi *api, Jso "$.represented_profile.id"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.represented_profile.friendship_status"); + fb_json_values_add(values, FB_JSON_TYPE_BOOL, FALSE, + "$.is_on_viewer_contact_list"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, "$.structured_name.text"); fb_json_values_add(values, FB_JSON_TYPE_STR, FALSE, @@ -2289,12 +2624,15 @@ fb_api_cb_contacts_nodes(FbApi *api, Jso } while (fb_json_values_update(values, &err)) { + gboolean in_contact_list; + str = fb_json_values_next_str(values, "0"); uid = FB_ID_FROM_STR(str); str = fb_json_values_next_str(values, NULL); + in_contact_list = fb_json_values_next_bool(values, FALSE); - if ((!purple_strequal(str, "ARE_FRIENDS") && - (uid != priv->uid)) || (uid == 0)) + if ((!in_contact_list && !purple_strequal(str, "ARE_FRIENDS") && + (uid != priv->uid)) || (uid == 0)) { if (!is_array) { break; Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/api.h =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/api.h +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/api.h @@ -98,6 +98,20 @@ #define FB_API_SECRET "374e60f8b9bb6b8cbb30f78030438895" /** + * FB_WORK_API_KEY: + * + * The Facebook workchat app API key. + */ +#define FB_WORK_API_KEY "312713275593566" + +/** + * FB_WORK_API_SECRET: + * + * The Facebook workchat app API secret. + */ +#define FB_WORK_API_SECRET "d2901dc6cb685df3b074b30b56b78d28" + +/** * FB_ORCA_AGENT * * The part of the user agent that looks like the official client, since the @@ -137,6 +151,15 @@ #define FB_API_URL_AUTH FB_API_BHOST "/method/auth.login" /** + * FB_API_URL_WORK_PRELOGIN + * + * The URL for workchat pre-login information, indicating what auth method + * should be used + */ + +#define FB_API_URL_WORK_PRELOGIN FB_API_GHOST "/at_work/pre_login_info" + +/** * FB_API_URL_GQL: * * The URL for GraphQL requests. @@ -172,6 +195,14 @@ #define FB_API_URL_TOPIC FB_API_AHOST "/method/messaging.setthreadname" /** + * FB_API_SSO_URL: + * + * Template for the URL shown to workchat users when trying to authenticate + * with SSO. + */ +#define FB_API_SSO_URL "https://m.facebook.com/work/sso/mobile?app_id=312713275593566&response_url=fb-workchat-sso%%3A%%2F%%2Fsso&request_id=%s&code_challenge=%s&email=%s" + +/** * FB_API_QUERY_CONTACT: * * The query hash for the `UsersQuery`. @@ -319,6 +350,16 @@ #define FB_API_QUERY_XMA 10153919431161729 /** + * FB_API_WORK_COMMUNITY_PEEK: + * + * The docid with information about the work community of the currently + * authenticated user. + * + * Used when prelogin returns can_login_via_linked_account + */ +#define FB_API_WORK_COMMUNITY_PEEK 1295334753880530 + +/** * FB_API_CONTACTS_COUNT: * * The maximum amount of contacts to fetch in a single request. If this @@ -641,12 +682,49 @@ fb_api_error_emit(FbApi *api, GError *er * @api: The #FbApi. * @user: The Facebook user name, email, or phone number. * @pass: The Facebook password. + * @credentials_type: Type of work account credentials, or NULL * * Sends an authentication request to Facebook. This will obtain * session information, which is required for all other requests. */ void -fb_api_auth(FbApi *api, const gchar *user, const gchar *pass); +fb_api_auth(FbApi *api, const gchar *user, const gchar *pass, const gchar *credentials_type); + +/** + * fb_api_work_login: + * @api: The #FbApi. + * @user: The Facebook user name, email, or phone number. + * @pass: The Facebook password. + * + * Starts the workchat login sequence. + */ +void +fb_api_work_login(FbApi *api, const gchar *user, const gchar *pass); + +/** + * fb_api_work_gen_sso_url: + * @api: The #FbApi. + * @user: The Facebook user email. + * + * Generates the URL to be shown to the user to get the SSO auth token. This + * url contains a challenge and the corresponding verifier is saved in the + * FbApi instance to be used later. + * + * Returns: a newly allocated string. + */ +gchar * +fb_api_work_gen_sso_url(FbApi *api, const gchar *user); + +/** + * fb_api_work_got_nonce: + * @api: The #FbApi. + * @url: The fb-workchat-sso:// URL as entered by the user + * + * Parses the fb-workchat-sso:// URL that the user got redirected to and + * continues with work_sso_nonce auth + */ +void +fb_api_work_got_nonce(FbApi *api, const gchar *url); /** * fb_api_contact: Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/facebook.c =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/facebook.c +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/facebook.c @@ -828,6 +828,59 @@ fb_cb_api_typing(FbApi *api, FbApiTyping } static void +fb_cb_work_sso_input_cb(gpointer user_data, const gchar *nonce) +{ + FbApi *api; + FbData *fata = user_data; + + api = fb_data_get_api(fata); + + fb_api_work_got_nonce(api, nonce); +} + +static void +fb_cb_work_sso_cancel_cb(gpointer user_data) +{ + PurpleConnection *gc; + FbData *fata = user_data; + + gc = fb_data_get_connection(fata); + + purple_connection_error(gc, PURPLE_CONNECTION_ERROR_AUTHENTICATION_IMPOSSIBLE, + _("User cancelled authorization")); +} + +static void +fb_cb_api_work_sso_login(FbApi *api, gpointer data) +{ + FbData *fata = data; + PurpleAccount *acct; + PurpleConnection *gc; + gchar *url; + + gc = fb_data_get_connection(fata); + acct = purple_connection_get_account(gc); + + url = fb_api_work_gen_sso_url(api, purple_account_get_username(acct)); + + purple_notify_uri(gc, url); + purple_request_input(gc, _("Authorization Code"), "SSO auth", + _("Open this URL in your browser to authenticate.\n" + "Respond to this message with the URL starting with 'fb-workchat-sso://' that it attempts to redirect to.\n" + "If your browser says 'Address not understood' (like firefox), copy it from the address bar.\n" + "Otherwise you might have to right click -> view source in the last page and find it there. Good luck!"), + url, + FALSE, FALSE, NULL, + _("OK"), G_CALLBACK(fb_cb_work_sso_input_cb), + _("Cancel"), G_CALLBACK(fb_cb_work_sso_cancel_cb), + purple_connection_get_account(gc), NULL, NULL, fata); + + + g_free(url); +} + + +static void fb_mark_read(FbData *fata, FbId id, gboolean thread) { FbApi *api; @@ -1056,6 +1109,10 @@ fb_login(PurpleAccount *acct) "typing", G_CALLBACK(fb_cb_api_typing), fata); + g_signal_connect(api, + "work-sso-login", + G_CALLBACK(fb_cb_api_work_sso_login), + fata); purple_signal_connect(convh, "conversation-updated", @@ -1073,7 +1130,11 @@ fb_login(PurpleAccount *acct) pass = purple_connection_get_password(gc); purple_connection_update_progress(gc, _("Authenticating"), 1, 4); - fb_api_auth(api, user, pass); + if (purple_account_get_bool(acct, "work", FALSE)) { + fb_api_work_login(api, user, pass); + } else { + fb_api_auth(api, user, pass, NULL); + } return; } @@ -1678,6 +1739,11 @@ purple_init_plugin(PurplePlugin *plugin) "incoming messages"), "group-chat-open", TRUE); opts = g_list_prepend(opts, opt); + + opt = purple_account_option_bool_new(_("Login as a Workplace account"), + "work", FALSE); + opts = g_list_prepend(opts, opt); + pinfo.protocol_options = g_list_reverse(opts); inited = TRUE; Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/util.c =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/util.c +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/util.c @@ -564,3 +564,57 @@ fb_util_zlib_inflate(const GByteArray *b g_object_unref(conv); return ret; } + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len) +{ + gchar *out = g_base64_encode(data, len); + gchar *c; + + for (c = out; *c; c++) { + if (*c == '+') { + *c = '-'; + } else if (*c == '/') { + *c = '_'; + } else if (*c == '=') { + *c = '\0'; + break; + } + } + + return out; +} + +/* bitlbee compat */ +static void +random_bytes(guint8 *buf, gsize len) +{ + gsize i; + + for (i = 0; i < len; i++) { + buf[i] = (guint8) g_random_int_range(0, 256); + } +} + +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id) +{ + guint8 buf[32]; + GChecksum *gc; + gsize digest_len = sizeof buf; + + random_bytes(buf, sizeof buf); + + *verifier = fb_util_urlsafe_base64_encode(buf, sizeof buf); + + gc = g_checksum_new(G_CHECKSUM_SHA256); + g_checksum_update(gc, (guchar *) *verifier, -1); + g_checksum_get_digest(gc, buf, &digest_len); + g_checksum_free(gc); + + *challenge = fb_util_urlsafe_base64_encode(buf, sizeof buf); + + random_bytes(buf, 3); + + *req_id = fb_util_urlsafe_base64_encode(buf, 3); +} Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/util.h =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/util.h +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/util.h @@ -347,4 +347,30 @@ fb_util_zlib_deflate(const GByteArray *b GByteArray * fb_util_zlib_inflate(const GByteArray *bytes, GError **error); +/** + * fb_util_urlsafe_base64_encode: + * @data: the binary data to encode. + * @len: the length of data + * + * Wrapper around g_base64_encode() which substitutes '-' instead of '+' + * and '_' instead of '/' and removes the padding + * + * Returns: A newly allocated string. + */ + +gchar * +fb_util_urlsafe_base64_encode(const guchar *data, gsize len); + +/** + * fb_util_gen_sso_verifier: + * @challenge: base64 of sha256 of verifier + * @verifier: base64 of random data + * @req_id: base64 of random data + * + * Generates the challenge/response parameters used for the workchat SSO auth. + * All parameters are output parameters. + */ +void +fb_util_gen_sso_verifier(gchar **challenge, gchar **verifier, gchar **req_id); + #endif /* _FACEBOOK_UTIL_H_ */ Index: purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/data.c =================================================================== --- purple-facebook-0.9.6.orig/pidgin/libpurple/protocols/facebook/data.c +++ purple-facebook-0.9.6/pidgin/libpurple/protocols/facebook/data.c @@ -219,6 +219,13 @@ fb_data_load(FbData *fata) ret = FALSE; } + if (purple_account_get_bool(acct, "work", FALSE)) { + g_value_init(&val, G_TYPE_BOOLEAN); + g_value_set_boolean(&val, TRUE); + g_object_set_property(G_OBJECT(priv->api), "work", &val); + g_value_unset(&val); + } + fb_api_rehash(priv->api); return ret; }