├── NOTICE ├── icons ├── 16x16 │ └── threema.png ├── 22x22 │ └── threema.png ├── 48x48 │ └── threema.png ├── README.md └── threema.svg ├── src ├── threepl │ ├── Buddy.cpp │ ├── PrplHttpManager.cpp │ ├── prpl │ │ ├── threepl.h │ │ ├── buddy.h │ │ ├── xfer.h │ │ ├── list.h │ │ ├── connection.h │ │ ├── im.h │ │ ├── commands.h │ │ ├── actions.h │ │ ├── list.cpp │ │ ├── chat.h │ │ ├── buddy.cpp │ │ ├── xfer.cpp │ │ ├── connection.cpp │ │ └── commands.cpp │ ├── Logging.h │ ├── Buddy.h │ ├── ContactStore.h │ ├── ThreeplImport.h │ ├── Logging.cpp │ ├── PrplHttpManager.h │ ├── ContactStore.cpp │ ├── GroupStore.h │ ├── ThreeplConnection.h │ └── MessageHandler.h ├── types │ ├── formatstr.h │ ├── iter.h │ ├── flags.h │ ├── ptr_array.h │ └── bytes.h ├── protocol │ ├── packet │ │ ├── Status.cpp │ │ ├── MessageFlag.cpp │ │ ├── MessageFlag.h │ │ ├── Status.h │ │ ├── message_id.h │ │ ├── messagetypes.h │ │ ├── KeepAlive.h │ │ ├── Acknowledgement.h │ │ ├── Packet.h │ │ ├── payloads │ │ │ ├── PayloadPoll.h │ │ │ ├── PayloadText.h │ │ │ ├── PayloadControl.h │ │ │ ├── PayloadGroupControl.h │ │ │ ├── PayloadGroup.cpp │ │ │ ├── PayloadPoll.cpp │ │ │ ├── PayloadControl.cpp │ │ │ ├── PayloadText.cpp │ │ │ ├── MessagePayload.h │ │ │ ├── PayloadGroup.h │ │ │ ├── PayloadGroupControl.cpp │ │ │ └── MessagePayload.cpp │ │ ├── Acknowledgement.cpp │ │ ├── KeepAlive.cpp │ │ ├── Packet.cpp │ │ └── Message.h │ ├── protocol.cpp │ ├── data │ │ ├── Crypto.h │ │ ├── Blob.h │ │ ├── Group.h │ │ ├── Client.h │ │ └── Poll.h │ ├── protocol.h │ └── session.h ├── contact │ ├── Account.cpp │ ├── Contact.cpp │ ├── backup.h │ ├── Account.h │ ├── Contact.h │ └── backup.cpp ├── include │ ├── config.h │ └── config.h.in ├── encoding │ ├── crypto.cpp │ ├── pkcs7.h │ ├── sha256.cpp │ ├── sha256.h │ ├── pbkdf2-sha256.h │ ├── hex.h │ ├── pbkdf2-sha256.c │ ├── base32.h │ └── base64.h ├── api │ ├── HttpClientwolfSSL.cpp │ ├── HttpClientmbedTLS.cpp │ ├── HttpClientOpenSSL.cpp │ ├── API.h │ ├── HttpManager.cpp │ ├── IdentAPI.h │ ├── HttpManager.h │ └── API.cpp ├── logging │ ├── logging.h │ └── logging.cpp └── exceptions.h ├── .gitignore ├── .gitmodules ├── cmake ├── FindJsonCpp.cmake └── FindBotan.cmake └── README.md /NOTICE: -------------------------------------------------------------------------------- 1 | libceema 2 | Copyright 2017 Harold Bruintjes 3 | -------------------------------------------------------------------------------- /icons/16x16/threema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hbruintjes/ceema/HEAD/icons/16x16/threema.png -------------------------------------------------------------------------------- /icons/22x22/threema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hbruintjes/ceema/HEAD/icons/22x22/threema.png -------------------------------------------------------------------------------- /icons/48x48/threema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hbruintjes/ceema/HEAD/icons/48x48/threema.png -------------------------------------------------------------------------------- /src/threepl/Buddy.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 26/06/17. 3 | // 4 | 5 | #include "Buddy.h" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .build 2 | CMakeCache.txt 3 | CMakeFiles 4 | cmake_install.cmake 5 | Makefile 6 | 3rdparty/curl 7 | -------------------------------------------------------------------------------- /src/threepl/PrplHttpManager.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 24/02/17. 3 | // 4 | 5 | #include "PrplHttpManager.h" 6 | -------------------------------------------------------------------------------- /src/threepl/prpl/threepl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 22/02/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | static void threepl_init(PurplePlugin *plugin); 10 | static void threepl_destroy(PurplePlugin *plugin); 11 | -------------------------------------------------------------------------------- /src/threepl/Logging.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 20/05/17. 3 | // 4 | 5 | #ifndef CEEMA_LOGGING_H 6 | #define CEEMA_LOGGING_H 7 | 8 | namespace threepl { 9 | void init_logging(); 10 | void deinit_logging(); 11 | } 12 | 13 | #endif //CEEMA_LOGGING_H 14 | -------------------------------------------------------------------------------- /src/threepl/prpl/buddy.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 14/03/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | void threepl_add_buddy_with_invite(PurpleConnection *pc, PurpleBuddy *buddy, PurpleGroup *group, const char *message); 10 | -------------------------------------------------------------------------------- /icons/README.md: -------------------------------------------------------------------------------- 1 | # Threema protocol icons 2 | 3 | The icons provided here have to be copied into 4 | 5 | /usr/share/pixmaps/pidgin/protocols/16 6 | /usr/share/pixmaps/pidgin/protocols/22 7 | /usr/share/pixmaps/pidgin/protocols/48 8 | /usr/share/pixmaps/pidgin/protocols/scalable -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/l3pp"] 2 | path = 3rdparty/l3pp 3 | url = https://github.com/hbruintjes/l3pp.git 4 | [submodule "3rdparty/json"] 5 | path = 3rdparty/json 6 | url = https://github.com/nlohmann/json 7 | [submodule "3rdparty/variant"] 8 | path = 3rdparty/variant 9 | url = https://github.com/mpark/variant.git 10 | -------------------------------------------------------------------------------- /src/threepl/prpl/xfer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #ifndef CEEMA_XFER_H 6 | #define CEEMA_XFER_H 7 | 8 | #include 9 | 10 | gboolean threepl_can_receive_file(PurpleConnection *, const char *who); 11 | 12 | void threepl_send_file(PurpleConnection* gc, const char *who, const char *filename); 13 | 14 | #endif //CEEMA_XFER_H 15 | -------------------------------------------------------------------------------- /src/threepl/prpl/list.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #ifndef CEEMA_LIST_H 6 | #define CEEMA_LIST_H 7 | 8 | typedef struct _PurpleAccount PurpleAccount; 9 | typedef struct _PurpleBuddy PurpleBuddy; 10 | 11 | const char* threepl_list_icon(PurpleAccount *acct, PurpleBuddy *buddy); 12 | 13 | const char* threepl_list_emblem(PurpleBuddy* buddy); 14 | 15 | #endif //CEEMA_LIST_H 16 | -------------------------------------------------------------------------------- /src/threepl/prpl/connection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #ifndef CEEMA_CONNECTION_H 6 | #define CEEMA_CONNECTION_H 7 | 8 | #include 9 | #include 10 | 11 | void threepl_login(PurpleAccount *account); 12 | 13 | void threepl_close(PurpleConnection *gc); 14 | 15 | void threepl_keepalive(PurpleConnection* gc); 16 | 17 | #endif //CEEMA_CONNECTION_H 18 | -------------------------------------------------------------------------------- /src/threepl/prpl/im.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #ifndef CEEMA_IM_H 6 | #define CEEMA_IM_H 7 | 8 | #include 9 | #include 10 | 11 | int threepl_send_im(PurpleConnection* gc, const char *who, const char *message, PurpleMessageFlags); 12 | 13 | unsigned int threepl_send_typing(PurpleConnection* gc, const char* who, PurpleTypingState state); 14 | 15 | #endif //CEEMA_IM_H 16 | -------------------------------------------------------------------------------- /src/threepl/prpl/commands.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 20/05/17. 3 | // 4 | 5 | #ifndef CEEMA_COMMANDS_H 6 | #define CEEMA_COMMANDS_H 7 | 8 | #include 9 | #include 10 | 11 | void threepl_register_commands(PurplePlugin *plugin); 12 | void threepl_unregister_commands(PurplePlugin *plugin); 13 | 14 | PurpleCmdRet threepl_message_agree(PurpleConversation *, const gchar *cmd, 15 | gchar **args, gchar **error, void *data); 16 | 17 | #endif //CEEMA_COMMANDS_H 18 | -------------------------------------------------------------------------------- /src/threepl/prpl/actions.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 18-3-17. 3 | // 4 | 5 | #ifndef CEEMA_ACTIONS_H 6 | #define CEEMA_ACTIONS_H 7 | 8 | #include 9 | 10 | GList* threepl_actions(PurplePlugin *plugin, gpointer context); 11 | 12 | // typedef required for more complex actions like import from backup 13 | // added by Torsten (ttl) 14 | typedef struct { 15 | PurpleConnection* gc; 16 | ThreeplConnection* connection; 17 | char text[2048]; 18 | } threepl_actions_data; 19 | 20 | #endif //CEEMA_ACTIONS_H 21 | -------------------------------------------------------------------------------- /src/types/formatstr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 25/06/17. 3 | // 4 | 5 | #ifndef CEEMA_FORMATSTR_H 6 | #define CEEMA_FORMATSTR_H 7 | 8 | #include 9 | 10 | class formatstr: private std::stringstream { 11 | public: 12 | using std::stringstream::stringstream; 13 | 14 | operator std::string() const { 15 | return str(); 16 | } 17 | 18 | using std::stringstream::str; 19 | 20 | template 21 | formatstr& operator<<(T const& t) { 22 | static_cast(*this) << t; 23 | return *this; 24 | } 25 | }; 26 | 27 | #endif //CEEMA_FORMATSTR_H 28 | -------------------------------------------------------------------------------- /src/threepl/prpl/list.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #include "list.h" 6 | 7 | #include 8 | 9 | const char* threepl_list_icon(PurpleAccount *acct, PurpleBuddy *buddy) { 10 | return "threema"; 11 | } 12 | 13 | const char* threepl_list_emblem(PurpleBuddy* buddy) { 14 | const char* pk = purple_blist_node_get_string(&buddy->node, "public-key"); 15 | if (pk && pk[0] == '!') { 16 | return "not-authorized"; // Public key not found 17 | } 18 | if (purple_buddy_get_name(buddy)[0] == '*') { 19 | return "bot"; // Gateway account (starts with *) 20 | } 21 | return NULL; 22 | } -------------------------------------------------------------------------------- /src/threepl/Buddy.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 26/06/17. 3 | // 4 | 5 | #ifndef CEEMA_BUDDY_H 6 | #define CEEMA_BUDDY_H 7 | 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | /** 14 | * Class to hold buddy data 15 | */ 16 | class Buddy { 17 | ceema::Contact m_contact; 18 | PurpleBuddy* m_buddy; 19 | 20 | ceema::message_id m_lastMessage; 21 | std::string m_nickname; 22 | unsigned int m_featureLevel; 23 | unsigned int m_featureMask; 24 | time_t m_icon_ts; 25 | time_t m_update_ts; 26 | public: 27 | 28 | }; 29 | 30 | 31 | #endif //CEEMA_BUDDY_H 32 | -------------------------------------------------------------------------------- /src/protocol/packet/Status.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Status.h" 18 | -------------------------------------------------------------------------------- /src/contact/Account.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Account.h" 18 | 19 | namespace ceema { 20 | 21 | } -------------------------------------------------------------------------------- /src/contact/Contact.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "contact/Contact.h" 18 | 19 | namespace ceema { 20 | 21 | } -------------------------------------------------------------------------------- /src/protocol/protocol.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "protocol.h" 18 | 19 | namespace ceema { 20 | 21 | 22 | 23 | } -------------------------------------------------------------------------------- /src/include/config.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #define IPV6 20 | 21 | /* #undef USE_OPENSSL */ 22 | #define USE_MBEDTLS 23 | /* #undef USE_WOLFSSL */ 24 | 25 | /* #undef PLATFORM_BIG_ENDIAN */ 26 | -------------------------------------------------------------------------------- /src/encoding/crypto.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "crypto.h" 18 | 19 | namespace ceema { 20 | 21 | namespace crypto { 22 | 23 | int sodium_status = sodium_init(); 24 | 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/include/config.h.in: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #cmakedefine IPV6 20 | 21 | #cmakedefine USE_OPENSSL 22 | #cmakedefine USE_MBEDTLS 23 | #cmakedefine USE_WOLFSSL 24 | 25 | #cmakedefine PLATFORM_BIG_ENDIAN 26 | -------------------------------------------------------------------------------- /src/protocol/packet/MessageFlag.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 19/05/17. 3 | // 4 | 5 | #include "MessageFlag.h" 6 | 7 | #include 8 | 9 | namespace ceema { 10 | std::ostream& operator<<(std::ostream& os, MessageFlag flag) { 11 | switch(flag) { 12 | case MessageFlag::PUSH: 13 | return os << "PUSH"; 14 | case MessageFlag::NO_QUEUE: 15 | return os << "NO_QUEUE"; 16 | case MessageFlag::NO_ACK: 17 | return os << "NO_ACK"; 18 | case MessageFlag::NOTIFY: 19 | return os << "NOTIFY"; 20 | case MessageFlag::GROUP: 21 | return os << "GROUP"; 22 | } 23 | os << "(flag) << ">"; 24 | os.setstate(std::ostream::failbit); 25 | return os; 26 | } 27 | } -------------------------------------------------------------------------------- /src/protocol/packet/MessageFlag.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 19/05/17. 3 | // 4 | 5 | #ifndef CEEMA_MESSAGEFLAG_H_H 6 | #define CEEMA_MESSAGEFLAG_H_H 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | namespace ceema { 14 | enum class MessageFlag { 15 | /** Send as push message */ 16 | PUSH = 0x01, 17 | /** Do not queue message when recipient offline */ 18 | NO_QUEUE = 0x02, 19 | /** Do not ACK message by server */ 20 | NO_ACK = 0x04, 21 | /** Notify recipient when message arrives */ 22 | NOTIFY = 0x08, 23 | /** Message is a group message (has group payload) */ 24 | GROUP = 0x10, 25 | }; 26 | typedef Flags MessageFlags; 27 | 28 | std::ostream& operator<<(std::ostream& os, MessageFlag flag); 29 | } 30 | #endif //CEEMA_MESSAGEFLAG_H_H 31 | -------------------------------------------------------------------------------- /src/threepl/ContactStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 15/03/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef std::shared_ptr ContactPtr; 15 | 16 | class ContactStore { 17 | std::unordered_map m_idmap; 18 | ceema::IdentAPI& m_ident_api; 19 | 20 | public: 21 | ContactStore(ceema::IdentAPI& ident_api); 22 | 23 | bool has_contact(ceema::client_id const& id); 24 | 25 | ContactPtr get_contact(ceema::client_id const& id); 26 | 27 | void add_contact(ceema::Contact const& contact); 28 | 29 | ceema::future fetch_contact(ceema::client_id id); 30 | 31 | void update_buddies(PurpleAccount* account) const; 32 | void load_buddies(PurpleAccount* account); 33 | }; -------------------------------------------------------------------------------- /src/api/HttpClientwolfSSL.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpClient.h" 2 | 3 | #include 4 | 5 | namespace ceema { 6 | CURLcode HttpClient::ssl_ctx_callback_wolfssl(CURL *curl, void *ssl_ctx, void *client_ptr) { 7 | HttpClient& client = *static_cast(client_ptr); 8 | 9 | if (client.m_cert.empty()) { 10 | return CURLE_OK; 11 | } 12 | 13 | LOG_DEBUG(logging::loggerRoot, "Inserting SSL cert"); 14 | CURLcode status = CURLE_OK; 15 | 16 | auto res = wolfSSL_CTX_use_certificate_buffer( 17 | static_cast(ssl_ctx), 18 | reinterpret_cast(client.m_cert.c_str()), client.m_cert.size(), 19 | SSL_FILETYPE_PEM); 20 | if (res != SSL_SUCCESS) { 21 | // Error adding cert 22 | status = CURLE_SSL_CERTPROBLEM; 23 | } 24 | 25 | return status; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/contact/backup.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "contact/Account.h" 20 | 21 | #include 22 | 23 | namespace ceema { 24 | 25 | std::string make_backup(Account const& client, std::string const& password); 26 | Account decrypt_backup(std::string const &backup_string, std::string const &password); 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/threepl/ThreeplImport.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Torsten (ttl) 23.08.2019 3 | // 4 | 5 | #include "threepl/ThreeplConnection.h" 6 | #include "zip.h" 7 | 8 | class ThreeplImport { 9 | 10 | public: 11 | 12 | typedef struct { 13 | int b_new; 14 | int b_old; 15 | } imported; 16 | 17 | ThreeplImport (PurpleConnection* connection, 18 | ThreeplConnection* tpl_connection, 19 | char* password, char* backup_file); 20 | 21 | ~ThreeplImport (void); 22 | 23 | imported import_contacts (void); 24 | imported import_groups (void); 25 | 26 | private: 27 | 28 | bool open_file (const char* file_name); 29 | imported scan_contacts_file (void); 30 | imported scan_groups_file (void); 31 | void close_file (void); 32 | 33 | PurpleConnection* m_connection; 34 | ThreeplConnection* m_tpl_connection; 35 | char* m_password; 36 | char* m_backup_file; 37 | zip_t* m_backup; 38 | zip_file_t* m_file; 39 | 40 | }; 41 | -------------------------------------------------------------------------------- /src/protocol/packet/Status.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "Packet.h" 20 | 21 | #include 22 | 23 | namespace ceema { 24 | 25 | class Status : public Packet { 26 | public: 27 | Status(PacketType type) : Packet(type) { 28 | assert(type == PacketType::CONNECTED || type == PacketType::DISCONNECTED); 29 | } 30 | }; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/api/HttpClientmbedTLS.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpClient.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace ceema { 7 | CURLcode HttpClient::ssl_ctx_callback_mbedtls(CURL *curl, void *ssl_ctx, void *client_ptr) { 8 | HttpClient& client = *static_cast(client_ptr); 9 | 10 | if (client.m_cert.empty()) { 11 | return CURLE_OK; 12 | } 13 | 14 | LOG_DEBUG(logging::loggerRoot, "Inserting SSL cert"); 15 | CURLcode status = CURLE_OK; 16 | 17 | auto mbed_ctx = static_cast(ssl_ctx); 18 | 19 | mbedtls_x509_crt cacert; 20 | mbedtls_x509_crt_init( &cacert ); 21 | 22 | if (mbedtls_x509_crt_parse( 23 | &cacert, 24 | reinterpret_cast(client.m_cert.c_str()), 25 | client.m_cert.size() + 1) != 0) { 26 | status = CURLE_SSL_CERTPROBLEM; 27 | } else { 28 | mbedtls_ssl_conf_ca_chain(mbed_ctx, &cacert, nullptr); 29 | } 30 | 31 | return status; 32 | } 33 | } -------------------------------------------------------------------------------- /src/protocol/packet/message_id.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | struct message_id : public byte_array<8> { 24 | using byte_array::byte_array; 25 | }; 26 | 27 | inline message_id gen_message_id() { 28 | static std::uint8_t counter = 0; 29 | message_id id; 30 | id[0] = 0xce; 31 | id[1] = counter++; 32 | randombytes_buf(id.data() + 2, id.size() - 2); 33 | return id; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/contact/Account.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "Contact.h" 20 | 21 | namespace ceema { 22 | 23 | class Account : public Contact { 24 | private_key m_clientSK; 25 | 26 | public: 27 | Account(client_id const &id, private_key const &sk) : Contact(id, crypto::derive_public_key(sk)), 28 | m_clientSK(sk) { 29 | 30 | } 31 | 32 | private_key const &sk() const { 33 | return m_clientSK; 34 | } 35 | }; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/api/HttpClientOpenSSL.cpp: -------------------------------------------------------------------------------- 1 | #include "HttpClient.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace ceema { 9 | CURLcode HttpClient::ssl_ctx_callback_openssl(CURL *curl, void *ssl_ctx, void *client_ptr) { 10 | HttpClient& client = *static_cast(client_ptr); 11 | 12 | if (client.m_cert.empty()) { 13 | return CURLE_OK; 14 | } 15 | 16 | LOG_DEBUG(logging::loggerRoot, "Inserting SSL cert"); 17 | CURLcode status = CURLE_OK; 18 | 19 | auto bio = BIO_new_mem_buf(client.m_cert.c_str(), client.m_cert.size()); 20 | if (bio == NULL) { 21 | return CURLE_OUT_OF_MEMORY; 22 | } 23 | auto cert = PEM_read_bio_X509(bio, NULL, 0, NULL); 24 | BIO_free(bio); 25 | if (cert == NULL) { 26 | return CURLE_SSL_CERTPROBLEM; 27 | } 28 | 29 | auto store = SSL_CTX_get_cert_store(static_cast(ssl_ctx)); 30 | if (!X509_STORE_add_cert(store, cert)) { 31 | // Error adding cert 32 | status = CURLE_SSL_CERTPROBLEM; 33 | } 34 | X509_free(cert); 35 | 36 | return status; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/types/iter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | /** 20 | * Like std::copy, but returns the advanced input iterator, 21 | * and copies into output. 22 | * It copies output.size() elements 23 | * Usually useful for deserialization. 24 | * @tparam It Input iterator type 25 | * @tparam Array Output array type 26 | * @param input Input iterator 27 | * @param output Output array with known size 28 | * @return Advanced input iterator 29 | */ 30 | template 31 | It copy_iter(It input, Array& output) { 32 | std::copy(input, input + output.size(), output.begin()); 33 | return input + output.size(); 34 | } -------------------------------------------------------------------------------- /src/encoding/pkcs7.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | 19 | namespace ceema { 20 | 21 | namespace pkcs7 { 22 | 23 | inline void add_padding(byte_vector &data) { 24 | std::uint8_t val = static_cast(crypto::random::random() & 0xFFu); 25 | if (!val) { 26 | val++; 27 | } 28 | data.reserve(data.size() + val); 29 | data.insert(data.end(), val, val); 30 | } 31 | 32 | inline void strip_padding(byte_vector &data) { 33 | std::uint8_t val = data.back(); 34 | data.resize(data.size() - val); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/encoding/sha256.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "sha256.h" 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | 24 | sha256_hash hash_sha256(std::uint8_t const *data, std::size_t len) { 25 | sha256_hash hash; 26 | crypto_hash_sha256(hash.data(), data, len); 27 | return hash; 28 | } 29 | 30 | hmacsha256_auth mac_sha256(std::uint8_t const *data, std::size_t len, 31 | std::uint8_t const *key, std::size_t key_len) { 32 | hmacsha256_auth hash; 33 | crypto_auth_hmacsha256(hash.data(), data, len, key); 34 | return hash; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/protocol/data/Crypto.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "types/bytes.h" 20 | #include 21 | 22 | namespace ceema { 23 | struct private_key : public byte_array { 24 | using byte_array::byte_array; 25 | }; 26 | 27 | struct public_key : public byte_array { 28 | using byte_array::byte_array; 29 | }; 30 | 31 | struct shared_key : public byte_array { 32 | using byte_array::byte_array; 33 | }; 34 | 35 | struct nonce : public byte_array { 36 | using byte_array::byte_array; 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /src/logging/logging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #define LOG_TRACE(a, b) L3PP_LOG_TRACE(a, b) 22 | #define LOG_DEBUG(a, b) L3PP_LOG_DEBUG(a, b) 23 | #define LOG_INFO(a, b) L3PP_LOG_INFO(a, b) 24 | #define LOG_WARN(a, b) L3PP_LOG_WARN(a, b) 25 | #define LOG_ERROR(a, b) L3PP_LOG_ERROR(a, b) 26 | 27 | #define LOG_DBG(m) L3PP_LOG_DEBUG(::ceema::logging::loggerRoot, m) 28 | 29 | namespace ceema { 30 | namespace logging { 31 | /** 32 | * Setup logging, call before using the library 33 | */ 34 | void init(); 35 | 36 | extern l3pp::LogPtr loggerRoot; 37 | extern l3pp::LogPtr loggerNetwork; 38 | extern l3pp::LogPtr loggerSession; 39 | extern l3pp::LogPtr loggerProtocol; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/logging/logging.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include "logging.h" 19 | 20 | namespace ceema { 21 | 22 | namespace logging { 23 | 24 | l3pp::LogPtr loggerRoot = l3pp::Logger::getLogger("ceema"); 25 | l3pp::LogPtr loggerNetwork = l3pp::Logger::getLogger("ceema.network"); 26 | l3pp::LogPtr loggerSession = l3pp::Logger::getLogger("ceema.session"); 27 | l3pp::LogPtr loggerProtocol = l3pp::Logger::getLogger("ceema.protocol"); 28 | 29 | l3pp::Initializer l3pp_init = l3pp::Initializer::get(); 30 | 31 | void init() { 32 | l3pp::SinkPtr sink = l3pp::StreamSink::create(std::clog); 33 | l3pp::Logger::getRootLogger()->addSink(sink); 34 | l3pp::Logger::getRootLogger()->setLevel(l3pp::LogLevel::TRACE); 35 | 36 | loggerNetwork->setLevel(l3pp::LogLevel::WARN); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/exceptions.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | namespace ceema { 22 | /** 23 | * Exception occurring at session level 24 | */ 25 | class session_exception : public std::runtime_error { 26 | using std::runtime_error::runtime_error; 27 | }; 28 | 29 | /** 30 | * Exception occurring at Threema protocol level 31 | */ 32 | class protocol_exception : public std::runtime_error { 33 | using std::runtime_error::runtime_error; 34 | }; 35 | 36 | class packet_type_exception : public protocol_exception { 37 | using protocol_exception::protocol_exception; 38 | }; 39 | 40 | /** 41 | * Exception occurring when handling Threema message packets 42 | */ 43 | class message_exception : public std::runtime_error { 44 | using std::runtime_error::runtime_error; 45 | }; 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/contact/Contact.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | 24 | /** 25 | * Identifies a client, which can be the local user (has secret key), or some contact (only public key) 26 | */ 27 | class Contact { 28 | // Long term client keypair 29 | public_key m_clientPK; 30 | 31 | // Contact ID 32 | client_id m_clientID; 33 | std::string m_nick; 34 | public: 35 | Contact(client_id const &id, public_key const &pk) : m_clientPK(pk), m_clientID(id), m_nick(id.toString()) { 36 | } 37 | 38 | client_id const &id() const { 39 | return m_clientID; 40 | } 41 | 42 | std::string const &nick() const { 43 | return m_nick; 44 | } 45 | 46 | public_key const &pk() const { 47 | return m_clientPK; 48 | } 49 | 50 | 51 | }; 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/threepl/prpl/chat.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #ifndef CEEMA_CHAT_H 6 | #define CEEMA_CHAT_H 7 | 8 | #include 9 | 10 | /** 11 | * Get the name of a chat as used for running conversations, 12 | * or creating new ones 13 | * @param components Chat components 14 | * @return glib allocated string, caller must g_free 15 | */ 16 | char* threepl_get_chat_name(GHashTable *components); 17 | 18 | /** 19 | * Instruct threepl to join a chat based on provided chat info. Triggers 20 | * a server_got_join 21 | * @param gc 22 | * @param components 23 | */ 24 | void threepl_join_chat(PurpleConnection *gc, GHashTable* components); 25 | 26 | /* 27 | static void threepl_reject_chat(PurpleConnection *gc, GHashTable *components); 28 | 29 | static void threepl_chat_leave(PurpleConnection *gc, int id); 30 | */ 31 | 32 | GList* threepl_chat_info(PurpleConnection* gc); 33 | 34 | /** 35 | * Default values (generates chat ID automatically), or pulls it out 36 | * of room (as formatted using threepl_get_chat_name) 37 | * @param gc 38 | * @param room 39 | * @return 40 | */ 41 | GHashTable* threepl_chat_info_defaults(PurpleConnection* gc, const char* room); 42 | 43 | PurpleChat* threepl_find_blist_chat(PurpleAccount* account, const char* name); 44 | 45 | void threepl_chat_invite(PurpleConnection* gc, int id, const char* message, const char* who); 46 | 47 | int threepl_chat_send(PurpleConnection* gc, int id, const char* message, PurpleMessageFlags flags); 48 | 49 | void threepl_set_chat_topic(PurpleConnection *gc, int id, const char *topic); 50 | 51 | #endif //CEEMA_CHAT_H 52 | -------------------------------------------------------------------------------- /src/protocol/protocol.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "types/bytes.h" 20 | 21 | namespace ceema { 22 | 23 | template 24 | inline void htole(T const& value, std::uint8_t* buffer) { 25 | #ifdef PLATFORM_BIG_ENDIAN 26 | //DO thing 27 | #error "Big Endian platform not yet supported" 28 | #else 29 | *reinterpret_cast(buffer) = value; 30 | #endif 31 | } 32 | 33 | template 34 | inline void letoh(T& value, std::uint8_t const* buffer) { 35 | #ifdef PLATFORM_BIG_ENDIAN 36 | //DO thing 37 | #error "Big Endian platform not yet supported" 38 | #else 39 | value = *reinterpret_cast(buffer); 40 | #endif 41 | } 42 | 43 | template 44 | inline T letoh(std::uint8_t const* buffer) { 45 | #ifdef PLATFORM_BIG_ENDIAN 46 | //DO thing 47 | #error "Big Endian platform not yet supported" 48 | #else 49 | return *reinterpret_cast(buffer); 50 | #endif 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/encoding/sha256.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace ceema { 25 | const std::size_t hash_256_len = crypto_hash_sha256_BYTES; 26 | const std::size_t mac_256_len = crypto_auth_hmacsha256_BYTES; 27 | 28 | struct sha256_hash : byte_array { 29 | using byte_array::byte_array; 30 | }; 31 | 32 | struct hmacsha256_auth : byte_array { 33 | using byte_array::byte_array; 34 | }; 35 | 36 | /** 37 | * Hash the given data using SHA256. 38 | * @param data Data to hash 39 | * @param len Length of data in bytes 40 | * @return Hash 41 | */ 42 | sha256_hash hash_sha256(std::uint8_t const *data, std::size_t len); 43 | hmacsha256_auth mac_sha256(std::uint8_t const *data, std::size_t len, 44 | std::uint8_t const *key, std::size_t key_len); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/api/API.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "json.hpp" 20 | #include 21 | 22 | using json = nlohmann::json; 23 | 24 | namespace ceema { 25 | 26 | class API { 27 | HttpManager& m_manager; 28 | 29 | // Builtin certificate for the api server 30 | static const char* api_cert; 31 | protected: 32 | API(HttpManager& manager); 33 | 34 | future get(std::string const &url); 35 | 36 | future get(std::string const &url, IHttpTransfer* transfer); 37 | 38 | future post(std::string const &url, byte_vector const& data); 39 | 40 | future postFile(std::string url, byte_vector const& data, std::string const& filename); 41 | 42 | void postFile(std::string url, IHttpTransfer* transfer, std::string const& filename); 43 | 44 | future jsonGet(std::string const &url); 45 | 46 | future jsonPost(std::string const &url, json request); 47 | 48 | static json checkJSONResult(json data); 49 | }; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /cmake/FindJsonCpp.cmake: -------------------------------------------------------------------------------- 1 | # Find the JsonCpp include files and library. 2 | # 3 | # JsonCpp is a C++ library that can read/write JSON (JavaScript Object Notation) 4 | # documents. See http://jsoncpp.sourceforge.net/ for more details. 5 | # 6 | # This module defines: 7 | # JsonCpp_INCLUDE_DIRS - where to find json/json.h 8 | # JsonCpp_LIBRARIES - the libraries to link against to use JsonCpp 9 | # JsonCpp_FOUND - if false the library was not found. 10 | 11 | find_path(JsonCpp_INCLUDE_DIR "json/json.h" 12 | PATH_SUFFIXES "jsoncpp" 13 | DOC "Specify the JsonCpp include directory here") 14 | 15 | find_library(JsonCpp_LIBRARY 16 | NAMES jsoncpp 17 | PATHS 18 | DOC "Specify the JsonCpp library here") 19 | set(JsonCpp_INCLUDE_DIRS ${JsonCpp_INCLUDE_DIR}) 20 | set(JsonCpp_LIBRARIES "${JsonCpp_LIBRARY}") 21 | 22 | set(_JsonCpp_version_args) 23 | if (EXISTS "${JsonCpp_INCLUDE_DIR}/json/version.h") 24 | file(STRINGS "${JsonCpp_INCLUDE_DIR}/json/version.h" _JsonCpp_version_contents REGEX "JSONCPP_VERSION_[A-Z]+") 25 | foreach (_JsonCpp_version_part MAJOR MINOR PATCH) 26 | string(REGEX REPLACE ".*# *define +JSONCPP_VERSION_${_JsonCpp_version_part} +([0-9]+).*" "\\1" JsonCpp_VERSION_${_JsonCpp_version_part} "${_JsonCpp_version_contents}") 27 | endforeach () 28 | 29 | set(JsonCpp_VERSION_STRING "${JsonCpp_VERSION_MAJOR}.${JsonCpp_VERSION_MINOR}.${JsonCpp_VERSION_PATCH}") 30 | 31 | set(_JsonCpp_version_args VERSION_VAR JsonCpp_VERSION_STRING) 32 | endif () 33 | 34 | include(FindPackageHandleStandardArgs) 35 | find_package_handle_standard_args(JsonCpp 36 | REQUIRED_VARS JsonCpp_LIBRARIES JsonCpp_INCLUDE_DIRS 37 | ${_JsonCpp_version_args}) 38 | 39 | mark_as_advanced(JsonCpp_INCLUDE_DIR JsonCpp_LIBRARY) 40 | -------------------------------------------------------------------------------- /src/protocol/packet/messagetypes.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | 24 | enum class MessageType : std::uint8_t { 25 | NONE = 0x00, // Internal ID 26 | 27 | TEXT = 0x01, 28 | PICTURE = 0x02, 29 | LOCATION = 0x10, 30 | 31 | VIDEO = 0x13, 32 | AUDIO = 0x14, 33 | POLL = 0x15, 34 | POLL_VOTE = 0x16, 35 | FILE = 0x17, 36 | 37 | ICON = 0x18, 38 | ICON_CLEAR = 0x19, 39 | 40 | GROUP_TEXT = 0x41, 41 | GROUP_LOCATION = 0x42, 42 | GROUP_PICTURE = 0x43, 43 | GROUP_VIDEO = 0x44, 44 | GROUP_AUDIO = 0x45, 45 | GROUP_FILE = 0x46, 46 | 47 | GROUP_MEMBERS = 0x4A, 48 | GROUP_TITLE = 0x4B, 49 | GROUP_LEAVE = 0x4C, 50 | 51 | GROUP_ICON = 0x50, 52 | GROUP_SYNC = 0x51, 53 | GROUP_POLL = 0x52, 54 | GROUP_POLL_VOTE = 0x53, 55 | 56 | MESSAGE_STATUS = 0x80, 57 | CLIENT_TYPING = 0x90, 58 | }; 59 | 60 | std::ostream& operator<<(std::ostream& os, MessageType type); 61 | } 62 | -------------------------------------------------------------------------------- /src/protocol/data/Blob.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "types/bytes.h" 20 | #include "encoding/crypto.h" 21 | 22 | namespace ceema { 23 | 24 | struct blob_id : public byte_array<16> { 25 | using byte_array::byte_array; 26 | }; 27 | 28 | typedef std::uint32_t blob_size; 29 | 30 | /** 31 | * Types of blobs. Mostly relevant to distinguish between the fixed 32 | * nonces used to encrypt/decrypt the data (except for IMAGE, which 33 | * has none) 34 | */ 35 | enum class BlobType { 36 | IMAGE, 37 | VIDEO, 38 | VIDEO_THUMB, 39 | AUDIO, 40 | FILE, 41 | FILE_THUMB, 42 | GROUP_IMAGE, 43 | GROUP_ICON, 44 | ICON 45 | }; 46 | 47 | /** 48 | * Blob as used by newer functions, based on shared key 49 | * and a fixed nonce 50 | */ 51 | struct Blob { 52 | blob_id id; 53 | blob_size size; 54 | shared_key key; 55 | }; 56 | 57 | /** 58 | * Blob used by image API, uses contact's public/private keys and 59 | * random nonce 60 | */ 61 | struct LegacyBlob { 62 | blob_id id; 63 | blob_size size; 64 | nonce n; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/protocol/packet/KeepAlive.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "Packet.h" 20 | 21 | namespace ceema { 22 | 23 | class KeepAlive : public Packet { 24 | byte_vector m_payload; 25 | public: 26 | /** 27 | * Construct a new keep-alive packet with random payload 28 | */ 29 | KeepAlive(); 30 | 31 | byte_vector const& payload() const { 32 | return m_payload; 33 | } 34 | 35 | bool isAck() const { 36 | return m_type == PacketType ::KEEPALIVE_ACK; 37 | } 38 | 39 | KeepAlive generateAck() const; 40 | 41 | static KeepAlive fromPacket(PacketType type, byte_vector const& packet); 42 | 43 | byte_vector toPacket() const; 44 | 45 | private: 46 | /** 47 | * Construct Keep-alive from data 48 | * @param type 49 | * @param payload 50 | */ 51 | KeepAlive(PacketType type, byte_vector const& payload) : 52 | Packet(type), m_payload(payload) {}; 53 | 54 | /** 55 | * Construct Keep-alive without data 56 | * @param type 57 | * @param payload 58 | */ 59 | KeepAlive(PacketType type) : 60 | Packet(type) {}; 61 | }; 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/threepl/prpl/buddy.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 14/03/17. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "buddy.h" 9 | #include "threepl/ThreeplConnection.h" 10 | 11 | void threepl_add_buddy_with_invite(PurpleConnection* gc, PurpleBuddy *buddy, PurpleGroup *group, const char *message) { 12 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 13 | 14 | // Fetch the PK and store it if not already there 15 | const char *pk = purple_blist_node_get_string(&buddy->node, "public-key"); 16 | if (!pk) { 17 | ceema::client_id id; 18 | try { 19 | id = ceema::client_id::fromString(buddy->name); 20 | } catch (std::exception& e) { 21 | purple_notify_error(gc, "Invalid Threema ID", "Unable to add buddy", e.what()); 22 | purple_blist_node_set_string(&buddy->node, "public-key", "!"); 23 | return; 24 | } 25 | auto fut_contact = connection->contact_store().fetch_contact(id); 26 | fut_contact.next([buddy, gc](ceema::future fut) { 27 | try { 28 | ContactPtr contact = fut.get(); 29 | std::string pk_string = ceema::hex_encode(contact->pk()); 30 | purple_blist_node_set_string(&buddy->node, "public-key", pk_string.c_str()); 31 | purple_prpl_got_user_status(purple_buddy_get_account(buddy), 32 | purple_buddy_get_name(buddy), 33 | "online", NULL, NULL); 34 | } catch(std::exception& e) { 35 | purple_notify_error(gc, "Failed to get public-key", "Unable to retrieve public key for buddy", e.what()); 36 | purple_blist_node_set_string(&buddy->node, "public-key", "!"); 37 | } 38 | return true; 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/api/HttpManager.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "HttpManager.h" 18 | #include 19 | 20 | namespace ceema { 21 | HttpManager::HttpManager() : m_cert(), m_running_handles(0) { 22 | m_handle = curl_multi_init(); 23 | 24 | curl_multi_setopt(m_handle, CURLMOPT_SOCKETFUNCTION, &socket_callback); 25 | curl_multi_setopt(m_handle, CURLMOPT_SOCKETDATA, this); 26 | curl_multi_setopt(m_handle, CURLMOPT_TIMERFUNCTION, &timer_callback); 27 | curl_multi_setopt(m_handle, CURLMOPT_TIMERDATA, this); 28 | 29 | } 30 | 31 | HttpManager::~HttpManager() { 32 | for(auto& client: m_clients) { 33 | curl_multi_remove_handle(m_handle, client.getCURL()); 34 | } 35 | m_clients.clear(); 36 | 37 | curl_multi_cleanup(m_handle); 38 | } 39 | 40 | HttpClient& HttpManager::getFreeClient() { 41 | //TODO: invalid const-correctness 42 | for(auto& client: m_clients) { 43 | if (!client.getBusy()) { 44 | return const_cast(client); 45 | } 46 | } 47 | auto emplace_res = m_clients.emplace(*this, std::string{"Threema/Ceema"}); 48 | auto& client = const_cast(*emplace_res.first); 49 | client.set_cert(m_cert); 50 | return client; 51 | } 52 | } -------------------------------------------------------------------------------- /src/protocol/packet/Acknowledgement.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "Packet.h" 20 | #include "message_id.h" 21 | #include 22 | 23 | namespace ceema { 24 | 25 | class Acknowledgement : public Packet { 26 | client_id m_sender; 27 | message_id m_message; 28 | 29 | public: 30 | // Creates ACK packet of type ACK_CLIENT (see fromPacket for ACK_SERVER) 31 | Acknowledgement(client_id const& sender, message_id const& message) : Packet(PacketType::ACK_CLIENT), 32 | m_sender(sender), m_message(message) {} 33 | Acknowledgement(Acknowledgement const&) = default; 34 | Acknowledgement(Acknowledgement &&) = default; 35 | 36 | Acknowledgement& operator=(Acknowledgement const&) = default; 37 | Acknowledgement& operator=(Acknowledgement &&) = default; 38 | 39 | client_id const& sender() const { 40 | return m_sender; 41 | } 42 | 43 | message_id const& message() const { 44 | return m_message; 45 | } 46 | 47 | static Acknowledgement fromPacket(PacketType type, byte_vector const& packet); 48 | byte_vector toPacket() const; 49 | 50 | private: 51 | Acknowledgement() : Packet(PacketType::ACK_SERVER) {} 52 | 53 | }; 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/protocol/packet/Packet.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "types/bytes.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace ceema { 26 | 27 | const unsigned PACKET_TYPE_SIZE = 4; 28 | 29 | enum class PacketType : std::uint32_t { 30 | KEEPALIVE = 0x00, 31 | 32 | MESSAGE_SEND = 0x01, 33 | MESSAGE_RECV = 0x02, 34 | 35 | KEEPALIVE_ACK = 0x80, 36 | 37 | ACK_SERVER = 0x81, 38 | ACK_CLIENT = 0x82, 39 | 40 | CONNECTED = 0xd0, 41 | DISCONNECTED = 0xe0, 42 | }; 43 | 44 | /** 45 | * Packets consist of a 4-byte type and some payload. 46 | * The payload is defined by sub-classes. 47 | */ 48 | class Packet { 49 | protected: 50 | PacketType m_type; 51 | public: 52 | PacketType type() const { 53 | return m_type; 54 | } 55 | 56 | /** 57 | * Return pointer to a new packet based on the received packet data 58 | * (not including the leading protocol length 59 | * bytes). 60 | * @param data 61 | * @return 62 | */ 63 | static std::unique_ptr fromPacket(byte_vector const& data); 64 | 65 | protected: 66 | Packet(PacketType type) : m_type(type) {}; 67 | }; 68 | 69 | std::ostream& operator<<(std::ostream& os, PacketType type); 70 | } 71 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadPoll.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace ceema { 26 | 27 | /** 28 | * Poll data is sent as JSON configuration mostly 29 | */ 30 | struct PayloadPoll { 31 | static constexpr MessageType Type = MessageType::POLL; 32 | static /*constexpr*/ MessageFlags default_flags() { 33 | return MessageFlags{MessageFlag::PUSH}; 34 | } 35 | 36 | poll_id id; 37 | 38 | Poll poll; 39 | 40 | static PayloadPoll deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 41 | byte_vector serialize() const; 42 | }; 43 | 44 | /** 45 | * Poll vote data is sent as JSON configuration mostly 46 | */ 47 | struct PayloadPollVote { 48 | static constexpr MessageType Type = MessageType::POLL_VOTE; 49 | static /*constexpr*/ MessageFlags default_flags() { 50 | return MessageFlags{MessageFlag::PUSH}; 51 | } 52 | 53 | poll_uid id; 54 | 55 | std::vector choices; 56 | 57 | static PayloadPollVote deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 58 | byte_vector serialize() const; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /src/threepl/Logging.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 20/05/17. 3 | // 4 | 5 | #include "Logging.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace threepl { 11 | static l3pp::SinkPtr sink; 12 | 13 | /** 14 | * Logging sink that uses purple's logging facilities 15 | */ 16 | class PrplSink: public l3pp::Sink { 17 | public: 18 | /** 19 | * Create a PrplSink from some output stream. 20 | * @param os Output stream. 21 | */ 22 | static l3pp::SinkPtr create() { 23 | return l3pp::SinkPtr(new PrplSink()); 24 | } 25 | 26 | protected: 27 | void logEntry(l3pp::LogLevel level, std::string const& entry) const override { 28 | switch(level) { 29 | default: 30 | case l3pp::LogLevel::TRACE: 31 | case l3pp::LogLevel::DEBUG: 32 | purple_debug(PURPLE_DEBUG_MISC, "threepl", entry.c_str()); 33 | break; 34 | case l3pp::LogLevel::INFO: 35 | purple_debug(PURPLE_DEBUG_INFO, "threepl", entry.c_str()); 36 | break; 37 | case l3pp::LogLevel::WARN: 38 | purple_debug(PURPLE_DEBUG_WARNING, "threepl", entry.c_str()); 39 | break; 40 | case l3pp::LogLevel::ERR: 41 | purple_debug(PURPLE_DEBUG_ERROR, "threepl", entry.c_str()); 42 | break; 43 | case l3pp::LogLevel::FATAL: 44 | purple_debug(PURPLE_DEBUG_FATAL, "threepl", entry.c_str()); 45 | break; 46 | } 47 | } 48 | 49 | private: 50 | explicit PrplSink() {} 51 | }; 52 | 53 | void init_logging() { 54 | sink = PrplSink::create(); 55 | l3pp::Logger::getRootLogger()->addSink(sink); 56 | l3pp::Logger::getRootLogger()->setLevel(l3pp::LogLevel::ALL); 57 | } 58 | 59 | void deinit_logging() { 60 | l3pp::Logger::getRootLogger()->removeSink(sink); 61 | } 62 | } -------------------------------------------------------------------------------- /src/protocol/packet/Acknowledgement.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Acknowledgement.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace ceema { 25 | // ACK packet is fixed length 26 | const unsigned PAYLOAD_ACK_SIZE = sizeof(PacketType) + client_id::array_size + message_id::array_size; 27 | 28 | Acknowledgement Acknowledgement::fromPacket(PacketType type, byte_vector const& packet) { 29 | if (type != PacketType::ACK_SERVER) { 30 | throw protocol_exception("Invalid Acknowledgement type"); 31 | } 32 | if (packet.size() != PAYLOAD_ACK_SIZE) { 33 | throw protocol_exception("Invalid Acknowledgement size"); 34 | } 35 | 36 | Acknowledgement ack; 37 | 38 | auto iter = packet.begin() + sizeof(PacketType); 39 | copy_iter(iter, ack.m_sender); 40 | copy_iter(iter, ack.m_message); 41 | 42 | return ack; 43 | } 44 | 45 | byte_vector Acknowledgement::toPacket() const { 46 | byte_vector packet; 47 | packet.resize(PAYLOAD_ACK_SIZE); 48 | 49 | auto packet_iter = packet.begin(); 50 | 51 | htole(m_type, &*packet_iter); 52 | packet_iter += sizeof(m_type); 53 | 54 | packet_iter = std::copy(m_sender.begin(), m_sender.end(), packet_iter); 55 | std::copy(m_message.begin(), m_message.end(), packet_iter); 56 | return packet; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/protocol/data/Group.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "Client.h" 20 | 21 | namespace ceema { 22 | /** 23 | * Group ID used by group owner 24 | */ 25 | struct group_id : public byte_array<8> { 26 | using byte_array::byte_array; 27 | }; 28 | 29 | /** 30 | * Unique group ID, consisting of owner ID and group ID 31 | */ 32 | struct group_uid : public byte_array { 33 | using byte_array::byte_array; 34 | 35 | group_id gid() const { 36 | return group_id(slice_array(*this)); 37 | } 38 | 39 | client_id cid() const { 40 | return client_id(slice_array<0, client_id::array_size>(*this)); 41 | } 42 | }; 43 | 44 | /** 45 | * Generates a new group ID randomly 46 | * @return Group ID 47 | */ 48 | inline group_id gen_group_id() { 49 | group_id id; 50 | randombytes_buf(id.data(), id.size()); 51 | return id; 52 | } 53 | 54 | /** 55 | * Constructs a group UID from a group ID and a client ID 56 | * @param cid ID of the group owner 57 | * @param gid group ID 58 | * @return Group UID 59 | */ 60 | inline group_uid make_group_uid(client_id const& cid, group_id const& gid) { 61 | group_uid uid; 62 | auto next = std::copy(cid.begin(), cid.end(), uid.begin()); 63 | std::copy(gid.begin(), gid.end(), next); 64 | return uid; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadText.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace ceema { 25 | /** 26 | * Payload for text messages, contains a single string that is UTF-8 encoded 27 | */ 28 | struct PayloadText { 29 | static constexpr MessageType Type = MessageType::TEXT; 30 | static /*constexpr*/ MessageFlags default_flags() { 31 | return MessageFlags{MessageFlag::PUSH}; 32 | } 33 | 34 | /** UTF-8 encoded message text */ 35 | std::string m_text; 36 | 37 | static PayloadText deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 38 | byte_vector serialize() const; 39 | }; 40 | 41 | /** 42 | * Location is sent as 3 floats (lat, long, acc) optionally followed by 43 | * newline and string representation 44 | */ 45 | struct PayloadLocation { 46 | static constexpr MessageType Type = MessageType::LOCATION; 47 | static /*constexpr*/ MessageFlags default_flags() { 48 | return MessageFlags{MessageFlag::PUSH}; 49 | } 50 | 51 | double m_lattitude; 52 | double m_longitude; 53 | double m_accuracy; 54 | std::string m_location; 55 | std::string m_description; 56 | 57 | static PayloadLocation deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 58 | byte_vector serialize() const; 59 | }; 60 | } -------------------------------------------------------------------------------- /src/encoding/pbkdf2-sha256.h: -------------------------------------------------------------------------------- 1 | /*- 2 | * Copyright 2005,2007,2009 Colin Percival 3 | * All rights reserved. 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions 7 | * are met: 8 | * 1. Redistributions of source code must retain the above copyright 9 | * notice, this list of conditions and the following disclaimer. 10 | * 2. Redistributions in binary form must reproduce the above copyright 11 | * notice, this list of conditions and the following disclaimer in the 12 | * documentation and/or other materials provided with the distribution. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 | * SUCH DAMAGE. 25 | * 26 | */ 27 | 28 | /** 29 | * Modified for use with ceema: 30 | * - Adjust include paths 31 | * - Name parameters 32 | */ 33 | 34 | #ifndef pbkdf2_sha256_H 35 | #define pbkdf2_sha256_H 36 | 37 | #include 38 | #include 39 | 40 | #ifdef __cplusplus 41 | extern "C" { 42 | #endif 43 | 44 | /** 45 | * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): 46 | * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and 47 | * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). 48 | */ 49 | void PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, 50 | size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen); 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | 56 | #endif /* !pbkdf2_sha256_H */ 57 | -------------------------------------------------------------------------------- /src/protocol/packet/KeepAlive.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "KeepAlive.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace ceema { 24 | KeepAlive::KeepAlive() : Packet(PacketType::KEEPALIVE) { 25 | m_payload.resize(sizeof(std::uint32_t)); 26 | auto val = crypto::random::random(); 27 | *reinterpret_cast(m_payload.data()) = val; 28 | } 29 | 30 | KeepAlive KeepAlive::generateAck() const { 31 | if (isAck()) { 32 | throw protocol_exception("Invalid KeepAlive type to ack"); 33 | } 34 | 35 | return KeepAlive(PacketType::KEEPALIVE_ACK, m_payload); 36 | } 37 | 38 | KeepAlive KeepAlive::fromPacket(PacketType type, byte_vector const& packet) { 39 | if (type != PacketType::KEEPALIVE && type != PacketType ::KEEPALIVE_ACK) { 40 | throw protocol_exception("Invalid KeepAlive type"); 41 | } 42 | 43 | KeepAlive ka(type); 44 | 45 | ka.m_payload.resize(packet.size() - PACKET_TYPE_SIZE); 46 | std::copy(packet.begin() + PACKET_TYPE_SIZE, packet.end(), 47 | ka.m_payload.begin()); 48 | 49 | return ka; 50 | } 51 | 52 | byte_vector KeepAlive::toPacket() const { 53 | byte_vector packet; 54 | packet.resize(PACKET_TYPE_SIZE + m_payload.size()); 55 | 56 | auto packet_iter = packet.begin(); 57 | 58 | htole(m_type, &*packet_iter); 59 | packet_iter += sizeof(m_type); 60 | 61 | std::copy(m_payload.begin(), m_payload.end(), packet_iter); 62 | return packet; 63 | } 64 | } -------------------------------------------------------------------------------- /src/threepl/prpl/xfer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #include "xfer.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | gboolean threepl_can_receive_file(PurpleConnection *, const char *who) { 12 | // Conditional based on existence of PK? Only relevant for images, 13 | // otherwise just send a message and hope for the best 14 | return TRUE; 15 | } 16 | 17 | void threepl_send_file(PurpleConnection* gc, const char *who, const char *filename) { 18 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 19 | PrplBlobUploadTransfer* transfer = connection->new_xfer(gc, who); 20 | 21 | auto cid = ceema::client_id::fromString(who); 22 | 23 | transfer->get_future().next([connection, cid](ceema::future fut){ 24 | LOG_DBG("Sending transfer message"); 25 | UploadData data = fut.get(); 26 | //TODO: catch exception and write message 27 | 28 | //TODO: send message with blob 29 | 30 | ceema::PayloadFile payload; 31 | 32 | payload.id = data.blob.id; 33 | payload.key = data.blob.key; 34 | payload.size = data.blob.size; 35 | 36 | payload.has_thumb = data.hasThumb; 37 | payload.thumb_id = data.thumb; 38 | payload.filename = data.filename; 39 | payload.flag = ceema::PayloadFile::FileFlag::DEFAULT; 40 | 41 | // Attempt to figure out the mimetype 42 | GError* err; 43 | struct _GFile * gfile = g_file_new_for_path(data.localFilename.c_str()); 44 | struct _GFileInfo * gfileinfo = g_file_query_info (gfile, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 45 | G_FILE_QUERY_INFO_NONE, NULL, &err); 46 | 47 | payload.mime_type = g_file_info_get_content_type(gfileinfo); 48 | 49 | g_object_unref(gfileinfo); 50 | g_object_unref(gfile); 51 | 52 | connection->send_message(cid, payload); 53 | }); 54 | 55 | auto xfer = transfer->xfer(); 56 | if (filename) { 57 | // Send via via DnD or similar, filename is known 58 | purple_xfer_request_accepted(xfer, filename); 59 | } else { 60 | // Send via menu, request name via dialog 61 | purple_xfer_request(xfer); 62 | } 63 | } -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | namespace ceema { 27 | enum class MessageStatus : std::uint8_t { 28 | RECEIVED = 0x01, 29 | SEEN = 0x02, 30 | AGREED = 0x03, 31 | DISAGREED = 0x04, 32 | }; 33 | 34 | std::ostream& operator<<(std::ostream& os, MessageStatus status); 35 | 36 | /** 37 | * Message status is a status flag and the message IDs it 38 | * applies to 39 | */ 40 | struct PayloadMessageStatus { 41 | static constexpr MessageType Type = MessageType::MESSAGE_STATUS; 42 | static /*constexpr*/ MessageFlags default_flags() { 43 | return MessageFlags(); 44 | } 45 | MessageStatus m_status; 46 | std::vector m_ids; 47 | 48 | static PayloadMessageStatus deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 49 | byte_vector serialize() const; 50 | }; 51 | 52 | /** 53 | * Simple boolean indicating if the user is typing a message 54 | */ 55 | struct PayloadTyping { 56 | static constexpr MessageType Type = MessageType::CLIENT_TYPING; 57 | static /*constexpr*/ MessageFlags default_flags() { 58 | return MessageFlags{MessageFlag::NO_ACK, MessageFlag::NO_QUEUE}; 59 | } 60 | bool m_typing; 61 | 62 | static PayloadTyping deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 63 | byte_vector serialize() const; 64 | }; 65 | } -------------------------------------------------------------------------------- /cmake/FindBotan.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find Botan 2 | # If Botan_SUFFIX isdefined, search will use this as well 3 | # Once done this will define 4 | # Botan_FOUND - System has Botan 5 | # Botan_INCLUDE_DIR - The Botan include directory 6 | # Botan_LIBRARIES - The libraries needed to use Botan 7 | # Botan_VERSION_STRING - The version of Botan ("major.minor") 8 | 9 | # use pkg-config to get the directories and then use these values 10 | # in the find_path() and find_library() calls 11 | find_package(PkgConfig QUIET) 12 | PKG_CHECK_MODULES(PC_Botan QUIET Botan) 13 | 14 | set(Botan_NAMES "botan") 15 | if (Botan_SUFFIX) 16 | set(Botan_NAMES ${Botan_NAMES} "botan-${Botan_SUFFIX}") 17 | endif() 18 | 19 | find_path(Botan_INCLUDE_DIR NAMES botan/botan.h 20 | HINTS 21 | ${PC_Botan_INCLUDEDIR} 22 | ${PC_Botan_INCLUDE_DIRS} 23 | ~/.local/include 24 | PATH_SUFFIXES ${Botan_NAMES} 25 | ) 26 | 27 | find_library(Botan_LIBRARIES NAMES ${Botan_NAMES} 28 | HINTS 29 | ${PC_Botan_LIBDIR} 30 | ${PC_Botan_LIBRARY_DIRS} 31 | ~/.local/lib 32 | ) 33 | 34 | if(PC_Botan_VERSION) 35 | set(Botan_VERSION_STRING ${PC_Botan_VERSION}) 36 | elseif(Botan_INCLUDE_DIR AND EXISTS "${Botan_INCLUDE_DIR}/botan/build.h") 37 | file(STRINGS "${Botan_INCLUDE_DIR}/botan/build.h" Botan_major_version 38 | REGEX "^#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+.+") 39 | file(STRINGS "${Botan_INCLUDE_DIR}/botan/build.h" Botan_minor_version 40 | REGEX "^#define[\t ]+BOTAN_VERSION_MINOR[\t ]+.+") 41 | string(REGEX REPLACE "^#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+(.+)" "\\1" 42 | Botan_major_version "${Botan_major_version}") 43 | string(REGEX REPLACE "^#define[\t ]+BOTAN_VERSION_MINOR[\t ]+(.+)" "\\1" 44 | Botan_minor_version "${Botan_minor_version}") 45 | set(Botan_VERSION_STRING "${Botan_major_version}.${Botan_minor_version}") 46 | unset(Botan_major_version) 47 | unset(Botan_minor_version) 48 | endif() 49 | 50 | # handle the QUIETLY and REQUIRED arguments and set Botan_FOUND to TRUE if 51 | # all listed variables are TRUE 52 | include(FindPackageHandleStandardArgs) 53 | FIND_PACKAGE_HANDLE_STANDARD_ARGS(Botan 54 | REQUIRED_VARS Botan_LIBRARIES Botan_INCLUDE_DIR 55 | VERSION_VAR Botan_VERSION_STRING) 56 | 57 | mark_as_advanced(Botan_INCLUDE_DIR Botan_LIBRARIES Botan_VERSION_STRING) 58 | -------------------------------------------------------------------------------- /src/threepl/prpl/connection.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 30/05/17. 3 | // 4 | 5 | #include "connection.h" 6 | 7 | #include 8 | #include 9 | 10 | void threepl_login(PurpleAccount *account) { 11 | PurpleConnection* gc = purple_account_get_connection(account); 12 | 13 | const char* username = purple_account_get_username(account); 14 | const char* password = purple_account_get_password(account); 15 | const char* backup = purple_account_get_string(account, "backup", ""); 16 | 17 | try { 18 | ceema::Account c = *backup ? ceema::decrypt_backup(backup, password) : 19 | ceema::Account(ceema::client_id::fromString(username), ceema::hex_decode( 20 | std::string(password))); 21 | 22 | if (c.id().toString() != username) { 23 | gchar* msg = g_strdup_printf("The user ID '%s' does not match the backup string ID '%s'", username, c.id().toString().c_str()); 24 | purple_notify_warning(gc, "Invalid username", "Username mismatch", msg); 25 | g_free(msg); 26 | } 27 | 28 | ThreeplConnection* connection = new ThreeplConnection(account, c); 29 | 30 | // Initialize known contacts 31 | connection->contact_store().load_buddies(account); 32 | 33 | // Load chats 34 | connection->group_store().load_chats(account); 35 | 36 | purple_connection_set_protocol_data(gc, connection); 37 | connection->start_connect(); 38 | } catch (std::exception& e) { 39 | purple_notify_error(gc, "Login error", e.what(), 40 | "Invalid username, password or backup string"); 41 | return; 42 | } 43 | } 44 | 45 | void threepl_close(PurpleConnection *gc) { 46 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 47 | 48 | if (!connection) { 49 | return; 50 | } 51 | 52 | connection->close(); 53 | 54 | connection->contact_store().update_buddies(connection->acct()); 55 | connection->group_store().update_chats(connection->acct()); 56 | 57 | delete connection; 58 | purple_connection_set_protocol_data(gc, nullptr); 59 | } 60 | 61 | void threepl_keepalive(PurpleConnection* gc) { 62 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 63 | 64 | connection->send_keepalive(); 65 | } -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadGroupControl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | namespace ceema { 27 | struct PayloadGroupInfo { 28 | static /*constexpr*/ MessageFlags default_flags() { 29 | return MessageFlags{MessageFlag::GROUP}; 30 | } 31 | 32 | group_id group; 33 | }; 34 | 35 | struct PayloadGroupMembers : PayloadGroupInfo { 36 | static constexpr MessageType Type = MessageType::GROUP_MEMBERS; 37 | 38 | static PayloadGroupMembers deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 39 | byte_vector serialize() const; 40 | 41 | std::vector members; 42 | }; 43 | 44 | struct PayloadGroupTitle : PayloadGroupInfo { 45 | static constexpr MessageType Type = MessageType::GROUP_TITLE; 46 | 47 | static PayloadGroupTitle deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 48 | byte_vector serialize() const; 49 | 50 | std::string title; 51 | }; 52 | 53 | struct PayloadGroupIcon : PayloadGroupInfo { 54 | static constexpr MessageType Type = MessageType::GROUP_ICON; 55 | 56 | static PayloadGroupIcon deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 57 | byte_vector serialize() const; 58 | 59 | blob_id id; 60 | blob_size size; 61 | shared_key key; 62 | }; 63 | 64 | struct PayloadGroupSync : PayloadGroupInfo { 65 | static constexpr MessageType Type = MessageType::GROUP_SYNC; 66 | 67 | static PayloadGroupSync deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 68 | byte_vector serialize() const; 69 | }; 70 | 71 | } -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadGroup.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | #include "PayloadGroup.h" 20 | 21 | namespace ceema { 22 | PayloadGroupPicture PayloadGroupPicture::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 23 | if (size != group_uid::array_size + blob_id::array_size + sizeof(blob_size) + shared_key::array_size) { 24 | throw std::runtime_error("Invalid group picture payload"); 25 | } 26 | 27 | PayloadGroupPicture payload; 28 | payload_data = copy_iter(payload_data, payload.group); 29 | 30 | payload_data = copy_iter(payload_data, payload.id); 31 | letoh(payload.size, &*payload_data); 32 | payload_data += sizeof(payload.size); 33 | payload_data = copy_iter(payload_data, payload.key); 34 | 35 | return payload; 36 | } 37 | 38 | byte_vector PayloadGroupPicture::serialize() const { 39 | byte_vector data; 40 | data.resize(group_uid::array_size + blob_id::array_size + sizeof(blob_size) + shared_key::array_size); 41 | 42 | auto iter = std::copy(group.begin(), group.end(), data.begin()); 43 | 44 | iter = std::copy(id.begin(), id.end(), iter); 45 | htole(size, &*iter); 46 | iter += sizeof(blob_size); 47 | iter = std::copy(key.begin(), key.end(), iter); 48 | 49 | return data; 50 | } 51 | 52 | PayloadGroupLeave PayloadGroupLeave::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 53 | if (size < group_uid::array_size) { 54 | throw std::runtime_error("Invalid group leave payload"); 55 | } 56 | 57 | PayloadGroupLeave payload; 58 | payload_data = copy_iter(payload_data, payload.group); 59 | 60 | return payload; 61 | } 62 | 63 | byte_vector PayloadGroupLeave::serialize() const { 64 | return byte_vector(group.begin(), group.end()); 65 | } 66 | } -------------------------------------------------------------------------------- /src/api/IdentAPI.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "api/API.h" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace ceema { 26 | 27 | class IdentAPI : public API { 28 | // E-Mail addresses are hashed with fixed key 29 | static byte_array<32> emailKey; 30 | // Phone nos. are hashed with fixed key 31 | static byte_array<32> phoneKey; 32 | static nonce createIdentNonce; 33 | 34 | public: 35 | explicit IdentAPI(HttpManager& manager) : API(manager) {} 36 | 37 | future getClientInfo(std::string client_id); 38 | 39 | future setFeatureLevel(Account const& client, int featureLevel); 40 | 41 | future> checkFeatureLevel(std::vector const& clients); 42 | /* 43 | bool identCheck(std::vector const& clients); 44 | 45 | bool checkRevocationKey(Account const& client); 46 | */ 47 | future setRevocationKey(Account const &client, std::string const &key); 48 | 49 | future linkEmail(Account const& client, std::string email); 50 | 51 | future linkMobile(Account const& client, std::string mobileNo, bool call); 52 | 53 | future callMobile(Account const& client, std::string const& verifyId); 54 | 55 | future verifyMobile(Account const& client, std::string const& verifyId, std::string const& code); 56 | /* 57 | bool checkPriv(Account const& client); 58 | 59 | Account createIdentity(std::string const& license); 60 | 61 | bool checkVersionStatus(); 62 | 63 | bool match(std::unordered_map& phone, std::unordered_map& mail); 64 | */ 65 | private: 66 | bool insertChallengeResponse(json& request, json const& challenge, private_key const& sk, nonce const& n = crypto::generate_nonce()); 67 | 68 | std::string hash_phone(std::string phone); 69 | std::string hash_mail(std::string mail); 70 | 71 | 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadPoll.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "PayloadPoll.h" 18 | 19 | namespace ceema { 20 | 21 | PayloadPoll PayloadPoll::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 22 | if (size < poll_id::array_size) { 23 | throw std::runtime_error("Invalid poll payload"); 24 | } 25 | auto end_data = payload_data + size; 26 | 27 | PayloadPoll poll; 28 | std::copy(payload_data, payload_data + poll_id::array_size, poll.id.begin()); 29 | payload_data += poll_id::array_size; 30 | 31 | json val = json::parse(payload_data, end_data); 32 | poll.poll = val.get(); 33 | 34 | return poll; 35 | 36 | } 37 | 38 | byte_vector PayloadPoll::serialize() const { 39 | byte_vector res; 40 | res.insert(res.begin(), id.begin(), id.end()); 41 | json poll = json(this->poll); 42 | std::string jsonString = poll.dump(); 43 | res.insert(res.end(), jsonString.begin(), jsonString.end()); 44 | 45 | return res; 46 | } 47 | 48 | PayloadPollVote PayloadPollVote::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 49 | if (size < poll_uid::array_size) { 50 | throw std::runtime_error("Invalid poll vote payload"); 51 | } 52 | auto end_data = payload_data + size; 53 | 54 | PayloadPollVote payload; 55 | std::copy(payload_data, payload_data + poll_uid::array_size, payload.id.begin()); 56 | payload_data += poll_uid::array_size; 57 | 58 | json val = json::parse(payload_data, end_data); 59 | payload.choices = val.get>(); 60 | 61 | return payload; 62 | } 63 | 64 | byte_vector PayloadPollVote::serialize() const { 65 | byte_vector res; 66 | res.insert(res.begin(), id.begin(), id.end()); 67 | json choices = json(this->choices); 68 | std::string jsonString = choices.dump(); 69 | res.insert(res.end(), jsonString.begin(), jsonString.end()); 70 | 71 | return res; 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/protocol/data/Client.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "types/bytes.h" 20 | 21 | namespace ceema { 22 | 23 | /** 8 character client ID (e.g. ABCDEFGH or *THREEMA) */ 24 | const unsigned CLIENTID_SIZE = 8; 25 | class client_id : public byte_array { 26 | public: 27 | using byte_array::byte_array; 28 | 29 | bool operator==(client_id const& other) const { 30 | return std::equal(begin(), end(), other.begin()); 31 | } 32 | bool operator!=(client_id const& other) const { 33 | return !(*this == other); 34 | } 35 | 36 | std::string toString() const { 37 | return std::string(reinterpret_cast(this->data()), CLIENTID_SIZE); 38 | } 39 | 40 | /** 41 | * Create client_id from a string. Converts to upper case 42 | * using current locale. Input should match [*A-Za-z0-9][A-Za-z0-9]{7} 43 | * but is not enforced (it most likely will result in failure to 44 | * find PK) 45 | * @param str client id as string 46 | * @return client_id 47 | * @throws runtime_error if str is of the wrong length 48 | */ 49 | static client_id fromString(std::string const& str) { 50 | if (str.size() != CLIENTID_SIZE) { 51 | throw std::runtime_error("Invalid client id"); 52 | } 53 | 54 | client_id id; 55 | auto iter = id.begin(); 56 | for(auto c: str) { 57 | // Convert to upper case for ease of use 58 | *iter = static_cast(toupper(c)); 59 | ++iter; 60 | } 61 | return id; 62 | } 63 | }; 64 | 65 | } 66 | 67 | namespace std { 68 | template<> struct hash 69 | { 70 | typedef ceema::client_id argument_type; 71 | typedef std::size_t result_type; 72 | result_type operator()(argument_type const& s) const 73 | { 74 | return std::hash{}(*reinterpret_cast(s.data())) ; 75 | } 76 | }; 77 | } 78 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadControl.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "PayloadControl.h" 18 | 19 | #include 20 | 21 | namespace ceema { 22 | PayloadMessageStatus PayloadMessageStatus::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 23 | if (!size || (size-1) % message_id::array_size != 0) { 24 | throw std::runtime_error("Invalid status payload"); 25 | } 26 | 27 | auto numIDs = (size-1) / message_id::array_size; 28 | 29 | PayloadMessageStatus payload; 30 | payload.m_status = static_cast(payload_data[0]); 31 | payload_data++; 32 | 33 | payload.m_ids.resize(numIDs); 34 | for(std::size_t i = 0; i < numIDs; i++) { 35 | std::copy(payload_data, payload_data+8, payload.m_ids[i].begin()); 36 | payload_data += 8; 37 | } 38 | return payload; 39 | } 40 | 41 | byte_vector PayloadMessageStatus::serialize() const { 42 | byte_vector res; 43 | res.resize(1 + m_ids.size()*message_id::array_size); 44 | 45 | res[0] = static_cast(m_status); 46 | auto iter = res.begin()+1; 47 | for(auto const& id: m_ids) { 48 | iter = std::copy(id.begin(), id.end(), iter); 49 | } 50 | 51 | return res; 52 | } 53 | 54 | PayloadTyping PayloadTyping::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 55 | if (size != 1) { 56 | throw std::runtime_error("Invalid typing payload"); 57 | } 58 | return PayloadTyping{!!payload_data[0]}; 59 | } 60 | 61 | byte_vector PayloadTyping::serialize() const { 62 | return byte_vector{m_typing ? std::uint8_t(1u) : std::uint8_t(0u)}; 63 | } 64 | 65 | std::ostream& operator<<(std::ostream& os, MessageStatus status) { 66 | switch(status) { 67 | case MessageStatus::RECEIVED: 68 | return os << "RECEIVED"; 69 | case MessageStatus::SEEN: 70 | return os << "SEEN"; 71 | case MessageStatus::AGREED: 72 | return os << "AGREED"; 73 | case MessageStatus::DISAGREED: 74 | return os << "DISAGREED"; 75 | } 76 | os << "(status) << ">"; 77 | os.setstate(std::ostream::failbit); 78 | return os; 79 | } 80 | } -------------------------------------------------------------------------------- /src/protocol/packet/Packet.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Packet.h" 18 | 19 | #include "Status.h" 20 | #include "Message.h" 21 | #include "Acknowledgement.h" 22 | #include "KeepAlive.h" 23 | 24 | #include "logging/logging.h" 25 | #include 26 | 27 | namespace ceema { 28 | 29 | std::unique_ptr Packet::fromPacket(byte_vector const &data) { 30 | PacketType type; 31 | letoh(type, data.data()); 32 | 33 | switch (type) { 34 | case PacketType::CONNECTED: 35 | case PacketType::DISCONNECTED: 36 | // Status Packet 37 | return std::make_unique(type); 38 | case PacketType::ACK_SERVER: 39 | return std::make_unique( 40 | Acknowledgement::fromPacket(type, data)); 41 | case PacketType::MESSAGE_RECV: 42 | return std::make_unique( 43 | Message::fromPacket(type, data)); 44 | case PacketType::KEEPALIVE: 45 | case PacketType::KEEPALIVE_ACK: 46 | return std::make_unique( 47 | KeepAlive::fromPacket(type, data)); 48 | default: 49 | throw packet_type_exception(formatstr() << "Unexpect packet type: 0x" << 50 | std::hex << std::to_string(static_cast(type))); 51 | } 52 | } 53 | 54 | std::ostream& operator<<(std::ostream& os, PacketType type) { 55 | switch (type) { 56 | case PacketType::CONNECTED: 57 | return os << "CONNECTED"; 58 | case PacketType::DISCONNECTED: 59 | return os << "DISCONNECTED"; 60 | case PacketType::ACK_SERVER: 61 | return os << "ACK_SERVER"; 62 | case PacketType::ACK_CLIENT: 63 | return os << "ACK_CLIENT"; 64 | case PacketType::MESSAGE_RECV: 65 | return os << "MESSAGE_RECV"; 66 | case PacketType::MESSAGE_SEND: 67 | return os << "MESSAGE_SEND"; 68 | case PacketType::KEEPALIVE: 69 | return os << "KEEPALIVE"; 70 | case PacketType::KEEPALIVE_ACK: 71 | return os << "KEEPALIVE_ACK"; 72 | } 73 | os << "(type) << ">"; 74 | os.setstate(std::ostream::failbit); 75 | return os; 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/threepl/PrplHttpManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 24/02/17. 3 | // 4 | 5 | #ifndef CEEMA_PRPLHTTPMANAGER_H 6 | #define CEEMA_PRPLHTTPMANAGER_H 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class PrplHttpManager : public ceema::HttpManager { 13 | struct SocketCallbacks { 14 | guint read; 15 | guint write; 16 | }; 17 | 18 | guint timeout; 19 | 20 | public: 21 | void* registerRead(int fd, void* ptr) override { 22 | SocketCallbacks* callbacks = static_cast(ptr); 23 | if (!callbacks) { 24 | callbacks = new SocketCallbacks{}; 25 | } 26 | if (!callbacks->read) { 27 | callbacks->read = purple_input_add(fd, static_cast(PURPLE_INPUT_READ), 28 | &on_http_data_event, this); 29 | } 30 | return callbacks; 31 | } 32 | 33 | void* registerWrite(int fd, void* ptr) override { 34 | SocketCallbacks* callbacks = static_cast(ptr); 35 | if (!callbacks) { 36 | callbacks = new SocketCallbacks{}; 37 | } 38 | if (!callbacks->write) { 39 | callbacks->write = purple_input_add(fd, static_cast(PURPLE_INPUT_WRITE), 40 | &on_http_data_event, this); 41 | } 42 | return callbacks; 43 | } 44 | 45 | void* unregister(int fd, void* ptr) override { 46 | if (ptr) { 47 | SocketCallbacks* callbacks = static_cast(ptr); 48 | 49 | if (callbacks->read) { 50 | purple_input_remove(callbacks->read); 51 | } 52 | if (callbacks->write) { 53 | purple_input_remove(callbacks->write); 54 | } 55 | 56 | delete callbacks; 57 | } 58 | return nullptr; 59 | } 60 | 61 | void registerTimeout(long timeout_ms) override { 62 | if (timeout_ms == -1) { 63 | purple_timeout_remove(timeout); 64 | } else { 65 | timeout = purple_timeout_add(timeout_ms, &on_http_timeout_event, this); 66 | } 67 | } 68 | 69 | protected: 70 | static void on_http_data_event(gpointer data, gint fd, PurpleInputCondition condition) { 71 | PrplHttpManager* mgr = static_cast(data); 72 | 73 | int running_handles = 0; 74 | int ev_bitmask = (condition & PURPLE_INPUT_READ) ? CURL_CSELECT_IN : 0; 75 | ev_bitmask |= (condition & PURPLE_INPUT_WRITE) ? CURL_CSELECT_OUT : 0; 76 | 77 | curl_multi_socket_action(mgr->m_handle, fd, ev_bitmask, &running_handles); 78 | 79 | mgr->checkMessages(); 80 | } 81 | 82 | static gboolean on_http_timeout_event(gpointer data) { 83 | PrplHttpManager* mgr = static_cast(data); 84 | 85 | int running_handles = 0; 86 | curl_multi_socket_action(mgr->m_handle, CURL_SOCKET_TIMEOUT, 0, &running_handles); 87 | 88 | mgr->checkMessages(); 89 | 90 | return FALSE; // curl asks for non-repeating 91 | } 92 | 93 | }; 94 | 95 | 96 | #endif //CEEMA_PRPLHTTPMANAGER_H 97 | -------------------------------------------------------------------------------- /src/encoding/hex.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | static const std::array hex_chars = { 24 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',}; 25 | 26 | static const std::array hex_table = { 27 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 28 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 29 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 64, 30 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 64, 64, 64, 64, 64, 64, 31 | 64, 10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 32 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 33 | 64, 10, 11, 12, 13, 14, 15, 64, 64, 64, 64, 64, 64, 64, 64, 64, 34 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 35 | }; 36 | 37 | template 38 | inline void hex_encode(ArrayIn const& from, ArrayOut& to) { 39 | if (to.size() / 2 != from.size()) { 40 | throw std::runtime_error("Invalid hex output size"); 41 | } 42 | for(std::size_t i = 0; i < from.size(); i++) { 43 | to[i*2] = hex_chars[from[i] >> 4]; 44 | to[i*2+1] = hex_chars[from[i] & 0x0f]; 45 | } 46 | } 47 | 48 | template 49 | inline std::string hex_encode(Array const& from) { 50 | std::string to; 51 | to.resize(from.size() * 2); 52 | hex_encode(from, to); 53 | return to; 54 | } 55 | 56 | template 57 | inline void hex_decode(ArrayIn const& from, ArrayOut& to) { 58 | if (from.size() % 2 != 0) { 59 | throw std::runtime_error("Invalid hex input size "); 60 | } 61 | if (to.size() * 2 != from.size()) { 62 | throw std::runtime_error("Invalid output size"); 63 | } 64 | for(std::size_t i = 0; i < from.size() - 1; i += 2) { 65 | auto hi_char = from[i]; 66 | auto lo_char = from[i+1]; 67 | if ((hi_char > 127) || (hi_char < 0) || (hex_table[hi_char] == 64) || 68 | (lo_char > 127) || (lo_char < 0) || (hex_table[lo_char] == 64)) { 69 | throw std::runtime_error("Invalid hex input"); 70 | } 71 | auto hi_val = hex_table[hi_char]; 72 | auto lo_val = hex_table[lo_char]; 73 | to[i/2] = hi_val << 4 | lo_val; 74 | } 75 | } 76 | 77 | template 78 | inline byte_vector hex_decode(Array const& from) { 79 | byte_vector res; 80 | res.resize(from.size() / 2); 81 | hex_decode(from, res); 82 | return res; 83 | } 84 | 85 | template 86 | inline ArrayOut hex_decode(ArrayIn const& from) { 87 | ArrayOut res; 88 | hex_decode(from, res); 89 | return res; 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadText.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "PayloadText.h" 18 | 19 | namespace ceema { 20 | PayloadText PayloadText::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 21 | if (size < 1) { 22 | throw std::runtime_error("Invalid text payload"); 23 | } 24 | return PayloadText{std::string(reinterpret_cast(&*payload_data), size)}; 25 | } 26 | 27 | byte_vector PayloadText::serialize() const { 28 | return byte_vector(m_text.begin(), m_text.end()); 29 | } 30 | 31 | PayloadLocation PayloadLocation::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 32 | if (size < 1) { 33 | throw std::runtime_error("Invalid location payload"); 34 | } 35 | 36 | PayloadLocation payload; 37 | 38 | std::vector lines; 39 | std::string payloadString = std::string(reinterpret_cast(&*payload_data), size); 40 | std::string::size_type startpos = 0; 41 | std::string::size_type endpos; 42 | while(lines.size() < 2 && (endpos = payloadString.find('\n', startpos)) != std::string::npos) { 43 | lines.emplace_back(payloadString.begin()+startpos, payloadString.begin()+endpos); 44 | startpos = endpos+1; 45 | } 46 | if (startpos < payloadString.size()) { 47 | lines.emplace_back(payloadString.begin()+startpos, payloadString.end()); 48 | } 49 | 50 | std::stringstream ss(lines[0]); 51 | ss >> payload.m_lattitude; 52 | if (ss.peek() == ',') { 53 | ss.ignore(); 54 | } else { 55 | throw std::runtime_error("Invalid location payload"); 56 | } 57 | ss >> payload.m_longitude; 58 | if (ss.peek() == ',') { 59 | ss.ignore(); 60 | ss >> payload.m_accuracy; 61 | } else { 62 | payload.m_accuracy = 0.0; 63 | } 64 | 65 | if (lines.size() > 1) { 66 | payload.m_location = lines[1]; 67 | if (lines.size() > 2) { 68 | payload.m_description = lines[2]; 69 | while((endpos = payload.m_description.find("\\n")) != std::string::npos) { 70 | payload.m_description.replace(endpos, 2, 1, '\n'); 71 | } 72 | } 73 | } 74 | 75 | if (!ss) { 76 | throw std::runtime_error("Invalid location payload"); 77 | } 78 | return payload; 79 | } 80 | 81 | byte_vector PayloadLocation::serialize() const { 82 | std::stringstream ss; 83 | ss << m_lattitude << ',' << m_longitude; 84 | if (m_accuracy != 0.0) { 85 | ss << ',' << m_accuracy; 86 | } 87 | if (!m_location.empty()) { 88 | ss << '\n' << m_location; 89 | if (!m_description.empty()) { 90 | ss << '\n' << m_description; 91 | } 92 | } 93 | std::string str = ss.str(); 94 | return byte_vector(str.begin(), str.end()); 95 | } 96 | } -------------------------------------------------------------------------------- /src/types/flags.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | namespace ceema { 24 | 25 | /** 26 | * Class to handle bit flag values based on a (class) enum 27 | * @tparam FlagEnum Type of enum containing flag values. Members should 28 | * have a singel bit set 29 | * @tparam FlagType Store type for combined flags 30 | */ 31 | template::type> 32 | struct Flags { 33 | typedef FlagType ValueType; 34 | FlagType value; 35 | 36 | explicit constexpr Flags() : value(FlagType(0)) {} 37 | explicit /*constexpr*/ Flags(FlagType val) : value(val) {} 38 | explicit constexpr Flags(std::initializer_list vals) : value(0) { 39 | for(auto val: vals) { 40 | value |= static_cast(val); 41 | } 42 | } 43 | 44 | Flags& operator=(FlagType val) { 45 | value = val; 46 | return *this; 47 | } 48 | 49 | /** 50 | * Check if flag is set 51 | * @param flag Flag to test 52 | * @return true iff set 53 | */ 54 | bool isset(FlagEnum flag) const { 55 | return (value & static_cast(flag)) != 0; 56 | } 57 | 58 | /** 59 | * Check if flag is not set 60 | * @param flag Flag to test 61 | * @return true iff not set 62 | */ 63 | bool isnset(FlagEnum flag) const { 64 | return (value & static_cast(flag)) == 0; 65 | } 66 | 67 | /** 68 | * Set flag 69 | * @param flag Flag to set 70 | */ 71 | void set(FlagEnum flag) { 72 | value |= static_cast(flag); 73 | } 74 | 75 | /** 76 | * Clear flag 77 | * @param flag Flag to clear 78 | */ 79 | void unset(FlagEnum flag) { 80 | value &= ~static_cast(flag); 81 | } 82 | }; 83 | 84 | template 85 | Flags operator|(Flags flags, E const& flag) { 86 | flags.set(flag); 87 | return flags; 88 | } 89 | 90 | template 91 | Flags operator|(Flags flags, Flags const& other) { 92 | flags.value |= other.value; 93 | return flags; 94 | } 95 | 96 | template 97 | inline std::ostream& operator<<(std::ostream& os, Flags flags) { 98 | if (!flags.value) { 99 | return os << "0"; 100 | } 101 | bool first = true; 102 | for(unsigned i = 0; i < sizeof(Type)*8; ++i) { 103 | if ((flags.value >> i) & 0x01) { 104 | if (!first) { 105 | os << " | "; 106 | } 107 | os << static_cast(0x01 << i); 108 | first = false; 109 | } 110 | } 111 | return os; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /icons/threema.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 18 | 20 | image/svg+xml 21 | 23 | 24 | 25 | 26 | 27 | 30 | 36 | 42 | 48 | 57 | 64 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /src/api/HttpManager.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include "HttpClient.h" 22 | 23 | namespace ceema { 24 | 25 | /** 26 | * Manages various instances of HttpClient using curl_multi interface 27 | */ 28 | class HttpManager { 29 | protected: 30 | CURLM* m_handle; 31 | std::string m_cert; 32 | 33 | std::unordered_set m_clients; 34 | int m_running_handles; 35 | public: 36 | HttpManager(); 37 | 38 | virtual ~HttpManager(); 39 | 40 | virtual void* registerRead(int fd, void* ptr) = 0; 41 | virtual void* registerWrite(int fd, void* ptr) = 0; 42 | virtual void* unregister(int fd, void* ptr) = 0; 43 | 44 | virtual void registerTimeout(long timeout_ms) = 0; 45 | 46 | void set_cert(std::string cert) { 47 | m_cert = cert; 48 | } 49 | 50 | HttpClient& getFreeClient(); 51 | 52 | void queueClient(HttpClient& client) { 53 | if (!client.getBusy()) { 54 | throw std::runtime_error("Attempt to queue taskless HTTP client"); 55 | } 56 | curl_multi_add_handle(m_handle, client.getCURL()); 57 | } 58 | 59 | protected: 60 | void checkMessages() { 61 | CURLMsg* m; 62 | do { 63 | int msgq = 0; 64 | m = curl_multi_info_read(m_handle, &msgq); 65 | if(m && (m->msg == CURLMSG_DONE)) { 66 | curl_multi_remove_handle(m_handle, m->easy_handle); 67 | 68 | HttpClient* client; 69 | curl_easy_getinfo(m->easy_handle, CURLINFO_PRIVATE, &client); 70 | client->completeTask(m->data.result); 71 | } 72 | } while(m); 73 | 74 | } 75 | 76 | static void timer_callback(CURLM *multi, long timeout_ms, void *userp) { 77 | ceema::HttpManager* mgr = static_cast(userp); 78 | mgr->registerTimeout(timeout_ms); 79 | } 80 | 81 | static int socket_callback(CURL *easy, curl_socket_t s, int action, void *userp, void *socketp) { 82 | ceema::HttpManager* mgr = static_cast(userp); 83 | switch(action) { 84 | case CURL_POLL_IN: 85 | socketp = mgr->registerRead(s, socketp); 86 | break; 87 | case CURL_POLL_OUT: 88 | socketp = mgr->registerWrite(s, socketp); 89 | break; 90 | case CURL_POLL_INOUT: 91 | socketp = mgr->registerRead(s, socketp); 92 | socketp = mgr->registerWrite(s, socketp); 93 | break; 94 | case CURL_POLL_REMOVE: 95 | socketp = mgr->unregister(s, socketp); 96 | break; 97 | case CURL_POLL_NONE: 98 | default: 99 | break; 100 | } 101 | 102 | curl_multi_assign(mgr->m_handle, s, socketp); 103 | 104 | return CURLM_OK; 105 | } 106 | }; 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/threepl/ContactStore.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 15/03/17. 3 | // 4 | 5 | #include "ContactStore.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | ContactStore::ContactStore(ceema::IdentAPI& ident_api) : m_ident_api(ident_api) {} 12 | 13 | bool ContactStore::has_contact(ceema::client_id const& id) { 14 | auto search = m_idmap.find(id); 15 | return (search != m_idmap.end()); 16 | } 17 | 18 | ContactPtr ContactStore::get_contact(ceema::client_id const& id) { 19 | return m_idmap.find(id)->second; 20 | //return m_idmap[id]; 21 | } 22 | 23 | void ContactStore::add_contact(ceema::Contact const& contact) { 24 | m_idmap.emplace(contact.id(), std::make_shared(contact)); 25 | } 26 | 27 | ceema::future ContactStore::fetch_contact(ceema::client_id id) { 28 | if (has_contact(id)) { 29 | ceema::promise prom; 30 | prom.set_value(m_idmap[id]); 31 | return prom.get_future(); 32 | } 33 | auto fut_contact = m_ident_api.getClientInfo(id.toString()); 34 | return fut_contact.next([this, id](ceema::future fut) { 35 | try { 36 | auto contact_ptr = std::make_shared(fut.get()); 37 | m_idmap.emplace(id, contact_ptr); 38 | return contact_ptr; 39 | } catch (std::exception& e) { 40 | LOG_DBG("Failed to fetch contact: " << e.what()); 41 | m_idmap.emplace(id, nullptr); 42 | throw; //std::rethrow_exception(std::current_exception()); 43 | } 44 | }); 45 | } 46 | 47 | void ContactStore::update_buddies(PurpleAccount* account) const { 48 | for(auto& contact_pair: m_idmap) { 49 | 50 | auto idStr = contact_pair.first.toString(); 51 | PurpleBuddy* buddy = purple_find_buddy(account, idStr.c_str()); 52 | if (buddy) { 53 | if (!contact_pair.second) { 54 | purple_blist_node_set_string(&buddy->node, "public-key", 55 | "!"); 56 | } else { 57 | purple_blist_node_set_string(&buddy->node, "public-key", 58 | ceema::hex_encode( 59 | contact_pair.second->pk()).c_str()); 60 | } 61 | } 62 | } 63 | } 64 | 65 | void ContactStore::load_buddies(PurpleAccount* account) { 66 | PurpleConnection* gc = purple_account_get_connection(account); 67 | 68 | for (GSList* buddies = purple_find_buddies(account, NULL); buddies; 69 | buddies = g_slist_delete_link(buddies, buddies)) { 70 | PurpleBuddy* buddy = static_cast(buddies->data); 71 | const char* client_id_str = purple_buddy_get_name(buddy); 72 | 73 | // Retrieve last-known alias 74 | const char* nickname = purple_blist_node_get_string(&buddy->node, "nickname"); 75 | if (nickname) { 76 | serv_got_alias(gc, client_id_str, nickname); 77 | } 78 | 79 | // Convert PK to Contact 80 | ceema::client_id id; 81 | try { 82 | id = ceema::client_id::fromString(client_id_str); 83 | } catch (std::runtime_error& e) { 84 | // Invalid ID, broken buddy 85 | continue; 86 | } 87 | if (!has_contact(id)) { 88 | const char *pk = purple_blist_node_get_string(&buddy->node, "public-key"); 89 | if (pk) { 90 | if (*pk == '!') { 91 | m_idmap.emplace(id, nullptr); 92 | } else { 93 | std::string pk_string{pk}; 94 | add_contact(ceema::Contact(id, ceema::public_key{ 95 | ceema::hex_decode(pk_string)})); 96 | } 97 | } 98 | } 99 | 100 | if (has_contact(id)) { 101 | purple_prpl_got_user_status(account, purple_buddy_get_name(buddy), "online", NULL, NULL); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/MessagePayload.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "protocol/packet/messagetypes.h" 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | using json = nlohmann::json; 31 | 32 | namespace ceema { 33 | 34 | struct PayloadNone { 35 | static constexpr MessageType Type = MessageType::NONE; 36 | static constexpr MessageFlags default_flags() { 37 | return MessageFlags(); 38 | } 39 | static PayloadNone deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 40 | byte_vector serialize() const; 41 | }; 42 | 43 | /** 44 | * Class representing a union of all possible Payload types, 45 | * with corresponding (templated) getters. 46 | */ 47 | struct MessagePayload { 48 | mpark::variant< 49 | PayloadNone, 50 | 51 | PayloadText, 52 | PayloadPicture, 53 | PayloadLocation, 54 | PayloadVideo, 55 | PayloadAudio, 56 | PayloadPoll, 57 | PayloadPollVote, 58 | PayloadFile, 59 | 60 | PayloadIcon, 61 | PayloadIconClear, 62 | 63 | PayloadGroupText, 64 | PayloadGroupLocation, 65 | PayloadGroupPicture, 66 | PayloadGroupVideo, 67 | PayloadGroupAudio, 68 | PayloadGroupFile, 69 | 70 | PayloadGroupMembers, 71 | PayloadGroupTitle, 72 | PayloadGroupLeave, 73 | 74 | PayloadGroupIcon, 75 | PayloadGroupSync, 76 | PayloadGroupPoll, 77 | PayloadGroupPollVote, 78 | 79 | PayloadMessageStatus, 80 | PayloadTyping 81 | > m_payload; 82 | 83 | MessagePayload() : m_payload(PayloadNone{}) {} 84 | 85 | template 86 | explicit MessagePayload(Payload && payload) : m_payload(std::forward(payload)) {} 87 | 88 | template 89 | auto& get() { 90 | return mpark::get(m_payload); 91 | } 92 | template 93 | auto const& get() const { 94 | return mpark::get(m_payload); 95 | } 96 | 97 | MessageType get_type() const { 98 | return mpark::visit([](const auto& x) -> MessageType { return std::decay::type::Type; }, m_payload); 99 | } 100 | 101 | MessageFlags default_flags() const { 102 | return mpark::visit([](const auto& x) -> MessageFlags { return std::decay::type::default_flags(); }, m_payload); 103 | } 104 | 105 | static MessagePayload deserialize(byte_vector const& payload_data); 106 | 107 | byte_vector serialize() { 108 | return mpark::visit([](const auto& x) -> byte_vector { return x.serialize(); }, m_payload); 109 | } 110 | 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /src/threepl/prpl/commands.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by harold on 20/05/17. 3 | // 4 | 5 | #include 6 | #include 7 | #include 8 | #include "threepl/ThreeplConnection.h" 9 | 10 | class Command { 11 | PurpleCmdId m_id; 12 | public: 13 | explicit Command(PurpleCmdId id) : m_id(id) {} 14 | ~Command() { 15 | if (m_id != 0) { 16 | purple_cmd_unregister(m_id); 17 | } 18 | } 19 | 20 | Command(Command const&) = delete; 21 | Command(Command && other) : m_id(other.m_id) { 22 | other.m_id = 0; 23 | } 24 | 25 | Command& operator=(Command const&) = delete; 26 | Command& operator=(Command && other) { 27 | std::swap(m_id, other.m_id); 28 | return *this; 29 | } 30 | }; 31 | 32 | static std::vector commands; 33 | 34 | PurpleCmdRet threepl_message_agree(PurpleConversation * conv, bool agree) { 35 | PurpleAccount* acct = purple_conversation_get_account(conv); 36 | PurpleConnection* gc = purple_account_get_connection(acct); 37 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 38 | 39 | const char* who = purple_conversation_get_name(conv); 40 | 41 | try { 42 | if (connection->send_agreement(ceema::client_id::fromString(who), 43 | agree)) { 44 | return PURPLE_CMD_RET_OK; 45 | } 46 | } catch (std::runtime_error& e) { 47 | // Invalid ID 48 | return PURPLE_CMD_RET_FAILED; 49 | } 50 | 51 | return PURPLE_CMD_RET_FAILED; 52 | } 53 | 54 | PurpleCmdRet threepl_command_agree(PurpleConversation * conv, const gchar *, 55 | gchar **, gchar **, void *) { 56 | return threepl_message_agree(conv, true); 57 | } 58 | 59 | PurpleCmdRet threepl_command_disagree(PurpleConversation * conv, const gchar *, 60 | gchar **, gchar **, void *) { 61 | return threepl_message_agree(conv, false); 62 | } 63 | 64 | PurpleCmdRet threepl_command_sync(PurpleConversation * conv, const gchar *, 65 | gchar **, gchar **, void *) { 66 | PurpleAccount* acct = purple_conversation_get_account(conv); 67 | PurpleConnection* gc = purple_account_get_connection(acct); 68 | ThreeplConnection* connection = static_cast(purple_connection_get_protocol_data(gc)); 69 | 70 | PurpleConvChat* conv_chat = PURPLE_CONV_CHAT(conv); 71 | int id = purple_conv_chat_get_id(conv_chat); 72 | ThreeplGroup* group_data = connection->group_store().find_group(id); 73 | if (group_data && group_data->owner() != connection->account().id()) { 74 | ceema::PayloadGroupSync payload; 75 | payload.group = group_data->gid(); 76 | connection->send_message(group_data->owner(), payload); 77 | } else{ 78 | return PURPLE_CMD_RET_FAILED; 79 | } 80 | 81 | return PURPLE_CMD_RET_OK; 82 | } 83 | 84 | void threepl_register_commands(PurplePlugin*) { 85 | if (!commands.empty()) { 86 | return; 87 | } 88 | 89 | PurpleCmdId id = purple_cmd_register("disagree", "", PURPLE_CMD_P_PRPL, 90 | static_cast(PURPLE_CMD_FLAG_IM ), 91 | "prpl-threepl", threepl_command_disagree, 92 | "disagree: Disagree with last message received.", nullptr); 93 | commands.emplace_back(id); 94 | id = purple_cmd_register("agree", "", PURPLE_CMD_P_PRPL, 95 | static_cast(PURPLE_CMD_FLAG_IM ), 96 | "prpl-threepl", threepl_command_agree, 97 | "agree: Agree with last message received.", nullptr); 98 | commands.emplace_back(id); 99 | 100 | id = purple_cmd_register("sync", "", PURPLE_CMD_P_PRPL, 101 | static_cast(PURPLE_CMD_FLAG_CHAT ), 102 | "prpl-threepl", threepl_command_sync, 103 | "sync: Request group sync.", nullptr); 104 | commands.emplace_back(id); 105 | } 106 | 107 | void threepl_unregister_commands(PurplePlugin*) { 108 | commands.clear(); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /src/protocol/packet/Message.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include "types/bytes.h" 22 | #include "protocol/protocol.h" 23 | #include "Packet.h" 24 | #include "messagetypes.h" 25 | #include "message_id.h" 26 | #include "contact/Contact.h" 27 | #include "protocol/packet/payloads/MessagePayload.h" 28 | #include "Acknowledgement.h" 29 | #include "MessageFlag.h" 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | namespace ceema { 39 | typedef std::uint32_t timestamp; 40 | 41 | const unsigned NICKNAME_SIZE = 32u; 42 | 43 | class Message : public Packet { 44 | client_id m_sender; 45 | client_id m_recipient; 46 | 47 | message_id m_id; 48 | 49 | timestamp m_time; 50 | 51 | MessageFlags m_flags; 52 | 53 | std::string m_nick; 54 | 55 | nonce m_nonce; 56 | 57 | byte_vector m_payloadData; 58 | 59 | MessagePayload m_payload; 60 | 61 | public: 62 | template 63 | Message(client_id const& sender, client_id const& recipient, 64 | Payload && payload) : 65 | Packet(PacketType::MESSAGE_SEND), m_sender(sender), 66 | m_recipient(recipient), m_id(gen_message_id()), 67 | m_time(static_cast( 68 | std::chrono::duration_cast( 69 | std::chrono::system_clock::now().time_since_epoch()).count())), 70 | m_flags(payload.default_flags()), m_nick(sender.toString()), m_nonce(crypto::generate_nonce()), 71 | m_payload(std::forward(payload)) 72 | { 73 | } 74 | 75 | client_id const& sender() const { 76 | return m_sender; 77 | } 78 | client_id const& recipient() const { 79 | return m_recipient; 80 | } 81 | 82 | message_id const& id() const { 83 | return m_id; 84 | } 85 | 86 | timestamp const& time() const { 87 | return m_time; 88 | } 89 | MessageFlags const& flags() const { 90 | return m_flags; 91 | } 92 | MessageFlags& flags() { 93 | return m_flags; 94 | } 95 | 96 | std::string const& nick() const { 97 | return m_nick; 98 | } 99 | 100 | std::string& nick() { 101 | return m_nick; 102 | } 103 | 104 | nonce const& data_nonce() const { 105 | return m_nonce; 106 | } 107 | 108 | MessageType payloadType() const { 109 | return m_payload.get_type(); 110 | } 111 | 112 | template 113 | auto& payload() { 114 | return m_payload.get(); 115 | } 116 | 117 | template 118 | auto const& payload() const { 119 | return m_payload.get(); 120 | } 121 | 122 | Acknowledgement generateAck() const { 123 | return Acknowledgement(sender(), id()); 124 | } 125 | 126 | static Message fromPacket(PacketType type, byte_vector const& packet); 127 | 128 | byte_vector toPacket() const; 129 | 130 | void encrypt(Account const& sender, Contact const& recipient); 131 | 132 | void decrypt(Contact const& sender, Account const& recipient); 133 | 134 | private: 135 | Message(); 136 | }; 137 | 138 | 139 | 140 | 141 | } 142 | -------------------------------------------------------------------------------- /src/types/ptr_array.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | 24 | template 25 | struct ptr_array { 26 | typedef T value_type; 27 | typedef T *pointer; 28 | typedef const T *const_pointer; 29 | typedef value_type &reference; 30 | typedef const value_type &const_reference; 31 | typedef pointer iterator; 32 | typedef const_pointer const_iterator; 33 | typedef std::size_t size_type; 34 | typedef std::ptrdiff_t difference_type; 35 | typedef std::reverse_iterator reverse_iterator; 36 | typedef std::reverse_iterator const_reverse_iterator; 37 | 38 | iterator m_begin; 39 | iterator m_end; 40 | 41 | ptr_array(iterator begin, size_type size) : m_begin(begin), m_end(begin + size) {} 42 | 43 | void fill(const value_type &__u) { std::fill_n(begin(), size(), __u); } 44 | 45 | void 46 | swap(ptr_array& other) noexcept { 47 | std::swap(m_begin, other.m_begin); 48 | std::swap(m_end, other.m_end); 49 | } 50 | 51 | // Iterators. 52 | iterator 53 | begin() noexcept { return iterator(m_begin); } 54 | 55 | const_iterator 56 | begin() const noexcept { return const_iterator(m_begin); } 57 | 58 | iterator 59 | end() noexcept { return iterator(m_end); } 60 | 61 | const_iterator 62 | end() const noexcept { return const_iterator(m_end); } 63 | 64 | reverse_iterator 65 | rbegin() noexcept { return reverse_iterator(end()); } 66 | 67 | const_reverse_iterator 68 | rbegin() const noexcept { return const_reverse_iterator(end()); } 69 | 70 | reverse_iterator 71 | rend() noexcept { return reverse_iterator(begin()); } 72 | 73 | const_reverse_iterator 74 | rend() const noexcept { return const_reverse_iterator(begin()); } 75 | 76 | const_iterator 77 | cbegin() const noexcept { return const_iterator(begin()); } 78 | 79 | const_iterator 80 | cend() const noexcept { return const_iterator(end()); } 81 | 82 | const_reverse_iterator 83 | crbegin() const noexcept { return const_reverse_iterator(end()); } 84 | 85 | const_reverse_iterator 86 | crend() const noexcept { return const_reverse_iterator(begin()); } 87 | 88 | // Capacity. 89 | size_type 90 | size() const noexcept { return m_end - m_begin; } 91 | 92 | size_type 93 | max_size() const noexcept { return size(); } 94 | 95 | bool 96 | empty() const noexcept { return size() == 0; } 97 | 98 | // Element access. 99 | reference 100 | operator[](size_type __n) { return *(begin() + __n); } 101 | 102 | constexpr const_reference 103 | operator[](size_type __n) const noexcept { return *(cbegin() + __n); } 104 | 105 | reference 106 | at(size_type __n) { 107 | return *(begin() + __n); 108 | } 109 | 110 | const_reference 111 | at(size_type __n) const { 112 | return *(cbegin() + __n); 113 | } 114 | 115 | reference 116 | front() { return *begin(); } 117 | 118 | const_reference 119 | front() const { return *begin(); } 120 | 121 | reference 122 | back() { return size() ? *(end() - 1) : *end(); } 123 | 124 | const_reference 125 | back() const { return size() ? *(end() - 1) : *end(); } 126 | 127 | pointer 128 | data() noexcept { return begin(); } 129 | 130 | const_pointer 131 | data() const noexcept { return cbegin(); } 132 | }; 133 | 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadGroup.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | namespace ceema { 27 | struct PayloadGroupMessage { 28 | static /*constexpr*/ MessageFlags default_flags() { 29 | return MessageFlags{MessageFlag::GROUP}; 30 | } 31 | 32 | group_uid group; 33 | }; 34 | 35 | template 36 | struct PayloadGroupBase : public PayloadGroupMessage, public Payload { 37 | static /*constexpr*/ MessageFlags default_flags() { 38 | auto flags = Payload::default_flags(); 39 | return flags | MessageFlag::GROUP; 40 | } 41 | 42 | static Impl deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 43 | if (size < group_uid::array_size) { 44 | throw std::runtime_error("Invalid group payload"); 45 | } 46 | 47 | Impl payload; 48 | payload_data = copy_iter(payload_data, payload.group); 49 | size -= group_uid::array_size; 50 | static_cast(payload) = Payload::deserialize(payload_data, size); 51 | 52 | return payload; 53 | } 54 | 55 | byte_vector serialize() const { 56 | byte_vector data = Payload::serialize(); 57 | data.insert(data.begin(), group.begin(), group.end()); 58 | return data; 59 | } 60 | }; 61 | 62 | struct PayloadGroupText : public PayloadGroupBase { 63 | static constexpr MessageType Type = MessageType::GROUP_TEXT; 64 | }; 65 | 66 | struct PayloadGroupLocation : public PayloadGroupBase { 67 | static constexpr MessageType Type = MessageType::GROUP_LOCATION; 68 | }; 69 | 70 | struct PayloadGroupPicture : public PayloadGroupMessage { 71 | static constexpr MessageType Type = MessageType::GROUP_PICTURE; 72 | 73 | static /*constexpr*/ MessageFlags default_flags() { 74 | return MessageFlags{MessageFlag::PUSH, MessageFlag::GROUP}; 75 | } 76 | 77 | blob_id id; 78 | blob_size size; 79 | shared_key key; 80 | 81 | static PayloadGroupPicture deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 82 | byte_vector serialize() const; 83 | }; 84 | 85 | struct PayloadGroupVideo : public PayloadGroupBase { 86 | static constexpr MessageType Type = MessageType::GROUP_VIDEO; 87 | }; 88 | 89 | struct PayloadGroupAudio : public PayloadGroupBase { 90 | static constexpr MessageType Type = MessageType::GROUP_AUDIO; 91 | }; 92 | 93 | struct PayloadGroupFile : public PayloadGroupBase { 94 | static constexpr MessageType Type = MessageType::GROUP_FILE; 95 | }; 96 | 97 | struct PayloadGroupPoll : public PayloadGroupBase { 98 | static constexpr MessageType Type = MessageType::GROUP_POLL; 99 | }; 100 | 101 | struct PayloadGroupPollVote : public PayloadGroupBase { 102 | static constexpr MessageType Type = MessageType::GROUP_POLL_VOTE; 103 | }; 104 | 105 | struct PayloadGroupLeave : public PayloadGroupMessage { 106 | static constexpr MessageType Type = MessageType::GROUP_LEAVE; 107 | 108 | static PayloadGroupLeave deserialize(byte_vector::const_iterator& payload_data, std::size_t size); 109 | byte_vector serialize() const; 110 | }; 111 | } -------------------------------------------------------------------------------- /src/protocol/session.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "protocol.h" 20 | 21 | #include "contact/Contact.h" 22 | #include "socket/socket.h" 23 | #include "protocol/packet/Packet.h" 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | namespace ceema { 30 | const unsigned PROTO_NONCE_PREFIX_SIZE = 16u; 31 | using nonce_prefix = byte_array; 32 | 33 | /** Public key of server (fixed) */ 34 | extern public_key const serverPK; 35 | extern public_key const serverPKGateway; 36 | 37 | inline nonce make_nonce(nonce_prefix const &prefix, std::uint64_t seq) { 38 | nonce n; 39 | auto iter = std::copy(prefix.begin(), prefix.end(), n.begin()); 40 | htole(seq, iter); 41 | return n; 42 | } 43 | 44 | /** 45 | * Session keeps track of nonces, temporary server key and message sequence IDs 46 | */ 47 | class Session { 48 | public: 49 | enum class State { 50 | DISCONNECTED, 51 | WAIT_CONNECT, 52 | WAIT_HELLO, 53 | WAIT_AUTH, 54 | WAIT_PKT_HEADER, 55 | WAIT_PKT 56 | }; 57 | 58 | private: 59 | // Nonce information 60 | nonce_prefix m_noncePrefix; 61 | nonce_prefix m_serverNoncePrefix; 62 | std::uint64_t m_counter; 63 | std::uint64_t m_serverCounter; 64 | 65 | // Session (short term) keypair, public + secret 66 | public_key m_sessionPK; 67 | private_key m_sessionSK; 68 | public_key m_sessionServerPK; 69 | 70 | // Contact data 71 | Account m_client; 72 | 73 | // The connection to server 74 | public_key m_serverPK; 75 | TcpClient m_socket; 76 | bool m_useProxy; 77 | State m_state; 78 | 79 | // Buffers for socket data 80 | std::size_t m_nextReadSize; 81 | byte_vector m_readBuffer; 82 | byte_vector m_writeBuffer; 83 | 84 | // Packet buffer 85 | std::deque> m_packetQueue; 86 | 87 | public: 88 | Session(Account const &client); 89 | 90 | /** 91 | * Connect to remote server and perform handshake. Throws exception on error 92 | * @param hostname 93 | * @param port 94 | * @param serverPK 95 | */ 96 | void connect(TcpClient socket, bool useProxy = true); 97 | 98 | void terminate(); 99 | 100 | // Returns if not EOF 101 | bool onReadyRead(); 102 | void onReadyWrite(); 103 | 104 | bool has_packet() const; 105 | std::unique_ptr get_packet(); 106 | void send_packet(Packet const& packet); 107 | 108 | State getState() const { 109 | return m_state; 110 | } 111 | 112 | bool hasWriteData() const { 113 | return m_writeBuffer.size() != 0; 114 | } 115 | 116 | Account const& client() const { 117 | return m_client; 118 | } 119 | protected: 120 | /** 121 | * Send client hello packet (nonce prefix and session PK) 122 | */ 123 | void send_hello(); 124 | 125 | /** 126 | * Receive server hello packet (nonce prefix and session PK) 127 | */ 128 | void read_hello(); 129 | 130 | /** 131 | * Send acknowledgement and client info 132 | */ 133 | void send_auth(); 134 | 135 | /** 136 | * Finish handshake 137 | */ 138 | void read_ack(); 139 | 140 | nonce nextClientNonce(); 141 | 142 | nonce nextServerNonce(); 143 | 144 | void read_packet(std::uint16_t length); 145 | void send_packet(byte_vector const& data); 146 | 147 | void packetReady(); 148 | }; 149 | 150 | } 151 | -------------------------------------------------------------------------------- /src/types/bytes.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include "slice.h" 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace ceema { 30 | 31 | /** Simple vector for bytes */ 32 | typedef std::vector byte_vector; 33 | 34 | /** Simple fixed size byte array */ 35 | template 36 | struct byte_array : public std::array { 37 | static constexpr std::size_t array_size = N; 38 | 39 | byte_array() = default; 40 | 41 | byte_array(byte_array const &) = default; 42 | 43 | byte_array(byte_array &&) = default; 44 | 45 | template 46 | explicit byte_array(slice const& slice) { 47 | std::copy(slice.begin(), slice.end(), this->begin()); 48 | } 49 | template 50 | explicit byte_array(slice && slice) { 51 | std::copy(slice.begin(), slice.end(), this->begin()); 52 | } 53 | 54 | explicit byte_array(byte_vector const& vec) { 55 | auto size = std::min(N, vec.size()); 56 | std::copy(vec.begin(), vec.begin() + size, this->begin()); 57 | } 58 | explicit byte_array(byte_vector&& vec) { 59 | auto size = std::min(N, vec.size()); 60 | std::copy(vec.begin(), vec.begin() + size, this->begin()); 61 | } 62 | 63 | byte_array& operator=(byte_array const &) = default; 64 | 65 | byte_array& operator=(byte_array &&) = default; 66 | 67 | template 68 | byte_array& operator=(slice const& slice) { 69 | std::copy(slice.begin(), slice.end(), this->begin()); 70 | return *this; 71 | } 72 | 73 | template 74 | explicit byte_array(Args const&&...args) : std::array{{std::forward(args)...}} {} 75 | }; 76 | 77 | } 78 | 79 | 80 | namespace std { 81 | 82 | template 83 | inline ostream &operator<<(ostream &os, ceema::byte_array const &array) { 84 | auto flags = os.setf(ios::hex); 85 | auto fill = os.fill('0'); 86 | for (auto const &element: array) { 87 | os << hex << setw(2) << static_cast(element); 88 | } 89 | os.flags(flags); 90 | os.fill(fill); 91 | return os; 92 | } 93 | 94 | template 95 | inline basic_ostream<_CharT, _Traits>& 96 | operator<<(basic_ostream<_CharT, _Traits>& os, ceema::byte_vector const &vec) { 97 | auto flags = os.setf(ios::hex); 98 | auto fill = os.fill('0'); 99 | for (auto const &element: vec) { 100 | os << hex << setw(2) << static_cast(element); 101 | } 102 | os.flags(flags); 103 | os.fill(fill); 104 | return os; 105 | } 106 | 107 | template 108 | inline typename enable_if::value, ostream &>::type 109 | operator<<(ostream &os, ceema::slice const& slice) { 110 | auto flags = os.setf(ios::hex); 111 | auto fill = os.fill('0'); 112 | for (auto const &element: slice) { 113 | os << hex << setw(2) << (unsigned int) element; 114 | } 115 | os.flags(flags); 116 | os.fill(fill); 117 | return os; 118 | } 119 | 120 | template 121 | inline typename enable_if::value, ostream &>::type 122 | operator<<(ostream &os, ceema::slice const& slice) { 123 | for (auto const &element: slice) { 124 | os << element; 125 | } 126 | return os; 127 | } 128 | 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/PayloadGroupControl.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "PayloadGroupControl.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace ceema { 25 | PayloadGroupMembers PayloadGroupMembers::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 26 | if (size < group_id::array_size || (size - group_id::array_size) % client_id::array_size != 0) { 27 | throw std::runtime_error("Invalid group create payload"); 28 | } 29 | 30 | PayloadGroupMembers payload; 31 | payload_data = copy_iter(payload_data, payload.group); 32 | payload.members.resize((size-group_id::array_size) / client_id::array_size); 33 | for(auto& member: payload.members) { 34 | payload_data = copy_iter(payload_data, member); 35 | } 36 | 37 | return payload; 38 | } 39 | 40 | byte_vector PayloadGroupMembers::serialize() const { 41 | byte_vector res; 42 | res.resize(group_id::array_size + members.size()*client_id::array_size); 43 | auto iter = res.begin(); 44 | iter = std::copy(group.begin(), group.end(), iter); 45 | for(auto const& id: members) { 46 | iter = std::copy(id.begin(), id.end(), iter); 47 | } 48 | 49 | return res; 50 | } 51 | 52 | PayloadGroupTitle PayloadGroupTitle::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 53 | if (size < group_id::array_size) { 54 | throw std::runtime_error("Invalid group title payload"); 55 | } 56 | auto payload_end = payload_data + size; 57 | 58 | PayloadGroupTitle payload; 59 | payload_data = copy_iter(payload_data, payload.group); 60 | payload.title = std::string(payload_data, payload_end); 61 | 62 | return payload; 63 | } 64 | 65 | byte_vector PayloadGroupTitle::serialize() const { 66 | byte_vector res; 67 | res.resize(group_id::array_size + title.size()); 68 | auto iter = res.begin(); 69 | iter = std::copy(group.begin(), group.end(), iter); 70 | std::copy(title.begin(), title.end(), iter); 71 | 72 | return res; 73 | } 74 | 75 | PayloadGroupIcon PayloadGroupIcon::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 76 | if (size != group_id::array_size) { 77 | throw std::runtime_error("Invalid group sync payload"); 78 | } 79 | 80 | PayloadGroupIcon payload; 81 | copy_iter(payload_data, payload.group); 82 | 83 | payload_data = copy_iter(payload_data, payload.id); 84 | letoh(payload.size, &*payload_data); 85 | payload_data += sizeof(payload.size); 86 | payload_data = copy_iter(payload_data, payload.key); 87 | 88 | return payload; 89 | } 90 | 91 | byte_vector PayloadGroupIcon::serialize() const { 92 | byte_vector res; 93 | res.resize(group_id::array_size + blob_id::array_size + sizeof(blob_size) + shared_key::array_size); 94 | auto iter = res.begin(); 95 | iter = std::copy(group.begin(), group.end(), iter); 96 | iter = std::copy(id.begin(), id.end(), iter); 97 | htole(size, &*iter); 98 | std::copy(key.begin(), key.end(), iter); 99 | 100 | return res; 101 | } 102 | 103 | PayloadGroupSync PayloadGroupSync::deserialize(byte_vector::const_iterator& payload_data, std::size_t size) { 104 | if (size != group_id::array_size) { 105 | throw std::runtime_error("Invalid group sync payload"); 106 | } 107 | 108 | PayloadGroupSync payload; 109 | copy_iter(payload_data, payload.group); 110 | 111 | return payload; 112 | } 113 | 114 | byte_vector PayloadGroupSync::serialize() const { 115 | byte_vector res; 116 | res.resize(group_id::array_size); 117 | auto iter = res.begin(); 118 | std::copy(group.begin(), group.end(), iter); 119 | 120 | return res; 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /src/contact/backup.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "backup.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace ceema { 25 | 26 | const unsigned HASH_LEN = 2u; 27 | const unsigned SALT_LEN = 8u; 28 | const unsigned PBKDF_ITER = 100000u; 29 | 30 | std::string make_backup(Account const& client, std::string const& password) { 31 | byte_array data; 32 | randombytes_buf(data.data(), SALT_LEN); 33 | auto iter = data.begin() + SALT_LEN; 34 | iter = std::copy(client.id().begin(), client.id().end(), iter); 35 | iter = std::copy(client.sk().begin(), client.sk().end(), iter); 36 | 37 | byte_array hash = hash_sha256(data.begin()+SALT_LEN, CLIENTID_SIZE + crypto_box_SECRETKEYBYTES); 38 | iter = std::copy(hash.begin(), hash.begin()+2, iter); 39 | 40 | // pbkdf2+HMAC hash the password, salt with client ID. Result is encryption key 41 | byte_array enckey; 42 | PBKDF2_SHA256(reinterpret_cast(password.data()), password.size(), 43 | data.data(), SALT_LEN, PBKDF_ITER, enckey.data(), enckey.size()); 44 | 45 | /* Encrypt backup with derived key and zero nonce */ 46 | byte_array nonce{}; 47 | crypto_stream_xor(data.data() + SALT_LEN, data.data() + SALT_LEN, 48 | CLIENTID_SIZE + crypto_box_SECRETKEYBYTES + HASH_LEN, nonce.data(), enckey.data()); 49 | 50 | std::string backupString = base32_encode(data); 51 | for(size_t i = backupString.size(); (i -= 4) > 3;) { 52 | backupString.insert(i, 1, '-'); 53 | } 54 | return backupString; 55 | } 56 | 57 | Account decrypt_backup(std::string const &backup_string, std::string const &password) { 58 | byte_array backup; 59 | 60 | /* decode Base32 */ 61 | if (base32_decode(backup_string, backup.data(), backup.size()) != backup.size()) { 62 | throw std::runtime_error("Invalid backup length"); 63 | } 64 | 65 | // 0.. 7 : salt 66 | auto salt = slice_array<0, SALT_LEN>(backup); 67 | // 8 .. 49 : encrypted data 68 | auto enc_data = slice_array(backup); 69 | 70 | // pbkdf2+HMAC hash the password, salt with client ID. Result is encryption key 71 | //TODO: when there is platform support, switch to libsodium implementation 72 | byte_array enckey; 73 | PBKDF2_SHA256(reinterpret_cast(password.data()), password.size(), 74 | salt.data(), salt.size(), PBKDF_ITER, enckey.data(), enckey.size()); 75 | 76 | /* Decrypt backup with derived key and zero nonce */ 77 | byte_array nonce{0}; 78 | crypto_stream_xor(enc_data.data(), enc_data.data(), enc_data.size(), nonce.data(), enckey.data()); 79 | 80 | // Backup now contains following data: 81 | // 8 .. 15 : client ID 82 | auto clientid = slice_array(backup); 83 | // 16 .. 47 : private key 84 | auto clientsk = slice_array(backup); 85 | // 48 .. 49 : hash prefix 86 | auto hash_prefix = slice_array(backup); 87 | 88 | /* Sanity check on result by comparing truncated SHA256 hash */ 89 | byte_array hash = hash_sha256(backup.begin() + SALT_LEN, CLIENTID_SIZE + crypto_box_SECRETKEYBYTES); 90 | 91 | if (!std::equal(hash_prefix.begin(), hash_prefix.end(), hash.begin())) { 92 | throw std::runtime_error("Backup corrupted"); // Invalid hash 93 | } 94 | 95 | client_id id; 96 | std::copy(clientid.begin(), clientid.end(), id.begin()); 97 | private_key sk; 98 | std::copy(clientsk.begin(), clientsk.end(), sk.begin()); 99 | return Account(id, sk); 100 | } 101 | } -------------------------------------------------------------------------------- /src/encoding/pbkdf2-sha256.c: -------------------------------------------------------------------------------- 1 | /** License of PBKDF2 */ 2 | /*- 3 | * Copyright 2005,2007,2009 Colin Percival 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions 8 | * are met: 9 | * 1. Redistributions of source code must retain the above copyright 10 | * notice, this list of conditions and the following disclaimer. 11 | * 2. Redistributions in binary form must reproduce the above copyright 12 | * notice, this list of conditions and the following disclaimer in the 13 | * documentation and/or other materials provided with the distribution. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 | * SUCH DAMAGE. 26 | */ 27 | 28 | /** store32_be license */ 29 | /* 30 | * ISC License 31 | * 32 | * Copyright (c) 2013-2018 33 | * Frank Denis 34 | * 35 | * Permission to use, copy, modify, and/or distribute this software for any 36 | * purpose with or without fee is hereby granted, provided that the above 37 | * copyright notice and this permission notice appear in all copies. 38 | * 39 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 40 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 41 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 42 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 43 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 44 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 45 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 46 | */ 47 | 48 | /** 49 | * Modified for use with ceema: 50 | * - Removed static compiler check 51 | * - replace STORE32_BE with libsodiums LE inline definition 52 | * - Adjust include paths 53 | */ 54 | 55 | #include "pbkdf2-sha256.h" 56 | 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | #include 63 | #include 64 | #include 65 | #include 66 | 67 | static inline void store32_be(uint8_t dst[4], uint32_t w) { 68 | dst[3] = (uint8_t) w; w >>= 8; 69 | dst[2] = (uint8_t) w; w >>= 8; 70 | dst[1] = (uint8_t) w; w >>= 8; 71 | dst[0] = (uint8_t) w; 72 | } 73 | 74 | /** 75 | * PBKDF2_SHA256(passwd, passwdlen, salt, saltlen, c, buf, dkLen): 76 | * Compute PBKDF2(passwd, salt, c, dkLen) using HMAC-SHA256 as the PRF, and 77 | * write the output to buf. The value dkLen must be at most 32 * (2^32 - 1). 78 | */ 79 | void 80 | PBKDF2_SHA256(const uint8_t *passwd, size_t passwdlen, const uint8_t *salt, 81 | size_t saltlen, uint64_t c, uint8_t *buf, size_t dkLen) 82 | { 83 | crypto_auth_hmacsha256_state PShctx, hctx; 84 | size_t i; 85 | uint8_t ivec[4]; 86 | uint8_t U[32]; 87 | uint8_t T[32]; 88 | uint64_t j; 89 | int k; 90 | size_t clen; 91 | 92 | crypto_auth_hmacsha256_init(&PShctx, passwd, passwdlen); 93 | crypto_auth_hmacsha256_update(&PShctx, salt, saltlen); 94 | 95 | for (i = 0; i * 32 < dkLen; i++) { 96 | store32_be(ivec, (uint32_t)(i + 1)); 97 | memcpy(&hctx, &PShctx, sizeof(crypto_auth_hmacsha256_state)); 98 | crypto_auth_hmacsha256_update(&hctx, ivec, 4); 99 | crypto_auth_hmacsha256_final(&hctx, U); 100 | 101 | memcpy(T, U, 32); 102 | /* LCOV_EXCL_START */ 103 | for (j = 2; j <= c; j++) { 104 | crypto_auth_hmacsha256_init(&hctx, passwd, passwdlen); 105 | crypto_auth_hmacsha256_update(&hctx, U, 32); 106 | crypto_auth_hmacsha256_final(&hctx, U); 107 | 108 | for (k = 0; k < 32; k++) { 109 | T[k] ^= U[k]; 110 | } 111 | } 112 | /* LCOV_EXCL_STOP */ 113 | 114 | clen = dkLen - i * 32; 115 | if (clen > 32) { 116 | clen = 32; 117 | } 118 | memcpy(&buf[i * 32], T, clen); 119 | } 120 | sodium_memzero((void *) &PShctx, sizeof PShctx); 121 | } 122 | -------------------------------------------------------------------------------- /src/protocol/data/Poll.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | using json = nlohmann::json; 25 | 26 | namespace ceema { 27 | 28 | /** 29 | * ID used by poll owner 30 | */ 31 | struct poll_id : byte_array<8> { 32 | using byte_array::byte_array; 33 | }; 34 | 35 | /** 36 | * Unique poll ID, both the owner ID and poll ID 37 | */ 38 | struct poll_uid : byte_array { 39 | using byte_array::byte_array; 40 | 41 | poll_id pid() const { 42 | return poll_id(slice_array(*this)); 43 | } 44 | 45 | client_id cid() const { 46 | return client_id(slice_array<0, client_id::array_size>(*this)); 47 | } 48 | }; 49 | 50 | enum class PollSelection { 51 | SINGLE = 0x00, 52 | MULTIPLE = 0x01 53 | }; 54 | 55 | enum class PollType { 56 | TEXT = 0x00 57 | }; 58 | 59 | enum class PollDisclose { 60 | ON_CLOSE = 0x00, 61 | INTERMEDIATE = 0x01 62 | }; 63 | 64 | enum class PollStatus { 65 | OPEN = 0x00, 66 | CLOSED = 0x01 67 | }; 68 | 69 | struct PollChoice { 70 | std::string name; 71 | unsigned id; 72 | unsigned order; 73 | // Contains the votes from each of the respondees 74 | std::vector votes; 75 | }; 76 | 77 | inline void to_json(json& j, PollChoice const& c) { 78 | json votes; 79 | for(bool v: c.votes) { 80 | votes.push_back(v?1:0); 81 | } 82 | j = json{{"i", c.id}, {"n", c.name}, {"o", c.order}, {"r", votes}}; 83 | } 84 | 85 | inline void from_json(json const& j, PollChoice& c) { 86 | c.name = j["n"].get(); 87 | c.id = j["i"].get(); 88 | c.order = j["o"].get(); 89 | for(auto const& vote: j["r"]) { 90 | c.votes.push_back(vote.get()?true:false); 91 | } 92 | } 93 | 94 | struct Poll { 95 | std::string description; 96 | PollSelection selection; 97 | PollType type; 98 | PollDisclose disclose; 99 | PollStatus status; 100 | 101 | std::vector choices; 102 | std::vector respondees; 103 | }; 104 | 105 | inline void to_json(json& j, Poll const& p) { 106 | j = json::object({ 107 | {"d", p.description}, 108 | {"s", static_cast(p.status)}, 109 | {"a", static_cast(p.selection)}, 110 | {"t", static_cast(p.disclose)}, 111 | {"o", static_cast(p.type)}, 112 | {"c", json::array()}, 113 | {"p", json::array()} 114 | }); 115 | json& jchoices = j["c"]; 116 | for(auto const& choice: p.choices) { 117 | jchoices.push_back(json{choice}); 118 | } 119 | json& participants = j["p"]; 120 | for(auto const& responder: p.respondees) { 121 | participants.push_back(responder.toString()); 122 | } 123 | } 124 | 125 | inline void from_json(json const& j, Poll& p) { 126 | p.description = j["d"].get(); 127 | p.status = static_cast(j["s"].get()); 128 | p.selection = static_cast(j["a"].get()); 129 | p.disclose = static_cast(j["t"].get()); 130 | p.type = static_cast(j["o"].get()); 131 | 132 | for(json const& choice: j["c"]) { 133 | p.choices.push_back(choice.get()); 134 | } 135 | for(auto const& participant: j["p"]) { 136 | p.respondees.push_back(client_id::fromString(participant)); 137 | } 138 | } 139 | 140 | struct PollVoteChoice { 141 | unsigned id; 142 | bool chosen; 143 | }; 144 | 145 | inline void to_json(json& j, PollVoteChoice const& c) { 146 | j = json::array({c.id, c.chosen?1:0}); 147 | } 148 | 149 | inline void from_json(json const& j, PollVoteChoice& c) { 150 | c.id = j[0].get(); 151 | c.chosen = j[1].get()?true:false; 152 | } 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/threepl/GroupStore.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 17/03/17. 3 | // 4 | 5 | #ifndef CEEMA_GROUPSTORE_H 6 | #define CEEMA_GROUPSTORE_H 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | class ThreeplConnection; 14 | 15 | class ThreeplGroup { 16 | ceema::client_id m_owner; 17 | ceema::group_id m_groupid; 18 | ceema::group_uid m_groupuid; 19 | std::string m_name; 20 | std::vector m_members; 21 | //TODO: icon path 22 | 23 | // (Unique) ID of the group, can be used as chat ID 24 | int m_id; 25 | 26 | public: 27 | /** 28 | * Create group from received data 29 | * @param owner 30 | * @param id 31 | */ 32 | ThreeplGroup(ceema::client_id owner, ceema::group_id gid, int id) : 33 | m_owner(owner), m_groupid(gid), m_groupuid(ceema::make_group_uid(m_owner, m_groupid)), 34 | m_members{owner}, m_id(id) 35 | { 36 | m_name = ceema::hex_encode(gid) + " owned by " + owner.toString(); 37 | } 38 | 39 | std::vector const& members() const { 40 | return m_members; 41 | } 42 | 43 | bool add_member(ceema::client_id const& member) { 44 | for(auto& cid: m_members) { 45 | if (cid == member) { 46 | return false; 47 | } 48 | } 49 | m_members.emplace_back(member); 50 | return true; 51 | } 52 | 53 | bool remove_member(ceema::client_id const& member) { 54 | for(auto iter = m_members.begin(); iter != m_members.end(); ++iter) { 55 | if (*iter == member) { 56 | m_members.erase(iter); 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | void set_members(std::vector const& members) { 64 | m_members = members; 65 | } 66 | 67 | 68 | std::string const& name() const { 69 | return m_name; 70 | } 71 | 72 | void set_name(std::string name); 73 | 74 | ceema::client_id owner() const { 75 | return m_owner; 76 | } 77 | 78 | ceema::group_uid const& uid() const { 79 | return m_groupuid; 80 | } 81 | 82 | ceema::group_id const& gid() const { 83 | return m_groupid; 84 | } 85 | 86 | int id() const { 87 | return m_id; 88 | } 89 | 90 | std::string chat_name() const { 91 | return chat_name(m_groupuid); 92 | } 93 | 94 | static std::string chat_name(ceema::group_uid uid) { 95 | return chat_name(uid.gid(), uid.cid()); 96 | } 97 | static std::string chat_name(ceema::group_id gid, ceema::client_id cid) { 98 | return ceema::hex_encode(gid) + "@" + cid.toString(); 99 | } 100 | 101 | PurpleConvChat* find_conversation(PurpleConnection *gc) const; 102 | PurpleConvChat* create_conversation(PurpleConnection *gc) const; 103 | void update_conversation(PurpleConvChat *conv) const; 104 | PurpleChat* find_blist_chat(PurpleAccount *account) const; 105 | void update_blist_chat(PurpleChat *chat) const; 106 | }; 107 | 108 | 109 | class GroupStore { 110 | // TODO: should be map, but no hash method yet 111 | std::vector m_groups; 112 | ThreeplConnection &m_conn; 113 | 114 | public: 115 | GroupStore(ThreeplConnection &conn) : m_conn(conn) {} 116 | 117 | ThreeplGroup* find_group(ceema::group_uid uid, bool add_if_new = false) { 118 | return find_group(uid.cid(), uid.gid(), add_if_new); 119 | } 120 | 121 | ThreeplGroup* find_group(ceema::client_id owner, ceema::group_id gid, bool add_if_new = false) { 122 | for(auto& group: m_groups) { 123 | if (group.gid() == gid && group.owner() == owner) { 124 | return &group; 125 | } 126 | } 127 | if (add_if_new) { 128 | return add_group(owner, gid); 129 | } else { 130 | return nullptr; 131 | } 132 | } 133 | 134 | ThreeplGroup* find_group(int id) { 135 | for(auto& group: m_groups) { 136 | if (group.id() == id) { 137 | return &group; 138 | } 139 | } 140 | return nullptr; 141 | } 142 | 143 | ThreeplGroup* find_or_create(GHashTable* components); 144 | 145 | ThreeplGroup* add_group(ceema::client_id owner, ceema::group_id id) { 146 | m_groups.emplace_back(owner, id, m_groups.size() + 1); 147 | return &m_groups.back(); 148 | } 149 | 150 | ThreeplGroup* add_group(ceema::group_uid uid) { 151 | m_groups.emplace_back(uid.cid(), uid.gid(), m_groups.size() + 1); 152 | return &m_groups.back(); 153 | } 154 | 155 | PurpleChat* find_chat(PurpleAccount* account, ThreeplGroup const& group) const; 156 | void update_chat(PurpleAccount* account, ThreeplGroup const& group) const; 157 | void update_chats(PurpleAccount* account) const; 158 | void load_chats(PurpleAccount* account); 159 | }; 160 | 161 | 162 | #endif //CEEMA_GROUPSTORE_H 163 | -------------------------------------------------------------------------------- /src/api/API.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "API.h" 18 | 19 | #include "logging/logging.h" 20 | 21 | namespace ceema { 22 | 23 | API::API(HttpManager& manager) : m_manager(manager) { 24 | m_manager.set_cert(api_cert); 25 | } 26 | 27 | future API::get(std::string const &url) { 28 | auto& client = m_manager.getFreeClient(); 29 | return client.get(url); 30 | } 31 | 32 | future API::get(std::string const &url, IHttpTransfer* transfer) { 33 | auto& client = m_manager.getFreeClient(); 34 | return client.get(url, transfer); 35 | } 36 | 37 | future API::post(std::string const &url, byte_vector const& data) { 38 | auto& client = m_manager.getFreeClient(); 39 | return client.post(url, data); 40 | } 41 | 42 | void API::postFile(std::string url, IHttpTransfer* transfer, std::string const& filename) { 43 | auto& client = m_manager.getFreeClient(); 44 | client.postFile(url, transfer, filename); 45 | } 46 | 47 | future API::postFile(std::string url, byte_vector const& data, std::string const& filename) { 48 | auto& client = m_manager.getFreeClient(); 49 | return client.postFile(url, data, filename); 50 | } 51 | 52 | future API::jsonGet(std::string const& url) { 53 | auto& client = m_manager.getFreeClient(); 54 | auto request_fut = client.get(url); 55 | auto json_fut = request_fut.next([](future fut) { 56 | byte_vector data = fut.get(); 57 | return checkJSONResult(json::parse(data.begin(), data.end())); 58 | }); 59 | 60 | return json_fut; 61 | } 62 | 63 | future API::jsonPost(std::string const& url, json request) { 64 | auto& client = m_manager.getFreeClient(); 65 | std::string jsonString = request.dump(); 66 | 67 | LOG_DBG("Submitting API request: " << request); 68 | 69 | auto request_fut = client.post(url, byte_vector(jsonString.begin(), jsonString.end())); 70 | auto json_fut = request_fut.next([](future fut) { 71 | byte_vector data = fut.get(); 72 | return checkJSONResult(json::parse(data.begin(), data.end())); 73 | }); 74 | 75 | return json_fut; 76 | } 77 | 78 | json API::checkJSONResult(json data) { 79 | LOG_DBG("Got JSON data for API: " << data); 80 | if (data.count("success") && !data["success"].get()) { 81 | throw std::runtime_error(data["error"].get().c_str()); 82 | } 83 | 84 | return data; 85 | } 86 | 87 | const char* API::api_cert = 88 | "-----BEGIN CERTIFICATE-----\n" 89 | "MIIEYTCCA0mgAwIBAgIJAM1DR/DBRFpQMA0GCSqGSIb3DQEBBQUAMH0xCzAJBgNV\n" 90 | "BAYTAkNIMQswCQYDVQQIEwJaSDEPMA0GA1UEBxMGWnVyaWNoMRAwDgYDVQQKEwdU\n" 91 | "aHJlZW1hMQswCQYDVQQLEwJDQTETMBEGA1UEAxMKVGhyZWVtYSBDQTEcMBoGCSqG\n" 92 | "SIb3DQEJARYNY2FAdGhyZWVtYS5jaDAeFw0xMjExMTMxMTU4NThaFw0zMjExMDgx\n" 93 | "MTU4NThaMH0xCzAJBgNVBAYTAkNIMQswCQYDVQQIEwJaSDEPMA0GA1UEBxMGWnVy\n" 94 | "aWNoMRAwDgYDVQQKEwdUaHJlZW1hMQswCQYDVQQLEwJDQTETMBEGA1UEAxMKVGhy\n" 95 | "ZWVtYSBDQTEcMBoGCSqGSIb3DQEJARYNY2FAdGhyZWVtYS5jaDCCASIwDQYJKoZI\n" 96 | "hvcNAQEBBQADggEPADCCAQoCggEBAK8GdoT7IpNC3Dz7IUGYW9pOBwx+9EnDZrkN\n" 97 | "VD8l3KfBHjGTdi9gQ6Nh+mQ9/yQ8254T2big9p0hcn8kjgEQgJWHpNhYnOhy3i0j\n" 98 | "cmlzb1MF/deFjJVtuMP3tqTwiMavpweoa20lGDn/CLZodu0Ra8oL78b6FVztNkWg\n" 99 | "PdiWClMk0JPPMlfLEiK8hfHE+6mRVXmi12itK1semmwyHKdj9fG4X9+rQ2sKuLfe\n" 100 | "jx7uFxnAF+GivCuCo8xfOesLw72vx+W7mmdYshg/lXOcqvszQQ/LmFEVQYxNaeeV\n" 101 | "nPSAs+ht8vUPW4sX9IkXKVgBJd1R1isUpoF6dKlUexmvLxEyf5cCAwEAAaOB4zCB\n" 102 | "4DAdBgNVHQ4EFgQUw6LaC7+J62rKdaTA37kAYYUbrkgwgbAGA1UdIwSBqDCBpYAU\n" 103 | "w6LaC7+J62rKdaTA37kAYYUbrkihgYGkfzB9MQswCQYDVQQGEwJDSDELMAkGA1UE\n" 104 | "CBMCWkgxDzANBgNVBAcTBlp1cmljaDEQMA4GA1UEChMHVGhyZWVtYTELMAkGA1UE\n" 105 | "CxMCQ0ExEzARBgNVBAMTClRocmVlbWEgQ0ExHDAaBgkqhkiG9w0BCQEWDWNhQHRo\n" 106 | "cmVlbWEuY2iCCQDNQ0fwwURaUDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUA\n" 107 | "A4IBAQARHMyIHBDFul+hvjACt6r0EAHYwR9GQSghIQsfHt8cyVczmEnJH9hrvh9Q\n" 108 | "Vivm7mrfveihmNXAn4WlGwQ+ACuVtTLxw8ErbST7IMAOx9npHf/kngnZ4nSwURF9\n" 109 | "rCEyHq179pNXpOzZ257E5r0avMNNXXDwulw03iBE21ebd00pG11GVq/I26s+8Bjn\n" 110 | "DKRPquKrSO4/luEDvL4ngiQjZp32S9Z1K9sVOzqtQ7I9zzeUADm3aVa/Bpaw4iMR\n" 111 | "1SI7o9aJYiRi1gxYP2BUA1IFqr8NzyfGD7tRHdq7bZOxXAluv81dcbz0SBX8SgV1\n" 112 | "4HEKc6xMANnYs/aYKjvmP0VpOvRU\n" 113 | "-----END CERTIFICATE-----"; 114 | 115 | } -------------------------------------------------------------------------------- /src/threepl/ThreeplConnection.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 14/03/17. 3 | // 4 | 5 | #ifndef CEEMA_THREEPLCONNECTION_H 6 | #define CEEMA_THREEPLCONNECTION_H 7 | 8 | #include "PrplHttpManager.h" 9 | #include "ContactStore.h" 10 | #include "GroupStore.h" 11 | #include "Transfer.h" 12 | #include "MessageHandler.h" 13 | #include 14 | #include 15 | #include 16 | 17 | /** 18 | * Holds data association with PurpleConnection specific for Threepl 19 | */ 20 | class ThreeplConnection { 21 | enum class State { 22 | DISCONNECTED, 23 | CONNECTING, 24 | AUTHENTICATING, 25 | CONNECTED 26 | }; 27 | 28 | // HTTP API 29 | PrplHttpManager m_httpManager; 30 | ceema::IdentAPI m_identAPI; 31 | ceema::BlobAPI m_blobAPI; 32 | 33 | // Messaging 34 | ContactStore m_store; 35 | GroupStore m_groups; 36 | ThreeplMessageHandler m_handler; 37 | 38 | // ceema connection data 39 | ceema::Session m_session; 40 | ceema::Account m_account; 41 | 42 | // Purple data 43 | PurpleAccount* m_prpl_acct; 44 | PurpleConnection* m_connection; 45 | 46 | gint session_socket; 47 | 48 | guint input_handler_read; 49 | guint input_handler_write; 50 | 51 | State m_state; 52 | 53 | public: 54 | //std::unordered_map m_groups; 55 | /** Map from ceema group to chat conv (inverse direction is handled by protocol data) */ 56 | //std::unordered_map m_group_chats; 57 | 58 | //TODO: make TLS usage configurable 59 | ThreeplConnection(PurpleAccount* acct, ceema::Account const& account) : 60 | m_identAPI(m_httpManager), m_blobAPI(m_httpManager, true), m_store(m_identAPI), 61 | m_groups(*this), 62 | m_handler(*this, m_store, m_groups, m_blobAPI), m_session(account), 63 | m_account(account), m_prpl_acct(acct), 64 | m_connection(purple_account_get_connection(m_prpl_acct)), 65 | session_socket(-1), input_handler_read(0), input_handler_write(0), 66 | m_state(State::DISCONNECTED) 67 | {} 68 | 69 | ContactStore& contact_store() { 70 | return m_store; 71 | } 72 | 73 | GroupStore& group_store() { 74 | return m_groups; 75 | } 76 | 77 | ceema::IdentAPI& ident_API() { 78 | return m_identAPI; 79 | } 80 | 81 | ceema::BlobAPI& blob_API() { 82 | return m_blobAPI; 83 | } 84 | 85 | PrplBlobUploadTransfer* new_xfer(PurpleConnection* gc, const char *who) { 86 | return new PrplBlobUploadTransfer(m_blobAPI, ceema::BlobType::FILE, gc, who); 87 | } 88 | 89 | ceema::Account const& account() const { 90 | return m_account; 91 | } 92 | 93 | const ceema::Session &session() const { 94 | return m_session; 95 | } 96 | 97 | PurpleAccount *acct() const { 98 | return m_prpl_acct; 99 | } 100 | 101 | PurpleConnection *connection() const { 102 | return m_connection; 103 | } 104 | 105 | State state() const { 106 | return m_state; 107 | } 108 | 109 | bool start_connect(); 110 | 111 | void close(); 112 | 113 | template 114 | ceema::future> send_message(ceema::client_id const& recipient, Payload&& payload) { 115 | if (state() != State::CONNECTED) { 116 | return ceema::future>(); 117 | } 118 | return m_handler.sendPayload(account(), recipient, std::move(payload)); 119 | } 120 | 121 | template 122 | std::vector>> send_group_message(ThreeplGroup* group, Payload&& payload) { 123 | if (state() != State::CONNECTED) { 124 | return std::vector>>(); 125 | } 126 | return m_handler.sendPayload(account(), group, std::move(payload)); 127 | } 128 | 129 | /** 130 | * Send the given packet. 131 | * @param packet Packet to send 132 | * @return true if the session is connected, false otherwise 133 | */ 134 | bool send_packet(ceema::Packet const& packet); 135 | 136 | void send_keepalive(); 137 | 138 | /** 139 | * Send agree/disagree for last received message 140 | * @param agree True to agree, false to disagree 141 | * @return true if ok, false on errors 142 | */ 143 | bool send_agreement(ceema::client_id const& who, bool agree); 144 | private: 145 | ThreeplMessageHandler& message_handler() { 146 | return m_handler; 147 | } 148 | 149 | /** 150 | * Continue session authentication. Returns true if completed 151 | * @return true iff authentication completed successfully and state is 152 | * connected. 153 | */ 154 | bool authenticate(); 155 | 156 | bool read_packets(); 157 | 158 | static void on_connect(gpointer data, gint source, const gchar *error_message); 159 | 160 | static void on_session_data_write(gpointer data, gint source, PurpleInputCondition condition); 161 | 162 | static void on_session_data_read(gpointer data, gint source, PurpleInputCondition condition); 163 | }; 164 | 165 | 166 | #endif //CEEMA_THREEPLCONNECTION_H 167 | -------------------------------------------------------------------------------- /src/threepl/MessageHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // Created by hbruintjes on 15/03/17. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | #include 10 | #include "GroupStore.h" 11 | 12 | class ThreeplConnection; 13 | class ContactStore; 14 | 15 | class message_exception : public std::runtime_error { 16 | ceema::client_id m_cid; 17 | public: 18 | message_exception(ceema::client_id id, const char* what) : 19 | std::runtime_error(what), m_cid(id) { 20 | } 21 | message_exception(ceema::client_id id, std::string const& what) : 22 | std::runtime_error(what), m_cid(id) { 23 | } 24 | 25 | ceema::client_id id() const { 26 | return m_cid; 27 | } 28 | }; 29 | 30 | class ThreeplMessageHandler { 31 | ThreeplConnection& m_connection; 32 | ContactStore& m_contacts; 33 | GroupStore& m_groups; 34 | ceema::BlobAPI& m_blobAPI; 35 | 36 | std::unordered_map, ceema::promise>>>> packetQueue; 37 | 38 | ceema::message_id m_lastAgreeable; 39 | public: 40 | ThreeplMessageHandler(ThreeplConnection& session, ContactStore& contacts, GroupStore& groups, ceema::BlobAPI& blobAPI) : 41 | m_connection(session), m_contacts(contacts), m_groups(groups), m_blobAPI(blobAPI), 42 | m_lastAgreeable{} {} 43 | 44 | void onRecvMessage(std::unique_ptr msg); 45 | 46 | template 47 | ceema::future> sendPayload(ceema::Account const& sender, ceema::client_id const& recipient, Payload&& payload) { 48 | std::unique_ptr msg = std::make_unique( 49 | sender.id(), recipient, std::forward(payload)); 50 | // Threepl does not make use of ACK, so no need to request one 51 | // If the network is down, Purple can't do much anyway 52 | msg->flags().set(ceema::MessageFlag::NO_ACK); 53 | if (sender.nick().size()) { 54 | msg->nick() = sender.nick(); 55 | } 56 | return sendMessage(std::move(msg)); 57 | } 58 | 59 | template 60 | std::vector>> sendPayload(ceema::Account const& sender, ThreeplGroup* group, Payload&& payload) { 61 | std::vector>> res; 62 | for(ceema::client_id const& member: group->members()) { 63 | if (member == sender.id()) { 64 | continue; 65 | } 66 | res.push_back(sendPayload(sender, member, payload)); 67 | } 68 | return res; 69 | } 70 | 71 | ThreeplGroup* find_or_create_group(ceema::group_uid uid, ceema::Message const &msg, bool add_chat = false); 72 | 73 | ceema::message_id const& lastAgreeable() const { 74 | return m_lastAgreeable; 75 | } 76 | private: 77 | ceema::future> sendMessage(std::unique_ptr msg); 78 | 79 | ceema::future> enqueue(std::unique_ptr msg); 80 | void recv(ceema::Message& msg); 81 | void send(ceema::Message& msg); 82 | 83 | bool onMsgText(ceema::Message const& msg, ceema::PayloadText const& payload); 84 | bool onMsgPicture(ceema::Message const& msg, ceema::PayloadPicture const& payload); 85 | bool onMsgLocation(ceema::Message const& msg, ceema::PayloadLocation const& payload); 86 | bool onMsgAudio(ceema::Message const& msg, ceema::PayloadAudio const& payload, bool del); 87 | bool onMsgVideo(ceema::Message const& msg, ceema::PayloadVideo const& payload, bool del); 88 | bool onMsgFile(ceema::Message const& msg, ceema::PayloadFile const& payload, bool del); 89 | 90 | bool onMsgIcon(ceema::Message const& msg, ceema::PayloadIcon const& payload); 91 | bool onMsgIconClear(ceema::Message const& msg, ceema::PayloadIconClear const& payload); 92 | 93 | bool onMsgGroupText(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupText const& payload); 94 | bool onMsgGroupLocation(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupLocation const& payload); 95 | bool onMsgGroupPicture(ceema::Message const& msg, ceema::PayloadGroupPicture const& payload); 96 | // Audio, Video, File all handled as with Contact messages 97 | 98 | bool onMsgGroupMembers(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupMembers const& payload); 99 | bool onMsgGroupTitle(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupTitle const& payload); 100 | bool onMsgGroupLeave(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupLeave const& payload); 101 | 102 | bool onMsgGroupIcon(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupIcon const& payload); 103 | bool onMsgGroupSync(ceema::Message const& msg, ThreeplGroup* group, ceema::PayloadGroupSync const& payload); 104 | 105 | bool onMsgStatus(ceema::Message const& msg, ceema::PayloadMessageStatus const& payload); 106 | bool onMsgTyping(ceema::Message const& msg, ceema::PayloadTyping const& payload); 107 | 108 | bool isOwner(ceema::group_uid const& id) const; 109 | void requestSync(ceema::Message const& msg, ceema::group_uid const& id); 110 | }; -------------------------------------------------------------------------------- /src/encoding/base32.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | 24 | static const std::array b32_chars = { 25 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 26 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', 27 | '=' }; 28 | 29 | static const char b32_table[128] = { 30 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 31 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 32 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 33 | 64, 64, 26, 27, 28, 29, 30, 31, 64, 64, 64, 64, 64, 64, 64, 64, 34 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 36 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 37 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 38 | }; 39 | 40 | /** 41 | * Decode Base32 encoded string into buffer, either until buffer is full, or string 42 | * is exhausted. Non-base32 characters are ignored 43 | * @param from String to decode, must represent a multiple of 40 bits. 44 | * @param to Buffer to write to 45 | * @param len Size of buffer in bytes 46 | * @return Length of decoded buffer in bytes, or -1 on error (invalid input length) 47 | */ 48 | inline int base32_decode(std::string const &from, unsigned char *to, std::size_t len) { 49 | unsigned int j = 0, bits = 0, value = 0; 50 | for (char ch: from) { 51 | if (ch < 0 || ch > 127 || b32_table[static_cast(ch)] == 64) { 52 | // Skip invalid char 53 | continue; 54 | } 55 | value = (value << 5) | b32_table[static_cast(ch)]; 56 | bits += 5; 57 | if (bits >= 8) { 58 | // Read top 8 bits 59 | bits -= 8; 60 | to[j++] = static_cast((value >> (bits)) & 0xFF); 61 | if (j > len) { 62 | // Buffer filled to capacity 63 | return j; 64 | } 65 | } 66 | } 67 | if (bits != 0) { 68 | // Invalid string length 69 | return -1; 70 | } 71 | 72 | // String exhausted 73 | return j; 74 | } 75 | 76 | template 77 | inline std::string base32_encode(Array const& from) { 78 | byte_vector to; 79 | int pad = from.size() % 5; 80 | to.resize(from.size() / 5 * 8 + (pad?8:0), 32); 81 | 82 | size_t i = 0, j = 0; 83 | for(; i < from.size()-4; i += 5, j += 8) { 84 | to[j] = from[i] >> 3; 85 | to[j+1] = (from[i] << 2 | from[i+1] >> 6) & 0x1f; 86 | to[j+2] = from[i+1] >> 1 & 0x1f; 87 | to[j+3] = (from[i+1] << 4 | from[i+2] >> 4) & 0x1f; 88 | to[j+4] = (from[i+2] << 1 | from[i+3] >> 7) & 0x1f; 89 | to[j+5] = from[i+3] >> 2 & 0x1f; 90 | to[j+6] = (from[i+3] << 3 | from[i+4] >> 5) & 0x1f; 91 | to[j+7] = from[i+4] & 0x1f; 92 | } 93 | if (pad) { 94 | to[j] = from[i] >> 3; 95 | if (pad != 1) { 96 | to[j+1] = (from[i] << 2 | from[i+1] >> 6) & 0x1f; 97 | to[j+2] = from[i+1] >> 1 & 0x1f; 98 | if (pad != 2) { 99 | to[j+3] = (from[i+1] << 4 | from[i+2] >> 4) & 0x1f; 100 | if (pad != 3) { 101 | to[j+4] = (from[i+2] << 1 | from[i+3] >> 7) & 0x1f; 102 | to[j+5] = from[i+3] >> 2 & 0x1f; 103 | if (pad != 4) { 104 | to[j+6] = (from[i+3] << 3 | from[i+4] >> 5) & 0x1f; 105 | } else { 106 | to[j+6] = from[i+3] << 3 & 0x1f; 107 | } 108 | } else { 109 | to[j+4] = from[i+2] << 1 & 0x1f; 110 | } 111 | } else { 112 | to[j+3] = from[i+1] << 4 & 0x1f; 113 | } 114 | 115 | } else { 116 | to[j+1] = from[i] << 2 & 0x1f; 117 | } 118 | 119 | } 120 | 121 | //TODO: copy just because of signedness 122 | //otherwise replace inplace 123 | std::string result; 124 | result.reserve(to.size()); 125 | for(auto v: to) { 126 | result += b32_chars[v]; 127 | } 128 | return result; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/encoding/base64.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | #include 21 | 22 | namespace ceema { 23 | static const std::array b64_chars = { 24 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 25 | 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 26 | 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 27 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; 28 | 29 | static const std::array b64_table = { 30 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 31 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 32 | 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 33 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 34 | 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 35 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, 36 | 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 37 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64 38 | }; 39 | 40 | template 41 | inline std::string base64_encode(Array const& from) { 42 | static_assert(std::is_unsigned::value, 43 | "Cannot encode signed types"); 44 | std::string to; 45 | size_t from_len = from.size(); 46 | size_t pad = from_len % 3; 47 | // Calculate output size 48 | 49 | size_t outlen = ((from_len / 3) + (pad ? 1 : 0)) * 4; 50 | 51 | to.resize(outlen, '='); 52 | 53 | size_t i = 0, j = 0; 54 | for(; i < from_len - 2; i += 3, j += 4) { 55 | to[j] = b64_chars[from[i] >> 2 & 0x3f]; 56 | to[j+1] = b64_chars[(from[i] << 4 | from[i+1] >> 4) & 0x3f]; 57 | to[j+2] = b64_chars[(from[i+1] << 2 | from[i+2] >> 6) & 0x3f]; 58 | to[j+3] = b64_chars[from[i+2] & 0x3f]; 59 | } 60 | if (pad) { 61 | to[j] = b64_chars[from[i] >> 2 & 0x3f]; 62 | to[j+1] = b64_chars[(from[i] << 4 | from[i+1] >> 4) & 0x3f]; 63 | if (pad == 2) { 64 | to[j+2] = b64_chars[(from[i+1] << 2 | from[i+2] >> 6) & 0x3f]; 65 | } 66 | } 67 | return to; 68 | } 69 | 70 | /** 71 | * Decode Base64 encoded string into buffer, either until buffer is full, or string 72 | * is exhausted. Non-base64 characters are ignored 73 | * @param from String to decode, should represent a multiple of 3 bytes (padding is optional). 74 | * @param to Buffer to write to 75 | * @param len Size of buffer in bytes 76 | * @return Length of decoded buffer in bytes 77 | */ 78 | inline int base64_decode(std::string const &from, unsigned char *to, std::size_t len) { 79 | unsigned int j = 0, bits = 0, value = 0; 80 | for (char c: from) { 81 | if ((c > 127) || (c < 0) || (b64_table[c] > 63)) { 82 | // Skip invalid char 83 | continue; 84 | } 85 | value = (value << 6) | b64_table[c]; 86 | bits += 6; 87 | if (bits >= 8) { 88 | // Read top 8 bits 89 | bits -= 8; 90 | to[j++] = static_cast((value >> (bits)) & 0xFF); 91 | if (j > len) { 92 | // Buffer filled to capacity 93 | throw std::runtime_error("Base decode buffer too small"); 94 | } 95 | } 96 | 97 | } 98 | if (bits != 0) { 99 | if (j < len) { 100 | to[j++] = static_cast(value & (0xFF >> (8 - bits))); 101 | } 102 | } 103 | 104 | // String exhausted 105 | if (j != len) { 106 | throw std::runtime_error("Base decode buffer too large"); 107 | } 108 | return j; 109 | } 110 | 111 | inline byte_vector base64_decode(std::string const &from) { 112 | byte_vector to; 113 | to.reserve(from.size() / 4 * 3); // Approximate result size, does not have to be exact 114 | 115 | for(std::size_t i = 0; i < from.size(); i += 4) { 116 | std::uint32_t val = (b64_table[from[i]]) << 18 | (b64_table[from[i+1]] << 12) | (b64_table[from[i+2]] << 6) | (b64_table[from[i+3]]); 117 | to.push_back(val >> 16); 118 | if (from[i+2] != '=') { 119 | to.push_back(val >> 8 & 0xff); 120 | if (from[i+3] != '=') { 121 | to.push_back(val & 0xff); 122 | } 123 | } 124 | } 125 | 126 | // String exhausted 127 | return to; 128 | } 129 | 130 | } 131 | -------------------------------------------------------------------------------- /src/protocol/packet/payloads/MessagePayload.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 Harold Bruintjes 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "MessagePayload.h" 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace ceema { 24 | 25 | template 26 | It copy_iter(It input, Array& output) { 27 | std::copy(input, input + Array::array_size, output.begin()); 28 | return input + Array::array_size; 29 | } 30 | 31 | MessagePayload MessagePayload::deserialize(byte_vector const& payload) { 32 | byte_vector::const_iterator payload_data = payload.begin(); 33 | MessageType type; 34 | letoh(type, &*payload_data); 35 | payload_data += sizeof(MessageType); 36 | auto size = payload.size() - sizeof(MessageType); 37 | switch(type) { 38 | case MessageType::NONE: 39 | return MessagePayload(PayloadNone::deserialize(payload_data, size)); 40 | case MessageType::TEXT: 41 | return MessagePayload(PayloadText::deserialize(payload_data, size)); 42 | case MessageType::PICTURE: 43 | return MessagePayload(PayloadPicture::deserialize(payload_data, size)); 44 | case MessageType::LOCATION: 45 | return MessagePayload(PayloadLocation::deserialize(payload_data, size)); 46 | case MessageType::VIDEO: 47 | return MessagePayload(PayloadVideo::deserialize(payload_data, size)); 48 | case MessageType::AUDIO: 49 | return MessagePayload(PayloadAudio::deserialize(payload_data, size)); 50 | case MessageType::POLL: 51 | return MessagePayload(PayloadPoll::deserialize(payload_data, size)); 52 | case MessageType::POLL_VOTE: 53 | return MessagePayload(PayloadPollVote::deserialize(payload_data, size)); 54 | case MessageType::FILE: 55 | return MessagePayload(PayloadFile::deserialize(payload_data, size)); 56 | case MessageType::ICON: 57 | return MessagePayload(PayloadIcon::deserialize(payload_data, size)); 58 | case MessageType::ICON_CLEAR: 59 | return MessagePayload(PayloadIconClear::deserialize(payload_data, size)); 60 | case MessageType::GROUP_TEXT: 61 | return MessagePayload(PayloadGroupText::deserialize(payload_data, size)); 62 | case MessageType::GROUP_LOCATION: 63 | return MessagePayload(PayloadGroupLocation::deserialize(payload_data, size)); 64 | case MessageType::GROUP_PICTURE: 65 | return MessagePayload(PayloadGroupPicture::deserialize(payload_data, size)); 66 | case MessageType::GROUP_VIDEO: 67 | return MessagePayload(PayloadGroupVideo::deserialize(payload_data, size)); 68 | case MessageType::GROUP_AUDIO: 69 | return MessagePayload(PayloadGroupAudio::deserialize(payload_data, size)); 70 | case MessageType::GROUP_FILE: 71 | return MessagePayload(PayloadGroupFile::deserialize(payload_data, size)); 72 | case MessageType::GROUP_MEMBERS: 73 | return MessagePayload(PayloadGroupMembers::deserialize(payload_data, size)); 74 | case MessageType::GROUP_TITLE: 75 | return MessagePayload(PayloadGroupTitle::deserialize(payload_data, size)); 76 | case MessageType::GROUP_LEAVE: 77 | return MessagePayload(PayloadGroupLeave::deserialize(payload_data, size)); 78 | case MessageType::GROUP_ICON: 79 | return MessagePayload(PayloadGroupIcon::deserialize(payload_data, size)); 80 | case MessageType::GROUP_SYNC: 81 | return MessagePayload(PayloadGroupSync::deserialize(payload_data, size)); 82 | case MessageType::GROUP_POLL: 83 | return MessagePayload(PayloadGroupPoll::deserialize(payload_data, size)); 84 | case MessageType::GROUP_POLL_VOTE: 85 | return MessagePayload(PayloadGroupPollVote::deserialize(payload_data, size)); 86 | case MessageType::MESSAGE_STATUS: 87 | return MessagePayload(PayloadMessageStatus::deserialize(payload_data, size)); 88 | case MessageType::CLIENT_TYPING: 89 | return MessagePayload(PayloadTyping::deserialize(payload_data, size)); 90 | } 91 | throw std::runtime_error(formatstr() << "Invalid type for MessagePayload 0x" << std::hex << static_cast(type)); 92 | } 93 | 94 | PayloadNone PayloadNone::deserialize(byte_vector::const_iterator&, std::size_t) { 95 | throw std::runtime_error("Cannot deserialize NONE message"); 96 | } 97 | 98 | byte_vector PayloadNone::serialize() const { 99 | throw std::logic_error("Cannot serialize NONE message"); 100 | } 101 | 102 | 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ceema 2 | 3 | This is the ceema library, which provides a C++ implementation of the Threema 4 | communication protocol (and, in the future, a C interface to it). The aim is to 5 | provide a somewhat simple interface to hook the Threema protocol in client 6 | applications such as multi-protocol IM clients. 7 | 8 | If you are looking for a ready-to-use desktop application, consider 9 | [openMittsu](https://github.com/blizzard4591/openMittsu). 10 | 11 | ## Building 12 | 13 | Since this is CMake based, building the library consists of the following steps 14 | 15 | git submodule init 16 | git submodule sync 17 | git submodule update --init --recursive 18 | mkdir build && cd build 19 | cmake ../ 20 | make 21 | 22 | ## Dependencies 23 | 24 | This library requires the following dependencies to be installed (both headers 25 | and libraries): 26 | 27 | * `curl` 28 | * `jsoncpp` 29 | * `openssl` 30 | * `sodium` 31 | 32 | ## Library layout 33 | 34 | Under `src` you will find: 35 | * `client`: Structures for dealing with clients/contact. The primary class is 36 | `Client`, and a utility class `ContactStore` 37 | * `encoding`: Some utilities for massaging data (hashing, padding, ...) 38 | * `logging`: What it says on the tin 39 | * `protocol`: Main protocol handling. Goes from packets, to messages, to 40 | payloads 41 | * `socket`: Low-level network interface 42 | * `types`: Type definitions used throughout the library 43 | 44 | ## Protocol 45 | 46 | The protocol is fairly straightforward. First, a client-server handshake is 47 | performed to set-up the encryption (using sodium/NaCl). This is handled by the 48 | Session class. Next, the client and 49 | server will communicate in (encrypted) packets as follows: 50 | 51 | - A (2 byte) length prefix is sent, followed by an encrypted packet 52 | - A packet consists of a (4 byte) type prefix, followed by an optional payload. 53 | There are roughtly 4 types of packets: 54 | - Session status packets, indicating start (after any pending messages have 55 | been sent), or end (server generally ends the session when the client 56 | connects using the same credentials form another source). Normally sent by the 57 | server only. The end packet contains a UTF-8 encoded string payload 58 | - Keep-alive/ping packets, containing an arbitrary payload that must be in the 59 | reply. Can be sent both by the server and the client 60 | - Messages (see below), identified by a random ID 61 | - Acknowledgements, sent by both the server and the client in response to 62 | receiving a message, containing the sender and ID of the message being 63 | acknowledged. Without an acknowledgement, the server will keep retrying to 64 | deliver the message. 65 | * Messages contain both a sender and recipient, some arbitrary message ID, 66 | a timestamp (seconds since epoch), the ID or nickname of the sender and some 67 | payload. They are prefixed by a (1 byte) type identifier, which determines 68 | the type of payload. The payload is encrypted using the keys of the sender and 69 | recipient, after adding padding according to PKCS#7. The following types of 70 | messages are known: 71 | - Client message: 72 | - Text: Payload is a UTF-8 encoded string. 73 | - Picture 74 | - Location: Payload is UTF-8 encoded string with coordinates: Two lines, 75 | separated by LF, first in decimal, then minutes 76 | - Video 77 | - Audio 78 | - Poll 79 | - User status message, either typing, or typing stopped 80 | - Client message-status message, can be received, seen, agree/disagree 81 | - Group message: 82 | - Create 83 | - Sync 84 | - Leave 85 | - Title 86 | - Text 87 | - Picture 88 | - Location 89 | - Video 90 | - Audio 91 | - Poll 92 | 93 | ## Backup string 94 | 95 | The Threema client allows for backup of the private key and client ID using a 96 | backup string. Its is a base32 encoded string consisting of a salt and the 97 | encrypted data. The salt and password generate a hash which can be used to 98 | decrypt the data, which will yield the client ID, private key, and 2 bytes of 99 | the SHA-256 hash of both which can be used to validate the decryption. 100 | 101 | ## Using the Pidgin Plugin 102 | 103 | ### Importing users and groups 104 | 105 | For importing users and groups from a backup, use menu *Accounts->Threema->Import from Backup* 106 | 107 | ### Adding users and groups manually 108 | 109 | #### Adding users 110 | 111 | Users have to be added manually via the `Add Buddy` menu in Pidgin. 112 | 113 | #### Adding groups 114 | 115 | When online with Pidgin while the administrator of a group creates or updates a group, this group is automatically created/updated by the plugin. However, this does not work if you are the administrator since it is not possible to be online with the same account on two devices. 116 | 117 | A workaraound for this and in case that you want to add a group that already exists and is not updated by the administrator, you can enter the relevant data in Pidgin's configuration file `blist.xml`. This file is located at `~/.purple/blist.xml`. The syntax for a group entry in the buddy list is the following: 118 | 119 | 120 | Alias 121 | GROUP-ID-Hex-16digits 122 | OACCOUNT 123 | Group Name 124 | MEMBER01MEMBER02...MEMBERxx 125 | 126 | 127 | 128 | --------------------------------------------------------------------------------