Commit 2fe5b7682df7d21389adc49fea367751c08152c4

Authored by matyapiro31
2 parents 566617d9 a081c151

Merge branch 'master' of http://altrepo.eu/git/purple-line

Conflicts:
	README.md
README.md
... ... @@ -5,43 +5,46 @@ libpurple (Pidgin, Finch) protocol plugin for [LINE](http://line.me/).
5 5  
6 6 ![Screenshot](http://i.imgur.com/By1yLXB.png)
7 7  
8   -Where are the binaries and packages?
9   -------------------------------------
  8 +Install via package manager (Ubuntu/Debian)
  9 +-------------------------------------------
10 10  
11   -I am not looking into "easy to install" options before I'm satisfied with the stability. I'd rather
12   -not have people who cannot figure out how to compile software by themselves be disappointed by an
13   -unstable plugin.
  11 +An APT repository is available for installing the plugin. The repository contains the purple-line
  12 +package itself, and the required Apache Thrift packages which are not properly packaged by either
  13 +distribution.
14 14  
15   -How to install
16   ---------------
  15 +* http://debian.altrepo.eu/ (main)
  16 +* http://debian.surlinter.net/ (mirror)
17 17  
18   -Make sure you have the required prerequisites:
  18 +For instructions for adding a custom repository on Ubuntu, see
  19 +[the Ubuntu wiki](https://help.ubuntu.com/community/Repositories/Ubuntu).
19 20  
20   -* libpurple - Library that provides the core functionality of Pidgin and other compatible clients.
21   - Probably available from your package manager
22   -* thrift / libthrift - Apache Thrift compiler and C++ library. May be available from your package
23   - manager.
  21 +For Debian, see [the Debian wiki](https://www.debian.org/releases/), or just add the following line
  22 +to your `sources.list` file:
24 23  
25   -To install the plugin system-wide, run:
  24 + deb http://debian.altrepo.eu/ stable main
26 25  
27   - make
28   - sudo make install
  26 +In order to validate package signatures you need to add
  27 +[this public key](http://debian.altrepo.eu/altrepo_eu.pub) to the APT key list. It can be done
  28 +using the following command:
29 29  
30   -The makefile supports a flag THRIFT_STATIC=true which causes it to download and build a version of
31   -Thrift and statically link it. This should be convenient for people using one of the numerous
32   -distributions that do not package Thrift.
  30 + wget -qO - http://debian.altrepo.eu/altrepo_eu.pub | sudo apt-key add -
33 31  
34   -You can also install the plugin for your user only by replacing `install` with `user-install`.
  32 +On either distribution, after adding the repository and key, run the following commands to install
  33 +the plugin:
  34 +
  35 + sudo apt-get update
  36 + sudo apt-get install purple-line
35 37  
36   -How to install (Arch Linux)
37   ----------------------------
  38 +Install from source (Arch Linux)
  39 +--------------------------------
38 40  
39 41 Arch Linux packages all the required dependencies, so you can install the plugin by simply typing:
40 42  
41   - sudo pacman -S thrift libpurple
  43 + sudo pacman -S thrift libpurple libgcrypt
42 44 make
43 45 sudo make install
44 46  
  47 +<<<<<<< HEAD
45 48 How to install (Ubuntu/Debian)
46 49 -----------------------
47 50  
... ... @@ -59,9 +62,32 @@ To install purple-line, just type command:
59 62 sudo apt-get install purple-line
60 63  
61 64 after you added and update your repository.
  65 +=======
  66 +Install from source (any distribution)
  67 +--------------------------------------
  68 +
  69 +Make sure you have the required prerequisites installed:
  70 +
  71 +* libpurple - Library that provides the core functionality of Pidgin and other compatible clients.
  72 + Probably available from your package manager.
  73 +* thrift - Apache Thrift compiler. May be available from your package manager.
  74 +* libthrift - Apache Thrift C++ library. May be available from your package manager.
  75 +* libgcrypt - Crypto library. Probably available from your package manager.
  76 +
  77 +To install the plugin system-wide, run:
  78 +
  79 + make
  80 + sudo make install
  81 +
  82 +The makefile supports a flag THRIFT_STATIC=true which causes it to download and build a version of
  83 +Thrift and statically link it. This should be convenient for people using one of the numerous
  84 +distributions that do not package Thrift.
  85 +
  86 +You can also install the plugin for your user only by replacing `install` with `user-install`.
  87 +>>>>>>> a081c15148e8ae0d8149dadd8aad85b1c266362c
62 88  
63   -Implemented
64   ------------
  89 +Features implemented
  90 +--------------------
65 91  
66 92 * Logging in
67 93 * Authentication
... ... @@ -93,8 +119,8 @@ Implemented
93 119 * Audio (send/receive)
94 120 * Location (receive)
95 121  
96   -To do
97   ------
  122 +Features not yet implemented
  123 +----------------------------
98 124  
99 125 * Only fetch unseen messages, let a log plugin handle already seen messages
100 126 * Implement timeouts for faster reconnections
... ...
libpurple/Makefile
... ... @@ -14,9 +14,11 @@ endif
14 14 CXX ?= g++
15 15 CXXFLAGS = -g -Wall -shared -fPIC \
16 16 -DHAVE_INTTYPES_H -DHAVE_CONFIG_H -DPURPLE_PLUGINS \
17   - `pkg-config --cflags purple` $(THRIFT_CXXFLAGS)
  17 + `pkg-config --cflags purple` `libgcrypt-config --cflags` `gpg-error-config --cflags` \
  18 + $(THRIFT_CXXFLAGS)
18 19  
19   -LIBS = `pkg-config --libs purple` $(THRIFT_LIBS)
  20 +LIBS = `pkg-config --libs purple` `libgcrypt-config --libs` `gpg-error-config --libs` \
  21 + $(THRIFT_LIBS)
20 22  
21 23 PURPLE_PLUGIN_DIR:=$(shell pkg-config --variable=plugindir purple)
22 24 PURPLE_DATA_ROOT_DIR:=$(shell pkg-config --variable=datarootdir purple)
... ...
libpurple/line.thrift
... ... @@ -248,6 +248,13 @@ struct Room {
248 248 10: list<Contact> contacts;
249 249 }
250 250  
  251 +struct RSAKey {
  252 + 1: string keynm;
  253 + 2: string nvalue;
  254 + 3: string evalue;
  255 + 4: string sessionKey;
  256 +}
  257 +
251 258 exception TalkException {
252 259 1: ErrorCode code;
253 260 2: string reason;
... ... @@ -298,6 +305,9 @@ service TalkService {
298 305 Room getRoom(
299 306 2: string roomId) throws(1: TalkException e);
300 307  
  308 + RSAKey getRSAKeyInfo(
  309 + 2: IdentityProvider provider) throws(1: TalkException e);
  310 +
301 311 LoginResult loginWithIdentityCredentialForCertificate(
302 312 8: IdentityProvider identityProvider,
303 313 3: string identifier,
... ...
libpurple/purpleline.hpp
... ... @@ -166,6 +166,7 @@ private:
166 166 void login_start();
167 167  
168 168 void get_auth_token();
  169 + std::string get_encrypted_credentials(line::RSAKey &key);
169 170 void set_auth_token(std::string auth_token);
170 171 void get_last_op_revision();
171 172 void get_profile();
... ...
libpurple/purpleline_login.cpp
1 1 #include "purpleline.hpp"
2 2  
  3 +#include <sstream>
  4 +#include <iomanip>
  5 +
3 6 #include <core.h>
4 7  
  8 +#include <gcrypt.h>
  9 +
  10 +static std::string hex_to_bytes(std::string hex) {
  11 + if (hex.size() % 2 != 0)
  12 + hex = std::string("0") + hex;
  13 +
  14 + std::string result(hex.size() / 2, '\0');
  15 + for (size_t i = 0; i < result.size(); i++)
  16 + result[i] = std::stoi(hex.substr(i * 2, 2), nullptr, 16);
  17 +
  18 + return result;
  19 +}
  20 +
  21 +static std::string bytes_to_hex(std::string bytes) {
  22 + std::ostringstream ss;
  23 +
  24 + for (size_t i = 0; i < bytes.size(); i++)
  25 + ss << std::hex << std::setfill('0') << std::setw(2) << (unsigned)(bytes[i] & 0xff);
  26 +
  27 + return ss.str();
  28 +}
  29 +
5 30 void PurpleLine::login_start() {
6 31 purple_connection_set_state(conn, PURPLE_CONNECTING);
7 32 purple_connection_update_progress(conn, "Logging in", 0, 3);
... ... @@ -51,33 +76,21 @@ void PurpleLine::login_start() {
51 76 }
52 77  
53 78 void PurpleLine::get_auth_token() {
54   - std::string certificate(purple_account_get_string(acct, LINE_ACCOUNT_CERTIFICATE, ""));
55   -
56 79 purple_debug_info("line", "Logging in with credentials to get new auth token.\n");
57 80  
58   - std::string ui_name = "purple-line";
59   -
60   - GHashTable *ui_info = purple_core_get_ui_info();
61   - gpointer ui_name_p = g_hash_table_lookup(ui_info, "name");
62   - if (ui_name_p)
63   - ui_name = (char *)ui_name_p;
64   -
65   - c_out->send_loginWithIdentityCredentialForCertificate(
66   - line::IdentityProvider::LINE,
67   - purple_account_get_username(acct),
68   - purple_account_get_password(acct),
69   - true,
70   - "127.0.0.1",
71   - ui_name,
72   - certificate);
  81 + c_out->send_getRSAKeyInfo(line::IdentityProvider::LINE);
73 82 c_out->send([this]() {
74   - line::LoginResult result;
  83 + line::RSAKey key;
  84 + std::string credentials;
75 85  
76 86 try {
77   - c_out->recv_loginWithIdentityCredentialForCertificate(result);
78   - } catch (line::TalkException &err) {
79   - std::string msg = "Could not log in. " + err.reason;
  87 + c_out->recv_getRSAKeyInfo(key);
  88 +
  89 + credentials = get_encrypted_credentials(key);
  90 + } catch (std::exception &ex) {
  91 + std::string msg = std::string("Could not log in. ") + ex.what();
80 92  
  93 + conn->wants_to_die = TRUE;
81 94 purple_connection_error(
82 95 conn,
83 96 msg.c_str());
... ... @@ -85,42 +98,160 @@ void PurpleLine::get_auth_token() {
85 98 return;
86 99 }
87 100  
88   - if (result.type == line::LoginResultType::SUCCESS && result.authToken != "")
89   - {
90   - set_auth_token(result.authToken);
  101 + std::string certificate(purple_account_get_string(acct, LINE_ACCOUNT_CERTIFICATE, ""));
91 102  
92   - get_last_op_revision();
93   - }
94   - else if (result.type == line::LoginResultType::REQUIRE_DEVICE_CONFIRM)
95   - {
96   - purple_debug_info("line", "Starting PIN verification.\n");
  103 + std::string ui_name = "purple-line";
97 104  
98   - pin_verifier.verify(result, [this](std::string auth_token, std::string certificate) {
99   - if (certificate != "") {
100   - purple_account_set_string(
101   - acct,
102   - LINE_ACCOUNT_CERTIFICATE,
103   - certificate.c_str());
104   - }
  105 + GHashTable *ui_info = purple_core_get_ui_info();
  106 + gpointer ui_name_p = g_hash_table_lookup(ui_info, "name");
  107 + if (ui_name_p)
  108 + ui_name = (char *)ui_name_p;
  109 +
  110 + c_out->send_loginWithIdentityCredentialForCertificate(
  111 + line::IdentityProvider::LINE,
  112 + key.keynm,
  113 + credentials,
  114 + true,
  115 + "127.0.0.1",
  116 + ui_name,
  117 + certificate);
  118 + c_out->send([this]() {
  119 + line::LoginResult result;
  120 +
  121 + try {
  122 + c_out->recv_loginWithIdentityCredentialForCertificate(result);
  123 + } catch (line::TalkException &err) {
  124 + std::string msg = "Could not log in. " + err.reason;
105 125  
106   - set_auth_token(auth_token);
  126 + conn->wants_to_die = TRUE;
  127 + purple_connection_error(
  128 + conn,
  129 + msg.c_str());
  130 +
  131 + return;
  132 + }
  133 +
  134 + if (result.type == line::LoginResultType::SUCCESS && result.authToken != "")
  135 + {
  136 + set_auth_token(result.authToken);
107 137  
108 138 get_last_op_revision();
109   - });
110   - }
111   - else
112   - {
113   - std::stringstream ss("Could not log in. Bad LoginResult type: ");
114   - ss << result.type;
115   - std::string msg = ss.str();
  139 + }
  140 + else if (result.type == line::LoginResultType::REQUIRE_DEVICE_CONFIRM)
  141 + {
  142 + purple_debug_info("line", "Starting PIN verification.\n");
116 143  
117   - purple_connection_error(
118   - conn,
119   - msg.c_str());
120   - }
  144 + pin_verifier.verify(result, [this](std::string auth_token, std::string certificate) {
  145 + if (certificate != "") {
  146 + purple_account_set_string(
  147 + acct,
  148 + LINE_ACCOUNT_CERTIFICATE,
  149 + certificate.c_str());
  150 + }
  151 +
  152 + set_auth_token(auth_token);
  153 +
  154 + get_last_op_revision();
  155 + });
  156 + }
  157 + else
  158 + {
  159 + std::stringstream ss("Could not log in. Bad LoginResult type: ");
  160 + ss << result.type;
  161 + std::string msg = ss.str();
  162 +
  163 + purple_connection_error(
  164 + conn,
  165 + msg.c_str());
  166 + }
  167 + });
121 168 });
122 169 }
123 170  
  171 +// This may throw.
  172 +std::string PurpleLine::get_encrypted_credentials(line::RSAKey &key) {
  173 + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) {
  174 + if (!gcry_check_version(GCRYPT_VERSION))
  175 + throw new std::runtime_error("libgcrypt version mismatch.");
  176 +
  177 + gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
  178 +
  179 + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
  180 + }
  181 +
  182 + std::string username = purple_account_get_username(acct);
  183 + std::string password = purple_account_get_password(acct);
  184 +
  185 + if (username.size() > 255)
  186 + throw std::runtime_error("Username is too long.");
  187 +
  188 + if (password.size() > 255)
  189 + throw std::runtime_error("Password is too long.");
  190 +
  191 + std::ostringstream buf;
  192 +
  193 + buf << (char)key.sessionKey.size() << key.sessionKey;
  194 + buf << (char)username.size() << username;
  195 + buf << (char)password.size() << password;
  196 +
  197 + std::string modulus = hex_to_bytes(key.nvalue);
  198 + std::string exponent = hex_to_bytes(key.evalue);
  199 +
  200 + std::string credentials = buf.str();
  201 +
  202 + gpg_error_t err;
  203 +
  204 + gcry_sexp_t public_key, plaintext, ciphertext, result;
  205 +
  206 + err = gcry_sexp_build(
  207 + &public_key, nullptr,
  208 + "(public-key (rsa (n %b) (e %b)))",
  209 + (int)modulus.size(), modulus.c_str(),
  210 + (int)exponent.size(), exponent.c_str());
  211 +
  212 + if (err)
  213 + throw std::runtime_error(std::string("ligbcrypt public key error: ") + gpg_strerror(err));
  214 +
  215 + err = gcry_sexp_build(
  216 + &plaintext, nullptr,
  217 + "(data (flags pkcs1) (value %b))",
  218 + (int)credentials.size(), credentials.c_str());
  219 +
  220 + if (err) {
  221 + gcry_sexp_release(public_key);
  222 + throw std::runtime_error(std::string("ligbcrypt data error: ") + gpg_strerror(err));
  223 + }
  224 +
  225 + err = gcry_pk_encrypt(&ciphertext, plaintext, public_key);
  226 +
  227 + gcry_sexp_release(plaintext);
  228 + gcry_sexp_release(public_key);
  229 +
  230 + if (err)
  231 + throw std::runtime_error(std::string("libgcrypt encryption error: ") + gpg_strerror(err));
  232 +
  233 + result = gcry_sexp_find_token(ciphertext, "a", 0);
  234 +
  235 + gcry_sexp_release(ciphertext);
  236 +
  237 + if (!result)
  238 + throw std::runtime_error("libgcrypt result token not found");
  239 +
  240 + size_t result_data_len;
  241 + const char *result_data = gcry_sexp_nth_data(result, 1, &result_data_len);
  242 +
  243 + if (!result_data) {
  244 + gcry_sexp_release(result);
  245 + throw std::runtime_error("libgcrypt result token value not found");
  246 + }
  247 +
  248 + std::string result_bytes(result_data, result_data_len);
  249 +
  250 + gcry_sexp_release(result);
  251 +
  252 + return bytes_to_hex(result_bytes);
  253 +}
  254 +
124 255 void PurpleLine::set_auth_token(std::string auth_token) {
125 256 purple_account_set_string(acct, LINE_ACCOUNT_AUTH_TOKEN, auth_token.c_str());
126 257  
... ...