// Copyright (c) 2012-2013 giv // Copyright (c) 2017-2018 orignal // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. //-------------------------------------------------------------------------------------------------- // EdDSA assumed #include "i2psam.h" #include #include #include // for memset #include #include #include #include #ifndef WIN32 #include #include #endif #ifndef WIN32 #define closesocket close #endif #define SAM_BUFSIZE 65536 #define I2P_DESTINATION_SIZE 521 // EcDSA, GOST and EdDSA, actual size is 524 with trailing A== namespace SAM { static void print_error(const std::string& err) { #ifdef WIN32 StreamSession::getLogStream () << err << "(" << WSAGetLastError() << ")" << std::endl; #else StreamSession::getLogStream () << err << "(" << errno << ")" << std::endl; #endif } #ifdef WIN32 int Socket::instances_ = 0; void Socket::initWSA() { WSADATA wsadata; int ret = WSAStartup(MAKEWORD(2,2), &wsadata); if (ret != NO_ERROR) print_error("Failed to initialize winsock library"); } void Socket::freeWSA() { WSACleanup(); } #endif Socket::Socket(const std::string& SAMHost, uint16_t SAMPort, const std::string& minVer, const std::string &maxVer) : socket_(SAM_INVALID_SOCKET), SAMHost_(SAMHost), SAMPort_(SAMPort), minVer_(minVer), maxVer_(maxVer) { #ifdef WIN32 if (instances_++ == 0) initWSA(); #endif memset(&servAddr_, 0, sizeof(servAddr_)); servAddr_.sin_family = AF_INET; servAddr_.sin_addr.s_addr = inet_addr(SAMHost.c_str()); servAddr_.sin_port = htons(SAMPort); init(); if (isOk()) handshake(); } Socket::Socket(const sockaddr_in& addr, const std::string &minVer, const std::string& maxVer) : socket_(SAM_INVALID_SOCKET), servAddr_(addr), minVer_(minVer), maxVer_(maxVer) { #ifdef WIN32 if (instances_++ == 0) initWSA(); #endif init(); if (isOk()) handshake(); } Socket::Socket(const Socket& rhs) : socket_(SAM_INVALID_SOCKET), servAddr_(rhs.servAddr_), minVer_(rhs.minVer_), maxVer_(rhs.maxVer_) { #ifdef WIN32 if (instances_++ == 0) initWSA(); #endif init(); if (isOk()) handshake(); } Socket::~Socket() { close(); #ifdef WIN32 if (--instances_ == 0) freeWSA(); #endif } void Socket::init() { socket_ = socket(AF_INET, SOCK_STREAM, 0); if (socket_ == SAM_INVALID_SOCKET) { print_error("Failed to create socket"); return; } if (connect(socket_, (const sockaddr*)&servAddr_, sizeof(servAddr_)) == SAM_SOCKET_ERROR) { close(); print_error("Failed to connect to SAM"); return; } } SOCKET Socket::release() { SOCKET temp = socket_; socket_ = SAM_INVALID_SOCKET; return temp; } void Socket::handshake() { this->write(Message::hello(minVer_, maxVer_)); const std::string answer = this->read(); const Message::eStatus answerStatus = Message::checkAnswer(answer); if (answerStatus == Message::OK) version_ = Message::getValue(answer, "VERSION"); else print_error("Handshake failed"); } void Socket::write(const std::string& msg) { if (!isOk()) { print_error("Failed to send data because socket is closed"); return; } StreamSession::getLogStream () << "Send: " << msg << std::endl; ssize_t sentBytes = send(socket_, msg.c_str(), msg.length(), 0); if (sentBytes == SAM_SOCKET_ERROR) { close(); print_error("Failed to send data"); return; } if (sentBytes == 0) { close(); print_error("Socket was closed"); return; } } std::string Socket::read() { if (!isOk()) { print_error("Failed to read data because socket is closed"); return std::string(); } char buffer[SAM_BUFSIZE]; memset(buffer, 0, SAM_BUFSIZE); ssize_t recievedBytes = recv(socket_, buffer, SAM_BUFSIZE, 0); if (recievedBytes == SAM_SOCKET_ERROR) { close(); print_error("Failed to receive data"); return std::string(); } if (recievedBytes == 0) { close(); print_error("Socket was closed"); } StreamSession::getLogStream () << "Reply: " << buffer << std::endl; return std::string(buffer); } void Socket::close() { if (socket_ != SAM_INVALID_SOCKET) ::closesocket(socket_); socket_ = SAM_INVALID_SOCKET; } bool Socket::isOk() const { return socket_ != SAM_INVALID_SOCKET; } const std::string& Socket::getHost() const { return SAMHost_; } uint16_t Socket::getPort() const { return SAMPort_; } const std::string& Socket::getVersion() const { return version_; } const std::string& Socket::getMinVer() const { return minVer_; } const std::string& Socket::getMaxVer() const { return maxVer_; } const sockaddr_in& Socket::getAddress() const { return servAddr_; } //-------------------------------------------------------------------------------------------------- StreamSession::StreamSession( const std::string& nickname, const std::string& SAMHost /*= SAM_DEFAULT_ADDRESS*/, uint16_t SAMPort /*= SAM_DEFAULT_PORT*/, const std::string& destination /*= SAM_GENERATE_MY_DESTINATION*/, const std::string& i2pOptions /*= SAM_DEFAULT_I2P_OPTIONS*/, const std::string& minVer /*= SAM_DEFAULT_MIN_VER*/, const std::string& maxVer /*= SAM_DEFAULT_MAX_VER*/) : socket_(SAMHost, SAMPort, minVer, maxVer) , nickname_(nickname) , sessionID_(generateSessionID()) , i2pOptions_(i2pOptions) , isSick_(false) { myDestination_ = createStreamSession(destination); getLogStream () << "Created a brand new SAM session (" << sessionID_ << ")" << std::endl; } StreamSession::StreamSession(StreamSession& rhs) : socket_(rhs.socket_) , nickname_(rhs.nickname_) , sessionID_(generateSessionID()) , myDestination_(rhs.myDestination_) , i2pOptions_(rhs.i2pOptions_) , isSick_(false) { rhs.fallSick(); rhs.socket_.close(); (void)createStreamSession(myDestination_.priv); for(ForwardedStreamsContainer::const_iterator it = rhs.forwardedStreams_.begin(), end = rhs.forwardedStreams_.end(); it != end; ++it) forward(it->host, it->port, it->silent); getLogStream () << "Created a new SAM session (" << sessionID_ << ") from another (" << rhs.sessionID_ << ")" << std::endl; } StreamSession::~StreamSession() { stopForwardingAll(); getLogStream () << "Closing SAM session (" << sessionID_ << ") ..." << std::endl; } /*static*/ std::string StreamSession::generateSessionID() { static const int minSessionIDLength = 5; static const int maxSessionIDLength = 9; static const char sessionIDAlphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; int length = minSessionIDLength - 1; std::string result; srand(time(NULL)); while(length < minSessionIDLength) length = rand() % maxSessionIDLength; while (length-- > 0) result += sessionIDAlphabet[rand() % (sizeof(sessionIDAlphabet)-1)]; return result; } RequestResult > StreamSession::accept(bool silent) { typedef RequestResult > ResultType; std::shared_ptr streamSocket(new Socket(socket_)); const Message::eStatus status = accept(*streamSocket, sessionID_, silent); switch(status) { case Message::OK: return std::move (RequestResult >(streamSocket)); case Message::EMPTY_ANSWER: case Message::CLOSED_SOCKET: case Message::INVALID_ID: case Message::I2P_ERROR: fallSick(); break; default: break; } return ResultType(); } RequestResult > StreamSession::connect(const std::string& destination, bool silent) { typedef RequestResult > ResultType; std::shared_ptr streamSocket(new Socket(socket_)); const Message::eStatus status = connect(*streamSocket, sessionID_, destination, silent); switch(status) { case Message::OK: return ResultType(streamSocket); case Message::EMPTY_ANSWER: case Message::CLOSED_SOCKET: case Message::INVALID_ID: case Message::I2P_ERROR: fallSick(); break; default: break; } return ResultType(); } RequestResult StreamSession::forward(const std::string& host, uint16_t port, bool silent) { typedef RequestResult ResultType; std::shared_ptr newSocket(new Socket(socket_)); const Message::eStatus status = forward(*newSocket, sessionID_, host, port, silent); switch(status) { case Message::OK: forwardedStreams_.push_back(ForwardedStream(newSocket.get(), host, port, silent)); newSocket = nullptr; // release after successful push_back only return ResultType(true); case Message::EMPTY_ANSWER: case Message::CLOSED_SOCKET: case Message::INVALID_ID: case Message::I2P_ERROR: fallSick(); break; default: break; } return ResultType(); } RequestResult StreamSession::namingLookup(const std::string& name) const { typedef RequestResult ResultType; typedef Message::Answer AnswerType; std::shared_ptr newSocket(new Socket(socket_)); const AnswerType answer = namingLookup(*newSocket, name); switch(answer.status) { case Message::OK: return ResultType(answer.value); case Message::EMPTY_ANSWER: case Message::CLOSED_SOCKET: fallSick(); break; default: break; } return ResultType(); } RequestResult StreamSession::destGenerate() const { typedef RequestResult ResultType; typedef Message::Answer AnswerType; std::shared_ptr newSocket(new Socket(socket_)); const AnswerType answer = destGenerate(*newSocket); switch(answer.status) { case Message::OK: return ResultType(answer.value); case Message::EMPTY_ANSWER: case Message::CLOSED_SOCKET: fallSick(); break; default: break; } return ResultType(); } FullDestination StreamSession::createStreamSession(const std::string& destination) { typedef Message::Answer AnswerType; const AnswerType answer = createStreamSession(socket_, sessionID_, nickname_, destination, i2pOptions_); if (answer.status != Message::OK) { fallSick(); return FullDestination(); } return FullDestination(answer.value.substr(0, I2P_DESTINATION_SIZE) + "A==", answer.value, (destination == SAM_GENERATE_MY_DESTINATION)); } void StreamSession::fallSick() const { isSick_ = true; } void StreamSession::stopForwarding(const std::string& host, uint16_t port) { for (ForwardedStreamsContainer::iterator it = forwardedStreams_.begin(); it != forwardedStreams_.end(); ) { if (it->port == port && it->host == host) { delete (it->socket); it = forwardedStreams_.erase(it); } else ++it; } } void StreamSession::stopForwardingAll() { for (ForwardedStreamsContainer::iterator it = forwardedStreams_.begin(); it != forwardedStreams_.end(); ++it) delete (it->socket); forwardedStreams_.clear(); } /*static*/ Message::Answer StreamSession::rawRequest(Socket& socket, const std::string& requestStr) { typedef Message::Answer AnswerType; if (!socket.isOk()) return AnswerType(Message::CLOSED_SOCKET); socket.write(requestStr); const std::string answer = socket.read(); const Message::eStatus status = Message::checkAnswer(answer); return AnswerType(status, answer); } /*static*/ Message::Answer StreamSession::request(Socket& socket, const std::string& requestStr, const std::string& keyOnSuccess) { typedef Message::Answer AnswerType; const AnswerType answer = rawRequest(socket, requestStr); return (answer.status == Message::OK) ? AnswerType(answer.status, Message::getValue(answer.value, keyOnSuccess)) : answer; } /*static*/ Message::eStatus StreamSession::request(Socket& socket, const std::string& requestStr) { return rawRequest(socket, requestStr).status; } /*static*/ Message::Answer StreamSession::createStreamSession(Socket& socket, const std::string& sessionID, const std::string& nickname, const std::string& destination, const std::string& options) { return request(socket, Message::sessionCreate(Message::sssStream, sessionID, nickname, destination, options), "DESTINATION"); } /*static*/ Message::Answer StreamSession::namingLookup(Socket& socket, const std::string& name) { return request(socket, Message::namingLookup(name), "VALUE"); } /*static*/ Message::Answer StreamSession::destGenerate(Socket& socket) { // while answer for a DEST GENERATE request doesn't contain a "RESULT" field we parse it manually typedef Message::Answer ResultType; if (!socket.isOk()) return ResultType(Message::CLOSED_SOCKET, FullDestination()); socket.write(Message::destGenerate()); const std::string answer = socket.read(); const std::string pub = Message::getValue(answer, "PUB"); const std::string priv = Message::getValue(answer, "PRIV"); return (!pub.empty() && !priv.empty()) ? ResultType(Message::OK, FullDestination(pub, priv, /*isGenerated*/ true)) : ResultType(Message::EMPTY_ANSWER, FullDestination()); } /*static*/ Message::eStatus StreamSession::accept(Socket& socket, const std::string& sessionID, bool silent) { return request(socket, Message::streamAccept(sessionID, silent)); } /*static*/ Message::eStatus StreamSession::connect(Socket& socket, const std::string& sessionID, const std::string& destination, bool silent) { return request(socket, Message::streamConnect(sessionID, destination, silent)); } /*static*/ Message::eStatus StreamSession::forward(Socket& socket, const std::string& sessionID, const std::string& host, uint16_t port, bool silent) { return request(socket, Message::streamForward(sessionID, host, port, silent)); } const std::string& StreamSession::getNickname() const { return nickname_; } const std::string& StreamSession::getSessionID() const { return sessionID_; } const std::string& StreamSession::getOptions() const { return i2pOptions_; } const FullDestination& StreamSession::getMyDestination() const { return myDestination_; } bool StreamSession::isSick() const { return isSick_; } const sockaddr_in& StreamSession::getSAMAddress() const { return socket_.getAddress(); } const std::string& StreamSession::getSAMHost() const { return socket_.getHost(); } uint16_t StreamSession::getSAMPort() const { return socket_.getPort(); } const std::string& StreamSession::getSAMMinVer() const { return socket_.getMinVer(); } const std::string& StreamSession::getSAMMaxVer() const { return socket_.getMaxVer(); } const std::string& StreamSession::getSAMVersion() const { return socket_.getVersion(); } std::shared_ptr StreamSession::logStream; std::ostream& StreamSession::getLogStream () { return logStream ? *logStream : std::cout; } void StreamSession::SetLogFile (const std::string& filename) { logStream = std::make_shared (filename, std::ofstream::out | std::ofstream::trunc); } void StreamSession::CloseLogFile () { logStream = nullptr; } //-------------------------------------------------------------------------------------------------- std::string Message::createSAMRequest(const char* format, ...) { char buffer[SAM_BUFSIZE]; memset(buffer, 0, SAM_BUFSIZE); va_list args; va_start (args, format); const int sizeToSend = vsnprintf(buffer, SAM_BUFSIZE, format, args); va_end(args); if (sizeToSend < 0) { print_error("Failed to format message"); return std::string(); } return std::string(buffer); } std::string Message::hello(const std::string &minVer, const std::string &maxVer) { /////////////////////////////////////////////////////////// // // -> HELLO VERSION // MIN=$min // MAX=$max // // <- HELLO REPLY // RESULT=OK // VERSION=$version // /////////////////////////////////////////////////////////// static const char* helloFormat = "HELLO VERSION MIN=%s MAX=%s\n"; return createSAMRequest(helloFormat, minVer.c_str(), maxVer.c_str()); } std::string Message::sessionCreate(SessionStyle style, const std::string& sessionID, const std::string& nickname, const std::string& destination /*= SAM_GENERATE_MY_DESTINATION*/, const std::string& options /*= ""*/) { /////////////////////////////////////////////////////////// // // -> SESSION CREATE // STYLE={STREAM,DATAGRAM,RAW} // ID={$nickname} // DESTINATION={$private_destination_key,TRANSIENT} // [option=value]* // // <- SESSION STATUS // RESULT=OK // DESTINATION=$private_destination_key // /////////////////////////////////////////////////////////// std::string sessionStyle; switch(style) { case sssStream: sessionStyle = "STREAM"; break; case sssDatagram: sessionStyle = "DATAGRAM"; break; case sssRaw: sessionStyle = "RAW"; break; } static const char* sessionCreateFormat = "SESSION CREATE STYLE=%s ID=%s DESTINATION=%s inbound.nickname=%s SIGNATURE_TYPE=7 %s\n"; // we add inbound.nickname option return createSAMRequest(sessionCreateFormat, sessionStyle.c_str(), sessionID.c_str(), destination.c_str(), nickname.c_str(), options.c_str()); } std::string Message::streamAccept(const std::string& sessionID, bool silent /*= false*/) { /////////////////////////////////////////////////////////// // // -> STREAM ACCEPT // ID={$nickname} // [SILENT={true,false}] // // <- STREAM STATUS // RESULT=$result // [MESSAGE=...] // /////////////////////////////////////////////////////////// static const char* streamAcceptFormat = "STREAM ACCEPT ID=%s SILENT=%s\n"; return createSAMRequest(streamAcceptFormat, sessionID.c_str(), silent ? "true" : "false"); } std::string Message::streamConnect(const std::string& sessionID, const std::string& destination, bool silent /*= false*/) { /////////////////////////////////////////////////////////// // // -> STREAM CONNECT // ID={$nickname} // DESTINATION=$peer_public_base64_key // [SILENT={true,false}] // // <- STREAM STATUS // RESULT=$result // [MESSAGE=...] // /////////////////////////////////////////////////////////// static const char* streamConnectFormat = "STREAM CONNECT ID=%s DESTINATION=%s SILENT=%s\n"; return createSAMRequest(streamConnectFormat, sessionID.c_str(), destination.c_str(), silent ? "true" : "false"); } std::string Message::streamForward(const std::string& sessionID, const std::string& host, uint16_t port, bool silent /*= false*/) { /////////////////////////////////////////////////////////// // // -> STREAM FORWARD // ID={$nickname} // PORT={$port} // [HOST={$host}] // [SILENT={true,false}] // // <- STREAM STATUS // RESULT=$result // [MESSAGE=...] // /////////////////////////////////////////////////////////// static const char* streamForwardFormat = "STREAM FORWARD ID=%s PORT=%u HOST=%s SILENT=%s\n"; return createSAMRequest(streamForwardFormat, sessionID.c_str(), (unsigned)port, host.c_str(), silent ? "true" : "false"); } std::string Message::namingLookup(const std::string& name) { /////////////////////////////////////////////////////////// // // -> NAMING LOOKUP // NAME=$name // // <- NAMING REPLY // RESULT=OK // NAME=$name // VALUE=$base64key // /////////////////////////////////////////////////////////// static const char* namingLookupFormat = "NAMING LOOKUP NAME=%s\n"; return createSAMRequest(namingLookupFormat, name.c_str()); } std::string Message::destGenerate() { /////////////////////////////////////////////////////////// // // -> DEST GENERATE // // <- DEST REPLY // PUB=$pubkey // PRIV=$privkey // /////////////////////////////////////////////////////////// static const char* destGenerateFormat = "DEST GENERATE SIGNATURE_TYPE=7\n"; return createSAMRequest(destGenerateFormat); } #define SAM_MAKESTRING(X) SAM_MAKESTRING2(X) #define SAM_MAKESTRING2(X) #X #define SAM_CHECK_RESULT(value) \ if (result == SAM_MAKESTRING(value)) return value Message::eStatus Message::checkAnswer(const std::string& answer) { if (answer.empty()) return EMPTY_ANSWER; const std::string result = getValue(answer, "RESULT"); SAM_CHECK_RESULT(OK); SAM_CHECK_RESULT(DUPLICATED_DEST); SAM_CHECK_RESULT(DUPLICATED_ID); SAM_CHECK_RESULT(I2P_ERROR); SAM_CHECK_RESULT(INVALID_ID); SAM_CHECK_RESULT(INVALID_KEY); SAM_CHECK_RESULT(CANT_REACH_PEER); SAM_CHECK_RESULT(TIMEOUT); SAM_CHECK_RESULT(NOVERSION); SAM_CHECK_RESULT(KEY_NOT_FOUND); SAM_CHECK_RESULT(PEER_NOT_FOUND); SAM_CHECK_RESULT(ALREADY_ACCEPTING); return CANNOT_PARSE_ERROR; } #undef SAM_CHECK_RESULT #undef SAM_MAKESTRING2 #undef SAM_MAKESTRING std::string Message::getValue(const std::string& answer, const std::string& key) { if (key.empty()) return std::string(); const std::string keyPattern = key + "="; size_t valueStart = answer.find(keyPattern); if (valueStart == std::string::npos) return std::string(); valueStart += keyPattern.length(); size_t valueEnd = answer.find_first_of(' ', valueStart); if (valueEnd == std::string::npos) valueEnd = answer.find_first_of('\n', valueStart); return answer.substr(valueStart, valueEnd - valueStart); } } // namespace SAM