| 224 g_hash_table_insert(table, g_strdup(key), g_strdup(value)); |
224 g_hash_table_insert(table, g_strdup(key), g_strdup(value)); |
| 225 } else { |
225 } else { |
| 226 /* TODO: Some dictionaries have multiple values for the same |
226 /* TODO: Some dictionaries have multiple values for the same |
| 227 * key. Should append to a GList to handle this case. */ |
227 * key. Should append to a GList to handle this case. */ |
| 228 purple_debug_info("msim", "msim_parse: key %s already exists, " |
228 purple_debug_info("msim", "msim_parse: key %s already exists, " |
| 229 "not overwriting or replacing; ignoring new value %s", key, |
229 "not overwriting or replacing; ignoring new value %s\n", key, |
| 230 value); |
230 value); |
| 231 } |
231 } |
| 232 } else { |
232 } else { |
| 233 key = token; |
233 key = token; |
| 234 } |
234 } |
| 249 * @param password User's cleartext password. |
249 * @param password User's cleartext password. |
| 250 * |
250 * |
| 251 * @return Encoded login challenge response, ready to send to the server. Must be g_free()'d |
251 * @return Encoded login challenge response, ready to send to the server. Must be g_free()'d |
| 252 * when finished. |
252 * when finished. |
| 253 */ |
253 */ |
| 254 static gchar* msim_compute_login_response(guchar nonce[2*NONCE_HALF_SIZE], gchar* email, gchar* password) |
254 static gchar* msim_compute_login_response(guchar nonce[2*NONCE_HALF_SIZE], |
| |
255 gchar* email, gchar* password) |
| 255 { |
256 { |
| 256 PurpleCipherContext *key_context; |
257 PurpleCipherContext *key_context; |
| 257 PurpleCipher *sha1; |
258 PurpleCipher *sha1; |
| 258 PurpleCipherContext *rc4; |
259 PurpleCipherContext *rc4; |
| 259 guchar hash_pw[HASH_SIZE]; |
260 guchar hash_pw[HASH_SIZE]; |
| 260 guchar key[HASH_SIZE]; |
261 guchar key[HASH_SIZE]; |
| 261 gchar* password_utf16le; |
262 gchar* password_utf16le; |
| 262 guchar* data; |
263 guchar* data; |
| 263 guchar* data_out; |
264 guchar* data_out; |
| 264 gchar* response; |
265 gchar* response; |
| 265 int i; |
|
| 266 size_t data_len, data_out_len; |
266 size_t data_len, data_out_len; |
| |
267 gsize conv_bytes_read, conv_bytes_written; |
| |
268 GError* conv_error; |
| 267 |
269 |
| 268 //memset(nonce, 0, NONCE_HALF_SIZE); |
270 //memset(nonce, 0, NONCE_HALF_SIZE); |
| 269 //memset(nonce + NONCE_HALF_SIZE, 1, NONCE_HALF_SIZE); |
271 //memset(nonce + NONCE_HALF_SIZE, 1, NONCE_HALF_SIZE); |
| 270 |
272 |
| 271 /* Convert ASCII password to UTF16 little endian */ |
273 /* Convert ASCII password to UTF16 little endian */ |
| 272 /* TODO: use the built-in facility to do this, like Nathan Peterson does. */ |
274 purple_debug_info("msim", "converting password to UTF-16LE\n"); |
| 273 purple_debug_info("msim", "converting password to utf16le\n"); |
275 conv_error = NULL; |
| 274 //printf("pw=<%s>\n",password); |
276 password_utf16le = g_convert(password, -1, "UTF-16LE", "UTF-8", |
| |
277 &conv_bytes_read, &conv_bytes_written, &conv_error); |
| |
278 g_assert(conv_bytes_read == strlen(password)); |
| |
279 if (conv_error != NULL) |
| |
280 { |
| |
281 purple_debug_error("msim", |
| |
282 "g_convert password UTF8->UTF16LE failed: %s", |
| |
283 conv_error->message); |
| |
284 g_error_free(conv_error); |
| |
285 } |
| |
286 |
| |
287 #if 0 |
| 275 password_utf16le = g_new0(gchar, strlen(password) * 2); |
288 password_utf16le = g_new0(gchar, strlen(password) * 2); |
| 276 for (i = 0; i < strlen(password) * 2; i += 2) |
289 for (i = 0; i < strlen(password) * 2; i += 2) |
| 277 { |
290 { |
| 278 password_utf16le[i] = password[i / 2]; |
291 password_utf16le[i] = password[i / 2]; |
| 279 password_utf16le[i + 1] = 0; |
292 password_utf16le[i + 1] = 0; |
| 280 } |
293 } |
| |
294 #endif |
| 281 |
295 |
| 282 /* Compute password hash */ |
296 /* Compute password hash */ |
| 283 purple_cipher_digest_region("sha1", (guchar*)password_utf16le, strlen(password) * 2, |
297 purple_cipher_digest_region("sha1", (guchar*)password_utf16le, |
| 284 sizeof(hash_pw), hash_pw, NULL); |
298 conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); |
| |
299 g_free(password_utf16le); |
| 285 |
300 |
| 286 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE |
301 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE |
| 287 printf("pwhash = "); |
302 printf("pwhash = "); |
| 288 for (i = 0; i < sizeof(hash_pw); i++) |
303 for (i = 0; i < sizeof(hash_pw); i++) |
| 289 printf("%.2x ", hash_pw[i]); |
304 printf("%.2x ", hash_pw[i]); |
| 369 |
384 |
| 370 ret = send(session->fd, msg, strlen(msg), 0); |
385 ret = send(session->fd, msg, strlen(msg), 0); |
| 371 |
386 |
| 372 if (ret != strlen(msg)) |
387 if (ret != strlen(msg)) |
| 373 { |
388 { |
| 374 purple_debug_info("msim", "msim_send(%s): strlen=%d, but only wrote %s\n", |
389 purple_debug_info("msim", |
| |
390 "msim_send(%s): strlen=%d, but only wrote %s\n", |
| 375 msg, strlen(msg), ret); |
391 msg, strlen(msg), ret); |
| 376 /* TODO: better error */ |
392 /* TODO: better error */ |
| 377 } |
393 } |
| 378 } |
394 } |
| 379 |
395 |
| 469 elements = g_strsplit(item, "=", 2); |
485 elements = g_strsplit(item, "=", 2); |
| 470 |
486 |
| 471 key = elements[0]; |
487 key = elements[0]; |
| 472 if (!key) |
488 if (!key) |
| 473 { |
489 { |
| 474 purple_debug_info("msim", "msim_parse_body(%s): null key", body_str); |
490 purple_debug_info("msim", "msim_parse_body(%s): null key\n", |
| |
491 body_str); |
| 475 g_strfreev(elements); |
492 g_strfreev(elements); |
| 476 break; |
493 break; |
| 477 } |
494 } |
| 478 |
495 |
| 479 value = elements[1]; |
496 value = elements[1]; |
| 480 if (!value) |
497 if (!value) |
| 481 { |
498 { |
| 482 purple_debug_info("msim", "msim_parse_body(%s): null value", body_str); |
499 purple_debug_info("msim", "msim_parse_body(%s): null value\n", |
| |
500 body_str); |
| 483 g_strfreev(elements); |
501 g_strfreev(elements); |
| 484 break; |
502 break; |
| 485 } |
503 } |
| 486 |
504 |
| 487 //printf("-- %s: %s\n", key, value); |
505 //printf("-- %s: %s\n", key, value); |
| 610 username = g_hash_table_lookup(body, "UserName"); |
628 username = g_hash_table_lookup(body, "UserName"); |
| 611 if (username) |
629 if (username) |
| 612 { |
630 { |
| 613 g_hash_table_insert(session->user_lookup_cache, g_strdup(username), body); |
631 g_hash_table_insert(session->user_lookup_cache, g_strdup(username), body); |
| 614 } else { |
632 } else { |
| 615 purple_debug_info("msim", "msim_process_reply: not caching <%s>, no UserName", |
633 purple_debug_info("msim", |
| |
634 "msim_process_reply: not caching <%s>, no UserName\n", |
| 616 g_hash_table_lookup(table, "body")); |
635 g_hash_table_lookup(table, "body")); |
| 617 } |
636 } |
| 618 |
637 |
| 619 /* If a callback is registered for this userid lookup, call it. */ |
638 /* If a callback is registered for this userid lookup, call it. */ |
| 620 |
639 |
| 621 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
640 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
| 622 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
641 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
| 623 |
642 |
| 624 if (cb) |
643 if (cb) |
| 625 { |
644 { |
| 626 purple_debug_info("msim", "msim_process_body: calling callback now\n"); |
645 purple_debug_info("msim", |
| |
646 "msim_process_body: calling callback now\n"); |
| 627 cb(session, table, data); |
647 cb(session, table, data); |
| 628 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
648 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid)); |
| 629 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
649 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid)); |
| 630 |
650 |
| 631 /* Return 1 to tell caller of msim_process (msim_input_cb) to |
651 /* Return 1 to tell caller of msim_process (msim_input_cb) to |
| 632 * not destroy 'table'; allow 'cb' to hang on to it and destroy |
652 * not destroy 'table'; allow 'cb' to hang on to it and destroy |
| 633 * it when it wants. */ |
653 * it when it wants. */ |
| 634 return 1; |
654 return 1; |
| 635 } else { |
655 } else { |
| 636 purple_debug_info("msim", "msim_process_body: no callback for rid %d\n", rid); |
656 purple_debug_info("msim", |
| |
657 "msim_process_body: no callback for rid %d\n", rid); |
| 637 } |
658 } |
| 638 } |
659 } |
| 639 return 0; |
660 return 0; |
| 640 } |
661 } |
| 641 |
662 |
| 723 |
744 |
| 724 |
745 |
| 725 userid = g_hash_table_lookup(table, "f"); |
746 userid = g_hash_table_lookup(table, "f"); |
| 726 msg = g_hash_table_lookup(table, "msg"); |
747 msg = g_hash_table_lookup(table, "msg"); |
| 727 |
748 |
| 728 purple_debug_info("msim", "msim_incoming_im: got msg <%s> from <%s>, resolving username\n", |
749 purple_debug_info("msim", |
| |
750 "msim_incoming_im: got msg <%s> from <%s>, resolving username\n", |
| 729 msg, userid); |
751 msg, userid); |
| 730 |
752 |
| 731 msim_lookup_user(session, userid, msim_incoming_im_cb, g_strdup(msg)); |
753 msim_lookup_user(session, userid, msim_incoming_im_cb, g_strdup(msg)); |
| 732 |
754 |
| 733 return 0; |
755 return 0; |
| 779 { |
801 { |
| 780 purple_debug_info("msim", "msim_status_cb: no username?!\n"); |
802 purple_debug_info("msim", "msim_status_cb: no username?!\n"); |
| 781 return; |
803 return; |
| 782 } |
804 } |
| 783 |
805 |
| 784 purple_debug_info("msim", "msim_status_cb: updating status for <%s> to <%s>\n", username, status_str); |
806 purple_debug_info("msim", |
| |
807 "msim_status_cb: updating status for <%s> to <%s>\n", |
| |
808 username, status_str); |
| 785 |
809 |
| 786 /* TODO: generic functions to split into a GList */ |
810 /* TODO: generic functions to split into a GList */ |
| 787 status_array = g_strsplit(status_str, "|", 0); |
811 status_array = g_strsplit(status_str, "|", 0); |
| 788 for (list = NULL, i = 0; |
812 for (list = NULL, i = 0; |
| 789 status_array[i]; |
813 status_array[i]; |
| 801 /* Add buddy if not found */ |
825 /* Add buddy if not found */ |
| 802 buddy = purple_find_buddy(session->account, username); |
826 buddy = purple_find_buddy(session->account, username); |
| 803 if (!buddy) |
827 if (!buddy) |
| 804 { |
828 { |
| 805 /* TODO: purple aliases, userids and usernames */ |
829 /* TODO: purple aliases, userids and usernames */ |
| 806 purple_debug_info("msim", "msim_status: making new buddy for %s\n", username); |
830 purple_debug_info("msim", |
| |
831 "msim_status: making new buddy for %s\n", username); |
| 807 buddy = purple_buddy_new(session->account, username, NULL); |
832 buddy = purple_buddy_new(session->account, username, NULL); |
| 808 |
833 |
| 809 /* TODO: sometimes (when click on it), buddy list disappears. Fix. */ |
834 /* TODO: sometimes (when click on it), buddy list disappears. Fix. */ |
| 810 purple_blist_add_buddy(buddy, NULL, NULL, NULL); |
835 purple_blist_add_buddy(buddy, NULL, NULL, NULL); |
| 811 } else { |
836 } else { |
| 855 return 0; |
880 return 0; |
| 856 } |
881 } |
| 857 |
882 |
| 858 /* TODO: if buddies were identified on buddy list by uid, wouldn't have to lookup |
883 /* TODO: if buddies were identified on buddy list by uid, wouldn't have to lookup |
| 859 * before updating the status! Much more efficient. */ |
884 * before updating the status! Much more efficient. */ |
| 860 purple_debug_info("msim", "msim_status: got status msg <%s> for <%s>, scheduling lookup\n", |
885 purple_debug_info("msim", |
| |
886 "msim_status: got status msg <%s> for <%s>, scheduling lookup\n", |
| 861 status_str, userid); |
887 status_str, userid); |
| 862 |
888 |
| 863 /* Actually update status once obtain username */ |
889 /* Actually update status once obtain username */ |
| 864 msim_lookup_user(session, userid, msim_status_cb, g_strdup(status_str)); |
890 msim_lookup_user(session, userid, msim_status_cb, g_strdup(status_str)); |
| 865 |
891 |
| 982 close(source); |
1008 close(source); |
| 983 return; |
1009 return; |
| 984 } |
1010 } |
| 985 |
1011 |
| 986 purple_debug_info("msim", "buffer at %d (max %d), reading up to %d\n", |
1012 purple_debug_info("msim", "buffer at %d (max %d), reading up to %d\n", |
| 987 session->rxoff, MSIM_READ_BUF_SIZE, MSIM_READ_BUF_SIZE - session->rxoff); |
1013 session->rxoff, MSIM_READ_BUF_SIZE, |
| |
1014 MSIM_READ_BUF_SIZE - session->rxoff); |
| 988 |
1015 |
| 989 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds |
1016 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds |
| 990 * the file descriptor, but it sometimes differs from the 'source' parameter. |
1017 * the file descriptor, but it sometimes differs from the 'source' parameter. |
| 991 */ |
1018 */ |
| 992 n = recv(session->fd, session->rxbuf + session->rxoff, MSIM_READ_BUF_SIZE - session->rxoff, 0); |
1019 n = recv(session->fd, session->rxbuf + session->rxoff, MSIM_READ_BUF_SIZE - session->rxoff, 0); |
| 1017 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ |
1044 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ |
| 1018 if (strlen(session->rxbuf + session->rxoff) != n) |
1045 if (strlen(session->rxbuf + session->rxoff) != n) |
| 1019 { |
1046 { |
| 1020 /* Occurs after login, but it is not a null byte. */ |
1047 /* Occurs after login, but it is not a null byte. */ |
| 1021 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" |
1048 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" |
| 1022 "--null byte encountered?\n", strlen(session->rxbuf + session->rxoff), n); |
1049 "--null byte encountered?\n", |
| |
1050 strlen(session->rxbuf + session->rxoff), n); |
| 1023 //purple_connection_error(gc, "Invalid message - null byte on input"); |
1051 //purple_connection_error(gc, "Invalid message - null byte on input"); |
| 1024 return; |
1052 return; |
| 1025 } |
1053 } |
| 1026 |
1054 |
| 1027 session->rxoff += n; |
1055 session->rxoff += n; |
| 1266 |
1295 |
| 1267 g_return_if_fail(MSIM_SESSION_VALID(session)); |
1296 g_return_if_fail(MSIM_SESSION_VALID(session)); |
| 1268 g_return_if_fail(user != NULL); |
1297 g_return_if_fail(user != NULL); |
| 1269 g_return_if_fail(cb != NULL); |
1298 g_return_if_fail(cb != NULL); |
| 1270 |
1299 |
| 1271 purple_debug_info("msim", "msim_lookup_userid", "asynchronously looking up <%s>\n", user); |
1300 purple_debug_info("msim", "msim_lookup_userid", |
| |
1301 "asynchronously looking up <%s>\n", user); |
| 1272 |
1302 |
| 1273 /* TODO: check if this user's info was cached and fresh; if so return immediately */ |
1303 /* TODO: check if this user's info was cached and fresh; if so return immediately */ |
| 1274 #if 0 |
1304 #if 0 |
| 1275 /* If already know userid, then call callback immediately */ |
1305 /* If already know userid, then call callback immediately */ |
| 1276 cached_userid = g_hash_table_lookup(session->userid_cache, who); |
1306 cached_userid = g_hash_table_lookup(session->userid_cache, who); |
| 1350 session = gc->proto_data; |
1380 session = gc->proto_data; |
| 1351 |
1381 |
| 1352 /* If numeric ID, can send message immediately without userid lookup */ |
1382 /* If numeric ID, can send message immediately without userid lookup */ |
| 1353 if (msim_is_userid(who)) |
1383 if (msim_is_userid(who)) |
| 1354 { |
1384 { |
| 1355 purple_debug_info("msim", "msim_send_im: numeric 'who' detected, sending asap\n"); |
1385 purple_debug_info("msim", |
| |
1386 "msim_send_im: numeric 'who' detected, sending asap\n"); |
| 1356 msim_send_im_by_userid(session, who, message, flags); |
1387 msim_send_im_by_userid(session, who, message, flags); |
| 1357 return 1; |
1388 return 1; |
| 1358 } |
1389 } |
| 1359 |
1390 |
| 1360 /* Otherwise, add callback to IM when userid of destination is available */ |
1391 /* Otherwise, add callback to IM when userid of destination is available */ |