Branch data Line data Source code
1 : : // *****************************************************************************
2 : : /*!
3 : : \file src/cli_matrix_thread.cpp
4 : : \copyright 2022-2025 J. Bakosi,
5 : : All rights reserved. See the LICENSE file for details.
6 : : \brief Piac matrix client functionality
7 : : */
8 : : // *****************************************************************************
9 : :
10 : : #include <iostream>
11 : : #include <fstream>
12 : :
13 : : #include <nlohmann/json.hpp>
14 : :
15 : : #if defined(__clang__)
16 : : #pragma clang diagnostic push
17 : : #pragma clang diagnostic ignored "-Wpadded"
18 : : #pragma clang diagnostic ignored "-Wextra-semi"
19 : : #pragma clang diagnostic ignored "-Wweak-vtables"
20 : : #pragma clang diagnostic ignored "-Wold-style-cast"
21 : : #pragma clang diagnostic ignored "-Wc++98-compat-pedantic"
22 : : #pragma clang diagnostic ignored "-Wdocumentation-unknown-command"
23 : : #endif
24 : :
25 : : #include "mtxclient/http/client.hpp"
26 : : #include "mtxclient/crypto/client.hpp"
27 : : #include "mtx/responses/sync.hpp"
28 : : #include "mtx/responses/common.hpp"
29 : : #include "mtx/responses/create_room.hpp"
30 : : #include "mtx/events.hpp"
31 : : #include "mtx/events/encrypted.hpp"
32 : :
33 : : #if defined(__clang__)
34 : : #pragma clang diagnostic pop
35 : : #endif
36 : :
37 : : #include "cli_matrix_thread.hpp"
38 : : #include "logging_util.hpp"
39 : : #include "string_util.hpp"
40 : :
41 : : constexpr auto OLM_ALGO = "m.olm.v1.curve25519-aes-sha2";
42 : : static std::shared_ptr< mtx::http::Client> g_mtxclient = nullptr;
43 : : static std::shared_ptr< mtx::crypto::OlmClient> g_olmclient = nullptr;
44 : : static std::string g_mtx_account_db_filename;
45 : : static std::string g_mtx_session_db_filename;
46 : : static std::string g_mtx_db_storage_key;
47 : : static std::vector< zmqpp::socket > g_msg_mtx;
48 : :
49 : : uint16_t piac::g_matrix_sync_timeout = 10'000; // milliseconds
50 : : bool piac::g_matrix_connected = false;
51 : : bool piac::g_matrix_shutdown = false;
52 : : zmqpp::context piac::g_ctx_msg;
53 : :
54 [ - - ][ - - ]: 0 : struct OutboundSessionData {
[ - - ][ - - ]
[ - - ]
55 : : std::string session_id;
56 : : std::string session_key;
57 : : uint64_t message_index = 0;
58 : : };
59 : :
60 : : static inline void
61 : 0 : to_json( nlohmann::json& obj, const OutboundSessionData& msg )
62 : : // *****************************************************************************
63 : : //! Serialize OutboundSessionData to json format
64 : : //! \param[in,out] obj nlohmann::json object to write to
65 : : //! \param[in] msg OutboundSessionData message to read from
66 : : // *****************************************************************************
67 : : {
68 [ - - ]: 0 : obj[ "session_id" ] = msg.session_id;
69 [ - - ][ - - ]: 0 : obj[ "session_key" ] = msg.session_key;
70 [ - - ]: 0 : obj[ "message_index" ] = msg.message_index;
71 : 0 : }
72 : :
73 : : static inline void
74 : 0 : from_json( const nlohmann::json& obj, OutboundSessionData& msg )
75 : : // *****************************************************************************
76 : : //! Deserialize OutboundSessionData from json format
77 : : //! \param[in] obj nlohmann::json object to read from
78 : : //! \param[in,out] msg OutboundSessionData message to write to
79 : : // *****************************************************************************
80 : : {
81 [ - - ][ - - ]: 0 : msg.session_id = obj.at( "session_id" ).get< std::string >();
[ - - ]
82 [ - - ][ - - ]: 0 : msg.session_key = obj.at( "session_key" ).get< std::string >();
[ - - ]
83 [ - - ][ - - ]: 0 : msg.message_index = obj.at( "message_index" ).get< uint64_t >();
84 : 0 : }
85 : :
86 : 0 : struct OutboundSessionDataRef {
87 : : OlmOutboundGroupSession* session;
88 : : OutboundSessionData data;
89 : : };
90 : :
91 [ - - ][ - - ]: 5 : struct DevKeys {
92 : : std::string ed25519;
93 : : std::string curve25519;
94 : : };
95 : :
96 : : static inline void
97 : 5 : to_json( nlohmann::json& obj, const DevKeys& msg )
98 : : // *****************************************************************************
99 : : //! Serialize DevKeys to json format
100 : : //! \param[in,out] obj nlohmann::json object to write to
101 : : //! \param[in] msg DevKeys message to read from
102 : : // *****************************************************************************
103 : : {
104 [ + - ]: 5 : obj[ "ed25519" ] = msg.ed25519;
105 [ + - ]: 5 : obj[ "curve25519" ] = msg.curve25519;
106 : 5 : }
107 : :
108 : : static inline void
109 : 0 : from_json( const nlohmann::json& obj, DevKeys& msg )
110 : : // *****************************************************************************
111 : : //! Deserialize DevKeys from json format
112 : : //! \param[in] obj nlohmann::json object to read from
113 : : //! \param[in,out] msg DevKeys message to write to
114 : : // *****************************************************************************
115 : : {
116 [ - - ][ - - ]: 0 : msg.ed25519 = obj.at( "ed25519" ).get< std::string >();
[ - - ]
117 [ - - ][ - - ]: 0 : msg.curve25519 = obj.at( "curve25519" ).get< std::string >();
[ - - ]
118 : 0 : }
119 : :
120 : : #if defined(__clang__)
121 : : #pragma clang diagnostic push
122 : : #pragma clang diagnostic ignored "-Wpadded"
123 : : #endif
124 : :
125 [ - - ][ - - ]: 0 : struct OlmCipherContent {
126 : : std::string body;
127 : : uint8_t type;
128 : : };
129 : :
130 : : #if defined(__clang__)
131 : : #pragma clang diagnostic pop
132 : : #endif
133 : :
134 : 0 : inline void from_json(const nlohmann::json& obj, OlmCipherContent& msg )
135 : : // *****************************************************************************
136 : : //! Deserialize OlmCipherContent from json format
137 : : //! \param[in] obj nlohmann::json object to read from
138 : : //! \param[in,out] msg OlmCipherContent to write to
139 : : // *****************************************************************************
140 : : {
141 [ - - ][ - - ]: 0 : msg.body = obj.at("body").get< std::string >();
[ - - ]
142 [ - - ][ - - ]: 0 : msg.type = obj.at("type").get< uint8_t >();
143 : 0 : }
144 : :
145 : : struct OlmMessage {
146 : : std::string sender_key;
147 : : std::string sender;
148 : : using RecipientKey = std::string;
149 : : std::map< RecipientKey, OlmCipherContent > ciphertext;
150 : : };
151 : :
152 : 0 : inline void from_json( const nlohmann::json& obj, OlmMessage& msg )
153 : : // *****************************************************************************
154 : : //! Deserialize OlmMessage from json format
155 : : //! \param[in] obj nlohmann::json object to read from
156 : : //! \param[in,out] msg OlmMessage to write to
157 : : // *****************************************************************************
158 : : {
159 [ - - ][ - - ]: 0 : if (obj.at("type") != "m.room.encrypted") {
160 [ - - ]: 0 : throw std::invalid_argument( "invalid type for olm message" );
161 : : }
162 : :
163 [ - - ][ - - ]: 0 : if (obj.at("content").at("algorithm") != OLM_ALGO)
[ - - ][ - - ]
[ - - ][ - - ]
164 [ - - ]: 0 : throw std::invalid_argument("invalid algorithm for olm message");
165 : :
166 [ - - ][ - - ]: 0 : msg.sender = obj.at("sender").get< std::string >();
[ - - ]
167 [ - - ][ - - ]: 0 : msg.sender_key = obj.at("content").at("sender_key").get< std::string >();
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
168 [ - - ][ - - ]: 0 : msg.ciphertext = obj.at("content").at("ciphertext").
[ - - ][ - - ]
[ - - ]
169 [ - - ][ - - ]: 0 : get< std::map< std::string, OlmCipherContent > >();
170 : 0 : }
171 : :
172 : : struct Storage {
173 : :
174 : : using OlmSessionPtr = mtx::crypto::OlmSessionPtr;
175 : : using InboundGroupSessionPtr = mtx::crypto::InboundGroupSessionPtr;
176 : : using OutboundGroupSessionPtr = mtx::crypto::OutboundGroupSessionPtr;
177 : :
178 : : //! Storage for the user_id -> list of devices mapping
179 : : std::map< std::string, std::vector< std::string > > devices;
180 : : //! Storage for the identity key for a device.
181 : : std::map< std::string, DevKeys > device_keys;
182 : : //! Flag that indicate if a specific room has encryption enabled.
183 : : std::map< std::string, bool > encrypted_rooms;
184 : :
185 : : //! Keep track of members per room.
186 : : std::map< std::string, std::map< std::string, bool > > members;
187 : :
188 : 8 : void add_member( const std::string& room_id, const std::string& user_id ) {
189 : 8 : members[room_id][user_id] = true;
190 : 8 : }
191 : :
192 : : //! Mapping from curve25519 to session
193 : : std::map< std::string, OlmSessionPtr > olm_inbound_sessions;
194 : : std::map< std::string, OlmSessionPtr > olm_outbound_sessions;
195 : :
196 : : std::map< std::string, InboundGroupSessionPtr > inbound_group_sessions;
197 : : std::map< std::string, OutboundSessionData > outbound_group_session_data;
198 : : std::map< std::string, OutboundGroupSessionPtr > outbound_group_sessions;
199 : :
200 [ - - ]: 0 : bool outbound_group_exists( const std::string& room_id ) {
201 : : return
202 : : outbound_group_sessions.find( room_id ) !=
203 [ - - ][ - - ]: 0 : end( outbound_group_sessions ) &&
204 : : outbound_group_session_data.find( room_id ) !=
205 : 0 : end( outbound_group_session_data );
206 : : }
207 : :
208 : 0 : void set_outbound_group_session( const std::string& room_id,
209 : : OutboundGroupSessionPtr session,
210 : : OutboundSessionData data )
211 : : {
212 : 0 : outbound_group_session_data[ room_id ] = data;
213 : 0 : outbound_group_sessions[ room_id ] = std::move( session );
214 : 0 : }
215 : :
216 : : OutboundSessionDataRef
217 : 0 : get_outbound_group_session( const std::string& room_id ) {
218 : 0 : return { outbound_group_sessions[ room_id ].get(),
219 : 0 : outbound_group_session_data[ room_id ] };
220 : : }
221 : :
222 : 0 : bool inbound_group_exists( const std::string& room_id,
223 : : const std::string& session_id,
224 : : const std::string& sender_key )
225 : : {
226 [ - - ][ - - ]: 0 : const auto key = room_id + session_id + sender_key;
227 : 0 : return inbound_group_sessions.find( key ) != end(inbound_group_sessions);
228 : : }
229 : :
230 : 0 : void set_inbound_group_session( const std::string& room_id,
231 : : const std::string& session_id,
232 : : const std::string& sender_key,
233 : : InboundGroupSessionPtr session )
234 : : {
235 [ - - ]: 0 : const auto key = room_id + session_id + sender_key;
236 [ - - ]: 0 : inbound_group_sessions[ key ] = std::move( session );
237 : 0 : }
238 : :
239 : : OlmInboundGroupSession*
240 : 0 : get_inbound_group_session( const std::string& room_id,
241 : : const std::string& session_id,
242 : : const std::string& sender_key )
243 : : {
244 [ - - ]: 0 : const auto key = room_id + session_id + sender_key;
245 [ - - ]: 0 : return inbound_group_sessions[ key ].get();
246 : : }
247 : :
248 : 3 : void load() {
249 : : using namespace std;
250 : : using namespace mtx::crypto;
251 [ + - ][ + - ]: 6 : MDEBUG( "matrix session restoring storage" );
252 : :
253 : 5 : ifstream db( g_mtx_session_db_filename );
254 [ + + ]: 3 : if (not db.is_open()) {
255 [ + - ][ + - ]: 2 : MINFO( "couldn't open matrix session db " << g_mtx_session_db_filename );
[ + - ]
256 : 1 : return;
257 : : }
258 : : string db_data( (istreambuf_iterator< char >( db )),
259 : : istreambuf_iterator< char >() );
260 : :
261 [ - + ]: 2 : if (db_data.empty()) return;
262 : :
263 [ + - ][ - - ]: 2 : nlohmann::json obj = nlohmann::json::parse( db_data );
264 : :
265 [ + - ][ + - ]: 4 : devices = obj.at( "devices" ).get< map< string, vector< string > > >();
[ + - ][ - + ]
[ + - ]
266 [ + - ][ + - ]: 4 : device_keys = obj.at( "device_keys" ).get< map< string, DevKeys > >();
[ + - ][ - + ]
[ + - ]
267 [ + - ][ + - ]: 4 : encrypted_rooms = obj.at( "encrypted_rooms" ).get< map< string, bool> >();
[ + - ][ - + ]
[ + - ]
268 [ + - ][ + - ]: 4 : members = obj.at( "members" ).get< map< string, map< string, bool > > >();
[ + - ][ - + ]
[ + - ]
269 : :
270 [ - + ]: 2 : if (obj.count( "olm_inbound_sessions" ) != 0) {
271 [ - - ][ - - ]: 0 : auto sessions = obj.at( "olm_inbound_sessions" ).get<map<string, string>>();
[ - - ]
272 [ - - ]: 0 : for (const auto &s : sessions)
273 [ - - ]: 0 : olm_inbound_sessions[ s.first ] =
274 [ - - ]: 0 : unpickle< SessionObject >( s.second, g_mtx_db_storage_key );
275 : : }
276 : :
277 [ - + ]: 2 : if (obj.count( "olm_outbound_sessions" ) != 0) {
278 [ - - ][ - - ]: 0 : auto sessions = obj.at("olm_outbound_sessions").get<map<string,string>>();
[ - - ]
279 [ - - ]: 0 : for (const auto &s : sessions)
280 [ - - ]: 0 : olm_outbound_sessions[ s.first ] =
281 [ - - ]: 0 : unpickle< SessionObject >( s.second, g_mtx_db_storage_key );
282 : : }
283 : :
284 [ - + ]: 2 : if (obj.count( "inbound_group_sessions" ) != 0) {
285 [ - - ][ - - ]: 0 : auto sessions = obj.at("inbound_group_sessions").get<map<string, string>>();
[ - - ]
286 [ - - ]: 0 : for (const auto &s : sessions)
287 [ - - ]: 0 : inbound_group_sessions[ s.first ] =
288 [ - - ]: 0 : unpickle< InboundSessionObject >( s.second, g_mtx_db_storage_key );
289 : : }
290 : :
291 [ - + ]: 2 : if (obj.count( "outbound_group_sessions" ) != 0) {
292 [ - - ][ - - ]: 0 : auto sessions = obj.at("outbound_group_sessions").get<map<string, string>>();
[ - - ]
293 [ - - ]: 0 : for (const auto &s : sessions)
294 [ - - ]: 0 : outbound_group_sessions[ s.first ] =
295 [ - - ]: 0 : unpickle< OutboundSessionObject >( s.second, g_mtx_db_storage_key );
296 : : }
297 : :
298 [ - + ]: 2 : if (obj.count( "outbound_group_session_data" ) != 0) {
299 [ - - ][ - - ]: 0 : auto sessions = obj.at( "outbound_group_session_data" ).
300 [ - - ]: 0 : get<map<string,OutboundSessionData>>();
301 [ - - ]: 0 : for (const auto &s : sessions)
302 [ - - ]: 0 : outbound_group_session_data[s.first] = s.second;
303 : : }
304 : : }
305 : :
306 : 3 : void save() {
307 : : using namespace mtx::crypto;
308 [ + - ][ + - ]: 6 : MDEBUG( "matrix session saving storage" );
309 : :
310 : 6 : std::ofstream db( g_mtx_session_db_filename );
311 [ - + ]: 3 : if (not db.is_open()) {
312 [ - - ][ - - ]: 0 : MERROR( "Couldn't open matrix session db " << g_mtx_session_db_filename );
[ - - ]
313 : 0 : return;
314 : : }
315 : :
316 : : nlohmann::json data;
317 [ + - ][ + - ]: 6 : data[ "devices" ] = devices;
318 [ + - ][ + - ]: 6 : data[ "device_keys" ] = device_keys;
319 [ + - ][ + - ]: 6 : data[ "encrypted_rooms" ] = encrypted_rooms;
320 [ + - ]: 6 : data[ "members" ] = members;
321 : :
322 : : // Save inbound sessions
323 [ - + ]: 3 : for (const auto &s : olm_inbound_sessions)
324 [ - - ][ - - ]: 0 : data[ "olm_inbound_sessions" ][ s.first ] =
325 [ - - ][ - - ]: 0 : pickle< SessionObject >( s.second.get(), g_mtx_db_storage_key );
[ - - ]
326 : :
327 [ - + ]: 3 : for (const auto &s : olm_outbound_sessions)
328 [ - - ][ - - ]: 0 : data[ "olm_outbound_sessions" ][ s.first ] =
329 [ - - ][ - - ]: 0 : pickle< SessionObject >( s.second.get(), g_mtx_db_storage_key );
[ - - ]
330 : :
331 [ - + ]: 3 : for (const auto &s : inbound_group_sessions)
332 [ - - ][ - - ]: 0 : data[ "inbound_group_sessions" ][ s.first ] =
333 [ - - ][ - - ]: 0 : pickle< InboundSessionObject >( s.second.get(), g_mtx_db_storage_key );
[ - - ]
334 : :
335 [ - + ]: 3 : for (const auto &s : outbound_group_sessions)
336 [ - - ][ - - ]: 0 : data[ "outbound_group_sessions" ][ s.first ] =
337 [ - - ][ - - ]: 0 : pickle< OutboundSessionObject >( s.second.get(), g_mtx_db_storage_key );
[ - - ]
338 : :
339 [ - + ]: 3 : for (const auto &s : outbound_group_session_data)
340 [ - - ][ - - ]: 0 : data[ "outbound_group_session_data" ][ s.first ] = s.second;
[ - - ]
341 : :
342 : : // Save to file
343 [ + - ]: 3 : db << data.dump( 2 );
344 [ + - ]: 3 : db.close();
345 : : }
346 : : };
347 : :
348 : : static Storage g_storage;
349 : :
350 : : static void
351 : 1 : print_errors( mtx::http::RequestErr err )
352 : : // *****************************************************************************
353 : : //! Log matrix request errors
354 : : //! \param[in] err Matrix error object
355 : : // *****************************************************************************
356 : : {
357 [ + - ]: 1 : if (err->status_code)
358 [ + - ][ + - ]: 3 : MERROR( "status code: " << static_cast<uint16_t>(err->status_code));
[ + - ]
359 [ - + ]: 1 : if (not err->matrix_error.error.empty() )
360 [ - - ][ - - ]: 0 : MERROR( "error msg: " << err->matrix_error.error );
361 [ + - ]: 1 : if (err->error_code)
362 [ + - ][ + - ]: 3 : MERROR( "error code: " << err->error_code );
[ + - ]
363 [ + - ]: 1 : if (not err->parse_error.empty())
364 [ + - ][ + - ]: 2 : MERROR( "parse error: " << err->parse_error );
365 : 1 : }
366 : :
367 : : static void
368 : 0 : decrypt_olm_message( const OlmMessage& olm_msg )
369 : : // *****************************************************************************
370 : : //! Decrypt Olm message
371 : : //! \param[in] olm_msg Message to decrypt
372 : : // *****************************************************************************
373 : : {
374 [ - - ][ - - ]: 0 : MINFO( "OLM message" );
375 [ - - ][ - - ]: 0 : MINFO( "sender: " << olm_msg.sender );
376 [ - - ][ - - ]: 0 : MINFO( "sender_key: " << olm_msg.sender_key );
377 : :
378 : 0 : const auto my_id_key = g_olmclient->identity_keys().curve25519;
379 [ - - ]: 0 : for (const auto& cipher : olm_msg.ciphertext) {
380 [ - - ]: 0 : if (cipher.first == my_id_key) {
381 : : const auto msg_body = cipher.second.body;
382 : 0 : const auto msg_type = cipher.second.type;
383 : :
384 [ - - ][ - - ]: 0 : MINFO( "the message is meant for us" );
[ - - ]
385 [ - - ][ - - ]: 0 : MINFO( "body: " << msg_body );
[ - - ]
386 [ - - ][ - - ]: 0 : MINFO( "type: " << msg_type );
[ - - ]
387 : :
388 [ - - ]: 0 : if (msg_type == 0) {
389 [ - - ][ - - ]: 0 : MINFO( "opening session with " << olm_msg.sender );
[ - - ][ - - ]
390 [ - - ]: 0 : auto inbound_session = g_olmclient->create_inbound_session( msg_body );
391 : :
392 [ - - ]: 0 : auto ok = mtx::crypto::matches_inbound_session_from(
393 [ - - ]: 0 : inbound_session.get(), olm_msg.sender_key, msg_body );
394 : :
395 [ - - ]: 0 : if (not ok) {
396 [ - - ][ - - ]: 0 : MERROR( "session could not be established" );
[ - - ]
397 : : } else {
398 : : auto output = g_olmclient->decrypt_message( inbound_session.get(),
399 : : msg_type,
400 [ - - ]: 0 : msg_body );
401 : :
402 : : auto plaintext = nlohmann::json::parse(
403 [ - - ][ - - ]: 0 : std::string( begin(output), end(output) ) );
[ - - ][ - - ]
404 [ - - ][ - - ]: 0 : MINFO( "decrypted message: " << plaintext.dump(2) );
[ - - ][ - - ]
405 : :
406 : : g_storage.olm_inbound_sessions.emplace( olm_msg.sender_key,
407 : : std::move( inbound_session ) );
408 : :
409 : : std::string room_id =
410 [ - - ][ - - ]: 0 : plaintext.at( "content" ).at( "room_id" ).get< std::string >();
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
411 : : std::string session_id =
412 [ - - ][ - - ]: 0 : plaintext.at( "content ").at( "session_id" ).get <std::string >();
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
413 : : std::string session_key =
414 [ - - ][ - - ]: 0 : plaintext.at( "content" ).at( "session_key" ).get <std::string >();
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
415 : :
416 [ - - ][ - - ]: 0 : if (g_storage.inbound_group_exists( room_id, session_id,
417 : : olm_msg.sender_key ))
418 : : {
419 [ - - ][ - - ]: 0 : MWARNING( "megolm session already exists" );
[ - - ][ - - ]
420 : : } else {
421 : : auto megolm_session =
422 [ - - ]: 0 : g_olmclient->init_inbound_group_session( session_key );
423 [ - - ]: 0 : g_storage.set_inbound_group_session( room_id,
424 : : session_id,
425 : : olm_msg.sender_key,
426 : 0 : std::move( megolm_session ) );
427 [ - - ][ - - ]: 0 : MINFO( "megolm_session saved" );
[ - - ]
428 : : }
429 : : }
430 : : }
431 : : }
432 : : }
433 : 0 : }
434 : :
435 : : static void
436 [ - + ]: 16 : handle_to_device_msgs( const mtx::responses::ToDevice& msgs )
437 : : // *****************************************************************************
438 : : //! Handle to-device messages
439 : : //! \param[in] msgs Messages to handle
440 : : // *****************************************************************************
441 : : {
442 [ - + ]: 16 : if (not msgs.events.empty()) {
443 [ - - ][ - - ]: 0 : MINFO( "inspecting to_device messages " << msgs.events.size() );
444 : : }
445 : :
446 [ - + ]: 16 : for (const auto& msg : msgs.events) {
447 [ - - ][ - - ]: 0 : MINFO( std::visit(
[ - - ][ - - ]
448 : : [](const auto& e){ return nlohmann::json(e); }, msg ).dump( 2 ) );
449 : :
450 : : try {
451 : : OlmMessage olm_msg = std::visit(
452 [ - - ]: 0 : [](const auto &e){ return nlohmann::json(e).get< OlmMessage >(); }, msg
453 [ - - ]: 0 : );
454 [ - - ]: 0 : decrypt_olm_message( std::move( olm_msg ) );
455 [ - - ][ - ]: 0 : } catch ( const nlohmann::json::exception& e ) {
456 [ - - ][ - - ]: 0 : MWARNING( "parsing error for olm message: " << e.what() );
[ - - ]
457 : 0 : } catch ( const std::invalid_argument& e ) {
458 [ - - ][ - - ]: 0 : MWARNING( "validation error for olm message: " << e.what() );
[ - - ]
459 : : }
460 : : }
461 : 16 : }
462 : :
463 : : static void
464 [ - - ]: 0 : keys_uploaded_cb( const mtx::responses::UploadKeys&, mtx::http::RequestErr err )
465 : : // *****************************************************************************
466 : : // *****************************************************************************
467 : : {
468 [ - - ]: 0 : if (err) {
469 : 0 : print_errors( err );
470 : 0 : return;
471 : : }
472 : : g_olmclient->mark_keys_as_published();
473 [ - - ][ - - ]: 0 : MINFO( "keys uploaded" );
474 : : }
475 : :
476 : : template< class T >
477 : : static bool is_room_encryption( const T& event )
478 : : // *****************************************************************************
479 : : // *****************************************************************************
480 : : {
481 : : using namespace mtx::events;
482 : : using namespace mtx::events::state;
483 : : return std::holds_alternative< StateEvent< Encryption > >( event );
484 : : }
485 : :
486 : : template< class T >
487 : : static std::string
488 : : get_json( const T& event )
489 : : // *****************************************************************************
490 : : // *****************************************************************************
491 : : {
492 [ - - ][ - - ]: 0 : return std::visit( [](auto e){ return nlohmann::json(e).dump(2); }, event );
[ - - ][ - - ]
[ - - ][ - - ]
493 : : }
494 : :
495 : : static void
496 : 0 : mark_encrypted_room( const RoomId& id )
497 : : // *****************************************************************************
498 : : // *****************************************************************************
499 : : {
500 [ - - ][ - - ]: 0 : MINFO( "encryption is enabled for room: " << id.get() );
501 : 0 : g_storage.encrypted_rooms[ id.get() ] = true;
502 : 0 : }
503 : :
504 : : template<class T>
505 : : static bool
506 : : is_member_event( const T& event )
507 : : // *****************************************************************************
508 : : // *****************************************************************************
509 : : {
510 : : using namespace mtx::events;
511 : : using namespace mtx::events::state;
512 : : return std::holds_alternative< StateEvent< Member > >( event );
513 : : }
514 : :
515 : : static bool
516 : : is_encrypted( const mtx::events::collections::TimelineEvents& event )
517 : : // *****************************************************************************
518 : : // *****************************************************************************
519 : : {
520 : : using mtx::events::EncryptedEvent;
521 : : using mtx::events::msg::Encrypted;
522 : : return std::holds_alternative< EncryptedEvent< Encrypted > >( event );
523 : : }
524 : :
525 : : template<class Container, class Item>
526 : : static bool
527 : : exists( const Container& container, const Item& item )
528 : : // *****************************************************************************
529 : : // *****************************************************************************
530 : : {
531 : : return container.find(item) != end(container);
532 : : }
533 : :
534 : : static void
535 : 8 : save_device_keys( const mtx::responses::QueryKeys& res )
536 : : // *****************************************************************************
537 : : // *****************************************************************************
538 : : {
539 [ + + ]: 16 : for (const auto &entry : res.device_keys) {
540 : : const auto user_id = entry.first;
541 : :
542 [ + + ]: 8 : if (not exists( g_storage.devices, user_id ))
543 [ + - ][ + - ]: 12 : MINFO( "keys for " << user_id );
[ + - ][ - - ]
544 : :
545 : 8 : std::vector< std::string > device_list;
546 [ + + ]: 15 : for (const auto &device : entry.second) {
547 [ + - ]: 14 : const auto key_struct = device.second;
548 : :
549 : : const std::string device_id = key_struct.device_id;
550 [ + - ]: 7 : const std::string index = "curve25519:" + device_id;
551 : :
552 [ - + ][ - - ]: 7 : if (key_struct.keys.find(index) == end(key_struct.keys)) continue;
553 : :
554 [ + - ]: 7 : const auto key = key_struct.keys.at( index );
555 : :
556 [ + + ]: 7 : if (!exists(g_storage.device_keys, device_id)) {
557 [ + - ][ + - ]: 10 : MINFO( "save device id => key: " << device_id << " => " << key );
[ + - ]
558 [ + - ]: 5 : g_storage.device_keys[ device_id ] =
559 [ + - ][ + - ]: 10 : { key_struct.keys.at("ed25519:" + device_id),
[ - - ]
560 [ + - ][ + - ]: 15 : key_struct.keys.at("curve25519:" + device_id) };
[ + - ][ + - ]
[ - - ][ - - ]
561 : : }
562 : :
563 [ + - ]: 7 : device_list.push_back( device_id );
564 : : }
565 : :
566 [ + + ]: 8 : if (not exists( g_storage.devices, user_id )) {
567 [ + - ][ + - ]: 6 : g_storage.devices[ user_id ] = device_list;
568 : : }
569 : : }
570 : 8 : }
571 : :
572 : : static void
573 [ + - ]: 8 : get_device_keys( const UserId& user )
574 : : // *****************************************************************************
575 : : // *****************************************************************************
576 : : {
577 : : // Retrieve all devices keys
578 : 8 : mtx::requests::QueryKeys query;
579 [ + - ]: 8 : query.device_keys[ user.get() ] = {};
580 : :
581 [ + - ]: 8 : g_mtxclient->query_keys( query,
582 [ - + ]: 8 : [](const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) {
583 [ - + ]: 8 : if (err) {
584 : 0 : print_errors( err );
585 : 0 : return;
586 : : }
587 : :
588 [ + + ]: 16 : for (const auto& key : res.device_keys) {
589 : : const auto user_id = key.first;
590 : : const auto devices = key.second;
591 : :
592 [ + + ]: 15 : for (const auto &device : devices) {
593 : : const auto id = device.first;
594 [ + - ]: 14 : const auto data = device.second;
595 : :
596 : : try {
597 [ + - ]: 7 : auto ok = verify_identity_signature(
598 [ - + ][ - - ]: 7 : nlohmann::json( data ).get< mtx::crypto::DeviceKeys >(),
599 [ + + ][ - - ]: 7 : DeviceId( id ), UserId( user_id ) );
600 : :
601 [ - + ]: 7 : if (not ok) {
602 [ - - ][ - - ]: 0 : MWARNING( "signature could not be verified" );
[ - - ]
603 [ - - ][ - - ]: 0 : MWARNING( nlohmann::json( data ).dump( 2 ) );
[ - - ][ - - ]
604 : : }
605 [ - - ][ - - ]: 0 : } catch (const mtx::crypto::olm_exception &e) {
606 [ - - ][ - - ]: 0 : MWARNING( "exception: " << e.what() );
[ - - ]
607 : : }
608 : : }
609 : : }
610 : :
611 : 8 : save_device_keys( std::move(res) );
612 : 8 : } );
613 : 8 : }
614 : :
615 : : static void
616 : 0 : send_group_message( OlmOutboundGroupSession* session,
617 : : const std::string& session_id,
618 : : const std::string& room_id,
619 : : const std::string& msg )
620 : : // *****************************************************************************
621 : : // *****************************************************************************
622 : : {
623 : : // Create event payload
624 : : nlohmann::json doc{ {"type", "m.room.message"},
625 : : {"content", {{"type", "m.text"}, {"body", msg}}},
626 [ - - ][ - - ]: 0 : {"room_id", room_id} };
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
627 : :
628 [ - - ][ - - ]: 0 : auto payload = g_olmclient->encrypt_group_message( session, doc.dump() );
[ - - ]
629 : :
630 : : using namespace mtx::events;
631 : : using namespace mtx::identifiers;
632 : : using namespace mtx::http;
633 : :
634 : 0 : msg::Encrypted data;
635 [ - - ]: 0 : data.ciphertext = std::string( begin(payload), end(payload) );
636 [ - - ][ - - ]: 0 : data.sender_key = g_olmclient->identity_keys().curve25519;
637 : : data.session_id = session_id;
638 [ - - ]: 0 : data.device_id = g_mtxclient->device_id();
639 : : data.algorithm = OLM_ALGO;
640 : :
641 [ - - ]: 0 : g_mtxclient->send_room_message< msg::Encrypted >(
642 [ - - ]: 0 : room_id, data, [](const mtx::responses::EventId& res, RequestErr err) {
643 [ - - ]: 0 : if (err) {
644 : 0 : print_errors( err );
645 : 0 : return;
646 : : }
647 [ - - ][ - - ]: 0 : MINFO("message sent with event_id: " << res.event_id.to_string());
648 : 0 : } );
649 : 0 : }
650 : :
651 : : #if defined(__clang__)
652 : : #pragma clang diagnostic push
653 : : #pragma clang diagnostic ignored "-Wpadded"
654 : : #endif
655 : :
656 : : static void
657 : 0 : create_outbound_megolm_session( const std::string& room_id,
658 : : const std::string& reply_msg )
659 : : // *****************************************************************************
660 : : // *****************************************************************************
661 : : {
662 : : // Create an outbound session
663 : 0 : auto outbound_session = g_olmclient->init_outbound_group_session();
664 : :
665 [ - - ]: 0 : const auto session_id = mtx::crypto::session_id( outbound_session.get() );
666 [ - - ]: 0 : const auto session_key = mtx::crypto::session_key( outbound_session.get() );
667 : :
668 : 0 : mtx::events::DeviceEvent< mtx::events::msg::RoomKey > megolm_payload;
669 : : megolm_payload.content.algorithm = OLM_ALGO;
670 : : megolm_payload.content.room_id = room_id;
671 : : megolm_payload.content.session_id = session_id;
672 : : megolm_payload.content.session_key = session_key;
673 [ - - ]: 0 : megolm_payload.type = mtx::events::EventType::RoomKey;
674 : :
675 [ - - ]: 0 : if (g_storage.members.find(room_id) == end(g_storage.members)) {
676 [ - - ][ - - ]: 0 : MERROR( "no members found for room " << room_id );
[ - - ]
677 : 0 : return;
678 : : }
679 : :
680 [ - - ]: 0 : const auto members = g_storage.members[ room_id ];
681 : :
682 [ - - ]: 0 : for (const auto &member : members) {
683 [ - - ][ - - ]: 0 : const auto devices = g_storage.devices[ member.first ];
684 : :
685 : : // TODO: Figure out for which devices we don't have olm sessions.
686 [ - - ]: 0 : for (const auto &dev : devices) {
687 : : // TODO: check if we have downloaded the keys
688 [ - - ][ - - ]: 0 : const auto device_keys = g_storage.device_keys[ dev ];
689 : :
690 : : auto to_device_cb = [](mtx::http::RequestErr err) {
691 [ - - ]: 0 : if (err) {
692 : 0 : print_errors( err );
693 : : }
694 : : };
695 : :
696 [ - - ]: 0 : if (g_storage.olm_outbound_sessions.find(device_keys.curve25519) !=
697 : : end(g_storage.olm_outbound_sessions))
698 : : {
699 [ - - ][ - - ]: 0 : MINFO("found existing olm outbound session with device" << dev );
[ - - ]
700 : : auto olm_session =
701 [ - - ]: 0 : g_storage.olm_outbound_sessions[ device_keys.curve25519 ].get();
702 : :
703 : : auto device_msg =
704 : : g_olmclient->create_olm_encrypted_content( olm_session,
705 : : megolm_payload,
706 [ - - ]: 0 : UserId( member.first ),
707 : : device_keys.ed25519,
708 [ - - ][ - - ]: 0 : device_keys.curve25519 );
[ - - ]
709 : :
710 [ - - ][ - - ]: 0 : nlohmann::json body{{"messages", {{member, {{dev, device_msg}}}}}};
[ - - ][ - - ]
[ - - ][ - - ]
711 [ - - ][ - - ]: 0 : g_mtxclient->send_to_device("m.room.encrypted", body, to_device_cb);
[ - - ][ - - ]
712 : : // TODO: send message to device
713 : : } else {
714 [ - - ][ - - ]: 0 : MINFO( "claiming one time keys for device " << dev );
[ - - ]
715 : : using mtx::responses::ClaimKeys;
716 : : using mtx::http::RequestErr;
717 [ - - ][ - - ]: 0 : auto cb = [member = member.first, dev, megolm_payload, to_device_cb](
718 : : const ClaimKeys &res, RequestErr err) {
719 [ - - ]: 0 : if (err) {
720 : 0 : print_errors(err);
721 : 0 : return;
722 : : }
723 : :
724 [ - - ][ - - ]: 0 : MINFO( "claimed keys for member - dev: " << member << " - " << dev );
725 [ - - ][ - - ]: 0 : MINFO( "room_key " << nlohmann::json(megolm_payload).dump(4) );
[ - - ][ - - ]
726 : :
727 [ - - ][ - - ]: 0 : MWARNING( "signed one time keys" );
728 : 0 : auto retrieved_devices = res.one_time_keys.at( member );
729 [ - - ]: 0 : for (const auto &rd : retrieved_devices) {
730 [ - - ][ - - ]: 0 : MINFO( "devices: " << rd.first << " : \n " << rd.second.dump(2) );
[ - - ][ - - ]
731 : :
732 : : // TODO: Verify signatures
733 [ - - ][ - - ]: 0 : auto otk = rd.second.begin()->at("key");
[ - - ][ - - ]
[ - - ]
734 [ - - ]: 0 : auto id_key = g_storage.device_keys[dev].curve25519;
735 : :
736 : : auto session = g_olmclient->create_outbound_session( id_key,
737 [ - - ][ - - ]: 0 : otk.get< std::string >() );
[ - - ][ - - ]
738 : :
739 : : auto device_msg = g_olmclient->create_olm_encrypted_content(
740 : : session.get(),
741 [ - - ]: 0 : megolm_payload,
742 : 0 : UserId(member),
743 [ - - ]: 0 : g_storage.device_keys[dev].ed25519,
744 [ - - ][ - - ]: 0 : g_storage.device_keys[dev].curve25519 );
[ - - ][ - - ]
[ - - ]
745 : :
746 : : // TODO: saving should happen when the message is sent
747 [ - - ]: 0 : g_storage.olm_outbound_sessions[ id_key ] = std::move(session);
748 : :
749 [ - - ][ - - ]: 0 : nlohmann::json body{{"messages", {{member, {{dev, device_msg}}}}}};
[ - - ][ - - ]
[ - - ][ - - ]
750 : :
751 [ - - ][ - - ]: 0 : g_mtxclient->send_to_device("m.room.encrypted", body, to_device_cb);
[ - - ][ - - ]
752 : : }
753 [ - - ]: 0 : };
754 : :
755 : : mtx::requests::ClaimKeys claim_keys;
756 [ - - ][ - - ]: 0 : claim_keys.one_time_keys[ member.first ][ dev ] =
757 : : mtx::crypto::SIGNED_CURVE25519;
758 : :
759 : : // TODO: we should bulk request device keys here
760 [ - - ][ - - ]: 0 : g_mtxclient->claim_keys(claim_keys, cb);
[ - - ]
761 : : }
762 : : }
763 : : }
764 : :
765 [ - - ][ - - ]: 0 : MINFO( "waiting to send sendToDevice messages" );
[ - - ]
766 [ - - ]: 0 : std::this_thread::sleep_for( std::chrono::milliseconds(2000) );
767 [ - - ][ - - ]: 0 : MINFO( "sending encrypted group message" );
[ - - ]
768 : :
769 : : // TODO: This should be done after all sendToDevice messages have been sent.
770 [ - - ]: 0 : send_group_message( outbound_session.get(), session_id, room_id, reply_msg );
771 : :
772 : : // TODO: save message index also.
773 [ - - ]: 0 : g_storage.set_outbound_group_session(
774 : 0 : room_id, std::move(outbound_session), {session_id, session_key} );
775 : : }
776 : :
777 : : #if defined(__clang__)
778 : : #pragma clang diagnostic pop
779 : : #endif
780 : :
781 : : static void
782 : 1 : invite_room( const std::string& src_user,
783 : : const std::string& target_user,
784 : : const std::string& ad )
785 : : // *****************************************************************************
786 : : // Invite user to a room
787 : : // *****************************************************************************
788 : : {
789 [ + - ][ + - ]: 2 : MINFO( src_user << " invites " << target_user << " to room" );
790 : :
791 : 1 : mtx::requests::CreateRoom req;
792 [ + - ][ + - ]: 2 : req.name = "Conversation between " + src_user + " and " + target_user;
[ + - ][ - + ]
[ - + ][ + - ]
[ - - ]
793 : : req.topic = ad;
794 [ + - ][ + - ]: 3 : req.invite = { '@' + target_user + ':' + g_mtxclient->server() };
[ + - ][ + + ]
[ - + ][ - + ]
[ - + ][ - + ]
[ + - ][ - - ]
[ - - ][ - - ]
[ - - ][ - - ]
795 [ + - ]: 1 : g_mtxclient->create_room( req,
796 : 1 : [&](const mtx::responses::CreateRoom& res, mtx::http::RequestErr err) {
797 : 1 : print_errors( err );
798 : : auto room_id = res.room_id.to_string();
799 [ + - ][ + - ]: 2 : MINFO( src_user << " created room id " << room_id );
[ + - ][ + - ]
800 [ + - ]: 1 : g_mtxclient->invite_user( room_id,
801 [ + - ][ + - ]: 2 : '@' + target_user + ':' + g_mtxclient->server(),
[ + - ][ - + ]
[ - + ][ - + ]
[ - + ][ - - ]
[ - - ][ - - ]
[ - - ]
802 [ + - ][ + - ]: 10 : [room_id](const mtx::responses::Empty&, mtx::http::RequestErr) {} );
[ + - ][ - + ]
[ - + ][ + - ]
[ - - ][ - - ]
[ - - ]
803 : 2 : });
804 : 1 : }
805 : :
806 : : void
807 : 1 : piac::matrix_message( const std::string& src_user,
808 : : const std::string& target_user,
809 : : const std::string& msg )
810 : : // *****************************************************************************
811 : : // Send a message to a user
812 : : // *****************************************************************************
813 : : {
814 [ + - ][ + - ]: 2 : MINFO( src_user << " messages " << target_user );
815 : 1 : invite_room( src_user, target_user, msg );
816 : 1 : }
817 : :
818 : : static void
819 : 0 : send_encrypted_reply( const std::string& room_id, const std::string& reply_msg )
820 : : // *****************************************************************************
821 : : // *****************************************************************************
822 : : {
823 [ - - ][ - - ]: 0 : MINFO( "sending encrypted reply" );
824 : :
825 : : // Create a megolm session if it doesn't already exist
826 [ - - ]: 0 : if (g_storage.outbound_group_exists( room_id )) {
827 : 0 : auto session_obj = g_storage.get_outbound_group_session( room_id );
828 [ - - ]: 0 : send_group_message( session_obj.session,
829 : : session_obj.data.session_id,
830 : : room_id,
831 : : reply_msg );
832 : :
833 : : } else {
834 [ - - ][ - - ]: 0 : MINFO( "creating new megolm outbound session" );
835 : 0 : create_outbound_megolm_session( room_id, reply_msg );
836 : : }
837 : 0 : }
838 : :
839 : : static void
840 : 16 : parse_messages( const mtx::responses::Sync& res )
841 : : // *****************************************************************************
842 : : //! Parse received messages after sync
843 : : //! \param[in] res Sync response to parse
844 : : // *****************************************************************************
845 : : {
846 [ + - ][ + - ]: 32 : MDEBUG( "parse_messages" );
847 : :
848 [ + + ]: 17 : for (const auto& room : res.rooms.invite) {
849 : : auto room_id = room.first;
850 : :
851 [ + - ][ + - ]: 3 : MINFO( "joining room " + room_id );
[ + - ][ + - ]
852 [ + - ]: 1 : g_mtxclient->join_room( room_id,
853 [ + - ][ - + ]: 8 : [room_id](const mtx::responses::RoomId&, mtx::http::RequestErr e) {
854 [ - + ]: 1 : if (e) {
855 : 0 : print_errors( e );
856 [ - - ][ - - ]: 0 : MERROR( "failed to join room " << room_id );
857 : 0 : return;
858 : : }
859 [ + - ][ - + ]: 2 : } );
[ + - ][ - - ]
[ - - ]
860 : : }
861 : :
862 : : // Check if we have any new m.room_key messages
863 : : // (i.e starting a new megolm session)
864 : 16 : handle_to_device_msgs( res.to_device );
865 : :
866 : : // Check if the uploaded one time keys are enough
867 [ + + ]: 32 : for (const auto &device : res.device_one_time_keys_count) {
868 [ - + ]: 16 : if (device.second < 50) {
869 [ - - ][ - - ]: 0 : MDEBUG( "number of one time keys: " << device.second );
[ - - ]
870 : 0 : g_olmclient->generate_one_time_keys( 50 - device.second );
871 [ - - ][ - - ]: 0 : g_mtxclient->upload_keys( g_olmclient->create_upload_keys_request(),
[ - - ][ - - ]
872 : 0 : &keys_uploaded_cb );
873 : : }
874 : : }
875 : :
876 [ + + ]: 23 : for (const auto &room : res.rooms.join) {
877 : :
878 : : const std::string room_id = room.first;
879 : :
880 [ + + ]: 9 : for (const auto &e : room.second.state.events) {
881 [ - + ]: 2 : if (is_room_encryption( e )) {
882 [ - - ]: 0 : mark_encrypted_room( RoomId(room_id) );
883 [ - - ][ - - ]: 0 : MDEBUG( "room state events " << get_json(e) );
[ - - ]
884 [ + + ]: 2 : } else if (is_member_event( e )) {
885 : : using namespace mtx::events;
886 [ + - ]: 2 : auto m = std::get< StateEvent< state::Member > >( e );
887 [ + - ]: 1 : get_device_keys( UserId( m.state_key ) );
888 [ + - ]: 1 : g_storage.add_member( room_id, m.state_key );
889 : : }
890 : : }
891 : :
892 [ + + ]: 28 : for (const auto &e : room.second.timeline.events) {
893 [ - + ]: 21 : if (is_room_encryption(e)) {
894 : :
895 [ - - ]: 0 : mark_encrypted_room( RoomId( room_id ) );
896 [ - - ][ - - ]: 0 : MDEBUG( "room timeline events " << get_json(e) );
[ - - ]
897 : :
898 [ + + ]: 21 : } else if (is_member_event( e )) {
899 : :
900 : : using namespace mtx::events;
901 [ + - ]: 14 : auto m = std::get< StateEvent< state::Member > >( e );
902 [ + - ]: 7 : get_device_keys( UserId( m.state_key ) );
903 [ + - ]: 7 : g_storage.add_member( room_id, m.state_key );
904 : :
905 [ - + ]: 14 : } else if (is_encrypted( e )) {
906 : :
907 [ - - ][ - - ]: 0 : MDEBUG( "received an encrypted event: " << room_id );
[ - - ]
908 [ - - ][ - - ]: 0 : MDEBUG( get_json(e) );
[ - - ][ - - ]
909 : :
910 : : using mtx::events::EncryptedEvent;
911 : : using mtx::events::msg::Encrypted;
912 : : auto msg = std::get< EncryptedEvent< Encrypted > >( e );
913 : :
914 [ - - ][ - - ]: 0 : if (g_storage.inbound_group_exists(
915 : : room_id, msg.content.session_id, msg.content.sender_key))
916 : : {
917 : : auto r = g_olmclient->decrypt_group_message(
918 : : g_storage.get_inbound_group_session(
919 : : room_id, msg.content.session_id, msg.content.sender_key ),
920 [ - - ][ - - ]: 0 : msg.content.ciphertext );
921 : :
922 : : auto msg_str = std::string( begin(r.data), end(r.data) );
923 [ - - ][ - - ]: 0 : const auto body = nlohmann::json::parse(msg_str).
[ - - ][ - - ]
924 [ - - ][ - - ]: 0 : at("content").at("body").get< std::string >();
[ - - ][ - - ]
[ - - ][ - - ]
[ - - ]
925 : :
926 [ - - ][ - - ]: 0 : MDEBUG( "decrypted data: " << body );
[ - - ]
927 [ - - ][ - - ]: 0 : MDEBUG( "decrypted message index: " << r.message_index );
[ - - ][ - - ]
928 : :
929 [ - - ]: 0 : if (msg.sender != g_mtxclient->user_id().to_string()) {
930 : : // Send a reply back to the sender
931 [ - - ][ - - ]: 0 : std::string reply_txt( msg.sender + ": you said '" + body + "'");
[ - - ][ - - ]
[ - - ][ - - ]
932 [ - - ]: 0 : send_encrypted_reply( room_id, reply_txt );
933 : : }
934 : :
935 : : } else {
936 [ - - ][ - - ]: 0 : MWARNING( "no megolm session found to decrypt the event" );
[ - - ]
937 : : }
938 : :
939 : : }
940 : : }
941 : :
942 : : }
943 : :
944 : : // tell the message thread that we have just parsed messages
945 : 32 : zmqpp::message msg;
946 [ + - ]: 16 : msg << "parsed_messages";
947 [ + - ]: 16 : g_msg_mtx.back().send( msg );
948 : 16 : }
949 : :
950 : : static void
951 : 16 : sync_handler( const mtx::responses::Sync& res, mtx::http::RequestErr err )
952 : : // *****************************************************************************
953 : : // Callback to executed after a /sync request completes.
954 : : //! \param[in] res Sync response
955 : : //! \param[in] err Errors if any
956 : : // *****************************************************************************
957 : : {
958 [ + - ][ + - ]: 32 : MDEBUG( "sync_handler" );
959 : :
960 : 13 : mtx::http::SyncOpts opts;
961 : :
962 [ - + ]: 16 : if (err) {
963 [ - - ][ - - ]: 0 : MERROR( "error during sync" );
[ - - ]
964 [ - - ]: 0 : print_errors( err );
965 [ - - ]: 0 : opts.since = g_mtxclient->next_batch_token();
966 [ - - ]: 0 : g_mtxclient->sync( opts, &sync_handler );
967 : 3 : return;
968 : : }
969 : :
970 [ + + ]: 16 : if (piac::g_matrix_shutdown) return;
971 : :
972 [ + - ]: 13 : parse_messages( res );
973 : 13 : opts.timeout = piac::g_matrix_sync_timeout;
974 [ + - ]: 13 : opts.since = res.next_batch;
975 : : g_mtxclient->set_next_batch_token( res.next_batch );
976 [ + - ]: 26 : g_mtxclient->sync( opts, &sync_handler );
977 : : }
978 : :
979 : : static void
980 : 3 : initial_sync_handler( const mtx::responses::Sync& res,
981 : : mtx::http::RequestErr err )
982 : : // *****************************************************************************
983 : : // Callback execute after the first (initial) /sync request completes
984 : : //! \param[in] res Sync response
985 : : //! \param[in] err Errors if any
986 : : // *****************************************************************************
987 : : {
988 [ + - ][ + - ]: 6 : MDEBUG( "initial_sync_handler" );
989 : :
990 : 3 : mtx::http::SyncOpts opts;
991 : :
992 [ - + ]: 3 : if (err) {
993 [ - - ][ - - ]: 0 : MERROR( "error during initial sync" );
[ - - ]
994 [ - - ]: 0 : print_errors( err );
995 : :
996 [ - - ]: 0 : if (err->status_code != 200) {
997 [ - - ][ - - ]: 0 : MERROR( "retrying initial sync ..." );
[ - - ]
998 [ - - ]: 0 : opts.timeout = 0;
999 [ - - ]: 0 : g_mtxclient->sync( opts, &initial_sync_handler );
1000 : : }
1001 : :
1002 : 0 : return;
1003 : : }
1004 : :
1005 [ + - ]: 3 : if (piac::g_matrix_shutdown) return;
1006 : :
1007 [ + - ]: 3 : parse_messages( res );
1008 : :
1009 [ - + ]: 3 : for (const auto &room : res.rooms.join) {
1010 : : const auto room_id = room.first;
1011 [ - - ]: 0 : for (const auto &e : room.second.state.events) {
1012 [ - - ]: 0 : if (is_member_event(e)) {
1013 : : using namespace mtx::events;
1014 [ - - ]: 0 : auto m = std::get< StateEvent< state::Member > >( e );
1015 [ - - ]: 0 : get_device_keys( UserId( m.state_key ) );
1016 [ - - ]: 0 : g_storage.add_member( room_id, m.state_key );
1017 : : }
1018 : : }
1019 : : }
1020 : :
1021 [ + - ]: 3 : opts.since = res.next_batch;
1022 [ + - ]: 3 : opts.timeout = piac::g_matrix_sync_timeout;
1023 : : g_mtxclient->set_next_batch_token( res.next_batch );
1024 [ + - ]: 6 : g_mtxclient->sync( opts, &sync_handler );
1025 : : }
1026 : :
1027 : : static void
1028 : 3 : login_cb( const mtx::responses::Login&, mtx::http::RequestErr error )
1029 : : // *****************************************************************************
1030 : : //! Callback executed after login attempt
1031 : : //! \param[in] error Error if any
1032 : : // *****************************************************************************
1033 : : {
1034 : 6 : MLOG_SET_THREAD_NAME( "mtx" );
1035 [ + - ][ + - ]: 6 : MDEBUG( "login_cb" );
1036 : :
1037 [ - + ]: 3 : if (error) {
1038 [ - - ][ - - ]: 0 : MERROR( "login error" );
1039 : 0 : print_errors( error );
1040 [ - - ]: 0 : if (not error->matrix_error.error.empty() ) {
1041 : 0 : std::cerr << error->matrix_error.error << '\n';
1042 : : }
1043 : 0 : return;
1044 : : }
1045 : :
1046 [ + - ][ + - ]: 12 : MDEBUG( "user id: " + g_mtxclient->user_id().to_string() );
[ + - ][ - + ]
[ - - ]
1047 [ + - ][ + - ]: 12 : MDEBUG( "device id: " + g_mtxclient->device_id() );
[ + - ][ - + ]
[ - - ]
1048 [ + - ][ + - ]: 9 : MDEBUG( "ed25519: " + g_olmclient->identity_keys().ed25519 );
[ + - ][ + - ]
1049 [ + - ][ + - ]: 9 : MDEBUG( "curve25519: " + g_olmclient->identity_keys().curve25519 );
[ + - ][ + - ]
1050 : :
1051 : : // Upload one-time keys
1052 : 3 : g_olmclient->set_user_id( g_mtxclient->user_id().to_string() );
1053 : 3 : g_olmclient->set_device_id( g_mtxclient->device_id() );
1054 : 3 : g_olmclient->generate_one_time_keys( 50 );
1055 : :
1056 [ + - ][ + - ]: 3 : g_mtxclient->upload_keys( g_olmclient->create_upload_keys_request(),
[ - + ][ - - ]
1057 [ - + ]: 3 : [](const mtx::responses::UploadKeys&, mtx::http::RequestErr err ) {
1058 [ - + ]: 3 : if (err) {
1059 : 0 : print_errors(err);
1060 : 0 : return;
1061 : : }
1062 : : g_olmclient->mark_keys_as_published();
1063 [ + - ][ + - ]: 6 : MDEBUG( "keys uploaded" );
1064 [ + - ][ + - ]: 6 : MDEBUG( "starting initial sync" );
1065 : 3 : mtx::http::SyncOpts opts;
1066 [ + - ]: 3 : opts.timeout = 0;
1067 [ + - ]: 6 : g_mtxclient->sync( opts, &initial_sync_handler );
1068 : 3 : } );
1069 : : }
1070 : :
1071 : : void
1072 : 3 : piac::matrix_thread( const std::string& server,
1073 : : const std::string& username,
1074 : : const std::string& password,
1075 : : const std::string& db_key )
1076 : : // *****************************************************************************
1077 : : // Entry point to thread to communicate with a matrix server
1078 : : //! \param[in] server Matrix hostname to connect to as \<host\>[:port]
1079 : : //! \param[in] username Username to use
1080 : : //! \param[in] password Password to use
1081 : : //! \param[in] db_key Database key to use to encrypt session db on disk
1082 : : // *****************************************************************************
1083 : : {
1084 : 6 : MLOG_SET_THREAD_NAME( "mtx" );
1085 [ + - ][ + - ]: 6 : MINFO( "mtx thread initialized" );
1086 [ + - ][ + - ]: 9 : MDEBUG( "matrix login request to server: " << server << ", username: " <<
[ + - ]
1087 : : username << ", connected: " << std::boolalpha << g_matrix_connected );
1088 : :
1089 [ - + ]: 3 : if (g_matrix_connected) {
1090 [ - - ][ - - ]: 0 : MDEBUG( "already connected" );
1091 : 0 : return;
1092 : : }
1093 : :
1094 : 3 : int port = 443;
1095 [ + - ][ - + ]: 12 : auto [host,prt] = split( server, ":" );
[ - + ][ - - ]
1096 [ - + ]: 3 : if (prt.empty()) {
1097 : 0 : g_mtxclient = std::make_shared< mtx::http::Client >( host );
1098 : : } else {
1099 : : try {
1100 [ + - ]: 3 : port = std::stoi( prt );
1101 [ - - ]: 0 : } catch (std::exception&) {
1102 [ - - ]: 0 : std::cerr << "invalid <host>:<port> format\n";
1103 : : return;
1104 : : }
1105 : 3 : g_mtxclient = std::make_shared< mtx::http::Client >( host, port );
1106 : : }
1107 [ + - ][ + - ]: 9 : MDEBUG( "will connect as @" << username << ':' << host << ':' << port );
[ + - ][ + - ]
1108 : :
1109 [ + - ]: 3 : g_mtxclient->verify_certificates( false );
1110 : 3 : g_olmclient = std::make_shared< mtx::crypto::OlmClient >();
1111 : :
1112 [ + - ][ + - ]: 6 : std::string db_base_filename = "piac-matrix-@" + username + ':' + server;
[ + - ][ - + ]
[ - - ]
1113 [ + - ]: 3 : g_mtx_account_db_filename = db_base_filename + ".account.json";
1114 [ + - ][ + - ]: 6 : g_mtx_session_db_filename = db_base_filename + ".session.json";
1115 : : g_mtx_db_storage_key = db_key;
1116 : :
1117 [ + - ]: 6 : std::ifstream db( g_mtx_account_db_filename );
1118 : : std::string db_data( (std::istreambuf_iterator< char >( db )),
1119 : : std::istreambuf_iterator< char >() );
1120 [ + + ]: 3 : if (db_data.empty()) {
1121 [ + - ]: 1 : g_olmclient->create_new_account();
1122 : : } else {
1123 [ + - ]: 2 : g_olmclient->load(
1124 [ + - ][ + - ]: 4 : nlohmann::json::parse(db_data).at("account").get<std::string>(),
[ + - ][ + - ]
[ - + ][ - + ]
[ - - ][ - - ]
1125 : : g_mtx_db_storage_key );
1126 : : }
1127 [ + - ]: 3 : g_storage.load();
1128 : :
1129 : : // create socket to forward matrix messages to
1130 [ + - ][ + - ]: 3 : g_msg_mtx.emplace_back( piac::g_ctx_msg, zmqpp::socket_type::pair );
1131 [ + - ][ + - ]: 6 : g_msg_mtx.back().connect( "inproc://msg_mtx" );
[ + - ][ - - ]
1132 [ + - ][ + - ]: 6 : MDEBUG( "Connected to inproc:://msg_mtx" );
[ + - ]
1133 : :
1134 [ + - ]: 3 : g_matrix_connected = true;
1135 [ + - ][ + - ]: 6 : g_mtxclient->login( username, password, login_cb ); // blocking, syncing...
[ - - ]
1136 [ + - ]: 3 : g_mtxclient->close();
1137 : 3 : g_matrix_connected = false;
1138 : 3 : g_matrix_shutdown = false;
1139 : :
1140 : : // tell the message thread that we are shutting down
1141 [ + - ]: 6 : zmqpp::message msg;
1142 [ + - ]: 3 : msg << "SHUTDOWN";
1143 [ + - ]: 3 : g_msg_mtx.back().send( msg );
1144 : : g_msg_mtx.clear();
1145 : :
1146 [ + - ][ + - ]: 9 : MDEBUG( "saving matrix session to " +
[ + - ][ + - ]
[ + - ][ + - ]
[ - + ][ - + ]
[ - - ][ - - ]
1147 : : g_mtx_account_db_filename + " and " + g_mtx_session_db_filename );
1148 [ + - ]: 3 : g_storage.save();
1149 [ + - ]: 6 : std::ofstream odb( g_mtx_account_db_filename );
1150 [ - + ]: 3 : if (not odb.is_open()) {
1151 [ - - ][ - - ]: 0 : MERROR( "Couldn't open matrix account db " << g_mtx_account_db_filename );
[ - - ]
1152 : 0 : return;
1153 : : }
1154 : : nlohmann::json data;
1155 [ + - ][ + - ]: 9 : data[ "account" ] = g_olmclient->save( g_mtx_db_storage_key );
[ - + ][ - - ]
1156 [ + - ]: 3 : odb << data.dump( 2 );
1157 [ + - ]: 3 : odb.close();
1158 : :
1159 [ + - ][ + - ]: 6 : MINFO( "mtx thread quit" );
[ + - ]
1160 : : }
|