commit 8f69d8d11d74426e3c4caecce3ed236c642ff389 Author: Vysheng Date: Tue Nov 11 20:21:14 2014 +0300 Slightly working version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..204cd2b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.o +*.so +tg/ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c66802c --- /dev/null +++ b/Makefile @@ -0,0 +1,120 @@ +# +# Telegram Flags +# + +srcdir=. +CFLAGS=-g -Wall -Wextra -Werror -Wno-unused-parameter +LDFLAGS=-L/usr/local/lib +CPPFLAGS=-I/usr/local/include -Itg +DEFS= +COMPILE_FLAGS=${CFLAGS} ${CPPFLAGS} ${DEFS} -Wall -Wextra -Wno-deprecated-declarations -fno-strict-aliasing -fno-omit-frame-pointer -ggdb +EXTRA_LIBS=-lcrypto -lz -lm +LOCAL_LDFLAGS=-rdynamic -ggdb ${EXTRA_LIBS} +LINK_FLAGS=${LDFLAGS} ${LOCAL_LDFLAGS} + +INCLUDE=-I. -I${srcdir} +CC=cc +OBJECTS=tgp-net.o tgp-timers.o msglog.o telegram-base.o + +.SUFFIXES: + +.SUFFIXES: .c .h .o + + +# +# Plugin Flags +# + +LIBS_PURPLE = $(shell pkg-config --libs purple) +CFLAGS_PURPLE = $(shell pkg-config --cflags purple) +PLUGIN_DIR_PURPLE:=$(shell pkg-config --variable=plugindir purple) +DATA_ROOT_DIR_PURPLE:=$(shell pkg-config --variable=datarootdir purple) + +ARCH = "" +ifeq ($(ARCH),i686) + ARCHFLAGS = -m32 +else ifeq ($(ARCH),x86_64) + ARCHFLAGS = -m64 +else + ARCHFLAGS = +endif + +LD = $(CC) +PRPL_C_SRCS = telegram-purple.c +PRPL_C_OBJS = $(PRPL_C_SRCS:.c=.o) +PRPL_LIBNAME = telegram-purple.so +PRPL_INCLUDE = -I. -I./purple-plugin +PRPL_LDFLAGS = $(ARCHFLAGS) -shared +STRIP = strip +PRPL_CFLAGS = \ + $(ARCHFLAGS) \ + -fPIC \ + -DPURPLE_PLUGINS \ + -DPIC \ + -DDEBUG \ + -g \ + $(CFLAGS_PURPLE) + +# +# Telegram Objects +# + +.c.o : + ${CC} -fPIC -DPIC ${CFLAGS_PURPLE} ${COMPILE_FLAGS} ${INCLUDE} -c $< -o $@ + +# ${OBJECTS}: ${HEADERS} + +#telegram: ${OBJECTS} +# ${CC} ${OBJECTS} ${LINK_FLAGS} -o $@ + +# +# Plugin Objects +# + +$(PRPL_C_OBJS): $(PRPL_C_SRCS) + $(CC) -c $(PRPL_INCLUDE) $(PRPL_CFLAGS) $(CFLAGS) $(CPPFLAGS) -ggdb -o $@ $< + +$(PRPL_LIBNAME): $(OBJECTS) $(PRPL_C_OBJS) tg/libs/libtgl.a + $(LD) $(PRPL_LDFLAGS) $(LDFLAGS) $(PRPL_INCLUDE) $(LIBS_PURPLE) $(EXTRA_LIBS) -o $@ $^ + +.PHONY: all +all: ${PRPL_LIBNAME} + +plugin: $(PRPL_LIBNAME) + + +.PHONY: strip +strip: $(PRPL_LIBNAME) + $(STRIP) --strip-unneeded $(PRPL_LIBNAME) + +# TODO: Find a better place for server.pub +install: $(PRPL_LIBNAME) + install -D $(PRPL_LIBNAME) $(DESTDIR)$(PLUGIN_DIR_PURPLE)/$(PRPL_LIBNAME) + install -D tg-server.pub /etc/telegram-purple/server.pub + install -D imgs/telegram16.png $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/16/telegram.png + install -D imgs/telegram22.png $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/22/telegram.png + install -D imgs/telegram48.png $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/48/telegram.png + +.PHONY: uninstall +uninstall: + rm -f $(DESTDIR)$(PLUGIN_DIR_PURPLE)/$(PRPL_LIBNAME) + rm -f /etc/telegram-purple/server.pub + rm -f $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/16/telegram.png + rm -f $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/22/telegram.png + rm -f $(DESTDIR)$(DATA_ROOT_DIR_PURPLE)/pixmaps/pidgin/protocols/48/telegram.png + +.PHONY: run +run: install + pidgin -d | grep 'telegram\|plugin\|proxy' + +.PHONY: purge +purge: uninstall + rm -rf $(HOME)/.telegram-purple + +.PHONY: debug +debug: install + ddd pidgin + +clean: + rm -rf *.so *.a *.o telegram config.log config.status $(PRPL_C_OBJS) $(PRPL_LIBNAME) > /dev/null || echo "all clean" + diff --git a/imgs/telegram.png b/imgs/telegram.png new file mode 100644 index 0000000..b4ae1e5 Binary files /dev/null and b/imgs/telegram.png differ diff --git a/imgs/telegram16.png b/imgs/telegram16.png new file mode 100644 index 0000000..91de69d Binary files /dev/null and b/imgs/telegram16.png differ diff --git a/imgs/telegram22.png b/imgs/telegram22.png new file mode 100644 index 0000000..709f356 Binary files /dev/null and b/imgs/telegram22.png differ diff --git a/imgs/telegram48.png b/imgs/telegram48.png new file mode 100644 index 0000000..3d7263f Binary files /dev/null and b/imgs/telegram48.png differ diff --git a/msglog.c b/msglog.c new file mode 100644 index 0000000..0518025 --- /dev/null +++ b/msglog.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include "telegram-purple.h" + +#ifdef DEBUG +#define COLOR_GREY "\033[37;1m" +#define COLOR_YELLOW "\033[33;1m" +#define COLOR_RED "\033[0;31m" +#define COLOR_REDB "\033[1;31m" +#define COLOR_GREEN "\033[32;1m" +#define COLOR_NORMAL "\033[0m" +#else +#define COLOR_GREY "" +#define COLOR_YELLOW "" +#define COLOR_RED "" +#define COLOR_REDB "" +#define COLOR_GREEN "" +#define COLOR_NORMAL "" +#endif + +void hexdump (int *in_ptr, int *in_end) { + // TODO: figure out how to log hexdumps to purple log + int *ptr = in_ptr; + while (ptr < in_end) { + ++ ptr; + //printf (" %08x", *(ptr ++)); + } + //printf ("\n"); +} + +void log_level_printf (const char* format, va_list ap, int level, char *color) +{ + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), format, ap); + purple_debug(level, PLUGIN_ID, "%s%s%s ", color, buffer, COLOR_NORMAL); +} + +void debug(const char* format, ...) +{ + va_list ap; + va_start (ap, format); + log_level_printf (format, ap, PURPLE_DEBUG_MISC, COLOR_NORMAL); + va_end (ap); +} + +void info(const char* format, ...) +{ + va_list ap; + va_start (ap, format); + log_level_printf (format, ap, PURPLE_DEBUG_INFO, COLOR_GREEN); + va_end (ap); +} + +void warning(const char* format, ...) +{ + va_list ap; + va_start (ap, format); + log_level_printf (format, ap, PURPLE_DEBUG_WARNING, COLOR_YELLOW); + va_end (ap); +} + +void failure(const char* format, ...) +{ + va_list ap; + va_start (ap, format); + log_level_printf (format, ap, PURPLE_DEBUG_ERROR, COLOR_YELLOW); + va_end (ap); +} + +void fatal(const char* format, ...) +{ + va_list ap; + va_start (ap, format); + log_level_printf (format, ap, PURPLE_DEBUG_FATAL, COLOR_REDB); + va_end (ap); + info ("\n"); +} + diff --git a/msglog.h b/msglog.h new file mode 100644 index 0000000..17e324d --- /dev/null +++ b/msglog.h @@ -0,0 +1,14 @@ +#include + +/** + * Set a custom logging callback to use instead of regular printing + * to stdout + */ +void hexdump (int *in_ptr, int *in_end); + +void debug(const char* format, ...); +void info(const char* format, ...); +void warning(const char* format, ...); +void failure(const char* format, ...); +void fatal(const char* format, ...); + diff --git a/telegram-base.c b/telegram-base.c new file mode 100644 index 0000000..abae430 --- /dev/null +++ b/telegram-base.c @@ -0,0 +1,372 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "telegram-purple.h" +#include "msglog.h" + +#define DC_SERIALIZED_MAGIC 0x868aa81d +#define STATE_FILE_MAGIC 0x28949a93 +#define SECRET_CHAT_FILE_MAGIC 0x37a1988a + + +void read_state_file (struct tgl_state *TLS) { + char *name = 0; + if (asprintf (&name, "%s/%s", TLS->base_path, "state") < 0) { + return; + } + + int state_file_fd = open (name, O_CREAT | O_RDWR, 0600); + free (name); + + if (state_file_fd < 0) { + return; + } + int version, magic; + if (read (state_file_fd, &magic, 4) < 4) { close (state_file_fd); return; } + if (magic != (int)STATE_FILE_MAGIC) { close (state_file_fd); return; } + if (read (state_file_fd, &version, 4 || version < 0) < 4) { close (state_file_fd); return; } + int x[4]; + if (read (state_file_fd, x, 16) < 16) { + close (state_file_fd); + return; + } + int pts = x[0]; + int qts = x[1]; + int seq = x[2]; + int date = x[3]; + close (state_file_fd); + bl_do_set_seq (TLS, seq); + bl_do_set_pts (TLS, pts); + bl_do_set_qts (TLS, qts); + bl_do_set_date (TLS, date); +} + +void write_state_file (struct tgl_state *TLS) { + int wseq; + int wpts; + int wqts; + int wdate; + wseq = TLS->seq; wpts = TLS->pts; wqts = TLS->qts; wdate = TLS->date; + + char *name = 0; + if (asprintf (&name, "%s/%s", TLS->base_path, "state") < 0) { + return; + } + + int state_file_fd = open (name, O_CREAT | O_RDWR, 0600); + free (name); + + if (state_file_fd < 0) { + return; + } + int x[6]; + x[0] = STATE_FILE_MAGIC; + x[1] = 0; + x[2] = wpts; + x[3] = wqts; + x[4] = wseq; + x[5] = wdate; + assert (write (state_file_fd, x, 24) == 24); + close (state_file_fd); +} + +void write_dc (struct tgl_dc *DC, void *extra) { + int auth_file_fd = *(int *)extra; + if (!DC) { + int x = 0; + assert (write (auth_file_fd, &x, 4) == 4); + return; + } else { + int x = 1; + assert (write (auth_file_fd, &x, 4) == 4); + } + + assert (DC->has_auth); + + assert (write (auth_file_fd, &DC->port, 4) == 4); + int l = strlen (DC->ip); + assert (write (auth_file_fd, &l, 4) == 4); + assert (write (auth_file_fd, DC->ip, l) == l); + assert (write (auth_file_fd, &DC->auth_key_id, 8) == 8); + assert (write (auth_file_fd, DC->auth_key, 256) == 256); +} + +void write_auth_file (struct tgl_state *TLS) { + char *name = 0; + if (asprintf (&name, "%s/%s", TLS->base_path, "auth") < 0) { + return; + } + int auth_file_fd = open (name, O_CREAT | O_RDWR, 0600); + free (name); + if (auth_file_fd < 0) { return; } + int x = DC_SERIALIZED_MAGIC; + assert (write (auth_file_fd, &x, 4) == 4); + assert (write (auth_file_fd, &TLS->max_dc_num, 4) == 4); + assert (write (auth_file_fd, &TLS->dc_working_num, 4) == 4); + + tgl_dc_iterator_ex (TLS, write_dc, &auth_file_fd); + + assert (write (auth_file_fd, &TLS->our_id, 4) == 4); + close (auth_file_fd); +} + +void read_dc (struct tgl_state *TLS, int auth_file_fd, int id, unsigned ver) { + int port = 0; + assert (read (auth_file_fd, &port, 4) == 4); + int l = 0; + assert (read (auth_file_fd, &l, 4) == 4); + assert (l >= 0 && l < 100); + char ip[100]; + assert (read (auth_file_fd, ip, l) == l); + ip[l] = 0; + + long long auth_key_id; + static unsigned char auth_key[256]; + assert (read (auth_file_fd, &auth_key_id, 8) == 8); + assert (read (auth_file_fd, auth_key, 256) == 256); + + //bl_do_add_dc (id, ip, l, port, auth_key_id, auth_key); + bl_do_dc_option (TLS, id, 2, "DC", l, ip, port); + bl_do_set_auth_key_id (TLS, id, auth_key); + bl_do_dc_signed (TLS, id); +} + +void empty_auth_file (struct tgl_state *TLS) { + if (TLS->test_mode) { + bl_do_dc_option (TLS, 1, 0, "", strlen (TG_SERVER_TEST_1), TG_SERVER_TEST_1, 443); + bl_do_dc_option (TLS, 2, 0, "", strlen (TG_SERVER_TEST_2), TG_SERVER_TEST_2, 443); + bl_do_dc_option (TLS, 3, 0, "", strlen (TG_SERVER_TEST_3), TG_SERVER_TEST_3, 443); + bl_do_set_working_dc (TLS, TG_SERVER_TEST_DEFAULT); + } else { + bl_do_dc_option (TLS, 1, 0, "", strlen (TG_SERVER_1), TG_SERVER_1, 443); + bl_do_dc_option (TLS, 2, 0, "", strlen (TG_SERVER_2), TG_SERVER_2, 443); + bl_do_dc_option (TLS, 3, 0, "", strlen (TG_SERVER_3), TG_SERVER_3, 443); + bl_do_dc_option (TLS, 4, 0, "", strlen (TG_SERVER_4), TG_SERVER_4, 443); + bl_do_dc_option (TLS, 5, 0, "", strlen (TG_SERVER_5), TG_SERVER_5, 443); + bl_do_set_working_dc (TLS, TG_SERVER_DEFAULT);; + } +} + +void read_auth_file (struct tgl_state *TLS) { + char *name = 0; + if (asprintf (&name, "%s/%s", TLS->base_path, "auth") < 0) { + return; + } + int auth_file_fd = open (name, O_CREAT | O_RDWR, 0600); + free (name); + if (auth_file_fd < 0) { + empty_auth_file (TLS); + return; + } + assert (auth_file_fd >= 0); + unsigned x; + unsigned m; + if (read (auth_file_fd, &m, 4) < 4 || (m != DC_SERIALIZED_MAGIC)) { + close (auth_file_fd); + empty_auth_file (TLS); + return; + } + assert (read (auth_file_fd, &x, 4) == 4); + assert (x > 0); + int dc_working_num; + assert (read (auth_file_fd, &dc_working_num, 4) == 4); + + int i; + for (i = 0; i <= (int)x; i++) { + int y; + assert (read (auth_file_fd, &y, 4) == 4); + if (y) { + read_dc (TLS, auth_file_fd, i, m); + } + } + bl_do_set_working_dc (TLS, dc_working_num); + int our_id; + int l = read (auth_file_fd, &our_id, 4); + if (l < 4) { + assert (!l); + } + if (our_id) { + bl_do_set_our_id (TLS, our_id); + } + close (auth_file_fd); +} + +void telegram_export_authorization (struct tgl_state *TLS); +void export_auth_callback (struct tgl_state *TLS, void *extra, int success) { + assert (success); + telegram_export_authorization (TLS); +} + +void telegram_export_authorization (struct tgl_state *TLS) { + int i; + for (i = 0; i <= TLS->max_dc_num; i++) if (TLS->DC_list[i] && !tgl_signed_dc (TLS, TLS->DC_list[i])) { + tgl_do_export_auth (TLS, i, export_auth_callback, (void*)(long)TLS->DC_list[i]); + return; + } + write_auth_file (TLS); + telegram_on_ready (TLS); +} + +static void request_code (struct tgl_state *TLS); +static void request_name_and_code (struct tgl_state *TLS); +static void code_receive_result (struct tgl_state *TLS, void *extra, int success, struct tgl_user *U) { + if (!success) { + debug ("Bad code...\n"); + request_code (TLS); + } else { + telegram_export_authorization (TLS); + } +} + +static void code_auth_receive_result (struct tgl_state *TLS, void *extra, int success, struct tgl_user *U) { + if (!success) { + debug ("Bad code...\n"); + request_name_and_code (TLS); + } else { + telegram_export_authorization (TLS); + } +} + +static void request_code_entered (gpointer data, const gchar *code) { + struct tgl_state *TLS = data; + telegram_conn *conn = TLS->ev_base; + char const *username = purple_account_get_username(conn->pa); + tgl_do_send_code_result (TLS, username, conn->hash, code, code_receive_result, 0) ; +} + +static void request_code_canceled (gpointer data) { + struct tgl_state *TLS = data; + telegram_conn *conn = TLS->ev_base; + + purple_connection_error_reason(conn->gc, + PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED, "registration canceled"); +} + +static void request_name_code_entered (PurpleConnection* gc, PurpleRequestFields* fields) { + telegram_conn *conn = purple_connection_get_protocol_data(gc); + struct tgl_state *TLS = conn->TLS; + char const *username = purple_account_get_username(conn->pa); + + const char* first = purple_request_fields_get_string(fields, "first_name"); + const char* last = purple_request_fields_get_string(fields, "last_name"); + const char* code = purple_request_fields_get_string(fields, "code"); + if (!first || !last || !code) { + request_name_and_code (TLS); + return; + } + + tgl_do_send_code_result_auth (TLS, username, conn->hash, code, first, last, code_auth_receive_result, NULL); +} + +static void request_code (struct tgl_state *TLS) { + debug ("Client is not registered, registering...\n"); + telegram_conn *conn = TLS->ev_base; + + purple_request_input ( + conn->gc, // handle (the PurpleAccount) + "Telegram Code", // title + "Enter Telegram Code", // primary + "Telegram wants to verify your identity, please enter the code, that you have received via SMS.", // secondary + NULL, // default_value + 0, // multiline + 0, // masked + "code", // hint + "OK", // ok_text + G_CALLBACK(request_code_entered), // ok_cb + "Cancel", // cancel_text + G_CALLBACK(request_code_canceled), // cancel_cb + conn->pa, // account + NULL, // who + NULL, // conv + TLS // user_data + ); +} + +static void request_name_and_code (struct tgl_state *TLS) { + telegram_conn *conn = TLS->ev_base; + + debug ("Phone is not registered, registering...\n"); + + PurpleRequestFields* fields = purple_request_fields_new(); + PurpleRequestField* field = 0; + + PurpleRequestFieldGroup* group = purple_request_field_group_new("Registration"); + field = purple_request_field_string_new("first_name", "First Name", "", 0); + purple_request_field_group_add_field(group, field); + field = purple_request_field_string_new("last_name", "Last Name", "", 0); + purple_request_field_group_add_field(group, field); + purple_request_fields_add_group(fields, group); + + group = purple_request_field_group_new("Authorization"); + field = purple_request_field_string_new("code", "Telegram Code", "", 0); + purple_request_field_group_add_field(group, field); + purple_request_fields_add_group(fields, group); + + purple_request_fields(conn->gc, "Register", "Please register your phone number.", NULL, fields, "Ok", + G_CALLBACK( request_name_code_entered ), "Cancel", NULL, conn->pa, NULL, NULL, conn->gc); +} + +static void sign_in_callback (struct tgl_state *TLS, void *extra, int success, int registered, const char *mhash) { + assert (success); // TODO proper error handle + telegram_conn *conn = TLS->ev_base; + conn->hash = strdup (mhash); + + if (registered) { + request_code (TLS); + } else { + request_name_and_code (TLS); + } +} + +static void telegram_send_sms (struct tgl_state *TLS) { + if (tgl_signed_dc (TLS, TLS->DC_working)) { + telegram_export_authorization (TLS); + return; + } + telegram_conn *conn = TLS->ev_base; + char const *username = purple_account_get_username(conn->pa); + tgl_do_send_code (TLS, username, sign_in_callback, 0); +} + +static int all_authorized (struct tgl_state *TLS) { + int i; + for (i = 0; i <= TLS->max_dc_num; i++) if (TLS->DC_list[i]) { + if (!tgl_authorized_dc (TLS, TLS->DC_list[i])) { + return 0; + } + } + return 1; +} + +static int check_all_authorized (gpointer arg) { + struct tgl_state *TLS = arg; + if (all_authorized (TLS)) { + telegram_send_sms (TLS); + return FALSE; + } else { + return TRUE; + } +} + +void telegram_login (struct tgl_state *TLS) { + read_auth_file (TLS); + read_state_file (TLS); + if (all_authorized (TLS)) { + telegram_send_sms (TLS); + return; + } + purple_timeout_add (100, check_all_authorized, TLS); +} + diff --git a/telegram-base.h b/telegram-base.h new file mode 100644 index 0000000..1496ba6 --- /dev/null +++ b/telegram-base.h @@ -0,0 +1,9 @@ +#ifndef __TELEGRAM_BASE_H__ +#define __TELEGRAM_BASE_H__ +void read_state_file (struct tgl_state *TLS); +void read_auth_file (struct tgl_state *TLS); +void write_auth_file (struct tgl_state *TLS); +void write_state_file (struct tgl_state *TLS); + +void telegram_login (struct tgl_state *TLS); +#endif diff --git a/telegram-purple.c b/telegram-purple.c new file mode 100644 index 0000000..d78dec4 --- /dev/null +++ b/telegram-purple.c @@ -0,0 +1,1082 @@ +/** + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Libpurple Plugin Includes +#include "notify.h" +#include "plugin.h" +#include "version.h" +#include "account.h" +#include "accountopt.h" +#include "blist.h" +#include "cmds.h" +#include "conversation.h" +#include "connection.h" +#include "debug.h" +#include "privacy.h" +#include "prpl.h" +#include "roomlist.h" +#include "status.h" +#include "util.h" +#include "prpl.h" +#include "prefs.h" +#include "util.h" +#include "eventloop.h" +#include "request.h" + +// telegram-purple includes +#include +#include "telegram-purple.h" +//#include "structures.h" +#include "tgp-net.h" +#include "tgp-timers.h" +#include "msglog.h" +#include "telegram-base.h" + +#define CONFIG_DIR ".telegram-purple" +#define BUDDYNAME_MAX_LENGTH 128 + +static PurplePlugin *_telegram_protocol = NULL; +PurpleGroup *tggroup; + +void tgprpl_login_on_connected(); + +static PurpleChat *blist_find_chat_by_id(PurpleConnection *gc, const char *id); +//static void tgprpl_has_output(void *handle); +void on_chat_get_info (struct tgl_state *TLS, void *extra, int success, struct tgl_chat *C); +void on_user_get_info (struct tgl_state *TLS, void *show_info, int success, struct tgl_user *U); +static int user_get_alias (tgl_peer_t *user, char *buffer, int maxlen); + +static const char *chat_id_get_comp_val (PurpleConnection *gc, int id, char *value) +{ + gchar *name = g_strdup_printf ("%d", id); + PurpleChat *ch = blist_find_chat_by_id(gc, name); + g_free (name); + GHashTable *table = purple_chat_get_components(ch); + return g_hash_table_lookup(table, value); +} + + +/** + * Assure that the given chat is opened + */ +static PurpleConversation *chat_show (PurpleConnection *gc, int id) +{ + debug ("show chat"); + telegram_conn *conn = purple_connection_get_protocol_data(gc); + + PurpleConversation *convo = purple_find_chat(gc, id); + if (convo) { + if (purple_conv_chat_has_left(PURPLE_CONV_CHAT(convo))) + { + serv_got_joined_chat(gc, id, chat_id_get_comp_val(gc, id, "subject")); + } + } else { + gchar *name = g_strdup_printf ("%d", id); + if (g_hash_table_contains (conn->joining_chats, name)) { + // already joining this chat + } else { + // mark chat as already joining + g_hash_table_insert(conn->joining_chats, name, 0); + + // join chat first + tgl_do_get_chat_info (conn->TLS, TGL_MK_CHAT(id), 0, on_chat_get_info, NULL); + } + g_free(name); + } + return convo; +} +/* +static PurpleChat *get_chat_by_id (PurpleConnection *gc, int id) +{ + gchar *name = g_strdup_printf ("%d", id); + PurpleChat *chat = blist_find_chat_by_id (gc, name); + g_free (name); + return chat; +}*/ + +/** + * Returns the base icon name for the given buddy and account. + * If buddy is NULL and the account is non-NULL, it will return the + * name to use for the account's icon. If both are NULL, it will + * return the name to use for the protocol's icon. + * + * This must be implemented. + */ +static const char *tgprpl_list_icon(PurpleAccount * acct, PurpleBuddy * buddy) +{ + return "telegram"; +} + +static telegram_conn *get_conn_from_buddy (PurpleBuddy *buddy) +{ + telegram_conn *c = purple_connection_get_protocol_data ( + purple_account_get_connection (purple_buddy_get_account (buddy))); + return c; +} + +/** + * Allows the prpl to add text to a buddy's tooltip. + */ +static void tgprpl_tooltip_text(PurpleBuddy * buddy, PurpleNotifyUserInfo * info, gboolean full) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_tooltip_text()\n"); + + debug ("purple_buddy_get_protocol_data: %s\n", buddy->name); + tgl_peer_id_t *peer = purple_buddy_get_protocol_data(buddy); + if(peer == NULL) + { + purple_notify_user_info_add_pair_plaintext(info, "Status", "Offline"); + return; + } + tgl_peer_t *P = tgl_peer_get (get_conn_from_buddy(buddy)->TLS, *peer); + + purple_notify_user_info_add_pair_plaintext(info, "Status", P->user.status.online == 1 ? "Online" : "Offline"); + struct tm *tm = localtime ((void *)&P->user.status.when); + char buffer [21]; + sprintf (buffer, "[%04d/%02d/%02d %02d:%02d:%02d]", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); + purple_notify_user_info_add_pair_plaintext(info, "Last seen: ", buffer); +} + +/** + * Handle a proxy-close of telegram + * + * Remove all open inputs added to purple + */ + +gboolean queries_timerfunc (gpointer data) { + debug ("queries_timerfunc()\n"); + telegram_conn *conn = data; + + if (conn->updated) { + debug ("State updated, storing current session...\n"); + conn->updated = 0; + write_state_file (conn->TLS); + } + return 1; +} + +void telegram_on_ready (struct tgl_state *TLS) { + debug ("telegram_on_ready().\n"); + telegram_conn *conn = TLS->ev_base; + purple_connection_set_state(conn->gc, PURPLE_CONNECTED); + purple_connection_set_display_name(conn->gc, purple_account_get_username(conn->pa)); + purple_blist_add_account(conn->pa); + tggroup = purple_find_group("Telegram"); + if (tggroup == NULL) { + purple_debug_info (PLUGIN_ID, "PurpleGroup = NULL, creating"); + tggroup = purple_group_new ("Telegram"); + purple_blist_add_group (tggroup, NULL); + } + + tgl_do_get_difference (TLS, 0, 0, 0); + tgl_do_get_dialog_list (TLS, 0, 0); + tgl_do_update_contact_list (TLS, 0, 0); + + conn->timer = purple_timeout_add (5000, queries_timerfunc, conn); +} + +static void message_received_handler (struct tgl_state *TLS, struct tgl_message *M); +static void user_update_handler (struct tgl_state *TLS, struct tgl_user *U, unsigned flags); +static void chat_update_handler (struct tgl_state *TLS, struct tgl_chat *C, unsigned flags); +void on_user_typing (struct tgl_state *TLS, struct tgl_user *U, enum tgl_typing_status status); +struct tgl_update_callback tgp_callback = { + .logprintf = debug, + .new_msg = message_received_handler, + .msg_receive = message_received_handler, + .user_update = user_update_handler, + .chat_update = chat_update_handler, + .type_notification = on_user_typing +}; + +/** + * This must be implemented. + */ +static void tgprpl_login(PurpleAccount * acct) +{ + purple_debug_info (PLUGIN_ID, "tgprpl_login()\n"); + PurpleConnection *gc = purple_account_get_connection(acct); + char const *username = purple_account_get_username(acct); + + struct tgl_state *TLS = tgl_state_alloc (); + + const char *dir = CONFIG_DIR; + struct passwd *pw = getpwuid(getuid()); + int len = strlen (dir) + strlen (pw->pw_dir) + 2 + strlen (username); + TLS->base_path = malloc (len); + snprintf (TLS->base_path, len, "%s/%s/%s", pw->pw_dir, dir, username); + debug ("base configuration path: '%s'", TLS->base_path); + g_mkdir_with_parents(TLS->base_path, 0700); + + len += strlen ("/downloads"); + char *ddir = malloc (len); + sprintf (ddir, "%s/downloads", TLS->base_path); + tgl_set_download_directory (TLS, ddir); + g_mkdir_with_parents(ddir, 0700); + free (ddir); + + tgl_set_verbosity (TLS, 4); + tgl_set_rsa_key (TLS, "/etc/telegram-purple/server.pub"); + + + + // create handle to store additional info for libpurple in + // the new telegram instance + telegram_conn *conn = g_new0(telegram_conn, 1); + conn->TLS = TLS; + conn->gc = gc; + conn->pa = acct; + conn->new_messages = g_queue_new(); + conn->joining_chats = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + purple_connection_set_protocol_data (gc, conn); + + tgl_set_ev_base (TLS, conn); + tgl_set_net_methods (TLS, &tgp_conn_methods); + tgl_set_timer_methods (TLS, &tgp_timers); + tgl_set_callback (TLS, &tgp_callback); + + tgl_init (TLS); + purple_connection_set_state (conn->gc, PURPLE_CONNECTING); + + + telegram_login (TLS); +} + +/** + * This must be implemented. + */ +static void tgprpl_close(PurpleConnection * gc) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_close()\n"); + telegram_conn *conn = purple_connection_get_protocol_data(gc); + purple_timeout_remove(conn->timer); + tgl_free_all (conn->TLS); +} + +static int chat_add_message(struct tgl_state *TLS, struct tgl_message *M) +{ + telegram_conn *conn = TLS->ev_base; + + if (chat_show (conn->gc, M->to_id.id)) { + // chat initialies, add message now + if (tgl_get_peer_id (M->from_id) == TLS->our_id) { + serv_got_chat_in(conn->gc, tgl_get_peer_id (M->to_id), "You", + PURPLE_MESSAGE_RECV, M->message, M->date); + } else { + tgl_peer_t *fromPeer = tgl_peer_get (TLS, M->from_id); + char *alias = malloc(BUDDYNAME_MAX_LENGTH); + user_get_alias (fromPeer, alias, BUDDYNAME_MAX_LENGTH); + serv_got_chat_in (conn->gc, tgl_get_peer_id (M->to_id), alias, + PURPLE_MESSAGE_RECV, M->message, M->date); + g_free(alias); + } + return 1; + } else { + // add message once the chat was initialised + g_queue_push_tail (conn->new_messages, M); + return 0; + } +} + +void message_received_handler(struct tgl_state *TLS, struct tgl_message *M) +{ + debug ("message_allocated_handler\n"); + telegram_conn *conn = TLS->ev_base; + PurpleConnection *gc = conn->gc; + + if (M->service || (M->flags & (FLAG_MESSAGE_EMPTY | FLAG_DELETED))) { + // TODO: handle those messages properly, currently adding them + // causes a segfault for an unknown reason + debug ("service message, skipping...\n"); + return; + } + + int id = tgl_get_peer_id (M->from_id); + tgl_peer_id_t to_id = M->to_id; + char *from = g_strdup_printf("%d", id); + char *to = g_strdup_printf("%d", to_id.id); + switch (tgl_get_peer_type (to_id)) { + case TGL_PEER_CHAT: + debug ("PEER_CHAT\n"); + chat_add_message (TLS, M); + break; + + case TGL_PEER_USER: + debug ("PEER_USER\n"); + if (tgl_get_peer_id (M->from_id) == TLS->our_id) { + serv_got_im(gc, to, M->message, PURPLE_MESSAGE_SEND, M->date); + } else { + serv_got_im(gc, from, M->message, PURPLE_MESSAGE_RECV, M->date); + } + break; + + case TGL_PEER_ENCR_CHAT: + break; + + case TGL_PEER_GEO_CHAT: + break; + } + g_free(from); + conn->updated = 1; +} + +void on_new_user_status (struct tgl_state *TLS, void *peer) +{ + telegram_conn *conn = TLS->ev_base; + tgl_peer_t *p = peer; + char *who = g_strdup_printf("%d", tgl_get_peer_id (p->user.id)); + PurpleAccount *account = purple_connection_get_account(conn->gc); + if (p->user.status.online == 1) + purple_prpl_got_user_status(account, who, "available", "message", "", NULL); + else + purple_prpl_got_user_status(account, who, "mobile", "message", "", NULL); + g_free(who); +} + +void on_user_typing (struct tgl_state *TLS, struct tgl_user *U, enum tgl_typing_status status) +{ + telegram_conn *conn = TLS->ev_base; + + char *who = g_strdup_printf("%d", tgl_get_peer_id (U->id)); + serv_got_typing(conn->gc, who, 2, PURPLE_TYPING); + g_free(who); +} + +/* + * Search chats in hash table + * + * TODO: There has to be an easier way to do this + */ +static PurpleChat *blist_find_chat_by_hasht_cond(PurpleConnection *gc, int (*fn)(GHashTable *hasht, void *data), void *data) +{ + PurpleAccount *account = purple_connection_get_account(gc); + PurpleBlistNode *node = purple_blist_get_root(); + GHashTable *hasht; + while (node) { + if (PURPLE_BLIST_NODE_IS_CHAT(node)) { + PurpleChat *ch = PURPLE_CHAT(node); + if (purple_chat_get_account(ch) == account) { + hasht = purple_chat_get_components(ch); + if (fn(hasht, data)) + return ch; + } + } + node = purple_blist_node_next(node, 0); + } + return NULL; +} +static int hasht_cmp_id(GHashTable *hasht, void *data) +{ + return !strcmp(g_hash_table_lookup(hasht, "id"), *((char **)data)); +} +static PurpleChat *blist_find_chat_by_id(PurpleConnection *gc, const char *id) +{ + return blist_find_chat_by_hasht_cond(gc, hasht_cmp_id, &id); +} + +static char *peer_get_peer_id_as_string(tgl_peer_t *user) +{ + return g_strdup_printf("%d", tgl_get_peer_id (user->id)); +} + + +static void sanitize_alias(char *buffer) +{ + size_t len = strlen(buffer); + gchar *curr; + while ((curr = g_utf8_strchr(buffer, len, '\n'))) { + *curr = 0x20; + } +} + +static int user_get_alias (tgl_peer_t *user, char *buffer, int maxlen) +{ + char* last_name = (user->user.last_name && strlen(user->user.last_name)) ? user->user.last_name : ""; + char* first_name = (user->user.first_name && strlen(user->user.first_name)) ? user->user.first_name : ""; + sanitize_alias (last_name); + sanitize_alias (first_name); + if (strlen(first_name) && strlen(last_name)) { + return snprintf(buffer, maxlen, "%s %s", first_name, last_name); + } else if (strlen(first_name)) { + return snprintf(buffer, maxlen, "%s", first_name); + } else if (strlen(last_name)) { + return snprintf(buffer, maxlen, "%s", last_name); + } else { + return snprintf(buffer, maxlen, "%d", tgl_get_peer_id (user->id)); + } +} + +void user_update_handler (struct tgl_state *TLS, struct tgl_user *user, unsigned flags) +{ + if (!(flags & FLAG_CREATED)) { return; } + + telegram_conn *conn = TLS->ev_base; + PurpleConnection *gc = conn->gc; + PurpleAccount *pa = conn->pa; + + gchar *name = peer_get_peer_id_as_string ((tgl_peer_t *)user); //g_strdup_printf("%d", get_peer_id(user->id)); + debug("Allocated user: %s\n", name); + // TODO: this should probably be freed again somwhere + char *alias = malloc(BUDDYNAME_MAX_LENGTH); + if (user_get_alias((tgl_peer_t *)user, alias, BUDDYNAME_MAX_LENGTH) < 0) { + purple_debug_info(PLUGIN_ID, "Buddyalias of (%d) too long, not adding to buddy list.\n", + tgl_get_peer_id(user->id)); + return; + } + PurpleBuddy *buddy = purple_find_buddy(pa, name); + if (!buddy) { + char *actual = tgl_get_peer_id (user->id) == TLS->our_id ? "You" : alias; + purple_debug_info(PLUGIN_ID, "Adding %s to buddy list\n", name); + purple_debug_info(PLUGIN_ID, "Alias %s\n", actual); + buddy = purple_buddy_new(pa, name, actual); + purple_blist_add_buddy(buddy, NULL, tggroup, NULL); + tgl_do_get_user_info (TLS, user->id, 0, on_user_get_info, 0); + } + purple_buddy_set_protocol_data(buddy, (gpointer)&user->id); + + PurpleAccount *account = purple_connection_get_account(gc); + if (user->status.online == 1) + purple_prpl_got_user_status(account, name, "available", "message", "", NULL); + else + purple_prpl_got_user_status(account, name, "mobile", "message", "", NULL); + + g_free(alias); + g_free(name); +} + +void chat_update_handler (struct tgl_state *TLS, struct tgl_chat *chat, unsigned flags) +{ + if (!(flags & FLAG_CREATED)) { return; } + tgl_do_get_chat_info (TLS, chat->id, 0, on_chat_get_info, 0); + + telegram_conn *conn = TLS->ev_base; + PurpleConnection *gc = conn->gc; + PurpleAccount *pa = conn->pa; + + gchar *name = peer_get_peer_id_as_string ((tgl_peer_t *)chat); //g_strdup_printf("%d", get_peer_id(user->id)); + debug("Allocated chat: %s\n", name); + PurpleChat *ch = blist_find_chat_by_id(gc, name); + if (!ch) { + gchar *admin = g_strdup_printf("%d", chat->admin_id); + GHashTable *htable = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); + g_hash_table_insert(htable, g_strdup("subject"), chat->title); + g_hash_table_insert(htable, g_strdup("id"), name); + g_hash_table_insert(htable, g_strdup("owner"), admin); + debug("Adding chat to blist: %s (%s, %s)\n", chat->title, name, admin); + ch = purple_chat_new(pa, chat->title, htable); + purple_blist_add_chat(ch, NULL, NULL); + } + + GHashTable *gh = purple_chat_get_components(ch); + //char const *id = g_hash_table_lookup(gh, "id"); + char const *owner = g_hash_table_lookup(gh, "owner"); + + PurpleConversation *conv = purple_find_chat(gc, atoi(name)); + + purple_conv_chat_clear_users(purple_conversation_get_chat_data(conv)); + if (conv) { + struct tgl_chat_user *usr = chat->user_list; + int size = chat->user_list_size; + int i; + for (i = 0; i < size; i++) { + struct tgl_chat_user *cu = (usr + i); + // TODO: Inviter ID + // tgl_peer_id_t u = MK_USER (cu->user_id); + // tgl_peer_t *uchat = user_chat_get(u); + const char *cuname = g_strdup_printf("%d", cu->user_id); + debug("Adding user %s to chat %s\n", cuname, name); + purple_conv_chat_add_user(purple_conversation_get_chat_data(conv), cuname, "", + PURPLE_CBFLAGS_NONE | (!strcmp(owner, cuname) ? PURPLE_CBFLAGS_FOUNDER : 0), 0); + } + } +} + +PurpleNotifyUserInfo *create_user_notify_info(struct tgl_user *usr) +{ + PurpleNotifyUserInfo *info = purple_notify_user_info_new(); + purple_notify_user_info_add_pair(info, "First name", usr->first_name); + purple_notify_user_info_add_pair(info, "Last name", usr->last_name); + purple_notify_user_info_add_pair(info, "Phone", usr->phone); + purple_notify_user_info_add_pair(info, "Status", usr->status.online == 1 ? "Online" : "Offline"); + return info; +} + +void on_userpic_loaded (struct tgl_state *TLS, void *extra, int success, char *filename) { + telegram_conn *conn = TLS->ev_base; + + gchar *data = NULL; + size_t len; + GError *err = NULL; + g_file_get_contents (filename, &data, &len, &err); + int imgStoreId = purple_imgstore_add_with_id (g_memdup(data, len), len, NULL); + debug("Imagestore id: %d\n", imgStoreId); + //Create user info + struct download_desc *dld = extra; + struct tgl_user *U = dld->data; + + char *who = g_strdup_printf("%d", tgl_get_peer_id (U->id)); + + if(dld->type == 1) + { + PurpleNotifyUserInfo *info = create_user_notify_info(U); + char *profile_image = profile_image = g_strdup_printf("
", imgStoreId); + purple_notify_user_info_add_pair(info, "Profile image", profile_image); + purple_notify_userinfo(conn->gc, who, info, NULL, NULL); + g_free(profile_image); + } + purple_buddy_icons_set_for_user(conn->pa, who, g_memdup(data, len), len, NULL); + g_free(who); +} + +void on_user_get_info (struct tgl_state *TLS, void *show_info, int success, struct tgl_user *U) +{ + assert (success); + debug("Get user info. \n %d", show_info); + char *who = g_strdup_printf("%d", tgl_get_peer_id (U->id)); + if (U->photo.sizes_num == 0 && show_info) + { + telegram_conn *conn = TLS->ev_base; + PurpleNotifyUserInfo *info = create_user_notify_info(U); + purple_notify_userinfo(conn->gc, who, info, NULL, NULL); + } else { + struct download_desc *dld = malloc (sizeof(struct download_desc)); + dld->data = U; + dld->type = show_info ? 1 : 2; + tgl_do_load_photo (TLS, &U->photo, on_userpic_loaded, dld); + } + g_free(who); +} + +/** + * This PRPL function should return a positive value on success. + * If the message is too big to be sent, return -E2BIG. If + * the account is not connected, return -ENOTCONN. If the + * PRPL is unable to send the message for another reason, return + * some other negative value. You can use one of the valid + * errno values, or just big something. If the message should + * not be echoed to the conversation window, return 0. + */ +static int tgprpl_send_im(PurpleConnection * gc, const char *who, const char *message, PurpleMessageFlags flags) +{ + telegram_conn *conn = purple_connection_get_protocol_data(gc); + PurpleAccount *pa = conn->pa; + + purple_debug_info (PLUGIN_ID, "tgprpl_send_im()\n"); + PurpleBuddy *b = purple_find_buddy (pa, who); + tgl_peer_id_t *peer = purple_buddy_get_protocol_data (b); + + tgl_do_send_message (conn->TLS, *peer, message, strlen (message), 0, 0); + return 1; +} + +/** + * Send a message to a chat. + * This PRPL function should return a positive value on success. + * If the message is too big to be sent, return -E2BIG. If + * the account is not connected, return -ENOTCONN. If the + * PRPL is unable to send the message for another reason, return + * some other negative value. You can use one of the valid + * errno values, or just big something. + * + * @param id The id of the chat to send the message to. + * @param message The message to send to the chat. + * @param flags A bitwise OR of #PurpleMessageFlags representing + * message flags. + * @return A positive number or 0 in case of success, + * a negative error number in case of failure. + */ +static int tgprpl_send_chat(PurpleConnection * gc, int id, const char *message, PurpleMessageFlags flags) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_send_chat()\n"); + telegram_conn *conn = purple_connection_get_protocol_data (gc); + //PurpleConversation *convo = purple_find_chat(gc, id); + tgl_do_send_message (conn->TLS, TGL_MK_CHAT(id), message, strlen(message), 0, 0); + + //char *who = g_strdup_printf("%d", id); + //serv_got_chat_in(gc, id, "You", PURPLE_MESSAGE_RECV, message, time(NULL)); + //g_free(who); + return 1; +} + +/** + * Add a buddy to a group on the server. + * + * This PRPL function may be called in situations in which the buddy is + * already in the specified group. If the protocol supports + * authorization and the user is not already authorized to see the + * status of \a buddy, \a add_buddy should request authorization. + * + * @deprecated Since 2.8.0, add_buddy_with_invite is preferred. + * @see add_buddy_with_invite + */ +static void tgprpl_add_buddy(PurpleConnection * gc, PurpleBuddy * buddy, PurpleGroup * group) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_add_buddy()\n"); +} + +static void tgprpl_add_buddies(PurpleConnection * gc, GList * buddies, GList * groups) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_add_buddies()\n"); +} + +static void tgprpl_remove_buddy(PurpleConnection * gc, PurpleBuddy * buddy, PurpleGroup * group) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_remove_buddy()\n"); +} + +static void tgprpl_remove_buddies(PurpleConnection * gc, GList * buddies, GList * groups) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_remove_buddies()\n"); + +} + +static void tgprpl_convo_closed(PurpleConnection * gc, const char *who) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_convo_closed()\n"); + +} + +static void tgprpl_add_deny(PurpleConnection * gc, const char *name) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_add_deny()\n"); + +} + +static void tgprpl_rem_deny(PurpleConnection * gc, const char *name) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_rem_deny()\n"); + +} + +/** + * @return If this protocol requires the PURPLE_TYPING message to + * be sent repeatedly to signify that the user is still + * typing, then the PRPL should return the number of + * seconds to wait before sending a subsequent notification. + * Otherwise the PRPL should return 0. + */ +static unsigned int tgprpl_send_typing(PurpleConnection * gc, const char *who, PurpleTypingState typing) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_send_typing()\n"); + /*telegram_conn *conn = purple_connection_get_protocol_data(gc); + PurpleBuddy *b = purple_find_buddy(conn->pa, who); + if (b) { + tgl_peer_id_t *peer = purple_buddy_get_protocol_data(b); + if (peer) { + tgl_do_update_typing (conn->tg, *peer); + } + }*/ + return 0; +} + +/** + * Set the buddy icon for the given connection to @a img. The prpl + * does NOT own a reference to @a img; if it needs one, it must + * #purple_imgstore_ref(@a img) itself. + */ +static void tgprpl_set_buddy_icon(PurpleConnection * gc, PurpleStoredImage * img) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_set_buddy_icon()\n"); + +} + +/** + * File transfer callback + */ +static gboolean tgprpl_can_receive_file(PurpleConnection * gc, const char *who) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_can_receive_file()\n"); + return 0; +} + +/** + * Checks whether offline messages to @a buddy are supported. + + * @return @c 1 if @a buddy can be sent messages while they are + * offline, or @c 0 if not. + */ +static gboolean tgprpl_offline_message(const PurpleBuddy * buddy) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_offline_message()\n"); + return 0; +} + +/** + * Returns a list of #PurpleStatusType which exist for this account; + * this must be implemented, and must add at least the offline and + * online states. + */ +static GList *tgprpl_status_types(PurpleAccount * acct) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_status_types()\n"); + GList *types = NULL; + PurpleStatusType *type; + type = purple_status_type_new_with_attrs(PURPLE_STATUS_AVAILABLE, NULL, NULL, + 1, 1, 0, "message", "Message", purple_value_new(PURPLE_TYPE_STRING), NULL); + types = g_list_prepend(types, type); + + type = purple_status_type_new_with_attrs(PURPLE_STATUS_MOBILE, NULL, NULL, 1, + 1, 0, "message", "Message", purple_value_new(PURPLE_TYPE_STRING), NULL); + types = g_list_prepend(types, type); + + type = purple_status_type_new(PURPLE_STATUS_OFFLINE, NULL, NULL, 1); + types = g_list_append(types, type); + + return g_list_reverse(types); +} + +static void tgprpl_set_status(PurpleAccount * acct, PurpleStatus * status) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_set_status()\n"); +} + +/** + * Should arrange for purple_notify_userinfo() to be called with + * @a who's user info. + */ +static void tgprpl_get_info(PurpleConnection * gc, const char *username) +{ + + purple_debug_info(PLUGIN_ID, "tgprpl_get_info()\n"); + telegram_conn *conn = purple_connection_get_protocol_data(gc); + tgl_peer_id_t u = TGL_MK_USER(atoi(username)); + tgl_do_get_user_info (conn->TLS, u, 0, on_user_get_info, (void *)1l); + purple_debug_info(PLUGIN_ID, "tgprpl_get_info ready()\n"); + //fetch_alloc_user_full(tg->connection); + //fetch_user_full(tg->connection, user); + +} + +/** + * change a buddy's group on a server list/roster + */ +static void tgprpl_group_buddy(PurpleConnection * gc, const char *who, const char *old_group, const char *new_group) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_group_buddy()\n"); + +} + +/** + * rename a group on a server list/roster + */ +static void tgprpl_rename_group(PurpleConnection * gc, const char *old_name, PurpleGroup * group, GList * moved_buddies) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_rename_group()\n"); + +} + +/** + * Returns a list of #proto_chat_entry structs, which represent + * information required by the PRPL to join a chat. libpurple will + * call join_chat along with the information filled by the user. + * + * @return A list of #proto_chat_entry structs + */ +static GList *tgprpl_chat_join_info(PurpleConnection * gc) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_chat_join_info()\n"); + struct proto_chat_entry *pce; + + pce = g_new0(struct proto_chat_entry, 1); + pce->label = "_Subject:"; + pce->identifier = "subject"; + pce->required = TRUE; + return g_list_append(NULL, pce); +} + +/** + * Returns a hashtable which maps #proto_chat_entry struct identifiers + * to default options as strings based on chat_name. The resulting + * hashtable should be created with g_hash_table_new_full(g_str_hash, + * g_str_equal, NULL, g_free);. Use #get_chat_name if you instead need + * to extract a chat name from a hashtable. + * + * @param chat_name The chat name to be turned into components + * @return Hashtable containing the information extracted from chat_name + */ +static GHashTable *tgprpl_chat_info_defaults(PurpleConnection * gc, const char *chat_name) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_chat_info_defaults()\n"); + return NULL; +} + +/** + * Called when the user requests joining a chat. Should arrange for + * #serv_got_joined_chat to be called. + * + * @param components A hashtable containing information required to + * join the chat as described by the entries returned + * by #chat_info. It may also be called when accepting + * an invitation, in which case this matches the + * data parameter passed to #serv_got_chat_invite. + */ +static void tgprpl_chat_join(PurpleConnection * gc, GHashTable * data) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_chat_join()\n"); + + telegram_conn *conn = purple_connection_get_protocol_data(gc); + const char *groupname = g_hash_table_lookup(data, "subject"); + + char *id = g_hash_table_lookup(data, "id"); + if (!id) { + debug ("Got no chat id, aborting...\n"); + return; + } + if (!purple_find_chat(gc, atoi(id))) { + debug ("chat now known\n"); + //char *subject, *owner, *part; + tgl_do_get_chat_info (conn->TLS, TGL_MK_CHAT(atoi(id)), 0, on_chat_get_info, 0); + } else { + debug ("chat already known\n"); + serv_got_joined_chat(conn->gc, atoi(id), groupname); + } +} + +void on_chat_get_info (struct tgl_state *TLS, void *extra, int success, struct tgl_chat *C) { + assert (success); + debug ("on_chat_joined(%d)\n", tgl_get_peer_id (C->id)); + telegram_conn *conn = TLS->ev_base; + + PurpleConversation *conv = serv_got_joined_chat(conn->gc, tgl_get_peer_id (C->id), C->title); + int cnt = C->user_list_size; + struct tgl_chat_user *curr = C->user_list; + int i; + for (i = 0; i < cnt; i++) { + tgl_peer_id_t part_id = TGL_MK_USER((curr + i)->user_id); + char *name = g_strdup_printf ("%d", part_id.id); + int flags = PURPLE_CBFLAGS_NONE | ((C->admin_id == tgl_get_peer_id (part_id)) ? PURPLE_CBFLAGS_FOUNDER : 0); + debug ("purple_conv_chat_add_user (..., name=%s, ..., flags=%d)", name, flags); + purple_conv_chat_add_user( + purple_conversation_get_chat_data(conv), + name, + "", + flags, + 0 + ); + } + + debug ("g_queue_pop_head()\n"); + struct tgl_message *M = 0; + while ((M = g_queue_pop_head (conn->new_messages))) { + debug ("adding msg-id\n"); + //int id = tgl_get_peer_id (M->from_id); + if (!chat_add_message(TLS, M)) { + // chat still not working? + warning ("WARNING, chat %d still not existing... \n", tgl_get_peer_id (C->id)); + break; + } + } + + gchar *name = g_strdup_printf ("%d", tgl_get_peer_id (C->id)); + g_hash_table_remove (conn->joining_chats, name); + g_free (name); +} + +/** + * Invite a user to join a chat. + * + * @param id The id of the chat to invite the user to. + * @param message A message displayed to the user when the invitation + * is received. + * @param who The name of the user to send the invation to. + */ +static void tgprpl_chat_invite(PurpleConnection * gc, int id, const char *message, const char *name) { purple_debug_info(PLUGIN_ID, "tgprpl_chat_invite()\n"); } +/** + * Returns a chat name based on the information in components. Use + * #chat_info_defaults if you instead need to generate a hashtable + * from a chat name. + * + * @param components A hashtable containing information about the chat. + */ +static char *tgprpl_get_chat_name(GHashTable * data) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_get_chat_name()\n"); + return g_strdup(g_hash_table_lookup(data, "subject")); +} + +/** + * File transfer callback. + */ +static PurpleXfer *tgprpl_new_xfer(PurpleConnection * gc, const char *who) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_new_xfer()\n"); + return (PurpleXfer *)NULL; +} + +/** + * File transfer callback. + */ +static void tgprpl_send_file(PurpleConnection * gc, const char *who, const char *file) +{ + purple_debug_info(PLUGIN_ID, "tgprpl_send_file()\n"); + +} + +// SEE prpl.h +static PurplePluginProtocolInfo prpl_info = { + OPT_PROTO_NO_PASSWORD, /* options */ + NULL, /* user_splits, initialized in tgprpl_init() */ + NULL, /* protocol_options, initialized in tgprpl_init() */ + { /* icon_spec, a PurpleBuddyIconSpec */ + "png", /* format */ + 1, /* min_width */ + 1, /* min_height */ + 512, /* max_width */ + 512, /* max_height */ + 64000, /* max_filesize */ + PURPLE_ICON_SCALE_SEND, /* scale_rules */ + }, + tgprpl_list_icon, + NULL, + NULL, + tgprpl_tooltip_text, + tgprpl_status_types, + NULL, /* blist_node_menu */ + tgprpl_chat_join_info, + tgprpl_chat_info_defaults, /* chat_info_defaults */ + tgprpl_login, /* login */ + tgprpl_close, /* close */ + tgprpl_send_im, /* send_im */ + NULL, /* set_info */ + tgprpl_send_typing, /* send_typing */ + tgprpl_get_info, /* get_info */ + tgprpl_set_status, /* set_status */ + NULL, /* set_idle */ + NULL, /* change_passwd */ + tgprpl_add_buddy, /* add_buddy */ + tgprpl_add_buddies, /* add_buddies */ + tgprpl_remove_buddy, /* remove_buddy */ + tgprpl_remove_buddies, /* remove_buddies */ + NULL, /* add_permit */ + tgprpl_add_deny, /* add_deny */ + NULL, /* rem_permit */ + tgprpl_rem_deny, /* rem_deny */ + NULL, /* set_permit_deny */ + tgprpl_chat_join, /* join_chat */ + NULL, /* reject_chat */ + tgprpl_get_chat_name, /* get_chat_name */ + tgprpl_chat_invite, /* chat_invite */ + NULL, /* chat_leave */ + NULL, /* chat_whisper */ + tgprpl_send_chat, /* chat_send */ + NULL, /* keepalive */ + NULL, /* register_user */ + NULL, /* get_cb_info */ + NULL, /* get_cb_away */ + NULL, /* alias_buddy */ + tgprpl_group_buddy, /* group_buddy */ + tgprpl_rename_group, /* rename_group */ + NULL, /* buddy_free */ + tgprpl_convo_closed, /* convo_closed */ + purple_normalize_nocase, /* normalize */ + tgprpl_set_buddy_icon, /* set_buddy_icon */ + NULL, /* remove_group */ + NULL, /* get_cb_real_name */ + NULL, /* set_chat_topic */ + NULL, /* find_blist_chat */ + NULL, /* roomlist_get_list */ + NULL, /* roomlist_cancel */ + NULL, /* roomlist_expand_category */ + tgprpl_can_receive_file, /* can_receive_file */ + tgprpl_send_file, /* send_file */ + tgprpl_new_xfer, /* new_xfer */ + tgprpl_offline_message, /* offline_message */ + NULL, /* whiteboard_prpl_ops */ + NULL, /* send_raw */ + NULL, /* roomlist_room_serialize */ + NULL, /* unregister_user */ + NULL, /* send_attention */ + NULL, /* get_attention_types */ + sizeof(PurplePluginProtocolInfo), /* struct_size */ + NULL, /* get_account_text_table */ + NULL, /* initiate_media */ + NULL, /* get_media_caps */ + NULL, /* get_moods */ + NULL, /* set_public_alias */ + NULL, /* get_public_alias */ + NULL, /* add_buddy_with_invite */ + NULL /* add_buddies_with_invite */ +}; + +static void tgprpl_init(PurplePlugin *plugin) +{ + + PurpleAccountOption *option; + GList *verification_values = NULL; + + // Extra Options + #define ADD_VALUE(list, desc, v) { \ + PurpleKeyValuePair *kvp = g_new0(PurpleKeyValuePair, 1); \ + kvp->key = g_strdup((desc)); \ + kvp->value = g_strdup((v)); \ + list = g_list_prepend(list, kvp); \ + } + ADD_VALUE(verification_values, "Phone", TELEGRAM_AUTH_MODE_PHONE); + ADD_VALUE(verification_values, "SMS", TELEGRAM_AUTH_MODE_SMS); + + option = purple_account_option_list_new("Verification type", "verification_type", verification_values); + prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option); + + _telegram_protocol = plugin; +} + +static GList *tgprpl_actions(PurplePlugin * plugin, gpointer context) +{ + // return possible actions (See Libpurple doc) + return (GList *)NULL; +} + +static PurplePluginInfo plugin_info = { + PURPLE_PLUGIN_MAGIC, + PURPLE_MAJOR_VERSION, + PURPLE_MINOR_VERSION, + PURPLE_PLUGIN_PROTOCOL, + NULL, + 0, + NULL, + PURPLE_PRIORITY_DEFAULT, + PLUGIN_ID, + "Telegram", + TG_VERSION, + "Telegram", + TG_DESCRIPTION, + TG_AUTHOR, + "https://github.com/majn/telegram-purple", + NULL, // on load + NULL, // on unload + NULL, // on destroy + NULL, // ui specific struct + &prpl_info, // plugin info struct + NULL, // prefs info + tgprpl_actions, // actions + NULL, // reserved + NULL, // reserved + NULL, // reserved + NULL // reserved +}; + + +PURPLE_INIT_PLUGIN(telegram, tgprpl_init, plugin_info) diff --git a/telegram-purple.h b/telegram-purple.h new file mode 100644 index 0000000..6c12a8d --- /dev/null +++ b/telegram-purple.h @@ -0,0 +1,70 @@ +/** + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA + */ + +#ifndef __TG_PURPLE_H__ +#define __TG_PURPLE_H__ + +#define PLUGIN_ID "prpl-telegram" +#define TELEGRAM_AUTH_MODE_PHONE "phone" +#define TELEGRAM_AUTH_MODE_SMS "sms" +#define TG_AUTHOR "Christopher Althaus , Markus Endres , Matthias Jentsch . Based on telegram-cli by Vitaly Valtman." +#define TG_DESCRIPTION "Adds support for Telegram." +#define TG_VERSION "0.3.3" +#define TG_BUILD "8" + +#include +#include +#include +#include +#include +#include + +typedef struct { + struct tgl_state *TLS; + + /* + * Used during login + */ + char *hash; + + PurpleAccount *pa; + PurpleConnection *gc; + + /** + * Whether the state of the protocol has changed since the last save + */ + int updated; + + /** + * Queue of all new messages that need to be added to a chat + */ + GQueue *new_messages; + + /** + * Queue of all joined chats + */ + GHashTable *joining_chats; + + guint timer; +} telegram_conn; + +struct download_desc { + int type; + void *data; +}; + +void telegram_on_ready (struct tgl_state *TLS); +#endif diff --git a/tg-server.pub b/tg-server.pub new file mode 100644 index 0000000..5e38bb0 --- /dev/null +++ b/tg-server.pub @@ -0,0 +1,8 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 +lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS +an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw +Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ +8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n +Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB +-----END RSA PUBLIC KEY----- diff --git a/tgp-net.c b/tgp-net.c new file mode 100644 index 0000000..92a9614 --- /dev/null +++ b/tgp-net.c @@ -0,0 +1,584 @@ +/* + This file is part of tgl-library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Copyright Vitaly Valtman 2013-2014 +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tgp-net.h" +//#include "include.h" +#include +#include +//#include "mtproto-client.h" +//#include "mtproto-common.h" +//#include "tree.h" +//#include "tools.h" + +#include +#include + +#include "telegram-purple.h" + +#ifndef POLLRDHUP +#define POLLRDHUP 0 +#endif + +//double get_utime (int clock_id); + +//extern struct mtproto_methods auth_methods; + +static void fail_connection (struct connection *c); + +#define PING_TIMEOUT 10 + +static void start_ping_timer (struct connection *c); +static int ping_alarm (gpointer arg) { + struct connection *c = arg; + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG + 2,"ping alarm\n"); + assert (c->state == conn_ready || c->state == conn_connecting); + if (tglt_get_double_time () - c->last_receive_time > 6 * PING_TIMEOUT) { + vlogprintf (E_WARNING, "fail connection: reason: ping timeout\n"); + c->state = conn_failed; + fail_connection (c); + return FALSE; + } else if (tglt_get_double_time () - c->last_receive_time > 3 * PING_TIMEOUT && c->state == conn_ready) { + tgl_do_send_ping (c->TLS, c); + return TRUE; + } else { + return TRUE; + } +} + +static void stop_ping_timer (struct connection *c) { + purple_timeout_remove (c->ping_ev); +} + +static void start_ping_timer (struct connection *c) { + c->ping_ev = purple_timeout_add_seconds (PING_TIMEOUT, ping_alarm, c); +} + +static void restart_connection (struct connection *c); + +static int fail_alarm (gpointer arg) { + struct connection *c = arg; + c->in_fail_timer = 0; + restart_connection (c); + return FALSE; +} + +static void start_fail_timer (struct connection *c) { + if (c->in_fail_timer) { return; } + c->in_fail_timer = 1; + + c->fail_ev = purple_timeout_add_seconds (10, fail_alarm, c); +} + +static struct connection_buffer *new_connection_buffer (int size) { + struct connection_buffer *b = malloc (sizeof (*b)); + memset (b, 0, sizeof (*b)); + b->start = malloc (size); + b->end = b->start + size; + b->rptr = b->wptr = b->start; + return b; +} + +static void delete_connection_buffer (struct connection_buffer *b) { + free (b->start); + free (b); +} + +static void conn_try_write (gpointer arg, gint source, PurpleInputCondition cond); +int tgln_write_out (struct connection *c, const void *_data, int len) { + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG, "write_out: %d bytes\n", len); + const unsigned char *data = _data; + if (!len) { return 0; } + assert (len > 0); + int x = 0; + if (!c->out_bytes) { + assert (c->write_ev == -1); + c->write_ev = purple_input_add (c->fd, PURPLE_INPUT_WRITE, conn_try_write, c); + } + if (!c->out_head) { + struct connection_buffer *b = new_connection_buffer (1 << 20); + c->out_head = c->out_tail = b; + } + while (len) { + if (c->out_tail->end - c->out_tail->wptr >= len) { + memcpy (c->out_tail->wptr, data, len); + c->out_tail->wptr += len; + c->out_bytes += len; + return x + len; + } else { + int y = c->out_tail->end - c->out_tail->wptr; + assert (y < len); + memcpy (c->out_tail->wptr, data, y); + x += y; + len -= y; + data += y; + struct connection_buffer *b = new_connection_buffer (1 << 20); + c->out_tail->next = b; + b->next = 0; + c->out_tail = b; + c->out_bytes += y; + } + } + return x; +} + +int tgln_read_in (struct connection *c, void *_data, int len) { + unsigned char *data = _data; + if (!len) { return 0; } + assert (len > 0); + if (len > c->in_bytes) { + len = c->in_bytes; + } + int x = 0; + while (len) { + int y = c->in_head->wptr - c->in_head->rptr; + if (y > len) { + memcpy (data, c->in_head->rptr, len); + c->in_head->rptr += len; + c->in_bytes -= len; + return x + len; + } else { + memcpy (data, c->in_head->rptr, y); + c->in_bytes -= y; + x += y; + data += y; + len -= y; + void *old = c->in_head; + c->in_head = c->in_head->next; + if (!c->in_head) { + c->in_tail = 0; + } + delete_connection_buffer (old); + } + } + return x; +} + +int tgln_read_in_lookup (struct connection *c, void *_data, int len) { + unsigned char *data = _data; + if (!len || !c->in_bytes) { return 0; } + assert (len > 0); + if (len > c->in_bytes) { + len = c->in_bytes; + } + int x = 0; + struct connection_buffer *b = c->in_head; + while (len) { + int y = b->wptr - b->rptr; + if (y >= len) { + memcpy (data, b->rptr, len); + return x + len; + } else { + memcpy (data, b->rptr, y); + x += y; + data += y; + len -= y; + b = b->next; + } + } + return x; +} + +void tgln_flush_out (struct connection *c) { +} + +//#define MAX_CONNECTIONS 100 +//static struct connection *Connections[MAX_CONNECTIONS]; +//static int max_connection_fd; + +static void rotate_port (struct connection *c) { + switch (c->port) { + case 443: + c->port = 80; + break; + case 80: + c->port = 25; + break; + case 25: + c->port = 443; + break; + } +} + +static void try_read (struct connection *c); +static void try_write (struct connection *c); + +static void conn_try_read (gpointer arg, gint source, PurpleInputCondition cond) { + struct connection *c = arg; + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG + 1, "Try read. Fd = %d\n", c->fd); + try_read (c); +} + +static void conn_try_write (gpointer arg, gint source, PurpleInputCondition cond) { + struct connection *c = arg; + struct tgl_state *TLS = c->TLS; + if (c->state == conn_connecting) { + c->state = conn_ready; + c->methods->ready (TLS, c); + } + try_write (c); + if (!c->out_bytes) { + purple_input_remove (c->write_ev); + c->write_ev = -1; + } +} + +static void net_on_connected (gpointer arg, gint fd, const gchar *error_message) { + struct connection *c = arg; + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG - 2, "connect result: %d\n", fd); + + if (fd == -1) { + fail_connection (c); + return; + } + + c->fd = fd; + c->read_ev = purple_input_add (fd, PURPLE_INPUT_READ, conn_try_read, c); + + char byte = 0xef; + assert (tgln_write_out (c, &byte, 1) == 1); + + c->last_receive_time = tglt_get_double_time (); + start_ping_timer (c); +} + +struct connection *tgln_create_connection (struct tgl_state *TLS, const char *host, int port, struct tgl_session *session, struct tgl_dc *dc, struct mtproto_methods *methods) { + struct connection *c = malloc (sizeof (*c)); + memset (c, 0, sizeof (*c)); + c->TLS = TLS; + + + c->fd = -1; + c->state = conn_connecting; + + c->last_receive_time = tglt_get_double_time (); + c->ip = strdup (host); + c->flags = 0; + c->port = port; + + c->ping_ev = -1; + c->fail_ev = -1; + c->write_ev = -1; + c->read_ev = -1; + + c->dc = dc; + c->session = session; + c->methods = methods; + + telegram_conn *conn = TLS->ev_base; + c->prpl_data = purple_proxy_connect (conn->gc, conn->pa, host, port, net_on_connected, c); + + return c; +} + +static void restart_connection (struct connection *c) { + struct tgl_state *TLS = c->TLS; + if (c->last_connect_time == time (0)) { + start_fail_timer (c); + return; + } + + telegram_conn *conn = TLS->ev_base; + c->prpl_data = purple_proxy_connect (conn->gc, conn->pa, c->ip, c->port, net_on_connected, c); +} + +static void fail_connection (struct connection *c) { + struct tgl_state *TLS = c->TLS; + if (c->state == conn_ready) { + stop_ping_timer (c); + } + if (c->write_ev >= 0) { + purple_input_remove (c->write_ev); + c->write_ev = -1; + } + if (c->read_ev >= 0) { + purple_input_remove (c->write_ev); + c->read_ev = -1; + } + + rotate_port (c); + + struct connection_buffer *b = c->out_head; + while (b) { + struct connection_buffer *d = b; + b = b->next; + delete_connection_buffer (d); + } + b = c->in_head; + while (b) { + struct connection_buffer *d = b; + b = b->next; + delete_connection_buffer (d); + } + c->out_head = c->out_tail = c->in_head = c->in_tail = 0; + c->state = conn_failed; + c->out_bytes = c->in_bytes = 0; + + if (c->state == conn_ready) { + telegram_conn *conn = TLS->ev_base; + purple_connection_error_reason(conn->gc, PURPLE_CONNECTION_ERROR_OTHER_ERROR, "connection fail"); + } + c->prpl_data = NULL; // Did not find any destroy code. What should be done here? + + vlogprintf (E_NOTICE, "Lost connection to server... %s:%d\n", c->ip, c->port); + restart_connection (c); +} + +//extern FILE *log_net_f; +static void try_write (struct connection *c) { + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG, "try write: fd = %d\n", c->fd); + int x = 0; + while (c->out_head) { + int r = write (c->fd, c->out_head->rptr, c->out_head->wptr - c->out_head->rptr); + if (r >= 0) { + x += r; + c->out_head->rptr += r; + if (c->out_head->rptr != c->out_head->wptr) { + break; + } + struct connection_buffer *b = c->out_head; + c->out_head = b->next; + if (!c->out_head) { + c->out_tail = 0; + } + delete_connection_buffer (b); + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + vlogprintf (E_NOTICE, "fail_connection: write_error %m\n"); + fail_connection (c); + return; + } else { + break; + } + } + } + vlogprintf (E_DEBUG, "Sent %d bytes to %d\n", x, c->fd); + c->out_bytes -= x; +} + +static void try_rpc_read (struct connection *c) { + assert (c->in_head); + struct tgl_state *TLS = c->TLS; + + while (1) { + if (c->in_bytes < 1) { return; } + unsigned len = 0; + unsigned t = 0; + assert (tgln_read_in_lookup (c, &len, 1) == 1); + if (len >= 1 && len <= 0x7e) { + if (c->in_bytes < (int)(1 + 4 * len)) { return; } + } else { + if (c->in_bytes < 4) { return; } + assert (tgln_read_in_lookup (c, &len, 4) == 4); + len = (len >> 8); + if (c->in_bytes < (int)(4 + 4 * len)) { return; } + len = 0x7f; + } + + if (len >= 1 && len <= 0x7e) { + assert (tgln_read_in (c, &t, 1) == 1); + assert (t == len); + assert (len >= 1); + } else { + assert (len == 0x7f); + assert (tgln_read_in (c, &len, 4) == 4); + len = (len >> 8); + assert (len >= 1); + } + len *= 4; + int op; + assert (tgln_read_in_lookup (c, &op, 4) == 4); + c->methods->execute (TLS, c, op, len); + } +} + +static void try_read (struct connection *c) { + struct tgl_state *TLS = c->TLS; + vlogprintf (E_DEBUG, "try read: fd = %d\n", c->fd); + if (!c->in_tail) { + c->in_head = c->in_tail = new_connection_buffer (1 << 20); + } + #ifdef EVENT_V1 + struct timeval tv = {5, 0}; + event_add (c->read_ev, &tv); + #endif + int x = 0; + while (1) { + int r = read (c->fd, c->in_tail->wptr, c->in_tail->end - c->in_tail->wptr); + if (r > 0) { + c->last_receive_time = tglt_get_double_time (); + stop_ping_timer (c); + start_ping_timer (c); + } + if (r >= 0) { + c->in_tail->wptr += r; + x += r; + if (c->in_tail->wptr != c->in_tail->end) { + break; + } + struct connection_buffer *b = new_connection_buffer (1 << 20); + c->in_tail->next = b; + c->in_tail = b; + } else { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + vlogprintf (E_NOTICE, "fail_connection: read_error %m\n"); + fail_connection (c); + return; + } else { + break; + } + } + } + vlogprintf (E_DEBUG, "Received %d bytes from %d\n", x, c->fd); + c->in_bytes += x; + if (x) { + try_rpc_read (c); + } +} +/* +int tgl_connections_make_poll_array (struct pollfd *fds, int max) { + int _max = max; + int i; + for (i = 0; i <= max_connection_fd; i++) { + if (Connections[i] && Connections[i]->state == conn_failed) { + restart_connection (Connections[i]); + } + if (Connections[i] && Connections[i]->state != conn_failed) { + assert (max > 0); + struct connection *c = Connections[i]; + fds[0].fd = c->fd; + fds[0].events = POLLERR | POLLHUP | POLLRDHUP | POLLIN; + if (c->out_bytes || c->state == conn_connecting) { + fds[0].events |= POLLOUT; + } + fds ++; + max --; + } + } + return _max - max; +} + +void tgl_connections_poll_result (struct pollfd *fds, int max) { + int i; + for (i = 0; i < max; i++) { + struct connection *c = Connections[fds[i].fd]; + if (fds[i].revents & POLLIN) { + try_read (c); + } + if (fds[i].revents & (POLLHUP | POLLERR | POLLRDHUP)) { + vlogprintf (E_NOTICE, "fail_connection: events_mask=0x%08x\n", fds[i].revents); + fail_connection (c); + } else if (fds[i].revents & POLLOUT) { + if (c->state == conn_connecting) { + vlogprintf (E_DEBUG, "connection ready\n"); + c->state = conn_ready; + c->last_receive_time = tglt_get_double_time (); + } + if (c->out_bytes) { + try_write (c); + } + } + } +}*/ + +static void incr_out_packet_num (struct connection *c) { + c->out_packet_num ++; +} + +static struct tgl_dc *get_dc (struct connection *c) { + return c->dc; +} + +static struct tgl_session *get_session (struct connection *c) { + return c->session; +} + +static void tgln_free (struct connection *c) { + if (c->ip) { free (c->ip); } + struct connection_buffer *b = c->out_head; + while (b) { + struct connection_buffer *d = b; + b = b->next; + delete_connection_buffer (d); + } + b = c->in_head; + while (b) { + struct connection_buffer *d = b; + b = b->next; + delete_connection_buffer (d); + } + + if (c->ping_ev >= 0) { + purple_timeout_remove (c->ping_ev); + c->ping_ev = -1; + } + if (c->fail_ev >= 0) { + purple_timeout_remove (c->fail_ev); + c->fail_ev = -1; + } + + if (c->read_ev >= 0) { + purple_input_remove (c->read_ev); + } + if (c->write_ev >= 0) { + purple_input_remove (c->write_ev); + } + + c->fd = -1; +} + +struct tgl_net_methods tgp_conn_methods = { + .write_out = tgln_write_out, + .read_in = tgln_read_in, + .read_in_lookup = tgln_read_in_lookup, + .flush_out = tgln_flush_out, + .incr_out_packet_num = incr_out_packet_num, + .get_dc = get_dc, + .get_session = get_session, + .create_connection = tgln_create_connection, + .free = tgln_free +}; diff --git a/tgp-net.h b/tgp-net.h new file mode 100644 index 0000000..258a045 --- /dev/null +++ b/tgp-net.h @@ -0,0 +1,88 @@ +/* + This file is part of tgl-library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Copyright Vitaly Valtman 2013-2014 +*/ +#ifndef __NET_H__ +#define __NET_H__ + +struct connection_buffer { + unsigned char *start; + unsigned char *end; + unsigned char *rptr; + unsigned char *wptr; + struct connection_buffer *next; +}; + +enum conn_state { + conn_none, + conn_connecting, + conn_ready, + conn_failed, + conn_stopped +}; + +struct connection { + int fd; + char *ip; + int port; + int flags; + enum conn_state state; + int ipv6[4]; + struct connection_buffer *in_head; + struct connection_buffer *in_tail; + struct connection_buffer *out_head; + struct connection_buffer *out_tail; + int in_bytes; + int out_bytes; + int packet_num; + int out_packet_num; + int last_connect_time; + int in_fail_timer; + struct mtproto_methods *methods; + struct tgl_state *TLS; + struct tgl_session *session; + struct tgl_dc *dc; + void *extra; + int ping_ev; + int fail_ev; + int read_ev; + int write_ev; + double last_receive_time; + void *prpl_data; +}; + +//extern struct connection *Connections[]; + +int tgln_write_out (struct connection *c, const void *data, int len); +void tgln_flush_out (struct connection *c); +int tgln_read_in (struct connection *c, void *data, int len); +int tgln_read_in_lookup (struct connection *c, void *data, int len); + +//void tgln_insert_msg_id (struct tgl_session *S, long long id); + +extern struct tgl_net_methods tgp_conn_methods; + +//void create_all_outbound_connections (void); + +//struct connection *create_connection (const char *host, int port, struct tgl_session *session, struct connection_methods *methods); +//struct tgl_dc *tgln_alloc_dc (int id, char *ip, int port); +//void tgln_dc_create_session (struct tgl_dc *DC, struct mtproto_methods *methods); +struct connection *tgln_create_connection (struct tgl_state *TLS, const char *host, int port, struct tgl_session *session, struct tgl_dc *dc, struct mtproto_methods *methods); + +#define GET_DC(c) (c->session->dc) +#endif diff --git a/tgp-timers.c b/tgp-timers.c new file mode 100644 index 0000000..eec0456 --- /dev/null +++ b/tgp-timers.c @@ -0,0 +1,75 @@ +/* + This file is part of tgl-library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Copyright Vitaly Valtman 2013-2014 +*/ +#include +#include +#include +#include + +struct tgl_timer { + struct tgl_state *TLS; + void (*cb)(struct tgl_state *, void *); + void *arg; + int fd; +}; + +static int timer_alarm (gpointer arg) { + struct tgl_timer *t = arg; + t->cb (t->TLS, t->arg); + return FALSE; +} + +static struct tgl_timer *tgl_timer_alloc (struct tgl_state *TLS, void (*cb)(struct tgl_state *TLS, void *arg), void *arg) { + struct tgl_timer *t = malloc (sizeof (*t)); + t->TLS = TLS; + t->cb = cb; + t->arg = arg; + t->fd = -1; + return t; +} + +static void tgl_timer_insert (struct tgl_timer *t, double p) { + if (p < 0) { p = 0; } + if (p < 1) { + t->fd = purple_timeout_add (1000 * p, timer_alarm, t); + } else { + t->fd = purple_timeout_add_seconds (p, timer_alarm, t); + } +} + +static void tgl_timer_delete (struct tgl_timer *t) { + if (t->fd >= 0) { + purple_timeout_remove (t->fd); + t->fd = -1; + } +} + +static void tgl_timer_free (struct tgl_timer *t) { + if (t->fd >= 0) { + tgl_timer_delete (t); + } + free (t); +} + +struct tgl_timer_methods tgp_timers = { + .alloc = tgl_timer_alloc, + .insert = tgl_timer_insert, + .delete = tgl_timer_delete, + .free = tgl_timer_free +}; diff --git a/tgp-timers.h b/tgp-timers.h new file mode 100644 index 0000000..6791ec1 --- /dev/null +++ b/tgp-timers.h @@ -0,0 +1,27 @@ +/* + This file is part of tgl-library + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + Copyright Vitaly Valtman 2013-2014 +*/ + +#ifndef __TGL_TIMERS_H__ +#define __TGL_TIMERS_H__ + +#include "tgl.h" +extern struct tgl_timer_methods tgp_timers; + +#endif