disapproval of revision '18b0a096bd6f21011f16c7b08c1aac016d2c0fff'
[pidgin-git:pidgin-git.git] / libpurple / protocols / yahoo / yahoo.c
1 /*
2  * purple
3  *
4  * Purple is the legal property of its developers, whose names are too numerous
5  * to list here.  Please refer to the COPYRIGHT file distributed with this
6  * source distribution.
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111-1301  USA
21  *
22  */
23
24 #include "internal.h"
25
26 #include "account.h"
27 #include "accountopt.h"
28 #include "blist.h"
29 #include "cipher.h"
30 #include "cmds.h"
31 #include "core.h"
32 #include "debug.h"
33 #include "notify.h"
34 #include "privacy.h"
35 #include "prpl.h"
36 #include "proxy.h"
37 #include "request.h"
38 #include "server.h"
39 #include "util.h"
40 #include "version.h"
41
42 #include "yahoo.h"
43 #include "yahoochat.h"
44 #include "yahoo_aliases.h"
45 #include "yahoo_doodle.h"
46 #include "yahoo_filexfer.h"
47 #include "yahoo_friend.h"
48 #include "yahoo_packet.h"
49 #include "yahoo_picture.h"
50 #include "ycht.h"
51
52 /* #define YAHOO_DEBUG */
53
54 /* #define TRY_WEBMESSENGER_LOGIN 0 */
55
56 /* One hour */
57 #define PING_TIMEOUT 3600
58
59 /* One minute */
60 #define KEEPALIVE_TIMEOUT 60
61
62 static void yahoo_add_buddy(PurpleConnection *gc, PurpleBuddy *, PurpleGroup *);
63 #ifdef TRY_WEBMESSENGER_LOGIN
64 static void yahoo_login_page_cb(PurpleUtilFetchUrlData *url_data, gpointer user_data, const gchar *url_text, size_t len, const gchar *error_message);
65 #endif
66 static void yahoo_set_status(PurpleAccount *account, PurpleStatus *status);
67
68 static void yahoo_update_status(PurpleConnection *gc, const char *name, YahooFriend *f)
69 {
70         char *status = NULL;
71
72         if (!gc || !name || !f || !purple_find_buddy(purple_connection_get_account(gc), name))
73                 return;
74
75         switch (f->status) {
76         case YAHOO_STATUS_OFFLINE:
77                 status = YAHOO_STATUS_TYPE_OFFLINE;
78                 break;
79         case YAHOO_STATUS_AVAILABLE:
80                 status = YAHOO_STATUS_TYPE_AVAILABLE;
81                 break;
82         case YAHOO_STATUS_BRB:
83                 status = YAHOO_STATUS_TYPE_BRB;
84                 break;
85         case YAHOO_STATUS_BUSY:
86                 status = YAHOO_STATUS_TYPE_BUSY;
87                 break;
88         case YAHOO_STATUS_NOTATHOME:
89                 status = YAHOO_STATUS_TYPE_NOTATHOME;
90                 break;
91         case YAHOO_STATUS_NOTATDESK:
92                 status = YAHOO_STATUS_TYPE_NOTATDESK;
93                 break;
94         case YAHOO_STATUS_NOTINOFFICE:
95                 status = YAHOO_STATUS_TYPE_NOTINOFFICE;
96                 break;
97         case YAHOO_STATUS_ONPHONE:
98                 status = YAHOO_STATUS_TYPE_ONPHONE;
99                 break;
100         case YAHOO_STATUS_ONVACATION:
101                 status = YAHOO_STATUS_TYPE_ONVACATION;
102                 break;
103         case YAHOO_STATUS_OUTTOLUNCH:
104                 status = YAHOO_STATUS_TYPE_OUTTOLUNCH;
105                 break;
106         case YAHOO_STATUS_STEPPEDOUT:
107                 status = YAHOO_STATUS_TYPE_STEPPEDOUT;
108                 break;
109         case YAHOO_STATUS_INVISIBLE: /* this should never happen? */
110                 status = YAHOO_STATUS_TYPE_INVISIBLE;
111                 break;
112         case YAHOO_STATUS_CUSTOM:
113         case YAHOO_STATUS_IDLE:
114                 if (!f->away)
115                         status = YAHOO_STATUS_TYPE_AVAILABLE;
116                 else
117                         status = YAHOO_STATUS_TYPE_AWAY;
118                 break;
119         default:
120                 purple_debug_warning("yahoo", "Warning, unknown status %d\n", f->status);
121                 break;
122         }
123
124         if (status) {
125                 if (f->status == YAHOO_STATUS_CUSTOM)
126                         purple_prpl_got_user_status(purple_connection_get_account(gc), name, status, "message",
127                                                   yahoo_friend_get_status_message(f), NULL);
128                 else
129                         purple_prpl_got_user_status(purple_connection_get_account(gc), name, status, NULL);
130         }
131
132         if (f->idle != 0)
133                 purple_prpl_got_user_idle(purple_connection_get_account(gc), name, TRUE, f->idle);
134         else
135                 purple_prpl_got_user_idle(purple_connection_get_account(gc), name, FALSE, 0);
136
137         if (f->sms)
138                 purple_prpl_got_user_status(purple_connection_get_account(gc), name, YAHOO_STATUS_TYPE_MOBILE, NULL);
139         else
140                 purple_prpl_got_user_status_deactive(purple_connection_get_account(gc), name, YAHOO_STATUS_TYPE_MOBILE);
141 }
142
143 static void yahoo_process_status(PurpleConnection *gc, struct yahoo_packet *pkt)
144 {
145         PurpleAccount *account = purple_connection_get_account(gc);
146         GSList *l = pkt->hash;
147         YahooFriend *f = NULL;
148         char *name = NULL;
149         gboolean unicode = FALSE;
150         char *message = NULL;
151
152         if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
153                 if (!purple_account_get_remember_password(account))
154                         purple_account_set_password(account, NULL);
155                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NAME_IN_USE,
156                         _("You have signed on from another location."));
157                 return;
158         }
159
160         while (l) {
161                 struct yahoo_pair *pair = l->data;
162
163                 switch (pair->key) {
164                 case 0: /* we won't actually do anything with this */
165                 case 1: /* we won't actually do anything with this */
166                         break;
167                 case 8: /* how many online buddies we have */
168                         break;
169                 case 7: /* the current buddy */
170                         /* update the previous buddy before changing the variables */
171                         if (f) {
172                                 if (message)
173                                         yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
174                                 if (name)
175                                         yahoo_update_status(gc, name, f);
176                         }
177                         name = message = NULL;
178                         f = NULL;
179                         if (pair->value && g_utf8_validate(pair->value, -1, NULL)) {
180                                 name = pair->value;
181                                 f = yahoo_friend_find_or_new(gc, name);
182                         }
183                         break;
184                 case 10: /* state */
185                         if (!f)
186                                 break;
187
188                         f->status = strtol(pair->value, NULL, 10);
189                         if ((f->status >= YAHOO_STATUS_BRB) && (f->status <= YAHOO_STATUS_STEPPEDOUT))
190                                 f->away = 1;
191                         else
192                                 f->away = 0;
193
194                         if (f->status == YAHOO_STATUS_IDLE) {
195                                 /* Idle may have already been set in a more precise way in case 137 */
196                                 if (f->idle == 0)
197                                         f->idle = time(NULL);
198                         } else
199                                 f->idle = 0;
200
201                         if (f->status != YAHOO_STATUS_CUSTOM)
202                                 yahoo_friend_set_status_message(f, NULL);
203
204                         f->sms = 0;
205                         break;
206                 case 19: /* custom message */
207                         if (f)
208                                 message = pair->value;
209                         break;
210                 case 11: /* this is the buddy's session id */
211                         break;
212                 case 17: /* in chat? */
213                         break;
214                 case 47: /* is custom status away or not? 2=idle*/
215                         if (!f)
216                                 break;
217
218                         /* I have no idea what it means when this is
219                          * set when someone's available, but it doesn't
220                          * mean idle. */
221                         if (f->status == YAHOO_STATUS_AVAILABLE)
222                                 break;
223
224                         f->away = strtol(pair->value, NULL, 10);
225                         if (f->away == 2) {
226                                 /* Idle may have already been set in a more precise way in case 137 */
227                                 if (f->idle == 0)
228                                         f->idle = time(NULL);
229                         }
230
231                         break;
232                 case 138: /* either we're not idle, or we are but won't say how long */
233                         if (!f)
234                                 break;
235
236                         if (f->idle)
237                                 f->idle = -1;
238                         break;
239                 case 137: /* usually idle time in seconds, sometimes login time */
240                         if (!f)
241                                 break;
242
243                         if (f->status != YAHOO_STATUS_AVAILABLE)
244                                 f->idle = time(NULL) - strtol(pair->value, NULL, 10);
245                         break;
246                 case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */
247                         if (strtol(pair->value, NULL, 10) == 0) {
248                                 if (f)
249                                         f->status = YAHOO_STATUS_OFFLINE;
250                                 if (name) {
251                                         purple_prpl_got_user_status(account, name, "offline", NULL);
252                                         purple_prpl_got_user_status_deactive(account, name, YAHOO_STATUS_TYPE_MOBILE);
253                                 }
254                                 break;
255                         }
256                         break;
257                 case 60: /* SMS */
258                         if (f) {
259                                 f->sms = strtol(pair->value, NULL, 10);
260                                 yahoo_update_status(gc, name, f);
261                         }
262                         break;
263                 case 197: /* Avatars */
264                 {
265                         guchar *decoded;
266                         char *tmp;
267                         gsize len;
268
269                         if (pair->value) {
270                                 decoded = purple_base64_decode(pair->value, &len);
271                                 if (len) {
272                                         tmp = purple_str_binary_to_ascii(decoded, len);
273                                         purple_debug_info("yahoo", "Got key 197, value = %s\n", tmp);
274                                         g_free(tmp);
275                                 }
276                                 g_free(decoded);
277                         }
278                         break;
279                 }
280                 case 192: /* Pictures, aka Buddy Icons, checksum */
281                 {
282                         /* FIXME: Please, if you know this protocol,
283                          * FIXME: fix up the strtol() stuff if possible. */
284                         int cksum = strtol(pair->value, NULL, 10);
285                         const char *locksum = NULL;
286                         PurpleBuddy *b;
287
288                         if (!name)
289                                 break;
290
291                         b = purple_find_buddy(gc->account, name);
292
293                         if (!cksum || (cksum == -1)) {
294                                 if (f)
295                                         yahoo_friend_set_buddy_icon_need_request(f, TRUE);
296                                 purple_buddy_icons_set_for_user(gc->account, name, NULL, 0, NULL);
297                                 break;
298                         }
299
300                         if (!f)
301                                 break;
302
303                         yahoo_friend_set_buddy_icon_need_request(f, FALSE);
304                         if (b) {
305                                 locksum = purple_buddy_icons_get_checksum_for_user(b);
306                                 if (!locksum || (cksum != strtol(locksum, NULL, 10)))
307                                         yahoo_send_picture_request(gc, name);
308                         }
309
310                         break;
311                 }
312                 case 16: /* Custom error message */
313                         {
314                                 char *tmp = yahoo_string_decode(gc, pair->value, TRUE);
315                                 purple_notify_error(gc, NULL, tmp, NULL);
316                                 g_free(tmp);
317                         }
318                         break;
319                 case 97: /* Unicode status message */
320                         unicode = !strcmp(pair->value, "1");
321                         break;
322                 case 244: /* client version number. Yahoo Client Detection */
323                         if(f && strtol(pair->value, NULL, 10))
324                                 f->version_id = strtol(pair->value, NULL, 10);
325                         break;
326
327                 default:
328                         purple_debug_warning("yahoo",
329                                            "Unknown status key %d\n", pair->key);
330                         break;
331                 }
332
333                 l = l->next;
334         }
335
336         if (f) {
337                 if (pkt->service == YAHOO_SERVICE_LOGOFF)
338                         f->status = YAHOO_STATUS_OFFLINE;
339                 if (message)
340                         yahoo_friend_set_status_message(f, yahoo_string_decode(gc, message, unicode));
341
342                 if (name) /* update the last buddy */
343                         yahoo_update_status(gc, name, f);
344         }
345 }
346
347 static void yahoo_do_group_check(PurpleAccount *account, GHashTable *ht, const char *name, const char *group)
348 {
349         PurpleBuddy *b;
350         PurpleGroup *g;
351         GSList *list, *i;
352         gboolean onlist = 0;
353         char *oname = NULL;
354         char **oname_p = &oname;
355         GSList **list_p = &list;
356
357         if (!g_hash_table_lookup_extended(ht, purple_normalize(account, name), (gpointer *) oname_p, (gpointer *) list_p))
358                 list = purple_find_buddies(account, name);
359         else
360                 g_hash_table_steal(ht, name);
361
362         for (i = list; i; i = i->next) {
363                 b = i->data;
364                 g = purple_buddy_get_group(b);
365                 if (!purple_utf8_strcasecmp(group, g->name)) {
366                         purple_debug(PURPLE_DEBUG_MISC, "yahoo",
367                                 "Oh good, %s is in the right group (%s).\n", name, group);
368                         list = g_slist_delete_link(list, i);
369                         onlist = 1;
370                         break;
371                 }
372         }
373
374         if (!onlist) {
375                 purple_debug(PURPLE_DEBUG_MISC, "yahoo",
376                         "Uhoh, %s isn't on the list (or not in this group), adding him to group %s.\n", name, group);
377                 if (!(g = purple_find_group(group))) {
378                         g = purple_group_new(group);
379                         purple_blist_add_group(g, NULL);
380                 }
381                 b = purple_buddy_new(account, name, NULL);
382                 purple_blist_add_buddy(b, NULL, g, NULL);
383         }
384
385         if (list) {
386                 if (!oname)
387                         oname = g_strdup(purple_normalize(account, name));
388                 g_hash_table_insert(ht, oname, list);
389         } else if (oname)
390                 g_free(oname);
391 }
392
393 static void yahoo_do_group_cleanup(gpointer key, gpointer value, gpointer user_data)
394 {
395         char *name = key;
396         GSList *list = value, *i;
397         PurpleBuddy *b;
398         PurpleGroup *g;
399
400         for (i = list; i; i = i->next) {
401                 b = i->data;
402                 g = purple_buddy_get_group(b);
403                 purple_debug(PURPLE_DEBUG_MISC, "yahoo", "Deleting Buddy %s from group %s.\n", name, g->name);
404                 purple_blist_remove_buddy(b);
405         }
406 }
407
408 static char *_getcookie(char *rawcookie)
409 {
410         char *cookie = NULL;
411         char *tmpcookie;
412         char *cookieend;
413
414         if (strlen(rawcookie) < 2)
415                 return NULL;
416         tmpcookie = g_strdup(rawcookie+2);
417         cookieend = strchr(tmpcookie, ';');
418
419         if (cookieend)
420                 *cookieend = '\0';
421
422         cookie = g_strdup(tmpcookie);
423         g_free(tmpcookie);
424
425         return cookie;
426 }
427
428 static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
429 {
430         if (c[0] == 'Y') {
431                 if (yd->cookie_y)
432                         g_free(yd->cookie_y);
433                 yd->cookie_y = _getcookie(c);
434         } else if (c[0] == 'T') {
435                 if (yd->cookie_t)
436                         g_free(yd->cookie_t);
437                 yd->cookie_t = _getcookie(c);
438         } else
439                 purple_debug_info("yahoo", "Unrecognized cookie '%c'\n", c[0]);
440         yd->cookies = g_slist_prepend(yd->cookies, g_strdup(c));
441 }
442
443 static void yahoo_process_list_15(PurpleConnection *gc, struct yahoo_packet *pkt)
444 {
445         GSList *l = pkt->hash;
446
447         PurpleAccount *account = purple_connection_get_account(gc);
448         struct yahoo_data *yd = gc->proto_data;
449         GHashTable *ht;
450         char *norm_bud = NULL;
451         YahooFriend *f = NULL; /* It's your friends. They're going to want you to share your StarBursts. */
452                                /* But what if you had no friends? */
453         PurpleBuddy *b;
454         PurpleGroup *g;
455
456
457         ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
458
459         while (l) {
460                 struct yahoo_pair *pair = l->data;
461                 l = l->next;
462
463                 switch (pair->key) {
464                 case 302:
465                         /* This is always 318 before a group, 319 before the first s/n in a group, 320 before any ignored s/n.
466                          * It is not sent for s/n's in a group after the first.
467                          * All ignored s/n's are listed last, so when we see a 320 we clear the group and begin marking the
468                          * s/n's as ignored.  It is always followed by an identical 300 key.
469                          */
470                         if (pair->value && !strcmp(pair->value, "320")) {
471                                 /* No longer in any group; this indicates the start of the ignore list. */
472                                 g_free(yd->current_list15_grp);
473                                 yd->current_list15_grp = NULL;
474                         }
475
476                         break;
477                 case 301: /* This is 319 before all s/n's in a group after the first. It is followed by an identical 300. */
478                         break;
479                 case 300: /* This is 318 before a group, 319 before any s/n in a group, and 320 before any ignored s/n. */
480                         break;
481                 case 65: /* This is the group */
482                         g_free(yd->current_list15_grp);
483                         yd->current_list15_grp = yahoo_string_decode(gc, pair->value, FALSE);
484                         break;
485                 case 7: /* buddy's s/n */
486                         g_free(norm_bud);
487                         norm_bud = g_strdup(purple_normalize(account, pair->value));
488
489                         if (yd->current_list15_grp) {
490                                 /* This buddy is in a group */
491                                 f = yahoo_friend_find_or_new(gc, norm_bud);
492                                 if (!(b = purple_find_buddy(account, norm_bud))) {
493                                         if (!(g = purple_find_group(yd->current_list15_grp))) {
494                                                 g = purple_group_new(yd->current_list15_grp);
495                                                 purple_blist_add_group(g, NULL);
496                                         }
497                                         b = purple_buddy_new(account, norm_bud, NULL);
498                                         purple_blist_add_buddy(b, NULL, g, NULL);
499                                 }
500                                 yahoo_do_group_check(account, ht, norm_bud, yd->current_list15_grp);
501
502                         } else {
503                                 /* This buddy is on the ignore list (and therefore in no group) */
504                                 purple_debug_info("yahoo", "%s adding %s to the deny list because of the ignore list / no group was found\n",
505                                                                   account->username, norm_bud);
506                                 purple_privacy_deny_add(account, norm_bud, 1);
507                         }
508                         break;
509                 case 241: /* another protocol user */
510                         if (f) {
511                                 f->protocol = strtol(pair->value, NULL, 10);
512                                 purple_debug_info("yahoo", "Setting protocol to %d\n", f->protocol);
513                         }
514                         break;
515                 case 59: /* somebody told cookies come here too, but im not sure */
516                         yahoo_process_cookie(yd, pair->value);
517                         break;
518                 case 317: /* Stealth Setting */
519                         if (f && (strtol(pair->value, NULL, 10) == 2)) {
520                                 f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
521                         }
522                         break;
523                 /* case 242: */ /* this seems related to 241 */
524                         /* break; */
525                 }
526         }
527
528         g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
529         
530         /* Now that we have processed the buddy list, we can say yahoo has connected */
531         purple_connection_set_display_name(gc, purple_normalize(account, purple_account_get_username(account)));
532         purple_connection_set_state(gc, PURPLE_CONNECTED);
533         yd->logged_in = TRUE;
534         if (yd->picture_upload_todo) {
535                 yahoo_buddy_icon_upload(gc, yd->picture_upload_todo);
536                 yd->picture_upload_todo = NULL;
537         }
538         yahoo_set_status(account, purple_account_get_active_status(account));
539         purple_debug_info("yahoo","Authentication: Connection established\n");
540
541         g_hash_table_destroy(ht);
542         g_free(norm_bud);
543 }
544
545 static void yahoo_process_list(PurpleConnection *gc, struct yahoo_packet *pkt)
546 {
547         GSList *l = pkt->hash;
548         gboolean export = FALSE;
549         gboolean got_serv_list = FALSE;
550         PurpleBuddy *b;
551         PurpleGroup *g;
552         YahooFriend *f = NULL;
553         PurpleAccount *account = purple_connection_get_account(gc);
554         struct yahoo_data *yd = gc->proto_data;
555         GHashTable *ht;
556
557         char **lines;
558         char **split;
559         char **buddies;
560         char **tmp, **bud, *norm_bud;
561         char *grp = NULL;
562
563         if (pkt->id)
564                 yd->session_id = pkt->id;
565
566         while (l) {
567                 struct yahoo_pair *pair = l->data;
568                 l = l->next;
569
570                 switch (pair->key) {
571                 case 87:
572                         if (!yd->tmp_serv_blist)
573                                 yd->tmp_serv_blist = g_string_new(pair->value);
574                         else
575                                 g_string_append(yd->tmp_serv_blist, pair->value);
576                         break;
577                 case 88:
578                         if (!yd->tmp_serv_ilist)
579                                 yd->tmp_serv_ilist = g_string_new(pair->value);
580                         else
581                                 g_string_append(yd->tmp_serv_ilist, pair->value);
582                         break;
583                 case 89:
584                         yd->profiles = g_strsplit(pair->value, ",", -1);
585                         break;
586                 case 59: /* cookies, yum */
587                         yahoo_process_cookie(yd, pair->value);
588                         break;
589                 case YAHOO_SERVICE_PRESENCE_PERM:
590                         if (!yd->tmp_serv_plist)
591                                 yd->tmp_serv_plist = g_string_new(pair->value);
592                         else
593                                 g_string_append(yd->tmp_serv_plist, pair->value);
594                         break;
595                 }
596         }
597
598         if (pkt->status != 0)
599                 return;
600
601         if (yd->tmp_serv_blist) {
602                 ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
603
604                 lines = g_strsplit(yd->tmp_serv_blist->str, "\n", -1);
605                 for (tmp = lines; *tmp; tmp++) {
606                         split = g_strsplit(*tmp, ":", 2);
607                         if (!split)
608                                 continue;
609                         if (!split[0] || !split[1]) {
610                                 g_strfreev(split);
611                                 continue;
612                         }
613                         grp = yahoo_string_decode(gc, split[0], FALSE);
614                         buddies = g_strsplit(split[1], ",", -1);
615                         for (bud = buddies; bud && *bud; bud++) {
616                                 norm_bud = g_strdup(purple_normalize(account, *bud));
617                                 f = yahoo_friend_find_or_new(gc, norm_bud);
618
619                                 if (!(b = purple_find_buddy(account, norm_bud))) {
620                                         if (!(g = purple_find_group(grp))) {
621                                                 g = purple_group_new(grp);
622                                                 purple_blist_add_group(g, NULL);
623                                         }
624                                         b = purple_buddy_new(account, norm_bud, NULL);
625                                         purple_blist_add_buddy(b, NULL, g, NULL);
626                                         export = TRUE;
627                                 }
628
629                                 yahoo_do_group_check(account, ht, norm_bud, grp);
630                                 g_free(norm_bud);
631                         }
632                         g_strfreev(buddies);
633                         g_strfreev(split);
634                         g_free(grp);
635                 }
636                 g_strfreev(lines);
637
638                 g_string_free(yd->tmp_serv_blist, TRUE);
639                 yd->tmp_serv_blist = NULL;
640                 g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
641                 g_hash_table_destroy(ht);
642         }
643
644         if (yd->tmp_serv_ilist) {
645                 buddies = g_strsplit(yd->tmp_serv_ilist->str, ",", -1);
646                 for (bud = buddies; bud && *bud; bud++) {
647                         /* The server is already ignoring the user */
648                         got_serv_list = TRUE;
649                         purple_privacy_deny_add(account, *bud, 1);
650                 }
651                 g_strfreev(buddies);
652
653                 g_string_free(yd->tmp_serv_ilist, TRUE);
654                 yd->tmp_serv_ilist = NULL;
655         }
656
657         if (got_serv_list &&
658                 ((account->perm_deny != PURPLE_PRIVACY_ALLOW_BUDDYLIST) &&
659                 (account->perm_deny != PURPLE_PRIVACY_DENY_ALL) &&
660                 (account->perm_deny != PURPLE_PRIVACY_ALLOW_USERS)))
661         {
662                 account->perm_deny = PURPLE_PRIVACY_DENY_USERS;
663                 purple_debug_info("yahoo", "%s privacy defaulting to PURPLE_PRIVACY_DENY_USERS.\n",
664                                 account->username);
665         }
666
667         if (yd->tmp_serv_plist) {
668                 buddies = g_strsplit(yd->tmp_serv_plist->str, ",", -1);
669                 for (bud = buddies; bud && *bud; bud++) {
670                         f = yahoo_friend_find(gc, *bud);
671                         if (f) {
672                                 purple_debug_info("yahoo", "%s setting presence for %s to PERM_OFFLINE\n",
673                                                 account->username, *bud);
674                                 f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
675                         }
676                 }
677                 g_strfreev(buddies);
678                 g_string_free(yd->tmp_serv_plist, TRUE);
679                 yd->tmp_serv_plist = NULL;
680
681         }
682         /* Now that we've got the list, request aliases */
683         yahoo_fetch_aliases(gc);
684 }
685
686 static void yahoo_process_notify(PurpleConnection *gc, struct yahoo_packet *pkt)
687 {
688         PurpleAccount *account;
689         char *msg = NULL;
690         char *from = NULL;
691         char *stat = NULL;
692         char *game = NULL;
693         YahooFriend *f = NULL;
694         GSList *l = pkt->hash;
695
696         account = purple_connection_get_account(gc);
697
698         while (l) {
699                 struct yahoo_pair *pair = l->data;
700                 if (pair->key == 4)
701                         from = pair->value;
702                 if (pair->key == 49)
703                         msg = pair->value;
704                 if (pair->key == 13)
705                         stat = pair->value;
706                 if (pair->key == 14)
707                         game = pair->value;
708                 l = l->next;
709         }
710
711         if (!from || !msg)
712                 return;
713
714         if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING"))
715                 && (purple_privacy_check(account, from)))
716         {
717                 if (*stat == '1')
718                         serv_got_typing(gc, from, 0, PURPLE_TYPING);
719                 else
720                         serv_got_typing_stopped(gc, from);
721         } else if (!g_ascii_strncasecmp(msg, "GAME", strlen("GAME"))) {
722                 PurpleBuddy *bud = purple_find_buddy(account, from);
723
724                 if (!bud) {
725                         purple_debug(PURPLE_DEBUG_WARNING, "yahoo",
726                                            "%s is playing a game, and doesn't want "
727                                            "you to know.\n", from);
728                 }
729
730                 f = yahoo_friend_find(gc, from);
731                 if (!f)
732                         return; /* if they're not on the list, don't bother */
733
734                 yahoo_friend_set_game(f, NULL);
735
736                 if (*stat == '1') {
737                         yahoo_friend_set_game(f, game);
738                         if (bud)
739                                 yahoo_update_status(gc, from, f);
740                 }
741         } else if (!g_ascii_strncasecmp(msg, "WEBCAMINVITE", strlen("WEBCAMINVITE"))) {
742                 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, from, account);
743                 char *buf = g_strdup_printf(_("%s has sent you a webcam invite, which is not yet supported."), from);
744                 purple_conversation_write(conv, NULL, buf, PURPLE_MESSAGE_SYSTEM|PURPLE_MESSAGE_NOTIFY, time(NULL));
745                 g_free(buf);
746         }
747
748 }
749
750
751 struct _yahoo_im {
752         char *from;
753         char *active_id;
754         int time;
755         int utf8;
756         int buddy_icon;
757         char *id;
758         char *msg;
759 };
760
761 static void yahoo_process_message(PurpleConnection *gc, struct yahoo_packet *pkt)
762 {
763         PurpleAccount *account;
764         struct yahoo_data *yd = gc->proto_data;
765         GSList *l = pkt->hash;
766         GSList *list = NULL;
767         struct _yahoo_im *im = NULL;
768         const char *imv = NULL;
769
770         account = purple_connection_get_account(gc);
771
772         if (pkt->status <= 1 || pkt->status == 5) {
773                 while (l != NULL) {
774                         struct yahoo_pair *pair = l->data;
775                         if (pair->key == 4) {
776                                 im = g_new0(struct _yahoo_im, 1);
777                                 list = g_slist_append(list, im);
778                                 im->from = pair->value;
779                                 im->time = time(NULL);
780                                 im->utf8 = TRUE;
781                         }
782                         if (im && pair->key == 5)
783                                 im->active_id = pair->value;
784                         if (pair->key == 97)
785                                 if (im)
786                                         im->utf8 = strtol(pair->value, NULL, 10);
787                         if (pair->key == 15)
788                                 if (im)
789                                         im->time = strtol(pair->value, NULL, 10);
790                         if (pair->key == 206)
791                                 if (im)
792                                         im->buddy_icon = strtol(pair->value, NULL, 10);
793                         if (pair->key == 14) {
794                                 if (im)
795                                         im->msg = pair->value;
796                         }
797                         /* IMV key */
798                         if (pair->key == 63)
799                         {
800                                 imv = pair->value;
801                         }
802                         if (pair->key == 429)
803                                 if (im)
804                                         im->id = pair->value;
805                         l = l->next;
806                 }
807         } else if (pkt->status == 2) {
808                 purple_notify_error(gc, NULL,
809                                   _("Your Yahoo! message did not get sent."), NULL);
810         }
811
812         /** TODO: It seems that this check should be per IM, not global */
813         /* Check for the Doodle IMV */
814         if (im != NULL && imv!= NULL && im->from != NULL)
815         {
816                 g_hash_table_replace(yd->imvironments, g_strdup(im->from), g_strdup(imv));
817
818                 if (strstr(imv, "doodle;") != NULL)
819                 {
820                         PurpleWhiteboard *wb;
821
822                         if (!purple_privacy_check(account, im->from)) {
823                                 purple_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from);
824                                 return;
825                         }
826
827                         /* I'm not sure the following ever happens -DAA */
828
829                         wb = purple_whiteboard_get_session(account, im->from);
830
831                         /* If a Doodle session doesn't exist between this user */
832                         if(wb == NULL)
833                         {
834                                 doodle_session *ds;
835                                 wb = purple_whiteboard_create(account, im->from, DOODLE_STATE_REQUESTED);
836                                 ds = wb->proto_data;
837                                 ds->imv_key = g_strdup(imv);
838
839                                 yahoo_doodle_command_send_request(gc, im->from, imv);
840                                 yahoo_doodle_command_send_ready(gc, im->from, imv);
841                         }
842                 }
843         }
844
845         for (l = list; l; l = l->next) {
846                 YahooFriend *f;
847                 char *m, *m2;
848                 im = l->data;
849
850                 if (!im->from || !im->msg) {
851                         g_free(im);
852                         continue;
853                 }
854
855                 if (!purple_privacy_check(account, im->from)) {
856                         purple_debug_info("yahoo", "Message from %s dropped.\n", im->from);
857                         return;
858                 }
859
860                 /*
861                  * TODO: Is there anything else we should check when determining whether
862                  *       we should send an acknowledgement?
863                  */
864                 if (im->id != NULL) {
865                         /* Send acknowledgement.  If we don't do this then the official
866                          * Yahoo Messenger client for Windows will send us the same
867                          * message 7 seconds later as an offline message.  This is true
868                          * for at least version 9.0.0.2162 on Windows XP. */
869                         struct yahoo_packet *pkt2;
870                         pkt2 = yahoo_packet_new(YAHOO_SERVICE_MESSAGE_ACK,
871                                         YAHOO_STATUS_AVAILABLE, pkt->id);
872                         yahoo_packet_hash(pkt2, "ssisii",
873                                         1, im->active_id,  /* May not always be the connection's display name */
874                                         5, im->from,
875                                         302, 430,
876                                         430, im->id,
877                                         303, 430,
878                                         450, 0);
879                         yahoo_packet_send_and_free(pkt2, yd);
880                 }
881
882                 m = yahoo_string_decode(gc, im->msg, im->utf8);
883                 /* This may actually not be necessary, but it appears
884                  * that at least at one point some clients were sending
885                  * "\r\n" as line delimiters, so we want to avoid double
886                  * lines. */
887                 m2 = purple_strreplace(m, "\r\n", "\n");
888                 g_free(m);
889                 m = m2;
890                 purple_util_chrreplace(m, '\r', '\n');
891
892                 if (!strcmp(m, "<ding>")) {
893                         PurpleConversation *c;
894                         char *username;
895
896                         c = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, im->from, account);
897                         if (c == NULL)
898                                 c = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, im->from);
899
900                         username = g_markup_escape_text(im->from, -1);
901                         purple_prpl_got_attention(gc, username, YAHOO_BUZZ);
902                         g_free(username);
903                         g_free(m);
904                         g_free(im);
905                         continue;
906                 }
907
908                 m2 = yahoo_codes_to_html(m);
909                 g_free(m);
910                 serv_got_im(gc, im->from, m2, 0, im->time);
911                 g_free(m2);
912
913                 if ((f = yahoo_friend_find(gc, im->from)) && im->buddy_icon == 2) {
914                         if (yahoo_friend_get_buddy_icon_need_request(f)) {
915                                 yahoo_send_picture_request(gc, im->from);
916                                 yahoo_friend_set_buddy_icon_need_request(f, FALSE);
917                         }
918                 }
919
920                 g_free(im);
921         }
922         g_slist_free(list);
923 }
924
925 static void yahoo_process_sysmessage(PurpleConnection *gc, struct yahoo_packet *pkt)
926 {
927         GSList *l = pkt->hash;
928         char *prim, *me = NULL, *msg = NULL;
929
930         while (l) {
931                 struct yahoo_pair *pair = l->data;
932
933                 if (pair->key == 5)
934                         me = pair->value;
935                 if (pair->key == 14)
936                         msg = pair->value;
937
938                 l = l->next;
939         }
940
941         if (!msg || !g_utf8_validate(msg, -1, NULL))
942                 return;
943
944         prim = g_strdup_printf(_("Yahoo! system message for %s:"),
945                                me?me:purple_connection_get_display_name(gc));
946         purple_notify_info(NULL, NULL, prim, msg);
947         g_free(prim);
948 }
949
950 struct yahoo_add_request {
951         PurpleConnection *gc;
952         char *id;
953         char *who;
954         int protocol;
955 };
956
957 static void
958 yahoo_buddy_add_authorize_cb(gpointer data)
959 {
960         struct yahoo_add_request *add_req = data;
961         struct yahoo_packet *pkt;
962         struct yahoo_data *yd = add_req->gc->proto_data;
963
964         pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15, YAHOO_STATUS_AVAILABLE, 0);
965         yahoo_packet_hash(pkt, "ssiii",
966                                           1, add_req->id,
967                                           5, add_req->who,
968                                           241, add_req->protocol,
969                                           13, 1,
970                                           334, 0);
971         yahoo_packet_send_and_free(pkt, yd);
972
973         g_free(add_req->id);
974         g_free(add_req->who);
975         g_free(add_req);
976 }
977
978 static void
979 yahoo_buddy_add_deny_cb(struct yahoo_add_request *add_req, const char *msg)
980 {
981         struct yahoo_data *yd = add_req->gc->proto_data;
982         struct yahoo_packet *pkt;
983         char *encoded_msg = NULL;
984         PurpleAccount *account = purple_connection_get_account(add_req->gc);
985
986         if (msg && *msg)
987                 encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL);
988
989         pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH_REQ_15,
990                         YAHOO_STATUS_AVAILABLE, 0);
991
992         yahoo_packet_hash(pkt, "ssiiis",
993                         1, purple_normalize(account, purple_account_get_username(account)),
994                         5, add_req->who,
995                         13, 2,
996                         334, 0,
997                         97, 1,
998                         14, encoded_msg ? encoded_msg : "");
999
1000         yahoo_packet_send_and_free(pkt, yd);
1001
1002         g_free(encoded_msg);
1003
1004         g_free(add_req->id);
1005         g_free(add_req->who);
1006         g_free(add_req);
1007 }
1008
1009 static void
1010 yahoo_buddy_add_deny_noreason_cb(struct yahoo_add_request *add_req, const char*msg)
1011 {
1012         yahoo_buddy_add_deny_cb(add_req, NULL);
1013 }
1014
1015 static void
1016 yahoo_buddy_add_deny_reason_cb(gpointer data) {
1017         struct yahoo_add_request *add_req = data;
1018         purple_request_input(add_req->gc, NULL, _("Authorization denied message:"),
1019                         NULL, _("No reason given."), TRUE, FALSE, NULL,
1020                         _("OK"), G_CALLBACK(yahoo_buddy_add_deny_cb),
1021                         _("Cancel"), G_CALLBACK(yahoo_buddy_add_deny_noreason_cb),
1022                         purple_connection_get_account(add_req->gc), add_req->who, NULL,
1023                         add_req);
1024 }
1025
1026 static void yahoo_buddy_denied_our_add(PurpleConnection *gc, const char *who, const char *reason)
1027 {
1028         char *notify_msg;
1029         struct yahoo_data *yd = gc->proto_data;
1030
1031         if (who == NULL)
1032                 return;
1033
1034         if (reason != NULL) {
1035                 char *msg2 = yahoo_string_decode(gc, reason, FALSE);
1036                 notify_msg = g_strdup_printf(_("%s has (retroactively) denied your request to add them to your list for the following reason: %s."), who, msg2);
1037                 g_free(msg2);
1038         } else
1039                 notify_msg = g_strdup_printf(_("%s has (retroactively) denied your request to add them to your list."), who);
1040
1041         purple_notify_info(gc, NULL, _("Add buddy rejected"), notify_msg);
1042         g_free(notify_msg);
1043
1044         g_hash_table_remove(yd->friends, who);
1045         purple_prpl_got_user_status(purple_connection_get_account(gc), who, "offline", NULL); /* FIXME: make this set not on list status instead */
1046         /* TODO: Shouldn't we remove the buddy from our local list? */
1047 }
1048
1049 static void yahoo_buddy_auth_req_15(PurpleConnection *gc, struct yahoo_packet *pkt) {
1050         PurpleAccount *account;
1051         GSList *l = pkt->hash;
1052         const char *msg = NULL;
1053
1054         account = purple_connection_get_account(gc);
1055
1056         /* Buddy authorized/declined our addition */
1057         if (pkt->status == 1) {
1058                 const char *who = NULL;
1059                 int response = 0;
1060
1061                 while (l) {
1062                         struct yahoo_pair *pair = l->data;
1063
1064                         switch (pair->key) {
1065                         case 4:
1066                                 who = pair->value;
1067                                 break;
1068                         case 13:
1069                                 response = strtol(pair->value, NULL, 10);
1070                                 break;
1071                         case 14:
1072                                 msg = pair->value;
1073                                 break;
1074                         }
1075                         l = l->next;
1076                 }
1077
1078                 if (response == 1) /* Authorized */
1079                         purple_debug_info("yahoo", "Received authorization from buddy '%s'.\n", who ? who : "(Unknown Buddy)");
1080                 else if (response == 2) { /* Declined */
1081                         purple_debug_info("yahoo", "Received authorization decline from buddy '%s'.\n", who ? who : "(Unknown Buddy)");
1082                         yahoo_buddy_denied_our_add(gc, who, msg);
1083                 } else
1084                         purple_debug_error("yahoo", "Received unknown authorization response of %d from buddy '%s'.\n", response, who ? who : "(Unknown Buddy)");
1085
1086         }
1087         /* Buddy requested authorization to add us. */
1088         else if (pkt->status == 3) {
1089                 struct yahoo_add_request *add_req;
1090                 const char *firstname = NULL, *lastname = NULL;
1091
1092                 add_req = g_new0(struct yahoo_add_request, 1);
1093                 add_req->gc = gc;
1094
1095                 while (l) {
1096                         struct yahoo_pair *pair = l->data;
1097
1098                         switch (pair->key) {
1099                         case 4:
1100                                 add_req->who = g_strdup(pair->value);
1101                                 break;
1102                         case 5:
1103                                 add_req->id = g_strdup(pair->value);
1104                                 break;
1105                         case 14:
1106                                 msg = pair->value;
1107                                 break;
1108                         case 216:
1109                                 firstname = pair->value;
1110                                 break;
1111                         case 241:
1112                                 add_req->protocol = strtol(pair->value, NULL, 10);
1113                                 break;
1114                         case 254:
1115                                 lastname = pair->value;
1116                                 break;
1117
1118                         }
1119                         l = l->next;
1120                 }
1121
1122                 if (add_req->id && add_req->who) {
1123                         char *alias = NULL, *dec_msg = NULL;
1124
1125                         if (!purple_privacy_check(account, add_req->who))
1126                         {
1127                                 purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n",
1128                                                   add_req->who);
1129                                 yahoo_buddy_add_deny_cb(add_req, NULL);
1130                                 return;
1131                         }
1132
1133                         if (msg)
1134                                 dec_msg = yahoo_string_decode(gc, msg, FALSE);
1135
1136                         if (firstname && lastname)
1137                                 alias = g_strdup_printf("%s %s", firstname, lastname);
1138                         else if (firstname)
1139                                 alias = g_strdup(firstname);
1140                         else if (lastname)
1141                                 alias = g_strdup(lastname);
1142
1143                         /* DONE! this is almost exactly the same as what MSN does,
1144                          * this should probably be moved to the core.
1145                          */
1146                          purple_account_request_authorization(account, add_req->who, add_req->id,
1147                                         alias, dec_msg,
1148                                         purple_find_buddy(account, add_req->who) != NULL,
1149                                         yahoo_buddy_add_authorize_cb,
1150                                         yahoo_buddy_add_deny_reason_cb,
1151                                         add_req);
1152                         g_free(alias);
1153                         g_free(dec_msg);
1154                 } else {
1155                         g_free(add_req->id);
1156                         g_free(add_req->who);
1157                         g_free(add_req);
1158                 }
1159         } else {
1160                 purple_debug_error("yahoo", "Received authorization of unknown status (%d).\n", pkt->status);
1161         }
1162 }
1163
1164 /* I don't think this happens anymore in Version 15 */
1165 static void yahoo_buddy_added_us(PurpleConnection *gc, struct yahoo_packet *pkt) {
1166         PurpleAccount *account;
1167         struct yahoo_add_request *add_req;
1168         char *msg = NULL;
1169         GSList *l = pkt->hash;
1170
1171         account = purple_connection_get_account(gc);
1172
1173         add_req = g_new0(struct yahoo_add_request, 1);
1174         add_req->gc = gc;
1175
1176         while (l) {
1177                 struct yahoo_pair *pair = l->data;
1178
1179                 switch (pair->key) {
1180                 case 1:
1181                         add_req->id = g_strdup(pair->value);
1182                         break;
1183                 case 3:
1184                         add_req->who = g_strdup(pair->value);
1185                         break;
1186                 case 15: /* time, for when they add us and we're offline */
1187                         break;
1188                 case 14:
1189                         msg = pair->value;
1190                         break;
1191                 }
1192                 l = l->next;
1193         }
1194
1195         if (add_req->id && add_req->who) {
1196                 char *dec_msg = NULL;
1197
1198                 if (!purple_privacy_check(account, add_req->who)) {
1199                         purple_debug_misc("yahoo", "Auth. request from %s dropped and automatically denied due to privacy settings!\n",
1200                                           add_req->who);
1201                         yahoo_buddy_add_deny_cb(add_req, NULL);
1202                         return;
1203                 }
1204
1205                 if (msg)
1206                         dec_msg = yahoo_string_decode(gc, msg, FALSE);
1207
1208                 /* DONE! this is almost exactly the same as what MSN does,
1209                  * this should probably be moved to the core.
1210                  */
1211                  purple_account_request_authorization(account, add_req->who, add_req->id,
1212                                 NULL, dec_msg,
1213                                 purple_find_buddy(account,add_req->who) != NULL,
1214                                                 yahoo_buddy_add_authorize_cb,
1215                                                 yahoo_buddy_add_deny_reason_cb, add_req);
1216                 g_free(dec_msg);
1217         } else {
1218                 g_free(add_req->id);
1219                 g_free(add_req->who);
1220                 g_free(add_req);
1221         }
1222 }
1223
1224 /* I have no idea if this every gets called in version 15 */
1225 static void yahoo_buddy_denied_our_add_old(PurpleConnection *gc, struct yahoo_packet *pkt)
1226 {
1227         char *who = NULL;
1228         char *msg = NULL;
1229         GSList *l = pkt->hash;
1230
1231         while (l) {
1232                 struct yahoo_pair *pair = l->data;
1233
1234                 switch (pair->key) {
1235                 case 3:
1236                         who = pair->value;
1237                         break;
1238                 case 14:
1239                         msg = pair->value;
1240                         break;
1241                 }
1242                 l = l->next;
1243         }
1244
1245         yahoo_buddy_denied_our_add(gc, who, msg);
1246 }
1247
1248 static void yahoo_process_contact(PurpleConnection *gc, struct yahoo_packet *pkt)
1249 {
1250         switch (pkt->status) {
1251         case 1:
1252                 yahoo_process_status(gc, pkt);
1253                 return;
1254         case 3:
1255                 yahoo_buddy_added_us(gc, pkt);
1256                 break;
1257         case 7:
1258                 yahoo_buddy_denied_our_add_old(gc, pkt);
1259                 break;
1260         default:
1261                 break;
1262         }
1263 }
1264
1265 #define OUT_CHARSET "utf-8"
1266
1267 static char *yahoo_decode(const char *text)
1268 {
1269         char *converted = NULL;
1270         char *n, *new;
1271         const char *end, *p;
1272         int i, k;
1273
1274         n = new = g_malloc(strlen (text) + 1);
1275         end = text + strlen(text);
1276
1277         for (p = text; p < end; p++, n++) {
1278                 if (*p == '\\') {
1279                         if (p[1] >= '0' && p[1] <= '7') {
1280                                 p += 1;
1281                                 for (i = 0, k = 0; k < 3; k += 1) {
1282                                         char c = p[k];
1283                                         if (c < '0' || c > '7') break;
1284                                         i *= 8;
1285                                         i += c - '0';
1286                                 }
1287                                 *n = i;
1288                                 p += k - 1;
1289                         } else { /* bug 959248 */
1290                                 /* If we see a \ not followed by an octal number,
1291                                  * it means that it is actually a \\ with one \
1292                                  * already eaten by some unknown function.
1293                                  * This is arguably broken.
1294                                  *
1295                                  * I think wing is wrong here, there is no function
1296                                  * called that I see that could have done it. I guess
1297                                  * it is just really sending single \'s. That's yahoo
1298                                  * for you.
1299                                  */
1300                                 *n = *p;
1301                         }
1302                 }
1303                 else
1304                         *n = *p;
1305         }
1306
1307         *n = '\0';
1308
1309         if (strstr(text, "\033$B"))
1310                 converted = g_convert(new, n - new, OUT_CHARSET, "iso-2022-jp", NULL, NULL, NULL);
1311         if (!converted)
1312                 converted = g_convert(new, n - new, OUT_CHARSET, "iso-8859-1", NULL, NULL, NULL);
1313         g_free(new);
1314
1315         return converted;
1316 }
1317
1318 static void yahoo_process_mail(PurpleConnection *gc, struct yahoo_packet *pkt)
1319 {
1320         PurpleAccount *account = purple_connection_get_account(gc);
1321         struct yahoo_data *yd = gc->proto_data;
1322         const char *who = NULL;
1323         const char *email = NULL;
1324         const char *subj = NULL;
1325         const char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
1326         int count = 0;
1327         GSList *l = pkt->hash;
1328
1329         if (!purple_account_get_check_mail(account))
1330                 return;
1331
1332         while (l) {
1333                 struct yahoo_pair *pair = l->data;
1334                 if (pair->key == 9)
1335                         count = strtol(pair->value, NULL, 10);
1336                 else if (pair->key == 43)
1337                         who = pair->value;
1338                 else if (pair->key == 42)
1339                         email = pair->value;
1340                 else if (pair->key == 18)
1341                         subj = pair->value;
1342                 l = l->next;
1343         }
1344
1345         if (who && subj && email && *email) {
1346                 char *dec_who = yahoo_decode(who);
1347                 char *dec_subj = yahoo_decode(subj);
1348                 char *from = g_strdup_printf("%s (%s)", dec_who, email);
1349
1350                 purple_notify_email(gc, dec_subj, from, purple_account_get_username(account),
1351                                                   yahoo_mail_url, NULL, NULL);
1352
1353                 g_free(dec_who);
1354                 g_free(dec_subj);
1355                 g_free(from);
1356         } else if (count > 0) {
1357                 const char *tos[2] = { purple_account_get_username(account) };
1358                 const char *urls[2] = { yahoo_mail_url };
1359
1360                 purple_notify_emails(gc, count, FALSE, NULL, NULL, tos, urls,
1361                                                    NULL, NULL);
1362         }
1363 }
1364
1365 /* We use this structure once while we authenticate */
1366 struct yahoo_auth_data
1367 {
1368         PurpleConnection *gc;
1369         char *seed;
1370 };
1371
1372 /* This is the y64 alphabet... it's like base64, but has a . and a _ */
1373 static const char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
1374
1375 /* This is taken from Sylpheed by Hiroyuki Yamamoto. We have our own tobase64 function
1376  * in util.c, but it is different from the one yahoo uses */
1377 static void to_y64(char *out, const unsigned char *in, gsize inlen)
1378      /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
1379 {
1380         for (; inlen >= 3; inlen -= 3)
1381                 {
1382                         *out++ = base64digits[in[0] >> 2];
1383                         *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
1384                         *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
1385                         *out++ = base64digits[in[2] & 0x3f];
1386                         in += 3;
1387                 }
1388         if (inlen > 0)
1389                 {
1390                         unsigned char fragment;
1391
1392                         *out++ = base64digits[in[0] >> 2];
1393                         fragment = (in[0] << 4) & 0x30;
1394                         if (inlen > 1)
1395                                 fragment |= in[1] >> 4;
1396                         *out++ = base64digits[fragment];
1397                         *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c];
1398                         *out++ = '-';
1399                 }
1400         *out = '\0';
1401 }
1402
1403 static void yahoo_auth16_stage3(PurpleConnection *gc, const char *crypt)
1404 {
1405         struct yahoo_data *yd = gc->proto_data;
1406         PurpleAccount *account = purple_connection_get_account(gc);
1407         const char *name = purple_normalize(account, purple_account_get_username(account));
1408         PurpleCipher *md5_cipher;
1409         PurpleCipherContext *md5_ctx;
1410         guchar md5_digest[16];
1411         gchar base64_string[25];
1412         struct yahoo_packet *pkt;
1413
1414         purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage3\n");
1415
1416         md5_cipher = purple_ciphers_find_cipher("md5");
1417         md5_ctx = purple_cipher_context_new(md5_cipher, NULL);
1418         purple_cipher_context_append(md5_ctx, (guchar *)crypt, strlen(crypt));
1419         purple_cipher_context_digest(md5_ctx, sizeof(md5_digest), md5_digest, NULL);
1420
1421         to_y64(base64_string, md5_digest, 16);
1422
1423         purple_debug_info("yahoo", "yahoo status: %d\n", yd->current_status);
1424         pkt = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, yd->session_id);
1425         if(yd->jp) {
1426                 yahoo_packet_hash(pkt, "ssssssss",
1427                                         1, name,
1428                                         0, name,
1429                                         277, yd->cookie_y,
1430                                         278, yd->cookie_t,
1431                                         307, base64_string,
1432                                         2, name,
1433                                         2, "1",
1434                                         135, YAHOOJP_CLIENT_VERSION);
1435         } else  {
1436                 yahoo_packet_hash(pkt, "sssssssss",
1437                                         1, name,
1438                                         0, name,
1439                                         277, yd->cookie_y,
1440                                         278, yd->cookie_t,
1441                                         307, base64_string,
1442                                         244, YAHOO_CLIENT_VERSION_ID,
1443                                         2, name,
1444                                         2, "1",
1445                                         135, YAHOO_CLIENT_VERSION);
1446         }
1447         if (yd->picture_checksum)
1448                 yahoo_packet_hash_int(pkt, 192, yd->picture_checksum);
1449         yahoo_packet_send_and_free(pkt, yd);
1450
1451         purple_cipher_context_destroy(md5_ctx);
1452 }
1453
1454 static void yahoo_auth16_stage2(PurpleUtilFetchUrlData *unused, gpointer user_data, const gchar *ret_data, size_t len, const gchar *error_message)
1455 {
1456         struct yahoo_auth_data *auth_data = user_data;
1457         PurpleConnection *gc = auth_data->gc;
1458         struct yahoo_data *yd;
1459         gboolean try_login_on_error = FALSE;
1460
1461         purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage2\n");
1462
1463         if (!PURPLE_CONNECTION_IS_VALID(gc)) {
1464                 g_free(auth_data->seed);
1465                 g_free(auth_data);
1466                 g_return_if_reached();
1467         }
1468
1469         yd = (struct yahoo_data *)gc->proto_data;
1470
1471         if (error_message != NULL) {
1472                 purple_debug_error("yahoo", "Login Failed, unable to retrieve stage 2 url: %s\n", error_message);
1473                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
1474                 g_free(auth_data->seed);
1475                 g_free(auth_data);      
1476                 return;
1477         }
1478         else if (len > 0 && ret_data && *ret_data) {
1479                 gchar **split_data = g_strsplit(ret_data, "\r\n", -1);
1480                 int totalelements = 0;
1481                 int response_no = -1;
1482                 char *crumb = NULL;
1483                 char *crypt = NULL;
1484
1485 #if GLIB_CHECK_VERSION(2,6,0)
1486                 totalelements = g_strv_length(split_data);
1487 #else
1488                 while (split_data[++totalelements] != NULL);    
1489 #endif
1490                 if (totalelements >= 4) {
1491                         response_no = strtol(split_data[0], NULL, 10);
1492                         crumb = g_strdup(split_data[1] + strlen("crumb="));
1493                         yd->cookie_y = g_strdup(split_data[2] + strlen("Y="));
1494                         yd->cookie_t = g_strdup(split_data[3] + strlen("T="));
1495                 }
1496
1497                 g_strfreev(split_data);
1498
1499                 if(response_no != 0) {
1500                         /* Some error in the login process */
1501                         PurpleConnectionError error;
1502                         char *error_reason = NULL;
1503
1504                         switch(response_no) {
1505                                 case -1:
1506                                         /* Some error in the received stream */
1507                                         error_reason = g_strdup(_("Received invalid data"));
1508                                         error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1509                                         break;
1510                                 case 100:
1511                                         /* Unknown error */
1512                                         error_reason = g_strdup(_("Unknown error"));
1513                                         error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1514                                         break;
1515                                 default:
1516                                         /* if we have everything we need, why not try to login irrespective of response */
1517                                         if((crumb != NULL) && (yd->cookie_y != NULL) && (yd->cookie_t != NULL)) {
1518                                                 try_login_on_error = TRUE;
1519                                                 break;
1520                                         }
1521                                         error_reason = g_strdup(_("Unknown error"));
1522                                         error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1523                                         break;
1524                         }
1525                         if(error_reason) {
1526                                 purple_debug_error("yahoo", "Authentication error: %s\n",
1527                                                    error_reason);
1528                                 purple_connection_error_reason(gc, error, error_reason);
1529                                 g_free(error_reason);
1530                                 g_free(auth_data->seed);
1531                                 g_free(auth_data);
1532                                 return;
1533                         }
1534                 }
1535
1536                 crypt = g_strconcat(crumb, auth_data->seed, NULL);
1537                 yahoo_auth16_stage3(gc, crypt);
1538                 g_free(crypt);
1539                 g_free(crumb);
1540         }
1541         g_free(auth_data->seed);
1542         g_free(auth_data);
1543 }
1544
1545 static void yahoo_auth16_stage1_cb(PurpleUtilFetchUrlData *unused, gpointer user_data, const gchar *ret_data, size_t len, const gchar *error_message)
1546 {
1547         struct yahoo_auth_data *auth_data = user_data;
1548         PurpleConnection *gc = auth_data->gc;
1549
1550         purple_debug_info("yahoo","Authentication: In yahoo_auth16_stage1_cb\n");
1551
1552         if (!PURPLE_CONNECTION_IS_VALID(gc)) {
1553                 g_free(auth_data->seed);
1554                 g_free(auth_data);
1555                 g_return_if_reached();
1556         }
1557
1558         if (error_message != NULL) {
1559                 purple_debug_error("yahoo", "Login Failed, unable to retrieve login url: %s\n", error_message);
1560                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, error_message);
1561                 g_free(auth_data->seed);
1562                 g_free(auth_data);
1563                 return;
1564         }
1565         else if (len > 0 && ret_data && *ret_data) {
1566                 gchar **split_data = g_strsplit(ret_data, "\r\n", -1);
1567                 int totalelements = 0;
1568                 int response_no = -1;
1569                 char *token = NULL;
1570
1571 #if GLIB_CHECK_VERSION(2,6,0)
1572                 totalelements = g_strv_length(split_data);
1573 #else
1574                 while (split_data[++totalelements] != NULL);    
1575 #endif
1576                 if(totalelements >= 2) {
1577                         response_no = strtol(split_data[0], NULL, 10);
1578                         token = g_strdup(split_data[1] + strlen("ymsgr="));
1579                 }
1580
1581                 g_strfreev(split_data);
1582
1583                 if(response_no != 0) {
1584                         /* Some error in the login process */
1585                         PurpleConnectionError error;
1586                         char *error_reason;
1587
1588                         switch(response_no) {
1589                                 case -1:
1590                                         /* Some error in the received stream */
1591                                         error_reason = g_strdup(_("Received invalid data"));
1592                                         error = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1593                                         break;
1594                                 case 1212:
1595                                         /* Password incorrect */
1596                                         error_reason = g_strdup(_("Incorrect Password"));
1597                                         error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1598                                         break;
1599                                 case 1213:
1600                                         /* security lock from too many failed login attempts */
1601                                         error_reason = g_strdup(_("Account locked: Too many failed login attempts.\nLogging into the Yahoo! website may fix this."));
1602                                         error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1603                                         break;
1604                                 case 1235:
1605                                         /* the username does not exist */
1606                                         error_reason = g_strdup(_("Username does not exist"));
1607                                         error = PURPLE_CONNECTION_ERROR_INVALID_USERNAME;
1608                                         break;
1609                                 case 1214:
1610                                 case 1236:
1611                                         /* indicates a lock of some description */
1612                                         error_reason = g_strdup(_("Account locked: Unknown reason.\nLogging into the Yahoo! website may fix this."));
1613                                         error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1614                                         break;
1615                                 case 100:
1616                                         /* username or password missing */
1617                                         error_reason = g_strdup(_("Username or password missing"));
1618                                         error = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1619                                         break;
1620                                 default:
1621                                         /* Unknown error! */
1622                                         error_reason = g_strdup(_("Unknown error"));
1623                                         error = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1624                                         break;
1625                         }
1626                         purple_debug_error("yahoo", "Authentication error: %s\n",
1627                                            error_reason);
1628                         purple_connection_error_reason(gc, error, error_reason);
1629                         g_free(error_reason);
1630                         g_free(auth_data->seed);
1631                         g_free(auth_data);
1632                 }
1633                 else {
1634                         /* OK to login, correct information provided */
1635                         PurpleUtilFetchUrlData *url_data = NULL;
1636                         char *url = NULL;
1637                         gboolean yahoojp = purple_account_get_bool(purple_connection_get_account(gc),
1638                                 "yahoojp", 0);
1639
1640                         url = g_strdup_printf(yahoojp ? YAHOOJP_LOGIN_URL : YAHOO_LOGIN_URL, token);
1641                         url_data = purple_util_fetch_url_request_len_with_account(
1642                                         purple_connection_get_account(gc), url, TRUE,
1643                                         YAHOO_CLIENT_USERAGENT, TRUE, NULL, FALSE, -1,
1644                                         yahoo_auth16_stage2, auth_data);
1645                         g_free(url);
1646                         g_free(token);
1647                 }
1648         }
1649 }
1650
1651 static void yahoo_auth16_stage1(PurpleConnection *gc, const char *seed)
1652 {
1653         PurpleUtilFetchUrlData *url_data = NULL;
1654         struct yahoo_auth_data *auth_data = NULL;
1655         char *url = NULL;
1656         char *encoded_username;
1657         char *encoded_password;
1658         gboolean yahoojp;
1659
1660         purple_debug_info("yahoo", "Authentication: In yahoo_auth16_stage1\n");
1661
1662         if(!purple_ssl_is_supported()) {
1663                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NO_SSL_SUPPORT, _("SSL support unavailable"));
1664                 return;
1665         }
1666
1667         yahoojp =  purple_account_get_bool(purple_connection_get_account(gc),
1668                         "yahoojp", 0);
1669         auth_data = g_new0(struct yahoo_auth_data, 1);
1670         auth_data->gc = gc;
1671         auth_data->seed = g_strdup(seed);
1672
1673         encoded_username = g_strdup(purple_url_encode(purple_account_get_username(purple_connection_get_account(gc))));
1674         encoded_password = g_strdup(purple_url_encode(purple_connection_get_password(gc)));
1675         url = g_strdup_printf(yahoojp ? YAHOOJP_TOKEN_URL : YAHOO_TOKEN_URL,
1676                         encoded_username, encoded_password, purple_url_encode(seed));
1677         g_free(encoded_password);
1678         g_free(encoded_username);
1679
1680         url_data = purple_util_fetch_url_request_len_with_account(
1681                         purple_connection_get_account(gc), url, TRUE,
1682                         YAHOO_CLIENT_USERAGENT, TRUE, NULL, FALSE, -1,
1683                         yahoo_auth16_stage1_cb, auth_data);
1684
1685         g_free(url);
1686 }
1687
1688 static void yahoo_process_auth(PurpleConnection *gc, struct yahoo_packet *pkt)
1689 {
1690         char *seed = NULL;
1691         char *sn   = NULL;
1692         GSList *l = pkt->hash;
1693         int m = 0;
1694         gchar *buf;
1695
1696         while (l) {
1697                 struct yahoo_pair *pair = l->data;
1698                 if (pair->key == 94)
1699                         seed = pair->value;
1700                 if (pair->key == 1)
1701                         sn = pair->value;
1702                 if (pair->key == 13)
1703                         m = atoi(pair->value);
1704                 l = l->next;
1705         }
1706
1707         if (seed) {
1708                 switch (m) {
1709                 case 0:
1710                         /* used to be for really old auth routine, dont support now */
1711                 case 1:
1712                 case 2: /* Yahoo ver 16 authentication */
1713                         yahoo_auth16_stage1(gc, seed);
1714                         break;
1715                 default:
1716                         {
1717                                 GHashTable *ui_info = purple_core_get_ui_info();
1718
1719                                 buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized "
1720                                                         "authentication method.  You will probably not be able "
1721                                                         "to successfully sign on to Yahoo.  Check %s for updates."),
1722                                                         ((ui_info && g_hash_table_lookup(ui_info, "website")) ? (char *)g_hash_table_lookup(ui_info, "website") : PURPLE_WEBSITE));
1723                                 purple_notify_error(gc, "", _("Failed Yahoo! Authentication"),
1724                                                         buf);
1725                                 g_free(buf);
1726                                 yahoo_auth16_stage1(gc, seed); /* Can't hurt to try it anyway. */
1727                                 break;
1728                         }
1729                 }
1730         }
1731 }
1732
1733 static void ignore_buddy(PurpleBuddy *buddy) {
1734         PurpleGroup *group;
1735         PurpleAccount *account;
1736         gchar *name;
1737
1738         if (!buddy)
1739                 return;
1740
1741         group = purple_buddy_get_group(buddy);
1742         name = g_strdup(buddy->name);
1743         account = buddy->account;
1744
1745         purple_debug(PURPLE_DEBUG_INFO, "blist",
1746                 "Removing '%s' from buddy list.\n", buddy->name);
1747         purple_account_remove_buddy(account, buddy, group);
1748         purple_blist_remove_buddy(buddy);
1749
1750         serv_add_deny(account->gc, name);
1751
1752         g_free(name);
1753 }
1754
1755 static void keep_buddy(PurpleBuddy *b) {
1756         purple_privacy_deny_remove(b->account, b->name, 1);
1757 }
1758
1759 static void yahoo_process_ignore(PurpleConnection *gc, struct yahoo_packet *pkt) {
1760         PurpleBuddy *b;
1761         GSList *l;
1762         gchar *who = NULL;
1763         gchar *me = NULL;
1764         gchar buf[BUF_LONG];
1765         gboolean ignore = TRUE;
1766         gint status = 0;
1767
1768         for (l = pkt->hash; l; l = l->next) {
1769                 struct yahoo_pair *pair = l->data;
1770                 switch (pair->key) {
1771                 case 0:
1772                         who = pair->value;
1773                         break;
1774                 case 1:
1775                         me = pair->value;
1776                         break;
1777                 case 13:
1778                         /* 1 == ignore, 2 == unignore */
1779                         ignore = (strtol(pair->value, NULL, 10) == 1);
1780                         break;
1781                 case 66:
1782                         status = strtol(pair->value, NULL, 10);
1783                         break;
1784                 default:
1785                         break;
1786                 }
1787         }
1788
1789         /*
1790          * status
1791          * 0  - ok
1792          * 2  - already in ignore list, could not add
1793          * 3  - not in ignore list, could not delete
1794          * 12 - is a buddy, could not add (and possibly also a not-in-ignore list condition?)
1795          */
1796         switch (status) {
1797                 case 12:
1798                         purple_debug_info("yahoo", "Server reported \"is a buddy\" for %s while %s",
1799                                                           who, (ignore ? "ignoring" : "unignoring"));
1800
1801                         if (ignore) {
1802                                 b = purple_find_buddy(gc->account, who);
1803                                 g_snprintf(buf, sizeof(buf), _("You have tried to ignore %s, but the "
1804                                                                                            "user is on your buddy list.  Clicking \"Yes\" "
1805                                                                                            "will remove and ignore the buddy."), who);
1806                                 purple_request_yes_no(gc, NULL, _("Ignore buddy?"), buf, 0,
1807                                                                           gc->account, who, NULL,
1808                                                                           b,
1809                                                                           G_CALLBACK(ignore_buddy),
1810                                                                           G_CALLBACK(keep_buddy));
1811                                 break;
1812                         }
1813                 case 2:
1814                         purple_debug_info("yahoo", "Server reported that %s is already in the ignore list.",
1815                                                           who);
1816                         break;
1817                 case 3:
1818                         purple_debug_info("yahoo", "Server reported that %s is not in the ignore list; could not delete",
1819                                                           who);
1820                 case 0:
1821                 default:
1822                         break;
1823         }
1824 }
1825
1826 static void yahoo_process_authresp(PurpleConnection *gc, struct yahoo_packet *pkt)
1827 {
1828 #ifdef TRY_WEBMESSENGER_LOGIN
1829         struct yahoo_data *yd = gc->proto_data;
1830 #endif
1831         GSList *l = pkt->hash;
1832         int err = 0;
1833         char *msg;
1834         char *url = NULL;
1835         char *fullmsg;
1836         PurpleAccount *account = gc->account;
1837         PurpleConnectionError reason = PURPLE_CONNECTION_ERROR_OTHER_ERROR;
1838
1839         while (l) {
1840                 struct yahoo_pair *pair = l->data;
1841
1842                 if (pair->key == 66)
1843                         err = strtol(pair->value, NULL, 10);
1844                 else if (pair->key == 20)
1845                         url = pair->value;
1846
1847                 l = l->next;
1848         }
1849
1850         switch (err) {
1851         case 0:
1852                 msg = g_strdup(_("Unknown error."));
1853                 reason = PURPLE_CONNECTION_ERROR_NETWORK_ERROR;
1854                 break;
1855         case 3:
1856                 msg = g_strdup(_("Invalid username."));
1857                 reason = PURPLE_CONNECTION_ERROR_INVALID_USERNAME;
1858                 break;
1859         case 13:
1860 #ifdef TRY_WEBMESSENGER_LOGIN
1861                 if (!yd->wm) {
1862                         PurpleUtilFetchUrlData *url_data;
1863                         yd->wm = TRUE;
1864                         if (yd->fd >= 0)
1865                                 close(yd->fd);
1866                         if (gc->inpa)
1867                                 purple_input_remove(gc->inpa);
1868                         url_data = purple_util_fetch_url(WEBMESSENGER_URL, TRUE,
1869                                         "Purple/" VERSION, FALSE, yahoo_login_page_cb, gc);
1870                         if (url_data != NULL)
1871                                 yd->url_datas = g_slist_prepend(yd->url_datas, url_data);
1872                         return;
1873                 }
1874 #endif
1875                 if (!purple_account_get_remember_password(account))
1876                         purple_account_set_password(account, NULL);
1877
1878                 msg = g_strdup(_("Incorrect password."));
1879                 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1880                 break;
1881         case 14:
1882                 msg = g_strdup(_("Your account is locked, please log in to the Yahoo! website."));
1883                 reason = PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED;
1884                 break;
1885         default:
1886                 msg = g_strdup_printf(_("Unknown error number %d. Logging into the Yahoo! website may fix this."), err);
1887         }
1888
1889         if (url)
1890                 fullmsg = g_strdup_printf("%s\n%s", msg, url);
1891         else
1892                 fullmsg = g_strdup(msg);
1893
1894         purple_connection_error_reason(gc, reason, fullmsg);
1895         g_free(msg);
1896         g_free(fullmsg);
1897 }
1898
1899 static void yahoo_process_addbuddy(PurpleConnection *gc, struct yahoo_packet *pkt)
1900 {
1901         int err = 0;
1902         char *who = NULL;
1903         char *group = NULL;
1904         char *decoded_group;
1905         char *buf;
1906         YahooFriend *f;
1907         GSList *l = pkt->hash;
1908
1909         while (l) {
1910                 struct yahoo_pair *pair = l->data;
1911
1912                 switch (pair->key) {
1913                 case 66:
1914                         err = strtol(pair->value, NULL, 10);
1915                         break;
1916                 case 7:
1917                         who = pair->value;
1918                         break;
1919                 case 65:
1920                         group = pair->value;
1921                         break;
1922                 }
1923
1924                 l = l->next;
1925         }
1926
1927         if (!who)
1928                 return;
1929         if (!group)
1930                 group = "";
1931
1932         if (!err || (err == 2)) { /* 0 = ok, 2 = already on serv list */
1933                 f = yahoo_friend_find_or_new(gc, who);
1934                 yahoo_update_status(gc, who, f);
1935                 return;
1936         }
1937
1938         decoded_group = yahoo_string_decode(gc, group, FALSE);
1939         buf = g_strdup_printf(_("Could not add buddy %s to group %s to the server list on account %s."),
1940                                 who, decoded_group, purple_connection_get_display_name(gc));
1941         if (!purple_conv_present_error(who, purple_connection_get_account(gc), buf))
1942                 purple_notify_error(gc, NULL, _("Could not add buddy to server list"), buf);
1943         g_free(buf);
1944         g_free(decoded_group);
1945 }
1946
1947 static void yahoo_process_p2p(PurpleConnection *gc, struct yahoo_packet *pkt)
1948 {
1949         GSList *l = pkt->hash;
1950         char *who = NULL;
1951         char *base64 = NULL;
1952         guchar *decoded;
1953         gsize len;
1954
1955         while (l) {
1956                 struct yahoo_pair *pair = l->data;
1957
1958                 switch (pair->key) {
1959                 case 5:
1960                         /* our identity */
1961                         break;
1962                 case 4:
1963                         who = pair->value;
1964                         break;
1965                 case 1:
1966                         /* who again, the master identity this time? */
1967                         break;
1968                 case 12:
1969                         base64 = pair->value;
1970                         /* so, this is an ip address. in base64. decoded it's in ascii.
1971                            after strtol, it's in reversed byte order. Who thought this up?*/
1972                         break;
1973                 /*
1974                         TODO: figure these out
1975                         yahoo: Key: 61          Value: 0
1976                         yahoo: Key: 2   Value:
1977                         yahoo: Key: 13          Value: 0
1978                         yahoo: Key: 49          Value: PEERTOPEER
1979                         yahoo: Key: 140         Value: 1
1980                         yahoo: Key: 11          Value: -1786225828
1981                 */
1982
1983                 }
1984
1985                 l = l->next;
1986         }
1987
1988         if (base64) {
1989                 guint32 ip;
1990                 char *tmp2;
1991                 YahooFriend *f;
1992
1993                 decoded = purple_base64_decode(base64, &len);
1994                 if (len) {
1995                         char *tmp = purple_str_binary_to_ascii(decoded, len);
1996                         purple_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp);
1997                         g_free(tmp);
1998                 }
1999
2000                 tmp2 = g_strndup((const gchar *)decoded, len); /* so its \0 terminated...*/
2001                 ip = strtol(tmp2, NULL, 10);
2002                 g_free(tmp2);
2003                 g_free(decoded);
2004                 tmp2 = g_strdup_printf("%u.%u.%u.%u", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff,
2005                                        (ip >> 24) & 0xff);
2006                 f = yahoo_friend_find(gc, who);
2007                 if (f)
2008                         yahoo_friend_set_ip(f, tmp2);
2009                 g_free(tmp2);
2010         }
2011 }
2012
2013 static void yahoo_process_audible(PurpleConnection *gc, struct yahoo_packet *pkt)
2014 {
2015         PurpleAccount *account;
2016         char *who = NULL, *msg = NULL, *id = NULL;
2017         GSList *l = pkt->hash;
2018
2019         account = purple_connection_get_account(gc);
2020
2021         while (l) {
2022                 struct yahoo_pair *pair = l->data;
2023
2024                 switch (pair->key) {
2025                 case 4:
2026                         who = pair->value;
2027                         break;
2028                 case 5:
2029                         /* us */
2030                         break;
2031                 case 230:
2032                         /* the audible, in foo.locale.bar.baz format
2033                            eg: base.tw.smiley.smiley43 */
2034                         id = pair->value;
2035                         break;
2036                 case 231:
2037                         /* the text of the audible */
2038                         msg = pair->value;
2039                         break;
2040                 case 232:
2041                         /* weird number (md5 hash?), like 8ebab9094156135f5dcbaccbeee662a5c5fd1420 */
2042                         break;
2043                 }
2044
2045                 l = l->next;
2046         }
2047
2048         if (!msg)
2049                 msg = id;
2050         if (!who || !msg)
2051                 return;
2052         if (!g_utf8_validate(msg, -1, NULL)) {
2053                 purple_debug_misc("yahoo", "Warning, nonutf8 audible, ignoring!\n");
2054                 return;
2055         }
2056         if (!purple_privacy_check(account, who)) {
2057                 purple_debug_misc("yahoo", "Audible message from %s for %s dropped!\n",
2058                                 purple_account_get_username(account), who);
2059                 return;
2060         }
2061         if (id) {
2062                 /* "http://us.dl1.yimg.com/download.yahoo.com/dl/aud/"+locale+"/"+id+".swf" */
2063                 char **audible_locale = g_strsplit(id, ".", 0);
2064                 char *buf = g_strdup_printf(_("[ Audible %s/%s/%s.swf ] %s"), YAHOO_AUDIBLE_URL, audible_locale[1], id, msg);
2065                 g_strfreev(audible_locale);
2066
2067                 serv_got_im(gc, who, buf, 0, time(NULL));
2068                 g_free(buf);
2069         } else
2070                 serv_got_im(gc, who, msg, 0, time(NULL));
2071 }
2072
2073 static void yahoo_packet_process(PurpleConnection *gc, struct yahoo_packet *pkt)
2074 {
2075         switch (pkt->service) {
2076         case YAHOO_SERVICE_LOGON:
2077         case YAHOO_SERVICE_LOGOFF:
2078         case YAHOO_SERVICE_ISAWAY:
2079         case YAHOO_SERVICE_ISBACK:
2080         case YAHOO_SERVICE_GAMELOGON:
2081         case YAHOO_SERVICE_GAMELOGOFF:
2082         case YAHOO_SERVICE_CHATLOGON:
2083         case YAHOO_SERVICE_CHATLOGOFF:
2084         case YAHOO_SERVICE_Y6_STATUS_UPDATE:
2085         case YAHOO_SERVICE_STATUS_15:
2086                 yahoo_process_status(gc, pkt);
2087                 break;
2088         case YAHOO_SERVICE_NOTIFY:
2089                 yahoo_process_notify(gc, pkt);
2090                 break;
2091         case YAHOO_SERVICE_MESSAGE:
2092         case YAHOO_SERVICE_GAMEMSG:
2093         case YAHOO_SERVICE_CHATMSG:
2094                 yahoo_process_message(gc, pkt);
2095                 break;
2096         case YAHOO_SERVICE_SYSMESSAGE:
2097                 yahoo_process_sysmessage(gc, pkt);
2098                         break;
2099         case YAHOO_SERVICE_NEWMAIL:
2100                 yahoo_process_mail(gc, pkt);
2101                 break;
2102         case YAHOO_SERVICE_NEWCONTACT:
2103                 yahoo_process_contact(gc, pkt);
2104                 break;
2105         case YAHOO_SERVICE_AUTHRESP:
2106                 yahoo_process_authresp(gc, pkt);
2107                 break;
2108         case YAHOO_SERVICE_LIST:
2109                 yahoo_process_list(gc, pkt);
2110                 break;
2111         case YAHOO_SERVICE_LIST_15:
2112                 yahoo_process_list_15(gc, pkt);
2113                 break;
2114         case YAHOO_SERVICE_AUTH:
2115                 yahoo_process_auth(gc, pkt);
2116                 break;
2117         case YAHOO_SERVICE_AUTH_REQ_15:
2118                 yahoo_buddy_auth_req_15(gc, pkt);
2119                 break;
2120         case YAHOO_SERVICE_ADDBUDDY:
2121                 yahoo_process_addbuddy(gc, pkt);
2122                 break;
2123         case YAHOO_SERVICE_IGNORECONTACT:
2124                 yahoo_process_ignore(gc, pkt);
2125                 break;
2126         case YAHOO_SERVICE_CONFINVITE:
2127         case YAHOO_SERVICE_CONFADDINVITE:
2128                 yahoo_process_conference_invite(gc, pkt);
2129                 break;
2130         case YAHOO_SERVICE_CONFDECLINE:
2131                 yahoo_process_conference_decline(gc, pkt);
2132                 break;
2133         case YAHOO_SERVICE_CONFLOGON:
2134                 yahoo_process_conference_logon(gc, pkt);
2135                 break;
2136         case YAHOO_SERVICE_CONFLOGOFF:
2137                 yahoo_process_conference_logoff(gc, pkt);
2138                 break;
2139         case YAHOO_SERVICE_CONFMSG:
2140                 yahoo_process_conference_message(gc, pkt);
2141                 break;
2142         case YAHOO_SERVICE_CHATONLINE:
2143                 yahoo_process_chat_online(gc, pkt);
2144                 break;
2145         case YAHOO_SERVICE_CHATLOGOUT:
2146                 yahoo_process_chat_logout(gc, pkt);
2147                 break;
2148         case YAHOO_SERVICE_CHATGOTO:
2149                 yahoo_process_chat_goto(gc, pkt);
2150                 break;
2151         case YAHOO_SERVICE_CHATJOIN:
2152                 yahoo_process_chat_join(gc, pkt);
2153                 break;
2154         case YAHOO_SERVICE_CHATLEAVE: /* XXX is this right? */
2155         case YAHOO_SERVICE_CHATEXIT:
2156                 yahoo_process_chat_exit(gc, pkt);
2157                 break;
2158         case YAHOO_SERVICE_CHATINVITE: /* XXX never seen this one, might not do it right */
2159         case YAHOO_SERVICE_CHATADDINVITE:
2160                 yahoo_process_chat_addinvite(gc, pkt);
2161                 break;
2162         case YAHOO_SERVICE_COMMENT:
2163                 yahoo_process_chat_message(gc, pkt);
2164                 break;
2165         case YAHOO_SERVICE_PRESENCE_PERM:
2166         case YAHOO_SERVICE_PRESENCE_SESSION:
2167                 yahoo_process_presence(gc, pkt);
2168                 break;
2169         case YAHOO_SERVICE_P2PFILEXFER:
2170                 /* This case had no break and continued; thus keeping it this way.*/
2171                 yahoo_process_p2pfilexfer(gc, pkt);
2172         case YAHOO_SERVICE_FILETRANSFER:
2173                 yahoo_process_filetransfer(gc, pkt);
2174                 break;
2175         case YAHOO_SERVICE_PEERTOPEER:
2176                 yahoo_process_p2p(gc, pkt);
2177                 break;
2178         case YAHOO_SERVICE_PICTURE:
2179                 yahoo_process_picture(gc, pkt);
2180                 break;
2181         case YAHOO_SERVICE_PICTURE_CHECKSUM:
2182                 yahoo_process_picture_checksum(gc, pkt);
2183                 break;
2184         case YAHOO_SERVICE_PICTURE_UPLOAD:
2185                 yahoo_process_picture_upload(gc, pkt);
2186                 break;
2187         case YAHOO_SERVICE_PICTURE_UPDATE:
2188         case YAHOO_SERVICE_AVATAR_UPDATE:
2189                 yahoo_process_avatar_update(gc, pkt);
2190                 break;
2191         case YAHOO_SERVICE_AUDIBLE:
2192                 yahoo_process_audible(gc, pkt);
2193                 break;
2194         case YAHOO_SERVICE_FILETRANS_15:
2195                 yahoo_process_filetrans_15(gc, pkt);
2196                 break;
2197         case YAHOO_SERVICE_FILETRANS_INFO_15:
2198                 yahoo_process_filetrans_info_15(gc, pkt);
2199                 break;
2200         case YAHOO_SERVICE_FILETRANS_ACC_15:
2201                 yahoo_process_filetrans_acc_15(gc, pkt);
2202                 break;
2203
2204         default:
2205                 purple_debug(PURPLE_DEBUG_ERROR, "yahoo",
2206                                    "Unhandled service 0x%02x\n", pkt->service);
2207                 break;
2208         }
2209 }
2210
2211 static void yahoo_pending(gpointer data, gint source, PurpleInputCondition cond)
2212 {
2213         PurpleConnection *gc = data;
2214         struct yahoo_data *yd = gc->proto_data;
2215         char buf[1024];
2216         int len;
2217
2218         len = read(yd->fd, buf, sizeof(buf));
2219
2220         if (len < 0) {
2221                 gchar *tmp;
2222
2223                 if (errno == EAGAIN)
2224                         /* No worries */
2225                         return;
2226
2227                 tmp = g_strdup_printf(_("Lost connection with server:\n%s"),
2228                                 g_strerror(errno));
2229                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
2230                 g_free(tmp);
2231                 return;
2232         } else if (len == 0) {
2233                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2234                                 _("Server closed the connection."));
2235                 return;
2236         }
2237         gc->last_received = time(NULL);
2238         yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
2239         memcpy(yd->rxqueue + yd->rxlen, buf, len);
2240         yd->rxlen += len;
2241
2242         while (1) {
2243                 struct yahoo_packet *pkt;
2244                 int pos = 0;
2245                 int pktlen;
2246
2247                 if (yd->rxlen < YAHOO_PACKET_HDRLEN)
2248                         return;
2249
2250                 if (strncmp((char *)yd->rxqueue, "YMSG", MIN(4, yd->rxlen)) != 0) {
2251                         /* HEY! This isn't even a YMSG packet. What
2252                          * are you trying to pull? */
2253                         guchar *start;
2254
2255                         purple_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!\n");
2256
2257                         start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1);
2258                         if (start) {
2259                                 g_memmove(yd->rxqueue, start, yd->rxlen - (start - yd->rxqueue));
2260                                 yd->rxlen -= start - yd->rxqueue;
2261                                 continue;
2262                         } else {
2263                                 g_free(yd->rxqueue);
2264                                 yd->rxqueue = NULL;
2265                                 yd->rxlen = 0;
2266                                 return;
2267                         }
2268                 }
2269
2270                 pos += 4; /* YMSG */
2271                 pos += 2;
2272                 pos += 2;
2273
2274                 pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2;
2275                 purple_debug(PURPLE_DEBUG_MISC, "yahoo",
2276                                    "%d bytes to read, rxlen is %d\n", pktlen, yd->rxlen);
2277
2278                 if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen))
2279                         return;
2280
2281                 yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);
2282
2283                 pkt = yahoo_packet_new(0, 0, 0);
2284
2285                 pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2;
2286                 pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4;
2287                 purple_debug(PURPLE_DEBUG_MISC, "yahoo",
2288                                    "Yahoo Service: 0x%02x Status: %d\n",
2289                                    pkt->service, pkt->status);
2290                 pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4;
2291
2292                 yahoo_packet_read(pkt, yd->rxqueue + pos, pktlen);
2293
2294                 yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
2295                 if (yd->rxlen) {
2296                         guchar *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
2297                         g_free(yd->rxqueue);
2298                         yd->rxqueue = tmp;
2299                 } else {
2300                         g_free(yd->rxqueue);
2301                         yd->rxqueue = NULL;
2302                 }
2303
2304                 yahoo_packet_process(gc, pkt);
2305
2306                 yahoo_packet_free(pkt);
2307         }
2308 }
2309
2310 static void yahoo_got_connected(gpointer data, gint source, const gchar *error_message)
2311 {
2312         PurpleConnection *gc = data;
2313         struct yahoo_data *yd;
2314         struct yahoo_packet *pkt;
2315
2316         if (!PURPLE_CONNECTION_IS_VALID(gc)) {
2317                 close(source);
2318                 return;
2319         }
2320
2321         if (source < 0) {
2322                 gchar *tmp;
2323                 tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"),
2324                                 error_message);
2325                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
2326                 g_free(tmp);
2327                 return;
2328         }
2329
2330         yd = gc->proto_data;
2331         yd->fd = source;
2332
2333         pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, yd->current_status, 0);
2334
2335         yahoo_packet_hash_str(pkt, 1, purple_normalize(gc->account, purple_account_get_username(purple_connection_get_account(gc))));
2336         yahoo_packet_send_and_free(pkt, yd);
2337
2338         gc->inpa = purple_input_add(yd->fd, PURPLE_INPUT_READ, yahoo_pending, gc);
2339 }
2340
2341 #ifdef TRY_WEBMESSENGER_LOGIN
2342 static void yahoo_got_web_connected(gpointer data, gint source, const gchar *error_message)
2343 {
2344         PurpleConnection *gc = data;
2345         struct yahoo_data *yd;
2346         struct yahoo_packet *pkt;
2347
2348         if (!PURPLE_CONNECTION_IS_VALID(gc)) {
2349                 close(source);
2350                 return;
2351         }
2352
2353         if (source < 0) {
2354                 gchar *tmp;
2355                 tmp = g_strdup_printf(_("Could not establish a connection with the server:\n%s"),
2356                                 error_message);
2357                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
2358                 g_free(tmp);
2359                 return;
2360         }
2361
2362         yd = gc->proto_data;
2363         yd->fd = source;
2364
2365         pkt = yahoo_packet_new(YAHOO_SERVICE_WEBLOGIN, YAHOO_STATUS_WEBLOGIN, 0);
2366
2367         yahoo_packet_hash(pkt, "sss", 0,
2368                           purple_normalize(gc->account, purple_account_get_username(purple_connection_get_account(gc))),
2369                           1, purple_normalize(gc->account, purple_account_get_username(purple_connection_get_account(gc))),
2370                           6, yd->auth);
2371         yahoo_packet_send_and_free(pkt, yd);
2372
2373         g_free(yd->auth);
2374         gc->inpa = purple_input_add(yd->fd, PURPLE_INPUT_READ, yahoo_pending, gc);
2375 }
2376
2377 static void yahoo_web_pending(gpointer data, gint source, PurpleInputCondition cond)
2378 {
2379         PurpleConnection *gc = data;
2380         PurpleAccount *account = purple_connection_get_account(gc);
2381         struct yahoo_data *yd = gc->proto_data;
2382         char bufread[2048], *i = bufread, *buf = bufread;
2383         int len;
2384         GString *s;
2385
2386         len = read(source, bufread, sizeof(bufread) - 1);
2387
2388         if (len < 0) {
2389                 gchar *tmp;
2390
2391                 if (errno == EAGAIN)
2392                         /* No worries */
2393                         return;
2394
2395                 tmp = g_strdup_printf(_("Lost connection with server:\n%s"),
2396                                 g_strerror(errno));
2397                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR, tmp);
2398                 g_free(tmp);
2399                 return;
2400         } else if (len == 0) {
2401                 purple_connection_error_reason(gc, PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
2402                         _("Server closed the connection."));
2403                 return;
2404         }
2405
2406         if (yd->rxlen > 0 || !g_strstr_len(buf, len, "\r\n\r\n")) {
2407                 yd->rxqueue = g_realloc(yd->rxqueue, yd->rxlen + len + 1);
2408                 memcpy(yd->rxqueue + yd->rxlen, buf, len);
2409                 yd->rxlen += len;
2410                 i = buf = (char *)yd->rxqueue;
2411                 len = yd->rxlen;
2412         }
2413         buf[len] = '\0';
2414
2415         if ((strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) &&
2416                           strnc