summaryrefslogtreecommitdiff
path: root/libpurple/protocols/qq/buddy_opt.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/protocols/qq/buddy_opt.c')
-rw-r--r--libpurple/protocols/qq/buddy_opt.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/libpurple/protocols/qq/buddy_opt.c b/libpurple/protocols/qq/buddy_opt.c
new file mode 100644
index 0000000000..667d5c9e96
--- /dev/null
+++ b/libpurple/protocols/qq/buddy_opt.c
@@ -0,0 +1,580 @@
+/**
+ * @file buddy_opt.c
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include "debug.h"
+#include "internal.h"
+#include "notify.h"
+#include "request.h"
+
+#include "buddy_info.h"
+#include "buddy_list.h"
+#include "buddy_opt.h"
+#include "char_conv.h"
+#include "crypt.h"
+#include "header_info.h"
+#include "im.h"
+#include "keep_alive.h"
+#include "packet_parse.h"
+#include "send_core.h"
+#include "utils.h"
+
+#define GAIM_GROUP_QQ_FORMAT "QQ (%s)"
+#define GAIM_GROUP_QQ_UNKNOWN "QQ Unknown"
+#define GAIM_GROUP_QQ_BLOCKED "QQ Blocked"
+
+#define QQ_REMOVE_BUDDY_REPLY_OK 0x00
+#define QQ_REMOVE_SELF_REPLY_OK 0x00
+#define QQ_ADD_BUDDY_AUTH_REPLY_OK 0x30 /* ASCII value of "0" */
+
+enum {
+ QQ_MY_AUTH_APPROVE = 0x30, /* ASCII value of "0" */
+ QQ_MY_AUTH_REJECT = 0x31, /* ASCII value of "1" */
+ QQ_MY_AUTH_REQUEST = 0x32, /* ASCII value of "2" */
+};
+
+typedef struct _qq_add_buddy_request {
+ guint32 uid;
+ guint16 seq;
+} qq_add_buddy_request;
+
+/* send packet to remove a buddy from my buddy list */
+static void _qq_send_packet_remove_buddy(GaimConnection *gc, guint32 uid)
+{
+ gchar uid_str[11];
+
+ g_return_if_fail(uid > 0);
+
+ g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+ qq_send_cmd(gc, QQ_CMD_DEL_FRIEND, TRUE, 0,
+ TRUE, (guint8 *) uid_str, strlen(uid_str));
+}
+
+/* try to remove myself from someone's buddy list */
+static void _qq_send_packet_remove_self_from(GaimConnection *gc, guint32 uid)
+{
+ guint8 *raw_data, *cursor;
+
+ g_return_if_fail(uid > 0);
+
+ raw_data = g_newa(guint8, 4);
+ cursor = raw_data;
+ create_packet_dw(raw_data, &cursor, uid);
+
+ qq_send_cmd(gc, QQ_CMD_REMOVE_SELF, TRUE, 0, TRUE, raw_data, 4);
+}
+
+/* try to add a buddy without authentication */
+static void _qq_send_packet_add_buddy(GaimConnection *gc, guint32 uid)
+{
+ qq_data *qd;
+ qq_add_buddy_request *req;
+ gchar uid_str[11];
+
+ g_return_if_fail(uid > 0);
+
+ /* we need to send the ascii code of this uid to qq server */
+ g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+ qq_send_cmd(gc, QQ_CMD_ADD_FRIEND_WO_AUTH, TRUE, 0,
+ TRUE, (guint8 *) uid_str, strlen(uid_str));
+
+ /* must be set after sending packet to get the correct send_seq */
+ qd = (qq_data *) gc->proto_data;
+ req = g_new0(qq_add_buddy_request, 1);
+ req->seq = qd->send_seq;
+ req->uid = uid;
+ qd->add_buddy_request = g_list_append(qd->add_buddy_request, req);
+}
+
+/* this buddy needs authentication, text conversion is done at lowest level */
+static void _qq_send_packet_buddy_auth(GaimConnection *gc, guint32 uid, const gchar response, const gchar *text)
+{
+ gchar *text_qq, uid_str[11];
+ guint8 bar, *cursor, *raw_data;
+
+ g_return_if_fail(uid != 0);
+
+ g_snprintf(uid_str, sizeof(uid_str), "%d", uid);
+ bar = 0x1f;
+ raw_data = g_newa(guint8, QQ_MSG_IM_MAX);
+ cursor = raw_data;
+
+ create_packet_data(raw_data, &cursor, (guint8 *) uid_str, strlen(uid_str));
+ create_packet_b(raw_data, &cursor, bar);
+ create_packet_b(raw_data, &cursor, response);
+
+ if (text != NULL) {
+ text_qq = utf8_to_qq(text, QQ_CHARSET_DEFAULT);
+ create_packet_b(raw_data, &cursor, bar);
+ create_packet_data(raw_data, &cursor, (guint8 *) text_qq, strlen(text_qq));
+ g_free(text_qq);
+ }
+
+ qq_send_cmd(gc, QQ_CMD_BUDDY_AUTH, TRUE, 0, TRUE, raw_data, cursor - raw_data);
+}
+
+static void _qq_send_packet_add_buddy_auth_with_gc_and_uid(gc_and_uid *g, const gchar *text)
+{
+ GaimConnection *gc;
+ guint32 uid;
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid != 0);
+
+ _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REQUEST, text);
+ g_free(g);
+}
+
+/* the real packet to reject and request is sent from here */
+static void _qq_reject_add_request_real(gc_and_uid *g, const gchar *reason)
+{
+ gint uid;
+ GaimConnection *gc;
+
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid != 0);
+
+ _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_REJECT, reason);
+ g_free(g);
+}
+
+/* we approve other's request of adding me as friend */
+void qq_approve_add_request_with_gc_and_uid(gc_and_uid *g)
+{
+ gint uid;
+ GaimConnection *gc;
+
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid != 0);
+
+ _qq_send_packet_buddy_auth(gc, uid, QQ_MY_AUTH_APPROVE, NULL);
+ g_free(g);
+}
+
+void qq_do_nothing_with_gc_and_uid(gc_and_uid *g, const gchar *msg)
+{
+ g_free(g);
+}
+
+/* we reject other's request of adding me as friend */
+void qq_reject_add_request_with_gc_and_uid(gc_and_uid *g)
+{
+ gint uid;
+ gchar *msg1, *msg2;
+ GaimConnection *gc;
+ gc_and_uid *g2;
+
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid != 0);
+
+ g_free(g);
+
+ g2 = g_new0(gc_and_uid, 1);
+ g2->gc = gc;
+ g2->uid = uid;
+
+ msg1 = g_strdup_printf(_("You rejected %d's request"), uid);
+ msg2 = g_strdup(_("Input your reason:"));
+
+ gaim_request_input(gc, _("Reject request"), msg1, msg2,
+ _("Sorry, you are not my type..."), TRUE, FALSE,
+ NULL, _("Reject"), G_CALLBACK(_qq_reject_add_request_real), _("Cancel"), NULL, g2);
+}
+
+void qq_add_buddy_with_gc_and_uid(gc_and_uid *g)
+{
+ gint uid;
+ GaimConnection *gc;
+
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid != 0);
+
+ _qq_send_packet_add_buddy(gc, uid);
+ g_free(g);
+}
+
+void qq_block_buddy_with_gc_and_uid(gc_and_uid *g)
+{
+ guint32 uid;
+ GaimConnection *gc;
+ GaimBuddy buddy;
+ GaimGroup group;
+
+ g_return_if_fail(g != NULL);
+
+ gc = g->gc;
+ uid = g->uid;
+ g_return_if_fail(uid > 0);
+
+ buddy.name = uid_to_gaim_name(uid);
+ group.name = GAIM_GROUP_QQ_BLOCKED;
+
+ qq_remove_buddy(gc, &buddy, &group);
+ _qq_send_packet_remove_self_from(gc, uid);
+}
+
+/* process reply to add_buddy_auth request */
+void qq_process_add_buddy_auth_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+ qq_data *qd;
+ gint len;
+ guint8 *data, *cursor, reply;
+ gchar **segments, *msg_utf8;
+
+ g_return_if_fail(buf != NULL && buf_len != 0);
+
+ qd = (qq_data *) gc->proto_data;
+ len = buf_len;
+ data = g_newa(guint8, len);
+ cursor = data;
+
+ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+ read_packet_b(data, &cursor, len, &reply);
+ if (reply != QQ_ADD_BUDDY_AUTH_REPLY_OK) {
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy with auth request fails\n");
+ if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+ return;
+ msg_utf8 = qq_to_utf8(segments[1], QQ_CHARSET_DEFAULT);
+ gaim_notify_error(gc, NULL, _("Add buddy with auth request fails"), msg_utf8);
+ g_free(msg_utf8);
+ } else {
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy with auth request OK\n");
+ }
+ } else {
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy with auth reply\n");
+ }
+}
+
+/* process the server reply for my request to remove a buddy */
+void qq_process_remove_buddy_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+ qq_data *qd;
+ gint len;
+ guint8 *data, *cursor, reply;
+
+ g_return_if_fail(buf != NULL && buf_len != 0);
+
+ qd = (qq_data *) gc->proto_data;
+ len = buf_len;
+ data = g_newa(guint8, len);
+
+ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+ cursor = data;
+ read_packet_b(data, &cursor, len, &reply);
+ if (reply != QQ_REMOVE_BUDDY_REPLY_OK) {
+ /* there is no reason return from server */
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove buddy fails\n");
+ } else { /* if reply */
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove buddy OK\n");
+ gaim_notify_info(gc, NULL, _("You have successfully removed a buddy"), NULL);
+ }
+ } else {
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove buddy reply\n");
+ }
+}
+
+/* process the server reply for my request to remove myself from a buddy */
+void qq_process_remove_self_reply(guint8 *buf, gint buf_len, GaimConnection *gc)
+{
+ qq_data *qd;
+ gint len;
+ guint8 *data, *cursor, reply;
+
+ g_return_if_fail(buf != NULL && buf_len != 0);
+
+ qd = (qq_data *) gc->proto_data;
+ len = buf_len;
+ data = g_newa(guint8, len);
+
+ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+ cursor = data;
+ read_packet_b(data, &cursor, len, &reply);
+ if (reply != QQ_REMOVE_SELF_REPLY_OK)
+ /* there is no reason return from server */
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Remove self fails\n");
+ else { /* if reply */
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "Remove self from a buddy OK\n");
+ gaim_notify_info(gc, NULL, _("You have successfully removed yourself from a buddy"), NULL);
+ }
+ } else {
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt remove self reply\n");
+ }
+}
+
+void qq_process_add_buddy_reply(guint8 *buf, gint buf_len, guint16 seq, GaimConnection *gc)
+{
+ qq_data *qd;
+ gint len, for_uid;
+ gchar *msg, **segments, *uid, *reply;
+ guint8 *data;
+ GList *list;
+ GaimBuddy *b;
+ gc_and_uid *g;
+ qq_add_buddy_request *req;
+
+ g_return_if_fail(buf != NULL && buf_len != 0);
+
+ for_uid = 0;
+ qd = (qq_data *) gc->proto_data;
+ len = buf_len;
+
+ list = qd->add_buddy_request;
+ while (list != NULL) {
+ req = (qq_add_buddy_request *) list->data;
+ if (req->seq == seq) { /* reply to this */
+ for_uid = req->uid;
+ qd->add_buddy_request = g_list_remove(qd->add_buddy_request, qd->add_buddy_request->data);
+ g_free(req);
+ break;
+ }
+ list = list->next;
+ }
+
+ if (for_uid == 0) { /* we have no record for this */
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "We have no record for add buddy reply [%d], discard\n", seq);
+ return;
+ } else {
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "Add buddy reply [%d] is for id [%d]\n", seq, for_uid);
+ }
+
+ data = g_newa(guint8, len);
+
+ if (qq_crypt(DECRYPT, buf, buf_len, qd->session_key, data, &len)) {
+ if (NULL == (segments = split_data(data, len, "\x1f", 2)))
+ return;
+ uid = segments[0];
+ reply = segments[1];
+ if (strtol(uid, NULL, 10) != qd->uid) { /* should not happen */
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Add buddy reply is to [%s], not me!", uid);
+ g_strfreev(segments);
+ return;
+ }
+
+ if (strtol(reply, NULL, 10) > 0) { /* need auth */
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add buddy attempt fails, need authentication\n");
+ b = gaim_find_buddy(gc->account, uid_to_gaim_name(for_uid));
+ if (b != NULL)
+ gaim_blist_remove_buddy(b);
+ g = g_new0(gc_and_uid, 1);
+ g->gc = gc;
+ g->uid = for_uid;
+ msg = g_strdup_printf(_("User %d needs authentication"), for_uid);
+ gaim_request_input(gc, NULL, msg,
+ _("Input request here"),
+ _("Would you be my friend?"),
+ TRUE, FALSE, NULL, _("Send"),
+ G_CALLBACK
+ (_qq_send_packet_add_buddy_auth_with_gc_and_uid),
+ _("Cancel"), G_CALLBACK(qq_do_nothing_with_gc_and_uid), g);
+ g_free(msg);
+ } else { /* add OK */
+ qq_add_buddy_by_recv_packet(gc, for_uid, TRUE, TRUE);
+ msg = g_strdup_printf(_("You have added %d in buddy list"), for_uid);
+ gaim_notify_info(gc, NULL, msg, NULL);
+ g_free(msg);
+ }
+ g_strfreev(segments);
+ } else {
+ gaim_debug(GAIM_DEBUG_ERROR, "QQ", "Error decrypt add buddy reply\n");
+ }
+}
+
+GaimGroup *qq_get_gaim_group(const gchar *group_name)
+{
+ GaimGroup *g;
+
+ g_return_val_if_fail(group_name != NULL, NULL);
+
+ g = gaim_find_group(group_name);
+ if (g == NULL) {
+ g = gaim_group_new(group_name);
+ gaim_blist_add_group(g, NULL);
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new group: %s\n", group_name);
+ }
+
+ return g;
+}
+
+/* we add new buddy, if the received packet is from someone not in my list
+ * return the GaimBuddy that is just created */
+GaimBuddy *qq_add_buddy_by_recv_packet(GaimConnection *gc, guint32 uid, gboolean is_known, gboolean create)
+{
+ GaimAccount *a;
+ GaimBuddy *b;
+ GaimGroup *g;
+ qq_data *qd;
+ qq_buddy *q_bud;
+ gchar *name, *group_name;
+
+ a = gc->account;
+ qd = (qq_data *) gc->proto_data;
+ g_return_val_if_fail(a != NULL && uid != 0, NULL);
+
+ group_name = is_known ?
+ g_strdup_printf(GAIM_GROUP_QQ_FORMAT, gaim_account_get_username(a)) : g_strdup(GAIM_GROUP_QQ_UNKNOWN);
+
+ g = qq_get_gaim_group(group_name);
+
+ name = uid_to_gaim_name(uid);
+ b = gaim_find_buddy(gc->account, name);
+ /* remove old, we can not simply return here
+ * because there might be old local copy of this buddy */
+ if (b != NULL)
+ gaim_blist_remove_buddy(b);
+
+ b = gaim_buddy_new(a, name, NULL);
+
+ if (!create)
+ b->proto_data = NULL;
+ else {
+ q_bud = g_new0(qq_buddy, 1);
+ q_bud->uid = uid;
+ b->proto_data = q_bud;
+ qd->buddies = g_list_append(qd->buddies, q_bud);
+ qq_send_packet_get_info(gc, q_bud->uid, FALSE);
+ qq_send_packet_get_buddies_online(gc, QQ_FRIENDS_ONLINE_POSITION_START);
+ }
+
+ gaim_blist_add_buddy(b, NULL, g, NULL);
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "Add new buddy: [%s]\n", name);
+
+ g_free(name);
+ g_free(group_name);
+
+ return b;
+}
+
+/* add a buddy and send packet to QQ server
+ * note that when gaim load local cached buddy list into its blist
+ * it also calls this funtion, so we have to
+ * define qd->logged_in=TRUE AFTER serv_finish_login(gc) */
+void qq_add_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+ qq_data *qd;
+ guint32 uid;
+ GaimBuddy *b;
+
+ qd = (qq_data *) gc->proto_data;
+ if (!qd->logged_in)
+ return; /* IMPORTANT ! */
+
+ uid = gaim_name_to_uid(buddy->name);
+ if (uid > 0)
+ _qq_send_packet_add_buddy(gc, uid);
+ else {
+ b = gaim_find_buddy(gc->account, buddy->name);
+ if (b != NULL)
+ gaim_blist_remove_buddy(b);
+ gaim_notify_error(gc, NULL,
+ _("QQid Error"),
+ _("Invalid QQid"));
+ }
+}
+
+/* remove a buddy and send packet to QQ server accordingly */
+void qq_remove_buddy(GaimConnection *gc, GaimBuddy *buddy, GaimGroup *group)
+{
+ qq_data *qd;
+ GaimBuddy *b;
+ qq_buddy *q_bud;
+ guint32 uid;
+
+ qd = (qq_data *) gc->proto_data;
+ uid = gaim_name_to_uid(buddy->name);
+
+ if (!qd->logged_in)
+ return;
+
+ if (uid > 0)
+ _qq_send_packet_remove_buddy(gc, uid);
+
+ b = gaim_find_buddy(gc->account, buddy->name);
+ if (b != NULL) {
+ q_bud = (qq_buddy *) b->proto_data;
+ if (q_bud != NULL)
+ qd->buddies = g_list_remove(qd->buddies, q_bud);
+ else
+ gaim_debug(GAIM_DEBUG_WARNING, "QQ", "We have no qq_buddy record for %s\n", buddy->name);
+ /* remove buddy on blist, this does not trigger qq_remove_buddy again
+ * do this only if the request comes from block request,
+ * otherwise gaim segmentation fault */
+ if (g_ascii_strcasecmp(group->name, GAIM_GROUP_QQ_BLOCKED) == 0)
+ gaim_blist_remove_buddy(b);
+ }
+}
+
+/* free add buddy request queue */
+void qq_add_buddy_request_free(qq_data *qd)
+{
+ gint i;
+ qq_add_buddy_request *p;
+
+ i = 0;
+ while (qd->add_buddy_request) {
+ p = (qq_add_buddy_request *) (qd->add_buddy_request->data);
+ qd->add_buddy_request = g_list_remove(qd->add_buddy_request, p);
+ g_free(p);
+ i++;
+ }
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d add buddy requests are freed!\n", i);
+}
+
+/* free up all qq_buddy */
+void qq_buddies_list_free(GaimAccount *account, qq_data *qd)
+{
+ gint i;
+ qq_buddy *p;
+ gchar *name;
+ GaimBuddy *b;
+
+ i = 0;
+ while (qd->buddies) {
+ p = (qq_buddy *) (qd->buddies->data);
+ qd->buddies = g_list_remove(qd->buddies, p);
+ name = uid_to_gaim_name(p->uid);
+ b = gaim_find_buddy(account, name);
+ if(b != NULL)
+ b->proto_data = NULL;
+ else
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "qq_buddy %s not found in gaim proto_data\n", name);
+ g_free(name);
+
+ g_free(p);
+ i++;
+ }
+ gaim_debug(GAIM_DEBUG_INFO, "QQ", "%d qq_buddy structures are freed!\n", i);
+}