JabberData * jabber_data_create_from_xml(xmlnode *tag) { JabberData *data = g_new0(JabberData, 1); gsize size; gpointer raw_data = NULL; if (data == NULL) { purple_debug_error("jabber", "Could not allocate data object\n"); g_free(data); return NULL; } /* check if this is a "data" tag */ if (strcmp(tag->name, "data") != 0) { purple_debug_error("jabber", "Invalid data element"); g_free(data); return NULL; } data->cid = g_strdup(xmlnode_get_attrib(tag, "cid")); data->type = g_strdup(xmlnode_get_attrib(tag, "type")); raw_data = xmlnode_get_data(tag); data->data = purple_base64_decode(raw_data, &size); data->size = size; g_free(raw_data); return data; }
static void jabber_vcard_parse_avatar(JabberStream *js, const char *from, JabberIqType type, const char *id, xmlnode *packet, gpointer blah) { JabberBuddy *jb = NULL; xmlnode *vcard, *photo, *binval, *fn, *nick; char *text; if(!from) return; jb = jabber_buddy_find(js, from, TRUE); js->pending_avatar_requests = g_slist_remove(js->pending_avatar_requests, jb); if((vcard = xmlnode_get_child(packet, "vCard")) || (vcard = xmlnode_get_child_with_namespace(packet, "query", "vcard-temp"))) { /* The logic here regarding the nickname and full name is copied from * buddy.c:jabber_vcard_parse. */ gchar *nickname = NULL; if ((fn = xmlnode_get_child(vcard, "FN"))) nickname = xmlnode_get_data(fn); if ((nick = xmlnode_get_child(vcard, "NICKNAME"))) { char *tmp = xmlnode_get_data(nick); char *bare_jid = jabber_get_bare_jid(from); if (tmp && strstr(bare_jid, tmp) == NULL) { g_free(nickname); nickname = tmp; } else if (tmp) g_free(tmp); g_free(bare_jid); } if (nickname) { serv_got_alias(js->gc, from, nickname); g_free(nickname); } if ((photo = xmlnode_get_child(vcard, "PHOTO")) && (binval = xmlnode_get_child(photo, "BINVAL")) && (text = xmlnode_get_data(binval))) { guchar *data; gsize size; data = purple_base64_decode(text, &size); if (data) { gchar *hash = jabber_calculate_data_hash(data, size, "sha1"); purple_buddy_icons_set_for_user(js->gc->account, from, data, size, hash); g_free(hash); } g_free(text); } } }
END_TEST START_TEST(test_util_base64_decode) { gsize sz; guchar *out = purple_base64_decode("b3d0LXl0cm9mAA==", &sz); fail_unless(sz == 10, NULL); fail_unless(strcmp("owt-ytrof", out) == 0, NULL); g_free(out); }
static JabberSaslState jabber_cyrus_handle_challenge(JabberStream *js, xmlnode *packet, xmlnode **reply, char **error) { char *enc_in = xmlnode_get_data(packet); unsigned char *dec_in; char *enc_out; const char *c_out; unsigned int clen; gsize declen; dec_in = purple_base64_decode(enc_in, &declen); js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); g_free(enc_in); g_free(dec_in); if (js->sasl_state != SASL_CONTINUE && js->sasl_state != SASL_OK) { gchar *tmp = g_strdup_printf(_("SASL error: %s"), sasl_errdetail(js->sasl)); purple_debug_error("jabber", "Error is %d : %s\n", js->sasl_state, sasl_errdetail(js->sasl)); *error = tmp; return JABBER_SASL_STATE_FAIL; } else { xmlnode *response = xmlnode_new("response"); xmlnode_set_namespace(response, NS_XMPP_SASL); if (clen > 0) { /* Cyrus SASL 2.1.22 appears to contain code to add the charset * to the response for DIGEST-MD5 but there is no possibility * it will be executed. * * My reading of the digestmd5 plugin indicates the username and * realm are always encoded in UTF-8 (they seem to be the values * we pass in), so we need to ensure charset=utf-8 is set. */ if (!purple_strequal(js->current_mech, "DIGEST-MD5") || strstr(c_out, ",charset=")) /* If we're not using DIGEST-MD5 or Cyrus SASL is fixed */ enc_out = purple_base64_encode((unsigned char*)c_out, clen); else { char *tmp = g_strdup_printf("%s,charset=utf-8", c_out); enc_out = purple_base64_encode((unsigned char*)tmp, clen + 14); g_free(tmp); } xmlnode_insert_data(response, enc_out, -1); g_free(enc_out); } *reply = response; return JABBER_SASL_STATE_CONTINUE; } }
void purple_pn_xfer_got_invite(struct pn_peer_call *call, const char *branch, const char *context) { PurpleAccount *account; PurpleXfer *xfer; char *bin; gsize bin_len; guint32 file_size; char *file_name; gunichar2 *uni_name; account = msn_session_get_user_data (pn_peer_link_get_session (call->link)); call->cb = xfer_completed_cb; call->end_cb = xfer_end_cb; call->progress_cb = xfer_progress_cb; call->branch = g_strdup(branch); call->pending = TRUE; xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, pn_peer_link_get_passport (call->link)); if (xfer) { bin = (char *)purple_base64_decode(context, &bin_len); file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8)); uni_name = (gunichar2 *)(bin + 20); while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) { *uni_name = GUINT16_FROM_LE(*uni_name); uni_name++; } file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1, NULL, NULL, NULL); g_free(bin); purple_xfer_set_filename(xfer, file_name); purple_xfer_set_size(xfer, file_size); purple_xfer_set_init_fnc(xfer, xfer_init); purple_xfer_set_request_denied_fnc(xfer, xfer_cancel); purple_xfer_set_cancel_recv_fnc(xfer, xfer_cancel); call->xfer = xfer; purple_xfer_ref(call->xfer); xfer->data = call; purple_xfer_request(xfer); } }
static JabberSaslState scram_handle_success(JabberStream *js, PurpleXmlNode *packet, char **error) { JabberScramData *data = js->auth_mech_data; char *enc_in, *dec_in; char *dec_out = NULL; gsize len; enc_in = purple_xmlnode_get_data(packet); if (data->step != 3 && (!enc_in || *enc_in == '\0')) { *error = g_strdup(_("Invalid challenge from server")); g_free(enc_in); return JABBER_SASL_STATE_FAIL; } if (data->step == 3) { /* * If the server took the slow approach (sending the verifier * as a challenge/response pair), we get here. */ g_free(enc_in); return JABBER_SASL_STATE_OK; } if (data->step != 2) { *error = g_strdup(_("Unexpected response from server")); g_free(enc_in); return JABBER_SASL_STATE_FAIL; } dec_in = (gchar *)purple_base64_decode(enc_in, &len); g_free(enc_in); if (!dec_in || len != strlen(dec_in)) { /* Danger afoot; SCRAM shouldn't contain NUL bytes */ g_free(dec_in); *error = g_strdup(_("Malicious challenge from server")); return JABBER_SASL_STATE_FAIL; } purple_debug_misc("jabber", "decoded success: %s\n", dec_in); if (!jabber_scram_feed_parser(data, dec_in, &dec_out) || dec_out != NULL) { g_free(dec_in); g_free(dec_out); *error = g_strdup(_("Invalid challenge from server")); return JABBER_SASL_STATE_FAIL; } g_free(dec_in); /* Hooray */ return JABBER_SASL_STATE_OK; }
/*------------------------------------------------------------------------ * Decrypt a message using transport-layer encryption. * * @param session The MXit session object * @param message The encrypted message data (is base64-encoded). * @return The decrypted message. Must be g_free'd when no longer needed. */ char* mxit_decrypt_message( struct MXitSession* session, char* message ) { guchar* raw_message; gsize raw_len; char exkey[512]; GString* decoded = NULL; unsigned int i; /* remove optional header: <mxitencrypted ver="5.2"/> */ if ( strncmp( message, ENCRYPT_HEADER, strlen( ENCRYPT_HEADER ) ) == 0 ) message += strlen( ENCRYPT_HEADER ); /* base64 decode the message */ raw_message = purple_base64_decode( message, &raw_len ); /* AES-encrypted data is always blocks of 16 bytes */ if ( ( raw_len == 0 ) || ( raw_len % 16 != 0 ) ) return NULL; /* build the AES key */ ExpandKey( (unsigned char*) transport_layer_key( session ), (unsigned char*) exkey ); /* AES decrypt each block */ decoded = g_string_sized_new( raw_len ); for ( i = 0; i < raw_len; i += 16 ) { char block[16]; Decrypt( (unsigned char*) raw_message + i, (unsigned char*) exkey, (unsigned char*) block ); g_string_append_len( decoded, block, 16 ); } g_free( raw_message ); /* check that the decrypted message starts with header: <mxit/> */ if ( strncmp( decoded->str, SECRET_HEADER, strlen( SECRET_HEADER ) != 0 ) ) { g_string_free( decoded, TRUE ); return NULL; /* message could not be decrypted */ } /* remove ISO10126 padding */ padding_remove( decoded ); /* remove encryption header */ g_string_erase( decoded, 0, strlen( SECRET_HEADER ) ); return g_string_free( decoded, FALSE ); }
static JabberSaslState jabber_cyrus_handle_success(JabberStream *js, xmlnode *packet, char **error) { const void *x; /* The SASL docs say that if the client hasn't returned OK yet, we * should try one more round against it */ if (js->sasl_state != SASL_OK) { char *enc_in = xmlnode_get_data(packet); unsigned char *dec_in = NULL; const char *c_out; unsigned int clen; gsize declen = 0; if(enc_in != NULL) dec_in = purple_base64_decode(enc_in, &declen); js->sasl_state = sasl_client_step(js->sasl, (char*)dec_in, declen, NULL, &c_out, &clen); g_free(enc_in); g_free(dec_in); if (js->sasl_state != SASL_OK) { /* This happens when the server sends back jibberish * in the "additional data with success" case. * Seen with Wildfire 3.0.1. */ *error = g_strdup(_("Invalid response from server")); return JABBER_SASL_STATE_FAIL; } } /* If we've negotiated a security layer, we need to enable it */ if (js->sasl) { sasl_getprop(js->sasl, SASL_SSF, &x); if (*(int *)x > 0) { sasl_getprop(js->sasl, SASL_MAXOUTBUF, &x); js->sasl_maxbuf = *(int *)x; } } return JABBER_SASL_STATE_OK; }
JabberData * jabber_data_create_from_xml(xmlnode *tag) { JabberData *data; gchar *raw_data = NULL; const gchar *cid, *type; /* check if this is a "data" tag */ if (strcmp(tag->name, "data") != 0) { purple_debug_error("jabber", "Invalid data element\n"); return NULL; } cid = xmlnode_get_attrib(tag, "cid"); type = xmlnode_get_attrib(tag, "type"); if (!cid || !type) { purple_debug_error("jabber", "cid or type missing\n"); return NULL; } raw_data = xmlnode_get_data(tag); if (raw_data == NULL || *raw_data == '\0') { purple_debug_error("jabber", "data element was empty"); g_free(raw_data); return NULL; } data = g_new0(JabberData, 1); data->data = purple_base64_decode(raw_data, &data->size); g_free(raw_data); if (data->data == NULL) { purple_debug_error("jabber", "Malformed base64 data\n"); g_free(data); return NULL; } data->cid = g_strdup(cid); data->type = g_strdup(type); return data; }
void msn_switchboard_show_ink(MsnSwitchBoard *swboard, const char *passport, const char *data) { PurpleConnection *gc; guchar *image_data; size_t image_len; int imgid; char *image_msg; if (!purple_str_has_prefix(data, "base64:")) { purple_debug_error("msn", "Ignoring Ink not in Base64 format.\n"); return; } gc = purple_account_get_connection(swboard->session->account); data += sizeof("base64:") - 1; image_data = purple_base64_decode(data, &image_len); if (!image_data || !image_len) { purple_debug_error("msn", "Unable to decode Ink from Base64 format.\n"); return; } imgid = purple_imgstore_add_with_id(image_data, image_len, NULL); image_msg = g_strdup_printf("<IMG ID='%d'/>", imgid); if (swboard->current_users > 1 || ((swboard->conv != NULL) && purple_conversation_get_type(swboard->conv) == PURPLE_CONV_TYPE_CHAT)) serv_got_chat_in(gc, swboard->chat_id, passport, 0, image_msg, time(NULL)); else serv_got_im(gc, passport, image_msg, 0, time(NULL)); purple_imgstore_unref_by_id(imgid); g_free(image_msg); }
static void start_oscar_session_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, gsize len, const gchar *error_message) { OscarData *od; PurpleConnection *gc; char *host, *cookie; char *tls_certname = NULL; unsigned short port; guint8 *cookiedata; gsize cookiedata_len; od = user_data; gc = od->gc; od->url_data = NULL; if (error_message != NULL || len == 0) { gchar *tmp; /* Note to translators: The first %s is a URL, the second is an error message. */ tmp = g_strdup_printf(_("Error requesting %s: %s"), URL_START_OSCAR_SESSION, error_message); purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp); g_free(tmp); return; } if (!parse_start_oscar_session_response(gc, url_text, len, &host, &port, &cookie, &tls_certname)) return; cookiedata = purple_base64_decode(cookie, &cookiedata_len); oscar_connect_to_bos(gc, od, host, port, cookiedata, cookiedata_len, tls_certname); g_free(cookiedata); g_free(host); g_free(cookie); g_free(tls_certname); }
static void pb_set_base64_icon_for_buddy(const gchar *base64_icon, PurpleBuddy *buddy) { PurpleBuddyIcon *icon; guchar *icon_data; gsize icon_len; gchar *checksum; const gchar *old_checksum; checksum = g_strdup_printf("%ud", g_str_hash(base64_icon)); old_checksum = purple_buddy_icons_get_checksum_for_user(buddy); if (old_checksum && purple_strequal(old_checksum, checksum)) { g_free(checksum); return; } icon_data = purple_base64_decode(base64_icon, &icon_len); icon = purple_buddy_icon_new(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy), icon_data, icon_len, checksum); g_free(icon_data); g_free(checksum); }
static gboolean nexus_parse_collection(MsnNexus *nexus, int id, xmlnode *collection) { xmlnode *node; gboolean result; node = xmlnode_get_child(collection, "RequestSecurityTokenResponse"); if (!node) return FALSE; result = TRUE; for (; node && result; node = node->next) { xmlnode *endpoint = xmlnode_get_child(node, "AppliesTo/EndpointReference/Address"); char *address = xmlnode_get_data(endpoint); if (g_str_equal(address, "http://Passport.NET/tb")) { /* This node contains the stuff for updating tokens. */ char *data; xmlnode *cipher = xmlnode_get_child(node, "RequestedSecurityToken/EncryptedData/CipherData/CipherValue"); xmlnode *secret = xmlnode_get_child(node, "RequestedProofToken/BinarySecret"); g_free(nexus->cipher); nexus->cipher = xmlnode_get_data(cipher); data = xmlnode_get_data(secret); g_free(nexus->secret); nexus->secret = (char *)purple_base64_decode(data, NULL); g_free(data); } else { result = nexus_parse_token(nexus, id, node); } g_free(address); } return result; }
gboolean jabber_scram_feed_parser(JabberScramData *data, gchar *in, gchar **out) { gboolean ret; g_return_val_if_fail(data != NULL, FALSE); g_string_append_c(data->auth_message, ','); g_string_append(data->auth_message, in); if (data->step == 1) { gchar *nonce, *proof; GString *salt; guint iterations; ret = parse_server_step1(data, in, &nonce, &salt, &iterations); if (!ret) return FALSE; g_string_append_c(data->auth_message, ','); /* "biws" is the base64 encoding of "n,,". I promise. */ g_string_append_printf(data->auth_message, "c=%s,r=%s", "biws", nonce); #ifdef CHANNEL_BINDING #error fix this #endif ret = jabber_scram_calc_proofs(data, salt, iterations); g_string_free(salt, TRUE); salt = NULL; if (!ret) { g_free(nonce); return FALSE; } proof = purple_base64_encode((guchar *)data->client_proof->str, data->client_proof->len); *out = g_strdup_printf("c=%s,r=%s,p=%s", "biws", nonce, proof); g_free(nonce); g_free(proof); } else if (data->step == 2) { gchar *server_sig, *enc_server_sig; gsize len; ret = parse_server_step2(data, in, &enc_server_sig); if (!ret) return FALSE; server_sig = (gchar *)purple_base64_decode(enc_server_sig, &len); g_free(enc_server_sig); if (server_sig == NULL || len != data->server_signature->len) { g_free(server_sig); return FALSE; } if (0 != memcmp(server_sig, data->server_signature->str, len)) { g_free(server_sig); return FALSE; } g_free(server_sig); *out = NULL; } else { purple_debug_error("jabber", "SCRAM: There is no step %d\n", data->step); return FALSE; } return TRUE; }
/*------------------------------------------------------------------------ * Handle an URI clicked on the UI * * @param link the link name which has been clicked */ static void* mxit_link_click( const char* link64 ) { PurpleAccount* account; PurpleConnection* gc; gchar** parts = NULL; gchar* link = NULL; gsize len; gboolean is_command = FALSE; purple_debug_info( MXIT_PLUGIN_ID, "mxit_link_click (%s)\n", link64 ); if ( g_ascii_strncasecmp( link64, MXIT_LINK_PREFIX, strlen( MXIT_LINK_PREFIX ) ) != 0 ) { /* this is not for us */ goto skip; } /* decode the base64 payload */ link = (gchar*) purple_base64_decode( link64 + strlen( MXIT_LINK_PREFIX ), &len ); purple_debug_info( MXIT_PLUGIN_ID, "Clicked Link: '%s'\n", link ); parts = g_strsplit( link, "|", 6 ); /* check if this is a valid mxit link */ if ( ( !parts ) || ( !parts[0] ) || ( !parts[1] ) || ( !parts[2] ) || ( !parts[3] ) || ( !parts[4] ) || ( !parts[5] ) ) { /* this is not for us */ goto skip; } else if ( g_ascii_strcasecmp( parts[0], MXIT_LINK_KEY ) != 0 ) { /* this is not for us */ goto skip; } /* find the account */ account = purple_accounts_find( parts[1], parts[2] ); if ( !account ) goto skip; gc = purple_account_get_connection( account ); if ( !gc ) goto skip; /* determine if it's a command-response to send */ is_command = ( atoi( parts[4] ) == 1 ); /* send click message back to MXit */ mxit_send_message( purple_connection_get_protocol_data( gc ), parts[3], parts[5], FALSE, is_command ); g_free( link ); link = NULL; g_strfreev( parts ); parts = NULL; return (void*) link64; skip: /* this is not an internal mxit link */ if ( link ) g_free( link ); link = NULL; if ( parts ) g_strfreev( parts ); parts = NULL; if ( mxit_pidgin_uri_cb ) return mxit_pidgin_uri_cb( link64 ); else return (void*) link64; }
static gboolean parse_server_step1(JabberScramData *data, const char *challenge, gchar **out_nonce, GString **out_salt, guint *out_iterations) { char **tokens; char *token, *decoded, *tmp; gsize len; char *nonce = NULL; GString *salt = NULL; guint iterations; tokens = g_strsplit(challenge, ",", -1); if (tokens == NULL) return FALSE; token = tokens[0]; if (token[0] != 'r' || token[1] != '=') goto err; /* Ensure that the first cnonce_len bytes of the nonce are the original * cnonce we sent to the server. */ if (0 != strncmp(data->cnonce, token + 2, strlen(data->cnonce))) goto err; nonce = g_strdup(token + 2); /* The Salt, base64-encoded */ token = tokens[1]; if (token[0] != 's' || token[1] != '=') goto err; decoded = (gchar *)purple_base64_decode(token + 2, &len); if (!decoded || *decoded == '\0') { g_free(decoded); goto err; } salt = g_string_new_len(decoded, len); g_free(decoded); /* The iteration count */ token = tokens[2]; if (token[0] != 'i' || token[1] != '=' || token[2] == '\0') goto err; /* Validate the string */ for (tmp = token + 2; *tmp; ++tmp) if (!g_ascii_isdigit(*tmp)) goto err; iterations = strtoul(token + 2, NULL, 10); g_strfreev(tokens); *out_nonce = nonce; *out_salt = salt; *out_iterations = iterations; return TRUE; err: g_free(nonce); if (salt) g_string_free(salt, TRUE); g_strfreev(tokens); return FALSE; }
static JabberSaslState digest_md5_handle_challenge(JabberStream *js, PurpleXmlNode *packet, PurpleXmlNode **response, char **msg) { PurpleXmlNode *reply = NULL; char *enc_in = purple_xmlnode_get_data(packet); char *dec_in; char *enc_out; GHashTable *parts; JabberSaslState state = JABBER_SASL_STATE_CONTINUE; if (!enc_in) { *msg = g_strdup(_("Invalid response from server")); return JABBER_SASL_STATE_FAIL; } dec_in = (char *)purple_base64_decode(enc_in, NULL); purple_debug_misc("jabber", "decoded challenge (%" G_GSIZE_FORMAT "): %s\n", strlen(dec_in), dec_in); parts = jabber_auth_digest_md5_parse(dec_in); if (g_hash_table_lookup(parts, "rspauth")) { char *rspauth = g_hash_table_lookup(parts, "rspauth"); char *expected_rspauth = js->auth_mech_data; if (rspauth && purple_strequal(rspauth, expected_rspauth)) { reply = purple_xmlnode_new("response"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); } else { *msg = g_strdup(_("Invalid challenge from server")); state = JABBER_SASL_STATE_FAIL; } g_free(js->auth_mech_data); js->auth_mech_data = NULL; } else { /* assemble a response, and send it */ /* see RFC 2831 */ char *realm; char *nonce; /* Make sure the auth string contains everything that should be there. This isn't everything in RFC2831, but it is what we need. */ nonce = g_hash_table_lookup(parts, "nonce"); /* we're actually supposed to prompt the user for a realm if * the server doesn't send one, but that really complicates things, * so i'm not gonna worry about it until is poses a problem to * someone, or I get really bored */ realm = g_hash_table_lookup(parts, "realm"); if(!realm) realm = js->user->domain; if (nonce == NULL || realm == NULL) { *msg = g_strdup(_("Invalid challenge from server")); state = JABBER_SASL_STATE_FAIL; } else { GString *response = g_string_new(""); char *a2; char *auth_resp; char *cnonce; cnonce = g_strdup_printf("%x%u%x", g_random_int(), (int)time(NULL), g_random_int()); a2 = g_strdup_printf("AUTHENTICATE:xmpp/%s", realm); auth_resp = generate_response_value(js->user, purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); g_free(a2); a2 = g_strdup_printf(":xmpp/%s", realm); js->auth_mech_data = generate_response_value(js->user, purple_connection_get_password(js->gc), nonce, cnonce, a2, realm); g_free(a2); g_string_append_printf(response, "username=\"%s\"", js->user->node); g_string_append_printf(response, ",realm=\"%s\"", realm); g_string_append_printf(response, ",nonce=\"%s\"", nonce); g_string_append_printf(response, ",cnonce=\"%s\"", cnonce); g_string_append_printf(response, ",nc=00000001"); g_string_append_printf(response, ",qop=auth"); g_string_append_printf(response, ",digest-uri=\"xmpp/%s\"", realm); g_string_append_printf(response, ",response=%s", auth_resp); g_string_append_printf(response, ",charset=utf-8"); g_free(auth_resp); g_free(cnonce); enc_out = purple_base64_encode((guchar *)response->str, response->len); purple_debug_misc("jabber", "decoded response (%" G_GSIZE_FORMAT "): %s\n", response->len, response->str); reply = purple_xmlnode_new("response"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); purple_xmlnode_insert_data(reply, enc_out, -1); g_free(enc_out); g_string_free(response, TRUE); } } g_free(enc_in); g_free(dec_in); g_hash_table_destroy(parts); *response = reply; return state; }
static void nexus_got_update_cb(MsnSoapMessage *req, MsnSoapMessage *resp, gpointer data) { MsnNexusUpdateData *ud = data; MsnNexus *nexus = ud->nexus; char iv[8] = {0,0,0,0,0,0,0,0}; xmlnode *enckey; char *tmp; char *nonce; gsize len; char *key; GSList *updates; #if 0 char *decrypted_pp; #endif char *decrypted_data; if (resp == NULL) return; purple_debug_info("msn", "Got Update Response for %s.\n", ticket_domains[ud->id][SSO_VALID_TICKET_DOMAIN]); enckey = xmlnode_get_child(resp->xml, "Header/Security/DerivedKeyToken"); while (enckey) { if (g_str_equal(xmlnode_get_attrib(enckey, "Id"), "EncKey")) break; enckey = xmlnode_get_next_twin(enckey); } if (!enckey) { purple_debug_error("msn", "Invalid response in token update.\n"); return; } tmp = xmlnode_get_data(xmlnode_get_child(enckey, "Nonce")); nonce = (char *)purple_base64_decode(tmp, &len); key = rps_create_key(nexus->secret, 24, nonce, len); g_free(tmp); g_free(nonce); #if 0 /* Don't know what this is for yet */ tmp = xmlnode_get_data(xmlnode_get_child(resp->xml, "Header/EncryptedPP/EncryptedData/CipherData/CipherValue")); if (tmp) { decrypted_pp = des3_cbc(key, iv, tmp, len, TRUE); g_free(tmp); purple_debug_info("msn", "Got Response Header EncryptedPP: %s\n", decrypted_pp); g_free(decrypted_pp); } #endif tmp = xmlnode_get_data(xmlnode_get_child(resp->xml, "Body/EncryptedData/CipherData/CipherValue")); if (tmp) { char *unescaped; xmlnode *rstresponse; unescaped = (char *)purple_base64_decode(tmp, &len); g_free(tmp); decrypted_data = des3_cbc(key, iv, unescaped, len, TRUE); g_free(unescaped); purple_debug_info("msn", "Got Response Body EncryptedData: %s\n", decrypted_data); rstresponse = xmlnode_from_str(decrypted_data, -1); if (g_str_equal(rstresponse->name, "RequestSecurityTokenResponse")) nexus_parse_token(nexus, ud->id, rstresponse); else nexus_parse_collection(nexus, ud->id, rstresponse); g_free(decrypted_data); } updates = nexus->tokens[ud->id].updates; nexus->tokens[ud->id].updates = NULL; while (updates != NULL) { MsnNexusUpdateCallback *update = updates->data; if (update->cb) purple_timeout_add(0, update->cb, update->data); g_free(update); updates = g_slist_delete_link(updates, updates); } g_free(ud); g_free(key); }
static char * msn_rps_encrypt(MsnNexus *nexus) { char usr_key_base[MSN_USER_KEY_SIZE], *usr_key; const char magic1[] = "SESSION KEY HASH"; const char magic2[] = "SESSION KEY ENCRYPTION"; PurpleCipherContext *hmac; size_t len; guchar *hash; char *key1, *key2, *key3; gsize key1_len; const char *iv; char *nonce_fixed; char *cipher; char *response; usr_key = &usr_key_base[0]; /* Header */ msn_push32le(usr_key, 28); /* Header size */ msn_push32le(usr_key, CRYPT_MODE_CBC); /* Crypt mode */ msn_push32le(usr_key, CIPHER_TRIPLE_DES); /* Cipher type */ msn_push32le(usr_key, HASH_SHA1); /* Hash type */ msn_push32le(usr_key, 8); /* IV size */ msn_push32le(usr_key, 20); /* Hash size */ msn_push32le(usr_key, 72); /* Cipher size */ /* Data */ iv = usr_key; msn_push32le(usr_key, rand()); msn_push32le(usr_key, rand()); hash = (guchar *)usr_key; usr_key += 20; /* Remaining is cipher data */ key1 = (char *)purple_base64_decode((const char *)nexus->tokens[MSN_AUTH_MESSENGER].secret, &key1_len); key2 = rps_create_key(key1, key1_len, magic1, sizeof(magic1) - 1); key3 = rps_create_key(key1, key1_len, magic2, sizeof(magic2) - 1); len = strlen(nexus->nonce); hmac = purple_cipher_context_new_by_name("hmac", NULL); purple_cipher_context_set_option(hmac, "hash", "sha1"); purple_cipher_context_set_key_with_len(hmac, (guchar *)key2, 24); purple_cipher_context_append(hmac, (guchar *)nexus->nonce, len); purple_cipher_context_digest(hmac, 20, hash, NULL); purple_cipher_context_destroy(hmac); /* We need to pad this to 72 bytes, apparently */ nonce_fixed = g_malloc(len + 8); memcpy(nonce_fixed, nexus->nonce, len); memset(nonce_fixed + len, 0x08, 8); cipher = des3_cbc(key3, iv, nonce_fixed, len + 8, FALSE); g_free(nonce_fixed); memcpy(usr_key, cipher, 72); g_free(key1); g_free(key2); g_free(key3); g_free(cipher); response = purple_base64_encode((guchar *)usr_key_base, MSN_USER_KEY_SIZE); return response; }
/*Post the Offline Instant Message to User Conversation*/ static void msn_oim_report_to_user(MsnOimRecvData *rdata, const char *msg_str) { MsnMessage *message; const char *date; const char *from; const char *boundary; char *decode_msg = NULL; gsize body_len; char **tokens; char *passport = NULL; time_t stamp; message = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(message, msg_str, strlen(msg_str), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); purple_debug_info("msn", "oim body:{%s}\n", message->body); boundary = msn_message_get_attr(message, "boundary"); if (boundary != NULL) { char *bounds; char **part; bounds = g_strdup_printf("--%s" MSG_OIM_LINE_DEM, boundary); tokens = g_strsplit(message->body, bounds, 0); /* tokens+1 to skip the "This is a multipart message..." text */ for (part = tokens+1; *part != NULL; part++) { MsnMessage *multipart; const char *type; multipart = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(multipart, *part, strlen(*part), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); type = msn_message_get_content_type(multipart); if (type && !strcmp(type, "text/plain")) { decode_msg = (char *)purple_base64_decode(multipart->body, &body_len); msn_message_destroy(multipart); break; } msn_message_destroy(multipart); } g_strfreev(tokens); g_free(bounds); if (decode_msg == NULL) { purple_debug_error("msn", "Couldn't find text/plain OIM message.\n"); msn_message_destroy(message); return; } } else { decode_msg = (char *)purple_base64_decode(message->body, &body_len); } from = msn_message_get_attr(message, "X-OIM-originatingSource"); /* Match number to user's mobile number, FROM is a phone number if the other side pages you using your phone number */ if (from && !strncmp(from, "tel:+", 5)) { MsnUser *user = msn_userlist_find_user_with_mobile_phone( rdata->oim->session->userlist, from + 4); if (user && user->passport) passport = g_strdup(user->passport); } if (passport == NULL) { char *start, *end; from = msn_message_get_attr(message, "From"); tokens = g_strsplit(from, " ", 2); if (tokens[1] != NULL) from = (const char *)tokens[1]; start = strchr(from, '<'); if (start != NULL) { start++; end = strchr(from, '>'); if (end != NULL) passport = g_strndup(start, end - start); } if (passport == NULL) passport = g_strdup(_("Unknown")); g_strfreev(tokens); } date = msn_message_get_attr(message, "Date"); stamp = msn_oim_parse_timestamp(date); purple_debug_info("msn", "oim Date:{%s},passport{%s}\n", date, passport); serv_got_im(rdata->oim->session->account->gc, passport, decode_msg, 0, stamp); /*Now get the oim message ID from the oim_list. * and append to read list to prepare for deleting the Offline Message when sign out */ msn_oim_post_delete_msg(rdata); g_free(passport); g_free(decode_msg); msn_message_destroy(message); }
static JabberSaslState scram_handle_challenge(JabberStream *js, PurpleXmlNode *challenge, PurpleXmlNode **out, char **error) { JabberScramData *data = js->auth_mech_data; PurpleXmlNode *reply; gchar *enc_in, *dec_in = NULL; gchar *enc_out = NULL, *dec_out = NULL; gsize len; JabberSaslState state = JABBER_SASL_STATE_FAIL; enc_in = purple_xmlnode_get_data(challenge); if (!enc_in || *enc_in == '\0') { reply = purple_xmlnode_new("abort"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); data->step = -1; *error = g_strdup(_("Invalid challenge from server")); goto out; } dec_in = (gchar *)purple_base64_decode(enc_in, &len); if (!dec_in || len != strlen(dec_in)) { /* Danger afoot; SCRAM shouldn't contain NUL bytes */ reply = purple_xmlnode_new("abort"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); data->step = -1; *error = g_strdup(_("Malicious challenge from server")); goto out; } purple_debug_misc("jabber", "decoded challenge: %s\n", dec_in); if (!jabber_scram_feed_parser(data, dec_in, &dec_out)) { reply = purple_xmlnode_new("abort"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); data->step = -1; *error = g_strdup(_("Invalid challenge from server")); goto out; } data->step += 1; reply = purple_xmlnode_new("response"); purple_xmlnode_set_namespace(reply, NS_XMPP_SASL); purple_debug_misc("jabber", "decoded response: %s\n", dec_out ? dec_out : "(null)"); if (dec_out) { enc_out = purple_base64_encode((guchar *)dec_out, strlen(dec_out)); purple_xmlnode_insert_data(reply, enc_out, -1); } state = JABBER_SASL_STATE_CONTINUE; out: g_free(enc_in); g_free(dec_in); g_free(enc_out); g_free(dec_out); *out = reply; return state; }
void jabber_ibb_parse(JabberStream *js, const char *who, JabberIqType type, const char *id, PurpleXmlNode *child) { const char *name = child->name; gboolean data = g_str_equal(name, "data"); gboolean close = g_str_equal(name, "close"); gboolean open = g_str_equal(name, "open"); const gchar *sid = (data || close) ? purple_xmlnode_get_attrib(child, "sid") : NULL; JabberIBBSession *sess = sid ? g_hash_table_lookup(jabber_ibb_sessions, sid) : NULL; if (sess) { if (strcmp(who, jabber_ibb_session_get_who(sess)) != 0) { /* the iq comes from a different JID than the remote JID of the session, ignore it */ purple_debug_error("jabber", "Got IBB iq from wrong JID, ignoring\n"); } else if (data) { const gchar *seq_attr = purple_xmlnode_get_attrib(child, "seq"); guint16 seq = (seq_attr ? atoi(seq_attr) : 0); /* reject the data, and set the session in error if we get an out-of-order packet */ if (seq_attr && seq == jabber_ibb_session_get_recv_seq(sess)) { /* sequence # is the expected... */ JabberIq *result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, id); purple_xmlnode_set_attrib(result->node, "to", who); if (sess->data_received_cb) { gchar *base64 = purple_xmlnode_get_data(child); gsize size; gpointer rawdata = purple_base64_decode(base64, &size); g_free(base64); if (rawdata) { purple_debug_info("jabber", "got %" G_GSIZE_FORMAT " bytes of data on IBB stream\n", size); /* we accept other clients to send up to block-size of _unencoded_ data, since there's been some confusions regarding the interpretation of this attribute (including previous versions of libpurple) */ if (size > jabber_ibb_session_get_block_size(sess)) { purple_debug_error("jabber", "IBB: received a too large packet\n"); if (sess->error_cb) sess->error_cb(sess); g_free(rawdata); return; } else { purple_debug_info("jabber", "calling IBB callback for received data\n"); sess->data_received_cb(sess, rawdata, size); } g_free(rawdata); } else { purple_debug_error("jabber", "IBB: invalid BASE64 data received\n"); if (sess->error_cb) sess->error_cb(sess); return; } } (sess->recv_seq)++; jabber_iq_send(result); } else { purple_debug_error("jabber", "Received an out-of-order/invalid IBB packet\n"); sess->state = JABBER_IBB_SESSION_ERROR; if (sess->error_cb) { sess->error_cb(sess); } } } else if (close) { sess->state = JABBER_IBB_SESSION_CLOSED; purple_debug_info("jabber", "IBB: received close\n"); if (sess->closed_cb) { purple_debug_info("jabber", "IBB: calling closed handler\n"); sess->closed_cb(sess); } } } else if (open) { JabberIq *result; const GList *iterator; /* run all open handlers registered until one returns true */ for (iterator = open_handlers ; iterator ; iterator = g_list_next(iterator)) { JabberIBBOpenHandler *handler = iterator->data; if (handler(js, who, id, child)) { result = jabber_iq_new(js, JABBER_IQ_RESULT); purple_xmlnode_set_attrib(result->node, "to", who); jabber_iq_set_id(result, id); jabber_iq_send(result); return; } } /* no open callback returned success, reject */ jabber_ibb_send_error_response(js, who, id); } else { /* send error reply */ jabber_ibb_send_error_response(js, who, id); } }
/*Post the Offline Instant Message to User Conversation*/ static void msn_oim_report_to_user(MsnOimRecvData *rdata, const char *msg_str) { MsnMessage *message; const char *date; const char *from; const char *boundary; char *decode_msg = NULL, *clean_msg = NULL; gsize body_len; char **tokens; char *passport = NULL; time_t stamp; const char *charset = NULL; message = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(message, msg_str, strlen(msg_str), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); purple_debug_info("msn", "oim body:{%s}\n", message->body); boundary = msn_message_get_header_value(message, "boundary"); if (boundary != NULL) { char *bounds; char **part; bounds = g_strdup_printf("--%s" MSG_OIM_LINE_DEM, boundary); tokens = g_strsplit(message->body, bounds, 0); /* tokens+1 to skip the "This is a multipart message..." text */ for (part = tokens+1; *part != NULL; part++) { MsnMessage *multipart; const char *type; multipart = msn_message_new(MSN_MSG_UNKNOWN); msn_message_parse_payload(multipart, *part, strlen(*part), MSG_OIM_LINE_DEM, MSG_OIM_BODY_DEM); type = msn_message_get_content_type(multipart); if (type && !strcmp(type, "text/plain")) { decode_msg = (char *)purple_base64_decode(multipart->body, &body_len); charset = msn_message_get_charset(multipart); msn_message_unref(multipart); break; } msn_message_unref(multipart); } g_strfreev(tokens); g_free(bounds); if (decode_msg == NULL) { purple_debug_error("msn", "Couldn't find text/plain OIM message.\n"); msn_message_unref(message); return; } } else { decode_msg = (char *)purple_base64_decode(message->body, &body_len); charset = msn_message_get_charset(message); } if (charset && !((g_ascii_strncasecmp(charset, "UTF-8", 5) == 0) || (g_ascii_strncasecmp(charset, "UTF8", 4) == 0))) { clean_msg = g_convert(decode_msg, body_len, "UTF-8", charset, NULL, NULL, NULL); if (!clean_msg) { char *clean = purple_utf8_salvage(decode_msg); purple_debug_error("msn", "Failed to convert charset from %s to UTF-8 for OIM message: %s\n", charset, clean); clean_msg = g_strdup_printf(_("%s (There was an error receiving this message. " "Converting the encoding from %s to UTF-8 failed.)"), clean, charset); g_free(clean); } g_free(decode_msg); } else if (!g_utf8_validate(decode_msg, body_len, NULL)) { char *clean = purple_utf8_salvage(decode_msg); purple_debug_error("msn", "Received an OIM message that is not UTF-8," " and no encoding specified: %s\n", clean); if (charset) { clean_msg = g_strdup_printf(_("%s (There was an error receiving this message." " The charset was %s, but it was not valid UTF-8.)"), clean, charset); } else { clean_msg = g_strdup_printf(_("%s (There was an error receiving this message." " The charset was missing, but it was not valid UTF-8.)"), clean); } g_free(clean); g_free(decode_msg); } else { clean_msg = decode_msg; } from = msn_message_get_header_value(message, "X-OIM-originatingSource"); /* Match number to user's mobile number, FROM is a phone number if the other side pages you using your phone number */ if (from && !strncmp(from, "tel:+", 5)) { MsnUser *user = msn_userlist_find_user_with_mobile_phone( rdata->oim->session->userlist, from + 4); if (user && user->passport) passport = g_strdup(user->passport); } if (passport == NULL) { char *start, *end; from = msn_message_get_header_value(message, "From"); tokens = g_strsplit(from, " ", 2); if (tokens[1] != NULL) from = (const char *)tokens[1]; start = strchr(from, '<'); if (start != NULL) { start++; end = strchr(from, '>'); if (end != NULL) passport = g_strndup(start, end - start); } if (passport == NULL) passport = g_strdup(_("Unknown")); g_strfreev(tokens); } date = msn_message_get_header_value(message, "Date"); stamp = msn_oim_parse_timestamp(date); purple_debug_info("msn", "oim Date:{%s},passport{%s}\n", date, passport); purple_serv_got_im(purple_account_get_connection(rdata->oim->session->account), passport, clean_msg, 0, stamp); /*Now get the oim message ID from the oim_list. * and append to read list to prepare for deleting the Offline Message when sign out */ msn_oim_post_delete_msg(rdata); g_free(passport); g_free(clean_msg); msn_message_unref(message); }
static JabberSaslState fb_handle_challenge(JabberStream *js, xmlnode *packet, xmlnode **response, char **msg) { xmlnode *reply = NULL; gchar *challenge; guchar *decoded; gsize decoded_len; gchar **pairs, *method, *nonce; gsize i; GString *request; gchar *enc_out; /* Get base64-encoded challenge from XML */ challenge = xmlnode_get_data(packet); if (challenge == NULL) { *msg = g_strdup(_("Invalid response from server")); return JABBER_SASL_STATE_FAIL; } /* Decode challenge */ decoded = purple_base64_decode(challenge, &decoded_len); if (decoded == NULL) { purple_debug_error("jabber", "X-FACEBOOK-PLATFORM challenge " "wasn't valid base64: %s\n", challenge); *msg = g_strdup(_("Invalid response from server")); g_free(challenge); return JABBER_SASL_STATE_FAIL; } g_free(challenge); /* NULL-terminate the challenge so we can parse it */ challenge = g_strndup((const gchar *)decoded, decoded_len); g_free(decoded); purple_debug_misc("jabber", "X-FACEBOOK-PLATFORM decoded " "challenge is %s\n", challenge); /* Get method and nonce */ method = NULL; nonce = NULL; pairs = g_strsplit(challenge, "&", 0); for (i = 0; pairs[i] != NULL; i++) { if (g_str_has_prefix(pairs[i], "method=")) { g_free(method); // TODO: Should url decode this value method = g_strdup(strchr(pairs[i], '=') + 1); } else if (g_str_has_prefix(pairs[i], "nonce=")) { g_free(nonce); // TODO: Should url decode this value nonce = g_strdup(strchr(pairs[i], '=') + 1); } } g_strfreev(pairs); if (!method || !nonce) { purple_debug_error("jabber", "X-FACEBOOK-PLATFORM challenge " "is missing method or nonce: %s\n", challenge); *msg = g_strdup(_("Invalid response from server")); g_free(method); g_free(nonce); g_free(challenge); return JABBER_SASL_STATE_FAIL; } g_free(challenge); request = purple_fbapi_construct_request(purple_connection_get_account(js->gc), method, "v", "1.0", "session_key", purple_connection_get_password(js->gc), "nonce", nonce, NULL); g_free(method); g_free(nonce); purple_debug_misc("jabber", "X-FACEBOOK-PLATFORM response before " "encoding is %s\n", request->str); enc_out = purple_base64_encode((const guchar *)request->str, request->len); g_string_free(request, TRUE); reply = xmlnode_new("response"); xmlnode_set_namespace(reply, NS_XMPP_SASL); xmlnode_insert_data(reply, enc_out, -1); g_free(enc_out); *response = reply; return JABBER_SASL_STATE_CONTINUE; }
static void got_sessionreq(MsnSlpCall *slpcall, const char *branch, const char *euf_guid, const char *context) { pecan_debug ("euf_guid=[%s]", euf_guid); if (!strcmp(euf_guid, "A4268EEC-FEC5-49E5-95C3-F126696BDBF6")) { /* Emoticon or UserDisplay */ char *content; gsize len; MsnSlpSession *slpsession; MsnSlpLink *slplink; MsnSlpMessage *slpmsg; MsnObject *obj; char *msnobj_data; PecanBuffer *image; int type; /* Send Ok */ content = pecan_strdup_printf("SessionID: %lu\r\n\r\n", slpcall->session_id); send_ok(slpcall, branch, "application/x-msnmsgr-sessionreqbody", content); g_free(content); slplink = slpcall->slplink; msnobj_data = (char *)purple_base64_decode(context, &len); obj = msn_object_new_from_string(msnobj_data); type = msn_object_get_type(obj); g_free(msnobj_data); if (type == MSN_OBJECT_USERTILE) { /* image is owned by a local object, not obj */ image = msn_object_get_image(obj); } #if PURPLE_VERSION_CHECK(2,5,0) else if (type == MSN_OBJECT_EMOTICON) { PurpleStoredImage *img; char *path; path = g_build_filename(purple_smileys_get_storing_dir(), msn_object_get_location(obj), NULL); img = purple_imgstore_new_from_file(path); image = pecan_buffer_new_memdup ((const gpointer) purple_imgstore_get_data (img), purple_imgstore_get_size (img)); purple_imgstore_unref(img); g_free(path); } #endif /* PURPLE_VERSION_CHECK(2,5,0) */ else { pecan_error ("Wrong object?"); msn_object_destroy(obj); g_return_if_reached(); } if (!image) { pecan_error ("Wrong object"); msn_object_destroy (obj); g_return_if_reached (); } msn_object_destroy(obj); { gchar *tmp; tmp = msn_object_to_string (obj); pecan_info ("object requested: %s", tmp); g_free (tmp); } slpsession = msn_slplink_find_slp_session(slplink, slpcall->session_id); /* DATA PREP */ slpmsg = msn_slpmsg_new(slplink); slpmsg->slpcall = slpcall; slpmsg->slpsession = slpsession; slpmsg->session_id = slpsession->id; msn_slpmsg_set_body(slpmsg, NULL, 4); #ifdef PECAN_DEBUG_SLP slpmsg->info = "SLP DATA PREP"; #endif msn_slplink_queue_slpmsg(slplink, slpmsg); /* DATA */ slpmsg = msn_slpmsg_new(slplink); slpmsg->slpcall = slpcall; slpmsg->slpsession = slpsession; slpmsg->flags = 0x20; #ifdef PECAN_DEBUG_SLP slpmsg->info = "SLP DATA"; #endif msn_slpmsg_set_image (slpmsg, image); msn_slplink_queue_slpmsg(slplink, slpmsg); } else if (!strcmp(euf_guid, "5D3E02AB-6190-11D3-BBBB-00C04F795683")) { /* File Transfer */ PurpleAccount *account; PurpleXfer *xfer; char *bin; gsize bin_len; guint32 file_size; char *file_name; gunichar2 *uni_name; account = slpcall->slplink->session->account; slpcall->cb = msn_xfer_completed_cb; slpcall->end_cb = msn_xfer_end_cb; slpcall->progress_cb = msn_xfer_progress_cb; slpcall->branch = g_strdup(branch); slpcall->pending = TRUE; xfer = purple_xfer_new(account, PURPLE_XFER_RECEIVE, slpcall->slplink->remote_user); if (xfer) { bin = (char *)purple_base64_decode(context, &bin_len); file_size = GUINT32_FROM_LE(*(gsize *)(bin + 8)); uni_name = (gunichar2 *)(bin + 20); while(*uni_name != 0 && ((char *)uni_name - (bin + 20)) < MAX_FILE_NAME_LEN) { *uni_name = GUINT16_FROM_LE(*uni_name); uni_name++; } file_name = g_utf16_to_utf8((const gunichar2 *)(bin + 20), -1, NULL, NULL, NULL); g_free(bin); purple_xfer_set_filename(xfer, file_name); purple_xfer_set_size(xfer, file_size); purple_xfer_set_init_fnc(xfer, msn_xfer_init); purple_xfer_set_request_denied_fnc(xfer, msn_xfer_cancel); purple_xfer_set_cancel_recv_fnc(xfer, msn_xfer_cancel); slpcall->xfer = xfer; purple_xfer_ref(slpcall->xfer); xfer->data = slpcall; purple_xfer_request(xfer); } } }