/******************************************************************************* * libretroshare/src/chat: distantchat.cc * * * * libretroshare: retroshare core library * * * * Copyright 2014 by Cyril Soler * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 3 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public License * * along with this program. If not, see . * * * *******************************************************************************/ #include #include #include "openssl/rand.h" #include "openssl/dh.h" #include "openssl/err.h" #include "crypto/rsaes.h" #include "util/rsmemory.h" #include "util/rsprint.h" #include "rsitems/rsmsgitems.h" #include "retroshare/rsmsgs.h" #include "retroshare/rsidentity.h" #include "retroshare/rsiface.h" #include "rsserver/p3face.h" #include "services/p3idservice.h" #include "gxs/gxssecurity.h" #include "turtle/p3turtle.h" #include "retroshare/rsids.h" #include "distantchat.h" //#define DEBUG_DISTANT_CHAT #ifdef DEBUG_DISTANT_CHAT #include uint32_t msecs_of_day() { timeval tv ; gettimeofday(&tv,NULL) ; return tv.tv_usec / 1000 ; } #define DISTANT_CHAT_DEBUG() std::cerr << time(NULL) << "." << std::setfill('0') << std::setw(3) << msecs_of_day() << " : DISTANT_CHAT : " << __FUNCTION__ << " : " #endif //static const uint32_t DISTANT_CHAT_KEEP_ALIVE_TIMEOUT = 6 ; // send keep alive packet so as to avoid tunnel breaks. //static const uint32_t RS_DISTANT_CHAT_DH_STATUS_UNINITIALIZED = 0x0000 ; //static const uint32_t RS_DISTANT_CHAT_DH_STATUS_HALF_KEY_DONE = 0x0001 ; //static const uint32_t RS_DISTANT_CHAT_DH_STATUS_KEY_AVAILABLE = 0x0002 ; static const uint32_t DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID = 0xa0001 ; DistantChatService::DistantChatService() : // default: accept everyone mDistantChatPermissions(RS_DISTANT_CHAT_CONTACT_PERMISSION_FLAG_FILTER_NONE), mGxsTunnels(nullptr), mDistantChatMtx("distant chat") {} void DistantChatService::connectToGxsTunnelService(RsGxsTunnelService *tr) { mGxsTunnels = tr ; tr->registerClientService(DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID,this) ; } bool DistantChatService::handleOutgoingItem(RsChatItem *item) { RsGxsTunnelId tunnel_id ; { RS_STACK_MUTEX(mDistantChatMtx) ; std::map::const_iterator it=mDistantChatContacts.find(DistantChatPeerId(item->PeerId())); if(it == mDistantChatContacts.end()) return false ; } #ifdef CHAT_DEBUG DISTANT_CHAT_DEBUG() << "p3ChatService::handleOutgoingItem(): sending to " << item->PeerId() << ": interpreted as a distant chat virtual peer id." << std::endl; #endif uint32_t size = RsChatSerialiser().size(item) ; RsTemporaryMemory mem(size) ; if(!RsChatSerialiser().serialise(item,mem,&size)) { std::cerr << "(EE) serialisation error. Something's really wrong!" << std::endl; return false; } #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << " sending: " << RsUtil::BinToHex(mem,size,100) << std::endl; DISTANT_CHAT_DEBUG() << " size: " << std::dec << size << std::endl; DISTANT_CHAT_DEBUG() << " hash: " << RsDirUtil::sha1sum(mem,size) << std::endl; #endif mGxsTunnels->sendData( RsGxsTunnelId(item->PeerId()),DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID,mem,size); return true; } void DistantChatService::handleRecvChatStatusItem(RsChatStatusItem *cs) { if(cs->flags & RS_CHAT_FLAG_CONNEXION_REFUSED) { #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "(II) Distant chat: received notification that peer refuses conversation." << std::endl; #endif RsServer::notify()->notifyChatStatus(ChatId(DistantChatPeerId(cs->PeerId())),"Connexion refused by distant peer!") ; } if(cs->flags & RS_CHAT_FLAG_CLOSING_DISTANT_CONNECTION) markDistantChatAsClosed(DistantChatPeerId(cs->PeerId())) ; // nothing more to do, because the decryption routing will update the last_contact time when decrypting. if(cs->flags & RS_CHAT_FLAG_KEEP_ALIVE) std::cerr << "DistantChatService::handleRecvChatStatusItem(): received keep alive packet for inactive chat! peerId=" << cs->PeerId() << std::endl; } bool DistantChatService::acceptDataFromPeer(const RsGxsId& gxs_id,const RsGxsTunnelId& tunnel_id,bool am_I_client_side) { if(am_I_client_side) // always accept distant chat when we're the client side. return true ; bool res = true ; if(mDistantChatPermissions & RS_DISTANT_CHAT_CONTACT_PERMISSION_FLAG_FILTER_NON_CONTACTS) res = (rsIdentity!=NULL) && rsIdentity->isARegularContact(gxs_id) ; if(mDistantChatPermissions & RS_DISTANT_CHAT_CONTACT_PERMISSION_FLAG_FILTER_EVERYBODY) res = false ; if(!res) { #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "(II) refusing distant chat from peer " << gxs_id << ". Sending a notification back to tunnel " << tunnel_id << std::endl; #endif RsChatStatusItem *item = new RsChatStatusItem ; item->flags = RS_CHAT_FLAG_CONNEXION_REFUSED ; item->status_string.clear() ; // is not used yet! But could be set in GUI to some message (??). item->PeerId(RsPeerId(tunnel_id)) ; // we do not use handleOutGoingItem() because there's no distant chat contact, as the chat is refused. uint32_t size = RsChatSerialiser().size(item) ; RsTemporaryMemory mem(size) ; if(!RsChatSerialiser().serialise(item,mem,&size)) { std::cerr << "(EE) serialisation error. Something's really wrong!" << std::endl; return false; } #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << " sending: " << RsUtil::BinToHex(mem,size,100) << std::endl; DISTANT_CHAT_DEBUG() << " size: " << std::dec << std::endl; DISTANT_CHAT_DEBUG() << " hash: " << RsDirUtil::sha1sum(mem,size) << std::endl; #endif mGxsTunnels->sendData( RsGxsTunnelId(item->PeerId()),DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID,mem,size); } return res ; } void DistantChatService::notifyTunnelStatus( const RsGxsTunnelId& tunnel_id, uint32_t tunnel_status ) { #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "DistantChatService::notifyTunnelStatus(): got notification " << std::hex << tunnel_status << std::dec << " for tunnel " << tunnel_id << std::endl; #endif switch(tunnel_status) { default: case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_UNKNOWN: std::cerr << "(EE) don't know how to handle RS_GXS_TUNNEL_STATUS_UNKNOWN !" << std::endl; break ; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_CAN_TALK: RsServer::notify()->notifyChatStatus(ChatId(DistantChatPeerId(tunnel_id)),"Tunnel is secured. You can talk!") ; RsServer::notify()->notifyPeerStatusChanged(tunnel_id.toStdString(),RS_STATUS_ONLINE) ; break ; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_TUNNEL_DN: RsServer::notify()->notifyChatStatus(ChatId(DistantChatPeerId(tunnel_id)),"Tunnel is down...") ; RsServer::notify()->notifyPeerStatusChanged(tunnel_id.toStdString(),RS_STATUS_OFFLINE) ; break ; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_REMOTELY_CLOSED: RsServer::notify()->notifyChatStatus(ChatId(DistantChatPeerId(tunnel_id)),"Tunnel is down...") ; RsServer::notify()->notifyPeerStatusChanged(tunnel_id.toStdString(),RS_STATUS_OFFLINE) ; break ; } } void DistantChatService::receiveData( const RsGxsTunnelId& tunnel_id, unsigned char* data, uint32_t data_size) { #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "DistantChatService::receiveData(): got data of size " << std::dec << data_size << " for tunnel " << tunnel_id << std::endl; DISTANT_CHAT_DEBUG() << " received: " << RsUtil::BinToHex(data,data_size,100) << std::endl; DISTANT_CHAT_DEBUG() << " hash: " << RsDirUtil::sha1sum(data,data_size) << std::endl; DISTANT_CHAT_DEBUG() << " deserialising..." << std::endl; #endif // always make the contact up to date. This is useful for server side, which doesn't know about the chat until it // receives the first item. { RS_STACK_MUTEX(mDistantChatMtx) ; RsGxsTunnelService::GxsTunnelInfo tinfo; if(!mGxsTunnels->getTunnelInfo(tunnel_id,tinfo)) return ; // Check if the data is accepted. We cannot prevent the creation of tunnels at the level of p3GxsTunnels, since tunnels are shared between services. // however, DistantChatContact& contact(mDistantChatContacts[DistantChatPeerId(tunnel_id)]) ; contact.to_id = tinfo.destination_gxs_id ; contact.from_id = tinfo.source_gxs_id ; } RsItem *item = RsChatSerialiser().deserialise(data,&data_size) ; if(item != NULL) { item->PeerId(RsPeerId(tunnel_id)) ; // just in case, but normally this is already done. handleIncomingItem(item) ; RsServer::notify()->notifyListChange(NOTIFY_LIST_PRIVATE_INCOMING_CHAT, NOTIFY_TYPE_ADD); } else std::cerr << " (EE) item could not be deserialised!" << std::endl; } void DistantChatService::markDistantChatAsClosed(const DistantChatPeerId& dcpid) { mGxsTunnels->closeExistingTunnel( RsGxsTunnelId(dcpid), DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID ); RS_STACK_MUTEX(mDistantChatMtx) ; std::map::iterator it = mDistantChatContacts.find(dcpid) ; if(it != mDistantChatContacts.end()) mDistantChatContacts.erase(it) ; } bool DistantChatService::initiateDistantChatConnexion( const RsGxsId& to_gxs_id, const RsGxsId& from_gxs_id, DistantChatPeerId& dcpid, uint32_t& error_code, bool notify ) { RsGxsTunnelId tunnel_id ; if(!mGxsTunnels->requestSecuredTunnel(to_gxs_id,from_gxs_id,tunnel_id,DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID,error_code)) return false ; dcpid = DistantChatPeerId(tunnel_id) ; DistantChatContact& dc_contact(mDistantChatContacts[dcpid]) ; dc_contact.from_id = from_gxs_id ; dc_contact.to_id = to_gxs_id ; error_code = RS_DISTANT_CHAT_ERROR_NO_ERROR ; if(notify) { // Make a self message to raise the chat window RsChatMsgItem *item = new RsChatMsgItem; item->message = "[Starting distant chat. Please wait for secure tunnel"; item->message += " to be established]"; item->chatFlags = RS_CHAT_FLAG_PRIVATE; item->sendTime = time(NULL); item->PeerId(RsPeerId(tunnel_id)); handleRecvChatMsgItem(item); delete item ; } return true ; } bool DistantChatService::getDistantChatStatus(const DistantChatPeerId& tunnel_id, DistantChatPeerInfo& cinfo) { RS_STACK_MUTEX(mDistantChatMtx); RsGxsTunnelService::GxsTunnelInfo tinfo; if(!mGxsTunnels->getTunnelInfo(RsGxsTunnelId(tunnel_id),tinfo)) return false; cinfo.to_id = tinfo.destination_gxs_id; cinfo.own_id = tinfo.source_gxs_id; cinfo.pending_items = tinfo.pending_data_packets; cinfo.peer_id = tunnel_id; switch(tinfo.tunnel_status) { case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_CAN_TALK : cinfo.status = RS_DISTANT_CHAT_STATUS_CAN_TALK; break; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_TUNNEL_DN: cinfo.status = RS_DISTANT_CHAT_STATUS_TUNNEL_DN; break; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_REMOTELY_CLOSED: cinfo.status = RS_DISTANT_CHAT_STATUS_REMOTELY_CLOSED; break; case RsGxsTunnelService::RS_GXS_TUNNEL_STATUS_UNKNOWN: default: cinfo.status = RS_DISTANT_CHAT_STATUS_UNKNOWN; break; } return true; } bool DistantChatService::closeDistantChatConnexion(const DistantChatPeerId &tunnel_id) { mGxsTunnels->closeExistingTunnel(RsGxsTunnelId(tunnel_id), DISTANT_CHAT_GXS_TUNNEL_SERVICE_ID) ; // also remove contact. Or do we wait for the notification? return true ; } uint32_t DistantChatService::getDistantChatPermissionFlags() { return mDistantChatPermissions ; } bool DistantChatService::setDistantChatPermissionFlags(uint32_t flags) { if(mDistantChatPermissions != flags) { mDistantChatPermissions = flags ; #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "(II) Changing distant chat permissions to " << flags << ". Existing openned chats will however remain active until closed" << std::endl; #endif triggerConfigSave() ; } return true ; } void DistantChatService::addToSaveList(std::list& list) const { /* Save permission flags */ RsConfigKeyValueSet *vitem = new RsConfigKeyValueSet ; RsTlvKeyValue kv; kv.key = "DISTANT_CHAT_PERMISSION_FLAGS" ; kv.value = RsUtil::NumberToString(mDistantChatPermissions) ; vitem->tlvkvs.pairs.push_back(kv) ; list.push_back(vitem) ; } bool DistantChatService::processLoadListItem(const RsItem *item) { const RsConfigKeyValueSet *vitem = NULL ; if(NULL != (vitem = dynamic_cast(item))) for(std::list::const_iterator kit = vitem->tlvkvs.pairs.begin(); kit != vitem->tlvkvs.pairs.end(); ++kit) if(kit->key == "DISTANT_CHAT_PERMISSION_FLAGS") { #ifdef DEBUG_DISTANT_CHAT DISTANT_CHAT_DEBUG() << "Loaded distant chat permission flags: " << kit->value << std::endl ; #endif if (!kit->value.empty()) { std::istringstream is(kit->value) ; uint32_t tmp ; is >> tmp ; if(tmp < 3) mDistantChatPermissions = tmp ; else std::cerr << "(EE) Invalid value read for DistantChatPermission flags in config: " << tmp << std::endl; } return true; } return false ; }