comparison libpurple/protocols/myspace/myspace.c @ 24986:f0c2e27c7ae7

propagate from branch 'im.pidgin.pidgin' (head b478b184a46116ac87ac85b8cd352ea564224019) to branch 'im.pidgin.pidgin.vv' (head 6ace5b357bd34d1a93fad3fcf59b31f846c64e0b)
author Mike Ruprecht <maiku@soc.pidgin.im>
date Fri, 02 Jan 2009 23:14:27 +0000
parents 68f4edb42f39 c6cadb7bdcf7
children 0d6bd0eca4cb
comparison
equal deleted inserted replaced
24985:ee2a2a9dda01 24986:f0c2e27c7ae7
1 /* MySpaceIM Protocol Plugin 1 /**
2 * MySpaceIM Protocol Plugin
2 * 3 *
3 * \author Jeff Connelly 4 * \author Jeff Connelly
4 * 5 *
5 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im> 6 * Copyright (C) 2007, Jeff Connelly <jeff2@soc.pidgin.im>
6 * 7 *
33 34
34 #define PURPLE_PLUGIN 35 #define PURPLE_PLUGIN
35 36
36 #include "myspace.h" 37 #include "myspace.h"
37 38
38 /* Internal functions */ 39 #include "privacy.h"
39 40
40 #ifdef MSIM_DEBUG_MSG 41 static void msim_set_status(PurpleAccount *account, PurpleStatus *status);
41 static void print_hash_item(gpointer key, gpointer value, gpointer user_data); 42 static void msim_set_idle(PurpleConnection *gc, int time);
42 #endif 43
43 44 /**
44 static int msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes); 45 * Perform actual postprocessing on a message, adding userid as specified.
45 static gboolean msim_login_challenge(MsimSession *session, MsimMessage *msg); 46 *
46 static gchar *msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], const gchar *email, const gchar *password, guint *response_len); 47 * @param msg The message to postprocess.
47 48 * @param uid_before Name of field where to insert new field before, or NULL for end.
48 static gboolean msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg); 49 * @param uid_field_name Name of field to add uid to.
49 static gboolean msim_incoming_bm(MsimSession *session, MsimMessage *msg); 50 * @param uid The userid to insert.
50 static gboolean msim_incoming_status(MsimSession *session, MsimMessage *msg); 51 *
51 static gboolean msim_incoming_im(MsimSession *session, MsimMessage *msg); 52 * If the field named by uid_field_name already exists, then its string contents will
52 /* static gboolean msim_incoming_zap(MsimSession *session, MsimMessage *msg); - in zap.c */ 53 * be used for the field, except "<uid>" will be replaced by the userid.
53 static gboolean msim_incoming_action(MsimSession *session, MsimMessage *msg); 54 *
54 static gboolean msim_incoming_media(MsimSession *session, MsimMessage *msg); 55 * If the field named by uid_field_name does not exist, it will be added before the
55 static gboolean msim_incoming_unofficial_client(MsimSession *session, 56 * field named by uid_before, as an integer, with the userid.
56 MsimMessage *msg); 57 *
57 58 * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing().
58 #ifdef MSIM_SEND_CLIENT_VERSION 59 */
59 static gboolean msim_send_unofficial_client(MsimSession *session, gchar *username); 60 static MsimMessage *
60 #endif 61 msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before,
61 62 const gchar *uid_field_name, guint uid)
62 static void msim_get_info_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); 63 {
63 64 MsimMessageElement *elem;
64 static void msim_set_status_code(MsimSession *session, guint code, gchar *statstring); 65
65 66 /* First, check - if the field already exists, replace <uid> within it */
66 static gboolean msim_process_server_info(MsimSession *session, MsimMessage *msg); 67 if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) {
67 static gboolean msim_web_challenge(MsimSession *session, MsimMessage *msg); 68 gchar *fmt_string;
68 static gboolean msim_process_reply(MsimSession *session, MsimMessage *msg); 69 gchar *uid_str, *new_str;
69 70
70 static gboolean msim_preprocess_incoming(MsimSession *session, MsimMessage *msg); 71 /* Get the packed element, flattening it. This allows <uid> to be
71 72 * replaced within nested data structures, since the replacement is done
72 #ifdef MSIM_USE_KEEPALIVE 73 * on the linear, packed data, not on a complicated data structure.
73 static gboolean msim_check_alive(gpointer data); 74 *
74 #endif 75 * For example, if the field was originally a dictionary or a list, you
75 76 * would have to iterate over all the items in it to see what needs to
76 static gboolean msim_is_username_set(MsimSession *session, MsimMessage *msg); 77 * be replaced. But by packing it first, the <uid> marker is easily replaced
77 78 * just by a string replacement.
78 static gboolean msim_process(MsimSession *session, MsimMessage *msg); 79 */
79 80 fmt_string = msim_msg_pack_element_data(elem);
80 static MsimMessage *msim_do_postprocessing(MsimMessage *msg, const gchar *uid_field_name, const gchar *uid_before, guint uid); 81
81 static void msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); 82 uid_str = g_strdup_printf("%d", uid);
82 static gboolean msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg, const gchar *username, const gchar *uid_field_name, const gchar *uid_before); 83 new_str = purple_strreplace(fmt_string, "<uid>", uid_str);
83 84 g_free(uid_str);
84 static gboolean msim_error(MsimSession *session, MsimMessage *msg); 85 g_free(fmt_string);
85 86
86 static void msim_check_inbox_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); 87 /* Free the old element data */
87 static gboolean msim_check_inbox(gpointer data); 88 msim_msg_free_element_data(elem->data);
88 89
89 static void msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond); 90 /* Replace it with our new data */
90 91 elem->data = new_str;
91 92 elem->type = MSIM_TYPE_RAW;
92 static void msim_connect_cb(gpointer data, gint source, const gchar *error_message); 93
93 94 } else {
94 static void msim_import_friends(PurplePluginAction *action); 95 /* Otherwise, insert new field into outgoing message. */
95 static void msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data); 96 msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid));
96 static gboolean msim_get_contact_list(MsimSession *session, int what_to_do_after); 97 }
97 98
98 static gboolean msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params); 99 return msg;
99 static void msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); 100 }
100 static void msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data); 101
101 102 /**
102 /** 103 * Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid).
103 * Load the plugin. 104 *
104 */ 105 * @param session
105 gboolean 106 * @param userinfo The user information reply message, containing the user ID
106 msim_load(PurplePlugin *plugin) 107 * @param data The message to postprocess and send.
107 { 108 *
108 /* If compiled to use RC4 from libpurple, check if it is really there. */ 109 * The data message should contain these fields:
109 if (!purple_ciphers_find_cipher("rc4")) { 110 *
110 purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n"); 111 * _uid_field_name: string, name of field to add with userid from userinfo message
111 purple_notify_error(plugin, _("Missing Cipher"), 112 * _uid_before: string, name of field before field to insert, or NULL for end
112 _("The RC4 cipher could not be found"), 113 */
113 _("Upgrade " 114 static void
114 "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM " 115 msim_postprocess_outgoing_cb(MsimSession *session, const MsimMessage *userinfo,
115 "plugin will not be loaded.")); 116 gpointer data)
116 return FALSE; 117 {
117 } 118 gchar *uid_field_name, *uid_before, *username;
118 return TRUE; 119 guint uid;
120 MsimMessage *msg, *body;
121
122 msg = (MsimMessage *)data;
123
124 /* Obtain userid from userinfo message. */
125 body = msim_msg_get_dictionary(userinfo, "body");
126 g_return_if_fail(body != NULL);
127
128 uid = msim_msg_get_integer(body, "UserID");
129 msim_msg_free(body);
130
131 username = msim_msg_get_string(msg, "_username");
132
133 if (!uid) {
134 gchar *msg;
135
136 msg = g_strdup_printf(_("No such user: %s"), username);
137 if (!purple_conv_present_error(username, session->account, msg)) {
138 purple_notify_error(NULL, NULL, _("User lookup"), msg);
139 }
140
141 g_free(msg);
142 g_free(username);
143 /* TODO: free
144 * msim_msg_free(msg);
145 */
146 return;
147 }
148
149 uid_field_name = msim_msg_get_string(msg, "_uid_field_name");
150 uid_before = msim_msg_get_string(msg, "_uid_before");
151
152 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
153
154 /* Send */
155 if (!msim_msg_send(session, msg)) {
156 msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg);
157 }
158
159
160 /* Free field names AFTER sending message, because MsimMessage does NOT copy
161 * field names - instead, treats them as static strings (which they usually are).
162 */
163 g_free(uid_field_name);
164 g_free(uid_before);
165 g_free(username);
166 /* TODO: free
167 * msim_msg_free(msg);
168 */
169 }
170
171 /**
172 * Postprocess and send a message.
173 *
174 * @param session
175 * @param msg Message to postprocess. Will NOT be freed.
176 * @param username Username to resolve. Assumed to be a static string (will not be freed or copied).
177 * @param uid_field_name Name of new field to add, containing uid of username. Static string.
178 * @param uid_before Name of existing field to insert username field before. Static string.
179 *
180 * @return TRUE if successful.
181 */
182 static gboolean
183 msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg,
184 const gchar *username, const gchar *uid_field_name,
185 const gchar *uid_before)
186 {
187 PurpleBuddy *buddy;
188 guint uid;
189 gboolean rc;
190
191 g_return_val_if_fail(msg != NULL, FALSE);
192
193 /* Store information for msim_postprocess_outgoing_cb(). */
194 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
195 msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name));
196 msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before));
197
198 /* First, try the most obvious. If numeric userid is given, use that directly. */
199 if (msim_is_userid(username)) {
200 uid = atol(username);
201 } else {
202 /* Next, see if on buddy list and know uid. */
203 buddy = purple_find_buddy(session->account, username);
204 if (buddy) {
205 uid = purple_blist_node_get_int(&buddy->node, "UserID");
206 } else {
207 uid = 0;
208 }
209
210 if (!buddy || !uid) {
211 /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */
212 purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n",
213 username ? username : "(NULL)");
214 msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg));
215 return TRUE; /* not sure of status yet - haven't sent! */
216 }
217 }
218
219 /* Already have uid, postprocess and send msg immediately. */
220 purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n",
221 username ? username : "(NULL)", uid);
222
223 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
224
225 rc = msim_msg_send(session, msg);
226
227 /* TODO: free
228 * msim_msg_free(msg);
229 */
230
231 return rc;
232 }
233
234 /**
235 * Send a buddy message of a given type.
236 *
237 * @param session
238 * @param who Username to send message to.
239 * @param text Message text to send. Not freed; will be copied.
240 * @param type A MSIM_BM_* constant.
241 *
242 * @return TRUE if success, FALSE if fail.
243 *
244 * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
245 */
246 gboolean
247 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text,
248 int type)
249 {
250 gboolean rc;
251 MsimMessage *msg;
252 const gchar *from_username;
253
254 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
255 g_return_val_if_fail(who != NULL, FALSE);
256 g_return_val_if_fail(text != NULL, FALSE);
257
258 from_username = session->account->username;
259
260 g_return_val_if_fail(from_username != NULL, FALSE);
261
262 purple_debug_info("msim", "sending %d message from %s to %s: %s\n",
263 type, from_username, who, text);
264
265 msg = msim_msg_new(
266 "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
267 "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
268 /* 't' will be inserted here */
269 "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
270 "msg", MSIM_TYPE_STRING, g_strdup(text),
271 NULL);
272
273 rc = msim_postprocess_outgoing(session, msg, who, "t", "cv");
274
275 msim_msg_free(msg);
276
277 return rc;
278 }
279
280 /**
281 * Lookup a username by userid, from buddy list.
282 *
283 * @param wanted_uid
284 *
285 * @return Username of wanted_uid, if on blist, or NULL.
286 * This is a static string, so don't free it. Copy it if needed.
287 *
288 */
289 static const gchar *
290 msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid)
291 {
292 GSList *buddies, *cur;
293 const gchar *ret;
294
295 buddies = purple_find_buddies(account, NULL);
296
297 if (!buddies)
298 {
299 purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n");
300 return NULL;
301 }
302
303 ret = NULL;
304
305 for (cur = buddies; cur != NULL; cur = g_slist_next(cur))
306 {
307 PurpleBuddy *buddy;
308 guint uid;
309 const gchar *name;
310
311 /* See finch/gnthistory.c */
312 buddy = cur->data;
313
314 uid = purple_blist_node_get_int(&buddy->node, "UserID");
315 name = purple_buddy_get_name(buddy);
316
317 if (uid == wanted_uid)
318 {
319 ret = name;
320 break;
321 }
322 }
323
324 g_slist_free(buddies);
325 return ret;
326 }
327
328 /**
329 * Setup a callback, to be called when a reply is received with the returned rid.
330 *
331 * @param cb The callback, an MSIM_USER_LOOKUP_CB.
332 * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *).
333 *
334 * @return The request/reply ID, used to link replies with requests, or -1.
335 * Put the rid in your request, 'rid' field.
336 *
337 * TODO: Make more generic and more specific:
338 * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
339 * 2) data - make it an MsimMessage?
340 */
341 guint
342 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb,
343 gpointer data)
344 {
345 guint rid;
346
347 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
348
349 rid = session->next_rid++;
350
351 g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb);
352 g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data);
353
354 return rid;
355 }
356
357 /**
358 * Return the icon name for a buddy and account.
359 *
360 * @param acct The account to find the icon for, or NULL for protocol icon.
361 * @param buddy The buddy to find the icon for, or NULL for the account icon.
362 *
363 * @return The base icon name string.
364 */
365 static const gchar *
366 msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy)
367 {
368 /* Use a MySpace icon submitted by hbons at
369 * http://developer.pidgin.im/wiki/MySpaceIM. */
370 return "myspace";
371 }
372
373 /**
374 * Obtain the status text for a buddy.
375 *
376 * @param buddy The buddy to obtain status text for.
377 *
378 * @return Status text, or NULL if error. Caller g_free()'s.
379 */
380 static char *
381 msim_status_text(PurpleBuddy *buddy)
382 {
383 MsimSession *session;
384 MsimUser *user;
385 const gchar *display_name, *headline;
386
387 g_return_val_if_fail(buddy != NULL, NULL);
388
389 user = msim_get_user_from_buddy(buddy);
390
391 session = (MsimSession *)buddy->account->gc->proto_data;
392 g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL);
393
394 display_name = headline = NULL;
395
396 /* Retrieve display name and/or headline, depending on user preference. */
397 if (purple_account_get_bool(session->account, "show_headline", TRUE)) {
398 headline = user->headline;
399 }
400
401 if (purple_account_get_bool(session->account, "show_display_name", FALSE)) {
402 display_name = user->display_name;
403 }
404
405 /* Return appropriate combination of display name and/or headline, or neither. */
406
407 if (display_name && headline) {
408 return g_strconcat(display_name, " ", headline, NULL);
409 } else if (display_name) {
410 return g_strdup(display_name);
411 } else if (headline) {
412 return g_strdup(headline);
413 }
414
415 return NULL;
416 }
417
418 /**
419 * Obtain the tooltip text for a buddy.
420 *
421 * @param buddy Buddy to obtain tooltip text on.
422 * @param user_info Variable modified to have the tooltip text.
423 * @param full TRUE if should obtain full tooltip text.
424 */
425 static void
426 msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info,
427 gboolean full)
428 {
429 MsimUser *user;
430
431 g_return_if_fail(buddy != NULL);
432 g_return_if_fail(user_info != NULL);
433
434 user = msim_get_user_from_buddy(buddy);
435
436 if (PURPLE_BUDDY_IS_ONLINE(buddy)) {
437 MsimSession *session;
438
439 session = (MsimSession *)buddy->account->gc->proto_data;
440
441 g_return_if_fail(MSIM_SESSION_VALID(session));
442
443 /* TODO: if (full), do something different? */
444
445 /* TODO: request information? have to figure out how to do
446 * the asynchronous lookup like oscar does (tooltip shows
447 * 'retrieving...' if not yet available, then changes when it is).
448 *
449 * Right now, only show what we have on hand.
450 */
451
452 /* Show abbreviated user info. */
453 msim_append_user_info(session, user_info, user, FALSE);
454 }
119 } 455 }
120 456
121 /** 457 /**
122 * Get possible user status types. Based on mockprpl. 458 * Get possible user status types. Based on mockprpl.
123 * 459 *
124 * @return GList of status types. 460 * @return GList of status types.
125 */ 461 */
126 GList * 462 static GList *
127 msim_status_types(PurpleAccount *acct) 463 msim_status_types(PurpleAccount *acct)
128 { 464 {
129 GList *types; 465 GList *types;
130 PurpleStatusType *status; 466 PurpleStatusType *status;
131 467
175 511
176 return types; 512 return types;
177 } 513 }
178 514
179 /** 515 /**
180 * Return the icon name for a buddy and account. 516 * Compute the base64'd login challenge response based on username, password, nonce, and IPs.
181 * 517 *
182 * @param acct The account to find the icon for, or NULL for protocol icon. 518 * @param nonce The base64 encoded nonce ('nc') field from the server.
183 * @param buddy The buddy to find the icon for, or NULL for the account icon. 519 * @param email User's email address (used as login name).
184 * 520 * @param password User's cleartext password.
185 * @return The base icon name string. 521 * @param response_len Will be written with response length.
186 */ 522 *
187 const gchar * 523 * @return Binary login challenge response, ready to send to the server.
188 msim_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) 524 * Must be g_free()'d when finished. NULL if error.
189 { 525 */
190 /* Use a MySpace icon submitted by hbons at 526 static gchar *
191 * http://developer.pidgin.im/wiki/MySpaceIM. */ 527 msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE],
192 return "myspace"; 528 const gchar *email, const gchar *password, guint *response_len)
193 } 529 {
194 530 PurpleCipherContext *key_context;
195 #ifdef MSIM_DEBUG_MSG 531 PurpleCipher *sha1;
196 static void 532 PurpleCipherContext *rc4;
197 print_hash_item(gpointer key, gpointer value, gpointer user_data) 533
198 { 534 guchar hash_pw[HASH_SIZE];
199 purple_debug_info("msim", "%s=%s\n", 535 guchar key[HASH_SIZE];
200 key ? (gchar *)key : "(NULL)", 536 gchar *password_utf16le, *password_utf8_lc;
201 value ? (gchar *)value : "(NULL)"); 537 GString *data;
202 } 538 guchar *data_out;
539 size_t data_out_len;
540 gsize conv_bytes_read, conv_bytes_written;
541 GError *conv_error;
542 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
543 int i;
203 #endif 544 #endif
204 545
205 /** 546 g_return_val_if_fail(nonce != NULL, NULL);
206 * Send raw data (given as a NUL-terminated string) to the server. 547 g_return_val_if_fail(email != NULL, NULL);
207 * 548 g_return_val_if_fail(password != NULL, NULL);
208 * @param session 549 g_return_val_if_fail(response_len != NULL, NULL);
209 * @param msg The raw data to send, in a NUL-terminated string. 550
210 * 551 /* Convert password to lowercase (required for passwords containing
211 * @return TRUE if succeeded, FALSE if not. 552 * uppercase characters). MySpace passwords are lowercase,
212 * 553 * see ticket #2066. */
213 */ 554 password_utf8_lc = g_utf8_strdown(password, -1);
214 gboolean 555
215 msim_send_raw(MsimSession *session, const gchar *msg) 556 /* Convert ASCII password to UTF16 little endian */
216 { 557 purple_debug_info("msim", "converting password to UTF-16LE\n");
217 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); 558 conv_error = NULL;
218 g_return_val_if_fail(msg != NULL, FALSE); 559 password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8",
219 560 &conv_bytes_read, &conv_bytes_written, &conv_error);
220 purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg); 561 g_free(password_utf8_lc);
221 562
222 return msim_send_really_raw(session->gc, msg, strlen(msg)) == 563 g_return_val_if_fail(conv_bytes_read == strlen(password), NULL);
223 strlen(msg); 564
224 } 565 if (conv_error != NULL) {
225 566 purple_debug_error("msim",
226 /** Send raw data to the server, possibly with embedded NULs. 567 "g_convert password UTF8->UTF16LE failed: %s",
227 * 568 conv_error->message);
228 * Used in prpl_info struct, so that plugins can have the most possible 569 g_error_free(conv_error);
229 * control of what is sent over the connection. Inside this prpl, 570 return NULL;
230 * msim_send_raw() is used, since it sends NUL-terminated strings (easier). 571 }
231 * 572
232 * @param gc PurpleConnection 573 /* Compute password hash */
233 * @param buf Buffer to send 574 purple_cipher_digest_region("sha1", (guchar *)password_utf16le,
234 * @param total_bytes Size of buffer to send 575 conv_bytes_written, sizeof(hash_pw), hash_pw, NULL);
235 * 576 g_free(password_utf16le);
236 * @return Bytes successfully sent, or -1 on error. 577
237 */ 578 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
238 static int 579 purple_debug_info("msim", "pwhash = ");
239 msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes) 580 for (i = 0; i < sizeof(hash_pw); i++)
240 { 581 purple_debug_info("msim", "%.2x ", hash_pw[i]);
241 int total_bytes_sent; 582 purple_debug_info("msim", "\n");
242 MsimSession *session; 583 #endif
243 584
244 g_return_val_if_fail(gc != NULL, -1); 585 /* key = sha1(sha1(pw) + nonce2) */
245 g_return_val_if_fail(buf != NULL, -1); 586 sha1 = purple_ciphers_find_cipher("sha1");
246 g_return_val_if_fail(total_bytes >= 0, -1); 587 key_context = purple_cipher_context_new(sha1, NULL);
247 588 purple_cipher_context_append(key_context, hash_pw, HASH_SIZE);
248 session = (MsimSession *)gc->proto_data; 589 purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE);
249 590 purple_cipher_context_digest(key_context, sizeof(key), key, NULL);
250 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1); 591 purple_cipher_context_destroy(key_context);
251 592
252 /* Loop until all data is sent, or a failure occurs. */ 593 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
253 total_bytes_sent = 0; 594 purple_debug_info("msim", "key = ");
254 do { 595 for (i = 0; i < sizeof(key); i++) {
255 int bytes_sent; 596 purple_debug_info("msim", "%.2x ", key[i]);
256 597 }
257 bytes_sent = send(session->fd, buf + total_bytes_sent, 598 purple_debug_info("msim", "\n");
258 total_bytes - total_bytes_sent, 0); 599 #endif
259 600
260 if (bytes_sent < 0) { 601 rc4 = purple_cipher_context_new_by_name("rc4", NULL);
261 purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n", 602
262 buf, g_strerror(errno)); 603 /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash),
263 return total_bytes_sent; 604 * but only first 0x10 used for the RC4 key. */
264 } 605 purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
265 total_bytes_sent += bytes_sent; 606 purple_cipher_context_set_key(rc4, key);
266 607
267 } while(total_bytes_sent < total_bytes); 608 /* TODO: obtain IPs of network interfaces */
268 609
269 return total_bytes_sent; 610 /* rc4 encrypt:
270 } 611 * nonce1+email+IP list */
271 612
272 613 data = g_string_new(NULL);
273 /** 614 g_string_append_len(data, nonce, NONCE_SIZE);
274 * Start logging in to the MSIM servers. 615 g_string_append(data, email);
275 * 616 g_string_append_len(data, MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
276 * @param acct Account information to use to login. 617
277 */ 618 data_out = g_new0(guchar, data->len);
278 void 619
279 msim_login(PurpleAccount *acct) 620 purple_cipher_context_encrypt(rc4, (const guchar *)data->str,
280 { 621 data->len, data_out, &data_out_len);
281 PurpleConnection *gc; 622 purple_cipher_context_destroy(rc4);
282 const gchar *host; 623
283 int port; 624 if (data_out_len != data->len) {
284 625 purple_debug_info("msim", "msim_compute_login_response: "
285 g_return_if_fail(acct != NULL); 626 "data length mismatch: %" G_GSIZE_FORMAT " != %"
286 g_return_if_fail(acct->username != NULL); 627 G_GSIZE_FORMAT "\n", data_out_len, data->len);
287 628 }
288 purple_debug_info("msim", "logging in %s\n", acct->username); 629
289 630 g_string_free(data, TRUE);
290 gc = purple_account_get_connection(acct); 631
291 gc->proto_data = msim_session_new(acct); 632 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
292 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC; 633 purple_debug_info("msim", "response=<%s>\n", data_out);
293 634 #endif
294 /* 1. connect to server */ 635
295 purple_connection_update_progress(gc, _("Connecting"), 636 *response_len = data_out_len;
296 0, /* which connection step this is */ 637
297 4); /* total number of steps */ 638 return (gchar *)data_out;
298 639 }
299 host = purple_account_get_string(acct, "server", MSIM_SERVER); 640
300 port = purple_account_get_int(acct, "port", MSIM_PORT); 641 /**
301 642 * Process a login challenge, sending a response.
302 /* From purple.sf.net/api: 643 *
303 * """Note that this function name can be misleading--although it is called 644 * @param session
304 * "proxy connect," it is used for establishing any outgoing TCP connection,
305 * whether through a proxy or not.""" */
306
307 /* Calls msim_connect_cb when connected. */
308 if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) {
309 /* TODO: try other ports if in auto mode, then save
310 * working port and try that first next time. */
311 purple_connection_error_reason (gc,
312 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
313 _("Couldn't create socket"));
314 return;
315 }
316 }
317
318 /**
319 * Process a login challenge, sending a response.
320 *
321 * @param session
322 * @param msg Login challenge message. 645 * @param msg Login challenge message.
323 * 646 *
324 * @return TRUE if successful, FALSE if not 647 * @return TRUE if successful, FALSE if not
325 */ 648 */
326 static gboolean 649 static gboolean
327 msim_login_challenge(MsimSession *session, MsimMessage *msg) 650 msim_login_challenge(MsimSession *session, MsimMessage *msg)
328 { 651 {
329 PurpleAccount *account; 652 PurpleAccount *account;
330 gchar *response; 653 gchar *response;
331 guint response_len; 654 guint response_len;
332 gchar *nc; 655 gchar *nc;
381 704
382 return ret; 705 return ret;
383 } 706 }
384 707
385 /** 708 /**
386 * Compute the base64'd login challenge response based on username, password, nonce, and IPs. 709 * Process unrecognized information.
387 * 710 *
388 * @param nonce The base64 encoded nonce ('nc') field from the server. 711 * @param session
389 * @param email User's email address (used as login name). 712 * @param msg An MsimMessage that was unrecognized, or NULL.
390 * @param password User's cleartext password. 713 * @param note Information on what was unrecognized, or NULL.
391 * @param response_len Will be written with response length. 714 */
392 * 715 void
393 * @return Binary login challenge response, ready to send to the server. 716 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note)
394 * Must be g_free()'d when finished. NULL if error. 717 {
395 */ 718 /* TODO: Some more context, outwardly equivalent to a backtrace,
396 static gchar * 719 * for helping figure out what this msg is for. What was going on?
397 msim_compute_login_response(const gchar nonce[2 * NONCE_SIZE], 720 * But not too much information so that a user
398 const gchar *email, const gchar *password, guint *response_len) 721 * posting this dump reveals confidential information.
399 { 722 */
400 PurpleCipherContext *key_context; 723
401 PurpleCipher *sha1; 724 /* TODO: dump unknown msgs to file, so user can send them to me
402 PurpleCipherContext *rc4; 725 * if they wish, to help add support for new messages (inspired
403 726 * by Alexandr Shutko, who maintains OSCAR protocol documentation).
404 guchar hash_pw[HASH_SIZE]; 727 *
405 guchar key[HASH_SIZE]; 728 * Filed enhancement ticket for libpurple as #4688.
406 gchar *password_utf16le, *password_utf8_lc; 729 */
407 guchar *data; 730
408 guchar *data_out; 731 purple_debug_info("msim", "Unrecognized data on account for %s\n",
409 size_t data_len, data_out_len; 732 (session && session->account && session->account->username) ?
410 gsize conv_bytes_read, conv_bytes_written; 733 session->account->username : "(NULL)");
411 GError *conv_error; 734 if (note) {
412 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE 735 purple_debug_info("msim", "(Note: %s)\n", note);
413 int i; 736 }
737
738 if (msg) {
739 msim_msg_dump("Unrecognized message dump: %s\n", msg);
740 }
741 }
742
743 /** Called when the session key arrives to check whether the user
744 * has a username, and set one if desired. */
745 static gboolean
746 msim_is_username_set(MsimSession *session, MsimMessage *msg)
747 {
748 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
749 g_return_val_if_fail(msg != NULL, FALSE);
750 g_return_val_if_fail(session->gc != NULL, FALSE);
751
752 session->sesskey = msim_msg_get_integer(msg, "sesskey");
753 purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey);
754
755 /* What is proof? Used to be uid, but now is 52 base64'd bytes... */
756
757 /* Comes with: proof,profileid,userid,uniquenick -- all same values
758 * some of the time, but can vary. This is our own user ID. */
759 session->userid = msim_msg_get_integer(msg, "userid");
760
761 /* Save uid to account so this account can be looked up by uid. */
762 purple_account_set_int(session->account, "uid", session->userid);
763
764 /* Not sure what profileid is used for. */
765 if (msim_msg_get_integer(msg, "profileid") != session->userid) {
766 msim_unrecognized(session, msg,
767 "Profile ID didn't match user ID, don't know why");
768 }
769
770 /* We now know are our own username, only after we're logged in..
771 * which is weird, but happens because you login with your email
772 * address and not username. Will be freed in msim_session_destroy(). */
773 session->username = msim_msg_get_string(msg, "uniquenick");
774
775 /* If user lacks a username, help them get one. */
776 if (msim_msg_get_integer(msg, "uniquenick") == session->userid) {
777 purple_debug_info("msim_is_username_set", "no username is set\n");
778 purple_request_yes_no(session->gc,
779 _("MySpaceIM - No Username Set"),
780 _("You appear to have no MySpace username."),
781 _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"),
782 0,
783 session->account,
784 NULL,
785 NULL,
786 session->gc,
787 G_CALLBACK(msim_set_username_cb),
788 G_CALLBACK(msim_do_not_set_username_cb));
789 purple_debug_info("msim_is_username_set","'username not set' alert prompted\n");
790 return FALSE;
791 }
792 return TRUE;
793 }
794
795 #ifdef MSIM_USE_KEEPALIVE
796 /**
797 * Check if the connection is still alive, based on last communication.
798 */
799 static gboolean
800 msim_check_alive(gpointer data)
801 {
802 MsimSession *session;
803 time_t delta;
804
805 session = (MsimSession *)data;
806
807 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
808
809 delta = time(NULL) - session->last_comm;
810
811 /* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */
812 if (delta >= MSIM_KEEPALIVE_INTERVAL) {
813 purple_debug_info("msim",
814 "msim_check_alive: %zu > interval of %d, presumed dead\n",
815 delta, MSIM_KEEPALIVE_INTERVAL);
816 purple_connection_error_reason(session->gc,
817 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
818 _("Lost connection with server"));
819
820 return FALSE;
821 }
822
823 return TRUE;
824 }
414 #endif 825 #endif
415 826
416 g_return_val_if_fail(nonce != NULL, NULL); 827 /**
417 g_return_val_if_fail(email != NULL, NULL); 828 * Handle mail reply checks.
418 g_return_val_if_fail(password != NULL, NULL); 829 */
419 g_return_val_if_fail(response_len != NULL, NULL); 830 static void
420 831 msim_check_inbox_cb(MsimSession *session, const MsimMessage *reply, gpointer data)
421 /* Convert password to lowercase (required for passwords containing 832 {
422 * uppercase characters). MySpace passwords are lowercase, 833 MsimMessage *body;
423 * see ticket #2066. */ 834 guint old_inbox_status;
424 password_utf8_lc = g_utf8_strdown(password, -1); 835 guint i, n;
425 836 const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
426 /* Convert ASCII password to UTF16 little endian */ 837
427 purple_debug_info("msim", "converting password to UTF-16LE\n"); 838 /* Information for each new inbox message type. */
428 conv_error = NULL; 839 static struct
429 password_utf16le = g_convert(password_utf8_lc, -1, "UTF-16LE", "UTF-8", 840 {
430 &conv_bytes_read, &conv_bytes_written, &conv_error); 841 const gchar *key;
431 g_free(password_utf8_lc); 842 guint bit;
432 843 const gchar *url;
433 g_return_val_if_fail(conv_bytes_read == strlen(password), NULL); 844 const gchar *text;
434 845 } message_types[] = {
435 if (conv_error != NULL) { 846 { "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL },
436 purple_debug_error("msim", 847 { "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL },
437 "g_convert password UTF8->UTF16LE failed: %s", 848 { "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL },
438 conv_error->message); 849 { "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL },
439 g_error_free(conv_error); 850 { "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }
440 return NULL; 851 };
441 } 852
442 853 /* Can't write _()'d strings in array initializers. Workaround. */
443 /* Compute password hash */ 854 message_types[0].text = _("New mail messages");
444 purple_cipher_digest_region("sha1", (guchar *)password_utf16le, 855 message_types[1].text = _("New blog comments");
445 conv_bytes_written, sizeof(hash_pw), hash_pw, NULL); 856 message_types[2].text = _("New profile comments");
446 g_free(password_utf16le); 857 message_types[3].text = _("New friend requests!");
447 858 message_types[4].text = _("New picture comments");
448 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE 859
449 purple_debug_info("msim", "pwhash = "); 860 g_return_if_fail(reply != NULL);
450 for (i = 0; i < sizeof(hash_pw); i++) 861
451 purple_debug_info("msim", "%.2x ", hash_pw[i]); 862 body = msim_msg_get_dictionary(reply, "body");
452 purple_debug_info("msim", "\n"); 863
864 if (body == NULL)
865 return;
866
867 old_inbox_status = session->inbox_status;
868
869 n = 0;
870
871 for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) {
872 const gchar *key;
873 guint bit;
874
875 key = message_types[i].key;
876 bit = message_types[i].bit;
877
878 if (msim_msg_get(body, key)) {
879 /* Notify only on when _changes_ from no mail -> has mail
880 * (edge triggered) */
881 if (!(session->inbox_status & bit)) {
882 purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n",
883 key ? key : "(NULL)", n);
884
885 subjects[n] = message_types[i].text;
886 froms[n] = _("MySpace");
887 tos[n] = session->username;
888 /* TODO: append token, web challenge, so automatically logs in.
889 * Would also need to free strings because they won't be static
890 */
891 urls[n] = message_types[i].url;
892
893 ++n;
894 } else {
895 purple_debug_info("msim",
896 "msim_check_inbox_cb: already notified of %s\n",
897 key ? key : "(NULL)");
898 }
899
900 session->inbox_status |= bit;
901 }
902 }
903
904 if (n) {
905 purple_debug_info("msim",
906 "msim_check_inbox_cb: notifying of %d\n", n);
907
908 /* TODO: free strings with callback _if_ change to dynamic (w/ token) */
909 purple_notify_emails(session->gc, /* handle */
910 n, /* count */
911 TRUE, /* detailed */
912 subjects, froms, tos, urls,
913 NULL, /* PurpleNotifyCloseCallback cb */
914 NULL); /* gpointer user_data */
915
916 }
917
918 msim_msg_free(body);
919 }
920
921 /**
922 * Send request to check if there is new mail.
923 */
924 static gboolean
925 msim_check_inbox(gpointer data)
926 {
927 MsimSession *session;
928
929 session = (MsimSession *)data;
930
931 if (!MSIM_SESSION_VALID(session)) {
932 purple_debug_info("msim", "msim_check_inbox: session invalid, stopping the mail check.\n");
933 return FALSE;
934 }
935
936 purple_debug_info("msim", "msim_check_inbox: checking mail\n");
937 g_return_val_if_fail(msim_send(session,
938 "persist", MSIM_TYPE_INTEGER, 1,
939 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
940 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
941 "dsn", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_DSN,
942 "lid", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_LID,
943 "uid", MSIM_TYPE_INTEGER, session->userid,
944 "rid", MSIM_TYPE_INTEGER,
945 msim_new_reply_callback(session, msim_check_inbox_cb, NULL),
946 "body", MSIM_TYPE_STRING, g_strdup(""),
947 NULL), TRUE);
948
949 /* Always return true, so that we keep checking for mail. */
950 return TRUE;
951 }
952
953 /**
954 * Add contact from server to buddy list, after looking up username.
955 * Callback from msim_add_contact_from_server().
956 *
957 * @param data An MsimMessage * of the contact information. Will be freed.
958 */
959 static void
960 msim_add_contact_from_server_cb(MsimSession *session, const MsimMessage *user_lookup_info, gpointer data)
961 {
962 MsimMessage *contact_info, *user_lookup_info_body;
963 PurpleGroup *group;
964 PurpleBuddy *buddy;
965 MsimUser *user;
966 gchar *username, *group_name, *display_name;
967 guint uid, visibility;
968
969 contact_info = (MsimMessage *)data;
970 purple_debug_info("msim_add_contact_from_server_cb", "contact_info addr=%p\n", contact_info);
971 uid = msim_msg_get_integer(contact_info, "ContactID");
972
973 if (!user_lookup_info) {
974 username = g_strdup(msim_uid2username_from_blist(session->account, uid));
975 display_name = NULL;
976 g_return_if_fail(username != NULL);
977 } else {
978 user_lookup_info_body = msim_msg_get_dictionary(user_lookup_info, "body");
979 username = msim_msg_get_string(user_lookup_info_body, "UserName");
980 display_name = msim_msg_get_string(user_lookup_info_body, "DisplayName");
981 msim_msg_free(user_lookup_info_body);
982 g_return_if_fail(username != NULL);
983 }
984
985 purple_debug_info("msim_add_contact_from_server_cb",
986 "*** about to add/update username=%s\n", username);
987
988 /* 1. Creates a new group, or gets existing group if it exists (or so
989 * the documentation claims). */
990 group_name = msim_msg_get_string(contact_info, "GroupName");
991 if (!group_name || (*group_name == '\0')) {
992 g_free(group_name);
993 group_name = g_strdup(_("IM Friends"));
994 purple_debug_info("myspace", "No GroupName specified, defaulting to '%s'.\n", group_name);
995 }
996 group = purple_find_group(group_name);
997 if (!group) {
998 group = purple_group_new(group_name);
999 /* Add group to beginning. See #2752. */
1000 purple_blist_add_group(group, NULL);
1001 }
1002 g_free(group_name);
1003
1004 visibility = msim_msg_get_integer(contact_info, "Visibility");
1005 if (visibility == 2) {
1006 /* This buddy is blocked (and therefore not on our buddy list */
1007 purple_privacy_deny_add(session->account, username, TRUE);
1008 msim_msg_free(contact_info);
1009 g_free(username);
1010 g_free(display_name);
1011 return;
1012 }
1013
1014 /* 2. Get or create buddy */
1015 buddy = purple_find_buddy(session->account, username);
1016 if (!buddy) {
1017 purple_debug_info("msim_add_contact_from_server_cb",
1018 "creating new buddy: %s\n", username);
1019 buddy = purple_buddy_new(session->account, username, NULL);
1020 }
1021
1022 /* TODO: use 'Position' in contact_info to take into account where buddy is */
1023 purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */);
1024
1025 if (strtol(username, NULL, 10) == uid) {
1026 /*
1027 * This user has not set their username! Set their server
1028 * alias to their display name so that we don't see a bunch
1029 * of numbers in the buddy list.
1030 */
1031 if (display_name != NULL) {
1032 purple_blist_node_set_string(&buddy->node, "DisplayName", display_name);
1033 serv_got_alias(session->gc, username, display_name);
1034 } else {
1035 serv_got_alias(session->gc, username,
1036 purple_blist_node_get_string(&buddy->node, "DisplayName"));
1037 }
1038 }
1039 g_free(display_name);
1040
1041 /* 3. Update buddy information */
1042 user = msim_get_user_from_buddy(buddy);
1043
1044 user->id = uid;
1045 /* Keep track of the user ID across sessions */
1046 purple_blist_node_set_int(&buddy->node, "UserID", uid);
1047
1048 /* Stores a few fields in the MsimUser, relevant to the buddy itself.
1049 * AvatarURL, Headline, ContactID. */
1050 msim_store_user_info(session, contact_info, NULL);
1051
1052 /* TODO: other fields, store in 'user' */
1053 msim_msg_free(contact_info);
1054
1055 g_free(username);
1056 }
1057
1058 /**
1059 * Add first ContactID in contact_info to buddy's list. Used to add
1060 * server-side buddies to client-side list.
1061 *
1062 * @return TRUE if added.
1063 */
1064 static gboolean
1065 msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info)
1066 {
1067 guint uid;
1068 const gchar *username;
1069
1070 uid = msim_msg_get_integer(contact_info, "ContactID");
1071 g_return_val_if_fail(uid != 0, FALSE);
1072
1073 /* Lookup the username, since NickName and IMName is unreliable */
1074 username = msim_uid2username_from_blist(session->account, uid);
1075 if (!username) {
1076 gchar *uid_str;
1077
1078 uid_str = g_strdup_printf("%d", uid);
1079 purple_debug_info("msim_add_contact_from_server",
1080 "contact_info addr=%p\n", contact_info);
1081 msim_lookup_user(session, uid_str, msim_add_contact_from_server_cb, (gpointer)msim_msg_clone(contact_info));
1082 g_free(uid_str);
1083 } else {
1084 msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info));
1085 }
1086
1087 /* Say that the contact was added, even if we're still looking up
1088 * their username. */
1089 return TRUE;
1090 }
1091
1092 /**
1093 * Called when contact list is received from server.
1094 */
1095 static void
1096 msim_got_contact_list(MsimSession *session, const MsimMessage *reply, gpointer user_data)
1097 {
1098 MsimMessage *body, *body_node;
1099 gchar *msg;
1100 guint buddy_count;
1101
1102 body = msim_msg_get_dictionary(reply, "body");
1103 if (!body) {
1104 /* No friends. Not an error. */
1105 return;
1106 }
1107
1108 buddy_count = 0;
1109
1110 for (body_node = body;
1111 body_node != NULL;
1112 body_node = msim_msg_get_next_element_node(body_node))
1113 {
1114 MsimMessageElement *elem;
1115
1116 elem = (MsimMessageElement *)body_node->data;
1117
1118 if (g_str_equal(elem->name, "ContactID"))
1119 {
1120 /* Will look for first contact in body_node */
1121 if (msim_add_contact_from_server(session, body_node)) {
1122 ++buddy_count;
1123 }
1124 }
1125 }
1126
1127 switch (GPOINTER_TO_UINT(user_data)) {
1128 case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS:
1129 msg = g_strdup_printf(ngettext("%d buddy was added or updated from the server (including buddies already on the server-side list)",
1130 "%d buddies were added or updated from the server (including buddies already on the server-side list)",
1131 buddy_count),
1132 buddy_count);
1133 purple_notify_info(session->account, _("Add contacts from server"), msg, NULL);
1134 g_free(msg);
1135 break;
1136
1137 case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS:
1138 /* TODO */
1139 break;
1140
1141 case MSIM_CONTACT_LIST_INITIAL_FRIENDS:
1142 /* Nothing */
1143 break;
1144 }
1145
1146 msim_msg_free(body);
1147 }
1148
1149 /**
1150 * Get contact list, calling msim_got_contact_list() with
1151 * what_to_do_after as user_data gpointer.
1152 *
1153 * @param what_to_do_after should be one of the MSIM_CONTACT_LIST_* #defines.
1154 */
1155 static gboolean
1156 msim_get_contact_list(MsimSession *session, int what_to_do_after)
1157 {
1158 return msim_send(session,
1159 "persist", MSIM_TYPE_INTEGER, 1,
1160 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1161 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
1162 "dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN,
1163 "lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID,
1164 "uid", MSIM_TYPE_INTEGER, session->userid,
1165 "rid", MSIM_TYPE_INTEGER,
1166 msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)),
1167 "body", MSIM_TYPE_STRING, g_strdup(""),
1168 NULL);
1169 }
1170
1171 /** Called after username is set, if necessary and we're open for business. */
1172 gboolean msim_we_are_logged_on(MsimSession *session)
1173 {
1174 MsimMessage *body;
1175
1176 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1177
1178 /* Set display name to username (otherwise will show email address) */
1179 purple_connection_set_display_name(session->gc, session->username);
1180
1181 /* The session is now set up, ready to be connected. This emits the
1182 * signedOn signal, so clients can now do anything with msimprpl, and
1183 * we're ready for it (session key, userid, username all setup). */
1184 purple_connection_update_progress(session->gc, _("Connected"), 3, 4);
1185 purple_connection_set_state(session->gc, PURPLE_CONNECTED);
1186
1187 body = msim_msg_new(
1188 "UserID", MSIM_TYPE_INTEGER, session->userid,
1189 NULL);
1190
1191 /* Request IM info about ourself. */
1192 msim_send(session,
1193 "persist", MSIM_TYPE_INTEGER, 1,
1194 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1195 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
1196 "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN,
1197 "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID,
1198 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
1199 "UserID", MSIM_TYPE_INTEGER, session->userid,
1200 "body", MSIM_TYPE_DICTIONARY, body,
1201 NULL);
1202
1203 /* Request MySpace info about ourself. */
1204 msim_send(session,
1205 "persist", MSIM_TYPE_INTEGER, 1,
1206 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1207 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
1208 "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN,
1209 "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID,
1210 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
1211 "body", MSIM_TYPE_STRING, g_strdup(""),
1212 NULL);
1213
1214 /* TODO: set options (persist cmd=514,dsn=1,lid=10) */
1215 /* TODO: set blocklist */
1216
1217 /* Notify servers of our current status. */
1218 purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n");
1219 msim_set_status(session->account,
1220 purple_account_get_active_status(session->account));
1221
1222 /* TODO: setinfo */
1223 /*
1224 body = msim_msg_new(
1225 "TotalFriends", MSIM_TYPE_INTEGER, 666,
1226 NULL);
1227 msim_send(session,
1228 "setinfo", MSIM_TYPE_BOOLEAN, TRUE,
1229 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1230 "info", MSIM_TYPE_DICTIONARY, body,
1231 NULL);
1232 */
1233
1234 /* Disable due to problems with timeouts. TODO: fix. */
1235 #ifdef MSIM_USE_KEEPALIVE
1236 purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK,
1237 (GSourceFunc)msim_check_alive, session);
453 #endif 1238 #endif
454 1239
455 /* key = sha1(sha1(pw) + nonce2) */ 1240 /* Check mail if they want to. */
456 sha1 = purple_ciphers_find_cipher("sha1"); 1241 if (purple_account_get_check_mail(session->account)) {
457 key_context = purple_cipher_context_new(sha1, NULL); 1242 session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK,
458 purple_cipher_context_append(key_context, hash_pw, HASH_SIZE); 1243 (GSourceFunc)msim_check_inbox, session);
459 purple_cipher_context_append(key_context, (guchar *)(nonce + NONCE_SIZE), NONCE_SIZE); 1244 msim_check_inbox(session);
460 purple_cipher_context_digest(key_context, sizeof(key), key, NULL); 1245 }
461 purple_cipher_context_destroy(key_context); 1246
462 1247 msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS);
463 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE 1248
464 purple_debug_info("msim", "key = "); 1249 return TRUE;
465 for (i = 0; i < sizeof(key); i++) { 1250 }
466 purple_debug_info("msim", "%.2x ", key[i]); 1251
467 } 1252 /**
468 purple_debug_info("msim", "\n"); 1253 * Record the client version in the buddy list, from an incoming message.
469 #endif 1254 */
470
471 rc4 = purple_cipher_context_new_by_name("rc4", NULL);
472
473 /* Note: 'key' variable is 0x14 bytes (from SHA-1 hash),
474 * but only first 0x10 used for the RC4 key. */
475 purple_cipher_context_set_option(rc4, "key_len", (gpointer)0x10);
476 purple_cipher_context_set_key(rc4, key);
477
478 /* TODO: obtain IPs of network interfaces */
479
480 /* rc4 encrypt:
481 * nonce1+email+IP list */
482
483 data_len = NONCE_SIZE + strlen(email) + MSIM_LOGIN_IP_LIST_LEN;
484 data = g_new0(guchar, data_len);
485 memcpy(data, nonce, NONCE_SIZE);
486 memcpy(data + NONCE_SIZE, email, strlen(email));
487 memcpy(data + NONCE_SIZE + strlen(email), MSIM_LOGIN_IP_LIST, MSIM_LOGIN_IP_LIST_LEN);
488
489 data_out = g_new0(guchar, data_len);
490
491 purple_cipher_context_encrypt(rc4, (const guchar *)data,
492 data_len, data_out, &data_out_len);
493 purple_cipher_context_destroy(rc4);
494 g_free(data);
495
496 if (data_out_len != data_len) {
497 purple_debug_info("msim", "msim_compute_login_response: "
498 "data length mismatch: %" G_GSIZE_FORMAT " != %"
499 G_GSIZE_FORMAT "\n", data_out_len, data_len);
500 }
501
502 #ifdef MSIM_DEBUG_LOGIN_CHALLENGE
503 purple_debug_info("msim", "response=<%s>\n", data_out);
504 #endif
505
506 *response_len = data_out_len;
507
508 return (gchar *)data_out;
509 }
510
511 /**
512 * Schedule an IM to be sent once the user ID is looked up.
513 *
514 * @param gc Connection.
515 * @param who A user id, email, or username to send the message to.
516 * @param message Instant message text to send.
517 * @param flags Flags.
518 *
519 * @return 1 if successful or postponed, -1 if failed
520 *
521 * Allows sending to a user by username, email address, or userid. If
522 * a username or email address is given, the userid must be looked up.
523 * This function does that by calling msim_postprocess_outgoing().
524 */
525 int
526 msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message,
527 PurpleMessageFlags flags)
528 {
529 MsimSession *session;
530 gchar *message_msim;
531 int rc;
532
533 g_return_val_if_fail(gc != NULL, -1);
534 g_return_val_if_fail(who != NULL, -1);
535 g_return_val_if_fail(message != NULL, -1);
536
537 /* 'flags' has many options, not used here. */
538
539 session = (MsimSession *)gc->proto_data;
540
541 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
542
543 message_msim = html_to_msim_markup(session, message);
544
545 if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) {
546 /* Return 1 to have Purple show this IM as being sent, 0 to not. I always
547 * return 1 even if the message could not be sent, since I don't know if
548 * it has failed yet--because the IM is only sent after the userid is
549 * retrieved from the server (which happens after this function returns).
550 * If an error does occur, it should be logged to the IM window.
551 */
552 rc = 1;
553 } else {
554 rc = -1;
555 }
556
557 g_free(message_msim);
558
559 return rc;
560 }
561
562 /** Send a buddy message of a given type.
563 *
564 * @param session
565 * @param who Username to send message to.
566 * @param text Message text to send. Not freed; will be copied.
567 * @param type A MSIM_BM_* constant.
568 *
569 * @return TRUE if success, FALSE if fail.
570 *
571 * Buddy messages ('bm') include instant messages, action messages, status messages, etc.
572 *
573 */
574 gboolean
575 msim_send_bm(MsimSession *session, const gchar *who, const gchar *text,
576 int type)
577 {
578 gboolean rc;
579 MsimMessage *msg;
580 const gchar *from_username;
581
582 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
583 g_return_val_if_fail(who != NULL, FALSE);
584 g_return_val_if_fail(text != NULL, FALSE);
585
586 from_username = session->account->username;
587
588 g_return_val_if_fail(from_username != NULL, FALSE);
589
590 purple_debug_info("msim", "sending %d message from %s to %s: %s\n",
591 type, from_username, who, text);
592
593 msg = msim_msg_new(
594 "bm", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(type),
595 "sesskey", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(session->sesskey),
596 /* 't' will be inserted here */
597 "cv", MSIM_TYPE_INTEGER, GUINT_TO_POINTER(MSIM_CLIENT_VERSION),
598 "msg", MSIM_TYPE_STRING, g_strdup(text),
599 NULL);
600
601 rc = msim_postprocess_outgoing(session, msg, who, "t", "cv");
602
603 msim_msg_free(msg);
604
605 return rc;
606 }
607
608
609 /** Record the client version in the buddy list, from an incoming message. */
610 static gboolean 1255 static gboolean
611 msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg) 1256 msim_incoming_bm_record_cv(MsimSession *session, MsimMessage *msg)
612 { 1257 {
613 gchar *username, *cv; 1258 gchar *username, *cv;
614 gboolean ret; 1259 gboolean ret;
637 g_free(cv); 1282 g_free(cv);
638 1283
639 return ret; 1284 return ret;
640 } 1285 }
641 1286
642 /** Handle an incoming buddy message. */ 1287 #ifdef MSIM_SEND_CLIENT_VERSION
1288 /**
1289 * Send our client version to another unofficial client that understands it.
1290 */
1291 static gboolean
1292 msim_send_unofficial_client(MsimSession *session, gchar *username)
1293 {
1294 gchar *our_info;
1295 gboolean ret;
1296
1297 our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s",
1298 PURPLE_MAJOR_VERSION,
1299 PURPLE_MINOR_VERSION,
1300 PURPLE_MICRO_VERSION,
1301 MSIM_PRPL_VERSION_STRING);
1302
1303 ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT);
1304
1305 return ret;
1306 }
1307 #endif
1308
1309 /**
1310 * Process incoming status messages.
1311 *
1312 * @param session
1313 * @param msg Status update message. Caller frees.
1314 *
1315 * @return TRUE if successful.
1316 */
1317 static gboolean
1318 msim_incoming_status(MsimSession *session, MsimMessage *msg)
1319 {
1320 PurpleBuddyList *blist;
1321 MsimUser *user;
1322 GList *list;
1323 gchar *status_headline, *status_headline_escaped;
1324 gint status_code, purple_status_code;
1325 gchar *username;
1326 gchar *unrecognized_msg;
1327
1328 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1329 g_return_val_if_fail(msg != NULL, FALSE);
1330
1331 /* Helpfully looked up by msim_incoming_resolve() for us. */
1332 username = msim_msg_get_string(msg, "_username");
1333 g_return_val_if_fail(username != NULL, FALSE);
1334
1335 {
1336 gchar *ss;
1337
1338 ss = msim_msg_get_string(msg, "msg");
1339 purple_debug_info("msim",
1340 "msim_status: updating status for <%s> to <%s>\n",
1341 username, ss ? ss : "(NULL)");
1342 g_free(ss);
1343 }
1344
1345 /* Example fields:
1346 * |s|0|ss|Offline
1347 * |s|1|ss|:-)|ls||ip|0|p|0
1348 */
1349 list = msim_msg_get_list(msg, "msg");
1350
1351 status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
1352 purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code);
1353 status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE));
1354
1355 blist = purple_get_blist();
1356
1357 /* Add buddy if not found.
1358 * TODO: Could this be responsible for #3444? */
1359 user = msim_find_user(session, username);
1360 if (!user) {
1361 PurpleBuddy *buddy;
1362
1363 purple_debug_info("msim",
1364 "msim_status: making new buddy for %s\n", username);
1365 buddy = purple_buddy_new(session->account, username, NULL);
1366 purple_blist_add_buddy(buddy, NULL, NULL, NULL);
1367
1368 user = msim_get_user_from_buddy(buddy);
1369 user->id = msim_msg_get_integer(msg, "f");
1370
1371 /* Keep track of the user ID across sessions */
1372 purple_blist_node_set_int(&buddy->node, "UserID", user->id);
1373
1374 msim_store_user_info(session, msg, NULL);
1375 } else {
1376 purple_debug_info("msim", "msim_status: found buddy %s\n", username);
1377 }
1378
1379 if (status_headline && strcmp(status_headline, "") != 0) {
1380 /* The status headline is plaintext, but libpurple treats it as HTML,
1381 * so escape any HTML characters to their entity equivalents. */
1382 status_headline_escaped = g_markup_escape_text(status_headline, -1);
1383 } else {
1384 status_headline_escaped = NULL;
1385 }
1386
1387 g_free(status_headline);
1388
1389 /* don't copy; let the MsimUser own the headline, memory-wise */
1390 g_free(user->headline);
1391 user->headline = status_headline_escaped;
1392
1393 /* Set user status */
1394 switch (status_code) {
1395 case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN:
1396 purple_status_code = PURPLE_STATUS_OFFLINE;
1397 break;
1398
1399 case MSIM_STATUS_CODE_ONLINE:
1400 purple_status_code = PURPLE_STATUS_AVAILABLE;
1401 break;
1402
1403 case MSIM_STATUS_CODE_AWAY:
1404 purple_status_code = PURPLE_STATUS_AWAY;
1405 break;
1406
1407 case MSIM_STATUS_CODE_IDLE:
1408 /* Treat idle as an available status. */
1409 purple_status_code = PURPLE_STATUS_AVAILABLE;
1410 break;
1411
1412 default:
1413 purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n",
1414 username, status_code);
1415 purple_status_code = PURPLE_STATUS_AVAILABLE;
1416
1417 unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n",
1418 status_code);
1419 msim_unrecognized(session, NULL, unrecognized_msg);
1420 g_free(unrecognized_msg);
1421 }
1422
1423 purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
1424
1425 if (status_code == MSIM_STATUS_CODE_IDLE) {
1426 purple_debug_info("msim", "msim_status: got idle: %s\n", username);
1427 purple_prpl_got_user_idle(session->account, username, TRUE, 0);
1428 } else {
1429 /* All other statuses indicate going back to non-idle. */
1430 purple_prpl_got_user_idle(session->account, username, FALSE, 0);
1431 }
1432
1433 #ifdef MSIM_SEND_CLIENT_VERSION
1434 if (status_code == MSIM_STATUS_CODE_ONLINE) {
1435 /* Secretly whisper to unofficial clients our own version as they come online */
1436 msim_send_unofficial_client(session, username);
1437 }
1438 #endif
1439
1440 if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) {
1441 /* Get information when they come online.
1442 * TODO: periodically refresh?
1443 */
1444 purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username);
1445 msim_lookup_user(session, username, NULL, NULL);
1446 }
1447
1448 g_free(username);
1449 msim_msg_list_free(list);
1450
1451 return TRUE;
1452 }
1453
1454 /**
1455 * Handle an incoming instant message.
1456 *
1457 * @param session The session
1458 * @param msg Message from the server, containing 'f' (userid from) and 'msg'.
1459 * Should also contain username in _username from preprocessing.
1460 *
1461 * @return TRUE if successful.
1462 */
1463 static gboolean
1464 msim_incoming_im(MsimSession *session, MsimMessage *msg)
1465 {
1466 gchar *username, *msg_msim_markup, *msg_purple_markup;
1467 gchar *userid;
1468 time_t time_received;
1469 PurpleConversation *conv;
1470
1471 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1472 g_return_val_if_fail(msg != NULL, FALSE);
1473
1474 username = msim_msg_get_string(msg, "_username");
1475 /* I know this isn't really a string... but we need it to be one for
1476 * purple_find_conversation_with_account(). */
1477 userid = msim_msg_get_string(msg, "f");
1478 g_return_val_if_fail(username != NULL, FALSE);
1479
1480 purple_debug_info("msim_incoming_im", "UserID is %s", userid);
1481
1482 if (msim_is_userid(username)) {
1483 purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n",
1484 username, purple_account_get_username(session->account));
1485 g_free(username);
1486 return FALSE;
1487 }
1488
1489 /* See if a conversation with their UID already exists...*/
1490 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account);
1491 if (conv) {
1492 /* Since the conversation exists... We need to normalize it */
1493 purple_conversation_set_name(conv, username);
1494 }
1495
1496 msg_msim_markup = msim_msg_get_string(msg, "msg");
1497 g_return_val_if_fail(msg_msim_markup != NULL, FALSE);
1498
1499 msg_purple_markup = msim_markup_to_html(session, msg_msim_markup);
1500 g_free(msg_msim_markup);
1501
1502 time_received = msim_msg_get_integer(msg, "date");
1503 if (!time_received) {
1504 purple_debug_info("msim_incoming_im", "date in message not set.\n");
1505 time_received = time(NULL);
1506 }
1507
1508 serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received);
1509
1510 g_free(username);
1511 g_free(msg_purple_markup);
1512
1513 return TRUE;
1514 }
1515
1516 /**
1517 * Handle an incoming action message.
1518 *
1519 * @param session
1520 * @param msg
1521 *
1522 * @return TRUE if successful.
1523 */
1524 static gboolean
1525 msim_incoming_action(MsimSession *session, MsimMessage *msg)
1526 {
1527 gchar *msg_text, *username;
1528 gboolean rc;
1529
1530 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1531 g_return_val_if_fail(msg != NULL, FALSE);
1532
1533 msg_text = msim_msg_get_string(msg, "msg");
1534 g_return_val_if_fail(msg_text != NULL, FALSE);
1535
1536 username = msim_msg_get_string(msg, "_username");
1537 g_return_val_if_fail(username != NULL, FALSE);
1538
1539 purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n",
1540 msg_text, username);
1541
1542 if (g_str_equal(msg_text, "%typing%")) {
1543 serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
1544 rc = TRUE;
1545 } else if (g_str_equal(msg_text, "%stoptyping%")) {
1546 serv_got_typing_stopped(session->gc, username);
1547 rc = TRUE;
1548 } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) {
1549 rc = msim_incoming_zap(session, msg);
1550 } else if (strstr(msg_text, "!!!GroupCount=")) {
1551 /* TODO: support group chats. I think the number in msg_text has
1552 * something to do with the 'gid' field. */
1553 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
1554
1555 rc = TRUE;
1556 } else if (strstr(msg_text, "!!!Offline=")) {
1557 /* TODO: support group chats. This one might mean a user
1558 * went offline or exited the chat. */
1559 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text);
1560
1561 rc = TRUE;
1562 } else if (msim_msg_get_integer(msg, "aid") != 0) {
1563 purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n",
1564 msim_msg_get_integer(msg, "aid"),
1565 msim_msg_get_integer(msg, "f"),
1566 msg_text);
1567
1568 rc = TRUE;
1569 } else {
1570 msim_unrecognized(session, msg,
1571 "got to msim_incoming_action but unrecognized value for 'msg'");
1572 rc = FALSE;
1573 }
1574
1575 g_free(msg_text);
1576 g_free(username);
1577
1578 return rc;
1579 }
1580
1581 /**
1582 * Process an incoming media (message background?) message.
1583 */
1584 static gboolean
1585 msim_incoming_media(MsimSession *session, MsimMessage *msg)
1586 {
1587 gchar *username, *text;
1588
1589 username = msim_msg_get_string(msg, "_username");
1590 text = msim_msg_get_string(msg, "msg");
1591
1592 g_return_val_if_fail(username != NULL, FALSE);
1593 g_return_val_if_fail(text != NULL, FALSE);
1594
1595 purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text);
1596
1597 /* Media messages are sent when the user opens a window to someone.
1598 * Tell libpurple they started typing and stopped typing, to inform the Psychic
1599 * Mode plugin so it too can open a window to the user. */
1600 serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
1601 serv_got_typing_stopped(session->gc, username);
1602
1603 g_free(username);
1604
1605 return TRUE;
1606 }
1607
1608 /**
1609 * Process an incoming "unofficial client" message. The plugin for
1610 * Miranda IM sends this message with the plugin information.
1611 */
1612 static gboolean
1613 msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg)
1614 {
1615 MsimUser *user;
1616 gchar *username, *client_info;
1617
1618 username = msim_msg_get_string(msg, "_username");
1619 client_info = msim_msg_get_string(msg, "msg");
1620
1621 g_return_val_if_fail(username != NULL, FALSE);
1622 g_return_val_if_fail(client_info != NULL, FALSE);
1623
1624 purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n",
1625 username, client_info);
1626
1627 user = msim_find_user(session, username);
1628
1629 g_return_val_if_fail(user != NULL, FALSE);
1630
1631 if (user->client_info) {
1632 g_free(user->client_info);
1633 }
1634 user->client_info = client_info;
1635
1636 g_free(username);
1637 /* Do not free client_info - the MsimUser now owns it. */
1638
1639 return TRUE;
1640 }
1641
1642 /**
1643 * Handle an incoming buddy message.
1644 */
643 static gboolean 1645 static gboolean
644 msim_incoming_bm(MsimSession *session, MsimMessage *msg) 1646 msim_incoming_bm(MsimSession *session, MsimMessage *msg)
645 { 1647 {
646 guint bm; 1648 guint bm;
647 1649
659 case MSIM_BM_MEDIA: 1661 case MSIM_BM_MEDIA:
660 return msim_incoming_media(session, msg); 1662 return msim_incoming_media(session, msg);
661 case MSIM_BM_UNOFFICIAL_CLIENT: 1663 case MSIM_BM_UNOFFICIAL_CLIENT:
662 return msim_incoming_unofficial_client(session, msg); 1664 return msim_incoming_unofficial_client(session, msg);
663 default: 1665 default:
664 /* Not really an IM, but show it for informational 1666 /* Not really an IM, but show it for informational
665 * purposes during development. */ 1667 * purposes during development. */
666 return msim_incoming_im(session, msg); 1668 return msim_incoming_im(session, msg);
667 } 1669 }
668 } 1670 }
669 1671
670 /** 1672 /**
671 * Handle an incoming instant message. 1673 * Process the initial server information from the server.
672 * 1674 */
673 * @param session The session 1675 static gboolean
674 * @param msg Message from the server, containing 'f' (userid from) and 'msg'. 1676 msim_process_server_info(MsimSession *session, MsimMessage *msg)
675 * Should also contain username in _username from preprocessing. 1677 {
1678 MsimMessage *body;
1679
1680 body = msim_msg_get_dictionary(msg, "body");
1681 g_return_val_if_fail(body != NULL, FALSE);
1682
1683 /* Example body:
1684 AdUnitRefreshInterval=10.
1685 AlertPollInterval=360.
1686 AllowChatRoomEmoticonSharing=False.
1687 ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391.
1688 CurClientVersion=673.
1689 EnableIMBrowse=True.
1690 EnableIMStuffAvatars=False.
1691 EnableIMStuffZaps=False.
1692 MaxAddAllFriends=100.
1693 MaxContacts=1000.
1694 MinClientVersion=594.
1695 MySpaceIM_ENGLISH=78744676.
1696 MySpaceNowTimer=720.
1697 PersistenceDataTimeout=900.
1698 UseWebChallenge=1.
1699 WebTicketGoHome=False
1700
1701 Anything useful? TODO: use what is useful, and use it.
1702 */
1703 purple_debug_info("msim_process_server_info",
1704 "maximum contacts: %d\n",
1705 msim_msg_get_integer(body, "MaxContacts"));
1706
1707 session->server_info = body;
1708 /* session->server_info freed in msim_session_destroy */
1709
1710 return TRUE;
1711 }
1712
1713 /**
1714 * Process a web challenge, used to login to the web site.
1715 */
1716 static gboolean
1717 msim_web_challenge(MsimSession *session, MsimMessage *msg)
1718 {
1719 /* TODO: web challenge, store token. #2659. */
1720 return FALSE;
1721 }
1722
1723 /**
1724 * Process a persistance message reply from the server.
1725 *
1726 * @param session
1727 * @param msg Message reply from server.
676 * 1728 *
677 * @return TRUE if successful. 1729 * @return TRUE if successful.
678 */ 1730 *
679 static gboolean 1731 * msim_lookup_user sets callback for here
680 msim_incoming_im(MsimSession *session, MsimMessage *msg) 1732 */
681 { 1733 static gboolean
682 gchar *username, *msg_msim_markup, *msg_purple_markup; 1734 msim_process_reply(MsimSession *session, MsimMessage *msg)
683 gchar *userid; 1735 {
684 time_t time_received; 1736 MSIM_USER_LOOKUP_CB cb;
685 PurpleConversation *conv; 1737 gpointer data;
1738 guint rid, cmd, dsn, lid;
686 1739
687 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); 1740 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
688 g_return_val_if_fail(msg != NULL, FALSE); 1741 g_return_val_if_fail(msg != NULL, FALSE);
689 1742
690 username = msim_msg_get_string(msg, "_username"); 1743 msim_store_user_info(session, msg, NULL);
691 /* I know this isn't really a string... but we need it to be one for 1744
692 * purple_find_conversation_with_account(). */ 1745 rid = msim_msg_get_integer(msg, "rid");
693 userid = msim_msg_get_string(msg, "f"); 1746 cmd = msim_msg_get_integer(msg, "cmd");
694 g_return_val_if_fail(username != NULL, FALSE); 1747 dsn = msim_msg_get_integer(msg, "dsn");
695 1748 lid = msim_msg_get_integer(msg, "lid");
696 purple_debug_info("msim_incoming_im", "UserID is %s", userid); 1749
697 1750 /* Unsolicited messages */
698 if (msim_is_userid(username)) { 1751 if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) {
699 purple_debug_info("msim", "Ignoring message from spambot (%s) on account %s\n", 1752 if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) {
700 username, purple_account_get_username(session->account)); 1753 return msim_process_server_info(session, msg);
701 g_free(username); 1754 } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) {
702 return FALSE; 1755 return msim_web_challenge(session, msg);
703 } 1756 }
704 1757 }
705 /* See if a conversation with their UID already exists...*/ 1758
706 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, userid, session->account); 1759 /* If a callback is registered for this userid lookup, call it. */
707 if (conv) { 1760 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid));
708 /* Since the conversation exists... We need to normalize it */ 1761 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
709 purple_conversation_set_name(conv, username); 1762
710 } 1763 if (cb) {
711 1764 purple_debug_info("msim", "msim_process_reply: calling callback now\n");
712 msg_msim_markup = msim_msg_get_string(msg, "msg"); 1765 /* Clone message, so that the callback 'cb' can use it (needs to free it also). */
713 g_return_val_if_fail(msg_msim_markup != NULL, FALSE); 1766 cb(session, msg, data);
714 1767 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid));
715 msg_purple_markup = msim_markup_to_html(session, msg_msim_markup); 1768 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
716 g_free(msg_msim_markup); 1769 } else {
717 1770 purple_debug_info("msim",
718 time_received = msim_msg_get_integer(msg, "date"); 1771 "msim_process_reply: no callback for rid %d\n", rid);
719 if (!time_received) { 1772 }
720 purple_debug_info("msim_incoming_im", "date in message not set.\n");
721 time_received = time(NULL);
722 }
723
724 serv_got_im(session->gc, username, msg_purple_markup, PURPLE_MESSAGE_RECV, time_received);
725
726 g_free(username);
727 g_free(msg_purple_markup);
728 1773
729 return TRUE; 1774 return TRUE;
730 } 1775 }
731 1776
732 /** 1777 /**
733 * Process unrecognized information. 1778 * Handle an error from the server.
734 * 1779 *
735 * @param session 1780 * @param session
736 * @param msg An MsimMessage that was unrecognized, or NULL. 1781 * @param msg The message.
737 * @param note Information on what was unrecognized, or NULL. 1782 *
738 */ 1783 * @return TRUE if successfully reported error.
739 void 1784 */
740 msim_unrecognized(MsimSession *session, MsimMessage *msg, gchar *note) 1785 static gboolean
741 { 1786 msim_error(MsimSession *session, MsimMessage *msg)
742 /* TODO: Some more context, outwardly equivalent to a backtrace, 1787 {
743 * for helping figure out what this msg is for. What was going on? 1788 gchar *errmsg, *full_errmsg;
744 * But not too much information so that a user 1789 guint err;
745 * posting this dump reveals confidential information.
746 */
747
748 /* TODO: dump unknown msgs to file, so user can send them to me
749 * if they wish, to help add support for new messages (inspired
750 * by Alexandr Shutko, who maintains OSCAR protocol documentation).
751 *
752 * Filed enhancement ticket for libpurple as #4688.
753 */
754
755 purple_debug_info("msim", "Unrecognized data on account for %s\n",
756 (session && session->account && session->account->username) ?
757 session->account->username : "(NULL)");
758 if (note) {
759 purple_debug_info("msim", "(Note: %s)\n", note);
760 }
761
762 if (msg) {
763 msim_msg_dump("Unrecognized message dump: %s\n", msg);
764 }
765 }
766
767 /**
768 * Handle an incoming action message.
769 *
770 * @param session
771 * @param msg
772 *
773 * @return TRUE if successful.
774 *
775 */
776 static gboolean
777 msim_incoming_action(MsimSession *session, MsimMessage *msg)
778 {
779 gchar *msg_text, *username;
780 gboolean rc;
781 1790
782 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); 1791 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
783 g_return_val_if_fail(msg != NULL, FALSE); 1792 g_return_val_if_fail(msg != NULL, FALSE);
784 1793
785 msg_text = msim_msg_get_string(msg, "msg"); 1794 err = msim_msg_get_integer(msg, "err");
786 g_return_val_if_fail(msg_text != NULL, FALSE); 1795 errmsg = msim_msg_get_string(msg, "errmsg");
787 1796
788 username = msim_msg_get_string(msg, "_username"); 1797 full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err,
789 g_return_val_if_fail(username != NULL, FALSE); 1798 errmsg ? errmsg : "no 'errmsg' given");
790 1799
791 purple_debug_info("msim", "msim_incoming_action: action <%s> from <%s>\n", 1800 g_free(errmsg);
792 msg_text, username); 1801
793 1802 purple_debug_info("msim", "msim_error (sesskey=%d): %s\n",
794 if (g_str_equal(msg_text, "%typing%")) { 1803 session->sesskey, full_errmsg);
795 serv_got_typing(session->gc, username, 0, PURPLE_TYPING); 1804
796 rc = TRUE; 1805 /* Destroy session if fatal. */
797 } else if (g_str_equal(msg_text, "%stoptyping%")) { 1806 if (msim_msg_get(msg, "fatal")) {
798 serv_got_typing_stopped(session->gc, username); 1807 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
799 rc = TRUE; 1808 purple_debug_info("msim", "fatal error, closing\n");
800 } else if (strstr(msg_text, "!!!ZAP_SEND!!!=RTE_BTN_ZAPS_")) { 1809
801 rc = msim_incoming_zap(session, msg); 1810 switch (err) {
802 } else if (strstr(msg_text, "!!!GroupCount=")) { 1811 case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */
803 /* TODO: support group chats. I think the number in msg_text has 1812 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
804 * something to do with the 'gid' field. */ 1813 if (!purple_account_get_remember_password(session->account))
805 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); 1814 purple_account_set_password(session->account, NULL);
806 1815 #ifdef MSIM_MAX_PASSWORD_LENGTH
807 rc = TRUE; 1816 if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) {
808 } else if (strstr(msg_text, "!!!Offline=")) { 1817 gchar *suggestion;
809 /* TODO: support group chats. This one might mean a user 1818
810 * went offline or exited the chat. */ 1819 suggestion = g_strdup_printf(_("%s Your password is "
811 purple_debug_info("msim", "msim_incoming_action: TODO: implement #4691, group chats: %s\n", msg_text); 1820 "%d characters, greater than the "
812 1821 "expected maximum length of %d for "
813 rc = TRUE; 1822 "MySpaceIM. Please shorten your "
814 } else if (msim_msg_get_integer(msg, "aid") != 0) { 1823 "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."),
815 purple_debug_info("msim", "TODO: implement #4691, group chat from %d on %d: %s\n", 1824 full_errmsg, (int)
816 msim_msg_get_integer(msg, "aid"), 1825 strlen(session->account->password),
817 msim_msg_get_integer(msg, "f"), 1826 MSIM_MAX_PASSWORD_LENGTH);
818 msg_text); 1827
819 1828 /* Replace full_errmsg. */
820 rc = TRUE; 1829 g_free(full_errmsg);
1830 full_errmsg = suggestion;
1831 }
1832 #endif
1833 break;
1834 case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */
1835 reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
1836 if (!purple_account_get_remember_password(session->account))
1837 purple_account_set_password(session->account, NULL);
1838 break;
1839 }
1840 purple_connection_error_reason (session->gc, reason, full_errmsg);
821 } else { 1841 } else {
822 msim_unrecognized(session, msg, 1842 purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL);
823 "got to msim_incoming_action but unrecognized value for 'msg'"); 1843 }
824 rc = FALSE; 1844
825 } 1845 g_free(full_errmsg);
826
827 g_free(msg_text);
828 g_free(username);
829
830 return rc;
831 }
832
833 /* Process an incoming media (message background?) message. */
834 static gboolean
835 msim_incoming_media(MsimSession *session, MsimMessage *msg)
836 {
837 gchar *username, *text;
838
839 username = msim_msg_get_string(msg, "_username");
840 text = msim_msg_get_string(msg, "msg");
841
842 g_return_val_if_fail(username != NULL, FALSE);
843 g_return_val_if_fail(text != NULL, FALSE);
844
845 purple_debug_info("msim", "msim_incoming_media: from %s, got msg=%s\n", username, text);
846
847 /* Media messages are sent when the user opens a window to someone.
848 * Tell libpurple they started typing and stopped typing, to inform the Psychic
849 * Mode plugin so it too can open a window to the user. */
850 serv_got_typing(session->gc, username, 0, PURPLE_TYPING);
851 serv_got_typing_stopped(session->gc, username);
852
853 g_free(username);
854 1846
855 return TRUE; 1847 return TRUE;
856 } 1848 }
857 1849
858 /* Process an incoming "unofficial client" message. The plugin for 1850 /**
859 * Miranda IM sends this message with the plugin information. */ 1851 * Process a message.
860 static gboolean
861 msim_incoming_unofficial_client(MsimSession *session, MsimMessage *msg)
862 {
863 MsimUser *user;
864 gchar *username, *client_info;
865
866 username = msim_msg_get_string(msg, "_username");
867 client_info = msim_msg_get_string(msg, "msg");
868
869 g_return_val_if_fail(username != NULL, FALSE);
870 g_return_val_if_fail(client_info != NULL, FALSE);
871
872 purple_debug_info("msim", "msim_incoming_unofficial_client: %s is using client %s\n",
873 username, client_info);
874
875 user = msim_find_user(session, username);
876
877 g_return_val_if_fail(user != NULL, FALSE);
878
879 if (user->client_info) {
880 g_free(user->client_info);
881 }
882 user->client_info = client_info;
883
884 g_free(username);
885 /* Do not free client_info - the MsimUser now owns it. */
886
887 return TRUE;
888 }
889
890
891 #ifdef MSIM_SEND_CLIENT_VERSION
892 /** Send our client version to another unofficial client that understands it. */
893 static gboolean
894 msim_send_unofficial_client(MsimSession *session, gchar *username)
895 {
896 gchar *our_info;
897 gboolean ret;
898
899 our_info = g_strdup_printf("Libpurple %d.%d.%d - msimprpl %s",
900 PURPLE_MAJOR_VERSION,
901 PURPLE_MINOR_VERSION,
902 PURPLE_MICRO_VERSION,
903 MSIM_PRPL_VERSION_STRING);
904
905 ret = msim_send_bm(session, username, our_info, MSIM_BM_UNOFFICIAL_CLIENT);
906
907 return ret;
908 }
909 #endif
910
911 /**
912 * Handle when our user starts or stops typing to another user.
913 *
914 * @param gc
915 * @param name The buddy name to which our user is typing to
916 * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING
917 *
918 * @return 0
919 */
920 unsigned int
921 msim_send_typing(PurpleConnection *gc, const gchar *name,
922 PurpleTypingState state)
923 {
924 const gchar *typing_str;
925 MsimSession *session;
926
927 g_return_val_if_fail(gc != NULL, 0);
928 g_return_val_if_fail(name != NULL, 0);
929
930 session = (MsimSession *)gc->proto_data;
931
932 g_return_val_if_fail(MSIM_SESSION_VALID(session), 0);
933
934 switch (state) {
935 case PURPLE_TYPING:
936 typing_str = "%typing%";
937 break;
938
939 case PURPLE_TYPED:
940 case PURPLE_NOT_TYPING:
941 default:
942 typing_str = "%stoptyping%";
943 break;
944 }
945
946 purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
947 msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
948 return 0;
949 }
950
951
952
953 /** Callback for msim_get_info(), for when user info is received. */
954 static void
955 msim_get_info_cb(MsimSession *session, MsimMessage *user_info_msg,
956 gpointer data)
957 {
958 MsimMessage *msg;
959 gchar *username;
960 PurpleNotifyUserInfo *user_info;
961 MsimUser *user;
962
963 g_return_if_fail(MSIM_SESSION_VALID(session));
964
965 /* Get user{name,id} from msim_get_info, passed as an MsimMessage for
966 orthogonality. */
967 msg = (MsimMessage *)data;
968 g_return_if_fail(msg != NULL);
969
970 username = msim_msg_get_string(msg, "user");
971 if (!username) {
972 purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n");
973 return;
974 }
975
976 msim_msg_free(msg);
977 purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username);
978
979 user = msim_find_user(session, username);
980
981 if (!user) {
982 /* User isn't on blist, create a temporary user to store info. */
983 user = g_new0(MsimUser, 1);
984 user->temporary_user = TRUE;
985 }
986
987 /* Update user structure with new information */
988 msim_store_user_info(session, user_info_msg, user);
989
990 user_info = purple_notify_user_info_new();
991
992 /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */
993 msim_append_user_info(session, user_info, user, TRUE);
994
995 purple_notify_userinfo(session->gc, username, user_info, NULL, NULL);
996 purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username);
997
998 purple_notify_user_info_destroy(user_info);
999
1000 if (user->temporary_user) {
1001 g_free(user->client_info);
1002 g_free(user->gender);
1003 g_free(user->location);
1004 g_free(user->headline);
1005 g_free(user->display_name);
1006 g_free(user->username);
1007 g_free(user->image_url);
1008 g_free(user);
1009 }
1010 g_free(username);
1011 }
1012
1013 /** Retrieve a user's profile.
1014 * @param username Username, user ID, or email address to lookup.
1015 */
1016 void
1017 msim_get_info(PurpleConnection *gc, const gchar *username)
1018 {
1019 MsimSession *session;
1020 MsimUser *user;
1021 gchar *user_to_lookup;
1022 MsimMessage *user_msg;
1023
1024 g_return_if_fail(gc != NULL);
1025 g_return_if_fail(username != NULL);
1026
1027 session = (MsimSession *)gc->proto_data;
1028
1029 g_return_if_fail(MSIM_SESSION_VALID(session));
1030
1031 /* Obtain uid of buddy. */
1032 user = msim_find_user(session, username);
1033
1034 /* If is on buddy list, lookup by uid since it is faster. */
1035 if (user && user->id) {
1036 user_to_lookup = g_strdup_printf("%d", user->id);
1037 } else {
1038 /* Looking up buddy not on blist. Lookup by whatever user entered. */
1039 user_to_lookup = g_strdup(username);
1040 }
1041
1042 /* Pass the username to msim_get_info_cb(), because since we lookup
1043 * by userid, the userinfo message will only contain the uid (not
1044 * the username) but it would be useful to display the username too.
1045 */
1046 user_msg = msim_msg_new(
1047 "user", MSIM_TYPE_STRING, g_strdup(username),
1048 NULL);
1049 purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username);
1050
1051 msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg);
1052
1053 g_free(user_to_lookup);
1054 }
1055
1056 /** Set your status - callback for when user manually sets it. */
1057 void
1058 msim_set_status(PurpleAccount *account, PurpleStatus *status)
1059 {
1060 PurpleStatusType *type;
1061 PurplePresence *pres;
1062 MsimSession *session;
1063 guint status_code;
1064 const gchar *message;
1065 gchar *stripped;
1066 gchar *unrecognized_msg;
1067
1068 session = (MsimSession *)account->gc->proto_data;
1069
1070 g_return_if_fail(MSIM_SESSION_VALID(session));
1071
1072 type = purple_status_get_type(status);
1073 pres = purple_status_get_presence(status);
1074
1075 switch (purple_status_type_get_primitive(type)) {
1076 case PURPLE_STATUS_AVAILABLE:
1077 purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE,
1078 MSIM_STATUS_CODE_ONLINE);
1079 status_code = MSIM_STATUS_CODE_ONLINE;
1080 break;
1081
1082 case PURPLE_STATUS_INVISIBLE:
1083 purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE,
1084 MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN);
1085 status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN;
1086 break;
1087
1088 case PURPLE_STATUS_AWAY:
1089 purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY,
1090 MSIM_STATUS_CODE_AWAY);
1091 status_code = MSIM_STATUS_CODE_AWAY;
1092 break;
1093
1094 default:
1095 purple_debug_info("msim", "msim_set_status: unknown "
1096 "status interpreting as online");
1097 status_code = MSIM_STATUS_CODE_ONLINE;
1098
1099 unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n",
1100 purple_status_type_get_primitive(type));
1101 msim_unrecognized(session, NULL, unrecognized_msg);
1102 g_free(unrecognized_msg);
1103
1104 break;
1105 }
1106
1107 message = purple_status_get_attr_string(status, "message");
1108
1109 /* Status strings are plain text. */
1110 if (message != NULL)
1111 stripped = purple_markup_strip_html(message);
1112 else
1113 stripped = g_strdup("");
1114
1115 msim_set_status_code(session, status_code, stripped);
1116
1117 /* If we should be idle, set that status. Time is irrelevant here. */
1118 if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN)
1119 msim_set_idle(account->gc, 1);
1120
1121 }
1122
1123 /** Go idle. */
1124 void
1125 msim_set_idle(PurpleConnection *gc, int time)
1126 {
1127 MsimSession *session;
1128 PurpleStatus *status;
1129
1130 g_return_if_fail(gc != NULL);
1131
1132 session = (MsimSession *)gc->proto_data;
1133
1134 g_return_if_fail(MSIM_SESSION_VALID(session));
1135
1136 status = purple_account_get_active_status(session->account);
1137
1138 if (time == 0) {
1139 /* Going back from idle. In msim, idle is mutually exclusive
1140 * from the other states (you can only be away or idle, but not
1141 * both, for example), so by going non-idle I go back to what
1142 * libpurple says I should be.
1143 */
1144 msim_set_status(session->account, status);
1145 } else {
1146 const gchar *message;
1147 gchar *stripped;
1148
1149 /* Set the idle message to the status message from the real
1150 * current status.
1151 */
1152 message = purple_status_get_attr_string(status, "message");
1153 if (message != NULL)
1154 stripped = purple_markup_strip_html(message);
1155 else
1156 stripped = g_strdup("");
1157
1158 /* msim doesn't support idle time, so just go idle */
1159 msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped);
1160 }
1161 }
1162
1163 /** Set status using an MSIM_STATUS_CODE_* value.
1164 * @param status_code An MSIM_STATUS_CODE_* value.
1165 * @param statstring Status string, must be a dynamic string (will be freed by msim_send).
1166 */
1167 static void
1168 msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring)
1169 {
1170 g_return_if_fail(MSIM_SESSION_VALID(session));
1171 g_return_if_fail(statstring != NULL);
1172
1173 purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n",
1174 status_code, statstring);
1175
1176 if (!msim_send(session,
1177 "status", MSIM_TYPE_INTEGER, status_code,
1178 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1179 "statstring", MSIM_TYPE_STRING, statstring,
1180 "locstring", MSIM_TYPE_STRING, g_strdup(""),
1181 NULL))
1182 {
1183 purple_debug_info("msim", "msim_set_status: failed to set status\n");
1184 }
1185
1186 }
1187
1188 /** After a uid is resolved to username, tag it with the username and submit for processing.
1189 *
1190 * @param session
1191 * @param userinfo Response messsage to resolving request.
1192 * @param data MsimMessage *, the message to attach information to.
1193 */
1194 static void
1195 msim_incoming_resolved(MsimSession *session, MsimMessage *userinfo,
1196 gpointer data)
1197 {
1198 gchar *username;
1199 MsimMessage *msg, *body;
1200
1201 g_return_if_fail(MSIM_SESSION_VALID(session));
1202 g_return_if_fail(userinfo != NULL);
1203
1204 body = msim_msg_get_dictionary(userinfo, "body");
1205 g_return_if_fail(body != NULL);
1206
1207 username = msim_msg_get_string(body, "UserName");
1208 g_return_if_fail(username != NULL);
1209 /* Note: username will be owned by 'msg' below. */
1210
1211 msg = (MsimMessage *)data;
1212 g_return_if_fail(msg != NULL);
1213
1214 /* TODO: more elegant solution than below. attach whole message? */
1215 /* Special elements name beginning with '_', we'll use internally within the
1216 * program (did not come directly from the wire). */
1217 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */
1218
1219 /* TODO: attach more useful information, like ImageURL */
1220
1221 msim_process(session, msg);
1222
1223 /* TODO: Free copy cloned from msim_preprocess_incoming(). */
1224 /* msim_msg_free(msg); */
1225 msim_msg_free(body);
1226 }
1227
1228 /* Lookup a username by userid, from buddy list.
1229 *
1230 * @param wanted_uid
1231 *
1232 * @return Username of wanted_uid, if on blist, or NULL.
1233 * This is a static string, so don't free it. Copy it if needed.
1234 *
1235 */
1236 static const gchar *
1237 msim_uid2username_from_blist(PurpleAccount *account, guint wanted_uid)
1238 {
1239 GSList *buddies, *cur;
1240 const gchar *ret;
1241
1242 buddies = purple_find_buddies(account, NULL);
1243
1244 if (!buddies)
1245 {
1246 purple_debug_info("msim", "msim_uid2username_from_blist: no buddies?\n");
1247 return NULL;
1248 }
1249
1250 ret = NULL;
1251
1252 for (cur = buddies; cur != NULL; cur = g_slist_next(cur))
1253 {
1254 PurpleBuddy *buddy;
1255 guint uid;
1256 const gchar *name;
1257
1258 /* See finch/gnthistory.c */
1259 buddy = cur->data;
1260
1261 uid = purple_blist_node_get_int(&buddy->node, "UserID");
1262 name = purple_buddy_get_name(buddy);
1263
1264 if (uid == wanted_uid)
1265 {
1266 ret = name;
1267 break;
1268 }
1269 }
1270
1271 g_slist_free(buddies);
1272 return ret;
1273 }
1274
1275 /** Preprocess incoming messages, resolving as needed, calling msim_process() when ready to process.
1276 *
1277 * @param session
1278 * @param msg MsimMessage *, freed by caller.
1279 */
1280 static gboolean
1281 msim_preprocess_incoming(MsimSession *session, MsimMessage *msg)
1282 {
1283 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1284 g_return_val_if_fail(msg != NULL, FALSE);
1285
1286 if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) {
1287 guint uid;
1288 const gchar *username;
1289
1290 /* 'f' = userid message is from, in buddy messages */
1291 uid = msim_msg_get_integer(msg, "f");
1292
1293 username = msim_uid2username_from_blist(session->account, uid);
1294
1295 if (username) {
1296 /* Know username already, use it. */
1297 purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n",
1298 username);
1299 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
1300 return msim_process(session, msg);
1301
1302 } else {
1303 gchar *from;
1304
1305 /* Send lookup request. */
1306 /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */
1307 purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n");
1308 from = msim_msg_get_string(msg, "f");
1309 msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg));
1310 g_free(from);
1311
1312 /* indeterminate */
1313 return TRUE;
1314 }
1315 } else {
1316 /* Nothing to resolve - send directly to processing. */
1317 return msim_process(session, msg);
1318 }
1319 }
1320
1321 #ifdef MSIM_USE_KEEPALIVE
1322 /** Check if the connection is still alive, based on last communication. */
1323 static gboolean
1324 msim_check_alive(gpointer data)
1325 {
1326 MsimSession *session;
1327 time_t delta;
1328
1329 session = (MsimSession *)data;
1330
1331 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1332
1333 delta = time(NULL) - session->last_comm;
1334
1335 /* purple_debug_info("msim", "msim_check_alive: delta=%d\n", delta); */
1336 if (delta >= MSIM_KEEPALIVE_INTERVAL) {
1337 purple_debug_info("msim",
1338 "msim_check_alive: %zu > interval of %d, presumed dead\n",
1339 delta, MSIM_KEEPALIVE_INTERVAL);
1340 purple_connection_error_reason(session->gc,
1341 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
1342 _("Lost connection with server"));
1343
1344 return FALSE;
1345 }
1346
1347 return TRUE;
1348 }
1349 #endif
1350
1351 /** Handle mail reply checks. */
1352 static void
1353 msim_check_inbox_cb(MsimSession *session, MsimMessage *reply, gpointer data)
1354 {
1355 MsimMessage *body;
1356 guint old_inbox_status;
1357 guint i, n;
1358 const gchar *froms[5], *tos[5], *urls[5], *subjects[5];
1359
1360 /* Information for each new inbox message type. */
1361 static struct
1362 {
1363 const gchar *key;
1364 guint bit;
1365 const gchar *url;
1366 const gchar *text;
1367 } message_types[] = {
1368 { "Mail", MSIM_INBOX_MAIL, "http://messaging.myspace.com/index.cfm?fuseaction=mail.inbox", NULL },
1369 { "BlogComment", MSIM_INBOX_BLOG_COMMENT, "http://blog.myspace.com/index.cfm?fuseaction=blog", NULL },
1370 { "ProfileComment", MSIM_INBOX_PROFILE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL },
1371 { "FriendRequest", MSIM_INBOX_FRIEND_REQUEST, "http://messaging.myspace.com/index.cfm?fuseaction=mail.friendRequests", NULL },
1372 { "PictureComment", MSIM_INBOX_PICTURE_COMMENT, "http://home.myspace.com/index.cfm?fuseaction=user", NULL }
1373 };
1374
1375 /* Can't write _()'d strings in array initializers. Workaround. */
1376 message_types[0].text = _("New mail messages");
1377 message_types[1].text = _("New blog comments");
1378 message_types[2].text = _("New profile comments");
1379 message_types[3].text = _("New friend requests!");
1380 message_types[4].text = _("New picture comments");
1381
1382 g_return_if_fail(reply != NULL);
1383
1384 msim_msg_dump("msim_check_inbox_cb: reply=%s\n", reply);
1385
1386 body = msim_msg_get_dictionary(reply, "body");
1387
1388 if (body == NULL)
1389 return;
1390
1391 old_inbox_status = session->inbox_status;
1392
1393 n = 0;
1394
1395 for (i = 0; i < sizeof(message_types) / sizeof(message_types[0]); ++i) {
1396 const gchar *key;
1397 guint bit;
1398
1399 key = message_types[i].key;
1400 bit = message_types[i].bit;
1401
1402 if (msim_msg_get(body, key)) {
1403 /* Notify only on when _changes_ from no mail -> has mail
1404 * (edge triggered) */
1405 if (!(session->inbox_status & bit)) {
1406 purple_debug_info("msim", "msim_check_inbox_cb: got %s, at %d\n",
1407 key ? key : "(NULL)", n);
1408
1409 subjects[n] = message_types[i].text;
1410 froms[n] = _("MySpace");
1411 tos[n] = session->username;
1412 /* TODO: append token, web challenge, so automatically logs in.
1413 * Would also need to free strings because they won't be static
1414 */
1415 urls[n] = message_types[i].url;
1416
1417 ++n;
1418 } else {
1419 purple_debug_info("msim",
1420 "msim_check_inbox_cb: already notified of %s\n",
1421 key ? key : "(NULL)");
1422 }
1423
1424 session->inbox_status |= bit;
1425 }
1426 }
1427
1428 if (n) {
1429 purple_debug_info("msim",
1430 "msim_check_inbox_cb: notifying of %d\n", n);
1431
1432 /* TODO: free strings with callback _if_ change to dynamic (w/ token) */
1433 purple_notify_emails(session->gc, /* handle */
1434 n, /* count */
1435 TRUE, /* detailed */
1436 subjects, froms, tos, urls,
1437 NULL, /* PurpleNotifyCloseCallback cb */
1438 NULL); /* gpointer user_data */
1439
1440 }
1441
1442 msim_msg_free(body);
1443 }
1444
1445 /* Send request to check if there is new mail. */
1446 static gboolean
1447 msim_check_inbox(gpointer data)
1448 {
1449 MsimSession *session;
1450
1451 session = (MsimSession *)data;
1452
1453 if (!MSIM_SESSION_VALID(session)) {
1454 purple_debug_info("msim", "msim_check_inbox: session invalid, stopping the mail check.\n");
1455 return FALSE;
1456 }
1457
1458 purple_debug_info("msim", "msim_check_inbox: checking mail\n");
1459 g_return_val_if_fail(msim_send(session,
1460 "persist", MSIM_TYPE_INTEGER, 1,
1461 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1462 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET,
1463 "dsn", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_DSN,
1464 "lid", MSIM_TYPE_INTEGER, MG_CHECK_MAIL_LID,
1465 "uid", MSIM_TYPE_INTEGER, session->userid,
1466 "rid", MSIM_TYPE_INTEGER,
1467 msim_new_reply_callback(session, msim_check_inbox_cb, NULL),
1468 "body", MSIM_TYPE_STRING, g_strdup(""),
1469 NULL), TRUE);
1470
1471 /* Always return true, so that we keep checking for mail. */
1472 return TRUE;
1473 }
1474
1475 #ifdef MSIM_CHECK_NEWER_VERSION
1476 /** Callback for when a currentversion.txt has been downloaded. */
1477 static void
1478 msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data,
1479 gpointer user_data,
1480 const gchar *url_text,
1481 gsize len,
1482 const gchar *error_message)
1483 {
1484 GKeyFile *keyfile;
1485 GError *error;
1486 GString *data;
1487 gchar *newest_filever;
1488
1489 if (!url_text) {
1490 purple_debug_info("msim_check_newer_version_cb",
1491 "got error: %s\n", error_message);
1492 return;
1493 }
1494
1495 purple_debug_info("msim_check_newer_version_cb",
1496 "url_text=%s\n", url_text ? url_text : "(NULL)");
1497
1498 /* Prepend [group] so that GKeyFile can parse it (requires a group). */
1499 data = g_string_new(url_text);
1500 purple_debug_info("msim", "data=%s\n", data->str
1501 ? data->str : "(NULL)");
1502 data = g_string_prepend(data, "[group]\n");
1503
1504 purple_debug_info("msim", "data=%s\n", data->str
1505 ? data->str : "(NULL)");
1506
1507 /* url_text is variable=data\n...†*/
1508
1509 /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */
1510 /* New (english) version can be downloaded from SETUPURL+SETUPFILE */
1511
1512 error = NULL;
1513 keyfile = g_key_file_new();
1514
1515 /* Default list seperator is ;, but currentversion.txt doesn't have
1516 * these, so set to an unused character to avoid parsing problems. */
1517 g_key_file_set_list_separator(keyfile, '\0');
1518
1519 g_key_file_load_from_data(keyfile, data->str, data->len,
1520 G_KEY_FILE_NONE, &error);
1521 g_string_free(data, TRUE);
1522
1523 if (error != NULL) {
1524 purple_debug_info("msim_check_newer_version_cb",
1525 "couldn't parse, error: %d %d %s\n",
1526 error->domain, error->code, error->message);
1527 g_error_free(error);
1528 return;
1529 }
1530
1531 gchar **ks;
1532 guint n;
1533 ks = g_key_file_get_keys(keyfile, "group", &n, NULL);
1534 purple_debug_info("msim", "n=%d\n", n);
1535 guint i;
1536 for (i = 0; ks[i] != NULL; ++i)
1537 {
1538 purple_debug_info("msim", "%d=%s\n", i, ks[i]);
1539 }
1540
1541 newest_filever = g_key_file_get_string(keyfile, "group",
1542 "FILEVER", &error);
1543
1544 purple_debug_info("msim_check_newer_version_cb",
1545 "newest filever: %s\n", newest_filever ?
1546 newest_filever : "(NULL)");
1547 if (error != NULL) {
1548 purple_debug_info("msim_check_newer_version_cb",
1549 "error: %d %d %s\n",
1550 error->domain, error->code, error->message);
1551 g_error_free(error);
1552 }
1553
1554 g_key_file_free(keyfile);
1555
1556 exit(0);
1557 }
1558 #endif
1559
1560 /** Called when the session key arrives to check whether the user
1561 * has a username, and set one if desired. */
1562 static gboolean
1563 msim_is_username_set(MsimSession *session, MsimMessage *msg)
1564 {
1565 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1566 g_return_val_if_fail(msg != NULL, FALSE);
1567 g_return_val_if_fail(session->gc != NULL, FALSE);
1568
1569 session->sesskey = msim_msg_get_integer(msg, "sesskey");
1570 purple_debug_info("msim", "SESSKEY=<%d>\n", session->sesskey);
1571
1572 /* What is proof? Used to be uid, but now is 52 base64'd bytes... */
1573
1574 /* Comes with: proof,profileid,userid,uniquenick -- all same values
1575 * some of the time, but can vary. This is our own user ID. */
1576 session->userid = msim_msg_get_integer(msg, "userid");
1577
1578 /* Save uid to account so this account can be looked up by uid. */
1579 purple_account_set_int(session->account, "uid", session->userid);
1580
1581 /* Not sure what profileid is used for. */
1582 if (msim_msg_get_integer(msg, "profileid") != session->userid) {
1583 msim_unrecognized(session, msg,
1584 "Profile ID didn't match user ID, don't know why");
1585 }
1586
1587 /* We now know are our own username, only after we're logged in..
1588 * which is weird, but happens because you login with your email
1589 * address and not username. Will be freed in msim_session_destroy(). */
1590 session->username = msim_msg_get_string(msg, "uniquenick");
1591
1592 /* If user lacks a username, help them get one. */
1593 if (msim_msg_get_integer(msg, "uniquenick") == session->userid) {
1594 purple_debug_info("msim_is_username_set", "no username is set\n");
1595 purple_request_yes_no(session->gc,
1596 _("MySpaceIM - No Username Set"),
1597 _("You appear to have no MySpace username."),
1598 _("Would you like to set one now? (Note: THIS CANNOT BE CHANGED!)"),
1599 0,
1600 session->account,
1601 NULL,
1602 NULL,
1603 session->gc,
1604 G_CALLBACK(msim_set_username_cb),
1605 G_CALLBACK(msim_do_not_set_username_cb));
1606 purple_debug_info("msim_is_username_set","'username not set' alert prompted\n");
1607 return FALSE;
1608 }
1609 return TRUE;
1610 }
1611
1612 /** Called after username is set, if necessary and we're open for business. */
1613 gboolean msim_we_are_logged_on(MsimSession *session)
1614 {
1615 MsimMessage *body;
1616
1617 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1618
1619 /* The session is now set up, ready to be connected. This emits the
1620 * signedOn signal, so clients can now do anything with msimprpl, and
1621 * we're ready for it (session key, userid, username all setup). */
1622 purple_connection_update_progress(session->gc, _("Connected"), 3, 4);
1623 purple_connection_set_state(session->gc, PURPLE_CONNECTED);
1624
1625 /* Set display name to username (otherwise will show email address) */
1626 purple_connection_set_display_name(session->gc, session->username);
1627
1628 body = msim_msg_new(
1629 "UserID", MSIM_TYPE_INTEGER, session->userid,
1630 NULL);
1631
1632 /* Request IM info about ourself. */
1633 msim_send(session,
1634 "persist", MSIM_TYPE_STRING, g_strdup("persist"),
1635 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1636 "dsn", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_DSN,
1637 "uid", MSIM_TYPE_INTEGER, session->userid,
1638 "lid", MSIM_TYPE_INTEGER, MG_OWN_MYSPACE_INFO_LID,
1639 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
1640 "body", MSIM_TYPE_DICTIONARY, body,
1641 NULL);
1642
1643 /* Request MySpace info about ourself. */
1644 msim_send(session,
1645 "persist", MSIM_TYPE_STRING, g_strdup("persist"),
1646 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1647 "dsn", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_DSN,
1648 "uid", MSIM_TYPE_INTEGER, session->userid,
1649 "lid", MSIM_TYPE_INTEGER, MG_OWN_IM_INFO_LID,
1650 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
1651 "body", MSIM_TYPE_STRING, g_strdup(""),
1652 NULL);
1653
1654 /* TODO: set options (persist cmd=514,dsn=1,lid=10) */
1655 /* TODO: set blocklist */
1656
1657 /* Notify servers of our current status. */
1658 purple_debug_info("msim", "msim_we_are_logged_on: notifying servers of status\n");
1659 msim_set_status(session->account,
1660 purple_account_get_active_status(session->account));
1661
1662 /* TODO: setinfo */
1663 /*
1664 body = msim_msg_new(
1665 "TotalFriends", MSIM_TYPE_INTEGER, 666,
1666 NULL);
1667 msim_send(session,
1668 "setinfo", MSIM_TYPE_BOOLEAN, TRUE,
1669 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
1670 "info", MSIM_TYPE_DICTIONARY, body,
1671 NULL);
1672 */
1673
1674 /* Disable due to problems with timeouts. TODO: fix. */
1675 #ifdef MSIM_USE_KEEPALIVE
1676 purple_timeout_add(MSIM_KEEPALIVE_INTERVAL_CHECK,
1677 (GSourceFunc)msim_check_alive, session);
1678 #endif
1679
1680 /* Check mail if they want to. */
1681 if (purple_account_get_check_mail(session->account)) {
1682 session->inbox_handle = purple_timeout_add(MSIM_MAIL_INTERVAL_CHECK,
1683 (GSourceFunc)msim_check_inbox, session);
1684 msim_check_inbox(session);
1685 }
1686
1687 msim_get_contact_list(session, MSIM_CONTACT_LIST_INITIAL_FRIENDS);
1688
1689 return TRUE;
1690 }
1691
1692 /**
1693 * Process a message.
1694 * 1852 *
1695 * @param session 1853 * @param session
1696 * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees. 1854 * @param msg A message from the server, ready for processing (possibly with resolved username information attached). Caller frees.
1697 * 1855 *
1698 * @return TRUE if successful. FALSE if processing failed. 1856 * @return TRUE if successful. FALSE if processing failed.
1699 */ 1857 */
1700 static gboolean 1858 static gboolean
1701 msim_process(MsimSession *session, MsimMessage *msg) 1859 msim_process(MsimSession *session, MsimMessage *msg)
1702 { 1860 {
1703 g_return_val_if_fail(session != NULL, FALSE); 1861 g_return_val_if_fail(session != NULL, FALSE);
1704 g_return_val_if_fail(msg != NULL, FALSE); 1862 g_return_val_if_fail(msg != NULL, FALSE);
1705
1706 #ifdef MSIM_DEBUG_MSG
1707 msim_msg_dump("ready to process: %s\n", msg);
1708 #endif
1709 1863
1710 if (msim_msg_get_integer(msg, "lc") == 1) { 1864 if (msim_msg_get_integer(msg, "lc") == 1) {
1711 return msim_login_challenge(session, msg); 1865 return msim_login_challenge(session, msg);
1712 } else if (msim_msg_get_integer(msg, "lc") == 2) { 1866 } else if (msim_msg_get_integer(msg, "lc") == 2) {
1713 /* return msim_we_are_logged_on(session, msg); */ 1867 /* return msim_we_are_logged_on(session, msg); */
1730 msim_unrecognized(session, msg, "in msim_process"); 1884 msim_unrecognized(session, msg, "in msim_process");
1731 return FALSE; 1885 return FALSE;
1732 } 1886 }
1733 } 1887 }
1734 1888
1735 /** Process the initial server information from the server. */ 1889 /**
1890 * After a uid is resolved to username, tag it with the username and submit for processing.
1891 *
1892 * @param session
1893 * @param userinfo Response messsage to resolving request.
1894 * @param data MsimMessage *, the message to attach information to.
1895 */
1896 static void
1897 msim_incoming_resolved(MsimSession *session, const MsimMessage *userinfo,
1898 gpointer data)
1899 {
1900 gchar *username;
1901 MsimMessage *msg, *body;
1902
1903 g_return_if_fail(MSIM_SESSION_VALID(session));
1904 g_return_if_fail(userinfo != NULL);
1905
1906 body = msim_msg_get_dictionary(userinfo, "body");
1907 g_return_if_fail(body != NULL);
1908
1909 username = msim_msg_get_string(body, "UserName");
1910 g_return_if_fail(username != NULL);
1911 /* Note: username will be owned by 'msg' below. */
1912
1913 msg = (MsimMessage *)data;
1914 g_return_if_fail(msg != NULL);
1915
1916 /* TODO: more elegant solution than below. attach whole message? */
1917 /* Special elements name beginning with '_', we'll use internally within the
1918 * program (did not come directly from the wire). */
1919 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, username); /* This makes 'msg' the owner of 'username' */
1920
1921 /* TODO: attach more useful information, like ImageURL */
1922
1923 msim_process(session, msg);
1924
1925 msim_msg_free(msg);
1926 msim_msg_free(body);
1927 }
1928
1929 /**
1930 * Preprocess incoming messages, resolving as needed, calling
1931 * msim_process() when ready to process.
1932 *
1933 * @param session
1934 * @param msg MsimMessage *, freed by caller.
1935 */
1736 static gboolean 1936 static gboolean
1737 msim_process_server_info(MsimSession *session, MsimMessage *msg) 1937 msim_preprocess_incoming(MsimSession *session, MsimMessage *msg)
1738 { 1938 {
1739 MsimMessage *body;
1740
1741 body = msim_msg_get_dictionary(msg, "body");
1742 g_return_val_if_fail(body != NULL, FALSE);
1743
1744 /* Example body:
1745 AdUnitRefreshInterval=10.
1746 AlertPollInterval=360.
1747 AllowChatRoomEmoticonSharing=False.
1748 ChatRoomUserIDs=78744676;163733130;1300326231;123521495;142663391.
1749 CurClientVersion=673.
1750 EnableIMBrowse=True.
1751 EnableIMStuffAvatars=False.
1752 EnableIMStuffZaps=False.
1753 MaxAddAllFriends=100.
1754 MaxContacts=1000.
1755 MinClientVersion=594.
1756 MySpaceIM_ENGLISH=78744676.
1757 MySpaceNowTimer=720.
1758 PersistenceDataTimeout=900.
1759 UseWebChallenge=1.
1760 WebTicketGoHome=False
1761
1762 Anything useful? TODO: use what is useful, and use it.
1763 */
1764 purple_debug_info("msim_process_server_info",
1765 "maximum contacts: %d\n",
1766 msim_msg_get_integer(body, "MaxContacts"));
1767
1768 session->server_info = body;
1769 /* session->server_info freed in msim_session_destroy */
1770
1771 return TRUE;
1772 }
1773
1774 /** Process a web challenge, used to login to the web site. */
1775 static gboolean
1776 msim_web_challenge(MsimSession *session, MsimMessage *msg)
1777 {
1778 /* TODO: web challenge, store token. #2659. */
1779 return FALSE;
1780 }
1781
1782 /**
1783 * Process a persistance message reply from the server.
1784 *
1785 * @param session
1786 * @param msg Message reply from server.
1787 *
1788 * @return TRUE if successful.
1789 *
1790 * msim_lookup_user sets callback for here
1791 */
1792 static gboolean
1793 msim_process_reply(MsimSession *session, MsimMessage *msg)
1794 {
1795 MSIM_USER_LOOKUP_CB cb;
1796 gpointer data;
1797 guint rid, cmd, dsn, lid;
1798
1799 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE); 1939 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1800 g_return_val_if_fail(msg != NULL, FALSE); 1940 g_return_val_if_fail(msg != NULL, FALSE);
1801 1941
1802 msim_store_user_info(session, msg, NULL); 1942 if (msim_msg_get(msg, "bm") && msim_msg_get(msg, "f")) {
1803 1943 guint uid;
1804 rid = msim_msg_get_integer(msg, "rid"); 1944 const gchar *username;
1805 cmd = msim_msg_get_integer(msg, "cmd"); 1945
1806 dsn = msim_msg_get_integer(msg, "dsn"); 1946 /* 'f' = userid message is from, in buddy messages */
1807 lid = msim_msg_get_integer(msg, "lid"); 1947 uid = msim_msg_get_integer(msg, "f");
1808 1948
1809 /* Unsolicited messages */ 1949 username = msim_uid2username_from_blist(session->account, uid);
1810 if (cmd == (MSIM_CMD_BIT_REPLY | MSIM_CMD_GET)) { 1950
1811 if (dsn == MG_SERVER_INFO_DSN && lid == MG_SERVER_INFO_LID) { 1951 if (username) {
1812 return msim_process_server_info(session, msg); 1952 /* Know username already, use it. */
1813 } else if (dsn == MG_WEB_CHALLENGE_DSN && lid == MG_WEB_CHALLENGE_LID) { 1953 purple_debug_info("msim", "msim_preprocess_incoming: tagging with _username=%s\n",
1814 return msim_web_challenge(session, msg); 1954 username);
1815 } 1955 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
1816 } 1956 return msim_process(session, msg);
1817 1957
1818 /* If a callback is registered for this userid lookup, call it. */
1819 cb = g_hash_table_lookup(session->user_lookup_cb, GUINT_TO_POINTER(rid));
1820 data = g_hash_table_lookup(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
1821
1822 if (cb) {
1823 purple_debug_info("msim", "msim_process_reply: calling callback now\n");
1824 msim_msg_dump("for msg=%s\n", msg);
1825 /* Clone message, so that the callback 'cb' can use it (needs to free it also). */
1826 cb(session, msim_msg_clone(msg), data);
1827 g_hash_table_remove(session->user_lookup_cb, GUINT_TO_POINTER(rid));
1828 g_hash_table_remove(session->user_lookup_cb_data, GUINT_TO_POINTER(rid));
1829 } else {
1830 purple_debug_info("msim",
1831 "msim_process_reply: no callback for rid %d\n", rid);
1832 }
1833
1834 return TRUE;
1835 }
1836
1837 /**
1838 * Handle an error from the server.
1839 *
1840 * @param session
1841 * @param msg The message.
1842 *
1843 * @return TRUE if successfully reported error.
1844 */
1845 static gboolean
1846 msim_error(MsimSession *session, MsimMessage *msg)
1847 {
1848 gchar *errmsg, *full_errmsg;
1849 guint err;
1850
1851 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1852 g_return_val_if_fail(msg != NULL, FALSE);
1853
1854 err = msim_msg_get_integer(msg, "err");
1855 errmsg = msim_msg_get_string(msg, "errmsg");
1856
1857 full_errmsg = g_strdup_printf(_("Protocol error, code %d: %s"), err,
1858 errmsg ? errmsg : "no 'errmsg' given");
1859
1860 g_free(errmsg);
1861
1862 purple_debug_info("msim", "msim_error (sesskey=%d): %s\n",
1863 session->sesskey, full_errmsg);
1864
1865 /* Destroy session if fatal. */
1866 if (msim_msg_get(msg, "fatal")) {
1867 PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1868 purple_debug_info("msim", "fatal error, closing\n");
1869
1870 switch (err) {
1871 case MSIM_ERROR_INCORRECT_PASSWORD: /* Incorrect password */
1872 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1873 if (!purple_account_get_remember_password(session->account))
1874 purple_account_set_password(session->account, NULL);
1875 #ifdef MSIM_MAX_PASSWORD_LENGTH
1876 if (session->account->password && (strlen(session->account->password) > MSIM_MAX_PASSWORD_LENGTH)) {
1877 gchar *suggestion;
1878
1879 suggestion = g_strdup_printf(_("%s Your password is "
1880 "%d characters, greater than the "
1881 "expected maximum length of %d for "
1882 "MySpaceIM. Please shorten your "
1883 "password at http://profileedit.myspace.com/index.cfm?fuseaction=accountSettings.changePassword and try again."),
1884 full_errmsg, (int)
1885 strlen(session->account->password),
1886 MSIM_MAX_PASSWORD_LENGTH);
1887
1888 /* Replace full_errmsg. */
1889 g_free(full_errmsg);
1890 full_errmsg = suggestion;
1891 }
1892 #endif
1893 break;
1894 case MSIM_ERROR_LOGGED_IN_ELSEWHERE: /* Logged in elsewhere */
1895 reason = PURPLE_CONNECTION_ERROR_NAME_IN_USE;
1896 if (!purple_account_get_remember_password(session->account))
1897 purple_account_set_password(session->account, NULL);
1898 break;
1899 }
1900 purple_connection_error_reason (session->gc, reason, full_errmsg);
1901 } else {
1902 purple_notify_error(session->account, _("MySpaceIM Error"), full_errmsg, NULL);
1903 }
1904
1905 g_free(full_errmsg);
1906
1907 return TRUE;
1908 }
1909
1910 /**
1911 * Process incoming status messages.
1912 *
1913 * @param session
1914 * @param msg Status update message. Caller frees.
1915 *
1916 * @return TRUE if successful.
1917 */
1918 static gboolean
1919 msim_incoming_status(MsimSession *session, MsimMessage *msg)
1920 {
1921 PurpleBuddyList *blist;
1922 MsimUser *user;
1923 GList *list;
1924 gchar *status_headline, *status_headline_escaped;
1925 gint status_code, purple_status_code;
1926 gchar *username;
1927 gchar *unrecognized_msg;
1928
1929 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
1930 g_return_val_if_fail(msg != NULL, FALSE);
1931
1932 msim_msg_dump("msim_status msg=%s\n", msg);
1933
1934 /* Helpfully looked up by msim_incoming_resolve() for us. */
1935 username = msim_msg_get_string(msg, "_username");
1936 g_return_val_if_fail(username != NULL, FALSE);
1937
1938 {
1939 gchar *ss;
1940
1941 ss = msim_msg_get_string(msg, "msg");
1942 purple_debug_info("msim",
1943 "msim_status: updating status for <%s> to <%s>\n",
1944 username, ss ? ss : "(NULL)");
1945 g_free(ss);
1946 }
1947
1948 /* Example fields:
1949 * |s|0|ss|Offline
1950 * |s|1|ss|:-)|ls||ip|0|p|0
1951 */
1952 list = msim_msg_get_list(msg, "msg");
1953
1954 status_code = msim_msg_get_integer_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_ONLINE));
1955 purple_debug_info("msim", "msim_status: %s's status code = %d\n", username, status_code);
1956 status_headline = msim_msg_get_string_from_element(g_list_nth_data(list, MSIM_STATUS_ORDINAL_HEADLINE));
1957
1958 blist = purple_get_blist();
1959
1960 /* Add buddy if not found.
1961 * TODO: Could this be responsible for #3444? */
1962 user = msim_find_user(session, username);
1963 if (!user) {
1964 PurpleBuddy *buddy;
1965
1966 purple_debug_info("msim",
1967 "msim_status: making new buddy for %s\n", username);
1968 buddy = purple_buddy_new(session->account, username, NULL);
1969 purple_blist_add_buddy(buddy, NULL, NULL, NULL);
1970
1971 user = msim_get_user_from_buddy(buddy);
1972 user->id = msim_msg_get_integer(msg, "f");
1973
1974 /* Keep track of the user ID across sessions */
1975 purple_blist_node_set_int(&buddy->node, "UserID", user->id);
1976
1977 msim_store_user_info(session, msg, NULL);
1978 } else {
1979 purple_debug_info("msim", "msim_status: found buddy %s\n", username);
1980 }
1981
1982 if (status_headline && strcmp(status_headline, "") != 0) {
1983 /* The status headline is plaintext, but libpurple treats it as HTML,
1984 * so escape any HTML characters to their entity equivalents. */
1985 status_headline_escaped = g_markup_escape_text(status_headline, strlen(status_headline));
1986 } else {
1987 status_headline_escaped = NULL;
1988 }
1989
1990 g_free(status_headline);
1991
1992 if (user->headline)
1993 g_free(user->headline);
1994
1995 /* don't copy; let the MsimUser own the headline, memory-wise */
1996 user->headline = status_headline_escaped;
1997
1998 /* Set user status */
1999 switch (status_code) {
2000 case MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN:
2001 purple_status_code = PURPLE_STATUS_OFFLINE;
2002 break;
2003
2004 case MSIM_STATUS_CODE_ONLINE:
2005 purple_status_code = PURPLE_STATUS_AVAILABLE;
2006 break;
2007
2008 case MSIM_STATUS_CODE_AWAY:
2009 purple_status_code = PURPLE_STATUS_AWAY;
2010 break;
2011
2012 case MSIM_STATUS_CODE_IDLE:
2013 /* Treat idle as an available status. */
2014 purple_status_code = PURPLE_STATUS_AVAILABLE;
2015 break;
2016
2017 default:
2018 purple_debug_info("msim", "msim_incoming_status for %s, unknown status code %d, treating as available\n",
2019 username, status_code);
2020 purple_status_code = PURPLE_STATUS_AVAILABLE;
2021
2022 unrecognized_msg = g_strdup_printf("msim_incoming_status, unrecognized status code: %d\n",
2023 status_code);
2024 msim_unrecognized(session, NULL, unrecognized_msg);
2025 g_free(unrecognized_msg);
2026
2027 }
2028
2029 purple_prpl_got_user_status(session->account, username, purple_primitive_get_id_from_type(purple_status_code), NULL);
2030
2031 if (status_code == MSIM_STATUS_CODE_IDLE) {
2032 purple_debug_info("msim", "msim_status: got idle: %s\n", username);
2033 purple_prpl_got_user_idle(session->account, username, TRUE, time(NULL));
2034 } else {
2035 /* All other statuses indicate going back to non-idle. */
2036 purple_prpl_got_user_idle(session->account, username, FALSE, time(NULL));
2037 }
2038
2039 #ifdef MSIM_SEND_CLIENT_VERSION
2040 if (status_code == MSIM_STATUS_CODE_ONLINE) {
2041 /* Secretly whisper to unofficial clients our own version as they come online */
2042 msim_send_unofficial_client(session, username);
2043 }
2044 #endif
2045
2046 if (status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN) {
2047 /* Get information when they come online.
2048 * TODO: periodically refresh?
2049 */
2050 purple_debug_info("msim_incoming_status", "%s came online, looking up\n", username);
2051 msim_lookup_user(session, username, NULL, NULL);
2052 }
2053
2054 g_free(username);
2055 msim_msg_list_free(list);
2056
2057 return TRUE;
2058 }
2059
2060 /** Add a buddy to user's buddy list. */
2061 void
2062 msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2063 {
2064 MsimSession *session;
2065 MsimMessage *msg;
2066 MsimMessage *msg_persist;
2067 MsimMessage *body;
2068
2069 session = (MsimSession *)gc->proto_data;
2070 purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n",
2071 buddy->name, (group && group->name) ? group->name : "(no group)");
2072
2073 msg = msim_msg_new(
2074 "addbuddy", MSIM_TYPE_BOOLEAN, TRUE,
2075 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2076 /* "newprofileid" will be inserted here with uid. */
2077 "reason", MSIM_TYPE_STRING, g_strdup(""),
2078 NULL);
2079
2080 if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) {
2081 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed."));
2082 msim_msg_free(msg);
2083 return;
2084 }
2085 msim_msg_free(msg);
2086
2087 /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from
2088 * buddy list since Purple adds it locally. */
2089
2090 body = msim_msg_new(
2091 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"),
2092 "GroupName", MSIM_TYPE_STRING, g_strdup(group->name),
2093 "Position", MSIM_TYPE_INTEGER, 1000,
2094 "Visibility", MSIM_TYPE_INTEGER, 1,
2095 "NickName", MSIM_TYPE_STRING, g_strdup(""),
2096 "NameSelect", MSIM_TYPE_INTEGER, 0,
2097 NULL);
2098
2099 /* TODO: Update blocklist. */
2100
2101 msg_persist = msim_msg_new(
2102 "persist", MSIM_TYPE_INTEGER, 1,
2103 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2104 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT,
2105 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN,
2106 "uid", MSIM_TYPE_INTEGER, session->userid,
2107 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID,
2108 /* TODO: Use msim_new_reply_callback to get rid. */
2109 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
2110 "body", MSIM_TYPE_DICTIONARY, body,
2111 NULL);
2112
2113 if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL))
2114 {
2115 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed"));
2116 msim_msg_free(msg_persist);
2117 return;
2118 }
2119 msim_msg_free(msg_persist);
2120
2121 }
2122
2123 /** Perform actual postprocessing on a message, adding userid as specified.
2124 *
2125 * @param msg The message to postprocess.
2126 * @param uid_before Name of field where to insert new field before, or NULL for end.
2127 * @param uid_field_name Name of field to add uid to.
2128 * @param uid The userid to insert.
2129 *
2130 * If the field named by uid_field_name already exists, then its string contents will
2131 * be used for the field, except "<uid>" will be replaced by the userid.
2132 *
2133 * If the field named by uid_field_name does not exist, it will be added before the
2134 * field named by uid_before, as an integer, with the userid.
2135 *
2136 * Does not handle sending, or scheduling userid lookup. For that, see msim_postprocess_outgoing().
2137 */
2138 static MsimMessage *
2139 msim_do_postprocessing(MsimMessage *msg, const gchar *uid_before,
2140 const gchar *uid_field_name, guint uid)
2141 {
2142 MsimMessageElement *elem;
2143 msim_msg_dump("msim_do_postprocessing msg: %s\n", msg);
2144
2145 /* First, check - if the field already exists, replace <uid> within it */
2146 if ((elem = msim_msg_get(msg, uid_field_name)) != NULL) {
2147 gchar *fmt_string;
2148 gchar *uid_str, *new_str;
2149
2150 /* Get the packed element, flattening it. This allows <uid> to be
2151 * replaced within nested data structures, since the replacement is done
2152 * on the linear, packed data, not on a complicated data structure.
2153 *
2154 * For example, if the field was originally a dictionary or a list, you
2155 * would have to iterate over all the items in it to see what needs to
2156 * be replaced. But by packing it first, the <uid> marker is easily replaced
2157 * just by a string replacement.
2158 */
2159 fmt_string = msim_msg_pack_element_data(elem);
2160
2161 uid_str = g_strdup_printf("%d", uid);
2162 new_str = purple_strreplace(fmt_string, "<uid>", uid_str);
2163 g_free(uid_str);
2164 g_free(fmt_string);
2165
2166 /* Free the old element data */
2167 msim_msg_free_element_data(elem->data);
2168
2169 /* Replace it with our new data */
2170 elem->data = new_str;
2171 elem->type = MSIM_TYPE_RAW;
2172
2173 } else {
2174 /* Otherwise, insert new field into outgoing message. */
2175 msg = msim_msg_insert_before(msg, uid_before, uid_field_name, MSIM_TYPE_INTEGER, GUINT_TO_POINTER(uid));
2176 }
2177
2178 msim_msg_dump("msim_postprocess_outgoing_cb: postprocessed msg=%s\n", msg);
2179
2180 return msg;
2181 }
2182
2183 /** Callback for msim_postprocess_outgoing() to add a userid to a message, and send it (once receiving userid).
2184 *
2185 * @param session
2186 * @param userinfo The user information reply message, containing the user ID
2187 * @param data The message to postprocess and send.
2188 *
2189 * The data message should contain these fields:
2190 *
2191 * _uid_field_name: string, name of field to add with userid from userinfo message
2192 * _uid_before: string, name of field before field to insert, or NULL for end
2193 *
2194 *
2195 */
2196 static void
2197 msim_postprocess_outgoing_cb(MsimSession *session, MsimMessage *userinfo,
2198 gpointer data)
2199 {
2200 gchar *uid_field_name, *uid_before, *username;
2201 guint uid;
2202 MsimMessage *msg, *body;
2203
2204 msg = (MsimMessage *)data;
2205
2206 msim_msg_dump("msim_postprocess_outgoing_cb() got msg=%s\n", msg);
2207
2208 /* Obtain userid from userinfo message. */
2209 body = msim_msg_get_dictionary(userinfo, "body");
2210 g_return_if_fail(body != NULL);
2211
2212 uid = msim_msg_get_integer(body, "UserID");
2213 msim_msg_free(body);
2214
2215 username = msim_msg_get_string(msg, "_username");
2216
2217 if (!uid) {
2218 gchar *msg;
2219
2220 msg = g_strdup_printf(_("No such user: %s"), username);
2221 if (!purple_conv_present_error(username, session->account, msg)) {
2222 purple_notify_error(NULL, NULL, _("User lookup"), msg);
2223 }
2224
2225 g_free(msg);
2226 g_free(username);
2227 /* TODO: free
2228 * msim_msg_free(msg);
2229 */
2230 return;
2231 }
2232
2233 uid_field_name = msim_msg_get_string(msg, "_uid_field_name");
2234 uid_before = msim_msg_get_string(msg, "_uid_before");
2235
2236 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
2237
2238 /* Send */
2239 if (!msim_msg_send(session, msg)) {
2240 msim_msg_dump("msim_postprocess_outgoing_cb: sending failed for message: %s\n", msg);
2241 }
2242
2243
2244 /* Free field names AFTER sending message, because MsimMessage does NOT copy
2245 * field names - instead, treats them as static strings (which they usually are).
2246 */
2247 g_free(uid_field_name);
2248 g_free(uid_before);
2249 g_free(username);
2250 /* TODO: free
2251 * msim_msg_free(msg);
2252 */
2253 }
2254
2255 /** Postprocess and send a message.
2256 *
2257 * @param session
2258 * @param msg Message to postprocess. Will NOT be freed.
2259 * @param username Username to resolve. Assumed to be a static string (will not be freed or copied).
2260 * @param uid_field_name Name of new field to add, containing uid of username. Static string.
2261 * @param uid_before Name of existing field to insert username field before. Static string.
2262 *
2263 * @return TRUE if successful.
2264 */
2265 gboolean
2266 msim_postprocess_outgoing(MsimSession *session, MsimMessage *msg,
2267 const gchar *username, const gchar *uid_field_name,
2268 const gchar *uid_before)
2269 {
2270 PurpleBuddy *buddy;
2271 guint uid;
2272 gboolean rc;
2273
2274 g_return_val_if_fail(msg != NULL, FALSE);
2275
2276 /* Store information for msim_postprocess_outgoing_cb(). */
2277 msim_msg_dump("msim_postprocess_outgoing: msg before=%s\n", msg);
2278 msg = msim_msg_append(msg, "_username", MSIM_TYPE_STRING, g_strdup(username));
2279 msg = msim_msg_append(msg, "_uid_field_name", MSIM_TYPE_STRING, g_strdup(uid_field_name));
2280 msg = msim_msg_append(msg, "_uid_before", MSIM_TYPE_STRING, g_strdup(uid_before));
2281
2282 /* First, try the most obvious. If numeric userid is given, use that directly. */
2283 if (msim_is_userid(username)) {
2284 uid = atol(username);
2285 } else {
2286 /* Next, see if on buddy list and know uid. */
2287 buddy = purple_find_buddy(session->account, username);
2288 if (buddy) {
2289 uid = purple_blist_node_get_int(&buddy->node, "UserID");
2290 } else { 1958 } else {
2291 uid = 0; 1959 gchar *from;
2292 } 1960
2293 1961 /* Send lookup request. */
2294 if (!buddy || !uid) { 1962 /* XXX: where is msim_msg_get_string() freed? make _strdup and _nonstrdup. */
2295 /* Don't have uid offhand - need to ask for it, and wait until hear back before sending. */ 1963 purple_debug_info("msim", "msim_incoming: sending lookup, setting up callback\n");
2296 purple_debug_info("msim", ">>> msim_postprocess_outgoing: couldn't find username %s in blist\n", 1964 from = msim_msg_get_string(msg, "f");
2297 username ? username : "(NULL)"); 1965 msim_lookup_user(session, from, msim_incoming_resolved, msim_msg_clone(msg));
2298 msim_msg_dump("msim_postprocess_outgoing - scheduling lookup, msg=%s\n", msg); 1966 g_free(from);
2299 /* TODO: where is cloned message freed? Should be in _cb. */ 1967
2300 msim_lookup_user(session, username, msim_postprocess_outgoing_cb, msim_msg_clone(msg)); 1968 /* indeterminate */
2301 return TRUE; /* not sure of status yet - haven't sent! */ 1969 return TRUE;
2302 }
2303 }
2304
2305 /* Already have uid, postprocess and send msg immediately. */
2306 purple_debug_info("msim", "msim_postprocess_outgoing: found username %s has uid %d\n",
2307 username ? username : "(NULL)", uid);
2308
2309 msg = msim_do_postprocessing(msg, uid_before, uid_field_name, uid);
2310
2311 msim_msg_dump("msim_postprocess_outgoing: msg after (uid immediate)=%s\n", msg);
2312
2313 rc = msim_msg_send(session, msg);
2314
2315 /* TODO: free
2316 * msim_msg_free(msg);
2317 */
2318
2319 return rc;
2320 }
2321
2322 /** Remove a buddy from the user's buddy list. */
2323 void
2324 msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2325 {
2326 MsimSession *session;
2327 MsimMessage *delbuddy_msg;
2328 MsimMessage *persist_msg;
2329 MsimMessage *blocklist_msg;
2330 GList *blocklist_updates;
2331
2332 session = (MsimSession *)gc->proto_data;
2333
2334 delbuddy_msg = msim_msg_new(
2335 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE,
2336 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2337 /* 'delprofileid' with uid will be inserted here. */
2338 NULL);
2339
2340 if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) {
2341 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed"));
2342 msim_msg_free(delbuddy_msg);
2343 return;
2344 }
2345 msim_msg_free(delbuddy_msg);
2346
2347 persist_msg = msim_msg_new(
2348 "persist", MSIM_TYPE_INTEGER, 1,
2349 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2350 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE,
2351 "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN,
2352 "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID,
2353 "uid", MSIM_TYPE_INTEGER, session->userid,
2354 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
2355 /* <uid> will be replaced by postprocessing */
2356 "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"),
2357 NULL);
2358
2359 if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) {
2360 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed"));
2361 msim_msg_free(persist_msg);
2362 return;
2363 }
2364 msim_msg_free(persist_msg);
2365
2366 blocklist_updates = NULL;
2367 blocklist_updates = g_list_prepend(blocklist_updates, "a-");
2368 blocklist_updates = g_list_prepend(blocklist_updates, "<uid>");
2369 blocklist_updates = g_list_prepend(blocklist_updates, "b-");
2370 blocklist_updates = g_list_prepend(blocklist_updates, "<uid>");
2371 blocklist_updates = g_list_reverse(blocklist_updates);
2372
2373 blocklist_msg = msim_msg_new(
2374 "blocklist", MSIM_TYPE_BOOLEAN, TRUE,
2375 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2376 /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */
2377 /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */
2378 "idlist", MSIM_TYPE_LIST, blocklist_updates,
2379 NULL);
2380
2381 if (!msim_postprocess_outgoing(session, blocklist_msg, buddy->name, "idlist", NULL)) {
2382 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("blocklist command failed"));
2383 msim_msg_free(blocklist_msg);
2384 return;
2385 }
2386 msim_msg_free(blocklist_msg);
2387 }
2388
2389 /**
2390 * Returns a string of a username in canonical form. Basically removes all the
2391 * spaces, lowercases the string, and looks up user IDs to usernames.
2392 * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'.
2393 *
2394 * Borrowed this code from oscar_normalize. Added checking for
2395 * "if userid, get name before normalizing"
2396 */
2397 const char *msim_normalize(const PurpleAccount *account, const char *str) {
2398 static char normalized[BUF_LEN];
2399 char *tmp1, *tmp2;
2400 int i, j;
2401 guint id;
2402
2403 g_return_val_if_fail(str != NULL, NULL);
2404
2405 if (msim_is_userid(str)) {
2406 /* Have user ID, we need to get their username first :) */
2407 const char *username;
2408
2409 /* If the account does not exist, we can't look up the user. */
2410 if (!account || !account->gc)
2411 return str;
2412
2413 id = atol(str);
2414 username = msim_uid2username_from_blist((PurpleAccount *)account, id);
2415 if (!username) {
2416 /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */
2417 /* Note: manual lookup using msim_lookup_user() is a problem inside
2418 * msim_normalize(), because msim_lookup_user() calls a callback function
2419 * when the user information has been looked up, but msim_normalize() expects
2420 * the result immediately. */
2421 strncpy(normalized, str, BUF_LEN);
2422 } else {
2423 strncpy(normalized, username, BUF_LEN);
2424 } 1970 }
2425 } else { 1971 } else {
2426 /* Have username. */ 1972 /* Nothing to resolve - send directly to processing. */
2427 strncpy(normalized, str, BUF_LEN); 1973 return msim_process(session, msg);
2428 } 1974 }
2429
2430 /* Strip spaces. */
2431 for (i=0, j=0; normalized[j]; i++, j++) {
2432 while (normalized[j] == ' ')
2433 j++;
2434 normalized[i] = normalized[j];
2435 }
2436 normalized[i] = '\0';
2437
2438 /* Lowercase and perform UTF-8 normalization. */
2439 tmp1 = g_utf8_strdown(normalized, -1);
2440 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
2441 g_snprintf(normalized, sizeof(normalized), "%s", tmp2);
2442 g_free(tmp2);
2443 g_free(tmp1);
2444
2445 /* TODO: re-add caps and spacing back to what the user wanted.
2446 * User can format their own names, for example 'msimprpl' is shown
2447 * as 'MsIm PrPl' in the official client.
2448 *
2449 * TODO: file a ticket to add this enhancement.
2450 */
2451
2452 return normalized;
2453 }
2454
2455 static GHashTable *
2456 msim_get_account_text_table(PurpleAccount *unused)
2457 {
2458 GHashTable *table;
2459
2460 table = g_hash_table_new(g_str_hash, g_str_equal);
2461
2462 g_hash_table_insert(table, "login_label", (gpointer)_("Email Address..."));
2463
2464 return table;
2465 }
2466
2467 /** Return whether the buddy can be messaged while offline.
2468 *
2469 * The protocol supports offline messages in just the same way as online
2470 * messages.
2471 */
2472 gboolean
2473 msim_offline_message(const PurpleBuddy *buddy)
2474 {
2475 return TRUE;
2476 } 1975 }
2477 1976
2478 /** 1977 /**
2479 * Callback when input available. 1978 * Callback when input available.
2480 * 1979 *
2482 * @param source File descriptor. 1981 * @param source File descriptor.
2483 * @param cond PURPLE_INPUT_READ 1982 * @param cond PURPLE_INPUT_READ
2484 * 1983 *
2485 * Reads the input, and calls msim_preprocess_incoming() to handle it. 1984 * Reads the input, and calls msim_preprocess_incoming() to handle it.
2486 */ 1985 */
2487 static void 1986 static void
2488 msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond) 1987 msim_input_cb(gpointer gc_uncasted, gint source, PurpleInputCondition cond)
2489 { 1988 {
2490 PurpleConnection *gc; 1989 PurpleConnection *gc;
2491 PurpleAccount *account; 1990 PurpleAccount *account;
2492 MsimSession *session; 1991 MsimSession *session;
2515 /* Mark down that we got data, so we don't timeout. */ 2014 /* Mark down that we got data, so we don't timeout. */
2516 session->last_comm = time(NULL); 2015 session->last_comm = time(NULL);
2517 2016
2518 /* If approaching end of buffer, reallocate some more memory. */ 2017 /* If approaching end of buffer, reallocate some more memory. */
2519 if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) { 2018 if (session->rxsize < session->rxoff + MSIM_READ_BUF_SIZE) {
2520 purple_debug_info("msim", 2019 purple_debug_info("msim",
2521 "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n", 2020 "msim_input_cb: %d-byte read buffer full, rxoff=%d, " "growing by %d bytes\n",
2522 session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE); 2021 session->rxsize, session->rxoff, MSIM_READ_BUF_SIZE);
2523 session->rxsize += MSIM_READ_BUF_SIZE; 2022 session->rxsize += MSIM_READ_BUF_SIZE;
2524 session->rxbuf = g_realloc(session->rxbuf, session->rxsize); 2023 session->rxbuf = g_realloc(session->rxbuf, session->rxsize);
2525 2024
2526 return; 2025 return;
2527 } 2026 }
2528 2027
2529 purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n", 2028 purple_debug_info("msim", "dynamic buffer at %d (max %d), reading up to %d\n",
2530 session->rxoff, session->rxsize, 2029 session->rxoff, session->rxsize,
2531 MSIM_READ_BUF_SIZE - session->rxoff - 1); 2030 MSIM_READ_BUF_SIZE - session->rxoff - 1);
2532 2031
2533 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds 2032 /* Read into buffer. On Win32, need recv() not read(). session->fd also holds
2534 * the file descriptor, but it sometimes differs from the 'source' parameter. 2033 * the file descriptor, but it sometimes differs from the 'source' parameter.
2535 */ 2034 */
2536 n = recv(session->fd, 2035 n = recv(session->fd,
2537 session->rxbuf + session->rxoff, 2036 session->rxbuf + session->rxoff,
2538 session->rxsize - session->rxoff - 1, 0); 2037 session->rxsize - session->rxoff - 1, 0);
2539 2038
2540 if (n < 0 && errno == EAGAIN) { 2039 if (n < 0 && errno == EAGAIN) {
2541 return; 2040 return;
2542 } else if (n < 0) { 2041 } else if (n < 0) {
2543 purple_debug_error("msim", "msim_input_cb: read error, ret=%d, " 2042 purple_debug_error("msim", "msim_input_cb: read error, ret=%d, "
2544 "error=%s, source=%d, fd=%d (%X))\n", 2043 "error=%s, source=%d, fd=%d (%X))\n",
2545 n, g_strerror(errno), source, session->fd, session->fd); 2044 n, g_strerror(errno), source, session->fd, session->fd);
2546 purple_connection_error_reason (gc, 2045 purple_connection_error_reason (gc,
2547 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 2046 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2548 _("Read error")); 2047 _("Read error"));
2549 return; 2048 return;
2550 } else if (n == 0) { 2049 } else if (n == 0) {
2551 purple_debug_info("msim", "msim_input_cb: server disconnected\n"); 2050 purple_debug_info("msim", "msim_input_cb: server disconnected\n");
2572 #ifdef MSIM_CHECK_EMBEDDED_NULLS 2071 #ifdef MSIM_CHECK_EMBEDDED_NULLS
2573 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */ 2072 /* Check for embedded NULs. I don't handle them, and they shouldn't occur. */
2574 if (strlen(session->rxbuf + session->rxoff) != n) { 2073 if (strlen(session->rxbuf + session->rxoff) != n) {
2575 /* Occurs after login, but it is not a null byte. */ 2074 /* Occurs after login, but it is not a null byte. */
2576 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes" 2075 purple_debug_info("msim", "msim_input_cb: strlen=%d, but read %d bytes"
2577 "--null byte encountered?\n", 2076 "--null byte encountered?\n",
2578 strlen(session->rxbuf + session->rxoff), n); 2077 strlen(session->rxbuf + session->rxoff), n);
2579 /*purple_connection_error_reason (gc, 2078 /*purple_connection_error_reason (gc,
2580 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 2079 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2581 "Invalid message - null byte on input"); */ 2080 "Invalid message - null byte on input"); */
2582 return; 2081 return;
2596 2095
2597 #ifdef MSIM_DEBUG_RXBUF 2096 #ifdef MSIM_DEBUG_RXBUF
2598 purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf); 2097 purple_debug_info("msim", "in loop: buf=<%s>\n", session->rxbuf);
2599 #endif 2098 #endif
2600 *end = 0; 2099 *end = 0;
2601 msg = msim_parse(g_strdup(session->rxbuf)); 2100 msg = msim_parse(session->rxbuf);
2602 if (!msg) { 2101 if (!msg) {
2603 purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n"); 2102 purple_debug_info("msim", "msim_input_cb: couldn't parse rxbuf\n");
2604 purple_connection_error_reason (gc, 2103 purple_connection_error_reason (gc,
2605 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 2104 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2606 _("Unparseable message")); 2105 _("Unparseable message"));
2614 msim_msg_free(msg); 2113 msim_msg_free(msg);
2615 } 2114 }
2616 2115
2617 /* Move remaining part of buffer to beginning. */ 2116 /* Move remaining part of buffer to beginning. */
2618 session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING); 2117 session->rxoff -= strlen(session->rxbuf) + strlen(MSIM_FINAL_STRING);
2619 memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING), 2118 memmove(session->rxbuf, end + strlen(MSIM_FINAL_STRING),
2620 session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf)); 2119 session->rxsize - (end + strlen(MSIM_FINAL_STRING) - session->rxbuf));
2621 2120
2622 /* Clear end of buffer 2121 /* Clear end of buffer
2623 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf)); 2122 * memset(end, 0, MSIM_READ_BUF_SIZE - (end - session->rxbuf));
2624 */ 2123 */
2625 } 2124 }
2626 }
2627
2628 /* Setup a callback, to be called when a reply is received with the returned rid.
2629 *
2630 * @param cb The callback, an MSIM_USER_LOOKUP_CB.
2631 * @param data Arbitrary user data to be passed to callback (probably an MsimMessage *).
2632 *
2633 * @return The request/reply ID, used to link replies with requests, or -1.
2634 * Put the rid in your request, 'rid' field.
2635 *
2636 * TODO: Make more generic and more specific:
2637 * 1) MSIM_USER_LOOKUP_CB - make it for PERSIST_REPLY, not just user lookup
2638 * 2) data - make it an MsimMessage?
2639 */
2640 guint
2641 msim_new_reply_callback(MsimSession *session, MSIM_USER_LOOKUP_CB cb,
2642 gpointer data)
2643 {
2644 guint rid;
2645
2646 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
2647
2648 rid = session->next_rid++;
2649
2650 g_hash_table_insert(session->user_lookup_cb, GUINT_TO_POINTER(rid), cb);
2651 g_hash_table_insert(session->user_lookup_cb_data, GUINT_TO_POINTER(rid), data);
2652
2653 return rid;
2654 } 2125 }
2655 2126
2656 /** 2127 /**
2657 * Callback when connected. Sets up input handlers. 2128 * Callback when connected. Sets up input handlers.
2658 * 2129 *
2659 * @param data A PurpleConnection pointer. 2130 * @param data A PurpleConnection pointer.
2660 * @param source File descriptor. 2131 * @param source File descriptor.
2661 * @param error_message 2132 * @param error_message
2662 */ 2133 */
2663 static void 2134 static void
2664 msim_connect_cb(gpointer data, gint source, const gchar *error_message) 2135 msim_connect_cb(gpointer data, gint source, const gchar *error_message)
2665 { 2136 {
2666 PurpleConnection *gc; 2137 PurpleConnection *gc;
2667 MsimSession *session; 2138 MsimSession *session;
2668 2139
2672 session = (MsimSession *)gc->proto_data; 2143 session = (MsimSession *)gc->proto_data;
2673 2144
2674 if (source < 0) { 2145 if (source < 0) {
2675 purple_connection_error_reason (gc, 2146 purple_connection_error_reason (gc,
2676 PURPLE_CONNECTION_ERROR_NETWORK_ERROR, 2147 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2677 g_strdup_printf(_("Couldn't connect to host: %s (%d)"), 2148 g_strdup_printf(_("Couldn't connect to host: %s (%d)"),
2678 error_message ? error_message : "no message given", 2149 error_message ? error_message : "no message given",
2679 source)); 2150 source));
2680 return; 2151 return;
2681 } 2152 }
2682 2153
2683 session->fd = source; 2154 session->fd = source;
2684 2155
2685 gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc); 2156 gc->inpa = purple_input_add(source, PURPLE_INPUT_READ, msim_input_cb, gc);
2686 } 2157 }
2687 2158
2688 2159 /**
2689 /** 2160 * Start logging in to the MSIM servers.
2161 *
2162 * @param acct Account information to use to login.
2163 */
2164 static void
2165 msim_login(PurpleAccount *acct)
2166 {
2167 PurpleConnection *gc;
2168 const gchar *host;
2169 int port;
2170
2171 g_return_if_fail(acct != NULL);
2172 g_return_if_fail(acct->username != NULL);
2173
2174 purple_debug_info("msim", "logging in %s\n", acct->username);
2175
2176 gc = purple_account_get_connection(acct);
2177 gc->proto_data = msim_session_new(acct);
2178 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_NO_URLDESC;
2179
2180 /*
2181 * Lets wipe out our local list of blocked buddies. We'll get a
2182 * list of all blocked buddies from the server, and we shouldn't
2183 * have stuff in the local list that isn't on the server list.
2184 */
2185 while (acct->deny != NULL)
2186 purple_privacy_deny_remove(acct, acct->deny->data, TRUE);
2187
2188 /* 1. connect to server */
2189 purple_connection_update_progress(gc, _("Connecting"),
2190 0, /* which connection step this is */
2191 4); /* total number of steps */
2192
2193 host = purple_account_get_string(acct, "server", MSIM_SERVER);
2194 port = purple_account_get_int(acct, "port", MSIM_PORT);
2195
2196 /* From purple.sf.net/api:
2197 * """Note that this function name can be misleading--although it is called
2198 * "proxy connect," it is used for establishing any outgoing TCP connection,
2199 * whether through a proxy or not.""" */
2200
2201 /* Calls msim_connect_cb when connected. */
2202 if (!purple_proxy_connect(gc, acct, host, port, msim_connect_cb, gc)) {
2203 /* TODO: try other ports if in auto mode, then save
2204 * working port and try that first next time. */
2205 purple_connection_error_reason (gc,
2206 PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2207 _("Couldn't create socket"));
2208 return;
2209 }
2210 }
2211
2212 /**
2690 * Close the connection. 2213 * Close the connection.
2691 * 2214 *
2692 * @param gc The connection. 2215 * @param gc The connection.
2693 */ 2216 */
2694 void 2217 static void
2695 msim_close(PurpleConnection *gc) 2218 msim_close(PurpleConnection *gc)
2696 { 2219 {
2697 MsimSession *session; 2220 MsimSession *session;
2698 2221
2699 if (gc == NULL) { 2222 if (gc == NULL) {
2711 } 2234 }
2712 2235
2713 if (session->gc->inpa) { 2236 if (session->gc->inpa) {
2714 purple_input_remove(session->gc->inpa); 2237 purple_input_remove(session->gc->inpa);
2715 } 2238 }
2239 if (session->fd >= 0) {
2240 close(session->fd);
2241 session->fd = -1;
2242 }
2716 2243
2717 msim_session_destroy(session); 2244 msim_session_destroy(session);
2718 } 2245 }
2719 2246
2720 2247 /**
2721 /** 2248 * Schedule an IM to be sent once the user ID is looked up.
2722 * Obtain the status text for a buddy. 2249 *
2723 * 2250 * @param gc Connection.
2724 * @param buddy The buddy to obtain status text for. 2251 * @param who A user id, email, or username to send the message to.
2725 * 2252 * @param message Instant message text to send.
2726 * @return Status text, or NULL if error. Caller g_free()'s. 2253 * @param flags Flags.
2727 * 2254 *
2728 */ 2255 * @return 1 if successful or postponed, -1 if failed
2729 char * 2256 *
2730 msim_status_text(PurpleBuddy *buddy) 2257 * Allows sending to a user by username, email address, or userid. If
2258 * a username or email address is given, the userid must be looked up.
2259 * This function does that by calling msim_postprocess_outgoing().
2260 */
2261 static int
2262 msim_send_im(PurpleConnection *gc, const gchar *who, const gchar *message,
2263 PurpleMessageFlags flags)
2264 {
2265 MsimSession *session;
2266 gchar *message_msim;
2267 int rc;
2268
2269 g_return_val_if_fail(gc != NULL, -1);
2270 g_return_val_if_fail(who != NULL, -1);
2271 g_return_val_if_fail(message != NULL, -1);
2272
2273 /* 'flags' has many options, not used here. */
2274
2275 session = (MsimSession *)gc->proto_data;
2276
2277 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
2278
2279 message_msim = html_to_msim_markup(session, message);
2280
2281 if (msim_send_bm(session, who, message_msim, MSIM_BM_INSTANT)) {
2282 /* Return 1 to have Purple show this IM as being sent, 0 to not. I always
2283 * return 1 even if the message could not be sent, since I don't know if
2284 * it has failed yet--because the IM is only sent after the userid is
2285 * retrieved from the server (which happens after this function returns).
2286 * If an error does occur, it should be logged to the IM window.
2287 */
2288 rc = 1;
2289 } else {
2290 rc = -1;
2291 }
2292
2293 g_free(message_msim);
2294
2295 return rc;
2296 }
2297
2298 /**
2299 * Handle when our user starts or stops typing to another user.
2300 *
2301 * @param gc
2302 * @param name The buddy name to which our user is typing to
2303 * @param state PURPLE_TYPING, PURPLE_TYPED, PURPLE_NOT_TYPING
2304 *
2305 * @return 0
2306 */
2307 static unsigned int
2308 msim_send_typing(PurpleConnection *gc, const gchar *name,
2309 PurpleTypingState state)
2310 {
2311 const gchar *typing_str;
2312 MsimSession *session;
2313
2314 g_return_val_if_fail(gc != NULL, 0);
2315 g_return_val_if_fail(name != NULL, 0);
2316
2317 session = (MsimSession *)gc->proto_data;
2318
2319 g_return_val_if_fail(MSIM_SESSION_VALID(session), 0);
2320
2321 switch (state) {
2322 case PURPLE_TYPING:
2323 typing_str = "%typing%";
2324 break;
2325
2326 case PURPLE_TYPED:
2327 case PURPLE_NOT_TYPING:
2328 default:
2329 typing_str = "%stoptyping%";
2330 break;
2331 }
2332
2333 purple_debug_info("msim", "msim_send_typing(%s): %d (%s)\n", name, state, typing_str);
2334 msim_send_bm(session, name, typing_str, MSIM_BM_ACTION);
2335 return 0;
2336 }
2337
2338 /**
2339 * Callback for msim_get_info(), for when user info is received.
2340 */
2341 static void
2342 msim_get_info_cb(MsimSession *session, const MsimMessage *user_info_msg,
2343 gpointer data)
2344 {
2345 MsimMessage *msg;
2346 gchar *username;
2347 PurpleNotifyUserInfo *user_info;
2348 MsimUser *user;
2349
2350 g_return_if_fail(MSIM_SESSION_VALID(session));
2351
2352 /* Get user{name,id} from msim_get_info, passed as an MsimMessage for
2353 orthogonality. */
2354 msg = (MsimMessage *)data;
2355 g_return_if_fail(msg != NULL);
2356
2357 username = msim_msg_get_string(msg, "user");
2358 if (!username) {
2359 purple_debug_info("msim", "msim_get_info_cb: no 'user' in msg\n");
2360 return;
2361 }
2362
2363 msim_msg_free(msg);
2364 purple_debug_info("msim", "msim_get_info_cb: got for user: %s\n", username);
2365
2366 user = msim_find_user(session, username);
2367
2368 if (!user) {
2369 /* User isn't on blist, create a temporary user to store info. */
2370 user = g_new0(MsimUser, 1);
2371 user->temporary_user = TRUE;
2372 }
2373
2374 /* Update user structure with new information */
2375 msim_store_user_info(session, user_info_msg, user);
2376
2377 user_info = purple_notify_user_info_new();
2378
2379 /* Append data from MsimUser to PurpleNotifyUserInfo for display, full */
2380 msim_append_user_info(session, user_info, user, TRUE);
2381
2382 purple_notify_userinfo(session->gc, username, user_info, NULL, NULL);
2383 purple_debug_info("msim", "msim_get_info_cb: username=%s\n", username);
2384
2385 purple_notify_user_info_destroy(user_info);
2386
2387 if (user->temporary_user) {
2388 g_free(user->client_info);
2389 g_free(user->gender);
2390 g_free(user->location);
2391 g_free(user->headline);
2392 g_free(user->display_name);
2393 g_free(user->username);
2394 g_free(user->image_url);
2395 g_free(user);
2396 }
2397 g_free(username);
2398 }
2399
2400 /**
2401 * Retrieve a user's profile.
2402 * @param username Username, user ID, or email address to lookup.
2403 */
2404 static void
2405 msim_get_info(PurpleConnection *gc, const gchar *username)
2731 { 2406 {
2732 MsimSession *session; 2407 MsimSession *session;
2733 MsimUser *user; 2408 MsimUser *user;
2734 const gchar *display_name, *headline; 2409 gchar *user_to_lookup;
2735 2410 MsimMessage *user_msg;
2736 g_return_val_if_fail(buddy != NULL, NULL); 2411
2737 2412 g_return_if_fail(gc != NULL);
2738 user = msim_get_user_from_buddy(buddy); 2413 g_return_if_fail(username != NULL);
2739 2414
2740 session = (MsimSession *)buddy->account->gc->proto_data; 2415 session = (MsimSession *)gc->proto_data;
2741 g_return_val_if_fail(MSIM_SESSION_VALID(session), NULL); 2416
2742 2417 g_return_if_fail(MSIM_SESSION_VALID(session));
2743 display_name = headline = NULL; 2418
2744 2419 /* Obtain uid of buddy. */
2745 /* Retrieve display name and/or headline, depending on user preference. */ 2420 user = msim_find_user(session, username);
2746 if (purple_account_get_bool(session->account, "show_headline", TRUE)) { 2421
2747 headline = user->headline; 2422 /* If is on buddy list, lookup by uid since it is faster. */
2748 } 2423 if (user && user->id) {
2749 2424 user_to_lookup = g_strdup_printf("%d", user->id);
2750 if (purple_account_get_bool(session->account, "show_display_name", FALSE)) { 2425 } else {
2751 display_name = user->display_name; 2426 /* Looking up buddy not on blist. Lookup by whatever user entered. */
2752 } 2427 user_to_lookup = g_strdup(username);
2753 2428 }
2754 /* Return appropriate combination of display name and/or headline, or neither. */ 2429
2755 2430 /* Pass the username to msim_get_info_cb(), because since we lookup
2756 if (display_name && headline) { 2431 * by userid, the userinfo message will only contain the uid (not
2757 return g_strconcat(display_name, " ", headline, NULL); 2432 * the username) but it would be useful to display the username too.
2758 } else if (display_name) { 2433 */
2759 return g_strdup(display_name); 2434 user_msg = msim_msg_new(
2760 } else if (headline) { 2435 "user", MSIM_TYPE_STRING, g_strdup(username),
2761 return g_strdup(headline); 2436 NULL);
2762 } 2437 purple_debug_info("msim", "msim_get_info, setting up lookup, user=%s\n", username);
2763 2438
2764 return NULL; 2439 msim_lookup_user(session, user_to_lookup, msim_get_info_cb, user_msg);
2765 } 2440
2766 2441 g_free(user_to_lookup);
2767 /** 2442 }
2768 * Obtain the tooltip text for a buddy. 2443
2769 * 2444 /**
2770 * @param buddy Buddy to obtain tooltip text on. 2445 * Set status using an MSIM_STATUS_CODE_* value.
2771 * @param user_info Variable modified to have the tooltip text. 2446 * @param status_code An MSIM_STATUS_CODE_* value.
2772 * @param full TRUE if should obtain full tooltip text. 2447 * @param statstring Status string, must be a dynamic string (will be freed by msim_send).
2773 * 2448 */
2774 */ 2449 static void
2775 void 2450 msim_set_status_code(MsimSession *session, guint status_code, gchar *statstring)
2776 msim_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, 2451 {
2777 gboolean full) 2452 g_return_if_fail(MSIM_SESSION_VALID(session));
2778 { 2453 g_return_if_fail(statstring != NULL);
2779 MsimUser *user; 2454
2780 2455 purple_debug_info("msim", "msim_set_status_code: going to set status to code=%d,str=%s\n",
2781 g_return_if_fail(buddy != NULL); 2456 status_code, statstring);
2782 g_return_if_fail(user_info != NULL); 2457
2783 2458 if (!msim_send(session,
2784 user = msim_get_user_from_buddy(buddy); 2459 "status", MSIM_TYPE_INTEGER, status_code,
2785 2460 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2786 if (PURPLE_BUDDY_IS_ONLINE(buddy)) { 2461 "statstring", MSIM_TYPE_STRING, statstring,
2787 MsimSession *session; 2462 "locstring", MSIM_TYPE_STRING, g_strdup(""),
2788 2463 NULL))
2789 session = (MsimSession *)buddy->account->gc->proto_data; 2464 {
2790 2465 purple_debug_info("msim", "msim_set_status: failed to set status\n");
2791 g_return_if_fail(MSIM_SESSION_VALID(session)); 2466 }
2792 2467 }
2793 /* TODO: if (full), do something different? */ 2468
2794 2469 /**
2795 /* TODO: request information? have to figure out how to do 2470 * Set your status - callback for when user manually sets it.
2796 * the asynchronous lookup like oscar does (tooltip shows 2471 */
2797 * 'retrieving...' if not yet available, then changes when it is). 2472 static void
2798 * 2473 msim_set_status(PurpleAccount *account, PurpleStatus *status)
2799 * Right now, only show what we have on hand. 2474 {
2475 PurpleStatusType *type;
2476 PurplePresence *pres;
2477 MsimSession *session;
2478 guint status_code;
2479 const gchar *message;
2480 gchar *stripped;
2481 gchar *unrecognized_msg;
2482
2483 session = (MsimSession *)account->gc->proto_data;
2484
2485 g_return_if_fail(MSIM_SESSION_VALID(session));
2486
2487 type = purple_status_get_type(status);
2488 pres = purple_status_get_presence(status);
2489
2490 switch (purple_status_type_get_primitive(type)) {
2491 case PURPLE_STATUS_AVAILABLE:
2492 purple_debug_info("msim", "msim_set_status: available (%d->%d)\n", PURPLE_STATUS_AVAILABLE,
2493 MSIM_STATUS_CODE_ONLINE);
2494 status_code = MSIM_STATUS_CODE_ONLINE;
2495 break;
2496
2497 case PURPLE_STATUS_INVISIBLE:
2498 purple_debug_info("msim", "msim_set_status: invisible (%d->%d)\n", PURPLE_STATUS_INVISIBLE,
2499 MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN);
2500 status_code = MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN;
2501 break;
2502
2503 case PURPLE_STATUS_AWAY:
2504 purple_debug_info("msim", "msim_set_status: away (%d->%d)\n", PURPLE_STATUS_AWAY,
2505 MSIM_STATUS_CODE_AWAY);
2506 status_code = MSIM_STATUS_CODE_AWAY;
2507 break;
2508
2509 default:
2510 purple_debug_info("msim", "msim_set_status: unknown "
2511 "status interpreting as online");
2512 status_code = MSIM_STATUS_CODE_ONLINE;
2513
2514 unrecognized_msg = g_strdup_printf("msim_set_status, unrecognized status type: %d\n",
2515 purple_status_type_get_primitive(type));
2516 msim_unrecognized(session, NULL, unrecognized_msg);
2517 g_free(unrecognized_msg);
2518
2519 break;
2520 }
2521
2522 message = purple_status_get_attr_string(status, "message");
2523
2524 /* Status strings are plain text. */
2525 if (message != NULL)
2526 stripped = purple_markup_strip_html(message);
2527 else
2528 stripped = g_strdup("");
2529
2530 msim_set_status_code(session, status_code, stripped);
2531
2532 /* If we should be idle, set that status. Time is irrelevant here. */
2533 if (purple_presence_is_idle(pres) && status_code != MSIM_STATUS_CODE_OFFLINE_OR_HIDDEN)
2534 msim_set_idle(account->gc, 1);
2535 }
2536
2537 /**
2538 * Go idle.
2539 */
2540 static void
2541 msim_set_idle(PurpleConnection *gc, int time)
2542 {
2543 MsimSession *session;
2544 PurpleStatus *status;
2545
2546 g_return_if_fail(gc != NULL);
2547
2548 session = (MsimSession *)gc->proto_data;
2549
2550 g_return_if_fail(MSIM_SESSION_VALID(session));
2551
2552 status = purple_account_get_active_status(session->account);
2553
2554 if (time == 0) {
2555 /* Going back from idle. In msim, idle is mutually exclusive
2556 * from the other states (you can only be away or idle, but not
2557 * both, for example), so by going non-idle I go back to what
2558 * libpurple says I should be.
2800 */ 2559 */
2801 2560 msim_set_status(session->account, status);
2802 /* Show abbreviated user info. */ 2561 } else {
2803 msim_append_user_info(session, user_info, user, FALSE); 2562 const gchar *message;
2804 } 2563 gchar *stripped;
2805 } 2564
2806 2565 /* Set the idle message to the status message from the real
2807 /** Add contact from server to buddy list, after looking up username. 2566 * current status.
2808 * Callback from msim_add_contact_from_server(). 2567 */
2809 * 2568 message = purple_status_get_attr_string(status, "message");
2810 * @param data An MsimMessage * of the contact information. Will be freed. 2569 if (message != NULL)
2570 stripped = purple_markup_strip_html(message);
2571 else
2572 stripped = g_strdup("");
2573
2574 /* msim doesn't support idle time, so just go idle */
2575 msim_set_status_code(session, MSIM_STATUS_CODE_IDLE, stripped);
2576 }
2577 }
2578
2579 /**
2580 * @return TRUE if everything was ok, FALSE if something went awry.
2581 */
2582 static gboolean
2583 msim_update_blocklist_for_buddy(MsimSession *session, const char *name, gboolean allow, gboolean block)
2584 {
2585 MsimMessage *msg;
2586 GList *list;
2587
2588 list = NULL;
2589 list = g_list_prepend(list, allow ? "a+" : "a-");
2590 list = g_list_prepend(list, "<uid>");
2591 list = g_list_prepend(list, block ? "b+" : "b-");
2592 list = g_list_prepend(list, "<uid>");
2593 list = g_list_reverse(list);
2594
2595 msg = msim_msg_new(
2596 "blocklist", MSIM_TYPE_BOOLEAN, TRUE,
2597 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2598 /* TODO: MsimMessage lists. Currently <uid> isn't replaced in lists. */
2599 /* "idlist", MSIM_TYPE_STRING, g_strdup("a-|<uid>|b-|<uid>"), */
2600 "idlist", MSIM_TYPE_LIST, list,
2601 NULL);
2602
2603 if (!msim_postprocess_outgoing(session, msg, name, "idlist", NULL)) {
2604 purple_debug_error("myspace",
2605 "blocklist command failed for %s, allow=%d, block=%d\n",
2606 name, allow, block);
2607 msim_msg_free(msg);
2608 return FALSE;
2609 }
2610
2611 msim_msg_free(msg);
2612
2613 return TRUE;
2614 }
2615
2616 /**
2617 * Add a buddy to user's buddy list.
2811 */ 2618 */
2812 static void 2619 static void
2813 msim_add_contact_from_server_cb(MsimSession *session, MsimMessage *user_lookup_info, gpointer data) 2620 msim_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2814 { 2621 {
2815 MsimMessage *contact_info, *user_lookup_info_body; 2622 MsimSession *session;
2816 PurpleGroup *group; 2623 MsimMessage *msg;
2817 PurpleBuddy *buddy; 2624 MsimMessage *msg_persist;
2818 MsimUser *user; 2625 MsimMessage *body;
2819 gchar *username, *group_name; 2626
2820 guint uid; 2627 session = (MsimSession *)gc->proto_data;
2821 2628 purple_debug_info("msim", "msim_add_buddy: want to add %s to %s\n",
2822 contact_info = (MsimMessage *)data; 2629 buddy->name, (group && group->name) ? group->name : "(no group)");
2823 purple_debug_info("msim_add_contact_from_server_cb", "contact_info addr=%p\n", contact_info); 2630
2824 uid = msim_msg_get_integer(contact_info, "ContactID"); 2631 msg = msim_msg_new(
2825 2632 "addbuddy", MSIM_TYPE_BOOLEAN, TRUE,
2826 if (!user_lookup_info) { 2633 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2827 username = g_strdup(msim_uid2username_from_blist(session->account, uid)); 2634 /* "newprofileid" will be inserted here with uid. */
2828 g_return_if_fail(username != NULL); 2635 "reason", MSIM_TYPE_STRING, g_strdup(""),
2829 } else { 2636 NULL);
2830 user_lookup_info_body = msim_msg_get_dictionary(user_lookup_info, "body"); 2637
2831 username = msim_msg_get_string(user_lookup_info_body, "UserName"); 2638 if (!msim_postprocess_outgoing(session, msg, buddy->name, "newprofileid", "reason")) {
2832 msim_msg_free(user_lookup_info_body); 2639 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("'addbuddy' command failed."));
2833 g_return_if_fail(username != NULL); 2640 msim_msg_free(msg);
2834 } 2641 return;
2835 2642 }
2836 purple_debug_info("msim_add_contact_from_server_cb", 2643 msim_msg_free(msg);
2837 "*** about to add/update username=%s\n", username); 2644
2838 2645 /* TODO: if addbuddy fails ('error' message is returned), delete added buddy from
2839 /* 1. Creates a new group, or gets existing group if it exists (or so 2646 * buddy list since Purple adds it locally. */
2840 * the documentation claims). */ 2647
2841 group_name = msim_msg_get_string(contact_info, "GroupName"); 2648 body = msim_msg_new(
2842 if (!group_name || (*group_name == '\0')) { 2649 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"),
2843 g_free(group_name); 2650 "GroupName", MSIM_TYPE_STRING, g_strdup(group->name),
2844 group_name = g_strdup(_("IM Friends")); 2651 "Position", MSIM_TYPE_INTEGER, 1000,
2845 purple_debug_info("myspace", "No GroupName specified, defaulting to '%s'.\n", group_name); 2652 "Visibility", MSIM_TYPE_INTEGER, 1,
2846 } 2653 "NickName", MSIM_TYPE_STRING, g_strdup(""),
2847 group = purple_find_group(group_name); 2654 "NameSelect", MSIM_TYPE_INTEGER, 0,
2848 if (!group) { 2655 NULL);
2849 group = purple_group_new(group_name); 2656
2850 /* Add group to beginning. See #2752. */ 2657 /* TODO: Update blocklist. */
2851 purple_blist_add_group(group, NULL); 2658
2852 } 2659 msg_persist = msim_msg_new(
2853 g_free(group_name); 2660 "persist", MSIM_TYPE_INTEGER, 1,
2854 2661 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2855 /* 2. Get or create buddy */ 2662 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT,
2856 buddy = purple_find_buddy(session->account, username); 2663 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN,
2857 if (!buddy) { 2664 "uid", MSIM_TYPE_INTEGER, session->userid,
2858 purple_debug_info("msim_add_contact_from_server_cb", 2665 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID,
2859 "creating new buddy: %s\n", username); 2666 /* TODO: Use msim_new_reply_callback to get rid. */
2860 buddy = purple_buddy_new(session->account, username, NULL); 2667 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
2861 } 2668 "body", MSIM_TYPE_DICTIONARY, body,
2862 2669 NULL);
2863 /* TODO: use 'Position' in contact_info to take into account where buddy is */ 2670
2864 purple_blist_add_buddy(buddy, NULL, group, NULL /* insertion point */); 2671 if (!msim_postprocess_outgoing(session, msg_persist, buddy->name, "body", NULL))
2865 2672 {
2866 /* 3. Update buddy information */ 2673 purple_notify_error(NULL, NULL, _("Failed to add buddy"), _("persist command failed"));
2867 user = msim_get_user_from_buddy(buddy); 2674 msim_msg_free(msg_persist);
2868 2675 return;
2869 user->id = uid; 2676 }
2870 /* Keep track of the user ID across sessions */ 2677 msim_msg_free(msg_persist);
2871 purple_blist_node_set_int(&buddy->node, "UserID", uid); 2678
2872 2679 /* Add to allow list, remove from block list */
2873 /* Stores a few fields in the MsimUser, relevant to the buddy itself. 2680 msim_update_blocklist_for_buddy(session, buddy->name, TRUE, FALSE);
2874 * AvatarURL, Headline, ContactID. */ 2681 }
2875 msim_store_user_info(session, contact_info, NULL); 2682
2876 2683 /**
2877 /* TODO: other fields, store in 'user' */ 2684 * Remove a buddy from the user's buddy list.
2878 msim_msg_free(contact_info); 2685 */
2879
2880 g_free(username);
2881 }
2882
2883 /** Add first ContactID in contact_info to buddy's list. Used to add
2884 * server-side buddies to client-side list.
2885 *
2886 * @return TRUE if added.
2887 * */
2888 static gboolean
2889 msim_add_contact_from_server(MsimSession *session, MsimMessage *contact_info)
2890 {
2891 guint uid;
2892 const gchar *username;
2893
2894 uid = msim_msg_get_integer(contact_info, "ContactID");
2895 g_return_val_if_fail(uid != 0, FALSE);
2896
2897 /* Lookup the username, since NickName and IMName is unreliable */
2898 username = msim_uid2username_from_blist(session->account, uid);
2899 if (!username) {
2900 gchar *uid_str;
2901
2902 uid_str = g_strdup_printf("%d", uid);
2903 purple_debug_info("msim_add_contact_from_server",
2904 "contact_info addr=%p\n", contact_info);
2905 msim_lookup_user(session, uid_str, msim_add_contact_from_server_cb, (gpointer)msim_msg_clone(contact_info));
2906 g_free(uid_str);
2907 } else {
2908 msim_add_contact_from_server_cb(session, NULL, (gpointer)msim_msg_clone(contact_info));
2909 }
2910
2911 /* Say that the contact was added, even if we're still looking up
2912 * their username. */
2913 return TRUE;
2914 }
2915
2916 /** Called when contact list is received from server. */
2917 static void 2686 static void
2918 msim_got_contact_list(MsimSession *session, MsimMessage *reply, gpointer user_data) 2687 msim_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
2919 { 2688 {
2920 MsimMessage *body, *body_node; 2689 MsimSession *session;
2921 gchar *msg; 2690 MsimMessage *delbuddy_msg;
2922 guint buddy_count; 2691 MsimMessage *persist_msg;
2923 2692
2924 msim_msg_dump("msim_got_contact_list: reply=%s", reply); 2693 session = (MsimSession *)gc->proto_data;
2925 2694
2926 body = msim_msg_get_dictionary(reply, "body"); 2695 delbuddy_msg = msim_msg_new(
2927 if (!body) { 2696 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE,
2928 /* No friends. Not an error. */ 2697 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2698 /* 'delprofileid' with uid will be inserted here. */
2699 NULL);
2700
2701 if (!msim_postprocess_outgoing(session, delbuddy_msg, buddy->name, "delprofileid", NULL)) {
2702 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("'delbuddy' command failed"));
2703 msim_msg_free(delbuddy_msg);
2929 return; 2704 return;
2930 } 2705 }
2931 2706 msim_msg_free(delbuddy_msg);
2932 buddy_count = 0; 2707
2933 2708 persist_msg = msim_msg_new(
2934 for (body_node = body;
2935 body_node != NULL;
2936 body_node = msim_msg_get_next_element_node(body_node))
2937 {
2938 MsimMessageElement *elem;
2939
2940 elem = (MsimMessageElement *)body_node->data;
2941
2942 if (g_str_equal(elem->name, "ContactID"))
2943 {
2944 /* Will look for first contact in body_node */
2945 if (msim_add_contact_from_server(session, body_node)) {
2946 ++buddy_count;
2947 }
2948 }
2949 }
2950
2951 switch (GPOINTER_TO_UINT(user_data)) {
2952 case MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS:
2953 msg = g_strdup_printf(ngettext("%d buddy was added or updated from the server (including buddies already on the server-side list)",
2954 "%d buddies were added or updated from the server (including buddies already on the server-side list)",
2955 buddy_count),
2956 buddy_count);
2957 purple_notify_info(session->account, _("Add contacts from server"), msg, NULL);
2958 g_free(msg);
2959 break;
2960
2961 case MSIM_CONTACT_LIST_IMPORT_TOP_FRIENDS:
2962 /* TODO */
2963 break;
2964
2965 case MSIM_CONTACT_LIST_INITIAL_FRIENDS:
2966 /* Nothing */
2967 break;
2968 }
2969
2970 msim_msg_free(body);
2971 }
2972
2973 /* Get contact list, calling msim_got_contact_list() with what_to_do_after as user_data gpointer. */
2974 static gboolean
2975 msim_get_contact_list(MsimSession *session, int what_to_do_after)
2976 {
2977 return msim_send(session,
2978 "persist", MSIM_TYPE_INTEGER, 1, 2709 "persist", MSIM_TYPE_INTEGER, 1,
2979 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, 2710 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2980 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_GET, 2711 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE,
2981 "dsn", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_DSN, 2712 "dsn", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_DSN,
2982 "lid", MSIM_TYPE_INTEGER, MG_LIST_ALL_CONTACTS_LID, 2713 "lid", MSIM_TYPE_INTEGER, MD_DELETE_BUDDY_LID,
2983 "uid", MSIM_TYPE_INTEGER, session->userid, 2714 "uid", MSIM_TYPE_INTEGER, session->userid,
2984 "rid", MSIM_TYPE_INTEGER, 2715 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
2985 msim_new_reply_callback(session, msim_got_contact_list, GUINT_TO_POINTER(what_to_do_after)), 2716 /* <uid> will be replaced by postprocessing */
2986 "body", MSIM_TYPE_STRING, g_strdup(""), 2717 "body", MSIM_TYPE_STRING, g_strdup("ContactID=<uid>"),
2987 NULL); 2718 NULL);
2988 } 2719
2989 2720 if (!msim_postprocess_outgoing(session, persist_msg, buddy->name, "body", NULL)) {
2990 2721 purple_notify_error(NULL, NULL, _("Failed to remove buddy"), _("persist command failed"));
2991 /** Called when friends have been imported to buddy list on server. */ 2722 msim_msg_free(persist_msg);
2992 static void
2993 msim_import_friends_cb(MsimSession *session, MsimMessage *reply, gpointer user_data)
2994 {
2995 MsimMessage *body;
2996 gchar *completed;
2997 msim_msg_dump("msim_import_friends_cb=%s", reply);
2998
2999 /* Check if the friends were imported successfully. */
3000 body = msim_msg_get_dictionary(reply, "body");
3001 g_return_if_fail(body != NULL);
3002 completed = msim_msg_get_string(body, "Completed");
3003 g_return_if_fail(body != NULL);
3004 msim_msg_free(body);
3005 if (!g_str_equal(completed, "True"))
3006 {
3007 purple_debug_info("msim_import_friends_cb",
3008 "failed to import friends: %s", completed);
3009 purple_notify_error(session->account, _("Add friends from MySpace.com"),
3010 _("Importing friends failed"), NULL);
3011 g_free(completed);
3012 return; 2723 return;
3013 } 2724 }
3014 g_free(completed); 2725 msim_msg_free(persist_msg);
3015 2726
3016 purple_debug_info("msim_import_friends_cb", 2727 /*
3017 "added friends to server-side buddy list, requesting new contacts from server"); 2728 * Remove from our approve list and from our block list (this
3018 2729 * doesn't seem like it would be necessary, but the official client
3019 msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS); 2730 * does it)
3020 2731 */
3021 /* TODO: show, X friends have been added */ 2732 if (!msim_update_blocklist_for_buddy(session, buddy->name, FALSE, FALSE)) {
3022 } 2733 purple_notify_error(NULL, NULL,
3023 2734 _("Failed to remove buddy"), _("blocklist command failed"));
3024 /** Import friends from myspace.com. */ 2735 return;
3025 static void msim_import_friends(PurplePluginAction *action) 2736 }
3026 { 2737 if (buddy->proto_data) {
3027 PurpleConnection *gc; 2738 msim_user_free(buddy->proto_data);
2739 buddy->proto_data = NULL;
2740 }
2741 }
2742
2743 /**
2744 * Remove a buddy from the user's buddy list and add them to the block list.
2745 */
2746 static void
2747 msim_add_deny(PurpleConnection *gc, const char *name)
2748 {
3028 MsimSession *session; 2749 MsimSession *session;
3029 gchar *group_name; 2750 MsimMessage *msg, *body;
3030 2751
3031 gc = (PurpleConnection *)action->context;
3032 session = (MsimSession *)gc->proto_data; 2752 session = (MsimSession *)gc->proto_data;
3033 2753
3034 group_name = "MySpace Friends"; 2754 /* Remove from buddy list */
3035 2755 msg = msim_msg_new(
3036 g_return_if_fail(msim_send(session, 2756 "delbuddy", MSIM_TYPE_BOOLEAN, TRUE,
2757 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
2758 /* 'delprofileid' with uid will be inserted here. */
2759 NULL);
2760 if (!msim_postprocess_outgoing(session, msg, name, "delprofileid", NULL))
2761 purple_debug_error("myspace", "delbuddy command failed\n");
2762 msim_msg_free(msg);
2763
2764 /* Remove from our approve list and add to our block list */
2765 msim_update_blocklist_for_buddy(session, name, FALSE, TRUE);
2766
2767 /*
2768 * Add the buddy to our list of blocked contacts, so we know they
2769 * are blocked if we log in with another client
2770 */
2771 body = msim_msg_new(
2772 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"),
2773 "Visibility", MSIM_TYPE_INTEGER, 2,
2774 NULL);
2775 msg = msim_msg_new(
3037 "persist", MSIM_TYPE_INTEGER, 1, 2776 "persist", MSIM_TYPE_INTEGER, 1,
3038 "sesskey", MSIM_TYPE_INTEGER, session->sesskey, 2777 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
3039 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT, 2778 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_PUT,
3040 "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN, 2779 "dsn", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_DSN,
3041 "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID, 2780 "lid", MSIM_TYPE_INTEGER, MC_CONTACT_INFO_LID,
3042 "uid", MSIM_TYPE_INTEGER, session->userid, 2781 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
3043 "rid", MSIM_TYPE_INTEGER, 2782 "body", MSIM_TYPE_DICTIONARY, body,
3044 msim_new_reply_callback(session, msim_import_friends_cb, NULL), 2783 NULL);
3045 "body", MSIM_TYPE_STRING, 2784 if (!msim_postprocess_outgoing(session, msg, name, "body", NULL))
3046 g_strdup_printf("GroupName=%s", group_name), 2785 purple_debug_error("myspace", "add to block list command failed\n");
3047 NULL)); 2786 msim_msg_free(msg);
3048 2787
3049 2788 /*
3050 } 2789 * TODO: MySpace doesn't allow blocked buddies on our buddy list,
3051 2790 * do they? If not then we need to remove the buddy from
3052 /** Actions menu for account. */ 2791 * libpurple's buddy list.
3053 GList * 2792 */
3054 msim_actions(PurplePlugin *plugin, gpointer context) 2793 }
3055 { 2794
3056 PurpleConnection *gc; 2795 /**
3057 GList *menu; 2796 * Remove a buddy from the user's block list.
3058 PurplePluginAction *act; 2797 */
3059 2798 static void
3060 gc = (PurpleConnection *)context; 2799 msim_rem_deny(PurpleConnection *gc, const char *name)
3061 2800 {
3062 menu = NULL; 2801 MsimSession *session;
3063 2802 MsimMessage *msg, *body;
3064 #if 0 2803
3065 /* TODO: find out how */ 2804 session = (MsimSession *)gc->proto_data;
3066 act = purple_plugin_action_new(_("Find people..."), msim_); 2805
3067 menu = g_list_append(menu, act); 2806 /*
3068 2807 * Remove from our list of blocked contacts, so we know they
3069 act = purple_plugin_action_new(_("Change IM name..."), NULL); 2808 * are no longer blocked if we log in with another client
3070 menu = g_list_append(menu, act); 2809 */
3071 #endif 2810 body = msim_msg_new(
3072 2811 "ContactID", MSIM_TYPE_STRING, g_strdup("<uid>"),
3073 act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends); 2812 NULL);
3074 menu = g_list_append(menu, act); 2813 msg = msim_msg_new(
3075 2814 "persist", MSIM_TYPE_INTEGER, 1,
3076 return menu; 2815 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
3077 } 2816 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_BIT_ACTION | MSIM_CMD_DELETE,
3078 2817 "dsn", MSIM_TYPE_INTEGER, MC_DELETE_CONTACT_INFO_DSN,
3079 /** Callbacks called by Purple, to access this plugin. */ 2818 "lid", MSIM_TYPE_INTEGER, MC_DELETE_CONTACT_INFO_LID,
2819 "rid", MSIM_TYPE_INTEGER, session->next_rid++,
2820 "body", MSIM_TYPE_DICTIONARY, body,
2821 NULL);
2822 if (!msim_postprocess_outgoing(session, msg, name, "body", NULL))
2823 purple_debug_error("myspace", "remove from block list command failed\n");
2824 msim_msg_free(msg);
2825
2826 /* Remove from our approve list and our block list */
2827 msim_update_blocklist_for_buddy(session, name, FALSE, FALSE);
2828 }
2829
2830 static void
2831 msim_buddy_free(PurpleBuddy *buddy)
2832 {
2833 msim_user_free(buddy->proto_data);
2834 buddy->proto_data = NULL;
2835 }
2836
2837 /**
2838 * Returns a string of a username in canonical form. Basically removes all the
2839 * spaces, lowercases the string, and looks up user IDs to usernames.
2840 * Normalizing tom, TOM, Tom, and 6221 wil all return 'tom'.
2841 *
2842 * Borrowed this code from oscar_normalize. Added checking for
2843 * "if userid, get name before normalizing"
2844 */
2845 static const char *msim_normalize(const PurpleAccount *account, const char *str) {
2846 static char normalized[BUF_LEN];
2847 char *tmp1, *tmp2;
2848 int i, j;
2849 guint id;
2850
2851 g_return_val_if_fail(str != NULL, NULL);
2852
2853 if (msim_is_userid(str)) {
2854 /* Have user ID, we need to get their username first :) */
2855 const char *username;
2856
2857 /* If the account does not exist, we can't look up the user. */
2858 if (!account || !account->gc)
2859 return str;
2860
2861 id = atol(str);
2862 username = msim_uid2username_from_blist((PurpleAccount *)account, id);
2863 if (!username) {
2864 /* Not in buddy list... scheisse... TODO: Manual Lookup! Bug #4631 */
2865 /* Note: manual lookup using msim_lookup_user() is a problem inside
2866 * msim_normalize(), because msim_lookup_user() calls a callback function
2867 * when the user information has been looked up, but msim_normalize() expects
2868 * the result immediately. */
2869 strncpy(normalized, str, BUF_LEN);
2870 } else {
2871 strncpy(normalized, username, BUF_LEN);
2872 }
2873 } else {
2874 /* Have username. */
2875 strncpy(normalized, str, BUF_LEN);
2876 }
2877
2878 /* Strip spaces. */
2879 for (i=0, j=0; normalized[j]; i++, j++) {
2880 while (normalized[j] == ' ')
2881 j++;
2882 normalized[i] = normalized[j];
2883 }
2884 normalized[i] = '\0';
2885
2886 /* Lowercase and perform UTF-8 normalization. */
2887 tmp1 = g_utf8_strdown(normalized, -1);
2888 tmp2 = g_utf8_normalize(tmp1, -1, G_NORMALIZE_DEFAULT);
2889 g_snprintf(normalized, sizeof(normalized), "%s", tmp2);
2890 g_free(tmp2);
2891 g_free(tmp1);
2892
2893 /* TODO: re-add caps and spacing back to what the user wanted.
2894 * User can format their own names, for example 'msimprpl' is shown
2895 * as 'MsIm PrPl' in the official client.
2896 *
2897 * TODO: file a ticket to add this enhancement.
2898 */
2899
2900 return normalized;
2901 }
2902
2903 /**
2904 * Return whether the buddy can be messaged while offline.
2905 *
2906 * The protocol supports offline messages in just the same way as online
2907 * messages.
2908 */
2909 static gboolean
2910 msim_offline_message(const PurpleBuddy *buddy)
2911 {
2912 return TRUE;
2913 }
2914
2915 /**
2916 * Send raw data to the server, possibly with embedded NULs.
2917 *
2918 * Used in prpl_info struct, so that plugins can have the most possible
2919 * control of what is sent over the connection. Inside this prpl,
2920 * msim_send_raw() is used, since it sends NUL-terminated strings (easier).
2921 *
2922 * @param gc PurpleConnection
2923 * @param buf Buffer to send
2924 * @param total_bytes Size of buffer to send
2925 *
2926 * @return Bytes successfully sent, or -1 on error.
2927 */
2928 /*
2929 * TODO: This needs to do non-blocking writes and use a watcher to check
2930 * when the fd is available to be written to.
2931 */
2932 static int
2933 msim_send_really_raw(PurpleConnection *gc, const char *buf, int total_bytes)
2934 {
2935 int total_bytes_sent;
2936 MsimSession *session;
2937
2938 g_return_val_if_fail(gc != NULL, -1);
2939 g_return_val_if_fail(buf != NULL, -1);
2940 g_return_val_if_fail(total_bytes >= 0, -1);
2941
2942 session = (MsimSession *)gc->proto_data;
2943
2944 g_return_val_if_fail(MSIM_SESSION_VALID(session), -1);
2945
2946 /* Loop until all data is sent, or a failure occurs. */
2947 total_bytes_sent = 0;
2948 do {
2949 int bytes_sent;
2950
2951 bytes_sent = send(session->fd, buf + total_bytes_sent,
2952 total_bytes - total_bytes_sent, 0);
2953
2954 if (bytes_sent < 0) {
2955 purple_debug_info("msim", "msim_send_raw(%s): send() failed: %s\n",
2956 buf, g_strerror(errno));
2957 return total_bytes_sent;
2958 }
2959 total_bytes_sent += bytes_sent;
2960
2961 } while(total_bytes_sent < total_bytes);
2962
2963 return total_bytes_sent;
2964 }
2965
2966 /**
2967 * Send raw data (given as a NUL-terminated string) to the server.
2968 *
2969 * @param session
2970 * @param msg The raw data to send, in a NUL-terminated string.
2971 *
2972 * @return TRUE if succeeded, FALSE if not.
2973 *
2974 */
2975 gboolean
2976 msim_send_raw(MsimSession *session, const gchar *msg)
2977 {
2978 size_t len;
2979
2980 g_return_val_if_fail(MSIM_SESSION_VALID(session), FALSE);
2981 g_return_val_if_fail(msg != NULL, FALSE);
2982
2983 purple_debug_info("msim", "msim_send_raw: writing <%s>\n", msg);
2984 len = strlen(msg);
2985
2986 return msim_send_really_raw(session->gc, msg, len) == len;
2987 }
2988
2989 static GHashTable *
2990 msim_get_account_text_table(PurpleAccount *unused)
2991 {
2992 GHashTable *table;
2993
2994 table = g_hash_table_new(g_str_hash, g_str_equal);
2995
2996 g_hash_table_insert(table, "login_label", (gpointer)_("Email Address..."));
2997
2998 return table;
2999 }
3000
3001 /**
3002 * Callbacks called by Purple, to access this plugin.
3003 */
3080 static PurplePluginProtocolInfo prpl_info = { 3004 static PurplePluginProtocolInfo prpl_info = {
3081 /* options */ 3005 /* options */
3082 OPT_PROTO_USE_POINTSIZE /* specify font size in sane point size */ 3006 OPT_PROTO_USE_POINTSIZE /* specify font size in sane point size */
3083 | OPT_PROTO_MAIL_CHECK, 3007 | OPT_PROTO_MAIL_CHECK,
3084 3008
3085 /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */ 3009 /* | OPT_PROTO_IM_IMAGE - TODO: direct images. */
3086 NULL, /* user_splits */ 3010 NULL, /* user_splits */
3087 NULL, /* protocol_options */ 3011 NULL, /* protocol_options */
3088 NO_BUDDY_ICONS, /* icon_spec - TODO: eventually should add this */ 3012 NO_BUDDY_ICONS, /* icon_spec - TODO: eventually should add this */
3089 msim_list_icon, /* list_icon */ 3013 msim_list_icon, /* list_icon */
3090 NULL, /* list_emblems */ 3014 NULL, /* list_emblems */
3106 msim_add_buddy, /* add_buddy */ 3030 msim_add_buddy, /* add_buddy */
3107 NULL, /* add_buddies */ 3031 NULL, /* add_buddies */
3108 msim_remove_buddy, /* remove_buddy */ 3032 msim_remove_buddy, /* remove_buddy */
3109 NULL, /* remove_buddies */ 3033 NULL, /* remove_buddies */
3110 NULL, /* add_permit */ 3034 NULL, /* add_permit */
3111 NULL, /* add_deny */ 3035 msim_add_deny, /* add_deny */
3112 NULL, /* rem_permit */ 3036 NULL, /* rem_permit */
3113 NULL, /* rem_deny */ 3037 msim_rem_deny, /* rem_deny */
3114 NULL, /* set_permit_deny */ 3038 NULL, /* set_permit_deny */
3115 NULL, /* join_chat */ 3039 NULL, /* join_chat */
3116 NULL, /* reject chat invite */ 3040 NULL, /* reject chat invite */
3117 NULL, /* get_chat_name */ 3041 NULL, /* get_chat_name */
3118 NULL, /* chat_invite */ 3042 NULL, /* chat_invite */
3124 NULL, /* get_cb_info */ 3048 NULL, /* get_cb_info */
3125 NULL, /* get_cb_away */ 3049 NULL, /* get_cb_away */
3126 NULL, /* alias_buddy */ 3050 NULL, /* alias_buddy */
3127 NULL, /* group_buddy */ 3051 NULL, /* group_buddy */
3128 NULL, /* rename_group */ 3052 NULL, /* rename_group */
3129 NULL, /* buddy_free */ 3053 msim_buddy_free, /* buddy_free */
3130 NULL, /* convo_closed */ 3054 NULL, /* convo_closed */
3131 msim_normalize, /* normalize */ 3055 msim_normalize, /* normalize */
3132 NULL, /* set_buddy_icon */ 3056 NULL, /* set_buddy_icon */
3133 NULL, /* remove_group */ 3057 NULL, /* remove_group */
3134 NULL, /* get_cb_real_name */ 3058 NULL, /* get_cb_real_name */
3151 msim_get_account_text_table, /* get_account_text_table */ 3075 msim_get_account_text_table, /* get_account_text_table */
3152 NULL, /* initiate_media */ 3076 NULL, /* initiate_media */
3153 NULL /* can_do_media */ 3077 NULL /* can_do_media */
3154 }; 3078 };
3155 3079
3156 3080 /**
3157 3081 * Load the plugin.
3158 /** Based on MSN's plugin info comments. */ 3082 */
3083 static gboolean
3084 msim_load(PurplePlugin *plugin)
3085 {
3086 /* If compiled to use RC4 from libpurple, check if it is really there. */
3087 if (!purple_ciphers_find_cipher("rc4")) {
3088 purple_debug_error("msim", "rc4 not in libpurple, but it is required - not loading MySpaceIM plugin!\n");
3089 purple_notify_error(plugin, _("Missing Cipher"),
3090 _("The RC4 cipher could not be found"),
3091 _("Upgrade "
3092 "to a libpurple with RC4 support (>= 2.0.1). MySpaceIM "
3093 "plugin will not be loaded."));
3094 return FALSE;
3095 }
3096 return TRUE;
3097 }
3098
3099 /**
3100 * Called when friends have been imported to buddy list on server.
3101 */
3102 static void
3103 msim_import_friends_cb(MsimSession *session, const MsimMessage *reply, gpointer user_data)
3104 {
3105 MsimMessage *body;
3106 gchar *completed;
3107
3108 /* Check if the friends were imported successfully. */
3109 body = msim_msg_get_dictionary(reply, "body");
3110 g_return_if_fail(body != NULL);
3111 completed = msim_msg_get_string(body, "Completed");
3112 g_return_if_fail(body != NULL);
3113 msim_msg_free(body);
3114 if (!g_str_equal(completed, "True"))
3115 {
3116 purple_debug_info("msim_import_friends_cb",
3117 "failed to import friends: %s", completed);
3118 purple_notify_error(session->account, _("Add friends from MySpace.com"),
3119 _("Importing friends failed"), NULL);
3120 g_free(completed);
3121 return;
3122 }
3123 g_free(completed);
3124
3125 purple_debug_info("msim_import_friends_cb",
3126 "added friends to server-side buddy list, requesting new contacts from server");
3127
3128 msim_get_contact_list(session, MSIM_CONTACT_LIST_IMPORT_ALL_FRIENDS);
3129
3130 /* TODO: show, X friends have been added */
3131 }
3132
3133 /**
3134 * Import friends from myspace.com.
3135 */
3136 static void msim_import_friends(PurplePluginAction *action)
3137 {
3138 PurpleConnection *gc;
3139 MsimSession *session;
3140 gchar *group_name;
3141
3142 gc = (PurpleConnection *)action->context;
3143 session = (MsimSession *)gc->proto_data;
3144
3145 group_name = "MySpace Friends";
3146
3147 g_return_if_fail(msim_send(session,
3148 "persist", MSIM_TYPE_INTEGER, 1,
3149 "sesskey", MSIM_TYPE_INTEGER, session->sesskey,
3150 "cmd", MSIM_TYPE_INTEGER, MSIM_CMD_PUT,
3151 "dsn", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_DSN,
3152 "lid", MSIM_TYPE_INTEGER, MC_IMPORT_ALL_FRIENDS_LID,
3153 "uid", MSIM_TYPE_INTEGER, session->userid,
3154 "rid", MSIM_TYPE_INTEGER,
3155 msim_new_reply_callback(session, msim_import_friends_cb, NULL),
3156 "body", MSIM_TYPE_STRING,
3157 g_strdup_printf("GroupName=%s", group_name),
3158 NULL));
3159 }
3160
3161 /**
3162 * Actions menu for account.
3163 */
3164 static GList *
3165 msim_actions(PurplePlugin *plugin, gpointer context)
3166 {
3167 PurpleConnection *gc;
3168 GList *menu;
3169 PurplePluginAction *act;
3170
3171 gc = (PurpleConnection *)context;
3172
3173 menu = NULL;
3174
3175 #if 0
3176 /* TODO: find out how */
3177 act = purple_plugin_action_new(_("Find people..."), msim_);
3178 menu = g_list_append(menu, act);
3179
3180 act = purple_plugin_action_new(_("Change IM name..."), NULL);
3181 menu = g_list_append(menu, act);
3182 #endif
3183
3184 act = purple_plugin_action_new(_("Add friends from MySpace.com"), msim_import_friends);
3185 menu = g_list_append(menu, act);
3186
3187 return menu;
3188 }
3189
3190 /**
3191 * Based on MSN's plugin info comments.
3192 */
3159 static PurplePluginInfo info = { 3193 static PurplePluginInfo info = {
3160 PURPLE_PLUGIN_MAGIC, 3194 PURPLE_PLUGIN_MAGIC,
3161 PURPLE_MAJOR_VERSION, 3195 PURPLE_MAJOR_VERSION,
3162 PURPLE_MINOR_VERSION, 3196 PURPLE_MINOR_VERSION,
3163 PURPLE_PLUGIN_PROTOCOL, /**< type */ 3197 PURPLE_PLUGIN_PROTOCOL, /**< type */
3187 NULL, /**< reserved2 */ 3221 NULL, /**< reserved2 */
3188 NULL, /**< reserved3 */ 3222 NULL, /**< reserved3 */
3189 NULL /**< reserved4 */ 3223 NULL /**< reserved4 */
3190 }; 3224 };
3191 3225
3192
3193 #ifdef MSIM_SELF_TEST 3226 #ifdef MSIM_SELF_TEST
3194 /** Test functions. 3227 /*
3228 * Test functions.
3195 * Used to test or try out the internal workings of msimprpl. If you're reading 3229 * Used to test or try out the internal workings of msimprpl. If you're reading
3196 * this code for the first time, these functions can be instructive in learning 3230 * this code for the first time, these functions can be instructive in learning
3197 * how msimprpl is architected. 3231 * how msimprpl is architected.
3198 */ 3232 */
3199 void 3233
3200 msim_test_all(void) { 3234 /**
3201 guint failures; 3235 * Test MsimMessage for basic functionality.
3202 3236 */
3203 3237 static int
3204 failures = 0;
3205 failures += msim_test_msg();
3206 failures += msim_test_escaping();
3207
3208 if (failures) {
3209 purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures);
3210 } else {
3211 purple_debug_info("msim", "msim_test_all - all tests passed!\n");
3212 }
3213 exit(0);
3214 }
3215
3216 /** Test MsimMessage for basic functionality. */
3217 int
3218 msim_test_msg(void) 3238 msim_test_msg(void)
3219 { 3239 {
3220 MsimMessage *msg, *msg_cloned, *msg2; 3240 MsimMessage *msg, *msg_cloned, *msg2;
3221 GList *list; 3241 GList *list;
3222 gchar *packed, *packed_expected, *packed_cloned; 3242 gchar *packed, *packed_expected, *packed_cloned;
3289 msim_msg_free(msg2); 3309 msim_msg_free(msg2);
3290 3310
3291 return failures; 3311 return failures;
3292 } 3312 }
3293 3313
3294 /** Test protocol-level escaping/unescaping. */ 3314 /**
3295 int 3315 * Test protocol-level escaping/unescaping.
3316 */
3317 static int
3296 msim_test_escaping(void) 3318 msim_test_escaping(void)
3297 { 3319 {
3298 guint failures; 3320 guint failures;
3299 gchar *raw, *escaped, *unescaped, *expected; 3321 gchar *raw, *escaped, *unescaped, *expected;
3300 3322
3321 ++failures, raw, unescaped); 3343 ++failures, raw, unescaped);
3322 } 3344 }
3323 3345
3324 return failures; 3346 return failures;
3325 } 3347 }
3348
3349 static void
3350 msim_test_all(void)
3351 {
3352 guint failures;
3353
3354 failures = 0;
3355 failures += msim_test_msg();
3356 failures += msim_test_escaping();
3357
3358 if (failures) {
3359 purple_debug_info("msim", "msim_test_all HAD FAILURES: %d\n", failures);
3360 } else {
3361 purple_debug_info("msim", "msim_test_all - all tests passed!\n");
3362 }
3363 exit(0);
3364 }
3326 #endif 3365 #endif
3366
3367 #ifdef MSIM_CHECK_NEWER_VERSION
3368 /**
3369 * Callback for when a currentversion.txt has been downloaded.
3370 */
3371 static void
3372 msim_check_newer_version_cb(PurpleUtilFetchUrlData *url_data,
3373 gpointer user_data,
3374 const gchar *url_text,
3375 gsize len,
3376 const gchar *error_message)
3377 {
3378 GKeyFile *keyfile;
3379 GError *error;
3380 GString *data;
3381 gchar *newest_filever;
3382
3383 if (!url_text) {
3384 purple_debug_info("msim_check_newer_version_cb",
3385 "got error: %s\n", error_message);
3386 return;
3387 }
3388
3389 purple_debug_info("msim_check_newer_version_cb",
3390 "url_text=%s\n", url_text ? url_text : "(NULL)");
3391
3392 /* Prepend [group] so that GKeyFile can parse it (requires a group). */
3393 data = g_string_new(url_text);
3394 purple_debug_info("msim", "data=%s\n", data->str
3395 ? data->str : "(NULL)");
3396 data = g_string_prepend(data, "[group]\n");
3397
3398 purple_debug_info("msim", "data=%s\n", data->str
3399 ? data->str : "(NULL)");
3400
3401 /* url_text is variable=data\n...†*/
3402
3403 /* Check FILEVER, 1.0.716.0. 716 is build, MSIM_CLIENT_VERSION */
3404 /* New (english) version can be downloaded from SETUPURL+SETUPFILE */
3405
3406 error = NULL;
3407 keyfile = g_key_file_new();
3408
3409 /* Default list seperator is ;, but currentversion.txt doesn't have
3410 * these, so set to an unused character to avoid parsing problems. */
3411 g_key_file_set_list_separator(keyfile, '\0');
3412
3413 g_key_file_load_from_data(keyfile, data->str, data->len,
3414 G_KEY_FILE_NONE, &error);
3415 g_string_free(data, TRUE);
3416
3417 if (error != NULL) {
3418 purple_debug_info("msim_check_newer_version_cb",
3419 "couldn't parse, error: %d %d %s\n",
3420 error->domain, error->code, error->message);
3421 g_error_free(error);
3422 return;
3423 }
3424
3425 gchar **ks;
3426 guint n;
3427 ks = g_key_file_get_keys(keyfile, "group", &n, NULL);
3428 purple_debug_info("msim", "n=%d\n", n);
3429 guint i;
3430 for (i = 0; ks[i] != NULL; ++i)
3431 {
3432 purple_debug_info("msim", "%d=%s\n", i, ks[i]);
3433 }
3434
3435 newest_filever = g_key_file_get_string(keyfile, "group",
3436 "FILEVER", &error);
3437
3438 purple_debug_info("msim_check_newer_version_cb",
3439 "newest filever: %s\n", newest_filever ?
3440 newest_filever : "(NULL)");
3441 if (error != NULL) {
3442 purple_debug_info("msim_check_newer_version_cb",
3443 "error: %d %d %s\n",
3444 error->domain, error->code, error->message);
3445 g_error_free(error);
3446 }
3447
3448 g_key_file_free(keyfile);
3449
3450 exit(0);
3451 }
3452 #endif
3453
3454 /**
3455 Handle a myim:addContact command, after username has been looked up.
3456 */
3457 static void
3458 msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
3459 {
3460 MsimMessage *body;
3461 gchar *username;
3462
3463 body = msim_msg_get_dictionary(userinfo, "body");
3464 username = msim_msg_get_string(body, "UserName");
3465 msim_msg_free(body);
3466
3467 if (!username) {
3468 guint uid;
3469
3470 uid = msim_msg_get_integer(userinfo, "UserID");
3471 g_return_if_fail(uid != 0);
3472
3473 username = g_strdup_printf("%d", uid);
3474 }
3475
3476 purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL);
3477
3478 g_free(username);
3479 }
3480
3481 /* TODO: move uid->username resolving to IM sending and buddy adding functions,
3482 * so that user can manually add or IM by userid and username automatically
3483 * looked up if possible? */
3484
3485 /**
3486 * Handle a myim:sendIM URI command, after username has been looked up.
3487 */
3488 static void
3489 msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
3490 {
3491 PurpleConversation *conv;
3492 MsimMessage *body;
3493 gchar *username;
3494
3495 body = msim_msg_get_dictionary(userinfo, "body");
3496 username = msim_msg_get_string(body, "UserName");
3497 msim_msg_free(body);
3498
3499 if (!username) {
3500 guint uid;
3501
3502 uid = msim_msg_get_integer(userinfo, "UserID");
3503 g_return_if_fail(uid != 0);
3504
3505 username = g_strdup_printf("%d", uid);
3506 }
3507
3508
3509 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, session->account);
3510 if (!conv) {
3511 purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", username);
3512 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, username);
3513 }
3514
3515 /* Just open the window so the user can send an IM. */
3516 purple_conversation_present(conv);
3517
3518 g_free(username);
3519 }
3327 3520
3328 static gboolean 3521 static gboolean
3329 msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params) 3522 msim_uri_handler(const gchar *proto, const gchar *cmd, GHashTable *params)
3330 { 3523 {
3331 PurpleAccount *account; 3524 PurpleAccount *account;
3349 3542
3350 /* TODO: if auto=true, "Add all the people on this page to my IM List!", on 3543 /* TODO: if auto=true, "Add all the people on this page to my IM List!", on
3351 * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */ 3544 * http://collect.myspace.com/index.cfm?fuseaction=im.friendslist. Don't need a cid. */
3352 3545
3353 /* Convert numeric contact ID back to a string. Needed for looking up. Don't just 3546 /* Convert numeric contact ID back to a string. Needed for looking up. Don't just
3354 * directly use cid directly from parameters, because it might not be numeric. 3547 * directly use cid directly from parameters, because it might not be numeric.
3355 * It is trivial to change this to allow cID to be a username, but that's not how 3548 * It is trivial to change this to allow cID to be a username, but that's not how
3356 * the official MySpaceIM client works, so don't provide that functionality. */ 3549 * the official MySpaceIM client works, so don't provide that functionality. */
3357 cid_str = g_strdup_printf("%d", cid); 3550 cid_str = g_strdup_printf("%d", cid);
3358 3551
3359 3552
3368 } 3561 }
3369 l = l->next; 3562 l = l->next;
3370 } 3563 }
3371 3564
3372 if (!account) { 3565 if (!account) {
3373 purple_notify_error(NULL, _("myim URL handler"), 3566 purple_notify_error(NULL, _("myim URL handler"),
3374 _("No suitable MySpaceIM account could be found to open this myim URL."), 3567 _("No suitable MySpaceIM account could be found to open this myim URL."),
3375 _("Enable the proper MySpaceIM account and try again.")); 3568 _("Enable the proper MySpaceIM account and try again."));
3376 g_free(cid_str); 3569 g_free(cid_str);
3377 return FALSE; 3570 return FALSE;
3378 } 3571 }
3379 3572
3380 session = (MsimSession *)account->gc->proto_data; 3573 session = (MsimSession *)account->gc->proto_data;
3381 g_return_val_if_fail(session != NULL, FALSE); 3574 g_return_val_if_fail(session != NULL, FALSE);
3382 3575
3383 /* Lookup userid to username. TODO: push this down, to IM sending/contact 3576 /* Lookup userid to username. TODO: push this down, to IM sending/contact
3384 * adding functions. */ 3577 * adding functions. */
3385 3578
3386 /* myim:sendIM?uID=USERID&cID=CONTACTID */ 3579 /* myim:sendIM?uID=USERID&cID=CONTACTID */
3387 if (!g_ascii_strcasecmp(cmd, "sendIM")) { 3580 if (!g_ascii_strcasecmp(cmd, "sendIM")) {
3388 msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL); 3581 msim_lookup_user(session, cid_str, (MSIM_USER_LOOKUP_CB)msim_uri_handler_sendIM_cb, NULL);
3397 } 3590 }
3398 3591
3399 return FALSE; 3592 return FALSE;
3400 } 3593 }
3401 3594
3402 /* TODO: move uid->username resolving to IM sending and buddy adding functions, 3595 /**
3403 * so that user can manually add or IM by userid and username automatically 3596 * Initialize plugin.
3404 * looked up if possible? */ 3597 */
3405
3406 /** Handle a myim:sendIM URI command, after username has been looked up. */
3407 static void 3598 static void
3408 msim_uri_handler_sendIM_cb(MsimSession *session, MsimMessage *userinfo, gpointer data) 3599 init_plugin(PurplePlugin *plugin)
3409 {
3410 PurpleConversation *conv;
3411 MsimMessage *body;
3412 gchar *username;
3413
3414 body = msim_msg_get_dictionary(userinfo, "body");
3415 username = msim_msg_get_string(body, "UserName");
3416 msim_msg_free(body);
3417
3418 if (!username) {
3419 guint uid;
3420
3421 uid = msim_msg_get_integer(userinfo, "UserID");
3422 g_return_if_fail(uid != 0);
3423
3424 username = g_strdup_printf("%d", uid);
3425 }
3426
3427
3428 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, username, session->account);
3429 if (!conv) {
3430 purple_debug_info("msim_uri_handler", "creating new conversation for %s\n", username);
3431 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, session->account, username);
3432 }
3433
3434 /* Just open the window so the user can send an IM. */
3435 purple_conversation_present(conv);
3436
3437 g_free(username);
3438 }
3439
3440 /** Handle a myim:addContact command, after username has been looked up. */
3441 static void
3442 msim_uri_handler_addContact_cb(MsimSession *session, MsimMessage *userinfo, gpointer data)
3443 {
3444 MsimMessage *body;
3445 gchar *username;
3446
3447 body = msim_msg_get_dictionary(userinfo, "body");
3448 username = msim_msg_get_string(body, "UserName");
3449 msim_msg_free(body);
3450
3451 if (!username) {
3452 guint uid;
3453
3454 uid = msim_msg_get_integer(userinfo, "UserID");
3455 g_return_if_fail(uid != 0);
3456
3457 username = g_strdup_printf("%d", uid);
3458 }
3459
3460
3461 purple_blist_request_add_buddy(session->account, username, _("Buddies"), NULL);
3462
3463 g_free(username);
3464 }
3465
3466 /** Initialize plugin. */
3467 void
3468 init_plugin(PurplePlugin *plugin)
3469 { 3600 {
3470 #ifdef MSIM_SELF_TEST 3601 #ifdef MSIM_SELF_TEST
3471 msim_test_all(); 3602 msim_test_all();
3472 exit(0); 3603 exit(0);
3473 #endif /* MSIM_SELF_TEST */ 3604 #endif /* MSIM_SELF_TEST */
3514 option = purple_account_option_int_new(_("Base font size (points)"), "base_font_size", MSIM_BASE_FONT_POINT_SIZE); 3645 option = purple_account_option_int_new(_("Base font size (points)"), "base_font_size", MSIM_BASE_FONT_POINT_SIZE);
3515 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); 3646 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3516 #endif 3647 #endif
3517 3648
3518 /* Code below only runs once. Based on oscar.c's oscar_init(). */ 3649 /* Code below only runs once. Based on oscar.c's oscar_init(). */
3519 if (initialized) 3650 if (initialized)
3520 return; 3651 return;
3521 3652
3522 initialized = TRUE; 3653 initialized = TRUE;
3523 3654
3524 purple_signal_connect(purple_get_core(), "uri-handler", &initialized, 3655 purple_signal_connect(purple_get_core(), "uri-handler", &initialized,