src/protocols/yahoo/yahoo.c

branch
gaim
changeset 20470
77693555855f
parent 13071
b98e72d4089a
parent 20469
b2836a24d81e
child 20471
1966704b3e42
equal deleted inserted replaced
13071:b98e72d4089a 20470:77693555855f
1 /*
2 * gaim
3 *
4 * Gaim 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 "debug.h"
32 #include "notify.h"
33 #include "privacy.h"
34 #include "prpl.h"
35 #include "proxy.h"
36 #include "request.h"
37 #include "server.h"
38 #include "util.h"
39 #include "version.h"
40
41 #include "yahoo.h"
42 #include "yahoochat.h"
43 #include "yahoo_auth.h"
44 #include "yahoo_crypt.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 static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *, GaimGroup *);
55 static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len);
56 static void yahoo_set_status(GaimAccount *account, GaimStatus *status);
57
58 static void
59 yahoo_add_permit(GaimConnection *gc, const char *who)
60 {
61 gaim_debug_info("yahoo",
62 "Permitting ID %s local contact rights for account %s\n", who, gc->account);
63 gaim_privacy_permit_add(gc->account,who,TRUE);
64 }
65
66 static void
67 yahoo_rem_permit(GaimConnection *gc, const char *who)
68 {
69 gaim_debug_info("yahoo",
70 "Denying ID %s local contact rights for account %s\n", who, gc->account);
71 gaim_privacy_permit_remove(gc->account,who,TRUE);
72 }
73
74 gboolean yahoo_privacy_check(GaimConnection *gc, const char *who)
75 {
76 /* returns TRUE if allowed through, FALSE otherwise */
77 gboolean permitted;
78
79 permitted = gaim_privacy_check(gc->account, who);
80
81 /* print some debug info */
82 if (!permitted) {
83 char *deb = NULL;
84 switch (gc->account->perm_deny)
85 {
86 case GAIM_PRIVACY_DENY_ALL:
87 deb = "GAIM_PRIVACY_DENY_ALL";
88 break;
89 case GAIM_PRIVACY_DENY_USERS:
90 deb = "GAIM_PRIVACY_DENY_USERS";
91 break;
92 case GAIM_PRIVACY_ALLOW_BUDDYLIST:
93 deb = "GAIM_PRIVACY_ALLOW_BUDDYLIST";
94 break;
95 }
96 if(deb)
97 gaim_debug_info("yahoo",
98 "%s blocked data received from %s (%s)\n",
99 gc->account->username,who, deb);
100 } else if (gc->account->perm_deny == GAIM_PRIVACY_ALLOW_USERS) {
101 gaim_debug_info("yahoo",
102 "%s allowed data received from %s (GAIM_PRIVACY_ALLOW_USERS)\n",
103 gc->account->username,who);
104 }
105
106 return permitted;
107 }
108
109 static void yahoo_update_status(GaimConnection *gc, const char *name, YahooFriend *f)
110 {
111 char *status = NULL;
112
113 if (!gc || !name || !f || !gaim_find_buddy(gaim_connection_get_account(gc), name))
114 return;
115
116 if (f->status == YAHOO_STATUS_OFFLINE)
117 {
118 return;
119 }
120
121 switch (f->status) {
122 case YAHOO_STATUS_AVAILABLE:
123 status = YAHOO_STATUS_TYPE_AVAILABLE;
124 break;
125 case YAHOO_STATUS_BRB:
126 status = YAHOO_STATUS_TYPE_BRB;
127 break;
128 case YAHOO_STATUS_BUSY:
129 status = YAHOO_STATUS_TYPE_BUSY;
130 break;
131 case YAHOO_STATUS_NOTATHOME:
132 status = YAHOO_STATUS_TYPE_NOTATHOME;
133 break;
134 case YAHOO_STATUS_NOTATDESK:
135 status = YAHOO_STATUS_TYPE_NOTATDESK;
136 break;
137 case YAHOO_STATUS_NOTINOFFICE:
138 status = YAHOO_STATUS_TYPE_NOTINOFFICE;
139 break;
140 case YAHOO_STATUS_ONPHONE:
141 status = YAHOO_STATUS_TYPE_ONPHONE;
142 break;
143 case YAHOO_STATUS_ONVACATION:
144 status = YAHOO_STATUS_TYPE_ONVACATION;
145 break;
146 case YAHOO_STATUS_OUTTOLUNCH:
147 status = YAHOO_STATUS_TYPE_OUTTOLUNCH;
148 break;
149 case YAHOO_STATUS_STEPPEDOUT:
150 status = YAHOO_STATUS_TYPE_STEPPEDOUT;
151 break;
152 case YAHOO_STATUS_INVISIBLE: /* this should never happen? */
153 status = YAHOO_STATUS_TYPE_INVISIBLE;
154 break;
155 case YAHOO_STATUS_CUSTOM:
156 if (!f->away)
157 status = YAHOO_STATUS_TYPE_AVAILABLE;
158 else
159 status = YAHOO_STATUS_TYPE_AWAY;
160 break;
161 case YAHOO_STATUS_IDLE:
162 status = YAHOO_STATUS_TYPE_AVAILABLE;
163 break;
164 default:
165 gaim_debug_warning("yahoo", "Warning, unknown status %d\n", f->status);
166 break;
167 }
168
169 if (status) {
170 if (f->status == YAHOO_STATUS_CUSTOM)
171 gaim_prpl_got_user_status(gaim_connection_get_account(gc), name, status, "message",
172 yahoo_friend_get_status_message(f), NULL);
173 else
174 gaim_prpl_got_user_status(gaim_connection_get_account(gc), name, status, NULL);
175 }
176
177 if (f->idle != 0)
178 gaim_prpl_got_user_idle(gaim_connection_get_account(gc), name, TRUE, f->idle);
179 else
180 gaim_prpl_got_user_idle(gaim_connection_get_account(gc), name, FALSE, 0);
181 }
182
183 static void yahoo_process_status(GaimConnection *gc, struct yahoo_packet *pkt)
184 {
185 GaimAccount *account = gaim_connection_get_account(gc);
186 struct yahoo_data *yd = gc->proto_data;
187 GSList *l = pkt->hash;
188 YahooFriend *f = NULL;
189 char *name = NULL;
190
191 if (pkt->service == YAHOO_SERVICE_LOGOFF && pkt->status == -1) {
192 gc->wants_to_die = TRUE;
193 gaim_connection_error(gc, _("You have signed on from another location."));
194 return;
195 }
196
197 while (l) {
198 struct yahoo_pair *pair = l->data;
199
200 switch (pair->key) {
201 case 0: /* we won't actually do anything with this */
202 break;
203 case 1: /* we don't get the full buddy list here. */
204 if (!yd->logged_in) {
205 gaim_connection_set_display_name(gc, pair->value);
206 gaim_connection_set_state(gc, GAIM_CONNECTED);
207 yd->logged_in = TRUE;
208 if (yd->picture_upload_todo) {
209 yahoo_buddy_icon_upload(gc, yd->picture_upload_todo);
210 yd->picture_upload_todo = NULL;
211 }
212 yahoo_set_status(account, gaim_account_get_active_status(account));
213
214 /* this requests the list. i have a feeling that this is very evil
215 *
216 * scs.yahoo.com sends you the list before this packet without it being
217 * requested
218 *
219 * do_import(gc, NULL);
220 * newpkt = yahoo_packet_new(YAHOO_SERVICE_LIST, YAHOO_STATUS_OFFLINE, 0);
221 * yahoo_packet_send_and_free(newpkt, yd);
222 */
223
224 }
225 break;
226 case 8: /* how many online buddies we have */
227 break;
228 case 7: /* the current buddy */
229 if (name && f) /* update the previous buddy before changing the variables */
230 yahoo_update_status(gc, name, f);
231 name = pair->value;
232 if (name && g_utf8_validate(name, -1, NULL))
233 f = yahoo_friend_find_or_new(gc, name);
234 else {
235 f = NULL;
236 name = NULL;
237 }
238 break;
239 case 10: /* state */
240 if (!f)
241 break;
242
243 f->status = strtol(pair->value, NULL, 10);
244 if ((f->status >= YAHOO_STATUS_BRB) && (f->status <= YAHOO_STATUS_STEPPEDOUT))
245 f->away = 1;
246 else
247 f->away = 0;
248
249 if (f->status == YAHOO_STATUS_IDLE) {
250 /* Idle may have already been set in a more precise way in case 137 */
251 if (f->idle == 0)
252 f->idle = time(NULL);
253 } else
254 f->idle = 0;
255
256 if (f->status != YAHOO_STATUS_CUSTOM)
257 yahoo_friend_set_status_message(f, NULL);
258
259 f->sms = 0;
260 break;
261 case 19: /* custom message */
262 if (f)
263 yahoo_friend_set_status_message(f, yahoo_string_decode(gc, pair->value, FALSE));
264 break;
265 case 11: /* this is the buddy's session id */
266 break;
267 case 17: /* in chat? */
268 break;
269 case 47: /* is custom status away or not? 2=idle*/
270 if (!f)
271 break;
272
273 /* I have no idea what it means when this is
274 * set when someone's available, but it doesn't
275 * mean idle. */
276 if (f->status == YAHOO_STATUS_AVAILABLE)
277 break;
278
279 f->away = strtol(pair->value, NULL, 10);
280 if (f->away == 2) {
281 /* Idle may have already been set in a more precise way in case 137 */
282 if (f->idle == 0)
283 f->idle = time(NULL);
284 }
285
286 break;
287 case 138: /* either we're not idle, or we are but won't say how long */
288 if (!f)
289 break;
290
291 if (f->idle)
292 f->idle = -1;
293 break;
294 case 137: /* usually idle time in seconds, sometimes login time */
295 if (!f)
296 break;
297
298 if (f->status != YAHOO_STATUS_AVAILABLE)
299 f->idle = time(NULL) - strtol(pair->value, NULL, 10);
300 break;
301 case 13: /* bitmask, bit 0 = pager, bit 1 = chat, bit 2 = game */
302 if (strtol(pair->value, NULL, 10) == 0) {
303 if (f)
304 f->status = YAHOO_STATUS_OFFLINE;
305 gaim_prpl_got_user_status(account, name, "offline", NULL);
306 break;
307 }
308 break;
309 case 60: /* SMS */
310 if (f) {
311 f->sms = strtol(pair->value, NULL, 10);
312 yahoo_update_status(gc, name, f);
313 }
314 break;
315 case 197: /* Avatars */
316 {
317 guchar *decoded;
318 char *tmp;
319 gsize len;
320
321 if (pair->value) {
322 decoded = gaim_base64_decode(pair->value, &len);
323 if (len) {
324 tmp = gaim_str_binary_to_ascii(decoded, len);
325 gaim_debug_info("yahoo", "Got key 197, value = %s\n", tmp);
326 g_free(tmp);
327 }
328 g_free(decoded);
329 }
330 break;
331 }
332 case 192: /* Pictures, aka Buddy Icons, checksum */
333 {
334 int cksum = strtol(pair->value, NULL, 10);
335 GaimBuddy *b;
336
337 if (!name)
338 break;
339
340 b = gaim_find_buddy(gc->account, name);
341
342 if (!cksum || (cksum == -1)) {
343 if (f)
344 yahoo_friend_set_buddy_icon_need_request(f, TRUE);
345 gaim_buddy_icons_set_for_user(gc->account, name, NULL, 0);
346 if (b)
347 gaim_blist_node_remove_setting((GaimBlistNode *)b, YAHOO_ICON_CHECKSUM_KEY);
348 break;
349 }
350
351 if (!f)
352 break;
353
354 yahoo_friend_set_buddy_icon_need_request(f, FALSE);
355 if (cksum != gaim_blist_node_get_int((GaimBlistNode*)b, YAHOO_ICON_CHECKSUM_KEY))
356 yahoo_send_picture_request(gc, name);
357
358 break;
359 }
360 case 16: /* Custom error message */
361 {
362 char *tmp = yahoo_string_decode(gc, pair->value, TRUE);
363 gaim_notify_error(gc, NULL, tmp, NULL);
364 g_free(tmp);
365 }
366 break;
367 default:
368 gaim_debug(GAIM_DEBUG_ERROR, "yahoo",
369 "Unknown status key %d\n", pair->key);
370 break;
371 }
372
373 l = l->next;
374 }
375
376 if (name && f) /* update the last buddy */
377 yahoo_update_status(gc, name, f);
378 }
379
380 static void yahoo_do_group_check(GaimAccount *account, GHashTable *ht, const char *name, const char *group)
381 {
382 GaimBuddy *b;
383 GaimGroup *g;
384 GSList *list, *i;
385 gboolean onlist = 0;
386 char *oname = NULL;
387 char **oname_p = &oname;
388 GSList **list_p = &list;
389
390 if (!g_hash_table_lookup_extended(ht, gaim_normalize(account, name), (gpointer *) oname_p, (gpointer *) list_p))
391 list = gaim_find_buddies(account, name);
392 else
393 g_hash_table_steal(ht, name);
394
395 for (i = list; i; i = i->next) {
396 b = i->data;
397 g = gaim_buddy_get_group(b);
398 if (!gaim_utf8_strcasecmp(group, g->name)) {
399 gaim_debug(GAIM_DEBUG_MISC, "yahoo",
400 "Oh good, %s is in the right group (%s).\n", name, group);
401 list = g_slist_delete_link(list, i);
402 onlist = 1;
403 break;
404 }
405 }
406
407 if (!onlist) {
408 gaim_debug(GAIM_DEBUG_MISC, "yahoo",
409 "Uhoh, %s isn't on the list (or not in this group), adding him to group %s.\n", name, group);
410 if (!(g = gaim_find_group(group))) {
411 g = gaim_group_new(group);
412 gaim_blist_add_group(g, NULL);
413 }
414 b = gaim_buddy_new(account, name, NULL);
415 gaim_blist_add_buddy(b, NULL, g, NULL);
416 }
417
418 if (list) {
419 if (!oname)
420 oname = g_strdup(gaim_normalize(account, name));
421 g_hash_table_insert(ht, oname, list);
422 } else if (oname)
423 g_free(oname);
424 }
425
426 static void yahoo_do_group_cleanup(gpointer key, gpointer value, gpointer user_data)
427 {
428 char *name = key;
429 GSList *list = value, *i;
430 GaimBuddy *b;
431 GaimGroup *g;
432
433 for (i = list; i; i = i->next) {
434 b = i->data;
435 g = gaim_buddy_get_group(b);
436 gaim_debug(GAIM_DEBUG_MISC, "yahoo", "Deleting Buddy %s from group %s.\n", name, g->name);
437 gaim_blist_remove_buddy(b);
438 }
439 }
440
441 static char *_getcookie(char *rawcookie)
442 {
443 char *cookie = NULL;
444 char *tmpcookie;
445 char *cookieend;
446
447 if (strlen(rawcookie) < 2)
448 return NULL;
449 tmpcookie = g_strdup(rawcookie+2);
450 cookieend = strchr(tmpcookie, ';');
451
452 if (cookieend)
453 *cookieend = '\0';
454
455 cookie = g_strdup(tmpcookie);
456 g_free(tmpcookie);
457
458 return cookie;
459 }
460
461 static void yahoo_process_cookie(struct yahoo_data *yd, char *c)
462 {
463 if (c[0] == 'Y') {
464 if (yd->cookie_y)
465 g_free(yd->cookie_y);
466 yd->cookie_y = _getcookie(c);
467 } else if (c[0] == 'T') {
468 if (yd->cookie_t)
469 g_free(yd->cookie_t);
470 yd->cookie_t = _getcookie(c);
471 }
472 }
473
474 static void yahoo_process_list(GaimConnection *gc, struct yahoo_packet *pkt)
475 {
476 GSList *l = pkt->hash;
477 gboolean export = FALSE;
478 gboolean got_serv_list = FALSE;
479 GaimBuddy *b;
480 GaimGroup *g;
481 YahooFriend *f = NULL;
482 GaimAccount *account = gaim_connection_get_account(gc);
483 struct yahoo_data *yd = gc->proto_data;
484 GHashTable *ht;
485
486 char **lines;
487 char **split;
488 char **buddies;
489 char **tmp, **bud, *norm_bud;
490 char *grp = NULL;
491
492 if (pkt->id)
493 yd->session_id = pkt->id;
494
495 while (l) {
496 struct yahoo_pair *pair = l->data;
497 l = l->next;
498
499 switch (pair->key) {
500 case 87:
501 if (!yd->tmp_serv_blist)
502 yd->tmp_serv_blist = g_string_new(pair->value);
503 else
504 g_string_append(yd->tmp_serv_blist, pair->value);
505 break;
506 case 88:
507 if (!yd->tmp_serv_ilist)
508 yd->tmp_serv_ilist = g_string_new(pair->value);
509 else
510 g_string_append(yd->tmp_serv_ilist, pair->value);
511 break;
512 case 59: /* cookies, yum */
513 yahoo_process_cookie(yd, pair->value);
514 break;
515 case YAHOO_SERVICE_PRESENCE_PERM:
516 if (!yd->tmp_serv_plist)
517 yd->tmp_serv_plist = g_string_new(pair->value);
518 else
519 g_string_append(yd->tmp_serv_plist, pair->value);
520 break;
521 }
522 }
523
524 if (pkt->status != 0)
525 return;
526
527 if (yd->tmp_serv_blist) {
528 ht = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) g_slist_free);
529
530 lines = g_strsplit(yd->tmp_serv_blist->str, "\n", -1);
531 for (tmp = lines; *tmp; tmp++) {
532 split = g_strsplit(*tmp, ":", 2);
533 if (!split)
534 continue;
535 if (!split[0] || !split[1]) {
536 g_strfreev(split);
537 continue;
538 }
539 grp = yahoo_string_decode(gc, split[0], FALSE);
540 buddies = g_strsplit(split[1], ",", -1);
541 for (bud = buddies; bud && *bud; bud++) {
542 norm_bud = g_strdup(gaim_normalize(account, *bud));
543 f = yahoo_friend_find_or_new(gc, norm_bud);
544
545 if (!(b = gaim_find_buddy(account, norm_bud))) {
546 if (!(g = gaim_find_group(grp))) {
547 g = gaim_group_new(grp);
548 gaim_blist_add_group(g, NULL);
549 }
550 b = gaim_buddy_new(account, norm_bud, NULL);
551 gaim_blist_add_buddy(b, NULL, g, NULL);
552 export = TRUE;
553 }
554
555 yahoo_do_group_check(account, ht, norm_bud, grp);
556 g_free(norm_bud);
557 }
558 g_strfreev(buddies);
559 g_strfreev(split);
560 g_free(grp);
561 }
562 g_strfreev(lines);
563
564 g_string_free(yd->tmp_serv_blist, TRUE);
565 yd->tmp_serv_blist = NULL;
566 g_hash_table_foreach(ht, yahoo_do_group_cleanup, NULL);
567 g_hash_table_destroy(ht);
568 }
569
570 if (yd->tmp_serv_ilist) {
571 buddies = g_strsplit(yd->tmp_serv_ilist->str, ",", -1);
572 for (bud = buddies; bud && *bud; bud++) {
573 /* The server is already ignoring the user */
574 got_serv_list = TRUE;
575 gaim_privacy_deny_add(gc->account, *bud, 1);
576 }
577 g_strfreev(buddies);
578
579 g_string_free(yd->tmp_serv_ilist, TRUE);
580 yd->tmp_serv_ilist = NULL;
581 }
582
583 if (got_serv_list &&
584 ((gc->account->perm_deny != GAIM_PRIVACY_ALLOW_BUDDYLIST) &&
585 (gc->account->perm_deny != GAIM_PRIVACY_DENY_ALL) &&
586 (gc->account->perm_deny != GAIM_PRIVACY_ALLOW_USERS)))
587 {
588 gc->account->perm_deny = GAIM_PRIVACY_DENY_USERS;
589 gaim_debug_info("yahoo", "%s privacy defaulting to GAIM_PRIVACY_DENY_USERS.\n",
590 gc->account->username);
591 }
592
593 if (yd->tmp_serv_plist) {
594 buddies = g_strsplit(yd->tmp_serv_plist->str, ",", -1);
595 for (bud = buddies; bud && *bud; bud++) {
596 f = yahoo_friend_find(gc, *bud);
597 if (f) {
598 gaim_debug_info("yahoo", "%s setting presence for %s to PERM_OFFLINE\n",
599 gc->account->username, *bud);
600 f->presence = YAHOO_PRESENCE_PERM_OFFLINE;
601 }
602 }
603 g_strfreev(buddies);
604 g_string_free(yd->tmp_serv_plist, TRUE);
605 yd->tmp_serv_plist = NULL;
606
607 }
608 }
609
610 static void yahoo_process_notify(GaimConnection *gc, struct yahoo_packet *pkt)
611 {
612 char *msg = NULL;
613 char *from = NULL;
614 char *stat = NULL;
615 char *game = NULL;
616 YahooFriend *f = NULL;
617 GSList *l = pkt->hash;
618
619 while (l) {
620 struct yahoo_pair *pair = l->data;
621 if (pair->key == 4)
622 from = pair->value;
623 if (pair->key == 49)
624 msg = pair->value;
625 if (pair->key == 13)
626 stat = pair->value;
627 if (pair->key == 14)
628 game = pair->value;
629 l = l->next;
630 }
631
632 if (!from || !msg)
633 return;
634
635 if (!g_ascii_strncasecmp(msg, "TYPING", strlen("TYPING"))
636 && (yahoo_privacy_check(gc, from))) {
637 if (*stat == '1')
638 serv_got_typing(gc, from, 0, GAIM_TYPING);
639 else
640 serv_got_typing_stopped(gc, from);
641 } else if (!g_ascii_strncasecmp(msg, "GAME", strlen("GAME"))) {
642 GaimBuddy *bud = gaim_find_buddy(gc->account, from);
643
644 if (!bud) {
645 gaim_debug(GAIM_DEBUG_WARNING, "yahoo",
646 "%s is playing a game, and doesn't want "
647 "you to know.\n", from);
648 }
649
650 f = yahoo_friend_find(gc, from);
651 if (!f)
652 return; /* if they're not on the list, don't bother */
653
654 yahoo_friend_set_game(f, NULL);
655
656 if (*stat == '1') {
657 yahoo_friend_set_game(f, game);
658 if (bud)
659 yahoo_update_status(gc, from, f);
660 }
661 }
662 }
663
664
665 struct _yahoo_im {
666 char *from;
667 int time;
668 int utf8;
669 int buddy_icon;
670 char *msg;
671 };
672
673 static void yahoo_process_message(GaimConnection *gc, struct yahoo_packet *pkt)
674 {
675 GSList *l = pkt->hash;
676 GSList *list = NULL;
677 struct _yahoo_im *im = NULL;
678
679 char imv[16];
680
681 if (pkt->status <= 1 || pkt->status == 5) {
682 while (l != NULL) {
683 struct yahoo_pair *pair = l->data;
684 if (pair->key == 4) {
685 im = g_new0(struct _yahoo_im, 1);
686 list = g_slist_append(list, im);
687 im->from = pair->value;
688 im->time = time(NULL);
689 }
690 if (pair->key == 97)
691 if (im)
692 im->utf8 = strtol(pair->value, NULL, 10);
693 if (pair->key == 15)
694 if (im)
695 im->time = strtol(pair->value, NULL, 10);
696 if (pair->key == 206)
697 if (im)
698 im->buddy_icon = strtol(pair->value, NULL, 10);
699 if (pair->key == 14) {
700 if (im)
701 im->msg = pair->value;
702 }
703 /* IMV key */
704 if (pair->key == 63)
705 {
706 strcpy(imv, pair->value);
707 }
708 l = l->next;
709 }
710 } else if (pkt->status == 2) {
711 gaim_notify_error(gc, NULL,
712 _("Your Yahoo! message did not get sent."), NULL);
713 }
714
715 /* Check for the Doodle IMV */
716 if(!strcmp(imv, "doodle;11"))
717 {
718 GaimWhiteboard *wb;
719
720 if (!yahoo_privacy_check(gc, im->from)) {
721 gaim_debug_info("yahoo", "Doodle request from %s dropped.\n", im->from);
722 return;
723 }
724
725 wb = gaim_whiteboard_get_session(gc->account, im->from);
726
727 /* If a Doodle session doesn't exist between this user */
728 if(wb == NULL)
729 {
730 wb = gaim_whiteboard_create(gc->account, im->from, DOODLE_STATE_REQUESTED);
731
732 yahoo_doodle_command_send_request(gc, im->from);
733 yahoo_doodle_command_send_ready(gc, im->from);
734 }
735 }
736
737 for (l = list; l; l = l->next) {
738 YahooFriend *f;
739 char *m, *m2;
740 im = l->data;
741
742 if (!im->from || !im->msg) {
743 g_free(im);
744 continue;
745 }
746
747 if (!yahoo_privacy_check(gc, im->from)) {
748 gaim_debug_info("yahoo", "Message from %s dropped.\n", im->from);
749 return;
750 }
751
752 m = yahoo_string_decode(gc, im->msg, im->utf8);
753 /* This may actually not be necessary, but it appears
754 * that at least at one point some clients were sending
755 * "\r\n" as line delimiters, so we want to avoid double
756 * lines. */
757 m2 = gaim_strreplace(m, "\r\n", "\n");
758 g_free(m);
759 m = m2;
760 gaim_util_chrreplace(m, '\r', '\n');
761
762 if (!strcmp(m, "<ding>")) {
763 GaimConversation *c = gaim_conversation_new(GAIM_CONV_TYPE_IM,
764 gaim_connection_get_account(gc), im->from);
765 gaim_conv_im_write(GAIM_CONV_IM(c), "", _("Buzz!!"), GAIM_MESSAGE_NICK|GAIM_MESSAGE_RECV,
766 im->time);
767 g_free(m);
768 g_free(im);
769 continue;
770 }
771
772 m2 = yahoo_codes_to_html(m);
773 g_free(m);
774 serv_got_im(gc, im->from, m2, 0, im->time);
775 g_free(m2);
776
777 if ((f = yahoo_friend_find(gc, im->from)) && im->buddy_icon == 2) {
778 if (yahoo_friend_get_buddy_icon_need_request(f)) {
779 yahoo_send_picture_request(gc, im->from);
780 yahoo_friend_set_buddy_icon_need_request(f, FALSE);
781 }
782 }
783
784 g_free(im);
785 }
786 g_slist_free(list);
787 }
788
789 static void yahoo_process_sysmessage(GaimConnection *gc, struct yahoo_packet *pkt)
790 {
791 GSList *l = pkt->hash;
792 char *prim, *me = NULL, *msg = NULL, *escmsg = NULL;
793
794 while (l) {
795 struct yahoo_pair *pair = l->data;
796
797 if (pair->key == 5)
798 me = pair->value;
799 if (pair->key == 14)
800 msg = pair->value;
801
802 l = l->next;
803 }
804
805 if (!msg || !g_utf8_validate(msg, -1, NULL))
806 return;
807
808 escmsg = g_markup_escape_text(msg, -1);
809
810 prim = g_strdup_printf(_("Yahoo! system message for %s:"),
811 me?me:gaim_connection_get_display_name(gc));
812 gaim_notify_info(NULL, NULL, prim, escmsg);
813 g_free(prim);
814 g_free(escmsg);
815 }
816
817 struct yahoo_add_request {
818 GaimConnection *gc;
819 char *id;
820 char *who;
821 char *msg;
822 };
823
824 static void
825 yahoo_buddy_add_authorize_cb(struct yahoo_add_request *add_req, const char *msg) {
826 GaimBuddy *buddy = gaim_find_buddy(add_req->gc->account, add_req->who);
827
828 if (buddy != NULL)
829 gaim_account_notify_added(add_req->gc->account, add_req->who,
830 add_req->id, NULL, add_req->msg);
831 else
832 gaim_account_request_add(add_req->gc->account, add_req->who,
833 add_req->id, NULL, add_req->msg);
834
835 g_free(add_req->id);
836 g_free(add_req->who);
837 g_free(add_req->msg);
838 g_free(add_req);
839 }
840
841 static void
842 yahoo_buddy_add_deny_cb(struct yahoo_add_request *add_req, const char *msg) {
843 struct yahoo_packet *pkt;
844 char *encoded_msg = NULL;
845 struct yahoo_data *yd = add_req->gc->proto_data;
846
847 if (msg)
848 encoded_msg = yahoo_string_encode(add_req->gc, msg, NULL);
849
850 pkt = yahoo_packet_new(YAHOO_SERVICE_REJECTCONTACT,
851 YAHOO_STATUS_AVAILABLE, 0);
852
853 yahoo_packet_hash(pkt, "sss",
854 1, gaim_normalize(add_req->gc->account,
855 gaim_account_get_username(
856 gaim_connection_get_account(
857 add_req->gc))),
858 7, add_req->who,
859 14, encoded_msg ? encoded_msg : "");
860
861 yahoo_packet_send_and_free(pkt, yd);
862
863 g_free(encoded_msg);
864
865 g_free(add_req->id);
866 g_free(add_req->who);
867 g_free(add_req->msg);
868 g_free(add_req);
869 }
870
871 static void yahoo_buddy_added_us(GaimConnection *gc, struct yahoo_packet *pkt) {
872 struct yahoo_add_request *add_req;
873 char *msg = NULL;
874 GSList *l = pkt->hash;
875
876 add_req = g_new0(struct yahoo_add_request, 1);
877 add_req->gc = gc;
878
879 while (l) {
880 struct yahoo_pair *pair = l->data;
881
882 switch (pair->key) {
883 case 1:
884 add_req->id = g_strdup(pair->value);
885 break;
886 case 3:
887 add_req->who = g_strdup(pair->value);
888 break;
889 case 15: /* time, for when they add us and we're offline */
890 break;
891 case 14:
892 msg = pair->value;
893 break;
894 }
895 l = l->next;
896 }
897
898 if (add_req->id) {
899 char *prompt_msg;
900 if (msg)
901 add_req->msg = yahoo_string_decode(gc, msg, FALSE);
902
903 /* TODO: this is almost exactly the same as what MSN does,
904 * this should probably be moved to the core.
905 */
906 prompt_msg = g_strdup_printf(_("The user %s wants to add %s to "
907 "his or her buddy list%s%s."),
908 add_req->who, add_req->id,
909 add_req->msg ? ": " : "",
910 add_req->msg ? add_req->msg : "");
911 gaim_request_input(gc, NULL, prompt_msg,
912 _("Message (optional) :"),
913 NULL, TRUE, FALSE, NULL,
914 _("Authorize"), G_CALLBACK(
915 yahoo_buddy_add_authorize_cb),
916 _("Deny"), G_CALLBACK(
917 yahoo_buddy_add_deny_cb),
918 add_req);
919 g_free(prompt_msg);
920 } else {
921 g_free(add_req->id);
922 g_free(add_req->who);
923 /*g_free(add_req->msg);*/
924 g_free(add_req);
925 }
926 }
927
928 static void yahoo_buddy_denied_our_add(GaimConnection *gc, struct yahoo_packet *pkt)
929 {
930 char *who = NULL;
931 char *msg = NULL;
932 GSList *l = pkt->hash;
933 GString *buf = NULL;
934 struct yahoo_data *yd = gc->proto_data;
935
936 while (l) {
937 struct yahoo_pair *pair = l->data;
938
939 switch (pair->key) {
940 case 3:
941 who = pair->value;
942 break;
943 case 14:
944 msg = pair->value;
945 break;
946 }
947 l = l->next;
948 }
949
950 if (who) {
951 char *msg2;
952 buf = g_string_sized_new(0);
953 if (!msg) {
954 g_string_printf(buf, _("%s has (retroactively) denied your request to add them to your list."), who);
955 } else {
956 msg2 = yahoo_string_decode(gc, msg, FALSE);
957 g_string_printf(buf, _("%s has (retroactively) denied your request to add them to your list for the following reason: %s."), who, msg2);
958 g_free(msg2);
959 }
960 gaim_notify_info(gc, NULL, _("Add buddy rejected"), buf->str);
961 g_string_free(buf, TRUE);
962 g_hash_table_remove(yd->friends, who);
963 gaim_prpl_got_user_status(gaim_connection_get_account(gc), who, "offline", NULL); /* FIXME: make this set not on list status instead */
964 /* TODO: Shouldn't we remove the buddy from our local list? */
965 }
966 }
967
968 static void yahoo_process_contact(GaimConnection *gc, struct yahoo_packet *pkt)
969 {
970 switch (pkt->status) {
971 case 1:
972 yahoo_process_status(gc, pkt);
973 return;
974 case 3:
975 yahoo_buddy_added_us(gc, pkt);
976 break;
977 case 7:
978 yahoo_buddy_denied_our_add(gc, pkt);
979 break;
980 default:
981 break;
982 }
983 }
984
985 #define OUT_CHARSET "utf-8"
986
987 static char *yahoo_decode(const char *text)
988 {
989 char *converted = NULL;
990 char *n, *new;
991 const char *end, *p;
992 int i, k;
993
994 n = new = g_malloc(strlen (text) + 1);
995 end = text + strlen(text);
996
997 for (p = text; p < end; p++, n++) {
998 if (*p == '\\') {
999 if (p[1] >= '0' && p[1] <= '7') {
1000 p += 1;
1001 for (i = 0, k = 0; k < 3; k += 1) {
1002 char c = p[k];
1003 if (c < '0' || c > '7') break;
1004 i *= 8;
1005 i += c - '0';
1006 }
1007 *n = i;
1008 p += k - 1;
1009 } else { /* bug 959248 */
1010 /* If we see a \ not followed by an octal number,
1011 * it means that it is actually a \\ with one \
1012 * already eaten by some unknown function.
1013 * This is arguably broken.
1014 *
1015 * I think wing is wrong here, there is no function
1016 * called that I see that could have done it. I guess
1017 * it is just really sending single \'s. That's yahoo
1018 * for you.
1019 */
1020 *n = *p;
1021 }
1022 }
1023 else
1024 *n = *p;
1025 }
1026
1027 *n = '\0';
1028
1029 if (strstr(text, "\033$B"))
1030 converted = g_convert(new, n - new, OUT_CHARSET, "iso-2022-jp", NULL, NULL, NULL);
1031 if (!converted)
1032 converted = g_convert(new, n - new, OUT_CHARSET, "iso-8859-1", NULL, NULL, NULL);
1033 g_free(new);
1034
1035 return converted;
1036 }
1037
1038 static void yahoo_process_mail(GaimConnection *gc, struct yahoo_packet *pkt)
1039 {
1040 GaimAccount *account = gaim_connection_get_account(gc);
1041 struct yahoo_data *yd = gc->proto_data;
1042 char *who = NULL;
1043 char *email = NULL;
1044 char *subj = NULL;
1045 char *yahoo_mail_url = (yd->jp? YAHOOJP_MAIL_URL: YAHOO_MAIL_URL);
1046 int count = 0;
1047 GSList *l = pkt->hash;
1048
1049 if (!gaim_account_get_check_mail(account))
1050 return;
1051
1052 while (l) {
1053 struct yahoo_pair *pair = l->data;
1054 if (pair->key == 9)
1055 count = strtol(pair->value, NULL, 10);
1056 else if (pair->key == 43)
1057 who = pair->value;
1058 else if (pair->key == 42)
1059 email = pair->value;
1060 else if (pair->key == 18)
1061 subj = pair->value;
1062 l = l->next;
1063 }
1064
1065 if (who && subj && email && *email) {
1066 char *dec_who = yahoo_decode(who);
1067 char *dec_subj = yahoo_decode(subj);
1068 char *from = g_strdup_printf("%s (%s)", dec_who, email);
1069
1070 gaim_notify_email(gc, dec_subj, from, gaim_account_get_username(account),
1071 yahoo_mail_url, NULL, NULL);
1072
1073 g_free(dec_who);
1074 g_free(dec_subj);
1075 g_free(from);
1076 } else if (count > 0) {
1077 const char *to = gaim_account_get_username(account);
1078 const char *url = yahoo_mail_url;
1079
1080 gaim_notify_emails(gc, count, FALSE, NULL, NULL, &to, &url,
1081 NULL, NULL);
1082 }
1083 }
1084 /* This is the y64 alphabet... it's like base64, but has a . and a _ */
1085 char base64digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
1086
1087 /* This is taken from Sylpheed by Hiroyuki Yamamoto. We have our own tobase64 function
1088 * in util.c, but it has a bug I don't feel like finding right now ;) */
1089 static void to_y64(char *out, const unsigned char *in, gsize inlen)
1090 /* raw bytes in quasi-big-endian order to base 64 string (NUL-terminated) */
1091 {
1092 for (; inlen >= 3; inlen -= 3)
1093 {
1094 *out++ = base64digits[in[0] >> 2];
1095 *out++ = base64digits[((in[0] << 4) & 0x30) | (in[1] >> 4)];
1096 *out++ = base64digits[((in[1] << 2) & 0x3c) | (in[2] >> 6)];
1097 *out++ = base64digits[in[2] & 0x3f];
1098 in += 3;
1099 }
1100 if (inlen > 0)
1101 {
1102 unsigned char fragment;
1103
1104 *out++ = base64digits[in[0] >> 2];
1105 fragment = (in[0] << 4) & 0x30;
1106 if (inlen > 1)
1107 fragment |= in[1] >> 4;
1108 *out++ = base64digits[fragment];
1109 *out++ = (inlen < 2) ? '-' : base64digits[(in[1] << 2) & 0x3c];
1110 *out++ = '-';
1111 }
1112 *out = '\0';
1113 }
1114
1115 static void yahoo_process_auth_old(GaimConnection *gc, const char *seed)
1116 {
1117 struct yahoo_packet *pack;
1118 GaimAccount *account = gaim_connection_get_account(gc);
1119 const char *name = gaim_normalize(account, gaim_account_get_username(account));
1120 const char *pass = gaim_connection_get_password(gc);
1121 struct yahoo_data *yd = gc->proto_data;
1122
1123 /* So, Yahoo has stopped supporting its older clients in India, and undoubtedly
1124 * will soon do so in the rest of the world.
1125 *
1126 * The new clients use this authentication method. I warn you in advance, it's
1127 * bizarre, convoluted, inordinately complicated. It's also no more secure than
1128 * crypt() was. The only purpose this scheme could serve is to prevent third
1129 * party clients from connecting to their servers.
1130 *
1131 * Sorry, Yahoo.
1132 */
1133
1134 GaimCipher *cipher;
1135 GaimCipherContext *context;
1136 guchar digest[16];
1137
1138 char *crypt_result;
1139 char password_hash[25];
1140 char crypt_hash[25];
1141 char *hash_string_p = g_malloc(50 + strlen(name));
1142 char *hash_string_c = g_malloc(50 + strlen(name));
1143
1144 char checksum;
1145
1146 int sv;
1147
1148 char result6[25];
1149 char result96[25];
1150
1151 sv = seed[15];
1152 sv = sv % 8;
1153
1154 cipher = gaim_ciphers_find_cipher("md5");
1155 context = gaim_cipher_context_new(cipher, NULL);
1156
1157 gaim_cipher_context_append(context, (const guchar *)pass, strlen(pass));
1158 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
1159
1160 to_y64(password_hash, digest, 16);
1161
1162 crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");
1163
1164 gaim_cipher_context_reset(context, NULL);
1165 gaim_cipher_context_append(context, (const guchar *)crypt_result, strlen(crypt_result));
1166 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
1167 to_y64(crypt_hash, digest, 16);
1168
1169 switch (sv) {
1170 case 1:
1171 case 6:
1172 checksum = seed[seed[9] % 16];
1173 g_snprintf(hash_string_p, strlen(name) + 50,
1174 "%c%s%s%s", checksum, name, seed, password_hash);
1175 g_snprintf(hash_string_c, strlen(name) + 50,
1176 "%c%s%s%s", checksum, name, seed, crypt_hash);
1177 break;
1178 case 2:
1179 case 7:
1180 checksum = seed[seed[15] % 16];
1181 g_snprintf(hash_string_p, strlen(name) + 50,
1182 "%c%s%s%s", checksum, seed, password_hash, name);
1183 g_snprintf(hash_string_c, strlen(name) + 50,
1184 "%c%s%s%s", checksum, seed, crypt_hash, name);
1185 break;
1186 case 3:
1187 checksum = seed[seed[1] % 16];
1188 g_snprintf(hash_string_p, strlen(name) + 50,
1189 "%c%s%s%s", checksum, name, password_hash, seed);
1190 g_snprintf(hash_string_c, strlen(name) + 50,
1191 "%c%s%s%s", checksum, name, crypt_hash, seed);
1192 break;
1193 case 4:
1194 checksum = seed[seed[3] % 16];
1195 g_snprintf(hash_string_p, strlen(name) + 50,
1196 "%c%s%s%s", checksum, password_hash, seed, name);
1197 g_snprintf(hash_string_c, strlen(name) + 50,
1198 "%c%s%s%s", checksum, crypt_hash, seed, name);
1199 break;
1200 case 0:
1201 case 5:
1202 checksum = seed[seed[7] % 16];
1203 g_snprintf(hash_string_p, strlen(name) + 50,
1204 "%c%s%s%s", checksum, password_hash, name, seed);
1205 g_snprintf(hash_string_c, strlen(name) + 50,
1206 "%c%s%s%s", checksum, crypt_hash, name, seed);
1207 break;
1208 }
1209
1210 gaim_cipher_context_reset(context, NULL);
1211 gaim_cipher_context_append(context, (const guchar *)hash_string_p, strlen(hash_string_p));
1212 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
1213 to_y64(result6, digest, 16);
1214
1215 gaim_cipher_context_reset(context, NULL);
1216 gaim_cipher_context_append(context, (const guchar *)hash_string_c, strlen(hash_string_c));
1217 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
1218 gaim_cipher_context_destroy(context);
1219 to_y64(result96, digest, 16);
1220
1221 pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, YAHOO_STATUS_AVAILABLE, 0);
1222 yahoo_packet_hash(pack, "ssss", 0, name, 6, result6, 96, result96, 1, name);
1223 yahoo_packet_send_and_free(pack, yd);
1224
1225 g_free(hash_string_p);
1226 g_free(hash_string_c);
1227 }
1228
1229 /* I'm dishing out some uber-mad props to Cerulean Studios for cracking this
1230 * and sending the fix! Thanks guys. */
1231
1232 static void yahoo_process_auth_new(GaimConnection *gc, const char *seed)
1233 {
1234 struct yahoo_packet *pack = NULL;
1235 GaimAccount *account = gaim_connection_get_account(gc);
1236 const char *name = gaim_normalize(account, gaim_account_get_username(account));
1237 const char *pass = gaim_connection_get_password(gc);
1238 struct yahoo_data *yd = gc->proto_data;
1239
1240 GaimCipher *md5_cipher;
1241 GaimCipherContext *md5_ctx;
1242 guchar md5_digest[16];
1243
1244 GaimCipher *sha1_cipher;
1245 GaimCipherContext *sha1_ctx1;
1246 GaimCipherContext *sha1_ctx2;
1247
1248 char *alphabet1 = "FBZDWAGHrJTLMNOPpRSKUVEXYChImkwQ";
1249 char *alphabet2 = "F0E1D2C3B4A59687abcdefghijklmnop";
1250
1251 char *challenge_lookup = "qzec2tb3um1olpar8whx4dfgijknsvy5";
1252 char *operand_lookup = "+|&%/*^-";
1253 char *delimit_lookup = ",;";
1254
1255 char *password_hash = (char *)g_malloc(25);
1256 char *crypt_hash = (char *)g_malloc(25);
1257 char *crypt_result = NULL;
1258
1259 unsigned char pass_hash_xor1[64];
1260 unsigned char pass_hash_xor2[64];
1261 unsigned char crypt_hash_xor1[64];
1262 unsigned char crypt_hash_xor2[64];
1263 char resp_6[100];
1264 char resp_96[100];
1265
1266 unsigned char digest1[20];
1267 unsigned char digest2[20];
1268 unsigned char comparison_src[20];
1269 unsigned char magic_key_char[4];
1270 const char *magic_ptr;
1271
1272 unsigned int magic[64];
1273 unsigned int magic_work = 0;
1274 unsigned int magic_4 = 0;
1275
1276 int x;
1277 int y;
1278 int cnt = 0;
1279 int magic_cnt = 0;
1280 int magic_len;
1281
1282 memset(password_hash, 0, 25);
1283 memset(crypt_hash, 0, 25);
1284 memset(&pass_hash_xor1, 0, 64);
1285 memset(&pass_hash_xor2, 0, 64);
1286 memset(&crypt_hash_xor1, 0, 64);
1287 memset(&crypt_hash_xor2, 0, 64);
1288 memset(&digest1, 0, 20);
1289 memset(&digest2, 0, 20);
1290 memset(&magic, 0, 64);
1291 memset(&resp_6, 0, 100);
1292 memset(&resp_96, 0, 100);
1293 memset(&magic_key_char, 0, 4);
1294 memset(&comparison_src, 0, 20);
1295
1296 md5_cipher = gaim_ciphers_find_cipher("md5");
1297 md5_ctx = gaim_cipher_context_new(md5_cipher, NULL);
1298
1299 sha1_cipher = gaim_ciphers_find_cipher("sha1");
1300 sha1_ctx1 = gaim_cipher_context_new(sha1_cipher, NULL);
1301 sha1_ctx2 = gaim_cipher_context_new(sha1_cipher, NULL);
1302
1303 /*
1304 * Magic: Phase 1. Generate what seems to be a 30 byte value (could change if base64
1305 * ends up differently? I don't remember and I'm tired, so use a 64 byte buffer.
1306 */
1307
1308 magic_ptr = seed;
1309
1310 while (*magic_ptr != (int)NULL) {
1311 char *loc;
1312
1313 /* Ignore parentheses. */
1314
1315 if (*magic_ptr == '(' || *magic_ptr == ')') {
1316 magic_ptr++;
1317 continue;
1318 }
1319
1320 /* Characters and digits verify against the challenge lookup. */
1321
1322 if (isalpha(*magic_ptr) || isdigit(*magic_ptr)) {
1323 loc = strchr(challenge_lookup, *magic_ptr);
1324 if (!loc) {
1325 /* SME XXX Error - disconnect here */
1326 }
1327
1328 /* Get offset into lookup table and shl 3. */
1329
1330 magic_work = loc - challenge_lookup;
1331 magic_work <<= 3;
1332
1333 magic_ptr++;
1334 continue;
1335 } else {
1336 unsigned int local_store;
1337
1338 loc = strchr(operand_lookup, *magic_ptr);
1339 if (!loc) {
1340 /* SME XXX Disconnect */
1341 }
1342
1343 local_store = loc - operand_lookup;
1344
1345 /* Oops; how did this happen? */
1346
1347 if (magic_cnt >= 64)
1348 break;
1349
1350 magic[magic_cnt++] = magic_work | local_store;
1351 magic_ptr++;
1352 continue;
1353 }
1354 }
1355
1356 magic_len = magic_cnt;
1357 magic_cnt = 0;
1358
1359 /* Magic: Phase 2. Take generated magic value and sprinkle fairy
1360 * dust on the values.
1361 */
1362
1363 for (magic_cnt = magic_len-2; magic_cnt >= 0; magic_cnt--) {
1364 unsigned char byte1;
1365 unsigned char byte2;
1366
1367 /* Bad. Abort. */
1368
1369 if ((magic_cnt + 1 > magic_len) || (magic_cnt > magic_len))
1370 break;
1371
1372 byte1 = magic[magic_cnt];
1373 byte2 = magic[magic_cnt+1];
1374
1375 byte1 *= 0xcd;
1376 byte1 ^= byte2;
1377
1378 magic[magic_cnt+1] = byte1;
1379 }
1380
1381 /*
1382 * Magic: Phase 3. This computes 20 bytes. The first 4 bytes are used as our magic
1383 * key (and may be changed later); the next 16 bytes are an MD5 sum of the magic key
1384 * plus 3 bytes. The 3 bytes are found by looping, and they represent the offsets
1385 * into particular functions we'll later call to potentially alter the magic key.
1386 *
1387 * %-)
1388 */
1389
1390 magic_cnt = 1;
1391 x = 0;
1392
1393 do {
1394 unsigned int bl = 0;
1395 unsigned int cl = magic[magic_cnt++];
1396
1397 if (magic_cnt >= magic_len)
1398 break;
1399
1400 if (cl > 0x7F) {
1401 if (cl < 0xe0)
1402 bl = cl = (cl & 0x1f) << 6;
1403 else {
1404 bl = magic[magic_cnt++];
1405 cl = (cl & 0x0f) << 6;
1406 bl = ((bl & 0x3f) + cl) << 6;
1407 }
1408
1409 cl = magic[magic_cnt++];
1410 bl = (cl & 0x3f) + bl;
1411 } else
1412 bl = cl;
1413
1414 comparison_src[x++] = (bl & 0xff00) >> 8;
1415 comparison_src[x++] = bl & 0xff;
1416 } while (x < 20);
1417
1418 /* First four bytes are magic key. */
1419 memcpy(&magic_key_char[0], comparison_src, 4);
1420 magic_4 = magic_key_char[0] | (magic_key_char[1]<<8) | (magic_key_char[2]<<16) | (magic_key_char[3]<<24);
1421
1422 /*
1423 * Magic: Phase 4. Determine what function to use later by getting outside/inside
1424 * loop values until we match our previous buffer.
1425 */
1426 for (x = 0; x < 65535; x++) {
1427 int leave = 0;
1428
1429 for (y = 0; y < 5; y++) {
1430 unsigned char test[3];
1431
1432 /* Calculate buffer. */
1433 test[0] = x;
1434 test[1] = x >> 8;
1435 test[2] = y;
1436
1437 gaim_cipher_context_reset(md5_ctx, NULL);
1438 gaim_cipher_context_append(md5_ctx, magic_key_char, 4);
1439 gaim_cipher_context_append(md5_ctx, test, 3);
1440 gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
1441 md5_digest, NULL);
1442
1443 if (!memcmp(md5_digest, comparison_src+4, 16)) {
1444 leave = 1;
1445 break;
1446 }
1447 }
1448
1449 if (leave == 1)
1450 break;
1451 }
1452
1453 /* If y != 0, we need some help. */
1454 if (y != 0) {
1455 unsigned int updated_key;
1456
1457 /* Update magic stuff.
1458 * Call it twice because Yahoo's encryption is super bad ass.
1459 */
1460 updated_key = yahoo_auth_finalCountdown(magic_4, 0x60, y, x);
1461 updated_key = yahoo_auth_finalCountdown(updated_key, 0x60, y, x);
1462
1463 magic_key_char[0] = updated_key & 0xff;
1464 magic_key_char[1] = (updated_key >> 8) & 0xff;
1465 magic_key_char[2] = (updated_key >> 16) & 0xff;
1466 magic_key_char[3] = (updated_key >> 24) & 0xff;
1467 }
1468
1469 /* Get password and crypt hashes as per usual. */
1470 gaim_cipher_context_reset(md5_ctx, NULL);
1471 gaim_cipher_context_append(md5_ctx, (const guchar *)pass, strlen(pass));
1472 gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
1473 md5_digest, NULL);
1474 to_y64(password_hash, md5_digest, 16);
1475
1476 crypt_result = yahoo_crypt(pass, "$1$_2S43d5f$");
1477 gaim_cipher_context_reset(md5_ctx, NULL);
1478 gaim_cipher_context_append(md5_ctx, (const guchar *)crypt_result, strlen(crypt_result));
1479 gaim_cipher_context_digest(md5_ctx, sizeof(md5_digest),
1480 md5_digest, NULL);
1481 to_y64(crypt_hash, md5_digest, 16);
1482
1483 /* Our first authentication response is based off of the password hash. */
1484 for (x = 0; x < (int)strlen(password_hash); x++)
1485 pass_hash_xor1[cnt++] = password_hash[x] ^ 0x36;
1486
1487 if (cnt < 64)
1488 memset(&(pass_hash_xor1[cnt]), 0x36, 64-cnt);
1489
1490 cnt = 0;
1491
1492 for (x = 0; x < (int)strlen(password_hash); x++)
1493 pass_hash_xor2[cnt++] = password_hash[x] ^ 0x5c;
1494
1495 if (cnt < 64)
1496 memset(&(pass_hash_xor2[cnt]), 0x5c, 64-cnt);
1497
1498 /*
1499 * The first context gets the password hash XORed with 0x36 plus a magic value
1500 * which we previously extrapolated from our challenge.
1501 */
1502
1503 gaim_cipher_context_append(sha1_ctx1, pass_hash_xor1, 64);
1504 if (y >= 3)
1505 gaim_cipher_context_set_option(sha1_ctx1, "sizeLo", GINT_TO_POINTER(0x1ff));
1506 gaim_cipher_context_append(sha1_ctx1, magic_key_char, 4);
1507 gaim_cipher_context_digest(sha1_ctx1, sizeof(digest1), digest1, NULL);
1508
1509 /*
1510 * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
1511 * of the first context.
1512 */
1513
1514 gaim_cipher_context_append(sha1_ctx2, pass_hash_xor2, 64);
1515 gaim_cipher_context_append(sha1_ctx2, digest1, 20);
1516 gaim_cipher_context_digest(sha1_ctx2, sizeof(digest2), digest2, NULL);
1517
1518 /*
1519 * Now that we have digest2, use it to fetch characters from an alphabet to construct
1520 * our first authentication response.
1521 */
1522
1523 for (x = 0; x < 20; x += 2) {
1524 unsigned int val = 0;
1525 unsigned int lookup = 0;
1526
1527 char byte[6];
1528
1529 memset(&byte, 0, 6);
1530
1531 /* First two bytes of digest stuffed together. */
1532
1533 val = digest2[x];
1534 val <<= 8;
1535 val += digest2[x+1];
1536
1537 lookup = (val >> 0x0b);
1538 lookup &= 0x1f;
1539 if (lookup >= strlen(alphabet1))
1540 break;
1541 sprintf(byte, "%c", alphabet1[lookup]);
1542 strcat(resp_6, byte);
1543 strcat(resp_6, "=");
1544
1545 lookup = (val >> 0x06);
1546 lookup &= 0x1f;
1547 if (lookup >= strlen(alphabet2))
1548 break;
1549 sprintf(byte, "%c", alphabet2[lookup]);
1550 strcat(resp_6, byte);
1551
1552 lookup = (val >> 0x01);
1553 lookup &= 0x1f;
1554 if (lookup >= strlen(alphabet2))
1555 break;
1556 sprintf(byte, "%c", alphabet2[lookup]);
1557 strcat(resp_6, byte);
1558
1559 lookup = (val & 0x01);
1560 if (lookup >= strlen(delimit_lookup))
1561 break;
1562 sprintf(byte, "%c", delimit_lookup[lookup]);
1563 strcat(resp_6, byte);
1564 }
1565
1566 /* Our second authentication response is based off of the crypto hash. */
1567
1568 cnt = 0;
1569 memset(&digest1, 0, 20);
1570 memset(&digest2, 0, 20);
1571
1572 for (x = 0; x < (int)strlen(crypt_hash); x++)
1573 crypt_hash_xor1[cnt++] = crypt_hash[x] ^ 0x36;
1574
1575 if (cnt < 64)
1576 memset(&(crypt_hash_xor1[cnt]), 0x36, 64-cnt);
1577
1578 cnt = 0;
1579
1580 for (x = 0; x < (int)strlen(crypt_hash); x++)
1581 crypt_hash_xor2[cnt++] = crypt_hash[x] ^ 0x5c;
1582
1583 if (cnt < 64)
1584 memset(&(crypt_hash_xor2[cnt]), 0x5c, 64-cnt);
1585
1586 gaim_cipher_context_reset(sha1_ctx1, NULL);
1587 gaim_cipher_context_reset(sha1_ctx2, NULL);
1588
1589 /*
1590 * The first context gets the password hash XORed with 0x36 plus a magic value
1591 * which we previously extrapolated from our challenge.
1592 */
1593
1594 gaim_cipher_context_append(sha1_ctx1, crypt_hash_xor1, 64);
1595 if (y >= 3) {
1596 gaim_cipher_context_set_option(sha1_ctx1, "sizeLo",
1597 GINT_TO_POINTER(0x1ff));
1598 }
1599 gaim_cipher_context_append(sha1_ctx1, magic_key_char, 4);
1600 gaim_cipher_context_digest(sha1_ctx1, sizeof(digest1), digest1, NULL);
1601
1602 /*
1603 * The second context gets the password hash XORed with 0x5c plus the SHA-1 digest
1604 * of the first context.
1605 */
1606
1607 gaim_cipher_context_append(sha1_ctx2, crypt_hash_xor2, 64);
1608 gaim_cipher_context_append(sha1_ctx2, digest1, 20);
1609 gaim_cipher_context_digest(sha1_ctx2, sizeof(digest2), digest2, NULL);
1610
1611 /*
1612 * Now that we have digest2, use it to fetch characters from an alphabet to construct
1613 * our first authentication response.
1614 */
1615
1616 for (x = 0; x < 20; x += 2) {
1617 unsigned int val = 0;
1618 unsigned int lookup = 0;
1619
1620 char byte[6];
1621
1622 memset(&byte, 0, 6);
1623
1624 /* First two bytes of digest stuffed together. */
1625
1626 val = digest2[x];
1627 val <<= 8;
1628 val += digest2[x+1];
1629
1630 lookup = (val >> 0x0b);
1631 lookup &= 0x1f;
1632 if (lookup >= strlen(alphabet1))
1633 break;
1634 sprintf(byte, "%c", alphabet1[lookup]);
1635 strcat(resp_96, byte);
1636 strcat(resp_96, "=");
1637
1638 lookup = (val >> 0x06);
1639 lookup &= 0x1f;
1640 if (lookup >= strlen(alphabet2))
1641 break;
1642 sprintf(byte, "%c", alphabet2[lookup]);
1643 strcat(resp_96, byte);
1644
1645 lookup = (val >> 0x01);
1646 lookup &= 0x1f;
1647 if (lookup >= strlen(alphabet2))
1648 break;
1649 sprintf(byte, "%c", alphabet2[lookup]);
1650 strcat(resp_96, byte);
1651
1652 lookup = (val & 0x01);
1653 if (lookup >= strlen(delimit_lookup))
1654 break;
1655 sprintf(byte, "%c", delimit_lookup[lookup]);
1656 strcat(resp_96, byte);
1657 }
1658 gaim_debug_info("yahoo", "yahoo status: %d\n", yd->current_status);
1659 pack = yahoo_packet_new(YAHOO_SERVICE_AUTHRESP, yd->current_status, 0);
1660 yahoo_packet_hash(pack, "sssss", 0, name, 6, resp_6, 96, resp_96, 1,
1661 name, 135, "6,0,0,1710");
1662 if (yd->picture_checksum)
1663 yahoo_packet_hash_int(pack, 192, yd->picture_checksum);
1664
1665 yahoo_packet_send_and_free(pack, yd);
1666
1667 gaim_cipher_context_destroy(md5_ctx);
1668 gaim_cipher_context_destroy(sha1_ctx1);
1669 gaim_cipher_context_destroy(sha1_ctx2);
1670
1671 g_free(password_hash);
1672 g_free(crypt_hash);
1673 }
1674
1675 static void yahoo_process_auth(GaimConnection *gc, struct yahoo_packet *pkt)
1676 {
1677 char *seed = NULL;
1678 char *sn = NULL;
1679 GSList *l = pkt->hash;
1680 int m = 0;
1681 gchar *buf;
1682
1683 while (l) {
1684 struct yahoo_pair *pair = l->data;
1685 if (pair->key == 94)
1686 seed = pair->value;
1687 if (pair->key == 1)
1688 sn = pair->value;
1689 if (pair->key == 13)
1690 m = atoi(pair->value);
1691 l = l->next;
1692 }
1693
1694 if (seed) {
1695 switch (m) {
1696 case 0:
1697 yahoo_process_auth_old(gc, seed);
1698 break;
1699 case 1:
1700 yahoo_process_auth_new(gc, seed);
1701 break;
1702 default:
1703 buf = g_strdup_printf(_("The Yahoo server has requested the use of an unrecognized "
1704 "authentication method. This version of Gaim will likely not be able "
1705 "to successfully sign on to Yahoo. Check %s for updates."), GAIM_WEBSITE);
1706 gaim_notify_error(gc, "", _("Failed Yahoo! Authentication"),
1707 buf);
1708 g_free(buf);
1709 yahoo_process_auth_new(gc, seed); /* Can't hurt to try it anyway. */
1710 }
1711 }
1712 }
1713
1714 static void ignore_buddy(GaimBuddy *buddy) {
1715 GaimGroup *group;
1716 GaimAccount *account;
1717 gchar *name;
1718
1719 if (!buddy)
1720 return;
1721
1722 group = gaim_buddy_get_group(buddy);
1723 name = g_strdup(buddy->name);
1724 account = buddy->account;
1725
1726 gaim_debug(GAIM_DEBUG_INFO, "blist",
1727 "Removing '%s' from buddy list.\n", buddy->name);
1728 gaim_account_remove_buddy(account, buddy, group);
1729 gaim_blist_remove_buddy(buddy);
1730
1731 serv_add_deny(account->gc, name);
1732
1733 g_free(name);
1734 }
1735
1736 static void keep_buddy(GaimBuddy *b) {
1737 gaim_privacy_deny_remove(b->account, b->name, 1);
1738 }
1739
1740 static void yahoo_process_ignore(GaimConnection *gc, struct yahoo_packet *pkt) {
1741 GaimBuddy *b;
1742 GSList *l;
1743 gchar *who = NULL;
1744 gchar *sn = NULL;
1745 gchar buf[BUF_LONG];
1746 gint ignore = 0;
1747 gint status = 0;
1748
1749 for (l = pkt->hash; l; l = l->next) {
1750 struct yahoo_pair *pair = l->data;
1751 switch (pair->key) {
1752 case 0:
1753 who = pair->value;
1754 break;
1755 case 1:
1756 sn = pair->value;
1757 break;
1758 case 13:
1759 ignore = strtol(pair->value, NULL, 10);
1760 break;
1761 case 66:
1762 status = strtol(pair->value, NULL, 10);
1763 break;
1764 default:
1765 break;
1766 }
1767 }
1768
1769 switch (status) {
1770 case 12:
1771 b = gaim_find_buddy(gc->account, who);
1772 g_snprintf(buf, sizeof(buf), _("You have tried to ignore %s, but the "
1773 "user is on your buddy list. Clicking \"Yes\" "
1774 "will remove and ignore the buddy."), who);
1775 gaim_request_yes_no(gc, NULL, _("Ignore buddy?"), buf, 0, b,
1776 G_CALLBACK(ignore_buddy),
1777 G_CALLBACK(keep_buddy));
1778 break;
1779 case 2:
1780 case 3:
1781 case 0:
1782 default:
1783 break;
1784 }
1785 }
1786
1787 static void yahoo_process_authresp(GaimConnection *gc, struct yahoo_packet *pkt)
1788 {
1789 struct yahoo_data *yd = gc->proto_data;
1790 GSList *l = pkt->hash;
1791 int err = 0;
1792 char *msg;
1793 char *url = NULL;
1794 char *fullmsg;
1795
1796 while (l) {
1797 struct yahoo_pair *pair = l->data;
1798
1799 if (pair->key == 66)
1800 err = strtol(pair->value, NULL, 10);
1801 if (pair->key == 20)
1802 url = pair->value;
1803
1804 l = l->next;
1805 }
1806
1807 switch (err) {
1808 case 3:
1809 msg = g_strdup(_("Invalid username."));
1810 break;
1811 case 13:
1812 if (!yd->wm) {
1813 yd->wm = TRUE;
1814 if (yd->fd >= 0)
1815 close(yd->fd);
1816 if (gc->inpa)
1817 gaim_input_remove(gc->inpa);
1818 gaim_url_fetch(WEBMESSENGER_URL, TRUE, "Gaim/" VERSION, FALSE,
1819 yahoo_login_page_cb, gc);
1820 gaim_notify_warning(gc, NULL, _("Normal authentication failed!"),
1821 _("The normal authentication method has failed. "
1822 "This means either your password is incorrect, "
1823 "or Yahoo!'s authentication scheme has changed. "
1824 "Gaim will now attempt to log in using Web "
1825 "Messenger authentication, which will result "
1826 "in reduced functionality and features."));
1827 return;
1828 }
1829 msg = g_strdup(_("Incorrect password."));
1830 break;
1831 case 14:
1832 msg = g_strdup(_("Your account is locked, please log in to the Yahoo! website."));
1833 break;
1834 default:
1835 msg = g_strdup_printf(_("Unknown error number %d. Logging into the Yahoo! website may fix this."), err);
1836 }
1837
1838 if (url)
1839 fullmsg = g_strdup_printf("%s\n%s", msg, url);
1840 else
1841 fullmsg = g_strdup(msg);
1842
1843 gc->wants_to_die = TRUE;
1844 gaim_connection_error(gc, fullmsg);
1845 g_free(msg);
1846 g_free(fullmsg);
1847 }
1848
1849 static void yahoo_process_addbuddy(GaimConnection *gc, struct yahoo_packet *pkt)
1850 {
1851 int err = 0;
1852 char *who = NULL;
1853 char *group = NULL;
1854 char *decoded_group;
1855 char *buf;
1856 YahooFriend *f;
1857 GSList *l = pkt->hash;
1858
1859 while (l) {
1860 struct yahoo_pair *pair = l->data;
1861
1862 switch (pair->key) {
1863 case 66:
1864 err = strtol(pair->value, NULL, 10);
1865 break;
1866 case 7:
1867 who = pair->value;
1868 break;
1869 case 65:
1870 group = pair->value;
1871 break;
1872 }
1873
1874 l = l->next;
1875 }
1876
1877 if (!who)
1878 return;
1879 if (!group)
1880 group = "";
1881
1882 if (!err || (err == 2)) { /* 0 = ok, 2 = already on serv list */
1883 f = yahoo_friend_find_or_new(gc, who);
1884 yahoo_update_status(gc, who, f);
1885 return;
1886 }
1887
1888 decoded_group = yahoo_string_decode(gc, group, FALSE);
1889 buf = g_strdup_printf(_("Could not add buddy %s to group %s to the server list on account %s."),
1890 who, decoded_group, gaim_connection_get_display_name(gc));
1891 if (!gaim_conv_present_error(who, gaim_connection_get_account(gc), buf))
1892 gaim_notify_error(gc, NULL, _("Could not add buddy to server list"), buf);
1893 g_free(buf);
1894 g_free(decoded_group);
1895 }
1896
1897 static void yahoo_process_p2p(GaimConnection *gc, struct yahoo_packet *pkt)
1898 {
1899 GSList *l = pkt->hash;
1900 char *who = NULL;
1901 char *base64 = NULL;
1902 guchar *decoded;
1903 gsize len;
1904
1905 while (l) {
1906 struct yahoo_pair *pair = l->data;
1907
1908 switch (pair->key) {
1909 case 5:
1910 /* our identity */
1911 break;
1912 case 4:
1913 who = pair->value;
1914 break;
1915 case 1:
1916 /* who again, the master identity this time? */
1917 break;
1918 case 12:
1919 base64 = pair->value;
1920 /* so, this is an ip address. in base64. decoded it's in ascii.
1921 after strtol, it's in reversed byte order. Who thought this up?*/
1922 break;
1923 /*
1924 TODO: figure these out
1925 yahoo: Key: 61 Value: 0
1926 yahoo: Key: 2 Value:
1927 yahoo: Key: 13 Value: 0
1928 yahoo: Key: 49 Value: PEERTOPEER
1929 yahoo: Key: 140 Value: 1
1930 yahoo: Key: 11 Value: -1786225828
1931 */
1932
1933 }
1934
1935 l = l->next;
1936 }
1937
1938 if (base64) {
1939 guint32 ip;
1940 char *tmp2;
1941 YahooFriend *f;
1942
1943 decoded = gaim_base64_decode(base64, &len);
1944 if (len) {
1945 char *tmp = gaim_str_binary_to_ascii(decoded, len);
1946 gaim_debug_info("yahoo", "Got P2P service packet (from server): who = %s, ip = %s\n", who, tmp);
1947 g_free(tmp);
1948 }
1949
1950 tmp2 = g_strndup((const gchar *)decoded, len); /* so its \0 terminated...*/
1951 ip = strtol(tmp2, NULL, 10);
1952 g_free(tmp2);
1953 g_free(decoded);
1954 tmp2 = g_strdup_printf("%u.%u.%u.%u", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff,
1955 (ip >> 24) & 0xff);
1956 f = yahoo_friend_find(gc, who);
1957 if (f)
1958 yahoo_friend_set_ip(f, tmp2);
1959 g_free(tmp2);
1960 }
1961 }
1962
1963 static void yahoo_process_audible(GaimConnection *gc, struct yahoo_packet *pkt)
1964 {
1965 char *who = NULL, *msg = NULL, *id = NULL;
1966 GSList *l = pkt->hash;
1967
1968 while (l) {
1969 struct yahoo_pair *pair = l->data;
1970
1971 switch (pair->key) {
1972 case 4:
1973 who = pair->value;
1974 break;
1975 case 5:
1976 /* us */
1977 break;
1978 case 230:
1979 /* the audible, in foo.locale.bar.baz format
1980 eg: base.tw.smiley.smiley43 */
1981 id = pair->value;
1982 break;
1983 case 231:
1984 /* the text of the audible */
1985 msg = pair->value;
1986 break;
1987 case 232:
1988 /* weird number (md5 hash?), like 8ebab9094156135f5dcbaccbeee662a5c5fd1420 */
1989 break;
1990 }
1991
1992 l = l->next;
1993 }
1994
1995 if (!msg)
1996 msg = id;
1997 if (!who || !msg)
1998 return;
1999 if (!g_utf8_validate(msg, -1, NULL)) {
2000 gaim_debug_misc("yahoo", "Warning, nonutf8 audible, ignoring!\n");
2001 return;
2002 }
2003 if (!yahoo_privacy_check(gc, who)) {
2004 gaim_debug_misc("yahoo", "Audible message from %s for %s dropped!\n",
2005 gc->account->username, who);
2006 return;
2007 }
2008 if (id) {
2009 /* "http://us.dl1.yimg.com/download.yahoo.com/dl/aud/"+locale+"/"+id+".swf" */
2010 char **audible_locale = g_strsplit(id, ".", 0);
2011 char *buf = g_strdup_printf(_("[ Audible %s/%s/%s.swf ] %s"), YAHOO_AUDIBLE_URL, audible_locale[1], id, msg);
2012 g_strfreev(audible_locale);
2013
2014 serv_got_im(gc, who, buf, 0, time(NULL));
2015 g_free(buf);
2016 } else
2017 serv_got_im(gc, who, msg, 0, time(NULL));
2018 }
2019
2020 static void yahoo_packet_process(GaimConnection *gc, struct yahoo_packet *pkt)
2021 {
2022 switch (pkt->service) {
2023 case YAHOO_SERVICE_LOGON:
2024 case YAHOO_SERVICE_LOGOFF:
2025 case YAHOO_SERVICE_ISAWAY:
2026 case YAHOO_SERVICE_ISBACK:
2027 case YAHOO_SERVICE_GAMELOGON:
2028 case YAHOO_SERVICE_GAMELOGOFF:
2029 case YAHOO_SERVICE_CHATLOGON:
2030 case YAHOO_SERVICE_CHATLOGOFF:
2031 case YAHOO_SERVICE_Y6_STATUS_UPDATE:
2032 yahoo_process_status(gc, pkt);
2033 break;
2034 case YAHOO_SERVICE_NOTIFY:
2035 yahoo_process_notify(gc, pkt);
2036 break;
2037 case YAHOO_SERVICE_MESSAGE:
2038 case YAHOO_SERVICE_GAMEMSG:
2039 case YAHOO_SERVICE_CHATMSG:
2040 yahoo_process_message(gc, pkt);
2041 break;
2042 case YAHOO_SERVICE_SYSMESSAGE:
2043 yahoo_process_sysmessage(gc, pkt);
2044 break;
2045 case YAHOO_SERVICE_NEWMAIL:
2046 yahoo_process_mail(gc, pkt);
2047 break;
2048 case YAHOO_SERVICE_NEWCONTACT:
2049 yahoo_process_contact(gc, pkt);
2050 break;
2051 case YAHOO_SERVICE_AUTHRESP:
2052 yahoo_process_authresp(gc, pkt);
2053 break;
2054 case YAHOO_SERVICE_LIST:
2055 yahoo_process_list(gc, pkt);
2056 break;
2057 case YAHOO_SERVICE_AUTH:
2058 yahoo_process_auth(gc, pkt);
2059 break;
2060 case YAHOO_SERVICE_ADDBUDDY:
2061 yahoo_process_addbuddy(gc, pkt);
2062 break;
2063 case YAHOO_SERVICE_IGNORECONTACT:
2064 yahoo_process_ignore(gc, pkt);
2065 break;
2066 case YAHOO_SERVICE_CONFINVITE:
2067 case YAHOO_SERVICE_CONFADDINVITE:
2068 yahoo_process_conference_invite(gc, pkt);
2069 break;
2070 case YAHOO_SERVICE_CONFDECLINE:
2071 yahoo_process_conference_decline(gc, pkt);
2072 break;
2073 case YAHOO_SERVICE_CONFLOGON:
2074 yahoo_process_conference_logon(gc, pkt);
2075 break;
2076 case YAHOO_SERVICE_CONFLOGOFF:
2077 yahoo_process_conference_logoff(gc, pkt);
2078 break;
2079 case YAHOO_SERVICE_CONFMSG:
2080 yahoo_process_conference_message(gc, pkt);
2081 break;
2082 case YAHOO_SERVICE_CHATONLINE:
2083 yahoo_process_chat_online(gc, pkt);
2084 break;
2085 case YAHOO_SERVICE_CHATLOGOUT:
2086 yahoo_process_chat_logout(gc, pkt);
2087 break;
2088 case YAHOO_SERVICE_CHATGOTO:
2089 yahoo_process_chat_goto(gc, pkt);
2090 break;
2091 case YAHOO_SERVICE_CHATJOIN:
2092 yahoo_process_chat_join(gc, pkt);
2093 break;
2094 case YAHOO_SERVICE_CHATLEAVE: /* XXX is this right? */
2095 case YAHOO_SERVICE_CHATEXIT:
2096 yahoo_process_chat_exit(gc, pkt);
2097 break;
2098 case YAHOO_SERVICE_CHATINVITE: /* XXX never seen this one, might not do it right */
2099 case YAHOO_SERVICE_CHATADDINVITE:
2100 yahoo_process_chat_addinvite(gc, pkt);
2101 break;
2102 case YAHOO_SERVICE_COMMENT:
2103 yahoo_process_chat_message(gc, pkt);
2104 break;
2105 case YAHOO_SERVICE_PRESENCE_PERM:
2106 case YAHOO_SERVICE_PRESENCE_SESSION:
2107 yahoo_process_presence(gc, pkt);
2108 break;
2109 case YAHOO_SERVICE_P2PFILEXFER:
2110 /* This case had no break and continued; thus keeping it this way.*/
2111 yahoo_process_p2pfilexfer(gc, pkt);
2112 case YAHOO_SERVICE_FILETRANSFER:
2113 yahoo_process_filetransfer(gc, pkt);
2114 break;
2115 case YAHOO_SERVICE_PEERTOPEER:
2116 yahoo_process_p2p(gc, pkt);
2117 break;
2118 case YAHOO_SERVICE_PICTURE:
2119 yahoo_process_picture(gc, pkt);
2120 break;
2121 case YAHOO_SERVICE_PICTURE_UPDATE:
2122 yahoo_process_picture_update(gc, pkt);
2123 break;
2124 case YAHOO_SERVICE_PICTURE_CHECKSUM:
2125 yahoo_process_picture_checksum(gc, pkt);
2126 break;
2127 case YAHOO_SERVICE_PICTURE_UPLOAD:
2128 yahoo_process_picture_upload(gc, pkt);
2129 break;
2130 case YAHOO_SERVICE_AUDIBLE:
2131 yahoo_process_audible(gc, pkt);
2132 break;
2133 default:
2134 gaim_debug(GAIM_DEBUG_ERROR, "yahoo",
2135 "Unhandled service 0x%02x\n", pkt->service);
2136 break;
2137 }
2138 }
2139
2140 static void yahoo_pending(gpointer data, gint source, GaimInputCondition cond)
2141 {
2142 GaimConnection *gc = data;
2143 struct yahoo_data *yd = gc->proto_data;
2144 char buf[1024];
2145 int len;
2146
2147 len = read(yd->fd, buf, sizeof(buf));
2148
2149 if (len <= 0) {
2150 gaim_connection_error(gc, _("Unable to read"));
2151 return;
2152 }
2153
2154 yd->rxqueue = g_realloc(yd->rxqueue, len + yd->rxlen);
2155 memcpy(yd->rxqueue + yd->rxlen, buf, len);
2156 yd->rxlen += len;
2157
2158 while (1) {
2159 struct yahoo_packet *pkt;
2160 int pos = 0;
2161 int pktlen;
2162
2163 if (yd->rxlen < YAHOO_PACKET_HDRLEN)
2164 return;
2165
2166 if (strncmp((char *)yd->rxqueue, "YMSG", MIN(4, yd->rxlen)) != 0) {
2167 /* HEY! This isn't even a YMSG packet. What
2168 * are you trying to pull? */
2169 guchar *start;
2170
2171 gaim_debug_warning("yahoo", "Error in YMSG stream, got something not a YMSG packet!");
2172
2173 start = memchr(yd->rxqueue + 1, 'Y', yd->rxlen - 1);
2174 if (start) {
2175 g_memmove(yd->rxqueue, start, yd->rxlen - (start - yd->rxqueue));
2176 yd->rxlen -= start - yd->rxqueue;
2177 continue;
2178 } else {
2179 g_free(yd->rxqueue);
2180 yd->rxqueue = NULL;
2181 yd->rxlen = 0;
2182 return;
2183 }
2184 }
2185
2186 pos += 4; /* YMSG */
2187 pos += 2;
2188 pos += 2;
2189
2190 pktlen = yahoo_get16(yd->rxqueue + pos); pos += 2;
2191 gaim_debug(GAIM_DEBUG_MISC, "yahoo",
2192 "%d bytes to read, rxlen is %d\n", pktlen, yd->rxlen);
2193
2194 if (yd->rxlen < (YAHOO_PACKET_HDRLEN + pktlen))
2195 return;
2196
2197 yahoo_packet_dump(yd->rxqueue, YAHOO_PACKET_HDRLEN + pktlen);
2198
2199 pkt = yahoo_packet_new(0, 0, 0);
2200
2201 pkt->service = yahoo_get16(yd->rxqueue + pos); pos += 2;
2202 pkt->status = yahoo_get32(yd->rxqueue + pos); pos += 4;
2203 gaim_debug(GAIM_DEBUG_MISC, "yahoo",
2204 "Yahoo Service: 0x%02x Status: %d\n",
2205 pkt->service, pkt->status);
2206 pkt->id = yahoo_get32(yd->rxqueue + pos); pos += 4;
2207
2208 yahoo_packet_read(pkt, yd->rxqueue + pos, pktlen);
2209
2210 yd->rxlen -= YAHOO_PACKET_HDRLEN + pktlen;
2211 if (yd->rxlen) {
2212 guchar *tmp = g_memdup(yd->rxqueue + YAHOO_PACKET_HDRLEN + pktlen, yd->rxlen);
2213 g_free(yd->rxqueue);
2214 yd->rxqueue = tmp;
2215 } else {
2216 g_free(yd->rxqueue);
2217 yd->rxqueue = NULL;
2218 }
2219
2220 yahoo_packet_process(gc, pkt);
2221
2222 yahoo_packet_free(pkt);
2223 }
2224 }
2225
2226 static void yahoo_got_connected(gpointer data, gint source, GaimInputCondition cond)
2227 {
2228 GaimConnection *gc = data;
2229 struct yahoo_data *yd;
2230 struct yahoo_packet *pkt;
2231
2232 if (!g_list_find(gaim_connections_get_all(), gc)) {
2233 close(source);
2234 return;
2235 }
2236
2237 if (source < 0) {
2238 gaim_connection_error(gc, _("Unable to connect."));
2239 return;
2240 }
2241
2242 yd = gc->proto_data;
2243 yd->fd = source;
2244
2245 pkt = yahoo_packet_new(YAHOO_SERVICE_AUTH, yd->current_status, 0);
2246
2247 yahoo_packet_hash_str(pkt, 1, gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))));
2248 yahoo_packet_send_and_free(pkt, yd);
2249
2250 gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
2251 }
2252
2253 static void yahoo_got_web_connected(gpointer data, gint source, GaimInputCondition cond)
2254 {
2255 GaimConnection *gc = data;
2256 struct yahoo_data *yd;
2257 struct yahoo_packet *pkt;
2258
2259 if (!g_list_find(gaim_connections_get_all(), gc)) {
2260 close(source);
2261 return;
2262 }
2263
2264 if (source < 0) {
2265 gaim_connection_error(gc, _("Unable to connect."));
2266 return;
2267 }
2268
2269 yd = gc->proto_data;
2270 yd->fd = source;
2271
2272 pkt = yahoo_packet_new(YAHOO_SERVICE_WEBLOGIN, YAHOO_STATUS_WEBLOGIN, 0);
2273
2274 yahoo_packet_hash(pkt, "sss", 0,
2275 gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))),
2276 1, gaim_normalize(gc->account, gaim_account_get_username(gaim_connection_get_account(gc))),
2277 6, yd->auth);
2278 yahoo_packet_send_and_free(pkt, yd);
2279
2280 g_free(yd->auth);
2281 gc->inpa = gaim_input_add(yd->fd, GAIM_INPUT_READ, yahoo_pending, gc);
2282 }
2283
2284 static void yahoo_web_pending(gpointer data, gint source, GaimInputCondition cond)
2285 {
2286 GaimConnection *gc = data;
2287 GaimAccount *account = gaim_connection_get_account(gc);
2288 struct yahoo_data *yd = gc->proto_data;
2289 char buf[2048], *i = buf;
2290 int len;
2291 GString *s;
2292
2293 len = read(source, buf, sizeof(buf)-1);
2294 if (len <= 0 || (strncmp(buf, "HTTP/1.0 302", strlen("HTTP/1.0 302")) &&
2295 strncmp(buf, "HTTP/1.1 302", strlen("HTTP/1.1 302")))) {
2296 gaim_connection_error(gc, _("Unable to read"));
2297 return;
2298 }
2299
2300 s = g_string_sized_new(len);
2301 buf[sizeof(buf)-1] = '\0';
2302
2303 while ((i = strstr(i, "Set-Cookie: "))) {
2304 i += strlen("Set-Cookie: ");
2305 for (;*i != ';' && *i != '\0'; i++)
2306 g_string_append_c(s, *i);
2307
2308 g_string_append(s, "; ");
2309 }
2310
2311 yd->auth = g_string_free(s, FALSE);
2312 gaim_input_remove(gc->inpa);
2313 close(source);
2314 /* Now we have our cookies to login with. I'll go get the milk. */
2315 if (gaim_proxy_connect(account, "wcs2.msg.dcn.yahoo.com",
2316 gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
2317 yahoo_got_web_connected, gc) != 0) {
2318 gaim_connection_error(gc, _("Connection problem"));
2319 return;
2320 }
2321 }
2322
2323 static void yahoo_got_cookies(gpointer data, gint source, GaimInputCondition cond)
2324 {
2325 GaimConnection *gc = data;
2326 struct yahoo_data *yd = gc->proto_data;
2327 if (source < 0) {
2328 gaim_connection_error(gc, _("Unable to connect."));
2329 return;
2330 }
2331 write(source, yd->auth, strlen(yd->auth));
2332 g_free(yd->auth);
2333 gc->inpa = gaim_input_add(source, GAIM_INPUT_READ, yahoo_web_pending, gc);
2334 }
2335
2336 static void yahoo_login_page_hash_iter(const char *key, const char *val, GString *url)
2337 {
2338 if (!strcmp(key, "passwd"))
2339 return;
2340 url = g_string_append_c(url, '&');
2341 url = g_string_append(url, key);
2342 url = g_string_append_c(url, '=');
2343 if (!strcmp(key, ".save") || !strcmp(key, ".js"))
2344 url = g_string_append_c(url, '1');
2345 else if (!strcmp(key, ".challenge"))
2346 url = g_string_append(url, val);
2347 else
2348 url = g_string_append(url, gaim_url_encode(val));
2349 }
2350
2351 static GHashTable *yahoo_login_page_hash(const char *buf, size_t len)
2352 {
2353 GHashTable *hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2354 const char *c = buf;
2355 char *d;
2356 char name[64], value[64];
2357 int count;
2358 int input_len = strlen("<input ");
2359 int name_len = strlen("name=\"");
2360 int value_len = strlen("value=\"");
2361 while ((len > ((c - buf) + input_len))
2362 && (c = strstr(c, "<input "))) {
2363 if (!(c = g_strstr_len(c, len - (c - buf), "name=\"")))
2364 continue;
2365 c += name_len;
2366 count = sizeof(name)-1;
2367 for (d = name; (len > ((c - buf) + 1)) && *c!='"'
2368 && count; c++, d++, count--)
2369 *d = *c;
2370 *d = '\0';
2371 count = sizeof(value)-1;
2372 if (!(d = g_strstr_len(c, len - (c - buf), "value=\"")))
2373 continue;
2374 d += value_len;
2375 if (strchr(c, '>') < d)
2376 break;
2377 for (c = d, d = value; (len > ((c - buf) + 1))
2378 && *c!='"' && count; c++, d++, count--)
2379 *d = *c;
2380 *d = '\0';
2381 g_hash_table_insert(hash, g_strdup(name), g_strdup(value));
2382 }
2383 return hash;
2384 }
2385
2386 static void yahoo_login_page_cb(void *user_data, const char *buf, size_t len)
2387 {
2388 GaimConnection *gc = (GaimConnection *)user_data;
2389 GaimAccount *account = gaim_connection_get_account(gc);
2390 struct yahoo_data *yd = gc->proto_data;
2391 const char *sn = gaim_account_get_username(account);
2392 const char *pass = gaim_connection_get_password(gc);
2393 GHashTable *hash = yahoo_login_page_hash(buf, len);
2394 GString *url = g_string_new("GET http://login.yahoo.com/config/login?login=");
2395 char md5[33], *hashp = md5, *chal;
2396 int i;
2397 GaimCipher *cipher;
2398 GaimCipherContext *context;
2399 guchar digest[16];
2400
2401 url = g_string_append(url, sn);
2402 url = g_string_append(url, "&passwd=");
2403
2404 cipher = gaim_ciphers_find_cipher("md5");
2405 context = gaim_cipher_context_new(cipher, NULL);
2406
2407 gaim_cipher_context_append(context, (const guchar *)pass, strlen(pass));
2408 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
2409 for (i = 0; i < 16; ++i) {
2410 g_snprintf(hashp, 3, "%02x", digest[i]);
2411 hashp += 2;
2412 }
2413
2414 chal = g_strconcat(md5, g_hash_table_lookup(hash, ".challenge"), NULL);
2415 gaim_cipher_context_reset(context, NULL);
2416 gaim_cipher_context_append(context, (const guchar *)chal, strlen(chal));
2417 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
2418 hashp = md5;
2419 for (i = 0; i < 16; ++i) {
2420 g_snprintf(hashp, 3, "%02x", digest[i]);
2421 hashp += 2;
2422 }
2423 /*
2424 * I dunno why this is here and commented out.. but in case it's needed
2425 * I updated it..
2426
2427 gaim_cipher_context_reset(context, NULL);
2428 gaim_cipher_context_append(context, md5, strlen(md5));
2429 gaim_cipher_context_digest(context, sizeof(digest), digest, NULL);
2430 hashp = md5;
2431 for (i = 0; i < 16; ++i) {
2432 g_snprintf(hashp, 3, "%02x", digest[i]);
2433 hashp += 2;
2434 }
2435 */
2436 g_free(chal);
2437
2438 url = g_string_append(url, md5);
2439 g_hash_table_foreach(hash, (GHFunc)yahoo_login_page_hash_iter, url);
2440
2441 url = g_string_append(url, "&.hash=1&.md5=1 HTTP/1.1\r\n"
2442 "Host: login.yahoo.com\r\n\r\n");
2443 g_hash_table_destroy(hash);
2444 yd->auth = g_string_free(url, FALSE);
2445 if (gaim_proxy_connect(account, "login.yahoo.com", 80, yahoo_got_cookies, gc) != 0) {
2446 gaim_connection_error(gc, _("Connection problem"));
2447 return;
2448 }
2449
2450 gaim_cipher_context_destroy(context);
2451 }
2452
2453 static void yahoo_server_check(GaimAccount *account)
2454 {
2455 const char *server;
2456
2457 server = gaim_account_get_string(account, "server", YAHOO_PAGER_HOST);
2458
2459 if (strcmp(server, "scs.yahoo.com") == 0)
2460 gaim_account_set_string(account, "server", YAHOO_PAGER_HOST);
2461 }
2462
2463 static void yahoo_picture_check(GaimAccount *account)
2464 {
2465 GaimConnection *gc = gaim_account_get_connection(account);
2466 char *buddyicon;
2467
2468 buddyicon = gaim_buddy_icons_get_full_path(gaim_account_get_buddy_icon(account));
2469 yahoo_set_buddy_icon(gc, buddyicon);
2470 g_free(buddyicon);
2471 }
2472
2473 static int get_yahoo_status_from_gaim_status(GaimStatus *status)
2474 {
2475 GaimPresence *presence;
2476 const char *status_id;
2477 const char *msg;
2478
2479 presence = gaim_status_get_presence(status);
2480 status_id = gaim_status_get_id(status);
2481 msg = gaim_status_get_attr_string(status, "message");
2482
2483 if (!strcmp(status_id, YAHOO_STATUS_TYPE_AVAILABLE)) {
2484 if ((msg != NULL) && (*msg != '\0'))
2485 return YAHOO_STATUS_CUSTOM;
2486 else
2487 return YAHOO_STATUS_AVAILABLE;
2488 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BRB)) {
2489 return YAHOO_STATUS_BRB;
2490 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_BUSY)) {
2491 return YAHOO_STATUS_BUSY;
2492 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTATHOME)) {
2493 return YAHOO_STATUS_NOTATHOME;
2494 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTATDESK)) {
2495 return YAHOO_STATUS_NOTATDESK;
2496 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_NOTINOFFICE)) {
2497 return YAHOO_STATUS_NOTINOFFICE;
2498 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_ONPHONE)) {
2499 return YAHOO_STATUS_ONPHONE;
2500 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_ONVACATION)) {
2501 return YAHOO_STATUS_ONVACATION;
2502 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_OUTTOLUNCH)) {
2503 return YAHOO_STATUS_OUTTOLUNCH;
2504 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_STEPPEDOUT)) {
2505 return YAHOO_STATUS_STEPPEDOUT;
2506 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_INVISIBLE)) {
2507 return YAHOO_STATUS_INVISIBLE;
2508 } else if (!strcmp(status_id, YAHOO_STATUS_TYPE_AWAY)) {
2509 return YAHOO_STATUS_CUSTOM;
2510 } else if (gaim_presence_is_idle(presence)) {
2511 return YAHOO_STATUS_IDLE;
2512 } else {
2513 gaim_debug_error("yahoo", "Unexpected GaimStatus!\n");
2514 return YAHOO_STATUS_AVAILABLE;
2515 }
2516 }
2517
2518 static void yahoo_login(GaimAccount *account) {
2519 GaimConnection *gc = gaim_account_get_connection(account);
2520 struct yahoo_data *yd = gc->proto_data = g_new0(struct yahoo_data, 1);
2521 GaimStatus *status = gaim_account_get_active_status(account);
2522 gc->flags |= GAIM_CONNECTION_HTML | GAIM_CONNECTION_NO_BGCOLOR | GAIM_CONNECTION_NO_URLDESC;
2523
2524 gaim_connection_update_progress(gc, _("Connecting"), 1, 2);
2525
2526 gaim_connection_set_display_name(gc, gaim_account_get_username(account));
2527
2528 yd->fd = -1;
2529 yd->friends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, yahoo_friend_free);
2530 yd->confs = NULL;
2531 yd->conf_id = 2;
2532
2533 yd->current_status = get_yahoo_status_from_gaim_status(status);
2534
2535 yahoo_server_check(account);
2536 yahoo_picture_check(account);
2537
2538 if (gaim_account_get_bool(account, "yahoojp", FALSE)) {
2539 yd->jp = TRUE;
2540 if (gaim_proxy_connect(account,
2541 gaim_account_get_string(account, "serverjp", YAHOOJP_PAGER_HOST),
2542 gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
2543 yahoo_got_connected, gc) != 0)
2544 {
2545 gaim_connection_error(gc, _("Connection problem"));
2546 return;
2547 }
2548 } else {
2549 yd->jp = FALSE;
2550 if (gaim_proxy_connect(account,
2551 gaim_account_get_string(account, "server", YAHOO_PAGER_HOST),
2552 gaim_account_get_int(account, "port", YAHOO_PAGER_PORT),
2553 yahoo_got_connected, gc) != 0)
2554 {
2555 gaim_connection_error(gc, _("Connection problem"));
2556 return;
2557 }
2558 }
2559 }
2560
2561 static void yahoo_close(GaimConnection *gc) {
2562 struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
2563 GSList *l;
2564
2565 if (gc->inpa)
2566 gaim_input_remove(gc->inpa);
2567
2568 for (l = yd->confs; l; l = l->next) {
2569 GaimConversation *conv = l->data;
2570
2571 yahoo_conf_leave(yd, gaim_conversation_get_name(conv),
2572 gaim_connection_get_display_name(gc),
2573 gaim_conv_chat_get_users(GAIM_CONV_CHAT(conv)));
2574 }
2575 g_slist_free(yd->confs);
2576
2577 yd->chat_online = 0;
2578 if (yd->in_chat)
2579 yahoo_c_leave(gc, 1); /* 1 = YAHOO_CHAT_ID */
2580
2581 g_hash_table_destroy(yd->friends);
2582 if (yd->chat_name)
2583 g_free(yd->chat_name);
2584
2585 if (yd->cookie_y)
2586 g_free(yd->cookie_y);
2587 if (yd->cookie_t)
2588 g_free(yd->cookie_t);
2589
2590 if (yd->fd >= 0)
2591 close(yd->fd);
2592
2593 if (yd->rxqueue)
2594 g_free(yd->rxqueue);
2595 yd->rxlen = 0;
2596 if (yd->picture_url)
2597 g_free(yd->picture_url);
2598 if (yd->picture_upload_todo)
2599 yahoo_buddy_icon_upload_data_free(yd->picture_upload_todo);
2600 if (yd->ycht)
2601 ycht_connection_close(yd->ycht);
2602
2603 g_free(yd);
2604 }
2605
2606 static const char *yahoo_list_icon(GaimAccount *a, GaimBuddy *b)
2607 {
2608 return "yahoo";
2609 }
2610
2611 static void yahoo_list_emblems(GaimBuddy *b, const char **se, const char **sw, const char **nw, const char **ne)
2612 {
2613 int i = 0;
2614 char *emblems[4] = {NULL,NULL,NULL,NULL};
2615 GaimAccount *account;
2616 GaimConnection *gc;
2617 struct yahoo_data *yd;
2618 YahooFriend *f;
2619 GaimPresence *presence;
2620 GaimStatus *status;
2621 const char *status_id;
2622
2623 if (!b || !(account = b->account) || !(gc = gaim_account_get_connection(account)) ||
2624 !(yd = gc->proto_data))
2625 return;
2626
2627 f = yahoo_friend_find(gc, b->name);
2628 if (!f) {
2629 *se = "notauthorized";
2630 return;
2631 }
2632
2633 presence = gaim_buddy_get_presence(b);
2634 status = gaim_presence_get_active_status(presence);
2635 status_id = gaim_status_get_id(status);
2636
2637 if (gaim_presence_is_online(presence) == FALSE) {
2638 *se = "offline";
2639 return;
2640 } else {
2641 if (f->away)
2642 emblems[i++] = "away";
2643 if (f->sms)
2644 emblems[i++] = "wireless";
2645 if (yahoo_friend_get_game(f))
2646 emblems[i++] = "game";
2647 }
2648 *se = emblems[0];
2649 *sw = emblems[1];
2650 *nw = emblems[2];
2651 *ne = emblems[3];
2652 }
2653
2654 static char *yahoo_get_status_string(enum yahoo_status a)
2655 {
2656 switch (a) {
2657 case YAHOO_STATUS_BRB:
2658 return _("Be Right Back");
2659 case YAHOO_STATUS_BUSY:
2660 return _("Busy");
2661 case YAHOO_STATUS_NOTATHOME:
2662 return _("Not at Home");
2663 case YAHOO_STATUS_NOTATDESK:
2664 return _("Not at Desk");
2665 case YAHOO_STATUS_NOTINOFFICE:
2666 return _("Not in Office");
2667 case YAHOO_STATUS_ONPHONE:
2668 return _("On the Phone");
2669 case YAHOO_STATUS_ONVACATION:
2670 return _("On Vacation");
2671 case YAHOO_STATUS_OUTTOLUNCH:
2672 return _("Out to Lunch");
2673 case YAHOO_STATUS_STEPPEDOUT:
2674 return _("Stepped Out");
2675 case YAHOO_STATUS_INVISIBLE:
2676 return _("Invisible");
2677 case YAHOO_STATUS_IDLE:
2678 return _("Idle");
2679 case YAHOO_STATUS_OFFLINE:
2680 return _("Offline");
2681 default:
2682 return _("Available");
2683 }
2684 }
2685
2686 static void yahoo_initiate_conference(GaimBlistNode *node, gpointer data) {
2687
2688 GaimBuddy *buddy;
2689 GaimConnection *gc;
2690
2691 GHashTable *components;
2692 struct yahoo_data *yd;
2693 int id;
2694
2695 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
2696
2697 buddy = (GaimBuddy *) node;
2698 gc = gaim_account_get_connection(buddy->account);
2699 yd = gc->proto_data;
2700 id = yd->conf_id;
2701
2702 components = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2703 g_hash_table_replace(components, g_strdup("room"),
2704 g_strdup_printf("%s-%d", gaim_connection_get_display_name(gc), id));
2705 g_hash_table_replace(components, g_strdup("topic"), g_strdup("Join my conference..."));
2706 g_hash_table_replace(components, g_strdup("type"), g_strdup("Conference"));
2707 yahoo_c_join(gc, components);
2708 g_hash_table_destroy(components);
2709
2710 yahoo_c_invite(gc, id, "Join my conference...", buddy->name);
2711 }
2712
2713 static void yahoo_presence_settings(GaimBlistNode *node, gpointer data) {
2714 GaimBuddy *buddy;
2715 GaimConnection *gc;
2716 int presence_val = GPOINTER_TO_INT(data);
2717
2718 buddy = (GaimBuddy *) node;
2719 gc = gaim_account_get_connection(buddy->account);
2720
2721 yahoo_friend_update_presence(gc, buddy->name, presence_val);
2722 }
2723
2724 static void yahoo_game(GaimBlistNode *node, gpointer data) {
2725
2726 GaimBuddy *buddy;
2727 GaimConnection *gc;
2728
2729 struct yahoo_data *yd;
2730 const char *game;
2731 char *game2;
2732 char *t;
2733 char url[256];
2734 YahooFriend *f;
2735
2736 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
2737
2738 buddy = (GaimBuddy *) node;
2739 gc = gaim_account_get_connection(buddy->account);
2740 yd = (struct yahoo_data *) gc->proto_data;
2741
2742 f = yahoo_friend_find(gc, buddy->name);
2743 if (!f)
2744 return;
2745
2746 game = yahoo_friend_get_game(f);
2747 if (!game)
2748 return;
2749
2750 t = game2 = g_strdup(strstr(game, "ante?room="));
2751 while (*t && *t != '\t')
2752 t++;
2753 *t = 0;
2754 g_snprintf(url, sizeof url, "http://games.yahoo.com/games/%s", game2);
2755 gaim_notify_uri(gc, url);
2756 g_free(game2);
2757 }
2758
2759 static char *yahoo_status_text(GaimBuddy *b)
2760 {
2761 YahooFriend *f = NULL;
2762 const char *msg;
2763 char *msg2;
2764
2765 f = yahoo_friend_find(b->account->gc, b->name);
2766 if (!f)
2767 return g_strdup(_("Not on server list"));
2768
2769 switch (f->status) {
2770 case YAHOO_STATUS_AVAILABLE:
2771 return NULL;
2772 case YAHOO_STATUS_IDLE:
2773 if (f->idle == -1)
2774 return g_strdup(yahoo_get_status_string(f->status));
2775 return NULL;
2776 case YAHOO_STATUS_CUSTOM:
2777 if (!(msg = yahoo_friend_get_status_message(f)))
2778 return NULL;
2779 msg2 = g_markup_escape_text(msg, strlen(msg));
2780 gaim_util_chrreplace(msg2, '\n', ' ');
2781 return msg2;
2782
2783 default:
2784 return g_strdup(yahoo_get_status_string(f->status));
2785 }
2786 }
2787
2788 void yahoo_tooltip_text(GaimBuddy *b, GString *str, gboolean full)
2789 {
2790 YahooFriend *f;
2791 char *escaped, *status = NULL, *presence = NULL;
2792
2793 f = yahoo_friend_find(b->account->gc, b->name);
2794 if (!f)
2795 status = g_strdup_printf("\n%s", _("Not on server list"));
2796 else {
2797 switch (f->status) {
2798 case YAHOO_STATUS_CUSTOM:
2799 if (!yahoo_friend_get_status_message(f))
2800 return;
2801 status = g_strdup(yahoo_friend_get_status_message(f));
2802 break;
2803 case YAHOO_STATUS_OFFLINE:
2804 break;
2805 default:
2806 status = g_strdup(yahoo_get_status_string(f->status));
2807 break;
2808 }
2809
2810 switch (f->presence) {
2811 case YAHOO_PRESENCE_ONLINE:
2812 presence = _("Appear Online");
2813 break;
2814 case YAHOO_PRESENCE_PERM_OFFLINE:
2815 presence = _("Appear Permanently Offline");
2816 break;
2817 case YAHOO_PRESENCE_DEFAULT:
2818 break;
2819 default:
2820 gaim_debug_error("yahoo", "Unknown presence in yahoo_tooltip_text\n");
2821 break;
2822 }
2823 }
2824
2825 if (status != NULL) {
2826 escaped = g_markup_escape_text(status, strlen(status));
2827 g_string_append_printf(str, _("\n<b>%s:</b> %s"), _("Status"), escaped);
2828 g_free(status);
2829 g_free(escaped);
2830 }
2831
2832 if (presence != NULL)
2833 g_string_append_printf(str, _("\n<b>%s:</b> %s"),
2834 _("Presence"), presence);
2835 }
2836
2837 static void yahoo_addbuddyfrommenu_cb(GaimBlistNode *node, gpointer data)
2838 {
2839 GaimBuddy *buddy;
2840 GaimConnection *gc;
2841
2842 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
2843
2844 buddy = (GaimBuddy *) node;
2845 gc = gaim_account_get_connection(buddy->account);
2846
2847 yahoo_add_buddy(gc, buddy, NULL);
2848 }
2849
2850
2851 static void yahoo_chat_goto_menu(GaimBlistNode *node, gpointer data)
2852 {
2853 GaimBuddy *buddy;
2854 GaimConnection *gc;
2855
2856 g_return_if_fail(GAIM_BLIST_NODE_IS_BUDDY(node));
2857
2858 buddy = (GaimBuddy *) node;
2859 gc = gaim_account_get_connection(buddy->account);
2860
2861 yahoo_chat_goto(gc, buddy->name);
2862 }
2863
2864 static GList *build_presence_submenu(YahooFriend *f, GaimConnection *gc) {
2865 GList *m = NULL;
2866 GaimMenuAction *act;
2867 struct yahoo_data *yd = (struct yahoo_data *) gc->proto_data;
2868
2869 if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
2870 if (f->presence != YAHOO_PRESENCE_ONLINE) {
2871 act = gaim_menu_action_new(_("Appear Online"),
2872 GAIM_CALLBACK(yahoo_presence_settings),
2873 GINT_TO_POINTER(YAHOO_PRESENCE_ONLINE),
2874 NULL);
2875 m = g_list_append(m, act);
2876 } else if (f->presence != YAHOO_PRESENCE_DEFAULT) {
2877 act = gaim_menu_action_new(_("Appear Offline"),
2878 GAIM_CALLBACK(yahoo_presence_settings),
2879 GINT_TO_POINTER(YAHOO_PRESENCE_DEFAULT),
2880 NULL);
2881 m = g_list_append(m, act);
2882 }
2883 }
2884
2885 if (f->presence == YAHOO_PRESENCE_PERM_OFFLINE) {
2886 act = gaim_menu_action_new(_("Don't Appear Permanently Offline"),
2887 GAIM_CALLBACK(yahoo_presence_settings),
2888 GINT_TO_POINTER(YAHOO_PRESENCE_DEFAULT),
2889 NULL);
2890 m = g_list_append(m, act);
2891 } else {
2892 act = gaim_menu_action_new(_("Appear Permanently Offline"),
2893 GAIM_CALLBACK(yahoo_presence_settings),
2894 GINT_TO_POINTER(YAHOO_PRESENCE_PERM_OFFLINE),
2895 NULL);
2896 m = g_list_append(m, act);
2897 }
2898
2899 return m;
2900 }
2901
2902 static void yahoo_doodle_blist_node(GaimBlistNode *node, gpointer data)
2903 {
2904 GaimBuddy *b = (GaimBuddy *)node;
2905 GaimConnection *gc = b->account->gc;
2906
2907 yahoo_doodle_initiate(gc, b->name);
2908 }
2909
2910 static GList *yahoo_buddy_menu(GaimBuddy *buddy)
2911 {
2912 GList *m = NULL;
2913 GaimMenuAction *act;
2914
2915 GaimConnection *gc = gaim_account_get_connection(buddy->account);
2916 struct yahoo_data *yd = gc->proto_data;
2917 static char buf2[1024];
2918 YahooFriend *f;
2919
2920 f = yahoo_friend_find(gc, buddy->name);
2921
2922 if (!f && !yd->wm) {
2923 act = gaim_menu_action_new(_("Add Buddy"),
2924 GAIM_CALLBACK(yahoo_addbuddyfrommenu_cb),
2925 NULL, NULL);
2926 m = g_list_append(m, act);
2927
2928 return m;
2929
2930 }
2931
2932 if (f && f->status != YAHOO_STATUS_OFFLINE) {
2933 if (!yd->wm) {
2934 act = gaim_menu_action_new(_("Join in Chat"),
2935 GAIM_CALLBACK(yahoo_chat_goto_menu),
2936 NULL, NULL);
2937 m = g_list_append(m, act);
2938 }
2939
2940 act = gaim_menu_action_new(_("Initiate Conference"),
2941 GAIM_CALLBACK(yahoo_initiate_conference),
2942 NULL, NULL);
2943 m = g_list_append(m, act);
2944
2945 if (yahoo_friend_get_game(f)) {
2946 const char *game = yahoo_friend_get_game(f);
2947 char *room;
2948 char *t;
2949
2950 if ((room = strstr(game, "&follow="))) {/* skip ahead to the url */
2951 while (*room && *room != '\t') /* skip to the tab */
2952 room++;
2953 t = room++; /* room as now at the name */
2954 while (*t != '\n')
2955 t++; /* replace the \n with a space */
2956 *t = ' ';
2957 g_snprintf(buf2, sizeof buf2, "%s", room);
2958
2959 act = gaim_menu_action_new(buf2,
2960 GAIM_CALLBACK(yahoo_game),
2961 NULL, NULL);
2962 m = g_list_append(m, act);
2963 }
2964 }
2965 }
2966
2967 if (f) {
2968 act = gaim_menu_action_new(_("Presence Settings"), NULL, NULL,
2969 build_presence_submenu(f, gc));
2970 m = g_list_append(m, act);
2971 }
2972
2973 if (f) {
2974 act = gaim_menu_action_new(_("Start Doodling"),
2975 GAIM_CALLBACK(yahoo_doodle_blist_node),
2976 NULL, NULL);
2977 m = g_list_append(m, act);
2978 }
2979
2980 return m;
2981 }
2982
2983 static GList *yahoo_blist_node_menu(GaimBlistNode *node)
2984 {
2985 if(GAIM_BLIST_NODE_IS_BUDDY(node)) {
2986 return yahoo_buddy_menu((GaimBuddy *) node);
2987 } else {
2988 return NULL;
2989 }
2990 }
2991
2992 static void yahoo_act_id(GaimConnection *gc, const char *entry)
2993 {
2994 struct yahoo_data *yd = gc->proto_data;
2995
2996 struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_IDACT, YAHOO_STATUS_AVAILABLE, 0);
2997 yahoo_packet_hash_str(pkt, 3, entry);
2998 yahoo_packet_send_and_free(pkt, yd);
2999
3000 gaim_connection_set_display_name(gc, entry);
3001 }
3002
3003 static void yahoo_show_act_id(GaimPluginAction *action)
3004 {
3005 GaimConnection *gc = (GaimConnection *) action->context;
3006 gaim_request_input(gc, NULL, _("Active which ID?"), NULL,
3007 gaim_connection_get_display_name(gc), FALSE, FALSE, NULL,
3008 _("OK"), G_CALLBACK(yahoo_act_id),
3009 _("Cancel"), NULL, gc);
3010 }
3011
3012 static void yahoo_show_chat_goto(GaimPluginAction *action)
3013 {
3014 GaimConnection *gc = (GaimConnection *) action->context;
3015 gaim_request_input(gc, NULL, _("Join who in chat?"), NULL,
3016 "", FALSE, FALSE, NULL,
3017 _("OK"), G_CALLBACK(yahoo_chat_goto),
3018 _("Cancel"), NULL, gc);
3019 }
3020
3021 static GList *yahoo_actions(GaimPlugin *plugin, gpointer context) {
3022 GList *m = NULL;
3023 GaimPluginAction *act;
3024
3025 act = gaim_plugin_action_new(_("Activate ID..."),
3026 yahoo_show_act_id);
3027 m = g_list_append(m, act);
3028
3029 act = gaim_plugin_action_new(_("Join User in Chat..."),
3030 yahoo_show_chat_goto);
3031 m = g_list_append(m, act);
3032
3033 return m;
3034 }
3035
3036 static int yahoo_send_im(GaimConnection *gc, const char *who, const char *what, GaimMessageFlags flags)
3037 {
3038 struct yahoo_data *yd = gc->proto_data;
3039 struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_MESSAGE, YAHOO_STATUS_OFFLINE, 0);
3040 char *msg = yahoo_html_to_codes(what);
3041 char *msg2;
3042 gboolean utf8 = TRUE;
3043 GaimWhiteboard *wb;
3044 int ret = 1;
3045
3046 msg2 = yahoo_string_encode(gc, msg, &utf8);
3047
3048 yahoo_packet_hash(pkt, "ss", 1, gaim_connection_get_display_name(gc), 5, who);
3049 if (utf8)
3050 yahoo_packet_hash_str(pkt, 97, "1");
3051 yahoo_packet_hash_str(pkt, 14, msg2);
3052
3053 /* If this message is to a user who is also Doodling with the local user,
3054 * format the chat packet with the correct IMV information (thanks Yahoo!)
3055 */
3056 wb = gaim_whiteboard_get_session(gc->account, (char*)who);
3057 if (wb)
3058 yahoo_packet_hash_str(pkt, 63, "doodle;11");
3059 else
3060 yahoo_packet_hash_str(pkt, 63, ";0"); /* IMvironment */
3061
3062 yahoo_packet_hash_str(pkt, 64, "0"); /* no idea */
3063 yahoo_packet_hash_str(pkt, 1002, "1"); /* no idea, Yahoo 6 or later only it seems */
3064 if (!yd->picture_url)
3065 yahoo_packet_hash_str(pkt, 206, "0"); /* 0 = no picture, 2 = picture, maybe 1 = avatar? */
3066 else
3067 yahoo_packet_hash_str(pkt, 206, "2");
3068
3069 /* We may need to not send any packets over 2000 bytes, but I'm not sure yet. */
3070 if ((YAHOO_PACKET_HDRLEN + yahoo_packet_length(pkt)) <= 2000)
3071 yahoo_packet_send(pkt, yd);
3072 else
3073 ret = -E2BIG;
3074
3075 yahoo_packet_free(pkt);
3076
3077 g_free(msg);
3078 g_free(msg2);
3079
3080 return ret;
3081 }
3082
3083 static int yahoo_send_typing(GaimConnection *gc, const char *who, int typ)
3084 {
3085 struct yahoo_data *yd = gc->proto_data;
3086 struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_NOTIFY, YAHOO_STATUS_TYPING, 0);
3087 yahoo_packet_hash(pkt, "ssssss", 49, "TYPING", 1, gaim_connection_get_display_name(gc),
3088 14, " ", 13, typ == GAIM_TYPING ? "1" : "0",
3089 5, who, 1002, "1");
3090
3091 yahoo_packet_send_and_free(pkt, yd);
3092
3093 return 0;
3094 }
3095
3096 static void yahoo_session_presence_remove(gpointer key, gpointer value, gpointer data)
3097 {
3098 YahooFriend *f = value;
3099 if (f && f->presence == YAHOO_PRESENCE_ONLINE)
3100 f->presence = YAHOO_PRESENCE_DEFAULT;
3101 }
3102
3103 static void yahoo_set_status(GaimAccount *account, GaimStatus *status)
3104 {
3105 GaimConnection *gc;
3106 GaimPresence *presence;
3107 struct yahoo_data *yd;
3108 struct yahoo_packet *pkt;
3109 int old_status;
3110 const char *msg = NULL;
3111 char *tmp = NULL;
3112 char *conv_msg = NULL;
3113
3114 if (!gaim_status_is_active(status))
3115 return;
3116
3117 gc = gaim_account_get_connection(account);
3118 presence = gaim_status_get_presence(status);
3119 yd = (struct yahoo_data *)gc->proto_data;
3120 old_status = yd->current_status;
3121
3122 yd->current_status = get_yahoo_status_from_gaim_status(status);
3123
3124 if (yd->current_status == YAHOO_STATUS_CUSTOM)
3125 {
3126 msg = gaim_status_get_attr_string(status, "message");
3127
3128 if (gaim_status_is_available(status)) {
3129 tmp = yahoo_string_encode(gc, msg, NULL);
3130 conv_msg = gaim_markup_strip_html(tmp);
3131 g_free(tmp);
3132 } else {
3133 if ((msg == NULL) || (*msg == '\0'))
3134 msg = _("Away");
3135 tmp = yahoo_string_encode(gc, msg, NULL);
3136 conv_msg = gaim_markup_strip_html(tmp);
3137 g_free(tmp);
3138 }
3139 }
3140
3141 if (yd->current_status == YAHOO_STATUS_INVISIBLE) {
3142 pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0);
3143 yahoo_packet_hash_str(pkt, 13, "2");
3144 yahoo_packet_send_and_free(pkt, yd);
3145
3146 return;
3147 }
3148
3149 pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, YAHOO_STATUS_AVAILABLE, 0);
3150 yahoo_packet_hash_int(pkt, 10, yd->current_status);
3151
3152 if (yd->current_status == YAHOO_STATUS_CUSTOM) {
3153 yahoo_packet_hash_str(pkt, 19, conv_msg);
3154 } else {
3155 yahoo_packet_hash_str(pkt, 19, "");
3156 }
3157
3158 g_free(conv_msg);
3159
3160 if (gaim_presence_is_idle(presence))
3161 yahoo_packet_hash_str(pkt, 47, "2");
3162 else if (!gaim_status_is_available(status))
3163 yahoo_packet_hash_str(pkt, 47, "1");
3164
3165 yahoo_packet_send_and_free(pkt, yd);
3166
3167 if (old_status == YAHOO_STATUS_INVISIBLE) {
3168 pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_VISIBLE_TOGGLE, YAHOO_STATUS_AVAILABLE, 0);
3169 yahoo_packet_hash_str(pkt, 13, "1");
3170 yahoo_packet_send_and_free(pkt, yd);
3171
3172 /* Any per-session presence settings are removed */
3173 g_hash_table_foreach(yd->friends, yahoo_session_presence_remove, NULL);
3174
3175 }
3176 }
3177
3178 static void yahoo_set_idle(GaimConnection *gc, int idle)
3179 {
3180 struct yahoo_data *yd = gc->proto_data;
3181 struct yahoo_packet *pkt = NULL;
3182 char *msg = NULL, *msg2 = NULL;
3183
3184 if (idle && yd->current_status == YAHOO_STATUS_AVAILABLE)
3185 yd->current_status = YAHOO_STATUS_IDLE;
3186 else if (!idle && yd->current_status == YAHOO_STATUS_IDLE)
3187 yd->current_status = YAHOO_STATUS_AVAILABLE;
3188
3189 pkt = yahoo_packet_new(YAHOO_SERVICE_Y6_STATUS_UPDATE, YAHOO_STATUS_AVAILABLE, 0);
3190
3191 yahoo_packet_hash_int(pkt, 10, yd->current_status);
3192 if (yd->current_status == YAHOO_STATUS_CUSTOM) {
3193 const char *tmp;
3194 GaimStatus *status = gaim_presence_get_active_status(gaim_account_get_presence(gaim_connection_get_account(gc)));
3195 tmp = gaim_status_get_attr_string(status, "message");
3196 if (tmp != NULL) {
3197 msg = yahoo_string_encode(gc, tmp, NULL);
3198 msg2 = gaim_markup_strip_html(msg);
3199 yahoo_packet_hash_str(pkt, 19, msg2);
3200 } else {
3201 yahoo_packet_hash_str(pkt, 19, "");
3202 }
3203 } else {
3204 yahoo_packet_hash_str(pkt, 19, "");
3205 }
3206
3207 if (idle)
3208 yahoo_packet_hash_str(pkt, 47, "2");
3209 else if (!gaim_presence_is_available(gaim_account_get_presence(gaim_connection_get_account(gc))))
3210 yahoo_packet_hash_str(pkt, 47, "1");
3211
3212 yahoo_packet_send_and_free(pkt, yd);
3213
3214 g_free(msg);
3215 g_free(msg2);
3216 }
3217
3218 static GList *yahoo_status_types(GaimAccount *account)
3219 {
3220 GaimStatusType *type;
3221 GList *types = NULL;
3222
3223 type = gaim_status_type_new_with_attrs(GAIM_STATUS_AVAILABLE, YAHOO_STATUS_TYPE_AVAILABLE,
3224 NULL, TRUE, TRUE, FALSE,
3225 "message", _("Message"),
3226 gaim_value_new(GAIM_TYPE_STRING), NULL);
3227 types = g_list_append(types, type);
3228
3229 type = gaim_status_type_new_with_attrs(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_AWAY,
3230 NULL, TRUE, TRUE, FALSE,
3231 "message", _("Message"),
3232 gaim_value_new(GAIM_TYPE_STRING), NULL);
3233 types = g_list_append(types, type);
3234
3235 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_BRB, _("Be Right Back"), TRUE);
3236 types = g_list_append(types, type);
3237
3238 type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_BUSY, _("Busy"), TRUE);
3239 types = g_list_append(types, type);
3240
3241 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATHOME, _("Not at Home"), TRUE);
3242 types = g_list_append(types, type);
3243
3244 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTATDESK, _("Not at Desk"), TRUE);
3245 types = g_list_append(types, type);
3246
3247 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_NOTINOFFICE, _("Not in Office"), TRUE);
3248 types = g_list_append(types, type);
3249
3250 type = gaim_status_type_new(GAIM_STATUS_UNAVAILABLE, YAHOO_STATUS_TYPE_ONPHONE, _("On the Phone"), TRUE);
3251 types = g_list_append(types, type);
3252
3253 type = gaim_status_type_new(GAIM_STATUS_EXTENDED_AWAY, YAHOO_STATUS_TYPE_ONVACATION, _("On Vacation"), TRUE);
3254 types = g_list_append(types, type);
3255
3256 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_OUTTOLUNCH, _("Out to Lunch"), TRUE);
3257 types = g_list_append(types, type);
3258
3259 type = gaim_status_type_new(GAIM_STATUS_AWAY, YAHOO_STATUS_TYPE_STEPPEDOUT, _("Stepped Out"), TRUE);
3260 types = g_list_append(types, type);
3261
3262
3263 type = gaim_status_type_new(GAIM_STATUS_INVISIBLE, YAHOO_STATUS_TYPE_INVISIBLE, NULL, TRUE);
3264 types = g_list_append(types, type);
3265
3266 type = gaim_status_type_new(GAIM_STATUS_OFFLINE, YAHOO_STATUS_TYPE_OFFLINE, NULL, TRUE);
3267 types = g_list_append(types, type);
3268
3269 return types;
3270 }
3271
3272 static void yahoo_keepalive(GaimConnection *gc)
3273 {
3274 struct yahoo_data *yd = gc->proto_data;
3275 struct yahoo_packet *pkt = yahoo_packet_new(YAHOO_SERVICE_PING, YAHOO_STATUS_AVAILABLE, 0);
3276 yahoo_packet_send_and_free(pkt, yd);
3277
3278 if (!yd->chat_online)
3279 return;
3280
3281 if (yd->wm) {
3282 ycht_chat_send_keepalive(yd->ycht);
3283 return;
3284 }
3285
3286 pkt = yahoo_packet_new(YAHOO_SERVICE_CHATPING, YAHOO_STATUS_AVAILABLE, 0);
3287 yahoo_packet_hash_str(pkt, 109, gaim_connection_get_display_name(gc));
3288 yahoo_packet_send_and_free(pkt, yd);
3289 }
3290
3291 /* XXX - What's the deal with GaimGroup *foo? */
3292 static void yahoo_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *foo)
3293 {
3294 struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
3295 struct yahoo_packet *pkt;
3296 GaimGroup *g;
3297 char *group = NULL;
3298 char *group2 = NULL;
3299
3300 if (!yd->logged_in)
3301 return;
3302
3303 if (!yahoo_privacy_check(gc, gaim_buddy_get_name(buddy)))
3304 return;
3305
3306 if (foo)
3307 group = foo->name;
3308 if (!group) {
3309 g = gaim_buddy_get_group(gaim_find_buddy(gc->account, buddy->name));
3310 if (g)
3311 group = g->name;
3312 else
3313 group = "Buddies";
3314 }
3315
3316 group2 = yahoo_string_encode(gc, group, NULL);
3317 pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
3318 yahoo_packet_hash(pkt, "ssss", 1, gaim_connection_get_display_name(gc),
3319 7, buddy->name, 65, group2, 14, "");
3320 yahoo_packet_send_and_free(pkt, yd);
3321 g_free(group2);
3322 }
3323
3324 static void yahoo_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
3325 {
3326 struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
3327 YahooFriend *f;
3328 struct yahoo_packet *pkt;
3329 GSList *buddies, *l;
3330 GaimGroup *g;
3331 gboolean remove = TRUE;
3332 char *cg;
3333
3334 if (!(f = yahoo_friend_find(gc, buddy->name)))
3335 return;
3336
3337 buddies = gaim_find_buddies(gaim_connection_get_account(gc), buddy->name);
3338 for (l = buddies; l; l = l->next) {
3339 g = gaim_buddy_get_group(l->data);
3340 if (gaim_utf8_strcasecmp(group->name, g->name)) {
3341 remove = FALSE;
3342 break;
3343 }
3344 }
3345
3346 g_slist_free(buddies);
3347
3348 if (remove)
3349 g_hash_table_remove(yd->friends, buddy->name);
3350
3351 cg = yahoo_string_encode(gc, group->name, NULL);
3352 pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
3353 yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
3354 7, buddy->name, 65, cg);
3355 yahoo_packet_send_and_free(pkt, yd);
3356 g_free(cg);
3357 }
3358
3359 static void yahoo_add_deny(GaimConnection *gc, const char *who) {
3360 struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
3361 struct yahoo_packet *pkt;
3362
3363 if (!yd->logged_in)
3364 return;
3365 /* It seems to work better without this */
3366
3367 /* if (gc->account->perm_deny != 4)
3368 return; */
3369
3370 if (!who || who[0] == '\0')
3371 return;
3372
3373 pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, 0);
3374 yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
3375 7, who, 13, "1");
3376 yahoo_packet_send_and_free(pkt, yd);
3377 }
3378
3379 static void yahoo_rem_deny(GaimConnection *gc, const char *who) {
3380 struct yahoo_data *yd = (struct yahoo_data *)gc->proto_data;
3381 struct yahoo_packet *pkt;
3382
3383 if (!yd->logged_in)
3384 return;
3385
3386 if (!who || who[0] == '\0')
3387 return;
3388
3389 pkt = yahoo_packet_new(YAHOO_SERVICE_IGNORECONTACT, YAHOO_STATUS_AVAILABLE, 0);
3390 yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc), 7, who, 13, "2");
3391 yahoo_packet_send_and_free(pkt, yd);
3392 }
3393
3394 static void yahoo_set_permit_deny(GaimConnection *gc) {
3395 GaimAccount *acct;
3396 GSList *deny;
3397
3398 acct = gc->account;
3399
3400 switch (acct->perm_deny) {
3401 /* privacy 1 */
3402 case GAIM_PRIVACY_ALLOW_ALL:
3403 for (deny = acct->deny;deny;deny = deny->next)
3404 yahoo_rem_deny(gc, deny->data);
3405 break;
3406 /* privacy 3 */
3407 case GAIM_PRIVACY_ALLOW_USERS:
3408 for (deny = acct->deny;deny;deny = deny->next)
3409 yahoo_rem_deny(gc, deny->data);
3410 break;
3411 /* privacy 5 */
3412 case GAIM_PRIVACY_ALLOW_BUDDYLIST:
3413 /* privacy 4 */
3414 case GAIM_PRIVACY_DENY_USERS:
3415 for (deny = acct->deny;deny;deny = deny->next)
3416 yahoo_add_deny(gc, deny->data);
3417 break;
3418 /* privacy 2 */
3419 case GAIM_PRIVACY_DENY_ALL:
3420 default:
3421 break;
3422 }
3423 }
3424
3425 static gboolean yahoo_unload_plugin(GaimPlugin *plugin)
3426 {
3427 yahoo_dest_colorht();
3428
3429 return TRUE;
3430 }
3431
3432 static void yahoo_change_buddys_group(GaimConnection *gc, const char *who,
3433 const char *old_group, const char *new_group)
3434 {
3435 struct yahoo_data *yd = gc->proto_data;
3436 struct yahoo_packet *pkt;
3437 char *gpn, *gpo;
3438
3439 /* Step 0: If they aren't on the server list anyway,
3440 * don't bother letting the server know.
3441 */
3442 if (!yahoo_friend_find(gc, who))
3443 return;
3444
3445 /* If old and new are the same, we would probably
3446 * end up deleting the buddy, which would be bad.
3447 * This might happen because of the charset conversation.
3448 */
3449 gpn = yahoo_string_encode(gc, new_group, NULL);
3450 gpo = yahoo_string_encode(gc, old_group, NULL);
3451 if (!strcmp(gpn, gpo)) {
3452 g_free(gpn);
3453 g_free(gpo);
3454 return;
3455 }
3456
3457 /* Step 1: Add buddy to new group. */
3458 pkt = yahoo_packet_new(YAHOO_SERVICE_ADDBUDDY, YAHOO_STATUS_AVAILABLE, 0);
3459 yahoo_packet_hash(pkt, "ssss", 1, gaim_connection_get_display_name(gc),
3460 7, who, 65, gpn, 14, "");
3461 yahoo_packet_send_and_free(pkt, yd);
3462
3463 /* Step 2: Remove buddy from old group */
3464 pkt = yahoo_packet_new(YAHOO_SERVICE_REMBUDDY, YAHOO_STATUS_AVAILABLE, 0);
3465 yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc), 7, who, 65, gpo);
3466 yahoo_packet_send_and_free(pkt, yd);
3467 g_free(gpn);
3468 g_free(gpo);
3469 }
3470
3471 static void yahoo_rename_group(GaimConnection *gc, const char *old_name,
3472 GaimGroup *group, GList *moved_buddies)
3473 {
3474 struct yahoo_data *yd = gc->proto_data;
3475 struct yahoo_packet *pkt;
3476 char *gpn, *gpo;
3477
3478 gpn = yahoo_string_encode(gc, group->name, NULL);
3479 gpo = yahoo_string_encode(gc, old_name, NULL);
3480 if (!strcmp(gpn, gpo)) {
3481 g_free(gpn);
3482 g_free(gpo);
3483 return;
3484 }
3485
3486 pkt = yahoo_packet_new(YAHOO_SERVICE_GROUPRENAME, YAHOO_STATUS_AVAILABLE, 0);
3487 yahoo_packet_hash(pkt, "sss", 1, gaim_connection_get_display_name(gc),
3488 65, gpo, 67, gpn);
3489 yahoo_packet_send_and_free(pkt, yd);
3490 g_free(gpn);
3491 g_free(gpo);
3492 }
3493
3494 /********************************* Commands **********************************/
3495
3496 static GaimCmdRet
3497 yahoogaim_cmd_buzz(GaimConversation *c, const gchar *cmd, gchar **args, gchar **error, void *data) {
3498
3499 GaimAccount *account = gaim_conversation_get_account(c);
3500 const char *username = gaim_account_get_username(account);
3501
3502 if (*args && args[0])
3503 return GAIM_CMD_RET_FAILED;
3504
3505 gaim_debug(GAIM_DEBUG_INFO, "yahoo",
3506 "Sending <ding> on account %s to buddy %s.\n", username, c->name);
3507 gaim_conv_im_send(GAIM_CONV_IM(c), "<ding>");
3508 gaim_conv_im_write(GAIM_CONV_IM(c), "", _("Buzz!!"), GAIM_MESSAGE_NICK|GAIM_MESSAGE_SEND, time(NULL));
3509 return GAIM_CMD_RET_OK;
3510 }
3511
3512 static GaimPlugin *my_protocol = NULL;
3513
3514 static GaimCmdRet
3515 yahoogaim_cmd_chat_join(GaimConversation *conv, const char *cmd,
3516 char **args, char **error, void *data)
3517 {
3518 GHashTable *comp;
3519 GaimConnection *gc;
3520 struct yahoo_data *yd;
3521 int id;
3522
3523 if (!args || !args[0])
3524 return GAIM_CMD_RET_FAILED;
3525
3526 gc = gaim_conversation_get_gc(conv);
3527 yd = gc->proto_data;
3528 id = yd->conf_id;
3529 gaim_debug(GAIM_DEBUG_INFO, "yahoo",
3530 "Trying to join %s \n", args[0]);
3531
3532 comp = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3533 g_hash_table_replace(comp, g_strdup("room"),
3534 g_strdup_printf("%s", g_ascii_strdown(args[0], strlen(args[0]))));
3535 g_hash_table_replace(comp, g_strdup("type"), g_strdup("Chat"));
3536
3537 yahoo_c_join(gc, comp);
3538
3539 g_hash_table_destroy(comp);
3540 return GAIM_CMD_RET_OK;
3541 }
3542
3543 static GaimCmdRet
3544 yahoogaim_cmd_chat_list(GaimConversation *conv, const char *cmd,
3545 char **args, char **error, void *data)
3546 {
3547 GaimAccount *account = gaim_conversation_get_account(conv);
3548 if (*args && args[0])
3549 return GAIM_CMD_RET_FAILED;
3550 gaim_roomlist_show_with_account(account);
3551 return GAIM_CMD_RET_OK;
3552 }
3553
3554 static gboolean yahoo_offline_message(const GaimBuddy *buddy)
3555 {
3556 return TRUE;
3557 }
3558
3559 /************************** Plugin Initialization ****************************/
3560 static void
3561 yahoogaim_register_commands(void)
3562 {
3563 gaim_cmd_register("join", "s", GAIM_CMD_P_PRPL,
3564 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
3565 GAIM_CMD_FLAG_PRPL_ONLY,
3566 "prpl-yahoo", yahoogaim_cmd_chat_join,
3567 _("join &lt;room&gt;: Join a chat room on the Yahoo network"), NULL);
3568 gaim_cmd_register("list", "", GAIM_CMD_P_PRPL,
3569 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_CHAT |
3570 GAIM_CMD_FLAG_PRPL_ONLY,
3571 "prpl-yahoo", yahoogaim_cmd_chat_list,
3572 _("list: List rooms on the Yahoo network"), NULL);
3573 gaim_cmd_register("buzz", "", GAIM_CMD_P_PRPL,
3574 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY,
3575 "prpl-yahoo", yahoogaim_cmd_buzz,
3576 _("buzz: Buzz a user to get their attention"), NULL);
3577 gaim_cmd_register("doodle", "", GAIM_CMD_P_PRPL,
3578 GAIM_CMD_FLAG_IM | GAIM_CMD_FLAG_PRPL_ONLY,
3579 "prpl-yahoo", yahoo_doodle_gaim_cmd_start,
3580 _("doodle: Request user to start a Doodle session"), NULL);
3581 }
3582
3583 static GaimWhiteboardPrplOps yahoo_whiteboard_prpl_ops =
3584 {
3585 yahoo_doodle_start,
3586 yahoo_doodle_end,
3587 yahoo_doodle_get_dimensions,
3588 NULL,
3589 yahoo_doodle_get_brush,
3590 yahoo_doodle_set_brush,
3591 yahoo_doodle_send_draw_list,
3592 yahoo_doodle_clear
3593 };
3594
3595 static GaimPluginProtocolInfo prpl_info =
3596 {
3597 OPT_PROTO_MAIL_CHECK | OPT_PROTO_CHAT_TOPIC,
3598 NULL, /* user_splits */
3599 NULL, /* protocol_options */
3600 {"png", 96, 96, 96, 96, GAIM_ICON_SCALE_SEND},
3601 yahoo_list_icon,
3602 yahoo_list_emblems,
3603 yahoo_status_text,
3604 yahoo_tooltip_text,
3605 yahoo_status_types,
3606 yahoo_blist_node_menu,
3607 yahoo_c_info,
3608 yahoo_c_info_defaults,
3609 yahoo_login,
3610 yahoo_close,
3611 yahoo_send_im,
3612 NULL, /* set info */
3613 yahoo_send_typing,
3614 yahoo_get_info,
3615 yahoo_set_status,
3616 yahoo_set_idle,
3617 NULL, /* change_passwd*/
3618 yahoo_add_buddy,
3619 NULL, /* add_buddies */
3620 yahoo_remove_buddy,
3621 NULL, /*remove_buddies */
3622 yahoo_add_permit,
3623 yahoo_add_deny,
3624 yahoo_rem_permit,
3625 yahoo_rem_deny,
3626 yahoo_set_permit_deny,
3627 yahoo_c_join,
3628 NULL, /* reject chat invite */
3629 yahoo_get_chat_name,
3630 yahoo_c_invite,
3631 yahoo_c_leave,
3632 NULL, /* chat whisper */
3633 yahoo_c_send,
3634 yahoo_keepalive,
3635 NULL, /* register_user */
3636 NULL, /* get_cb_info */
3637 NULL, /* get_cb_away */
3638 NULL, /* alias_buddy */
3639 yahoo_change_buddys_group,
3640 yahoo_rename_group,
3641 NULL, /* buddy_free */
3642 NULL, /* convo_closed */
3643 gaim_normalize_nocase, /* normalize */
3644 yahoo_set_buddy_icon,
3645 NULL, /* void (*remove_group)(GaimConnection *gc, const char *group);*/
3646 NULL, /* char *(*get_cb_real_name)(GaimConnection *gc, int id, const char *who); */
3647 NULL, /* set_chat_topic */
3648 NULL, /* find_blist_chat */
3649 yahoo_roomlist_get_list,
3650 yahoo_roomlist_cancel,
3651 yahoo_roomlist_expand_category,
3652 NULL, /* can_receive_file */
3653 yahoo_send_file,
3654 yahoo_new_xfer,
3655 yahoo_offline_message, /* offline_message */
3656 &yahoo_whiteboard_prpl_ops,
3657 NULL, /* media_prpl_ops */
3658 };
3659
3660 static GaimPluginInfo info =
3661 {
3662 GAIM_PLUGIN_MAGIC,
3663 GAIM_MAJOR_VERSION,
3664 GAIM_MINOR_VERSION,
3665 GAIM_PLUGIN_PROTOCOL, /**< type */
3666 NULL, /**< ui_requirement */
3667 0, /**< flags */
3668 NULL, /**< dependencies */
3669 GAIM_PRIORITY_DEFAULT, /**< priority */
3670 "prpl-yahoo", /**< id */
3671 "Yahoo", /**< name */
3672 VERSION, /**< version */
3673 /** summary */
3674 N_("Yahoo Protocol Plugin"),
3675 /** description */
3676 N_("Yahoo Protocol Plugin"),
3677 NULL, /**< author */
3678 GAIM_WEBSITE, /**< homepage */
3679 NULL, /**< load */
3680 yahoo_unload_plugin, /**< unload */
3681 NULL, /**< destroy */
3682 NULL, /**< ui_info */
3683 &prpl_info, /**< extra_info */
3684 NULL,
3685 yahoo_actions
3686 };
3687
3688 static void
3689 init_plugin(GaimPlugin *plugin)
3690 {
3691 GaimAccountOption *option;
3692
3693 option = gaim_account_option_bool_new(_("Yahoo Japan"), "yahoojp", FALSE);
3694 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3695
3696 option = gaim_account_option_string_new(_("Pager host"), "server", YAHOO_PAGER_HOST);
3697 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3698
3699 option = gaim_account_option_string_new(_("Japan Pager host"), "serverjp", YAHOOJP_PAGER_HOST);
3700 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3701
3702 option = gaim_account_option_int_new(_("Pager port"), "port", YAHOO_PAGER_PORT);
3703 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3704
3705 option = gaim_account_option_string_new(_("File transfer host"), "xfer_host", YAHOO_XFER_HOST);
3706 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3707
3708 option = gaim_account_option_string_new(_("Japan file transfer host"), "xferjp_host", YAHOOJP_XFER_HOST);
3709 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3710
3711 option = gaim_account_option_int_new(_("File transfer port"), "xfer_port", YAHOO_XFER_PORT);
3712 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3713
3714 option = gaim_account_option_string_new(_("Chat room locale"), "room_list_locale", YAHOO_ROOMLIST_LOCALE);
3715 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3716
3717 option = gaim_account_option_bool_new(_("Ignore conference and chatroom invitations"), "ignore_invites", FALSE);
3718 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3719
3720 #if 0
3721 option = gaim_account_option_string_new(_("Chat room list URL"), "room_list", YAHOO_ROOMLIST_URL);
3722 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3723
3724 option = gaim_account_option_string_new(_("YCHT host"), "ycht-server", YAHOO_YCHT_HOST);
3725 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3726
3727 option = gaim_account_option_int_new(_("YCHT port"), "ycht-port", YAHOO_YCHT_PORT);
3728 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
3729 #endif
3730
3731 my_protocol = plugin;
3732 yahoogaim_register_commands();
3733 yahoo_init_colorht();
3734 }
3735
3736 GAIM_INIT_PLUGIN(yahoo, init_plugin, info);

mercurial