├── buffer.h ├── config.cpp ├── config.h ├── crawler.cpp ├── crawler.h ├── data.cpp ├── data.h ├── http.cpp ├── http.h ├── log.cpp ├── log.h ├── main.cpp ├── makefile ├── onlist ├── client.cpp ├── client.h ├── makefile └── onlist.cpp ├── outnet.svc ├── outnetStart.url ├── readme.md ├── sign.cpp ├── sign.h ├── sign ├── tweetnacl.c └── tweetnacl.h ├── sock.cpp ├── sock.h ├── svc.h ├── svclin.cpp ├── svcwin.cpp ├── upnp ├── upnpnat.cpp ├── upnpnat.h ├── xmlParser.cpp └── xmlParser.h ├── utils.cpp └── utils.h /buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef BUFFER_H_INCLUDED 2 | #define BUFFER_H_INCLUDED 3 | #include // memcpy 4 | 5 | template // uint32_t or uint64_t 6 | class Buffer { 7 | SIZET len; // size of data in buffer 8 | SIZET maxLen; // allocated memory size 9 | SIZET offset; // read() returns data from buff[offset] 10 | char* buff; // allocated memory pointer 11 | public: 12 | Buffer(): len(0), maxLen(0), offset(0), buff(nullptr) {} 13 | virtual ~Buffer(){ if(buff){ free(buff); } } 14 | 15 | void reset() { len = 0; offset = 0; } 16 | char* get() { return buff; } 17 | SIZET size() { return len; } 18 | 19 | SIZET read(void* bytes, SIZET count){ 20 | SIZET size = min(count, len-offset); 21 | memcpy(bytes, buff+offset, size); 22 | offset += size; 23 | return size; 24 | } 25 | 26 | void write(const void* bytes, SIZET count){ 27 | char* where = reserve(count); 28 | memcpy(where, bytes, count); 29 | } 30 | 31 | char* reserve(SIZET bytes){ 32 | char* where = buff+len; 33 | len += bytes; 34 | if(len > maxLen){ 35 | maxLen = len < (64*1024) ? (64*1024) : 2*len; 36 | buff = (char*) realloc(buff, maxLen); 37 | } 38 | return where; 39 | } 40 | }; 41 | typedef Buffer Buffer32; 42 | typedef Buffer Buffer64; 43 | 44 | 45 | #endif // BUFFER_H_INCLUDED -------------------------------------------------------------------------------- /config.cpp: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "sock.h" // Sock::ANY_PORT, Sock::stringToIP() 3 | #include "utils.h" 4 | #include "log.h" 5 | #include "svc.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | using namespace std; 14 | using namespace std::filesystem; 15 | 16 | 17 | // watch() looks in the current dir for files of the following types: 18 | // *.badip *.badkey - blacklists of IPs or public keys 19 | // *.service - service description file (one service description per line) 20 | int Config::watch() { 21 | while( true ){ 22 | for(uint32_t i=0; i < refreshRate; ++i){ 23 | if( !keepRunning() ) { 24 | log() << "Config thread exiting." << endl; 25 | return 0; 26 | } 27 | this_thread::sleep_for( seconds(1) ); 28 | } 29 | loadServiceFiles(); 30 | loadBlackListFiles(); 31 | } 32 | } 33 | // TODO: config could keep a list of ports forwarded on a router (with their lease times) 34 | // if a service no longer accepts a connection or "rejects" a 0 size UDP packet, 35 | // close the port and remove the service from the list 36 | 37 | 38 | struct ServiceString: public string { 39 | bool operator<(const Service& serv) const { return compare(serv.originalDescription) < 0; } 40 | }; // This class provides operator<(Service&) for string. Use it with stl algorithms 41 | 42 | 43 | struct IpPortProt { 44 | uint32_t ip; 45 | uint16_t port; 46 | uint8_t prot; 47 | IpPortProt(uint32_t ip1, uint16_t port1, uint8_t prot1): ip(ip1), port(port1), prot(prot1) {} 48 | }; 49 | 50 | 51 | // look for *.service in current directory and load their contents into LocalData::services 52 | int Config::loadServiceFiles(){ 53 | auto& os = log() << "Loading service files: "; 54 | static const string extension = ".service"; 55 | vector lines; 56 | int fCount = parseFilesIntoLines(extension, lines); 57 | os << fCount << " found." << endl; 58 | std::sort( begin(lines), end(lines) ); // set_difference needs sorted data 59 | 60 | vector newServices; // newServices(not in ldata->services) 61 | vector portsToOpen; 62 | unique_lock ulock(ldata->mutx); 63 | sort( begin(ldata->services), end(ldata->services) ); // set_difference needs sorted data 64 | vector& s = * reinterpret_cast*>( &lines ); // provides string::operator<(Service&) 65 | set_difference( begin(s), end(s), begin(ldata->services), end(ldata->services), back_inserter(newServices)); 66 | 67 | // vector oldServices; // oldServices(not in "lines") 68 | // set_difference( begin(ldata->services), end(ldata->services), begin(s), end(s), back_inserter(oldServices)); 69 | // for(Service& serv: oldServices){ // delete old services 70 | // ldata->services.erase( remove( begin(ldata->services), end(ldata->services), serv ) ); 71 | // } 72 | 73 | // insert new services and prepare to open ports for them 74 | for(const string& serv: newServices){ 75 | auto s = ldata->addService(serv); 76 | if(!s) { continue; } // did it parse correctly? 77 | portsToOpen.emplace_back(s->ip, s->port, s->tcpudp[0]); // tcp/udp comes from service description 78 | } 79 | ulock.unlock(); // after unlocking we can open and close ports which can take a while 80 | 81 | for(auto& ipPort: portsToOpen){ // open router ports using UPnP protocol for new services 82 | string ip = Sock::ipToString(ipPort.ip); 83 | const char* protocol = ('t'==ipPort.prot) ? "TCP" : "UDP"; // u == UDP 84 | upnp.add_port_mapping("OutNet", ip.c_str(), ipPort.port, ipPort.port, protocol); 85 | } 86 | 87 | // for(auto& old: oldServices){ // close ports for old services 88 | // upnp.closePort(old.ip, old.port); 89 | // } 90 | return 0; 91 | } 92 | 93 | 94 | int Config::loadBlackListFiles(){ // load *.badkey and *.badip files 95 | static const string ext1 = ".badip"; 96 | vector lines; 97 | auto& os = log() << "Loading IP blacklist files: "; 98 | int fCount = parseFilesIntoLines(ext1, lines); 99 | os << fCount << " found." << endl; 100 | 101 | static const string ext2 = ".badkey"; 102 | vector lines2; 103 | log() << "Loading KEY blacklist files: "; 104 | fCount = parseFilesIntoLines(ext2, lines2); 105 | os << fCount << " found." << endl; 106 | 107 | unique_lock ulock(blist->mutx); 108 | 109 | for(string& l: lines){ // load lines into blist->badHosts 110 | uint32_t ip = Sock::stringToIP( l.c_str() ); 111 | if(ip > 0){ 112 | blist->badHosts.push_back(ip); 113 | } 114 | } 115 | 116 | for(string& l: lines2){ // load lines2 into blist->badKeys 117 | if(l.length() == 2*PUBKEY_SIZE){ // each byte is 2 hex digits long 118 | PubKey& pk = blist->badKeys.emplace_back(); 119 | for(int i=0; i < PUBKEY_SIZE; ++i){ // convert hex string to bytes 120 | string sub = l.substr(i*2, 2); // each byte is 2 hex digits long 121 | pk.key[i] = strtol(sub.c_str(), nullptr, 16); 122 | } 123 | } 124 | } 125 | 126 | sort( blist->badHosts.begin(), blist->badHosts.end() ); // sort to make search faster 127 | sort( blist->badKeys.begin(), blist->badKeys.end() ); 128 | return 0; 129 | } 130 | 131 | 132 | int Config::findIPs(){ 133 | log() << "Looking for your NAT router..." << endl; 134 | if ( upnp.discovery() ){ 135 | log() << "Retrieving this host's IP and WAN IP from the router..." << endl; 136 | string ipStr; 137 | upnp.getExternalIP(ipStr, ldata->localIP); 138 | log() << "Local IP: " << Sock::ipToString(ldata->localIP) << endl; 139 | if( Sock::isRoutable(ldata->localIP) ) { 140 | ldata->myIP = ldata->localIP; 141 | log() << "Local IP is not private (routable)." << endl; 142 | return 0; 143 | } 144 | if( ipStr.length() > 6){ // at least x.x.x.x 145 | ldata->myIP = Sock::stringToIP( ipStr.c_str() ); 146 | if( ldata->myIP > 0 ){ 147 | log() << "WAN IP: " << ipStr << endl; 148 | if( !Sock::isRoutable(ldata->myIP) ){ 149 | log() << "# WARNING: router's WAN IP is not routable. You are behind multiple NAT devices." << endl; 150 | log() << "# Manually configure your other NAT devices to forward ports to " << ipStr << endl; 151 | log() << "# Otherwise no one can connect TO YOU from the internet." << endl; 152 | } 153 | return 0; 154 | } 155 | } // this is an indication there is no NAT taking place. 156 | logErr() << "Error retrieving IPs from the router." << endl; 157 | } else { 158 | logErr() << "Failed to find a NAT router. ERROR: " << upnp.get_last_error() << endl; 159 | } 160 | //1 TODO: fill ldata->myIP and ldata->localIP without using router's help 161 | // connecto to something and get local IP that way. 162 | return 0; 163 | } 164 | 165 | 166 | // a member of type "const std::string" cannot have an in-class initializerC/C++(1591) 167 | static const string configName = "outnet.cfg"; 168 | 169 | // load port and refresh rate from config file 170 | void Config::init(LocalData& lData, BlackList& bList){ 171 | ldata = &lData; 172 | blist = &bList; 173 | loadServiceFiles(); 174 | loadBlackListFiles(); 175 | findIPs(); 176 | 177 | log() << "Loading configuration data." << endl; 178 | ldata->myPort = Sock::ANY_PORT; // default port for OutNet to listen on 179 | 180 | ifstream config (configName); 181 | if( !config ){ 182 | log() << configName << " not found. Setting configuration data to defaults." << endl; 183 | return; 184 | } 185 | 186 | vector lines; 187 | parseLines(config, lines); 188 | config.close(); 189 | 190 | for(string& line: lines){ 191 | string key, value; 192 | if( keyValue(line, key, value)){ 193 | toLower(key); 194 | if( key == "sleepconfig" ){ 195 | refreshRate = strtol(value.c_str(), nullptr, 10); // base 10 196 | } else if (key == "sleepserver"){ 197 | ldata->sleepServer = strtol(value.c_str(), nullptr, 10); // base 10 198 | } else if (key == "sleepcrawler"){ 199 | ldata->sleepCrawler = strtol(value.c_str(), nullptr, 10); // base 10 200 | } else if (key == "timeoutserver"){ 201 | ldata->timeoutServer = strtol(value.c_str(), nullptr, 10); // base 10 202 | } else if (key == "timeoutcrawler"){ 203 | ldata->timeoutCrawler = strtol(value.c_str(), nullptr, 10); // base 10 204 | } else if (key == "timeoutupnp"){ 205 | ldata->timeoutUPNP = strtol(value.c_str(), nullptr, 10); // base 10 206 | } else if (key == "serverport"){ 207 | ldata->myPort = strtol(value.c_str(), nullptr, 10); // base 10 208 | } 209 | } 210 | } 211 | 212 | if( ldata->myPort != Sock::ANY_PORT ) { 213 | log() << "Configuration data loaded successfuly." << endl; 214 | }else { 215 | log() << "Config file is corrupted. It will be regenerated." << endl; 216 | } 217 | } 218 | 219 | 220 | int Config::saveToDisk(){ 221 | log() << "Saving configuration data to disk." << endl; 222 | ofstream config(configName); 223 | string msg1 = "# Configuration file for OutNet service https://github.com/rand3289/OutNet"; 224 | string msg2 = "# If this file is deleted or corrupted it will be regenerated with default values."; 225 | string msg3 = "# SleepZZZ and TimeoutZZZ are in seconds."; 226 | config << msg1 << endl << msg2 << endl << msg3 << endl << endl; 227 | 228 | shared_lock slock(ldata->mutx); 229 | config << "SleepConfig=" << refreshRate << "\t# how often do I look for new or updated files" << endl; 230 | config << "SleepServer=" << ldata->sleepServer << "\t# how long do I wait for client to close connection" << endl; 231 | config << "SleepCrawler=" << ldata->sleepCrawler << "\t# wait time between scanning other OutNet services" << endl; 232 | config << endl; 233 | config << "TimeoutServer=" << ldata->timeoutServer << "\t# server network read / write timeout" << endl; 234 | config << "TimeoutCrawler=" << ldata->timeoutCrawler << "\t# crawler network read / write timeout" << endl; 235 | config << "TimeoutUPNP=" << ldata->timeoutUPNP << " \t# UPNP client network read / write timeout" << endl; 236 | config << endl; 237 | config << "ServerPort=" << ldata->myPort << "\t# server accepts connections on this port" << endl; 238 | return 0; 239 | } 240 | 241 | 242 | bool Config::forwardLocalPort(uint16_t port){ 243 | string localAddr = Sock::ipToString(ldata->localIP); 244 | log() << "Forwarding router's WAN port " << port << " to local " << localAddr << ":" << port << endl; 245 | if( 0 == ldata->localIP || port == 0){ 246 | logErr() << "ERROR: local IP or PORT are blank! Can not forward port." << endl; 247 | return false; 248 | } 249 | if( !upnp.add_port_mapping("OutNet main", localAddr.c_str(), port, port, "TCP")){ 250 | logErr() << upnp.get_last_error() << endl; 251 | return false; 252 | } 253 | return true; 254 | } 255 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H_INCLUDED 2 | #define CONFIG_H_INCLUDED 3 | #include "data.h" 4 | #include "upnpnat.h" 5 | 6 | // loads refresh rate, port, local services, blacklists from disk, stores port 7 | class Config { 8 | UPNPNAT upnp; 9 | LocalData* ldata; 10 | BlackList* blist; 11 | uint32_t refreshRate = 600; // seconds default 12 | 13 | int loadBlackListFiles(); 14 | int loadServiceFiles(); 15 | int findIPs(); 16 | public: 17 | void init(LocalData& ldata, BlackList& blist); 18 | bool forwardLocalPort(uint16_t port); 19 | int saveToDisk(); 20 | int watch(); 21 | }; 22 | 23 | #endif // CONFIG_H_INCLUDED -------------------------------------------------------------------------------- /crawler.cpp: -------------------------------------------------------------------------------- 1 | #include "crawler.h" 2 | #include "sock.h" 3 | #include "http.h" 4 | #include "utils.h" 5 | #include "log.h" 6 | #include "svc.h" 7 | #include 8 | #include 9 | #include // erase_if(unordered_multimap, predicate) 10 | #include 11 | #include // since C++17 12 | #include 13 | #include 14 | #include // ofstream 15 | #include 16 | #include 17 | using namespace std; 18 | 19 | 20 | int ERR(const string& msg){ // makes error handling code a bit shorter 21 | logErr() << "ERROR " << msg << endl; 22 | return 0; 23 | } 24 | 25 | 26 | // filters is optional. It defaults to nullptr. 27 | int Crawler::queryRemoteService(HostInfo& hi, vector& newData, uint32_t select, vector* filters){ 28 | shared_lock slock(rdata.mutx); // TODO: construct query in run() to avoid locking here ??? 29 | uint32_t hiHost = hi.host; // make local copies so we can release slock 30 | uint16_t hiPort = hi.port; 31 | 32 | shared_ptr locPubKey; 33 | // if we have remote's public key, do not request it (turn it off in select) 34 | if( hi.signatureVerified ){ // Do not go by pure existence of a key. It has to be verified! 35 | locPubKey = hi.key; 36 | turnBitsOff(select, SELECTION::LKEY); 37 | } // if signature remote sends fails to verify, next time we request the key again 38 | 39 | stringstream ss; 40 | ss << "GET /?QUERY=" << select; 41 | // add "filter by time" if remote was contacted before. Get new data only. 42 | if( 0 == hi.offlineCount && hi.seen > system_clock::from_time_t(0) ){ 43 | int ageMinutes = (int) duration_cast(system_clock::now() - hi.seen).count(); 44 | ss << "&AGE_LT_" << ageMinutes; 45 | } 46 | slock.unlock(); // this is the last place where we read hi.blah 47 | 48 | if(portCopy>0){ // ad own server port for remote to connect back to 49 | ss << "&SPORT=" << portCopy; 50 | } 51 | if(filters){ // did the caller include any query parameters? 52 | for(string& f: *filters){ ss << "&" << f; } 53 | } 54 | ss << " HTTP/1.1\r\n\r\n"; 55 | 56 | log() << "Crawler connecting to " << Sock::ipToString(hiHost) << ":" << hiPort << endl; 57 | Sock sock; 58 | sock.setRWtimeout(ldata.timeoutCrawler); // seconds read/write timeout 59 | sock.setNoDelay(true); // request is one write() 60 | 61 | if( sock.connect(hiHost, hiPort) ){ 62 | // logErr() << "ERROR connecting to " << Sock::ipToString(hiHost) << ":" << hiPort << endl; 63 | unique_lock ulock(rdata.mutx); // release lock after each connection for other parts to work 64 | hi.missed = system_clock::now(); 65 | ++hi.offlineCount; 66 | --hi.rating; 67 | return 0; 68 | } 69 | 70 | int len = (int) ss.str().length(); 71 | if(len != sock.write(ss.str().c_str(), len ) ){ 72 | return ERR("sending HTTP request"); 73 | } 74 | 75 | // parse the response into unverifiedData 76 | // if we requested signature, and the server did not send it, rating -100, dispose of data 77 | // read PubKey, services and wait till end of parsing / signature verification to lock and copy to "hi" 78 | 79 | // read HTTP/1.1 200 OK\r\n\r\n 80 | char buff[256]; 81 | int rdsize = sock.readLine(buff, sizeof(buff) ); 82 | if( rdsize < 8 || nullptr == strstr(buff,"200") ) { 83 | logErr() << "ERROR in queryRemoteService() while parsing " << rdsize << " bytes: " << buff << endl; 84 | return 0; 85 | } 86 | while( sock.readLine(buff, sizeof(buff) ) > 0 ) {} // skip till empty line is read (HTTP protocol) 87 | 88 | /************ Everything read below this line is signed *************/ 89 | const bool sign = select & SELECTION::SIGN; // is signature requested? 90 | if(sign){ signer.init(); } 91 | 92 | uint32_t selectRet; // SELECT is always received 93 | rdsize = sock.read(&selectRet, sizeof(selectRet) ); 94 | if(rdsize != sizeof(selectRet)){ 95 | return ERR("reading 'select'"); 96 | } 97 | if(sign){ signer.write(&selectRet, sizeof(selectRet)); } // signer gets data in network byte order 98 | 99 | selectRet = ntohl(selectRet); 100 | if( sign && !(selectRet & SELECTION::SIGN) ){ // we requested a signature but it was not returned 101 | return ERR("remote refused to sign response"); // this is a security problem 102 | } 103 | 104 | if(selectRet & SELECTION::LKEY){ 105 | locPubKey = make_shared(); 106 | rdsize = sock.read(&*locPubKey, sizeof(PubKey)); 107 | if( rdsize != sizeof(PubKey) ){ 108 | return ERR("reading remote public key"); 109 | } 110 | if( blist.isBanned(*locPubKey) ){ 111 | return ERR("key is banned. Disconnecting!"); 112 | } 113 | if(sign){ signer.write(&*locPubKey, sizeof(PubKey)); } 114 | } 115 | 116 | if(selectRet & SELECTION::TIME){ 117 | int32_t timeRemote; 118 | rdsize = sock.read(&timeRemote, sizeof(timeRemote) ); 119 | if(rdsize != sizeof(timeRemote) ){ 120 | return ERR("reading remote's time."); 121 | } 122 | if(sign){ signer.write(&timeRemote, sizeof(timeRemote)); } 123 | timeRemote = ntohl(timeRemote); 124 | 125 | // check that timestamp is not too long in the past, otherwise it can be a replay attack 126 | int32_t minOld = timeMinutes() - timeRemote; // does not depend on a timezone 127 | if( abs(minOld) > 5 ){ 128 | return ERR("Remote time difference is " + to_string(minOld) + " minutes. Discarding data."); 129 | } 130 | } 131 | 132 | vector lservices; 133 | if( selectRet & SELECTION::LSVC ){ 134 | uint16_t count; 135 | rdsize = sock.read(&count, sizeof(count)); 136 | if(rdsize != sizeof(count) ){ 137 | return ERR("reading remote service count."); 138 | } 139 | if(sign){ signer.write(&count, sizeof(count)); } 140 | count = ntohs(count); 141 | 142 | for(int i=0; i < count; ++i){ 143 | rdsize = sock.readString(buff, sizeof(buff)); 144 | if(rdsize <=0){ 145 | return ERR("reading remote serices."); 146 | } 147 | lservices.push_back(buff); 148 | if(sign){ signer.writeString(buff); } 149 | } 150 | } 151 | 152 | /******************** remote data *************************/ 153 | uint32_t count; 154 | rdsize = sock.read(&count, sizeof(count) ); // count is always there even if data is not 155 | if(rdsize != sizeof(count) ){ 156 | return ERR("reading HostInfo count."); 157 | } 158 | if(sign){ signer.write(&count, sizeof(count)); } 159 | count = ntohl(count); 160 | 161 | vector unverifiedData; 162 | for(uint32_t i=0; i< count; ++i){ 163 | HostInfo& hil = unverifiedData.emplace_back(); 164 | hil.referIP = hiHost; 165 | hil.referPort = hiPort; 166 | hil.met = system_clock::now(); 167 | bool discard = false; // if record is bad, continue reading but discard it at the end 168 | 169 | if( selectRet & SELECTION::IP ){ // IP does not need ntohl() 170 | rdsize = sock.read( &hil.host, sizeof(hil.host)); 171 | if(rdsize != sizeof(hil.host)){ 172 | return ERR("reading IP."); 173 | } 174 | if(sign){ signer.write(&hil.host, sizeof(hil.host) ); } 175 | } 176 | 177 | if( selectRet & SELECTION::PORT ){ 178 | rdsize = sock.read(&hil.port, sizeof(hil.port) ); 179 | if(rdsize != sizeof(hil.port) ){ 180 | return ERR("reading port."); 181 | } 182 | if(sign){ signer.write(&hil.port, sizeof(hil.port)); } 183 | hil.port = ntohs(hil.port); 184 | } 185 | 186 | if( selectRet&SELECTION::IP && selectRet&SELECTION::PORT ){ 187 | if( hil.host==0 || hil.port==0 || !Sock::isRoutable(hil.host) ){ 188 | discard = true; // throw away services with invalid host or port 189 | } 190 | 191 | if( hil.host==hostCopy && hil.port==portCopy){ 192 | discard = true; // just found myself in the list of IPs 193 | } 194 | } 195 | 196 | if( selectRet & SELECTION::AGE ){ 197 | uint16_t age; 198 | rdsize = sock.read(&age, sizeof(age) ); 199 | if(rdsize != sizeof(age) ){ 200 | return ERR("reading age."); 201 | } 202 | if(sign){ signer.write(&age, sizeof(age)); } 203 | age = ntohs(age); 204 | hil.seen = system_clock::now() - minutes(age); // TODO: check some reserved values ??? 205 | } 206 | 207 | if( selectRet & SELECTION::RKEY ){ 208 | unsigned char keyCount = 0; 209 | rdsize = sock.read( &keyCount, sizeof(keyCount) ); 210 | if( rdsize != sizeof(keyCount) ){ 211 | return ERR("reading key count."); 212 | } 213 | if(sign){ signer.write(&keyCount, sizeof(keyCount)); } 214 | if(keyCount){ // for now only one key is allowed 215 | hil.key = make_shared (); 216 | rdsize = sock.read( &*hil.key, sizeof(PubKey) ); 217 | if(rdsize != sizeof(PubKey) ){ 218 | return ERR("reading public key."); 219 | } 220 | if(sign){ signer.write(&*hil.key, sizeof(PubKey)); } 221 | if( blist.isBanned(*hil.key) ) { discard = true; } 222 | } 223 | } 224 | 225 | if( selectRet & SELECTION::RSVC ){ 226 | uint16_t cnt; 227 | rdsize = sock.read(&cnt, sizeof(cnt) ); 228 | if(rdsize != sizeof(cnt) ){ 229 | return ERR("reading remote service count."); 230 | } 231 | if(sign){ signer.write(&cnt, sizeof(cnt)); } 232 | cnt = ntohs(cnt); 233 | 234 | for(int i=0; i< cnt; ++i){ 235 | rdsize = sock.readString(buff, sizeof(buff)); 236 | if(rdsize <=0){ 237 | return ERR("reading remote serivces."); 238 | } 239 | if(sign){ signer.writeString(buff); } 240 | hil.addService(buff); 241 | } 242 | } 243 | 244 | if( discard ){ 245 | unverifiedData.pop_back(); 246 | } 247 | } // for (adding HostInfo) 248 | 249 | if(sign){ 250 | PubSign signature; 251 | if( sizeof(signature) != sock.read( &signature, sizeof(signature) ) ) { 252 | return ERR("reading signature from remote host"); 253 | } 254 | 255 | // If signature can not be verified, discard the data and reduce hi.rating below 0 256 | if( !signer.verify(signature, *locPubKey) ){ 257 | unique_lock ulock2(rdata.mutx); // release lock after each connection for other parts to work 258 | //1 TODO: if keys are different, these are different hosts with the same IP !!! 259 | hi.signatureVerified = false; 260 | hi.key.reset(); // delete stored public key since signature verification failed 261 | hi.rating = hi.rating < 0 ? hi.rating-1 : -1; 262 | hi.offlineCount = 0; 263 | hi.seen = system_clock::now(); 264 | return ERR("verifying signature"); 265 | } else { 266 | auto& os = log(); 267 | os << "Remote's public key: "; 268 | printHex(os, locPubKey->key, sizeof(PubKey)); 269 | } 270 | } 271 | 272 | std::copy( move_iterator(begin(unverifiedData)), move_iterator(end(unverifiedData)), back_inserter(newData)); 273 | 274 | // there is no upgrade mechanism to unique_lock. 275 | // release the lock after each connection to allow other threads to work 276 | unique_lock ulock2(rdata.mutx); 277 | if(sign) { 278 | //1 TODO: if signatures are different, it's a different service!!! Create a new HostInfo. 279 | if( !hi.signatureVerified && locPubKey ){ // (selectRet&SELECTION::LKEY) && !hi.key 280 | hi.key = locPubKey; 281 | } 282 | hi.signatureVerified = true; // mark signature verified 283 | ++hi.rating; // increase remote's rating for verifying signature 284 | } 285 | 286 | hi.offlineCount = 0; 287 | hi.seen = system_clock::now(); 288 | for(string& ls: lservices){ 289 | hi.addService(ls); // add newly found services to that host's service list 290 | } 291 | 292 | return (int) unverifiedData.size(); 293 | } 294 | 295 | 296 | // merge new HostInfo from newData into rdata.hosts 297 | // keep track of services that change IP by key and merge them with new IP 298 | int Crawler::merge(vector& newData){ 299 | int newCount = 0; 300 | unique_lock ulock(rdata.mutx); 301 | 302 | for(HostInfo& hiNew: newData){ 303 | bool found = false; 304 | 305 | // iterate through mmap values with the same key(IP) 306 | for(auto hi = rdata.hosts.find(hiNew.host); hi != end(rdata.hosts); ++hi){ 307 | if(hi->second.port == hiNew.port){ // existing service (ip/port match) - merge info 308 | if( !hi->second.key && hiNew.key ){ // existing record does not have a key 309 | hi->second.key = hiNew.key; // keys are shared_ptr 310 | } 311 | mergeServices(hi->second.services, hiNew.services); 312 | found = true; 313 | break; 314 | } 315 | } 316 | if( found ){ continue; } // continue iterating over newData 317 | 318 | auto it = rdata.hosts.emplace( hiNew.host, move(hiNew) ); // insert new HostInfo record 319 | auto hinew = it->second; // hiNew is no longer valid after move. use hinew 320 | 321 | // try matching services by public key if IP or port changed 322 | // since it has a new ip, use inserted entry (hinew) and delete old 323 | for(auto& hi_pit: rdata.hosts){ 324 | auto hi = hi_pit.second; 325 | if( hi.host != hinew.host && *hi.key == *hinew.key ){ // keys are shared_ptr 326 | mergeServices(hinew.services, hi.services); 327 | hinew.signatureVerified = hi.signatureVerified; 328 | hinew.offlineCount = hi.offlineCount; 329 | hinew.rating = hi.rating; 330 | hinew.met = move(hi.met); 331 | hinew.seen = move(hi.seen); 332 | hinew.missed = move(hi.missed); 333 | hinew.referIP = hi.referIP; 334 | hinew.referPort = hi.referPort; 335 | found = true; 336 | hi.host = 0; // mark the old HostInfo entry for deletion 337 | break; 338 | } 339 | } 340 | 341 | if(found){ // if found, old HostInfo was found by KEY and it was marked for deletion 342 | erase_if(rdata.hosts, [](auto& hi){ return 0==hi.second.host; } ); // delete old entries 343 | } else { 344 | ++newCount; 345 | } 346 | } 347 | 348 | return newCount; 349 | } 350 | 351 | 352 | // go through RemoteData/HostInfo entries and retrieve more data from those services. 353 | int Crawler::run(){ 354 | shared_lock slock(ldata.mutx); 355 | hostCopy = ldata.myIP; 356 | portCopy = ldata.myPort; 357 | slock.unlock(); 358 | 359 | uint32_t select = 0b11111111111; // see SELECTION in http.h 360 | turnBitsOff(select, SELECTION::ISCHK); // signatureVerified is of interest to local services only 361 | turnBitsOff(select, SELECTION::RSVCF); // do not filter out remote services by protocol 362 | 363 | while( keepRunning() ){ 364 | vector newData; 365 | vector callList; 366 | 367 | shared_lock slock(rdata.mutx); // remote data 368 | 369 | for(pair& ip_hi: rdata.hosts){ 370 | HostInfo& hi = ip_hi.second; 371 | if( hi.rating < 0){ continue; } 372 | if( system_clock::now() - hi.seen < minutes(60) ){ continue; } // do not contact often 373 | // contact hosts that have been seen offline in the past less often 374 | if( hi.offlineCount && (system_clock::now() - hi.missed < hi.offlineCount*minutes(60) ) ){ continue; } 375 | if( blist.isBanned(hi.host) ){ continue; } 376 | callList.push_back(&hi); // prepare to call that service up 377 | } 378 | 379 | // Sort pointers to rdata.hosts No need for exclusive_lock. 380 | // only queryRemoteService(), main() and merge() modify individual HostInfo records 381 | std::sort( begin(callList), end(callList), [=](HostInfo* one, HostInfo* two){ return one->seen < two->seen; } ); 382 | slock.unlock(); // remote data 383 | 384 | // iterate over data, connect to each remote service, get the data and place into newData 385 | for(HostInfo* hi: callList){ 386 | queryRemoteService(*hi, newData, select); 387 | } 388 | 389 | int count = merge(newData); 390 | if( count<=0 ){ 391 | for(int i=0; i < ldata.sleepCrawler && keepRunning(); ++i){ 392 | this_thread::sleep_for( seconds( 1 ) ); 393 | } 394 | } else { saveRemoteDataToDisk(); } // found new services 395 | } // while( keepRunning() ) 396 | 397 | log() << "Crawler thread exiting." << endl; 398 | return 0; 399 | } 400 | 401 | 402 | const string RDFile = "outnetPeers.save"; 403 | const string tempRDFile = "outnetPeers.tmp"; 404 | 405 | 406 | // Data is periodically saved. When service is restarted, it is loaded back up 407 | // RemoteData does not have to be locked here 408 | int Crawler::loadRemoteDataFromDisk(){ 409 | log() << "Loading remote data from disk." << endl; 410 | ifstream file(RDFile, std::ios::binary); 411 | if(!file.good()){ 412 | log() << "Could not find remote data restore point " << RDFile << endl; 413 | return 0; 414 | } 415 | uint32_t size; 416 | file.read( (char*) &size, sizeof(size) ); 417 | if(!file){ 418 | logErr() << "Could not read data from " << RDFile << endl; 419 | return -1; 420 | } 421 | 422 | std::unordered_multimap hosts; 423 | for(uint32_t i = 0; i < size; ++i){ // read "size" HostInfo structures 424 | HostInfo hi; 425 | file.read((char*) &hi.host, sizeof(hi.host) ); 426 | file.read( (char*) &*hi.key, sizeof(PubKey) ); 427 | file.read( (char*) &hi.met, sizeof(hi.met) ); 428 | file.read( (char*) &hi.missed, sizeof(hi.missed) ); 429 | file.read( (char*) &hi.seen, sizeof(hi.seen) ); 430 | file.read( (char*) &hi.offlineCount, sizeof(hi.offlineCount) ); 431 | file.read( (char*) &hi.port, sizeof(hi.port) ); 432 | file.read( (char*) &hi.rating, sizeof(hi.rating) ); 433 | file.read( (char*) &hi.referIP, sizeof(hi.referIP) ); 434 | file.read( (char*) &hi.referPort, sizeof(hi.referPort) ); 435 | unsigned char sigVer; 436 | file.read( (char*) &sigVer, sizeof(sigVer) ); 437 | hi.signatureVerified = sigVer; // bool to uchar 438 | 439 | uint32_t ss; 440 | file.read( (char*) &ss, sizeof(ss) ); 441 | for(uint32_t i=0; i < ss; ++i){ 442 | string service = readString(file); 443 | hi.addService(service); 444 | } 445 | hosts.emplace( hi.host, move(hi) ); 446 | } // for 447 | 448 | unique_lock ulock(rdata.mutx); 449 | rdata.hosts.swap(hosts); 450 | return 0; 451 | } 452 | 453 | 454 | // have to save everything overwriting old data since old data could be modified by merge() 455 | int Crawler::saveRemoteDataToDisk(){ // save data to disk 456 | log() << "Saving remote data to disk." << endl; 457 | ofstream file(tempRDFile, std::ios::binary); 458 | shared_lock slock(rdata.mutx); 459 | uint32_t hs = rdata.hosts.size(); 460 | file.write( (char*) &hs, sizeof(hs) ); 461 | if(!file){ // check that the writes succeeded 462 | logErr() << "ERROR saving remote data to disk. Do you have write permissions?" << endl; 463 | return -1; 464 | } 465 | 466 | for(auto& hip: rdata.hosts){ 467 | HostInfo& hi = hip.second; 468 | file.write( (char*) &hi.host, sizeof(hi.host) ); 469 | file.write( (char*) &*hi.key, sizeof(PubKey) ); 470 | file.write( (char*) &hi.met, sizeof(hi.met) ); 471 | file.write( (char*) &hi.missed, sizeof(hi.missed) ); 472 | file.write( (char*) &hi.seen, sizeof(hi.seen) ); 473 | file.write( (char*) &hi.offlineCount, sizeof(hi.offlineCount) ); 474 | file.write( (char*) &hi.port, sizeof(hi.port) ); 475 | file.write( (char*) &hi.rating, sizeof(hi.rating) ); 476 | file.write( (char*) &hi.referIP, sizeof(hi.referIP) ); 477 | file.write( (char*) &hi.referPort, sizeof(hi.referPort) ); 478 | unsigned char sigVer = hi.signatureVerified; // bool to uchar 479 | file.write( (char*) &sigVer, sizeof(sigVer) ); 480 | uint32_t ss = hi.services.size(); 481 | file.write( (char*) &ss, sizeof(ss) ); 482 | 483 | for(auto& serv: hi.services){ 484 | writeString(file, serv.originalDescription); 485 | } 486 | } 487 | 488 | slock.unlock(); 489 | if(!file){ 490 | logErr() << "ERROR saving remote data to disk." << endl; 491 | return -2; 492 | } 493 | file.close(); 494 | string fold = RDFile+".old"; 495 | remove(fold.c_str()); 496 | rename(RDFile.c_str(), fold.c_str() ); 497 | rename(tempRDFile.c_str(), RDFile.c_str() ); 498 | return 0; 499 | } 500 | -------------------------------------------------------------------------------- /crawler.h: -------------------------------------------------------------------------------- 1 | #ifndef CRAWLER_H_INCLUDED 2 | #define CRAWLER_H_INCLUDED 3 | #include "data.h" 4 | #include "sign.h" 5 | #include 6 | #include 7 | 8 | 9 | class Crawler { 10 | Signature signer; 11 | uint32_t hostCopy; 12 | uint16_t portCopy; 13 | LocalData& ldata; 14 | RemoteData& rdata; 15 | BlackList& blist; 16 | int merge(std::vector& newData); 17 | public: 18 | Crawler(LocalData& lData, RemoteData& rData, BlackList& bList): 19 | signer(), hostCopy(0), portCopy(0), ldata(lData), rdata(rData), blist(bList) {} 20 | int queryRemoteService(HostInfo& hi, std::vector& newData, uint32_t select, std::vector* filters = nullptr); 21 | int loadRemoteDataFromDisk(); 22 | int saveRemoteDataToDisk(); 23 | int run(); 24 | }; 25 | 26 | #endif // CRAWLER_H_INCLUDED -------------------------------------------------------------------------------- /data.cpp: -------------------------------------------------------------------------------- 1 | #include "data.h" 2 | #include "http.h" 3 | #include "sock.h" 4 | #include "utils.h" 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | 11 | HostInfo::HostInfo(): host(0), port(0), key(), services(), signatureVerified(false), offlineCount(0), 12 | rating(DEFAULT_RATING), referIP(0), referPort(0) { 13 | met = seen = missed = system_clock::from_time_t(0); // time_point::min() is broken (overflows) !!! 14 | } 15 | 16 | 17 | Service* HostInfo::addService(const string& service){ 18 | if(services.end() != find(services.begin(), services.end(), service) ) { return nullptr; } // exists 19 | auto s = services.emplace( services.end() )->parse(service, host); // parsing remote service 20 | if(!s){ services.pop_back(); } // if it was not parsed, delete it 21 | return s; 22 | } 23 | 24 | 25 | Service* LocalData::addService(const string& service){ 26 | if(services.end() != find(services.begin(), services.end(), service) ) { return nullptr; } // exists 27 | auto s = services.emplace( services.end() )->parse(service, myIP); // parsing local service 28 | if(!s){ services.pop_back(); } // if it was not parsed, delete it 29 | return s; 30 | } 31 | 32 | 33 | // helper free function to merge vectors of services from destination to source 34 | void mergeServices(vector& dest, vector& source){ 35 | for(auto& src: source){ 36 | auto it = find(begin(dest), end(dest), src); 37 | if(it!= end(dest) ){ continue; } // exists 38 | dest.push_back(move(src)); 39 | } 40 | } 41 | 42 | // parse service description into fields separated by ":" 43 | // example - "printer:ipp:8.8.8.8:54321:2nd floor printer" 44 | Service* Service::parse(const string& servStr, uint32_t myIP){ 45 | originalDescription = servStr; 46 | 47 | char* start = const_cast( servStr.c_str() ); // living dangerous :) 48 | const char* end = start+servStr.length(); 49 | char* token; 50 | 51 | if( tokenize(start, end, token, ":") ){ 52 | service = token; // TODO: enforce max len ??? 53 | if(service.length() < 1){ return nullptr; } 54 | } else { return nullptr; } 55 | 56 | if( tokenize(start, end, token, ":") ){ 57 | tcpudp = token; // TODO: enforce max len ??? 58 | if(tcpudp.length() != 3){ return nullptr; } 59 | } else { return nullptr; } 60 | 61 | if( tokenize(start, end, token, ":") ){ 62 | protocol = token; // TODO: enforce max len ??? 63 | if(protocol.length() < 1){ return nullptr; } 64 | } else { return nullptr; } 65 | 66 | if( tokenize(start, end, token, ":") ){ 67 | ip = Sock::stringToIP(token); 68 | if(0==ip){ return nullptr; } 69 | } else { return nullptr; } 70 | 71 | if( tokenize(start, end, token, ":") ){ 72 | port = (uint16_t) strtol(token, nullptr, 10); // base 10 73 | if(0==port){ return nullptr; } 74 | } else { return nullptr; } 75 | 76 | if( tokenize(start, end, token, ":") ){ 77 | other = token; // TODO: take UTF8 into account??? 78 | } else { return nullptr; } 79 | 80 | // Translate local to NAT (router) IP 81 | uint32_t routableIP = Sock::isRoutable(ip) ? ip : myIP; 82 | stringstream ss; 83 | ss << service << ":" << tcpudp << ":" << protocol << ":" << Sock::ipToString(routableIP) << ":" << port << ":" << other; 84 | fullDescription = ss.str(); 85 | return this; 86 | } 87 | 88 | 89 | // Decides if a service passes a filter. 90 | // There can be two types of service related filters specified in the HTTP GET Query string: 91 | // local filters apply to local services (start with L) and remote (start with R) 92 | // example: RPROT_EQ_HTTP&RPORT_LT_1025 93 | bool Service::passFilters(const vector>& filters, bool remote) const { 94 | string svc = remote ? "RSVC" : "LSVC"; 95 | string prot = remote ? "RPROT" : "LPROT"; 96 | string lrport = remote ? "RPORT" : "LPORT"; 97 | 98 | for(auto& filter: filters){ 99 | const string& func = filter[0]; 100 | const string& oper = filter[1]; 101 | const string& value = filter[2]; 102 | 103 | if( func == svc ){ 104 | if( service != value) { return false; } 105 | } else if( func == prot ){ 106 | if( protocol != value){ return false; } 107 | } else if( func == lrport ){ 108 | uint32_t intVal = strtol(value.c_str(), nullptr, 10); // base 10 109 | if( oper == "EQ" ){ 110 | if( port != intVal ) { return false; } 111 | } else if( oper == "LT" ){ 112 | if( port >= intVal ) { return false; } 113 | } else if( oper == "GT" ){ 114 | if( port <= intVal ) { return false; } 115 | } 116 | } 117 | } 118 | return true; 119 | } 120 | 121 | 122 | // filter by: host, port, key, age 123 | // example: AGE_LT_600&KEY&PORT_GT_1024 124 | bool HostInfo::passFilters(vector>& filters) { 125 | for(auto& filter: filters){ 126 | string& func = filter[0]; 127 | string& oper = filter[1]; 128 | string& value = filter[2]; 129 | uint32_t intVal = strtol(value.c_str(), nullptr, 10); // base 10 130 | 131 | if( func == "AGE" ){ 132 | uint32_t tmin = duration_cast(system_clock::now() - met).count(); 133 | if( oper == "LT" ){ 134 | if( tmin >= intVal ) { return false; } 135 | } else if( oper == "GT" ){ 136 | if( tmin <= intVal ) { return false; } 137 | } 138 | } else if( func == "IP" ){ 139 | if( oper == "LT" ){ 140 | if( host >= intVal ) { return false; } 141 | } else if( oper == "GT" ){ 142 | if( host <= intVal ) { return false; } 143 | } 144 | } else if( func == "PORT" ){ 145 | if( oper == "LT" ){ 146 | if( port >= intVal ) { return false; } 147 | } else if( oper == "GT" ){ 148 | if( port <= intVal ) { return false; } 149 | } 150 | } else if( func == "KEY" ){ // all HostInfo records with RKEY 151 | if( !key ) { return false; } 152 | } 153 | } // for(filters) 154 | return true; 155 | } 156 | 157 | 158 | void RemoteData::addContact(IPADDR ip, uint16_t port){ 159 | unique_lock ulock(mutx); 160 | 161 | for( auto range = hosts.equal_range(ip); range.first != range.second; ++range.first){ 162 | if( port == range.first->second.port ) { // existing host 163 | return; 164 | } 165 | } 166 | 167 | HostInfo hi; 168 | hi.host = ip; 169 | hi.port = port; 170 | hi.referIP = ip; // set referrer to itself since that service contacted us 171 | hi.referPort = port; // or it was added through command line 172 | hi.met = system_clock::now(); 173 | hosts.emplace( ip, move(hi) ); 174 | } 175 | 176 | 177 | // relevant "select" flags: LKEY, TIME, LSVC, LSVCF, COUNTS??? 178 | int LocalData::send(Sock& sock, uint32_t select, vector>& filters, Signature& signer){ 179 | bool sign = select & SELECTION::SIGN; 180 | 181 | int bytes = 0; 182 | if(select & SELECTION::LKEY){ // send local public key. It does not change. Do not lock. 183 | bytes += sock.write( &localPubKey, sizeof(localPubKey) ); 184 | if(sign){ signer.write(&localPubKey, sizeof(localPubKey) ); } 185 | } 186 | 187 | // I wanted time to be a binary field and I want to use htonl() on it so int32_t it is. 188 | // timestamp is important to avoid a replay attack 189 | if(select & SELECTION::TIME){ 190 | int32_t now = htonl( timeMinutes() ); // in MINUTES !!! 191 | bytes += sock.write( &now, sizeof(now)); 192 | if(sign){ signer.write(&now, sizeof(now)); } 193 | } 194 | 195 | if( !(select & SELECTION::LSVC) ){ return bytes; } // service list was not requested 196 | 197 | // filter service list (toSend). get the count. write the count. write list. 198 | vector toSend; 199 | shared_lock lock(mutx); 200 | 201 | for(Service& s: services){ 202 | if( s.passFilters(filters, false) ){ 203 | toSend.push_back(&s); 204 | } 205 | } 206 | 207 | uint16_t count = htons( (uint16_t) toSend.size() ); 208 | bytes += sock.write( &count, sizeof(count) ); 209 | if(sign){ signer.write(&count, sizeof(count)); } 210 | 211 | for(Service* s: toSend){ 212 | bytes += sock.writeString(s->fullDescription); 213 | if(sign){ signer.writeString(s->fullDescription);} 214 | } 215 | return bytes; 216 | } 217 | 218 | 219 | // relevant "select" flags: IP, PORT, AGE, RKEY, RSVC, RSVCF 220 | int RemoteData::send(Sock& sock, uint32_t select, vector>& filters, Signature& signer){ 221 | bool sign = select & SELECTION::SIGN; 222 | shared_lock lock(mutx); 223 | 224 | vector data; 225 | for(std::pair& hi: hosts){ 226 | if( hi.second.passFilters(filters) ){ 227 | data.push_back(&hi.second); 228 | } 229 | } 230 | 231 | int bytes = 0; // write record count first 232 | uint32_t count = htonl((uint32_t) data.size()); 233 | bytes+= sock.write( &count, sizeof(count)); 234 | if(sign){ signer.write(&count, sizeof(count)); } 235 | 236 | for(HostInfo* hi: data){ 237 | if( select & SELECTION::IP ){ 238 | bytes+= sock.write( &hi->host, sizeof(hi->host) ); // writing without htonl() 239 | if(sign){ signer.write(&hi->host, sizeof(hi->host)); } 240 | } 241 | if( select & SELECTION::PORT ){ 242 | uint16_t p = htons(hi->port); 243 | bytes+= sock.write( &p, sizeof(p) ); 244 | if(sign){ signer.write(&p, sizeof(p)); } 245 | } 246 | if( select & SELECTION::AGE ){ 247 | auto deltaT = system_clock::now() - hi->seen; 248 | auto count = deltaT.count()/60000000; // ms to minutes 249 | uint16_t age = (uint16_t) (count > 65500 ? 65500 : count); // reserve all above 65500 250 | age = htons(age); 251 | bytes+= sock.write( &age, sizeof(age) ); 252 | if(sign){ signer.write(&age, sizeof(age) ); } 253 | } 254 | if( select & SELECTION::RKEY ){ 255 | unsigned char keyCount = hi->key ? 1 : 0; 256 | bytes+= sock.write( &keyCount, sizeof(keyCount)); 257 | if(sign){ signer.write(&keyCount, sizeof(keyCount)); } 258 | 259 | if(hi->key){ 260 | bytes+= sock.write( &*hi->key, sizeof(PubKey) ); 261 | if(sign){ signer.write( &*hi->key, sizeof(PubKey)); } 262 | } 263 | } 264 | if( select & SELECTION::ISCHK ){ // is key checked (verified) ? 265 | unsigned char c = hi->signatureVerified; 266 | bytes+= sock.write(&c, sizeof(c) ); 267 | if(sign){ signer.write(&c, sizeof(c) ); } 268 | } // hi->signatureVerified is of interest to local services querying OutNet 269 | 270 | if( select & SELECTION::RSVC ){ 271 | bool includeAllServices = !(select & SELECTION::RSVCF); 272 | vector svc; 273 | for( Service& s: hi->services ){ 274 | if(includeAllServices || s.passFilters(filters, true)){ 275 | svc.push_back(&s); 276 | } 277 | } 278 | uint16_t count = htons((uint16_t)svc.size()); 279 | bytes+= sock.write( &count, sizeof(count)); 280 | if(sign){ signer.write(&count, sizeof(count)); } 281 | 282 | for( Service* s: svc){ 283 | bytes+= sock.writeString(s->fullDescription); 284 | signer.writeString(s->fullDescription); 285 | } 286 | } // if RSVC 287 | } // for(HostInfo 288 | return bytes; 289 | } 290 | 291 | 292 | // is this ip in our black list 293 | bool BlackList::isBanned(IPADDR host){ 294 | return binary_search(badHosts.begin(), badHosts.end(), host); 295 | } 296 | 297 | 298 | // is this public key in our black list 299 | bool BlackList::isBanned(PubKey& key){ 300 | return binary_search(badKeys.begin(), badKeys.end(), key); 301 | } 302 | -------------------------------------------------------------------------------- /data.h: -------------------------------------------------------------------------------- 1 | #ifndef DATA_H_INCLUDED 2 | #define DATA_H_INCLUDED 3 | #include "sign.h" // PubKey 4 | #include // shared_ptr 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include // since C++17 11 | #include 12 | using namespace std::chrono; 13 | 14 | class Sock; 15 | 16 | 17 | struct Service { 18 | std::string fullDescription; // should have external routable IP (this is what we send) 19 | std::string originalDescription; // original (this is what we get from files) 20 | std::string service; // general type of service 21 | std::string tcpudp; // tcp or udp - important for port forwarding 22 | std::string protocol; // service protocol such as http, ftp etc... 23 | uint32_t ip; // real IP (local/non-routable IP) from originalDescription 24 | uint16_t port; // port number for this service 25 | std::string other; // user defined filed can be path or description or id or resourse defintion 26 | 27 | Service* parse(const std::string& service, uint32_t myIP); 28 | bool passFilters(const std::vector>& filters, bool remote) const; 29 | bool operator==(const Service& rhs) const { return fullDescription == rhs.fullDescription; } 30 | bool operator==(const std::string& rhs) const { return originalDescription == rhs; } 31 | bool operator<(const std::string& rhs) const { return originalDescription < rhs; } 32 | bool operator<(const Service& rhs) const { return originalDescription < rhs.originalDescription; } 33 | bool less(const Service& rhs) const { return originalDescription < rhs.originalDescription; } 34 | }; 35 | void mergeServices(std::vector& dest, std::vector& source); // helper free function 36 | 37 | 38 | struct LocalData { 39 | std::shared_mutex mutx; // This datastructure is accessed by several threads. Lock mutex before access. 40 | uint32_t localIP; // local (non-routable) IP of this host 41 | uint32_t myIP; // public ip of the local service 42 | uint32_t myPort; // local service is running on this port 43 | PubKey localPubKey; // local service public key 44 | std::vector services; // a list of local services we are "advertising" 45 | int sleepServer = 2; // all of these various sleep and timeouts are time in seconds 46 | int sleepCrawler = 60; // trottle crawler connections to avoid network saturation 47 | int timeoutServer = 10; 48 | int timeoutCrawler = 10; 49 | int timeoutUPNP = 10; 50 | 51 | int send(Sock& sock, uint32_t select, std::vector>& filters, Signature& signer); 52 | Service* addService(const std::string& service); 53 | }; 54 | 55 | 56 | struct HostInfo { // Information about remote services from one host 57 | constexpr static const int DEFAULT_RATING = 100; 58 | uint32_t host; // IPv4 address 59 | uint16_t port; // IPv4 port number (1-65535, 0=reserved) 60 | std::shared_ptr key; // remote service's public key 61 | std::vector services; // remote services list 62 | // these are remote service properties related to interaction with that service 63 | bool signatureVerified = false; // was service queried directly or key found by a relay service? 64 | int offlineCount; // server has been found offline this many times IN A ROW 65 | int rating; // our interaction rating for this service 66 | time_point met; // first time I learned about this host 67 | time_point seen; // last time I successfully connected to it 68 | time_point missed; // last time of unsuccessful connect attempt 69 | uint32_t referIP; // preserve where this information came from 70 | uint16_t referPort; // port of the service where we got this HostInfo record 71 | 72 | HostInfo(); 73 | bool passFilters(std::vector>& filters); 74 | Service* addService(const std::string& service); 75 | }; 76 | 77 | 78 | #ifndef IPADDR 79 | typedef uint32_t IPADDR; // sockaddr_in::sin_addr.s_addr defines it as "unsigned long" 80 | #endif 81 | 82 | struct RemoteData { 83 | std::shared_mutex mutx; 84 | std::unordered_multimap hosts; // IP to HostInfo map 85 | 86 | // send HostInfo records through the writer to a remote host 87 | int send(Sock& sock, uint32_t select, std::vector>& filters, Signature& signer); 88 | // Remember this host/port for crawler to contact 89 | void addContact(IPADDR ip, uint16_t port); 90 | }; 91 | 92 | 93 | // A way to ban some IPs and Public keys 94 | struct BlackList { 95 | std::shared_mutex mutx; 96 | std::vector badHosts; 97 | std::vector badKeys; 98 | bool isBanned(IPADDR host); 99 | bool isBanned(PubKey& key); 100 | }; 101 | 102 | 103 | #endif // DATA_H_INCLUDED 104 | -------------------------------------------------------------------------------- /http.cpp: -------------------------------------------------------------------------------- 1 | #include "data.h" 2 | #include "http.h" 3 | #include "sock.h" 4 | #include "utils.h" 5 | #include "log.h" 6 | #include // put_time() 7 | #include // time(), localtime() 8 | #include 9 | #include 10 | #include 11 | #include 12 | using namespace std; 13 | #include 14 | using namespace std::chrono; 15 | 16 | 17 | // take a query string filter ex: AGE_LT_600 and parse it in 3 strings separated by "_" 18 | bool Request::parseFilter(char* filter, array& funcOperVal){ // reference to array of strings 19 | char* end = filter + strlen(filter); 20 | char* token = nullptr; 21 | if( !tokenize(filter, end, token, "_") ){ return false; } 22 | funcOperVal[0] = token; // function 23 | 24 | if( tokenize(filter, end, token, "_") ){ // operator or value might not be present ex: "KEY" 25 | funcOperVal[1] = token; // operator (EQ/LT/GT) 26 | 27 | if( tokenize(filter, end, token, "_") ){ 28 | funcOperVal[2] = token; // value 29 | return true; 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | 36 | // look for a line like: GET /?QUERY=2036&SPORT=33344&PORT_EQ_2132 HTTP/1.1 37 | int Request::parse(Sock& conn, vector>& filters, uint16_t& port){ 38 | char buff[2048]; 39 | int rd = conn.readLine(buff, sizeof(buff)); 40 | if(rd < 0){ return -1; } // error reading more data (connection closed/timed out) 41 | printAscii(log(), (const unsigned char*)buff, rd); 42 | if( strncmp(buff,"GET",3) ){ // not an http GET query 43 | return -1; 44 | } 45 | string line = buff; // save it for debugging 46 | 47 | int query = 0; 48 | char* start = buff + 3; // skip "GET" 49 | const char * end = start+strlen(start); 50 | char* token = nullptr; 51 | while( tokenize( start, end, token, " &?/") ){ 52 | if( strncmp(token, "QUERY=", 6) == 0 ){ // parse QUERY 53 | query = strtol(token+6, nullptr, 10); // base 10 54 | } else if( strncmp(token, "SPORT=", 6) == 0){ // parse remote server port 55 | port = (uint16_t) strtol(token+6, nullptr, 10); // base 10 56 | } else if( strcmp(token,"HTTP") && strcmp(token,"1.1") ){ // throw away these tokens 57 | array funcOperVal; // auto funcOperVal = filters.emplace_back(); 58 | if( parseFilter(token, funcOperVal) ){ 59 | filters.push_back( move(funcOperVal) ); 60 | } 61 | } 62 | } 63 | if( query <= 0 ){ 64 | log() << "ERROR parsing query: " << line << endl; 65 | } 66 | return query; 67 | } 68 | 69 | 70 | // look for "Register:" or "Unregister:" HTTP request fields 71 | bool Request::registerServices(Sock& conn, LocalData& ldata){ 72 | char buff[2048]; 73 | while(true){ 74 | int rd = conn.readLine(buff, sizeof(buff)); 75 | if(rd <= 0){ return false; } // error reading more data (connection closed/timed out) 76 | int len = strlen(buff); 77 | if(0==len){ return true; } // header was parsed till the end 78 | if(len < 25){ continue; } // too short to be a field of interest 79 | if( strncmp(buff,"Register:", 9)==0 ){ 80 | string serv = buff+9; // serv gets destroyed during parsing 81 | rtrim(ltrim(serv)); // TODO: move ltrim()/rtrim() into addService() 82 | if( ldata.addService(serv) ){ 83 | log() << "Registered new local service: " << buff+9 << endl; 84 | } 85 | } else if( strncmp(buff,"Unregister:", 11)==0 ){ 86 | // ldata.removeService(buff+11); //1 TODO: 87 | } 88 | } 89 | } 90 | 91 | 92 | int Response::write(Sock& conn, uint32_t select, vector>& filters, LocalData& ldata, RemoteData& rdata){ 93 | static const string header = "HTTP/1.1 200 OK\r\n\r\n"; // TODO: need full header to bypass firewalls/proxies? 94 | int bytes = conn.write(header.c_str(), header.size() ); // no need to sign the header 95 | 96 | bool sign = select & SELECTION::SIGN; 97 | if(sign){ signer.init(); } 98 | 99 | uint32_t nsel = htonl(select); 100 | bytes+= conn.write(&nsel, sizeof(nsel)); 101 | if(sign) { signer.write(&nsel, sizeof(nsel) ); } 102 | 103 | bytes+= ldata.send(conn, select, filters, signer); 104 | bytes+= rdata.send(conn, select, filters, signer); 105 | 106 | //1 TODO: write signer's buffer to Sock in one go instead of writing parts or Sock::setCork() 107 | if(sign){ 108 | PubSign psign; 109 | signer.generate(psign); 110 | bytes+= conn.write( &psign, sizeof(psign) ); 111 | } 112 | return bytes; 113 | } 114 | 115 | 116 | void Response::writeDebug(Sock& conn, uint32_t select, std::vector>& filters){ 117 | stringstream ss; 118 | ss << ""; 119 | 120 | time_t now = time(NULL); 121 | std::tm lnow; 122 | #ifdef _WIN32 123 | localtime_s(&lnow, &now); // fucking MSFT 124 | #else 125 | localtime_r(&now, &lnow); 126 | #endif 127 | ss << put_time(&lnow,"%c %Z") << "
"; 128 | 129 | ss << "OutNet INFO
"; 130 | ss << "QUERY=" << select << "
"; 131 | for(auto f: filters){ 132 | ss << f[0] << "_" << f[1] << "_" << f[2] << "
"; 133 | } 134 | ss << ""; 135 | 136 | stringstream ssh; // ss header 137 | ssh << "HTTP/1.1 200 OK\r\n"; // second "\r\n" separates headers from html 138 | ssh << "Content-Type: text/html\r\n"; 139 | ssh << "Content-Length: " << ss.str().length() << "\r\n"; 140 | ssh << "\r\n"; 141 | ssh << ss.str(); 142 | 143 | conn.write(ssh.str().c_str(), ssh.str().size() ); 144 | } 145 | 146 | 147 | void Response::writeDenied(Sock& conn, const string& reason){ 148 | string msg = string("HTTP/1.1 403 ").append(reason).append("\r\n\r\n"); 149 | conn.write(msg.c_str(), msg.length() ); 150 | } 151 | -------------------------------------------------------------------------------- /http.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTP_H_INCLUDED 2 | #define HTTP_H_INCLUDED 3 | 4 | #include "sign.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class Sock; 10 | struct LocalData; 11 | struct RemoteData; 12 | 13 | 14 | // These are query flags. Some are "fileds" which can be selected by a query. 15 | // Field names should be abbreviations up to 5 char long. This way filters can use the same strings. 16 | enum SELECTION { 17 | LKEY = (1<<0), // local public key 18 | TIME = (1<<1), // current local datetime (to be used with signatures to avoid replay attack) 19 | SIGN = (1<<2), // signature (sign the whole message) 20 | LSVC = (1<<3), // local service list 21 | 22 | IP = (1<<4), // remote service IP 23 | PORT = (1<<5), // remote service port 24 | AGE = (1<<6), // age - how long ago (minutes) was remote service successfuly contacted 25 | RKEY = (1<<7), // remote public key 26 | RSVC = (1<<8), // remote service list 27 | ISCHK = (1<<9), // return signatureVerified flag 28 | 29 | RSVCF = (1<<10), // FILTER remote service list by protocol or send all? 30 | }; // if records are filtered by service, we can still send all services for that record. 31 | 32 | 33 | class Request{ // parse() returns QUERY bit field 34 | static bool parseFilter(char* filter, std::array& funcOperVal); 35 | public: 36 | static int parse(Sock& conn, std::vector>& filters, uint16_t& port); 37 | static bool registerServices(Sock& conn, LocalData& ldata); 38 | }; 39 | 40 | 41 | class Response{ 42 | Signature signer; 43 | public: 44 | int write(Sock& conn, uint32_t select, std::vector>& filters, LocalData& ldata, RemoteData& rdata); 45 | static void writeDebug(Sock& conn, uint32_t select, std::vector>& filters); 46 | static void writeDenied(Sock& conn, const std::string& reason); 47 | }; 48 | 49 | 50 | #endif // HTTP_H_INCLUDED -------------------------------------------------------------------------------- /log.cpp: -------------------------------------------------------------------------------- 1 | #include "log.h" 2 | #include 3 | #include 4 | #include 5 | #include // std::hex, std::dec 6 | #include 7 | using namespace std::chrono; 8 | using namespace std; 9 | // TODO: make logging code thread safe 10 | 11 | static ofstream devNull; 12 | static const int LINES_PER_FILE = 1000000; 13 | static LOG_LEVEL logLevel = LOG_LEVEL::INFO; 14 | void setLogLevel(LOG_LEVEL level){ logLevel = level; } 15 | 16 | 17 | void logTime(ostream& log){ // THE HORROR! When are we getting operator<<(time_point&) ??? 18 | auto t = system_clock::now(); 19 | auto ms = duration_cast(t.time_since_epoch()) % 1000; 20 | time_t tt = system_clock::to_time_t(t); 21 | tm *tmp = localtime(&tt); 22 | 23 | log << tmp->tm_hour << setfill('0'); 24 | log << setw(2) << tmp->tm_min; 25 | log << setw(2) << tmp->tm_sec << '.'; 26 | log << setw(3) << ms.count() << setw(0); 27 | } 28 | 29 | 30 | bool createLogFile(ofstream& log){ 31 | time_t now = time(0); 32 | tm *tmp = localtime(&now); 33 | 34 | stringstream ss; 35 | ss << setfill('0') << 1900+tmp->tm_year << setw(2) << tmp->tm_mon << setw(2) << tmp->tm_mday; 36 | string date = ss.str(); 37 | ss << "_" << setw(2) << tmp->tm_hour << setw(2) << tmp->tm_min << setw(2) << tmp->tm_sec; 38 | 39 | string fname = "outnet"+ss.str()+".log"; 40 | log.close(); 41 | log.open(fname, ios_base::out | ios_base::app); // append 42 | log << "LOG START date: " << date << endl; 43 | 44 | return log.good(); 45 | } 46 | 47 | 48 | ostream& logToFile(const string& logType){ 49 | static ofstream log; 50 | static int lineCount = 0; 51 | if( --lineCount <=0 ){ 52 | lineCount = LINES_PER_FILE; 53 | createLogFile(log); 54 | } 55 | 56 | logTime(log); 57 | log << " " << logType; 58 | return log; 59 | } 60 | 61 | 62 | ostream& log() { 63 | if( logLevel < LOG_LEVEL::LOG ){ return devNull; } 64 | return logToFile(""); 65 | } 66 | 67 | 68 | ostream& logErr(){ 69 | if( logLevel < LOG_LEVEL::LERR ){ return devNull; } 70 | return logToFile("ERR "); 71 | } 72 | 73 | 74 | ostream& logInf() { 75 | if( logLevel < LOG_LEVEL::INFO ){ return devNull; } 76 | return logToFile("INF "); 77 | } 78 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_LOG_H 2 | #define INCLUDED_LOG_H 3 | #include 4 | 5 | 6 | enum LOG_LEVEL { NONE=0, LERR=1, LOG=2, INFO=3 }; 7 | void setLogLevel(LOG_LEVEL level); 8 | 9 | std::ostream& log(); 10 | std::ostream& logErr(); 11 | std::ostream& logInf(); 12 | 13 | 14 | #endif // INCLUDED_LOG_H -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "data.h" 2 | #include "config.h" 3 | #include "crawler.h" 4 | #include "sock.h" 5 | #include "http.h" 6 | #include "utils.h" 7 | #include "svc.h" 8 | #include "log.h" 9 | #include 10 | #include // #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | using namespace std; 17 | 18 | 19 | std::string selectStr(uint32_t sel){ 20 | std::stringstream ss; 21 | ss << sel << ": "; 22 | if(sel & SELECTION::LKEY){ ss << "LKEY "; } 23 | if(sel & SELECTION::TIME){ ss << "TIME "; } 24 | if(sel & SELECTION::SIGN){ ss << "SIGN "; } 25 | if(sel & SELECTION::LSVC){ ss << "LSVC "; } 26 | if(sel & SELECTION::IP ){ ss << "IP "; } 27 | if(sel & SELECTION::PORT){ ss << "PORT "; } 28 | if(sel & SELECTION::AGE ){ ss << "AGE "; } 29 | if(sel & SELECTION::RKEY){ ss << "RKEY "; } 30 | if(sel & SELECTION::RSVC){ ss << "RSVC "; } 31 | if(sel & SELECTION::RSVCF){ss << "RSVCF ";} 32 | return ss.str(); 33 | } 34 | 35 | 36 | bool addFirstPeer(RemoteData& rdata){ 37 | const string filename = "outnetStart.url"; 38 | ifstream urlf (filename); 39 | if( !urlf ){ 40 | log() << filename << " NOT found." << endl; 41 | return false; 42 | } 43 | log() << "Loading " << filename << endl; 44 | 45 | vector lines; 46 | parseLines(urlf, lines); // line format is IP:PORT 47 | int added = 0; 48 | 49 | for(string& line: lines){ 50 | auto col = line.find(":"); 51 | if(string::npos == col) { continue; } 52 | 53 | string ips = line.substr(0,col); 54 | string ports = line.substr(col+1); 55 | uint32_t ip = Sock::stringToIP(ips.c_str()); 56 | uint16_t portInt = (uint16_t) strtol(ports.c_str(), nullptr, 10); // base 10 57 | 58 | if( 0!=ip && 0!=portInt ){ 59 | log() << "Adding " << Sock::ipToString(ip) << ":" << portInt << " to list of IPs to scan." << endl; 60 | rdata.addContact(ip, portInt); 61 | ++added; 62 | } 63 | } 64 | 65 | return added > 0; 66 | } 67 | 68 | 69 | void run(); 70 | // Three threads: main one serves requests, second (crawler) collects info, 71 | // third thread subscribes services & loads black lists. 72 | int main(int argc, char* argv[]){ 73 | if( !initNetwork() ){ // WSAStartup() on windows or set ignore SIGPIPE on unix 74 | logErr() << "Error initializing network." << endl; 75 | return 4; 76 | } 77 | return registerService(&run); 78 | } // main 79 | 80 | 81 | void run(){ 82 | log() << "OutNet service version 0.1 (" << __DATE__ << ")" << endl; 83 | // LocalData, RemoteData and BlackLists are shared among threads. They all have internal mutexes. 84 | LocalData ldata; // info about local services and local public key 85 | RemoteData rdata; // information gathered about remote services 86 | BlackList blist; // Black and White lists 87 | addFirstPeer(rdata); // load remote outnet service location to initialize from 88 | Config config; // config is aware of service port, LocalData and BWLists 89 | config.init(ldata, blist); // load ldata,bwlists 90 | 91 | if( !Signature::loadKeys(ldata.localPubKey) ){ // load public key from disk into ldata 92 | logErr() << "ERROR loading keys. Exiting." << endl; 93 | return; 94 | } 95 | auto& os = log(); 96 | os << "My public key: "; 97 | printHex(os, ldata.localPubKey.key, sizeof(ldata.localPubKey) ); 98 | os << endl; 99 | 100 | // create the server returning all queries 101 | // first time it starts running on a random port (ANY_PORT) 102 | // but if service ran before, reuse the port number. It was saved by Config class. 103 | Sock server; 104 | if( server.listen(ldata.myPort) < 0 ){ 105 | logErr() << "ERROR listening for connections on port " << ldata.myPort << ". Exiting." << endl; 106 | return; 107 | } 108 | 109 | uint16_t port = server.getPort(); // get bound server port number from socket 110 | if( ldata.myPort != port ){ // Assuming there was no config file since ports are different. 111 | ldata.myPort = port; // no need to lock since only one thread is running so far 112 | config.saveToDisk(); // this will overwrite the whole config file! 113 | } 114 | log() << "Running OutNet service on port " << port << endl; 115 | 116 | if( !Sock::isRoutable(ldata.localIP) ){ // could have a non-private (routable) IP 117 | if( config.forwardLocalPort(port) ){ 118 | log() << "Opened port " << port << " on the router (enabled port forwarding)." << endl; 119 | } else { 120 | logErr() << "If you have a NAT router, forward port " << port << " to local host manually!" << endl; 121 | } 122 | } 123 | 124 | Crawler crawler(ldata, rdata, blist); 125 | crawler.loadRemoteDataFromDisk(); // load rdata 126 | log() << "============================================================" << endl; // starting threads 127 | 128 | // create the information collector thread here (there could be many in the future) 129 | // it searches and fills rdata while honoring BlackLists 130 | std::thread search( &Crawler::run, &crawler); 131 | 132 | // create a thread that watches files for service and BWList updates 133 | std::thread watch( &Config::watch, &config); 134 | 135 | // unordered_map::operator[] crashes. Switching to map for now. 136 | // unordered_map connTime; // keep track of when someone connects to us 137 | map connTime; // keep track of when someone connects to us 138 | Response response; 139 | while( keepRunning() ){ 140 | Sock conn; 141 | if( INVALID_SOCKET == server.accept(conn, 2) ){ continue; } // timeout = 2 seconds 142 | conn.setRWtimeout(ldata.timeoutServer); // seconds read/write timeout 143 | 144 | uint32_t ip = conn.getIP(); 145 | if( blist.isBanned(ip) ){ 146 | log() << "Denying REQUEST from " << Sock::ipToString(ip) << " (BANNED)" << endl; 147 | Response::writeDenied(conn, "BANNED"); 148 | continue; 149 | } 150 | 151 | // prevent abuse if host connects too often but allow local IPs repeated queries 152 | bool routable = Sock::isRoutable(ip); 153 | auto now = system_clock::now(); 154 | auto& time = connTime[ip]; 155 | if( time > now - minutes(10) && routable ){ 156 | log() << "Denying REQUEST from " << Sock::ipToString(ip) << " (connects too often)" << endl; 157 | Response::writeDenied(conn, "DENIED"); 158 | continue; 159 | } 160 | time = now; 161 | 162 | // parse remote server port, "SELECT field bitmap" and filters from remote's "HTTP get" query 163 | vector> filters; // function + parameter pairs 164 | uint16_t port = 0; 165 | int select = Request::parse(conn, filters, port); 166 | 167 | if(!routable){ // local services can ask to register themselves in HTTP request when querying 168 | Request::registerServices(conn, ldata); 169 | } 170 | 171 | if(port > 0){ // remote sent us its server port. Add accepted connection to RemoteData 172 | rdata.addContact(conn.getIP(), port ); 173 | } 174 | 175 | if(select > 0){ // request parsed correctly and we have a "data selection bitmap" 176 | log() << "REQUEST from " << Sock::ipToString(ip) << " (" << selectStr(select) << ")" << endl; 177 | response.write(conn, select, filters, ldata, rdata); 178 | } else { 179 | log() << "Debug REQUEST from " << Sock::ipToString(ip) << endl; 180 | Response::writeDebug(conn, select, filters); 181 | } 182 | 183 | // windblows freaks out in recv() if you close connection right away 184 | // TODO: keep a list of active sockets for a few seconds. for now, sleep 185 | for(int i=0; i < ldata.sleepServer && keepRunning(); ++i){ 186 | this_thread::sleep_for(seconds(1)); 187 | } 188 | } // while() 189 | 190 | watch.join(); // wait for other threads to exit first 191 | search.join(); 192 | auto& outs = log() << "Main thread exiting." << endl; 193 | outs.flush(); 194 | } // run() 195 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Need C++20 for erase_if(unordered_multimap&, predicate). Otherwise C++17 would do. 2 | # -Werror -ansi -pedantic -Wall -Wextra -Wno-unused-parameter 3 | CC = g++ # notice CFLAGS contains -g which will compile everything in debug mode! 4 | CFLAGS = -g --std=c++20 -Wall -Wextra -Wno-unused-parameter -Iupnp -Isign -pthread 5 | DEPS = config.h crawler.h data.h http.h sign.h sock.h utils.h upnp/upnpnat.h upnp/xmlParser.h sign/tweetnacl.h 6 | OBJ = main.o sock.o data.o crawler.o config.o http.o utils.o sign.o log.o upnp/upnpnat.o upnp/xmlParser.o sign/tweetnacl.o svclin.o svcwin.o 7 | 8 | # Linux generates a warning when using -static 9 | # Using 'gethostbyname' in statically linked applications requires at runtime the shared libraries from the glibc version used for linking 10 | ifdef OS # windows defines this environment variable 11 | LDFLAGS = -lwsock32 -static 12 | endif 13 | 14 | %.o: %.cpp $(DEPS) 15 | $(CC) $(CFLAGS) -c -o $@ $< 16 | 17 | outnet: $(OBJ) 18 | $(CC) -o $@ $^ -lpthread $(LDFLAGS) 19 | 20 | clean: 21 | /bin/rm -f $(OBJ) 22 | -------------------------------------------------------------------------------- /onlist/client.cpp: -------------------------------------------------------------------------------- 1 | #include "client.h" 2 | #include "sock.h" 3 | #include // strstr() 4 | #include 5 | #include 6 | using namespace std; 7 | #include 8 | using namespace std::chrono; 9 | 10 | 11 | static bool ERR(const string& msg){ // makes error handling code a bit shorter 12 | cerr << "ERROR " << msg << endl; 13 | return false; 14 | } 15 | 16 | 17 | int queryOutNet(uint32_t select, HostInfo& outnet, vector& peers, uint16_t myPort, int rwTimeout, vector* filters ){ 18 | stringstream ss; 19 | ss << "GET /?QUERY=" << select; 20 | if(myPort>0){ // ad own server port for remote to connect back to 21 | ss << "&SPORT=" << myPort; 22 | } 23 | if(filters){ // did the caller include any query parameters? 24 | for(string& f: *filters){ ss << "&" << f; } 25 | } 26 | ss << " HTTP/1.1\r\n\r\n"; 27 | 28 | Sock sock; 29 | sock.setRWtimeout(rwTimeout); // seconds read/write timeout 30 | sock.setNoDelay(true); // request is one write() 31 | 32 | if( sock.connect(outnet.host, outnet.port) ){ 33 | cerr << "ERROR connecting to " << Sock::ipToString(outnet.host) << ":" << outnet.port << endl; 34 | return 0; 35 | } 36 | 37 | int len = (int) ss.str().length(); 38 | if(len != sock.write(ss.str().c_str(), len ) ){ 39 | return ERR("sending HTTP request"); 40 | } 41 | 42 | // parse the response into unverifiedData 43 | // read HTTP/1.1 200 OK\r\n\r\n 44 | char buff[256]; 45 | int rdsize = sock.readLine(buff, sizeof(buff) ); 46 | if( rdsize < 8 || nullptr == strstr(buff,"200") ) { 47 | cerr << "ERROR in queryRemoteService() while parsing " << rdsize << " bytes: " << buff << endl; 48 | return 0; 49 | } 50 | while( sock.readLine(buff, sizeof(buff) ) > 0 ) {} // skip till empty line is read (HTTP protocol) 51 | 52 | /************ Everything read below this line is signed *************/ 53 | Signature signer; 54 | const bool sign = select & SELECT::SIGN; // is signature requested? 55 | if(sign){ signer.init(); } 56 | 57 | uint32_t selectRet; // SELECT is always received 58 | rdsize = sock.read(&selectRet, sizeof(selectRet) ); 59 | if(rdsize != sizeof(selectRet)){ 60 | return ERR("reading 'select'"); 61 | } 62 | if(sign){ signer.write(&selectRet, sizeof(selectRet)); } // signer gets data in network byte order 63 | selectRet = ntohl(selectRet); 64 | 65 | if( sign && !(selectRet & SELECT::SIGN) ){ // we requested a signature but it was not returned 66 | return ERR("remote refused to sign response"); // this is a security problem 67 | } 68 | 69 | if(selectRet & SELECT::LKEY){ 70 | outnet.key = make_shared(); 71 | rdsize = sock.read(&*outnet.key, sizeof(PubKey)); 72 | if( rdsize != sizeof(PubKey) ){ 73 | return ERR("reading remote public key"); 74 | } 75 | if(sign){ signer.write(&*outnet.key, sizeof(PubKey)); } 76 | } 77 | 78 | if(selectRet & SELECT::TIME){ 79 | int32_t timeRemote; 80 | rdsize = sock.read(&timeRemote, sizeof(timeRemote) ); 81 | if(rdsize != sizeof(timeRemote) ){ 82 | return ERR("reading remote's time."); 83 | } 84 | if(sign){ signer.write(&timeRemote, sizeof(timeRemote)); } 85 | timeRemote = ntohl(timeRemote); 86 | 87 | // check that timestamp is not too long in the past, otherwise it can be a replay attack 88 | static auto epoch = system_clock::from_time_t(0); 89 | int32_t timeMinutes = duration_cast(system_clock::now() - epoch).count(); 90 | int32_t minOld = timeMinutes - timeRemote; // does not depend on a timezone 91 | if( abs(minOld) > 5 ){ 92 | return ERR("Remote time difference is " + to_string(minOld) + " minutes. Discarding data."); 93 | } 94 | } 95 | 96 | if( selectRet & SELECT::LSVC ){ 97 | uint16_t count; 98 | rdsize = sock.read(&count, sizeof(count)); 99 | if(rdsize != sizeof(count) ){ 100 | return ERR("reading remote service count."); 101 | } 102 | if(sign){ signer.write(&count, sizeof(count)); } 103 | count = ntohs(count); 104 | 105 | for(int i=0; i < count; ++i){ 106 | rdsize = sock.readString(buff, sizeof(buff)); 107 | if(rdsize <=0){ 108 | return ERR("reading remote serices."); 109 | } 110 | outnet.services.push_back(buff); 111 | if(sign){ signer.writeString(buff); } 112 | } 113 | } 114 | 115 | /******************** remote data *************************/ 116 | uint32_t count; 117 | rdsize = sock.read(&count, sizeof(count) ); // count is always there even if data is not 118 | if(rdsize != sizeof(count) ){ 119 | return ERR("reading HostInfo count."); 120 | } 121 | if(sign){ signer.write(&count, sizeof(count)); } 122 | count = ntohl(count); 123 | 124 | for(uint32_t i=0; i< count; ++i){ 125 | HostInfo& hil = peers.emplace_back(); 126 | 127 | if( selectRet & SELECT::IP ){ // IP does not need ntohl() 128 | rdsize = sock.read( &hil.host, sizeof(hil.host)); 129 | if(rdsize != sizeof(hil.host)){ 130 | return ERR("reading IP."); 131 | } 132 | if(sign){ signer.write(&hil.host, sizeof(hil.host) ); } 133 | } 134 | 135 | if( selectRet & SELECT::PORT ){ 136 | rdsize = sock.read(&hil.port, sizeof(hil.port) ); 137 | if(rdsize != sizeof(hil.port) ){ 138 | return ERR("reading port."); 139 | } 140 | if(sign){ signer.write(&hil.port, sizeof(hil.port)); } 141 | hil.port = ntohs(hil.port); 142 | } 143 | 144 | if( selectRet & SELECT::AGE ){ 145 | uint16_t age; 146 | rdsize = sock.read(&age, sizeof(age) ); 147 | if(rdsize != sizeof(age) ){ 148 | return ERR("reading age."); 149 | } 150 | if(sign){ signer.write(&age, sizeof(age)); } 151 | hil.age = ntohs(age); 152 | } 153 | 154 | if( selectRet & SELECT::RKEY ){ 155 | unsigned char keyCount = 0; 156 | rdsize = sock.read( &keyCount, sizeof(keyCount) ); 157 | if( rdsize != sizeof(keyCount) ){ 158 | return ERR("reading key count."); 159 | } 160 | if(sign){ signer.write(&keyCount, sizeof(keyCount)); } 161 | if(keyCount){ // for now only one key is allowed 162 | hil.key = make_shared (); 163 | rdsize = sock.read( &*hil.key, sizeof(PubKey) ); 164 | if(rdsize != sizeof(PubKey) ){ 165 | return ERR("reading public key."); 166 | } 167 | if(sign){ signer.write(&*hil.key, sizeof(PubKey)); } 168 | } 169 | } 170 | 171 | if(selectRet & SELECT::ISCHK){ 172 | unsigned char chk; 173 | rdsize = sock.read(&chk, sizeof(chk) ); 174 | if( rdsize != sizeof(chk) || chk>1 ){ // chk should be 0 or 1 175 | return ERR("reading signatureVerified."); 176 | } 177 | hil.signatureVerified = chk; 178 | } 179 | 180 | if( selectRet & SELECT::RSVC ){ 181 | uint16_t cnt; 182 | rdsize = sock.read(&cnt, sizeof(cnt) ); 183 | if(rdsize != sizeof(cnt) ){ 184 | return ERR("reading remote service count."); 185 | } 186 | if(sign){ signer.write(&cnt, sizeof(cnt)); } 187 | cnt = ntohs(cnt); 188 | 189 | for(int i=0; i< cnt; ++i){ 190 | rdsize = sock.readString(buff, sizeof(buff)); 191 | if(rdsize <=0){ 192 | return ERR("reading remote services."); 193 | } 194 | if(sign){ signer.writeString(buff); } 195 | hil.services.push_back(buff); 196 | } 197 | } 198 | } // for (adding HostInfo) 199 | 200 | if(sign){ // If signature can not be verified, discard the data 201 | PubSign signature; 202 | if( sizeof(signature) != sock.read( &signature, sizeof(signature) ) ) { 203 | return ERR("reading signature from remote host"); 204 | } 205 | 206 | if( !signer.verify(signature, *outnet.key) ){ 207 | return ERR("verifying signature"); 208 | } 209 | } 210 | 211 | return true; 212 | } 213 | -------------------------------------------------------------------------------- /onlist/client.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_CLIENT_H 2 | #define INCLUDED_CLIENT_H 3 | #include "sign.h" // PubKey 4 | #include 5 | #include 6 | #include 7 | 8 | struct HostInfo{ 9 | uint32_t host; // IPv4 address 10 | uint16_t port; // IPv4 port number (1-65535, 0=reserved) 11 | uint16_t age; // age of the record 12 | std::shared_ptr key; // remote service's public key 13 | bool signatureVerified = false; // was service queried directly or key found by a relay service? 14 | std::vector services; // remote services list 15 | }; 16 | 17 | // These are query flags. Some are "fileds" which can be selected by a query. 18 | // Field names are abbreviations up to 5 char long. Filters use the same strings. Ex: AGE_LT_60 19 | enum SELECT { 20 | LKEY = (1<<0), // local public key 21 | TIME = (1<<1), // current local datetime (to be used with signatures to avoid replay attack) 22 | SIGN = (1<<2), // signature (sign the whole message) 23 | LSVC = (1<<3), // local service list 24 | 25 | IP = (1<<4), // remote service IP 26 | PORT = (1<<5), // remote service port 27 | AGE = (1<<6), // age - how long ago (minutes) was remote service successfuly contacted 28 | RKEY = (1<<7), // remote public key 29 | RSVC = (1<<8), // remote service list 30 | ISCHK = (1<<9), // return signatureVerified flag 31 | 32 | RSVCF = (1<<10), // FILTER remote service list by protocol or send all? 33 | }; // if records are filtered by service, we can still send all services for that record. 34 | 35 | 36 | // local services add themselves by connecting to outnet with SPORT= set to their server port. 37 | // when outnet connects to it, reply without signing the response. 38 | // when OutNet detects local (non-routable) address, it adds services as its own local services. 39 | 40 | // select contains what you want OutNet to return in your query 41 | // outnet has to have host, port and optionally key filled in before the call 42 | // upon return outnet.services contains local services, local key and signatureVerified flag. 43 | // upon return peers contains a list of peers for your service to connect to 44 | // myPort is used for registering your local service with OutNet service 45 | // rwTimeout is a Read/Write time out in seconds for send() and recv() network operations 46 | // filters is a list of filters you want OutNet to apply before returning the results 47 | int queryOutNet(uint32_t select, HostInfo& outnet, std::vector& peers, uint16_t myPort=0, int rwTimeout=10, std::vector* filters = {} ); 48 | 49 | 50 | #endif // INCLUDED_CLIENT_H 51 | -------------------------------------------------------------------------------- /onlist/makefile: -------------------------------------------------------------------------------- 1 | # Need C++20 for erase_if(unordered_multimap&, predicate). Otherwise C++17 would do. 2 | # -Werror -ansi -pedantic -Wall -Wextra -Wno-unused-parameter 3 | CC = g++ # notice CFLAGS contains -g which will compile everything in debug mode! 4 | CFLAGS = -g --std=c++20 -Wall -Wextra -Wno-unused-parameter -I.. -I../sign 5 | DEPS = client.h ../sign.h ../sock.h ../sign/tweetnacl.h 6 | OBJ = onlist.o client.o ../sock.o ../sign.o ../log.o ../sign/tweetnacl.o 7 | 8 | ifdef OS # windows defines this environment variable 9 | LDFLAGS = -lwsock32 -static 10 | endif 11 | 12 | %.o: %.cpp $(DEPS) 13 | $(CC) $(CFLAGS) -c -o $@ $< 14 | 15 | onlist: $(OBJ) 16 | $(CC) -o $@ $^ $(LDFLAGS) 17 | 18 | clean: 19 | /bin/rm -f $(OBJ) onlist onlist.exe 20 | -------------------------------------------------------------------------------- /onlist/onlist.cpp: -------------------------------------------------------------------------------- 1 | // This is a stand alone utility to query OutNet service. To compile on linux/windows type "make". 2 | #include "client.h" 3 | #include "sock.h" 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | 10 | std::string toString(const PubKey& key) { 11 | stringstream ss; 12 | for(unsigned int i = 0; i< sizeof(key.key); ++i){ 13 | ss << std::setw(2) << std::setfill('0') << std::hex << (int) key.key[i]; 14 | } 15 | return ss.str(); 16 | } 17 | 18 | 19 | void printInfo(HostInfo& hi) { 20 | string key = hi.key ? toString(*hi.key) : ""; 21 | cout << Sock::ipToString(hi.host) << ":" << hi.port << "\t" << key << endl; 22 | for(string& s: hi.services){ 23 | cout << "\t" << s << endl; 24 | } 25 | } 26 | 27 | 28 | int main(int argc, char* argv[]) { 29 | if( argc != 3 ){ 30 | cerr << "OutNet service list utility. Usage: " << argv[0] << " OutNetIP OutNetPort" << endl; 31 | return -1; 32 | } 33 | initNetwork(); 34 | 35 | HostInfo service; // queryOutNet() connects to this OutNet service 36 | service.host = Sock::stringToIP(argv[1]); 37 | service.port = (uint16_t) strtol(argv[2], nullptr, 10); // base 10 38 | 39 | // we want local services, remote IP:PORT and services + all public keys 40 | uint32_t select = SELECT::LSVC | SELECT::LKEY | SELECT::IP | SELECT::PORT | SELECT::RSVC | SELECT::RKEY; 41 | 42 | vector newData; // results will be returned here 43 | vector filters; // = {"AGE_LT_600"}; // = {"RPROT=ftp"}; // for FTP servers 44 | queryOutNet(select, service, newData, 0, 10, &filters); 45 | 46 | printInfo(service); 47 | for(HostInfo& hi: newData){ 48 | printInfo(hi); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /outnet.svc: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=OutNet systemd service (github.com/rand3289/OutNet) 3 | After=network.target 4 | 5 | [Service] 6 | User=rootik 7 | WorkingDirectory=/home/rootik/projects/outnet/ 8 | ExecStart=/home/rootik/projects/outnet/outnet 9 | Type=forking 10 | Restart=always 11 | RestartSec=60 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /outnetStart.url: -------------------------------------------------------------------------------- 1 | 74.88.37.164:4444 2 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## OutNet - you are the internet! 2 | 3 | Reading further requires understanding of the following concepts: 4 | https://en.wikipedia.org/wiki/Network_service 5 | https://en.wikipedia.org/wiki/Server 6 | https://en.wikipedia.org/wiki/Search_engine 7 | https://en.wikipedia.org/wiki/App_store 8 | https://en.wikipedia.org/wiki/Social_network 9 | https://en.wikipedia.org/wiki/Peer-to-peer 10 | https://en.wikipedia.org/wiki/Distributed_computing 11 | https://en.wikipedia.org/wiki/Domain_name 12 | https://en.wikipedia.org/wiki/BitTorrent_tracker 13 | https://en.wikipedia.org/wiki/Dynamic_DNS 14 | 15 | As computers get faster the need for large service providers should decrease. More can be done locally on your own computer. Home users due to their sheer numbers have more computing power than any company. Yet corporations maintain control of your data by building centralized services. Use of centralized services can be avoided by distributing the equivalent of the server parts of these services among users. Everyone can run a tiny search engine, a phone app store or a social network at home. This model requires linking many small services in a distributed (P2P) network. 16 | 17 | Participating in OutNet or projects like OutNet is the only way for you to take control over your information, your content and monetization of your content. It is the only way of preventing large companies from owning your data. With OutNet, you get to host your videos, your posts, your pictures and no one can tell you what to do, shut you down or take it away from you. OutNet does not hide any illegal activities. It only stops other from gaining control over you. 18 | 19 | Currently domain names allow you to find services on the internet. Using domain names is not suitable for distributing services. When domain names are used, resourses must be maintained in a few central locations. An example is a BitTorrent tracker. Another problem is that most users on the internet do not have a registered domain name. You have to pay for registering most domains. You can not point your domain name to your home IP address since it may change except with dynamic DNS. Domain names can be shut down by third parties. With OutNet, instead of being regulated, detrimental aspects of the internet can be voted off by majority and get lost within the noise. 20 | 21 | 22 | Reading further requires understanding of the following concepts: 23 | https://en.wikipedia.org/wiki/Kazaa 24 | https://en.wikipedia.org/wiki/Digital_signature 25 | https://en.wikipedia.org/wiki/Public-key_cryptography 26 | https://en.wikipedia.org/wiki/Communication_protocol 27 | https://en.wikipedia.org/wiki/Open_source 28 | https://en.wikipedia.org/wiki/Domain_Name_System 29 | https://en.wikipedia.org/wiki/Blockchain 30 | https://en.wikipedia.org/wiki/Distributed_hash_table 31 | https://en.wikipedia.org/wiki/IPv4 32 | https://en.wikipedia.org/wiki/GNUnet 33 | 34 | OutNet is an alternative to "private/regulated/controlled" discovery protocols like Kazaa or domain names. OutNet is a free and open source distributed service directory network protocol (peer discovery). It is designed to find conventional or distributed (P2P) services on the internet. Services such as web pages, game servers, ftp servers, messengers, forums, video conferences, P2P and distributed services. Another goals of OutNet is to decentralize the internet making it resistant to control and sensorship. It does not matter if you run your services on a Raspberry Pi at home or in the cloud. You make the rules! 35 | 36 | OutNet provides anonymity. Instead of a domain or a user name, a public key is used to identify you, your services and services provided by others. Public key is generated locally. Your real name is no longer needed to provide or consume services on the internet. Your IP address however will be visible to the world unless used in conjunction with a VPN. OutNet is similar to the DNS system which allows finding services by name, however instead it allows finding services on the internet by a network protocol name, a service type or a public key. OutNet is different from a blockchain and much simpler. Peers can have a partial view of the information. There is similarity with GNUnet since OutNet provides peer discovery and authentication. However unlike GNUnet OutNet does not provide any encryption or privacy. The upside is there are NO dependencies on external components. 37 | 38 | 39 | ## Project status 40 | OutNet is written in C++ 20. Project does NOT have ANY external dependencies. Everything is built-in. All source code is available from ONE git repository. There is no need to install any libraries. OutNet was started in February 2020 by a single developer. Its most recent version is 0.1 41 | It compiles using 42 | * g++ version 10.2 under x86_64 linux 43 | * MinGW-w64 from msys2.org on x86_64 windows 44 | * Apple clang version 13.1.6 on arm64 (M1) macOS 45 | 46 | To compile type "make" in OutNet directory. To compile "onlist" utility, type "make" in OutNet/onlist directory. "onlist" utility queries the OutNet service. 47 | 48 | OutNet's home: https://github.com/rand3289/OutNet 49 | Other OutNet based services: 50 | https://github.com/rand3289/OutNetMsg 51 | https://github.com/rand3289/OutNetTray 52 | 53 | 54 | ## Implementation 55 | OutNet is implemented by a service with the same name that runs on your machine. It gathers and provides a list of IPv4 addresses, corresponding port numbers and ages of nodes participating in the OutNet. In addition, OutNet lists the types of remote services and local services you run such as your web sites, game servers and P2P services. 56 | 57 | When OutNet starts, it tries to contact some of the known remote OutNet severs. It collects their information such as public keys and lists of services they advertise. Local services can query OutNet to find a list of peers. Querying OutNet returns a response that contains it's public key, a list of local services OutNet is advertising, a list of remote OutNet services it knows and services they advertise. Response is signed by the private key. 58 | 59 | 60 | Reading further requires understanding of the following concepts: 61 | https://en.wikipedia.org/wiki/Private_network 62 | https://en.wikipedia.org/wiki/Network_address_translation 63 | https://en.wikipedia.org/wiki/Router_(computing) 64 | https://en.wikipedia.org/wiki/Port_(computer_networking) 65 | https://en.wikipedia.org/wiki/Open_port 66 | https://en.wikipedia.org/wiki/Universal_Plug_and_Play 67 | https://en.wikipedia.org/wiki/Connection-oriented_communication 68 | https://en.wikipedia.org/wiki/GNU_Compiler_Collection 69 | https://en.wikipedia.org/wiki/Mingw-w64 70 | https://en.wikipedia.org/wiki/Repository_(version_control) 71 | 72 | 73 | Since one does not want to expose ALL available local services on the internet, OutNet does not discover local services. Local services can register with OutNet or be added via configuration files. Service descriptions visible to the world have to contain routable (public/external) IP addresses instead of host names. If OutNet determines your service is behind a NAT router and IP is a non-routable IP, it will replace your non-routable IP with it's own external routable IP when listing your service. In addition, OutNet will open a port in the router via UPnP protocol that will allow your service to accept connections. Upon startup OutNet looks for outnetStart.url file that contains remote OutNet addresses (one ip:port per line) to start looking for peers. 74 | 75 | 76 | Reading further requires understanding of the following concepts: 77 | https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol 78 | https://en.wikipedia.org/wiki/RSA_(cryptosystem) 79 | https://en.wikipedia.org/wiki/OpenBSD 80 | https://en.wikipedia.org/wiki/Representational_state_transfer 81 | https://en.wikipedia.org/wiki/Firewall 82 | https://en.wikipedia.org/wiki/Transmission_Control_Protocol 83 | https://en.wikipedia.org/wiki/URL 84 | https://en.wikipedia.org/wiki/Multicast 85 | https://en.wikipedia.org/wiki/Unicast 86 | https://en.wikipedia.org/wiki/Information_retrieval 87 | https://en.wikipedia.org/wiki/Wide_area_network 88 | 89 | 90 | * OutNet runs as a REST service over HTTP to bypass some firewalls and network restrictions. It can run on different port numbers that can change over time. Your other services do not have to run over HTTP or TCP. The advantages of using http interfaces and presenting user interfaces as web applications however is that they ALL can be linked into one eco system. Any of your services built on top of OutNet know about other services by querying OutNet service. Any service can send messages to OutNetMsg or post notifications to OutNetTray. Browser based OutNetMsg will provide links to all registered OutNet services that serve /favicon.ico via HTTP GET request. 91 | 92 | * Your public local services can find their peers by querying a local OutNet instance. 93 | * OutNet is capable of "opening a port in your router" via UPnP in order to be accessible from outside of your network (WAN side). 94 | * It can "open" additional ports for your distributed services to accept connections. 95 | * OutNet can sign responses with a private key and supply a public key for signature verification. 96 | * OutNet can deny connections to external/routable IPs if frequency of requests coming from them is high. 97 | * OutNet has built in blacklist support for filtering selected IPs and keys. 98 | * Mechanisms/protocols used to implement OutNet are HTTP 1.1, UPnP and digital signatures. 99 | * OutNet provides libraries to help you query the OutNet service and register your service with OutNet. 100 | * To support your other services OutNet provides a library for signature creation and verification. Your private key does not have to be shared with your other services. 101 | 102 | 103 | ## OutNet installation instructions 104 | The first step in any installation is creating or editing outnetStart.url text file. 105 | It contains an initial url (IP:PORT) for OutNet to connect to. For example 127.0.0.1:12345 106 | This is where OutNet will connect to find peers. The file can be deleted later. 107 | You should trust the initial source of the information to avoid problems. 108 | 109 | When outnet first runs, it creates the following files: 110 | outnet.cfg outnet*.log outnetPublic.key outnetSecret.key and possibly outnetPeers.save 111 | User can further create *.service files with local service descriptions you want to advertise. 112 | User can also create *.badip and *.badkey files for blacklisted IPs and Public Keys respectively. 113 | 114 | 115 | ### Installation instructions for OutNet daemon on Linux with systemd 116 | 1. create outnetStart.url file as described above in the common instructions 117 | 2. edit outnet.svc to set your User, WorkingDirectory and ExecStart 118 | User should match "whoami" 119 | WorkingDirectory should be set to the location of the executable file "outnet" 120 | ExecStart should be set to the full path for the executable "outnet" 121 | 3. run "sudo mv outnet.svc /etc/systemd/system/outnet.service" in the install dir 122 | 4. run "sudo systemctl daemon-reload" 123 | 5. run "sudo systemctl enable outnet.service" 124 | 6. run "sudo systemctl start outnet.service" 125 | At this point OutNet will start running and create some files. 126 | 7. Change to OutNet directory and check outnet*.log file for errors. 127 | 128 | 129 | ### Installation instructions for OutNet service on MS Windows 130 | 1. create outnetStart.url file as described above in the common instructions. 131 | 2. Right click on a command prompt icon and select "Run as Administrator". Enter admin password. 132 | 3. Run the following command: "sc create OutNet binPath= c:\pathToOutnet\outnet.exe start= auto" 133 | Make sure to replace pathToOutNet with a full path to outnet.exe 134 | At this point you should see "[SC] CreateService SUCCESS" 135 | 4. run "sc start OutNet" 136 | Make sure it shows the STATE is "RUNNING" 137 | At this point OutNet will start running and create some files. 138 | 5. Change to OutNet directory and check outnet*.log file for errors. 139 | 140 | If anything goes wrong, run "sc delete OutNet" and repeat the steps above. 141 | If outnet fails to start, log file can end up in c:\windows\system32\outnet*.log 142 | 143 | 144 | Reading further requires understanding of the following concepts: 145 | https://en.wikipedia.org/wiki/Graphical_user_interface 146 | https://en.wikipedia.org/wiki/Library_(computing) 147 | https://en.wikipedia.org/wiki/Client_(computing) 148 | https://en.wikipedia.org/wiki/Whitelisting 149 | https://en.wikipedia.org/wiki/Media_type 150 | https://en.wikipedia.org/wiki/TCP_Wrappers 151 | https://en.wikipedia.org/wiki/Usenet 152 | https://en.wikipedia.org/wiki/Hyperlink 153 | https://en.wikipedia.org/wiki/Messaging_spam 154 | https://en.wikipedia.org/wiki/Network_News_Transfer_Protocol 155 | https://en.wikipedia.org/wiki/Web_of_trust 156 | https://en.wikipedia.org/wiki/Search_engine_indexing 157 | https://en.wikipedia.org/wiki/Authentication 158 | https://en.wikipedia.org/wiki/Hash_function 159 | https://en.wikipedia.org/wiki/Cryptographic_hash_function 160 | https://en.wikipedia.org/wiki/Cryptocurrency 161 | https://en.wikipedia.org/wiki/E-commerce 162 | 163 | 164 | ## Future Services based on OutNet 165 | 166 | * OutNet should be treated as a piece of an eco system. It is the backbone on top of which all other services are built. "onlist" utility that comes with OutNet is the simplest application based on OutNet. It is a text mode query tool to list registered local and remote services (peers). For example a query tool can get a list of servers for your favorite game or a list of zoom personal meeting rooms ready to chat. For example, here we se an OutNet service's IP:PORT followed by public key and OutNetMsg service on the next line that starts with a tab: 167 | 192.168.0.123:2233 44235ce17e199806c14fd6a5f0dbf65f4df47774e028e9937fd7ebd0a65ac421 168 | http:tcp:outnetmsg:67.17.38.99:2345:/index.html 169 | 170 | 171 | * While onlist provides manual public key to IP or IP to a public key translation, it should be easy to write an OutNet based DNS proxy for resolving outnet addresses to IP addresses. This way any URL, for example http://8fa9fa8dad8fa0dfadf89a75fa4323lk/blah can be resolved to an IP address and used in the browser or another program. One can avoid using DNS and write a SOCKS proxy which will translate OutNet addresses. 172 | 173 | 174 | * There is a need for OutNet notification service. E-mail is not shared by the OutNet service to prevent network-wide spam. OutNetMsg receives messages addressed to your public key. If it is from some one you trust (their public key is on your white list), it tries to open the message using the protocol/mime specified in the message. OutNetMsg can display the message, offer to open/save the message or file, forward it to one of the services running on your system (for example, by acting as a TCP wrapper) or suggest you install a corresponding protocol handler / service. For example it can open a Zoom conference. 175 | It has to be able to manage other's public keys to be able to put them on your contact list. It should be possible to create "groups" of public keys. It should be able to share public keys and public key lists with others and notify of updates to the list. It should be able to send a message or a file to the list. There should be a mechanism to request that your public key is added to someone's friend list or group list. Messages from public keys not on your list will be discarded. Only direct (non-public) messages will be handled by OutNetMsg. Public messages or publicly shared files should be handled by other services. OutNet service provides public key to IP:PORT mapping for OutNetMsg to find the recepients. See https://github.com/rand3289/OutNetMsg for more info. 176 | 177 | 178 | * While OutNetMsg takes care of direct communication, there is a need for distributed message board service similar to twitter, reddit, facebook or newsgroups. Public messages and files exchanged by this service OutNetExchange (alternatively OutNetX or OutNetShare) are not addressed to an individual but reach users subscribed on a particular subject or key. Subject (thread) can have a hierarchical structure for example: local.USA.SanFrancisco.pizza similar to a usenet group (section 3.2 in rfc977). software.x86.linux.games.myBlaster can be used to distribute software. Alternatively a public key could become a subject allowing subscription to an individual source. A file hash becomes a URL where documents are hyperlinked by using their hash. A hybrid model can be implemented where large files are distributed using BitTorrent and small files or file metadata/hashes propagate similar to usenet articles. OutNetExchange can duplicate some usenet features while avoiding these problems: 179 | + Spam (anyone could post / no signatures / no ratings). 180 | + Need for a usenet server (not everyone has it). 181 | + Use of a SINGLE server (all articles have to be in one place). 182 | + Maximum article size varied among servers. 183 | + NNTP is a text based protocol. 184 | 185 | 186 | * OutNetExchange shares all types of files, however a specific video sharing service might be better in certain cases. If you are a content provider who has a video channel, you do not need to ask a third party if you channel can be monetized. Your content type will not influence your ability to monetize. You decide what type of advertisements your channel will display. 187 | 188 | 189 | * Another significant aspect is a rating system. Ratings is the basis of trust in a distrubuted system. Two things things that need to be rated are data (text/images/video) and public keys. 190 | 191 | Each document, image or media file can be addressed by it's hash. All one needs to increase its rating is to make that piece of data local. By making it local you store it on your disk, allow others to find it by its hash and download it. Content rating count (number of seeds) should be inversly proportional to it's size: larger files will always have less seeds however they should be highly prized. On the other side small files will require more seeds to increase it's rating since the "price" of storing them on the hard drive is small. 192 | 193 | You should be able to rate your interactions with owners of a public key. Intention of this rating is different than the "Web of trust". In OutNet the key comes first and the name is secondary. The name is not important unless verified through personal communication. The rating does not state if you know the entity or entity's name in real life. It rates a specific type of transaction/interaction you had. For example an instance of a running OutNet service can be rated. An internet purchase of an item description signed by a key can be rated. A software/release signed by a key can be rated. Securyty (virus/trojan free) of the content can be ensured by the rating service. Software or content releases have to be signed by a private key of the author. Authors's public keys in turn will be rated by users. The way you trust in Microsoft, Google or Apple's content distribution system, individual authors have to earn your thrust in their public keys. Rating should always contain a subject as described in OutNetExchange since an owner of a key can provide multiple services. For example sell physical items or services and at the same time distribute free software. His web store should not be rated highly just because he makes great freeware video games. 194 | 195 | Information on how governing bodies try to regulate electronic IDs can be found here: https://en.wikipedia.org/wiki/EIDAS 196 | 197 | 198 | * OutNetSearch service is used to index information (keys, subjects, content, file hashes) distributed by local services and EXCHANGE it with other search services or local distributed services. You control what gets indexed. 199 | 200 | * OutNet can promote development of indie ONLINE games. Developers do not have to host a central location for players to share their IPs (rooms/servers/etc...) OutNet client can be built into a game, register with OutNet service and find others running games. 201 | 202 | * OutNet is a network which should be able to self-regulate. There is a need for a security service. Cybersecurity organizations you trust can limit detrimental activity such as botnets via releasing blacklists, ratings or software that interacts with OutNet. If you trust a blacklist signed by a certain public key, you can include it into your collection and limit propagation of certain protocols, host information or public keys. Blacklist mechanisms are built into OutNet. However downloading and updating black lists should be done by other programs/services for flexibility. For example you can trust a list provided by some organization on their web site that limits content equivalent to PG-13. This will stop propagation of certain information through your node. Other OutNet nodes will not be affected. If you find the list blocks your favorite content, you can add it to a white lists to override it. Adding an item to a black list or a white list can be shared as a rating for other users. 203 | 204 | 205 | * Authentication service is needed to enable seamless authentication on any conventional (server based) internet resource using your public/private key pair. Similar to authentication via amazon/google/yahoo/facebook. However instead of contacting them, it will contact you. 206 | 207 | 208 | * A system to associate OutNet public keys and cryptocurrency wallets. Could be done through OutNetMsg. 209 | 210 | 211 | * A free distributed e-commerce platform can be built based on this infrastructure. 212 | 213 | 214 | Reading further requires understanding of the following concepts: 215 | https://en.wikipedia.org/wiki/Service_Location_Protocol 216 | https://en.wikipedia.org/wiki/WS-Discovery 217 | https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol 218 | https://en.wikipedia.org/wiki/Network_Information_Service 219 | https://en.wikipedia.org/wiki/Multicast_DNS 220 | https://en.wikipedia.org/wiki/Bonjour_(software) 221 | 222 | ## Describing services 223 | OutNet can be based on existing standards. For example, list of services can be based on modified service description format of DNS-SD (section 4.1.2 of rfc6763), SLP (section 4.1 of rfc2608), WS-Discovery, SSDP, NIS, mDNS, Bonjour. Service description has to contain at least the following fields: type of service (ex: printer), internet protocol (tcp/udp), actual protocol (ex:ipp), IP, port number, user defined name/description/path/attribute. 224 | 225 | Other possible fields: version, piority[0-9], type of data (service/device/block/stream/file/messages/image/audio/video/conference/game/ xml/json/yaml/soap/rpc/wsdl/feed(structured data)/list/key-value/url/ binary/text/unicode/ascii/base64/uuencoded/ printer/speaker/display/ blockchain/cryptocurrency/geolocation/weather/virtualreality/time). 226 | 227 | Proposed maximum field lengths: priority(char[1]), service class(char[16]), protocol(char[16]), ipproto(char[3]), IP(char[15]), port(char[5]), description or path(char[32]). Maximum service description length in the range of 96 - 128 bytes. (DNS-SD limits service names to 15 characters. Key-value pairs are described in rfc6763 section 6.) 228 | 229 | Service description encoding can be limited to printable ASCII characters. User defined name/description/path can be UTF8. 230 | 231 | Examples: "printer:tcp:lpr:8.8.8.8:54321:2nd floor printer" 232 | Same device, different protocol: "printer:tcp:ipp:8.8.8.8:12345:2nd floor printer" 233 | 234 | This is an example of how OutNetMsg service registers with Outnet: 235 | http:tcp:outnetmsg:127.0.0.1:2345:/index.html 236 | 237 | 238 | ## OutNet service query parameters 239 | OutNet can be queried to return local info and/or a filtered list of discovered remote OutNet services. For example a query can limit the results by service type, availability of "remote public keys" or what fields are included in response. 240 | 241 | Returned fields can be any of the following: 242 | * local public key of the service being queried (LKEY) 243 | * signature (signing the whole message) (SIGN) 244 | * current local datetime (TIME) 245 | * local service list (LSVC) 246 | The following are fields in the list of known remote OutNet services: 247 | * remote OutNet service IP (IP) 248 | * remote OutNet service PORT (PORT) 249 | * remote record age - when was this remote OutNet service last seen (AGE) 250 | * remote public key (RKEY) 251 | * remote service list (RSVC) 252 | 253 | 254 | Where as fields are controlled by including them in the SELECT query parameter, returned records are limited by FILTER parameters: 255 | * local service type/protocol (exact string match) 256 | * IP (range or exact match) 257 | * port (range or exact match) 258 | * age (range) 259 | * remote service type/protocol (exact string match) 260 | * remote service list count (RSVCC) (greater than #) 261 | * remote public key (exact string match) 262 | * remote public key count (RKEYC) (greater than #) One key per service is allowed but OutNet can potentially receive different keys from other OutNet services for a single IP. 263 | 264 | 265 | Notes 266 | * For numeric operators greater/less/equal allowed operands are an immediate plus one of AGE, RKEYC, IP, PORT, RSVCC 267 | * For operator "string equal" allowed operands are a constant string plust one of RKEY, RSVC, LSVC 268 | * REST call (HTTP GET) Example: SELECT=2036&FILTER=RANGE_GT_500,RANGE_LT_900,RSVC_EQ_HTTP,RKEYC_GT_0 269 | * If any of the received bit fields or query parameters are not defined, they are ignored 270 | 271 | * Design OutNet without protocol identifiers to be less detectable and less likely to be blocked. 272 | * Reserve age values over 65500 ex: 0xFFFE = "IO error", 0xFFFD = "duplicate signature", 0xFFFC="coming soon", 0xFFFB="broken signature", 0xFFFA="unresponsive", 0xFFF9="wrong protocol", 0xFFF8="untrusted", 0xFFF0="offline", etc... 273 | 274 | 275 | Reading further requires understanding of the following concepts: 276 | https://en.wikipedia.org/wiki/SSH_(Secure_Shell) 277 | https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm 278 | https://en.wikipedia.org/wiki/Netsplit 279 | https://en.wikipedia.org/wiki/Botnet 280 | 281 | ## Generating public/private keys 282 | Public and private keys will be generated automatically if you do not have files outnetPublic.key and outnetSecret.key in the OutNet service directory. If one of the keys is missing, the keys will also be regenerated. Never share your private key! Make backup of you private and public keys. You will NOT be able to recover the private key if it is lost. 283 | 284 | ## Securing against network splits 285 | Small world network properties can be used to find relatively small semi-isolated connectivity islands that might be trying to split the network and exclude them. Any fresh client connecting to such an island can become isolated from the rest of the OutNet. It is important to trust your first connection source. OutNet services returning HostInfo records without any overlap with existing records can receive a negative rating. After verification of returned records the raiting should raise above 0. 286 | 287 | ## Concerns 288 | * OutNet needs to verify HostInfo records received from other OutNet instances by contacting the host in the record. An OutNet service raiting should be incremented for each verified record and decremented for each failed verification. Records received from OutNet services with negative rating should not be shared with other OutNet services. 289 | * Age of each record can be taken into account when rating 290 | * Younger records should be validated first if record age is taken into account during rating. 291 | * New records from highly rated services should be verified first. 292 | 293 | ## Securing agains Botnets 294 | Health and stability of the internet should be every user's goal. A botnet client can masquerade as a regular OutNet client. To prevent any botnets from using this service, it is the responsibility of each user to add IP filters as problems come up. OutNet has built in blacklist support for IPs and keys. 295 | 296 | ## TODO 297 | * Implement/fix all TODO in source code 298 | * Add an ability to share IPv6 addresses 299 | * Provide a signature for the IP+date+timestamp fields. 300 | * Add IPv4 SSDP capability so that other services can find OutNet. This way services do not need to be configured with OutNet IP:port 301 | * move buffer.h log.* sock.* utils.* svc*.* to ./lib/ ??? 302 | * move sign/* and upnp/* to deps/* 303 | 304 | 305 | This repository was cloned from https://github.com/rand3289/OutNet 306 | Created in February, 2021 307 | Last edited July 21, 2022 308 | -------------------------------------------------------------------------------- /sign.cpp: -------------------------------------------------------------------------------- 1 | #include "sign.h" 2 | #include "tweetnacl.h" 3 | #include "log.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include // call_once() 10 | #include 11 | #include 12 | using namespace std; 13 | 14 | static const string pubKeyFile = "outnetPublic.key"; 15 | static const string privateKeyFile = "outnetSecret.key"; 16 | bool Signature::keysLoaded = false; // static 17 | PrivateKey Signature::privateKey; // static 18 | PubKey Signature::publicKey; // static 19 | 20 | 21 | // Load both keys at once. If one file is missing, regenerate BOTH and save. 22 | bool Signature::loadKeys(PubKey& pubKey){ // static 23 | if(keysLoaded){ // TODO: return const reference instead??? 24 | memcpy(pubKey.key, publicKey.key, sizeof(publicKey)); 25 | return true; 26 | } 27 | 28 | ifstream pubkey(pubKeyFile, std::ios::binary); 29 | ifstream seckey(privateKeyFile, std::ios::binary); 30 | 31 | if( pubkey.good() && seckey.good() ){ // both files are there 32 | pubkey.read( (char*) publicKey.key, sizeof(publicKey) ); 33 | if( pubkey.good() ){ // read public key successfuly 34 | memcpy(pubKey.key, publicKey.key, sizeof(publicKey)); // return it to caller 35 | seckey.read( (char*) privateKey.key, sizeof(privateKey) ); 36 | if(seckey.good() ){ 37 | keysLoaded = true; 38 | return true; 39 | } 40 | } 41 | rename(privateKeyFile.c_str(), (privateKeyFile+".old").c_str() ); 42 | rename(pubKeyFile.c_str(), (pubKeyFile+".old").c_str() ); 43 | log() << "WARNING: one of the key files could not be read." << endl; 44 | log() << "Both files had to be renamed to *.old and will be regenerated." << endl; 45 | } else if( (!pubkey ^ !seckey) ){ // one of them exists instead of both. Rename it. 46 | const string& exists = !pubkey ? privateKeyFile : pubKeyFile; 47 | rename(privateKeyFile.c_str(), (privateKeyFile+".old").c_str() ); 48 | log() << "WARNING: missing ONE private or public key file!" << endl; 49 | log() << "Renamed existing file "<< exists << " to " << exists << ".old" << endl; 50 | } else { 51 | log() << "Public and private key files are missing and will be generated." << endl; 52 | } 53 | pubkey.close(); 54 | seckey.close(); 55 | 56 | crypto_sign_keypair(publicKey.key, privateKey.key); // Create new public and private keys 57 | log() << "Generated NEW private and public keys." << endl; 58 | memcpy(pubKey.key, publicKey.key, sizeof(publicKey)); 59 | 60 | ofstream pubk(pubKeyFile, std::ios::binary); 61 | ofstream seck(privateKeyFile, std::ios::binary); 62 | pubk.write( (char*) publicKey.key, sizeof(publicKey) ); 63 | seck.write( (char*) privateKey.key, sizeof(privateKey) ); 64 | if(!pubk || !seck){ // check that the writes succeeded 65 | logErr() << "ERROR saving keys. Do you have write permissions? Fix it and run OutNet again." << endl; 66 | return false; 67 | } 68 | 69 | keysLoaded = true; 70 | return true; 71 | } 72 | 73 | 74 | int Signature::init(){ // prepare to sign data written via write() 75 | buff.reset(); 76 | buff.reserve( SIGNATURE_SIZE ); // reserve space for signature 77 | return 0; 78 | } 79 | 80 | 81 | bool Signature::generate(PubSign& pubSign){ 82 | unsigned char* start = (unsigned char*) buff.get() + SIGNATURE_SIZE; 83 | unsigned long long len = buff.size();// buff had SIGNATURE_SIZE bytes reserved in init() 84 | vector m2(len); 85 | crypto_sign(&m2[0], &len, start, len-SIGNATURE_SIZE, privateKey.key); 86 | memcpy(pubSign.sign, &m2[0], SIGNATURE_SIZE); // first crypto_sign_BYTES is the signature 87 | // log() << "Signing " << buff.size() - SIGNATURE_SIZE << " bytes:" << endl; 88 | // printHex (start, buff.size() - SIGNATURE_SIZE); 89 | // log() << "Generated " << len << " bytes signed message:" << endl; 90 | // printHex( &m2[0], len); 91 | // printAscii( &m2[0], len); 92 | return true; 93 | } 94 | 95 | 96 | bool Signature::verify(const PubSign& sign, const PubKey& pubKey) { 97 | unsigned char* start = (unsigned char*) buff.get(); // pointer to the beginning of the buffer 98 | memcpy(start, sign.sign, sizeof(sign)); // prepend signature to the beginning of the buffer 99 | unsigned long long len = buff.size(); // len will be read twice & written to 100 | vector m2(len); // do not worry about extra SIGNATURE_SIZE bytes 101 | // log() << "Received "<< len << " bytes signed message:" << endl; 102 | // printHex( (unsigned char*) buff.get(), len); 103 | // printAscii( (unsigned char*) buff.get(), len); 104 | // log() << "Remote's public key: "; 105 | // printHex(pubKey.key, sizeof(pubKey)); 106 | return !crypto_sign_open(&m2[0], &len, start, len, (unsigned char*) pubKey.key); // returns 0 on success 107 | } 108 | 109 | 110 | int Signature::write(const void* data, size_t size){ // compute "PubSign sign" from data chunks 111 | buff.write(data, (uint32_t) size); 112 | return 0; 113 | } 114 | 115 | 116 | int Signature::writeString(const string& str){ 117 | unsigned char size = (unsigned char) str.length(); 118 | write(&size, sizeof(size)); 119 | write(str.c_str(), size); 120 | return size+1; 121 | } 122 | 123 | 124 | // this function is called once by tweetnacl when creating a key pair (byteCount is 32) 125 | void randombytes(unsigned char* bytes, unsigned long long byteCount){ 126 | // log() << "(" << byteCount << ") "; 127 | static shared_ptr device; 128 | static std::once_flag onceFlag; 129 | call_once(onceFlag, [](){ // try intilializing a true random device during first call 130 | try { 131 | device = make_shared(); // This could throw. 132 | device->operator()(); // generate one random number. This could throw. 133 | if(0 == device->entropy() ){ // pseudo random 134 | device.reset(); // we don't want it 135 | log() << "INFO: random device is not available on the system" << endl; 136 | } 137 | } catch(...){ 138 | device.reset(); // it can throw in operator() not just constructor 139 | log() << "INFO: random device is not available on the system." << endl; 140 | } 141 | }); 142 | 143 | if(device){ 144 | for(unsigned long long i=0; i < byteCount; ++i){ 145 | bytes[i] = device->operator()() % 256; // TODO: waisting 3/4 of entrophy here !!! 146 | } 147 | } else { 148 | auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); 149 | std::default_random_engine generator(seed); 150 | std::uniform_int_distribution distribution(0,255); 151 | for(unsigned long long i=0; i < byteCount; ++i){ 152 | bytes[i] = distribution(generator); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /sign.h: -------------------------------------------------------------------------------- 1 | #ifndef SIGN_H_INCLUDED 2 | #define SIGN_H_INCLUDED 3 | #include // size_t, memset(), memcpy(), memcmp() 4 | #include 5 | #include "tweetnacl.h" 6 | #include "buffer.h" 7 | 8 | #define SIGNATURE_SIZE (crypto_sign_BYTES) 9 | #define PUBKEY_SIZE (crypto_sign_PUBLICKEYBYTES) 10 | #define SECRETKEY_SIZE (crypto_sign_SECRETKEYBYTES) 11 | 12 | 13 | struct PubSign { unsigned char sign[SIGNATURE_SIZE]; }; 14 | struct PrivateKey { unsigned char key[SECRETKEY_SIZE]; }; 15 | 16 | struct PubKey { 17 | unsigned char key[PUBKEY_SIZE]; 18 | bool operator==(const PubKey& rhs) const { return 0==memcmp( &key, &rhs.key, sizeof(key) ); } 19 | bool operator< (const PubKey& rhs) const { return 0 >memcmp( &key, &rhs.key, sizeof(key) ); } 20 | // PubKey& operator=(const PubKey& rhs){ memcpy(key, rhs.key, sizeof(key)); return *this; } 21 | // PubKey(const PubKey& rhs){ memcpy(key, rhs.key, sizeof(key)); } 22 | }; 23 | 24 | 25 | class Signature { 26 | Buffer32 buff; 27 | static bool keysLoaded; 28 | static PrivateKey privateKey; // static field loaded by loadKeys() 29 | static PubKey publicKey; // cached publicKey - access it with loadKeys() 30 | public: 31 | static bool loadKeys(PubKey& publicKey); // load both keys, but return public key only. 32 | int init(); 33 | int write(const void* data, size_t size); 34 | int writeString(const std::string& str); 35 | bool generate(PubSign& pubSign); 36 | bool verify(const PubSign& signature, const PubKey& pubKey); 37 | }; 38 | 39 | 40 | #endif //SIGN_H_INCLUDED -------------------------------------------------------------------------------- /sign/tweetnacl.c: -------------------------------------------------------------------------------- 1 | // tweetnacl.* is part of public domain TweetNaCl library available here: https://tweetnacl.cr.yp.to 2 | // This file was modified in a few places to make it compile under g++ 10.2 without warnings 3 | #ifdef __GNUG__ // disable g++ warnings about signed/unsigned comparison 4 | #pragma GCC diagnostic ignored "-Wsign-compare" 5 | #endif 6 | 7 | #include "tweetnacl.h" 8 | #define FOR(i,n) for (i = 0;i < n;++i) 9 | #define sv static void 10 | 11 | typedef unsigned char u8; 12 | typedef unsigned long u32; 13 | typedef unsigned long long u64; 14 | typedef long long i64; 15 | typedef i64 gf[16]; 16 | extern void randombytes(u8 *,u64); 17 | 18 | static const u8 19 | _0[16] = {0}, 20 | _9[32] = {9}; 21 | static const gf 22 | gf0 = {0}, 23 | gf1 = {1}, 24 | _121665 = {0xDB41,1}, 25 | D = {0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203}, 26 | D2 = {0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406}, 27 | X = {0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169}, 28 | Y = {0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666}, 29 | I = {0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83}; 30 | 31 | static u32 L32(u32 x,int c) { return (x << c) | ((x&0xffffffff) >> (32 - c)); } 32 | 33 | static u32 ld32(const u8 *x) 34 | { 35 | u32 u = x[3]; 36 | u = (u<<8)|x[2]; 37 | u = (u<<8)|x[1]; 38 | return (u<<8)|x[0]; 39 | } 40 | 41 | static u64 dl64(const u8 *x) 42 | { 43 | u64 i,u=0; 44 | FOR(i,8) u=(u<<8)|x[i]; 45 | return u; 46 | } 47 | 48 | sv st32(u8 *x,u32 u) 49 | { 50 | int i; 51 | FOR(i,4) { x[i] = u; u >>= 8; } 52 | } 53 | 54 | sv ts64(u8 *x,u64 u) 55 | { 56 | int i; 57 | for (i = 7;i >= 0;--i) { x[i] = u; u >>= 8; } 58 | } 59 | 60 | static int vn(const u8 *x,const u8 *y,int n) 61 | { 62 | u32 i,d = 0; 63 | FOR(i,n) d |= x[i]^y[i]; 64 | return (1 & ((d - 1) >> 8)) - 1; 65 | } 66 | 67 | int crypto_verify_16(const u8 *x,const u8 *y) 68 | { 69 | return vn(x,y,16); 70 | } 71 | 72 | int crypto_verify_32(const u8 *x,const u8 *y) 73 | { 74 | return vn(x,y,32); 75 | } 76 | 77 | sv core(u8 *out,const u8 *in,const u8 *k,const u8 *c,int h) 78 | { 79 | u32 w[16],x[16],y[16],t[4]; 80 | int i,j,m; 81 | 82 | FOR(i,4) { 83 | x[5*i] = ld32(c+4*i); 84 | x[1+i] = ld32(k+4*i); 85 | x[6+i] = ld32(in+4*i); 86 | x[11+i] = ld32(k+16+4*i); 87 | } 88 | 89 | FOR(i,16) y[i] = x[i]; 90 | 91 | FOR(i,20) { 92 | FOR(j,4) { 93 | FOR(m,4) t[m] = x[(5*j+4*m)%16]; 94 | t[1] ^= L32(t[0]+t[3], 7); 95 | t[2] ^= L32(t[1]+t[0], 9); 96 | t[3] ^= L32(t[2]+t[1],13); 97 | t[0] ^= L32(t[3]+t[2],18); 98 | FOR(m,4) w[4*j+(j+m)%4] = t[m]; 99 | } 100 | FOR(m,16) x[m] = w[m]; 101 | } 102 | 103 | if (h) { 104 | FOR(i,16) x[i] += y[i]; 105 | FOR(i,4) { 106 | x[5*i] -= ld32(c+4*i); 107 | x[6+i] -= ld32(in+4*i); 108 | } 109 | FOR(i,4) { 110 | st32(out+4*i,x[5*i]); 111 | st32(out+16+4*i,x[6+i]); 112 | } 113 | } else 114 | FOR(i,16) st32(out + 4 * i,x[i] + y[i]); 115 | } 116 | 117 | int crypto_core_salsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c) 118 | { 119 | core(out,in,k,c,0); 120 | return 0; 121 | } 122 | 123 | int crypto_core_hsalsa20(u8 *out,const u8 *in,const u8 *k,const u8 *c) 124 | { 125 | core(out,in,k,c,1); 126 | return 0; 127 | } 128 | 129 | static const u8 sigma[17] = "expand 32-byte k"; 130 | 131 | int crypto_stream_salsa20_xor(u8 *c,const u8 *m,u64 b,const u8 *n,const u8 *k) 132 | { 133 | u8 z[16],x[64]; 134 | u32 u,i; 135 | if (!b) return 0; 136 | FOR(i,16) z[i] = 0; 137 | FOR(i,8) z[i] = n[i]; 138 | while (b >= 64) { 139 | crypto_core_salsa20(x,z,k,sigma); 140 | FOR(i,64) c[i] = (m?m[i]:0) ^ x[i]; 141 | u = 1; 142 | for (i = 8;i < 16;++i) { 143 | u += (u32) z[i]; 144 | z[i] = u; 145 | u >>= 8; 146 | } 147 | b -= 64; 148 | c += 64; 149 | if (m) m += 64; 150 | } 151 | if (b) { 152 | crypto_core_salsa20(x,z,k,sigma); 153 | FOR(i,b) c[i] = (m?m[i]:0) ^ x[i]; 154 | } 155 | return 0; 156 | } 157 | 158 | int crypto_stream_salsa20(u8 *c,u64 d,const u8 *n,const u8 *k) 159 | { 160 | return crypto_stream_salsa20_xor(c,0,d,n,k); 161 | } 162 | 163 | int crypto_stream(u8 *c,u64 d,const u8 *n,const u8 *k) 164 | { 165 | u8 s[32]; 166 | crypto_core_hsalsa20(s,n,k,sigma); 167 | return crypto_stream_salsa20(c,d,n+16,s); 168 | } 169 | 170 | int crypto_stream_xor(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) 171 | { 172 | u8 s[32]; 173 | crypto_core_hsalsa20(s,n,k,sigma); 174 | return crypto_stream_salsa20_xor(c,m,d,n+16,s); 175 | } 176 | 177 | sv add1305(u32 *h,const u32 *c) 178 | { 179 | u32 j,u = 0; 180 | FOR(j,17) { 181 | u += h[j] + c[j]; 182 | h[j] = u & 255; 183 | u >>= 8; 184 | } 185 | } 186 | 187 | static const u32 minusp[17] = { 188 | 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 252 189 | } ; 190 | 191 | int crypto_onetimeauth(u8 *out,const u8 *m,u64 n,const u8 *k) 192 | { 193 | u32 s,i,j,u,x[17],r[17],h[17],c[17],g[17]; 194 | 195 | FOR(j,17) r[j]=h[j]=0; 196 | FOR(j,16) r[j]=k[j]; 197 | r[3]&=15; 198 | r[4]&=252; 199 | r[7]&=15; 200 | r[8]&=252; 201 | r[11]&=15; 202 | r[12]&=252; 203 | r[15]&=15; 204 | 205 | while (n > 0) { 206 | FOR(j,17) c[j] = 0; 207 | for (j = 0;(j < 16) && (j < n);++j) c[j] = m[j]; 208 | c[j] = 1; 209 | m += j; n -= j; 210 | add1305(h,c); 211 | FOR(i,17) { 212 | x[i] = 0; 213 | FOR(j,17) x[i] += h[j] * ((j <= i) ? r[i - j] : 320 * r[i + 17 - j]); 214 | } 215 | FOR(i,17) h[i] = x[i]; 216 | u = 0; 217 | FOR(j,16) { 218 | u += h[j]; 219 | h[j] = u & 255; 220 | u >>= 8; 221 | } 222 | u += h[16]; h[16] = u & 3; 223 | u = 5 * (u >> 2); 224 | FOR(j,16) { 225 | u += h[j]; 226 | h[j] = u & 255; 227 | u >>= 8; 228 | } 229 | u += h[16]; h[16] = u; 230 | } 231 | 232 | FOR(j,17) g[j] = h[j]; 233 | add1305(h,minusp); 234 | s = -(h[16] >> 7); 235 | FOR(j,17) h[j] ^= s & (g[j] ^ h[j]); 236 | 237 | FOR(j,16) c[j] = k[j + 16]; 238 | c[16] = 0; 239 | add1305(h,c); 240 | FOR(j,16) out[j] = h[j]; 241 | return 0; 242 | } 243 | 244 | int crypto_onetimeauth_verify(const u8 *h,const u8 *m,u64 n,const u8 *k) 245 | { 246 | u8 x[16]; 247 | crypto_onetimeauth(x,m,n,k); 248 | return crypto_verify_16(h,x); 249 | } 250 | 251 | int crypto_secretbox(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) 252 | { 253 | int i; 254 | if (d < 32) return -1; 255 | crypto_stream_xor(c,m,d,n,k); 256 | crypto_onetimeauth(c + 16,c + 32,d - 32,c); 257 | FOR(i,16) c[i] = 0; 258 | return 0; 259 | } 260 | 261 | int crypto_secretbox_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k) 262 | { 263 | int i; 264 | u8 x[32]; 265 | if (d < 32) return -1; 266 | crypto_stream(x,32,n,k); 267 | if (crypto_onetimeauth_verify(c + 16,c + 32,d - 32,x) != 0) return -1; 268 | crypto_stream_xor(m,c,d,n,k); 269 | FOR(i,32) m[i] = 0; 270 | return 0; 271 | } 272 | 273 | sv set25519(gf r, const gf a) 274 | { 275 | int i; 276 | FOR(i,16) r[i]=a[i]; 277 | } 278 | 279 | sv car25519(gf o) 280 | { 281 | int i; 282 | i64 c; 283 | FOR(i,16) { 284 | o[i]+=(1LL<<16); 285 | c=o[i]>>16; 286 | o[(i+1)*(i<15)]+=c-1+37*(c-1)*(i==15); 287 | o[i]-=c<<16; 288 | } 289 | } 290 | 291 | sv sel25519(gf p,gf q,int b) 292 | { 293 | i64 t,i,c=~(b-1); 294 | FOR(i,16) { 295 | t= c&(p[i]^q[i]); 296 | p[i]^=t; 297 | q[i]^=t; 298 | } 299 | } 300 | 301 | sv pack25519(u8 *o,const gf n) 302 | { 303 | int i,j,b; 304 | gf m,t; 305 | FOR(i,16) t[i]=n[i]; 306 | car25519(t); 307 | car25519(t); 308 | car25519(t); 309 | FOR(j,2) { 310 | m[0]=t[0]-0xffed; 311 | for(i=1;i<15;i++) { 312 | m[i]=t[i]-0xffff-((m[i-1]>>16)&1); 313 | m[i-1]&=0xffff; 314 | } 315 | m[15]=t[15]-0x7fff-((m[14]>>16)&1); 316 | b=(m[15]>>16)&1; 317 | m[14]&=0xffff; 318 | sel25519(t,m,1-b); 319 | } 320 | FOR(i,16) { 321 | o[2*i]=t[i]&0xff; 322 | o[2*i+1]=t[i]>>8; 323 | } 324 | } 325 | 326 | static int neq25519(const gf a, const gf b) 327 | { 328 | u8 c[32],d[32]; 329 | pack25519(c,a); 330 | pack25519(d,b); 331 | return crypto_verify_32(c,d); 332 | } 333 | 334 | static u8 par25519(const gf a) 335 | { 336 | u8 d[32]; 337 | pack25519(d,a); 338 | return d[0]&1; 339 | } 340 | 341 | sv unpack25519(gf o, const u8 *n) 342 | { 343 | int i; 344 | FOR(i,16) o[i]=n[2*i]+((i64)n[2*i+1]<<8); 345 | o[15]&=0x7fff; 346 | } 347 | 348 | sv A(gf o,const gf a,const gf b) 349 | { 350 | int i; 351 | FOR(i,16) o[i]=a[i]+b[i]; 352 | } 353 | 354 | sv Z(gf o,const gf a,const gf b) 355 | { 356 | int i; 357 | FOR(i,16) o[i]=a[i]-b[i]; 358 | } 359 | 360 | sv M(gf o,const gf a,const gf b) 361 | { 362 | i64 i,j,t[31]; 363 | FOR(i,31) t[i]=0; 364 | FOR(i,16) FOR(j,16) t[i+j]+=a[i]*b[j]; 365 | FOR(i,15) t[i]+=38*t[i+16]; 366 | FOR(i,16) o[i]=t[i]; 367 | car25519(o); 368 | car25519(o); 369 | } 370 | 371 | sv S(gf o,const gf a) 372 | { 373 | M(o,a,a); 374 | } 375 | 376 | sv inv25519(gf o,const gf i) 377 | { 378 | gf c; 379 | int a; 380 | FOR(a,16) c[a]=i[a]; 381 | for(a=253;a>=0;a--) { 382 | S(c,c); 383 | if(a!=2&&a!=4) M(c,c,i); 384 | } 385 | FOR(a,16) o[a]=c[a]; 386 | } 387 | 388 | sv pow2523(gf o,const gf i) 389 | { 390 | gf c; 391 | int a; 392 | FOR(a,16) c[a]=i[a]; 393 | for(a=250;a>=0;a--) { 394 | S(c,c); 395 | if(a!=1) M(c,c,i); 396 | } 397 | FOR(a,16) o[a]=c[a]; 398 | } 399 | 400 | int crypto_scalarmult(u8 *q,const u8 *n,const u8 *p) 401 | { 402 | u8 z[32]; 403 | i64 x[80],r,i; 404 | gf a,b,c,d,e,f; 405 | FOR(i,31) z[i]=n[i]; 406 | z[31]=(n[31]&127)|64; 407 | z[0]&=248; 408 | unpack25519(x,p); 409 | FOR(i,16) { 410 | b[i]=x[i]; 411 | d[i]=a[i]=c[i]=0; 412 | } 413 | a[0]=d[0]=1; 414 | for(i=254;i>=0;--i) { 415 | r=(z[i>>3]>>(i&7))&1; 416 | sel25519(a,b,r); 417 | sel25519(c,d,r); 418 | A(e,a,c); 419 | Z(a,a,c); 420 | A(c,b,d); 421 | Z(b,b,d); 422 | S(d,e); 423 | S(f,a); 424 | M(a,c,a); 425 | M(c,b,e); 426 | A(e,a,c); 427 | Z(a,a,c); 428 | S(b,a); 429 | Z(c,d,f); 430 | M(a,c,_121665); 431 | A(a,a,d); 432 | M(c,c,a); 433 | M(a,d,f); 434 | M(d,b,x); 435 | S(b,e); 436 | sel25519(a,b,r); 437 | sel25519(c,d,r); 438 | } 439 | FOR(i,16) { 440 | x[i+16]=a[i]; 441 | x[i+32]=c[i]; 442 | x[i+48]=b[i]; 443 | x[i+64]=d[i]; 444 | } 445 | inv25519(x+32,x+32); 446 | M(x+16,x+16,x+32); 447 | pack25519(q,x+16); 448 | return 0; 449 | } 450 | 451 | int crypto_scalarmult_base(u8 *q,const u8 *n) 452 | { 453 | return crypto_scalarmult(q,n,_9); 454 | } 455 | 456 | int crypto_box_keypair(u8 *y,u8 *x) 457 | { 458 | randombytes(x,32); 459 | return crypto_scalarmult_base(y,x); 460 | } 461 | 462 | int crypto_box_beforenm(u8 *k,const u8 *y,const u8 *x) 463 | { 464 | u8 s[32]; 465 | crypto_scalarmult(s,x,y); 466 | return crypto_core_hsalsa20(k,_0,s,sigma); 467 | } 468 | 469 | int crypto_box_afternm(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *k) 470 | { 471 | return crypto_secretbox(c,m,d,n,k); 472 | } 473 | 474 | int crypto_box_open_afternm(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *k) 475 | { 476 | return crypto_secretbox_open(m,c,d,n,k); 477 | } 478 | 479 | int crypto_box(u8 *c,const u8 *m,u64 d,const u8 *n,const u8 *y,const u8 *x) 480 | { 481 | u8 k[32]; 482 | crypto_box_beforenm(k,y,x); 483 | return crypto_box_afternm(c,m,d,n,k); 484 | } 485 | 486 | int crypto_box_open(u8 *m,const u8 *c,u64 d,const u8 *n,const u8 *y,const u8 *x) 487 | { 488 | u8 k[32]; 489 | crypto_box_beforenm(k,y,x); 490 | return crypto_box_open_afternm(m,c,d,n,k); 491 | } 492 | 493 | static u64 R(u64 x,int c) { return (x >> c) | (x << (64 - c)); } 494 | static u64 Ch(u64 x,u64 y,u64 z) { return (x & y) ^ (~x & z); } 495 | static u64 Maj(u64 x,u64 y,u64 z) { return (x & y) ^ (x & z) ^ (y & z); } 496 | static u64 Sigma0(u64 x) { return R(x,28) ^ R(x,34) ^ R(x,39); } 497 | static u64 Sigma1(u64 x) { return R(x,14) ^ R(x,18) ^ R(x,41); } 498 | static u64 sigma0(u64 x) { return R(x, 1) ^ R(x, 8) ^ (x >> 7); } 499 | static u64 sigma1(u64 x) { return R(x,19) ^ R(x,61) ^ (x >> 6); } 500 | 501 | static const u64 K[80] = 502 | { 503 | 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, 504 | 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, 505 | 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, 506 | 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, 507 | 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, 508 | 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, 509 | 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, 510 | 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, 511 | 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, 512 | 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, 513 | 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, 514 | 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, 515 | 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, 516 | 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, 517 | 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, 518 | 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, 519 | 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, 520 | 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, 521 | 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, 522 | 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL 523 | }; 524 | 525 | int crypto_hashblocks(u8 *x,const u8 *m,u64 n) 526 | { 527 | u64 z[8],b[8],a[8],w[16],t; 528 | int i,j; 529 | 530 | FOR(i,8) z[i] = a[i] = dl64(x + 8 * i); 531 | 532 | while (n >= 128) { 533 | FOR(i,16) w[i] = dl64(m + 8 * i); 534 | 535 | FOR(i,80) { 536 | FOR(j,8) b[j] = a[j]; 537 | t = a[7] + Sigma1(a[4]) + Ch(a[4],a[5],a[6]) + K[i] + w[i%16]; 538 | b[7] = t + Sigma0(a[0]) + Maj(a[0],a[1],a[2]); 539 | b[3] += t; 540 | FOR(j,8) a[(j+1)%8] = b[j]; 541 | if (i%16 == 15) 542 | FOR(j,16) 543 | w[j] += w[(j+9)%16] + sigma0(w[(j+1)%16]) + sigma1(w[(j+14)%16]); 544 | } 545 | 546 | FOR(i,8) { a[i] += z[i]; z[i] = a[i]; } 547 | 548 | m += 128; 549 | n -= 128; 550 | } 551 | 552 | FOR(i,8) ts64(x+8*i,z[i]); 553 | 554 | return n; 555 | } 556 | 557 | static const u8 iv[64] = { 558 | 0x6a,0x09,0xe6,0x67,0xf3,0xbc,0xc9,0x08, 559 | 0xbb,0x67,0xae,0x85,0x84,0xca,0xa7,0x3b, 560 | 0x3c,0x6e,0xf3,0x72,0xfe,0x94,0xf8,0x2b, 561 | 0xa5,0x4f,0xf5,0x3a,0x5f,0x1d,0x36,0xf1, 562 | 0x51,0x0e,0x52,0x7f,0xad,0xe6,0x82,0xd1, 563 | 0x9b,0x05,0x68,0x8c,0x2b,0x3e,0x6c,0x1f, 564 | 0x1f,0x83,0xd9,0xab,0xfb,0x41,0xbd,0x6b, 565 | 0x5b,0xe0,0xcd,0x19,0x13,0x7e,0x21,0x79 566 | } ; 567 | 568 | int crypto_hash(u8 *out,const u8 *m,u64 n) 569 | { 570 | u8 h[64],x[256]; 571 | u64 i,b = n; 572 | 573 | FOR(i,64) h[i] = iv[i]; 574 | 575 | crypto_hashblocks(h,m,n); 576 | m += n; 577 | n &= 127; 578 | m -= n; 579 | 580 | FOR(i,256) x[i] = 0; 581 | FOR(i,n) x[i] = m[i]; 582 | x[n] = 128; 583 | 584 | n = 256-128*(n<112); 585 | x[n-9] = b >> 61; 586 | ts64(x+n-8,b<<3); 587 | crypto_hashblocks(h,x,n); 588 | 589 | FOR(i,64) out[i] = h[i]; 590 | 591 | return 0; 592 | } 593 | 594 | sv add(gf p[4],gf q[4]) 595 | { 596 | gf a,b,c,d,t,e,f,g,h; 597 | 598 | Z(a, p[1], p[0]); 599 | Z(t, q[1], q[0]); 600 | M(a, a, t); 601 | A(b, p[0], p[1]); 602 | A(t, q[0], q[1]); 603 | M(b, b, t); 604 | M(c, p[3], q[3]); 605 | M(c, c, D2); 606 | M(d, p[2], q[2]); 607 | A(d, d, d); 608 | Z(e, b, a); 609 | Z(f, d, c); 610 | A(g, d, c); 611 | A(h, b, a); 612 | 613 | M(p[0], e, f); 614 | M(p[1], h, g); 615 | M(p[2], g, f); 616 | M(p[3], e, h); 617 | } 618 | 619 | sv cswap(gf p[4],gf q[4],u8 b) 620 | { 621 | int i; 622 | FOR(i,4) 623 | sel25519(p[i],q[i],b); 624 | } 625 | 626 | sv pack(u8 *r,gf p[4]) 627 | { 628 | gf tx, ty, zi; 629 | inv25519(zi, p[2]); 630 | M(tx, p[0], zi); 631 | M(ty, p[1], zi); 632 | pack25519(r, ty); 633 | r[31] ^= par25519(tx) << 7; 634 | } 635 | 636 | sv scalarmult(gf p[4],gf q[4],const u8 *s) 637 | { 638 | int i; 639 | set25519(p[0],gf0); 640 | set25519(p[1],gf1); 641 | set25519(p[2],gf1); 642 | set25519(p[3],gf0); 643 | for (i = 255;i >= 0;--i) { 644 | u8 b = (s[i/8]>>(i&7))&1; 645 | cswap(p,q,b); 646 | add(q,p); 647 | add(p,p); 648 | cswap(p,q,b); 649 | } 650 | } 651 | 652 | sv scalarbase(gf p[4],const u8 *s) 653 | { 654 | gf q[4]; 655 | set25519(q[0],X); 656 | set25519(q[1],Y); 657 | set25519(q[2],gf1); 658 | M(q[3],X,Y); 659 | scalarmult(p,q,s); 660 | } 661 | 662 | int crypto_sign_keypair(u8 *pk, u8 *sk) 663 | { 664 | u8 d[64]; 665 | gf p[4]; 666 | int i; 667 | 668 | randombytes(sk, 32); 669 | crypto_hash(d, sk, 32); 670 | d[0] &= 248; 671 | d[31] &= 127; 672 | d[31] |= 64; 673 | 674 | scalarbase(p,d); 675 | pack(pk,p); 676 | 677 | FOR(i,32) sk[32 + i] = pk[i]; 678 | return 0; 679 | } 680 | 681 | static const u64 L[32] = {0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10}; 682 | 683 | sv modL(u8 *r,i64 x[64]) 684 | { 685 | i64 carry,i,j; 686 | for (i = 63;i >= 32;--i) { 687 | carry = 0; 688 | for (j = i - 32;j < i - 12;++j) { 689 | x[j] += carry - 16 * x[i] * L[j - (i - 32)]; 690 | carry = (x[j] + 128) >> 8; 691 | x[j] -= carry << 8; 692 | } 693 | x[j] += carry; 694 | x[i] = 0; 695 | } 696 | carry = 0; 697 | FOR(j,32) { 698 | x[j] += carry - (x[31] >> 4) * L[j]; 699 | carry = x[j] >> 8; 700 | x[j] &= 255; 701 | } 702 | FOR(j,32) x[j] -= carry * L[j]; 703 | FOR(i,32) { 704 | x[i+1] += x[i] >> 8; 705 | r[i] = x[i] & 255; 706 | } 707 | } 708 | 709 | sv reduce(u8 *r) 710 | { 711 | i64 x[64],i; 712 | FOR(i,64) x[i] = (u64) r[i]; 713 | FOR(i,64) r[i] = 0; 714 | modL(r,x); 715 | } 716 | 717 | int crypto_sign(u8 *sm,u64 *smlen,const u8 *m,u64 n,const u8 *sk) 718 | { 719 | u8 d[64],h[64],r[64]; 720 | i64 i,j,x[64]; 721 | gf p[4]; 722 | 723 | crypto_hash(d, sk, 32); 724 | d[0] &= 248; 725 | d[31] &= 127; 726 | d[31] |= 64; 727 | 728 | *smlen = n+64; 729 | FOR(i,n) sm[64 + i] = m[i]; 730 | FOR(i,32) sm[32 + i] = d[32 + i]; 731 | 732 | crypto_hash(r, sm+32, n+32); 733 | reduce(r); 734 | scalarbase(p,r); 735 | pack(sm,p); 736 | 737 | FOR(i,32) sm[i+32] = sk[i+32]; 738 | crypto_hash(h,sm,n + 64); 739 | reduce(h); 740 | 741 | FOR(i,64) x[i] = 0; 742 | FOR(i,32) x[i] = (u64) r[i]; 743 | FOR(i,32) FOR(j,32) x[i+j] += h[i] * (u64) d[j]; 744 | modL(sm + 32,x); 745 | 746 | return 0; 747 | } 748 | 749 | static int unpackneg(gf r[4],const u8 p[32]) 750 | { 751 | gf t, chk, num, den, den2, den4, den6; 752 | set25519(r[2],gf1); 753 | unpack25519(r[1],p); 754 | S(num,r[1]); 755 | M(den,num,D); 756 | Z(num,num,r[2]); 757 | A(den,r[2],den); 758 | 759 | S(den2,den); 760 | S(den4,den2); 761 | M(den6,den4,den2); 762 | M(t,den6,num); 763 | M(t,t,den); 764 | 765 | pow2523(t,t); 766 | M(t,t,num); 767 | M(t,t,den); 768 | M(t,t,den); 769 | M(r[0],t,den); 770 | 771 | S(chk,r[0]); 772 | M(chk,chk,den); 773 | if (neq25519(chk, num)) M(r[0],r[0],I); 774 | 775 | S(chk,r[0]); 776 | M(chk,chk,den); 777 | if (neq25519(chk, num)) return -1; 778 | 779 | if (par25519(r[0]) == (p[31]>>7)) Z(r[0],gf0,r[0]); 780 | 781 | M(r[3],r[0],r[1]); 782 | return 0; 783 | } 784 | 785 | int crypto_sign_open(u8 *m,u64 *mlen,const u8 *sm,u64 n,const u8 *pk) 786 | { 787 | int i; 788 | u8 t[32],h[64]; 789 | gf p[4],q[4]; 790 | 791 | *mlen = -1; 792 | if (n < 64) return -1; 793 | 794 | if (unpackneg(q,pk)) return -1; 795 | 796 | FOR(i,n) m[i] = sm[i]; 797 | FOR(i,32) m[i+32] = pk[i]; 798 | crypto_hash(h,m,n); 799 | reduce(h); 800 | scalarmult(p,q,h); 801 | 802 | scalarbase(q,sm + 32); 803 | add(p,q); 804 | pack(t,p); 805 | 806 | n -= 64; 807 | if (crypto_verify_32(sm, t)) { 808 | FOR(i,n) m[i] = 0; 809 | return -1; 810 | } 811 | 812 | FOR(i,n) m[i] = sm[i + 64]; 813 | *mlen = n; 814 | return 0; 815 | } 816 | -------------------------------------------------------------------------------- /sign/tweetnacl.h: -------------------------------------------------------------------------------- 1 | // tweetnacl.* is part of public domain TweetNaCl library available here: https://tweetnacl.cr.yp.to 2 | #ifndef TWEETNACL_H 3 | #define TWEETNACL_H 4 | #define crypto_auth_PRIMITIVE "hmacsha512256" 5 | #define crypto_auth crypto_auth_hmacsha512256 6 | #define crypto_auth_verify crypto_auth_hmacsha512256_verify 7 | #define crypto_auth_BYTES crypto_auth_hmacsha512256_BYTES 8 | #define crypto_auth_KEYBYTES crypto_auth_hmacsha512256_KEYBYTES 9 | #define crypto_auth_IMPLEMENTATION crypto_auth_hmacsha512256_IMPLEMENTATION 10 | #define crypto_auth_VERSION crypto_auth_hmacsha512256_VERSION 11 | #define crypto_auth_hmacsha512256_tweet_BYTES 32 12 | #define crypto_auth_hmacsha512256_tweet_KEYBYTES 32 13 | extern int crypto_auth_hmacsha512256_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *); 14 | extern int crypto_auth_hmacsha512256_tweet_verify(const unsigned char *,const unsigned char *,unsigned long long,const unsigned char *); 15 | #define crypto_auth_hmacsha512256_tweet_VERSION "-" 16 | #define crypto_auth_hmacsha512256 crypto_auth_hmacsha512256_tweet 17 | #define crypto_auth_hmacsha512256_verify crypto_auth_hmacsha512256_tweet_verify 18 | #define crypto_auth_hmacsha512256_BYTES crypto_auth_hmacsha512256_tweet_BYTES 19 | #define crypto_auth_hmacsha512256_KEYBYTES crypto_auth_hmacsha512256_tweet_KEYBYTES 20 | #define crypto_auth_hmacsha512256_VERSION crypto_auth_hmacsha512256_tweet_VERSION 21 | #define crypto_auth_hmacsha512256_IMPLEMENTATION "crypto_auth/hmacsha512256/tweet" 22 | #define crypto_box_PRIMITIVE "curve25519xsalsa20poly1305" 23 | #define crypto_box crypto_box_curve25519xsalsa20poly1305 24 | #define crypto_box_open crypto_box_curve25519xsalsa20poly1305_open 25 | #define crypto_box_keypair crypto_box_curve25519xsalsa20poly1305_keypair 26 | #define crypto_box_beforenm crypto_box_curve25519xsalsa20poly1305_beforenm 27 | #define crypto_box_afternm crypto_box_curve25519xsalsa20poly1305_afternm 28 | #define crypto_box_open_afternm crypto_box_curve25519xsalsa20poly1305_open_afternm 29 | #define crypto_box_PUBLICKEYBYTES crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES 30 | #define crypto_box_SECRETKEYBYTES crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES 31 | #define crypto_box_BEFORENMBYTES crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES 32 | #define crypto_box_NONCEBYTES crypto_box_curve25519xsalsa20poly1305_NONCEBYTES 33 | #define crypto_box_ZEROBYTES crypto_box_curve25519xsalsa20poly1305_ZEROBYTES 34 | #define crypto_box_BOXZEROBYTES crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES 35 | #define crypto_box_IMPLEMENTATION crypto_box_curve25519xsalsa20poly1305_IMPLEMENTATION 36 | #define crypto_box_VERSION crypto_box_curve25519xsalsa20poly1305_VERSION 37 | #define crypto_box_curve25519xsalsa20poly1305_tweet_PUBLICKEYBYTES 32 38 | #define crypto_box_curve25519xsalsa20poly1305_tweet_SECRETKEYBYTES 32 39 | #define crypto_box_curve25519xsalsa20poly1305_tweet_BEFORENMBYTES 32 40 | #define crypto_box_curve25519xsalsa20poly1305_tweet_NONCEBYTES 24 41 | #define crypto_box_curve25519xsalsa20poly1305_tweet_ZEROBYTES 32 42 | #define crypto_box_curve25519xsalsa20poly1305_tweet_BOXZEROBYTES 16 43 | extern int crypto_box_curve25519xsalsa20poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *,const unsigned char *); 44 | extern int crypto_box_curve25519xsalsa20poly1305_tweet_open(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *,const unsigned char *); 45 | extern int crypto_box_curve25519xsalsa20poly1305_tweet_keypair(unsigned char *,unsigned char *); 46 | extern int crypto_box_curve25519xsalsa20poly1305_tweet_beforenm(unsigned char *,const unsigned char *,const unsigned char *); 47 | extern int crypto_box_curve25519xsalsa20poly1305_tweet_afternm(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 48 | extern int crypto_box_curve25519xsalsa20poly1305_tweet_open_afternm(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 49 | #define crypto_box_curve25519xsalsa20poly1305_tweet_VERSION "-" 50 | #define crypto_box_curve25519xsalsa20poly1305 crypto_box_curve25519xsalsa20poly1305_tweet 51 | #define crypto_box_curve25519xsalsa20poly1305_open crypto_box_curve25519xsalsa20poly1305_tweet_open 52 | #define crypto_box_curve25519xsalsa20poly1305_keypair crypto_box_curve25519xsalsa20poly1305_tweet_keypair 53 | #define crypto_box_curve25519xsalsa20poly1305_beforenm crypto_box_curve25519xsalsa20poly1305_tweet_beforenm 54 | #define crypto_box_curve25519xsalsa20poly1305_afternm crypto_box_curve25519xsalsa20poly1305_tweet_afternm 55 | #define crypto_box_curve25519xsalsa20poly1305_open_afternm crypto_box_curve25519xsalsa20poly1305_tweet_open_afternm 56 | #define crypto_box_curve25519xsalsa20poly1305_PUBLICKEYBYTES crypto_box_curve25519xsalsa20poly1305_tweet_PUBLICKEYBYTES 57 | #define crypto_box_curve25519xsalsa20poly1305_SECRETKEYBYTES crypto_box_curve25519xsalsa20poly1305_tweet_SECRETKEYBYTES 58 | #define crypto_box_curve25519xsalsa20poly1305_BEFORENMBYTES crypto_box_curve25519xsalsa20poly1305_tweet_BEFORENMBYTES 59 | #define crypto_box_curve25519xsalsa20poly1305_NONCEBYTES crypto_box_curve25519xsalsa20poly1305_tweet_NONCEBYTES 60 | #define crypto_box_curve25519xsalsa20poly1305_ZEROBYTES crypto_box_curve25519xsalsa20poly1305_tweet_ZEROBYTES 61 | #define crypto_box_curve25519xsalsa20poly1305_BOXZEROBYTES crypto_box_curve25519xsalsa20poly1305_tweet_BOXZEROBYTES 62 | #define crypto_box_curve25519xsalsa20poly1305_VERSION crypto_box_curve25519xsalsa20poly1305_tweet_VERSION 63 | #define crypto_box_curve25519xsalsa20poly1305_IMPLEMENTATION "crypto_box/curve25519xsalsa20poly1305/tweet" 64 | #define crypto_core_PRIMITIVE "salsa20" 65 | #define crypto_core crypto_core_salsa20 66 | #define crypto_core_OUTPUTBYTES crypto_core_salsa20_OUTPUTBYTES 67 | #define crypto_core_INPUTBYTES crypto_core_salsa20_INPUTBYTES 68 | #define crypto_core_KEYBYTES crypto_core_salsa20_KEYBYTES 69 | #define crypto_core_CONSTBYTES crypto_core_salsa20_CONSTBYTES 70 | #define crypto_core_IMPLEMENTATION crypto_core_salsa20_IMPLEMENTATION 71 | #define crypto_core_VERSION crypto_core_salsa20_VERSION 72 | #define crypto_core_salsa20_tweet_OUTPUTBYTES 64 73 | #define crypto_core_salsa20_tweet_INPUTBYTES 16 74 | #define crypto_core_salsa20_tweet_KEYBYTES 32 75 | #define crypto_core_salsa20_tweet_CONSTBYTES 16 76 | extern int crypto_core_salsa20_tweet(unsigned char *,const unsigned char *,const unsigned char *,const unsigned char *); 77 | #define crypto_core_salsa20_tweet_VERSION "-" 78 | #define crypto_core_salsa20 crypto_core_salsa20_tweet 79 | #define crypto_core_salsa20_OUTPUTBYTES crypto_core_salsa20_tweet_OUTPUTBYTES 80 | #define crypto_core_salsa20_INPUTBYTES crypto_core_salsa20_tweet_INPUTBYTES 81 | #define crypto_core_salsa20_KEYBYTES crypto_core_salsa20_tweet_KEYBYTES 82 | #define crypto_core_salsa20_CONSTBYTES crypto_core_salsa20_tweet_CONSTBYTES 83 | #define crypto_core_salsa20_VERSION crypto_core_salsa20_tweet_VERSION 84 | #define crypto_core_salsa20_IMPLEMENTATION "crypto_core/salsa20/tweet" 85 | #define crypto_core_hsalsa20_tweet_OUTPUTBYTES 32 86 | #define crypto_core_hsalsa20_tweet_INPUTBYTES 16 87 | #define crypto_core_hsalsa20_tweet_KEYBYTES 32 88 | #define crypto_core_hsalsa20_tweet_CONSTBYTES 16 89 | extern int crypto_core_hsalsa20_tweet(unsigned char *,const unsigned char *,const unsigned char *,const unsigned char *); 90 | #define crypto_core_hsalsa20_tweet_VERSION "-" 91 | #define crypto_core_hsalsa20 crypto_core_hsalsa20_tweet 92 | #define crypto_core_hsalsa20_OUTPUTBYTES crypto_core_hsalsa20_tweet_OUTPUTBYTES 93 | #define crypto_core_hsalsa20_INPUTBYTES crypto_core_hsalsa20_tweet_INPUTBYTES 94 | #define crypto_core_hsalsa20_KEYBYTES crypto_core_hsalsa20_tweet_KEYBYTES 95 | #define crypto_core_hsalsa20_CONSTBYTES crypto_core_hsalsa20_tweet_CONSTBYTES 96 | #define crypto_core_hsalsa20_VERSION crypto_core_hsalsa20_tweet_VERSION 97 | #define crypto_core_hsalsa20_IMPLEMENTATION "crypto_core/hsalsa20/tweet" 98 | #define crypto_hashblocks_PRIMITIVE "sha512" 99 | #define crypto_hashblocks crypto_hashblocks_sha512 100 | #define crypto_hashblocks_STATEBYTES crypto_hashblocks_sha512_STATEBYTES 101 | #define crypto_hashblocks_BLOCKBYTES crypto_hashblocks_sha512_BLOCKBYTES 102 | #define crypto_hashblocks_IMPLEMENTATION crypto_hashblocks_sha512_IMPLEMENTATION 103 | #define crypto_hashblocks_VERSION crypto_hashblocks_sha512_VERSION 104 | #define crypto_hashblocks_sha512_tweet_STATEBYTES 64 105 | #define crypto_hashblocks_sha512_tweet_BLOCKBYTES 128 106 | extern int crypto_hashblocks_sha512_tweet(unsigned char *,const unsigned char *,unsigned long long); 107 | #define crypto_hashblocks_sha512_tweet_VERSION "-" 108 | #define crypto_hashblocks_sha512 crypto_hashblocks_sha512_tweet 109 | #define crypto_hashblocks_sha512_STATEBYTES crypto_hashblocks_sha512_tweet_STATEBYTES 110 | #define crypto_hashblocks_sha512_BLOCKBYTES crypto_hashblocks_sha512_tweet_BLOCKBYTES 111 | #define crypto_hashblocks_sha512_VERSION crypto_hashblocks_sha512_tweet_VERSION 112 | #define crypto_hashblocks_sha512_IMPLEMENTATION "crypto_hashblocks/sha512/tweet" 113 | #define crypto_hashblocks_sha256_tweet_STATEBYTES 32 114 | #define crypto_hashblocks_sha256_tweet_BLOCKBYTES 64 115 | extern int crypto_hashblocks_sha256_tweet(unsigned char *,const unsigned char *,unsigned long long); 116 | #define crypto_hashblocks_sha256_tweet_VERSION "-" 117 | #define crypto_hashblocks_sha256 crypto_hashblocks_sha256_tweet 118 | #define crypto_hashblocks_sha256_STATEBYTES crypto_hashblocks_sha256_tweet_STATEBYTES 119 | #define crypto_hashblocks_sha256_BLOCKBYTES crypto_hashblocks_sha256_tweet_BLOCKBYTES 120 | #define crypto_hashblocks_sha256_VERSION crypto_hashblocks_sha256_tweet_VERSION 121 | #define crypto_hashblocks_sha256_IMPLEMENTATION "crypto_hashblocks/sha256/tweet" 122 | #define crypto_hash_PRIMITIVE "sha512" 123 | #define crypto_hash crypto_hash_sha512 124 | #define crypto_hash_BYTES crypto_hash_sha512_BYTES 125 | #define crypto_hash_IMPLEMENTATION crypto_hash_sha512_IMPLEMENTATION 126 | #define crypto_hash_VERSION crypto_hash_sha512_VERSION 127 | #define crypto_hash_sha512_tweet_BYTES 64 128 | extern int crypto_hash_sha512_tweet(unsigned char *,const unsigned char *,unsigned long long); 129 | #define crypto_hash_sha512_tweet_VERSION "-" 130 | #define crypto_hash_sha512 crypto_hash_sha512_tweet 131 | #define crypto_hash_sha512_BYTES crypto_hash_sha512_tweet_BYTES 132 | #define crypto_hash_sha512_VERSION crypto_hash_sha512_tweet_VERSION 133 | #define crypto_hash_sha512_IMPLEMENTATION "crypto_hash/sha512/tweet" 134 | #define crypto_hash_sha256_tweet_BYTES 32 135 | extern int crypto_hash_sha256_tweet(unsigned char *,const unsigned char *,unsigned long long); 136 | #define crypto_hash_sha256_tweet_VERSION "-" 137 | #define crypto_hash_sha256 crypto_hash_sha256_tweet 138 | #define crypto_hash_sha256_BYTES crypto_hash_sha256_tweet_BYTES 139 | #define crypto_hash_sha256_VERSION crypto_hash_sha256_tweet_VERSION 140 | #define crypto_hash_sha256_IMPLEMENTATION "crypto_hash/sha256/tweet" 141 | #define crypto_onetimeauth_PRIMITIVE "poly1305" 142 | #define crypto_onetimeauth crypto_onetimeauth_poly1305 143 | #define crypto_onetimeauth_verify crypto_onetimeauth_poly1305_verify 144 | #define crypto_onetimeauth_BYTES crypto_onetimeauth_poly1305_BYTES 145 | #define crypto_onetimeauth_KEYBYTES crypto_onetimeauth_poly1305_KEYBYTES 146 | #define crypto_onetimeauth_IMPLEMENTATION crypto_onetimeauth_poly1305_IMPLEMENTATION 147 | #define crypto_onetimeauth_VERSION crypto_onetimeauth_poly1305_VERSION 148 | #define crypto_onetimeauth_poly1305_tweet_BYTES 16 149 | #define crypto_onetimeauth_poly1305_tweet_KEYBYTES 32 150 | extern int crypto_onetimeauth_poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *); 151 | extern int crypto_onetimeauth_poly1305_tweet_verify(const unsigned char *,const unsigned char *,unsigned long long,const unsigned char *); 152 | #define crypto_onetimeauth_poly1305_tweet_VERSION "-" 153 | #define crypto_onetimeauth_poly1305 crypto_onetimeauth_poly1305_tweet 154 | #define crypto_onetimeauth_poly1305_verify crypto_onetimeauth_poly1305_tweet_verify 155 | #define crypto_onetimeauth_poly1305_BYTES crypto_onetimeauth_poly1305_tweet_BYTES 156 | #define crypto_onetimeauth_poly1305_KEYBYTES crypto_onetimeauth_poly1305_tweet_KEYBYTES 157 | #define crypto_onetimeauth_poly1305_VERSION crypto_onetimeauth_poly1305_tweet_VERSION 158 | #define crypto_onetimeauth_poly1305_IMPLEMENTATION "crypto_onetimeauth/poly1305/tweet" 159 | #define crypto_scalarmult_PRIMITIVE "curve25519" 160 | #define crypto_scalarmult crypto_scalarmult_curve25519 161 | #define crypto_scalarmult_base crypto_scalarmult_curve25519_base 162 | #define crypto_scalarmult_BYTES crypto_scalarmult_curve25519_BYTES 163 | #define crypto_scalarmult_SCALARBYTES crypto_scalarmult_curve25519_SCALARBYTES 164 | #define crypto_scalarmult_IMPLEMENTATION crypto_scalarmult_curve25519_IMPLEMENTATION 165 | #define crypto_scalarmult_VERSION crypto_scalarmult_curve25519_VERSION 166 | #define crypto_scalarmult_curve25519_tweet_BYTES 32 167 | #define crypto_scalarmult_curve25519_tweet_SCALARBYTES 32 168 | extern int crypto_scalarmult_curve25519_tweet(unsigned char *,const unsigned char *,const unsigned char *); 169 | extern int crypto_scalarmult_curve25519_tweet_base(unsigned char *,const unsigned char *); 170 | #define crypto_scalarmult_curve25519_tweet_VERSION "-" 171 | #define crypto_scalarmult_curve25519 crypto_scalarmult_curve25519_tweet 172 | #define crypto_scalarmult_curve25519_base crypto_scalarmult_curve25519_tweet_base 173 | #define crypto_scalarmult_curve25519_BYTES crypto_scalarmult_curve25519_tweet_BYTES 174 | #define crypto_scalarmult_curve25519_SCALARBYTES crypto_scalarmult_curve25519_tweet_SCALARBYTES 175 | #define crypto_scalarmult_curve25519_VERSION crypto_scalarmult_curve25519_tweet_VERSION 176 | #define crypto_scalarmult_curve25519_IMPLEMENTATION "crypto_scalarmult/curve25519/tweet" 177 | #define crypto_secretbox_PRIMITIVE "xsalsa20poly1305" 178 | #define crypto_secretbox crypto_secretbox_xsalsa20poly1305 179 | #define crypto_secretbox_open crypto_secretbox_xsalsa20poly1305_open 180 | #define crypto_secretbox_KEYBYTES crypto_secretbox_xsalsa20poly1305_KEYBYTES 181 | #define crypto_secretbox_NONCEBYTES crypto_secretbox_xsalsa20poly1305_NONCEBYTES 182 | #define crypto_secretbox_ZEROBYTES crypto_secretbox_xsalsa20poly1305_ZEROBYTES 183 | #define crypto_secretbox_BOXZEROBYTES crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES 184 | #define crypto_secretbox_IMPLEMENTATION crypto_secretbox_xsalsa20poly1305_IMPLEMENTATION 185 | #define crypto_secretbox_VERSION crypto_secretbox_xsalsa20poly1305_VERSION 186 | #define crypto_secretbox_xsalsa20poly1305_tweet_KEYBYTES 32 187 | #define crypto_secretbox_xsalsa20poly1305_tweet_NONCEBYTES 24 188 | #define crypto_secretbox_xsalsa20poly1305_tweet_ZEROBYTES 32 189 | #define crypto_secretbox_xsalsa20poly1305_tweet_BOXZEROBYTES 16 190 | extern int crypto_secretbox_xsalsa20poly1305_tweet(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 191 | extern int crypto_secretbox_xsalsa20poly1305_tweet_open(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 192 | #define crypto_secretbox_xsalsa20poly1305_tweet_VERSION "-" 193 | #define crypto_secretbox_xsalsa20poly1305 crypto_secretbox_xsalsa20poly1305_tweet 194 | #define crypto_secretbox_xsalsa20poly1305_open crypto_secretbox_xsalsa20poly1305_tweet_open 195 | #define crypto_secretbox_xsalsa20poly1305_KEYBYTES crypto_secretbox_xsalsa20poly1305_tweet_KEYBYTES 196 | #define crypto_secretbox_xsalsa20poly1305_NONCEBYTES crypto_secretbox_xsalsa20poly1305_tweet_NONCEBYTES 197 | #define crypto_secretbox_xsalsa20poly1305_ZEROBYTES crypto_secretbox_xsalsa20poly1305_tweet_ZEROBYTES 198 | #define crypto_secretbox_xsalsa20poly1305_BOXZEROBYTES crypto_secretbox_xsalsa20poly1305_tweet_BOXZEROBYTES 199 | #define crypto_secretbox_xsalsa20poly1305_VERSION crypto_secretbox_xsalsa20poly1305_tweet_VERSION 200 | #define crypto_secretbox_xsalsa20poly1305_IMPLEMENTATION "crypto_secretbox/xsalsa20poly1305/tweet" 201 | #define crypto_sign_PRIMITIVE "ed25519" 202 | #define crypto_sign crypto_sign_ed25519 203 | #define crypto_sign_open crypto_sign_ed25519_open 204 | #define crypto_sign_keypair crypto_sign_ed25519_keypair 205 | #define crypto_sign_BYTES crypto_sign_ed25519_BYTES 206 | #define crypto_sign_PUBLICKEYBYTES crypto_sign_ed25519_PUBLICKEYBYTES 207 | #define crypto_sign_SECRETKEYBYTES crypto_sign_ed25519_SECRETKEYBYTES 208 | #define crypto_sign_IMPLEMENTATION crypto_sign_ed25519_IMPLEMENTATION 209 | #define crypto_sign_VERSION crypto_sign_ed25519_VERSION 210 | #define crypto_sign_ed25519_tweet_BYTES 64 211 | #define crypto_sign_ed25519_tweet_PUBLICKEYBYTES 32 212 | #define crypto_sign_ed25519_tweet_SECRETKEYBYTES 64 213 | extern int crypto_sign_ed25519_tweet(unsigned char *,unsigned long long *,const unsigned char *,unsigned long long,const unsigned char *); 214 | extern int crypto_sign_ed25519_tweet_open(unsigned char *,unsigned long long *,const unsigned char *,unsigned long long,const unsigned char *); 215 | extern int crypto_sign_ed25519_tweet_keypair(unsigned char *,unsigned char *); 216 | #define crypto_sign_ed25519_tweet_VERSION "-" 217 | #define crypto_sign_ed25519 crypto_sign_ed25519_tweet 218 | #define crypto_sign_ed25519_open crypto_sign_ed25519_tweet_open 219 | #define crypto_sign_ed25519_keypair crypto_sign_ed25519_tweet_keypair 220 | #define crypto_sign_ed25519_BYTES crypto_sign_ed25519_tweet_BYTES 221 | #define crypto_sign_ed25519_PUBLICKEYBYTES crypto_sign_ed25519_tweet_PUBLICKEYBYTES 222 | #define crypto_sign_ed25519_SECRETKEYBYTES crypto_sign_ed25519_tweet_SECRETKEYBYTES 223 | #define crypto_sign_ed25519_VERSION crypto_sign_ed25519_tweet_VERSION 224 | #define crypto_sign_ed25519_IMPLEMENTATION "crypto_sign/ed25519/tweet" 225 | #define crypto_stream_PRIMITIVE "xsalsa20" 226 | #define crypto_stream crypto_stream_xsalsa20 227 | #define crypto_stream_xor crypto_stream_xsalsa20_xor 228 | #define crypto_stream_KEYBYTES crypto_stream_xsalsa20_KEYBYTES 229 | #define crypto_stream_NONCEBYTES crypto_stream_xsalsa20_NONCEBYTES 230 | #define crypto_stream_IMPLEMENTATION crypto_stream_xsalsa20_IMPLEMENTATION 231 | #define crypto_stream_VERSION crypto_stream_xsalsa20_VERSION 232 | #define crypto_stream_xsalsa20_tweet_KEYBYTES 32 233 | #define crypto_stream_xsalsa20_tweet_NONCEBYTES 24 234 | extern int crypto_stream_xsalsa20_tweet(unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 235 | extern int crypto_stream_xsalsa20_tweet_xor(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 236 | #define crypto_stream_xsalsa20_tweet_VERSION "-" 237 | #define crypto_stream_xsalsa20 crypto_stream_xsalsa20_tweet 238 | #define crypto_stream_xsalsa20_xor crypto_stream_xsalsa20_tweet_xor 239 | #define crypto_stream_xsalsa20_KEYBYTES crypto_stream_xsalsa20_tweet_KEYBYTES 240 | #define crypto_stream_xsalsa20_NONCEBYTES crypto_stream_xsalsa20_tweet_NONCEBYTES 241 | #define crypto_stream_xsalsa20_VERSION crypto_stream_xsalsa20_tweet_VERSION 242 | #define crypto_stream_xsalsa20_IMPLEMENTATION "crypto_stream/xsalsa20/tweet" 243 | #define crypto_stream_salsa20_tweet_KEYBYTES 32 244 | #define crypto_stream_salsa20_tweet_NONCEBYTES 8 245 | extern int crypto_stream_salsa20_tweet(unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 246 | extern int crypto_stream_salsa20_tweet_xor(unsigned char *,const unsigned char *,unsigned long long,const unsigned char *,const unsigned char *); 247 | #define crypto_stream_salsa20_tweet_VERSION "-" 248 | #define crypto_stream_salsa20 crypto_stream_salsa20_tweet 249 | #define crypto_stream_salsa20_xor crypto_stream_salsa20_tweet_xor 250 | #define crypto_stream_salsa20_KEYBYTES crypto_stream_salsa20_tweet_KEYBYTES 251 | #define crypto_stream_salsa20_NONCEBYTES crypto_stream_salsa20_tweet_NONCEBYTES 252 | #define crypto_stream_salsa20_VERSION crypto_stream_salsa20_tweet_VERSION 253 | #define crypto_stream_salsa20_IMPLEMENTATION "crypto_stream/salsa20/tweet" 254 | #define crypto_verify_PRIMITIVE "16" 255 | #define crypto_verify crypto_verify_16 256 | #define crypto_verify_BYTES crypto_verify_16_BYTES 257 | #define crypto_verify_IMPLEMENTATION crypto_verify_16_IMPLEMENTATION 258 | #define crypto_verify_VERSION crypto_verify_16_VERSION 259 | #define crypto_verify_16_tweet_BYTES 16 260 | extern int crypto_verify_16_tweet(const unsigned char *,const unsigned char *); 261 | #define crypto_verify_16_tweet_VERSION "-" 262 | #define crypto_verify_16 crypto_verify_16_tweet 263 | #define crypto_verify_16_BYTES crypto_verify_16_tweet_BYTES 264 | #define crypto_verify_16_VERSION crypto_verify_16_tweet_VERSION 265 | #define crypto_verify_16_IMPLEMENTATION "crypto_verify/16/tweet" 266 | #define crypto_verify_32_tweet_BYTES 32 267 | extern int crypto_verify_32_tweet(const unsigned char *,const unsigned char *); 268 | #define crypto_verify_32_tweet_VERSION "-" 269 | #define crypto_verify_32 crypto_verify_32_tweet 270 | #define crypto_verify_32_BYTES crypto_verify_32_tweet_BYTES 271 | #define crypto_verify_32_VERSION crypto_verify_32_tweet_VERSION 272 | #define crypto_verify_32_IMPLEMENTATION "crypto_verify/32/tweet" 273 | #endif 274 | -------------------------------------------------------------------------------- /sock.cpp: -------------------------------------------------------------------------------- 1 | #include "sock.h" 2 | #include "log.h" 3 | #include // stringstream 4 | #include // cerr 5 | #include // memset() 6 | using namespace std; 7 | 8 | #ifdef _WIN32 9 | #include // typedef int socklen_t; 10 | #define close(s) closesocket(s) 11 | #define SHUT_RDWR SD_BOTH // shutdown(socket, how) 12 | #define MSG_NOSIGNAL 0 // send() param 13 | #else // posix 14 | #include // signal() 15 | #include // close() 16 | #include // gethostbyname() 17 | #include 18 | #include 19 | #include // inet_ntop() 20 | #include // TCP_CORK, TCP_NODELAY 21 | #define SOCKET_ERROR (-1) 22 | #endif 23 | 24 | 25 | int err(const string& msg) { // display error on cerr 26 | #ifdef _WIN32 27 | int error = WSAGetLastError(); 28 | #else 29 | int error = errno; 30 | #endif 31 | 32 | #ifdef _MSC_VER // Visual Studio only. MinGW-w64 has a different strerror_s() definition 33 | char buff[128]; 34 | strerror_s(buff, error); 35 | logErr() << "ERROR " << msg << buff << " (" << error << ")" << endl; 36 | #else 37 | logErr() << "ERROR " << msg << strerror(error) << " (" << error << ")" << endl; 38 | #endif 39 | return error; // errno returns positive numbers 40 | } 41 | 42 | 43 | bool initNetwork(){ 44 | static bool initialized = false; 45 | if(initialized){ return true; } 46 | #ifdef _WIN32 47 | WSADATA wsaData; 48 | if(WSAStartup(0x0202, &wsaData)){ 49 | return err("WSAStartup() failed: "); 50 | } 51 | #else // TODO: use sigaction()? set a handler and log received signals? 52 | signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE signal which occurs in send() 53 | #endif 54 | initialized = true; 55 | return true; 56 | } 57 | 58 | 59 | // set read()/write() timeout otherwise a remote client/server can get our process stuck! 60 | int Sock::setRWtimeout(int seconds){ 61 | #ifdef _WIN32 62 | DWORD tv = seconds*1000; // milliseconds // fucking MSFT 63 | #else 64 | timeval tv = { seconds, 0 }; // tv.tv_sec = seconds; tv.tv_usec = 0; 65 | #endif 66 | if( setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv) ) ){ // RCV 67 | return err("setting read timeout: "); // errno returns positive numbers 68 | } 69 | if( setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv) ) ){ // SND 70 | return err("setting write timeout: "); // errno returns positive numbers 71 | } 72 | return 0; 73 | } 74 | 75 | 76 | int Sock::setNoDelay(bool state){ 77 | int onoff = state ? 1 : 0; 78 | return setsockopt(s, SOL_SOCKET, TCP_NODELAY, (char*) &onoff, sizeof(onoff) ); 79 | } 80 | 81 | 82 | int Sock::setCork(bool state){ 83 | #ifndef TCP_CORK 84 | return 0; // no TCP_CORK on windblows and macOS 85 | #else 86 | int onoff = state ? 1 : 0; 87 | return setsockopt(s, SOL_SOCKET, TCP_CORK, (char*) &onoff, sizeof(onoff) ); 88 | #endif 89 | } 90 | 91 | 92 | // ip has to be in the network byte order!!! 93 | int Sock::connect(uint32_t ipaddr, uint16_t port){ 94 | ip.sin_family = AF_INET; 95 | ip.sin_addr.s_addr = ipaddr; 96 | ip.sin_port = htons(port); 97 | 98 | if( ::connect(s, (sockaddr*)&ip, sizeof(ip) ) ){ 99 | return err("connecting to remote host via TCP: "); 100 | } 101 | return 0; 102 | } 103 | 104 | 105 | int Sock::connect(const char* addr, uint16_t port){ 106 | uint32_t ip = stringToIP(addr); 107 | return 0==ip ? -1 : connect(ip, port); 108 | } 109 | 110 | 111 | SOCKET Sock::accept(){ 112 | sockaddr_in ipr; // used for a server 113 | socklen_t size = sizeof(ipr); 114 | SOCKET serv = ::accept(s, (sockaddr*)&ipr, &size); 115 | if(INVALID_SOCKET == serv ){ 116 | err("accepting connection: "); 117 | } 118 | return serv; 119 | } 120 | 121 | 122 | SOCKET Sock::accept(Sock& conn){ 123 | socklen_t size = sizeof(conn.ip); 124 | conn.s = ::accept(s, (sockaddr*)&conn.ip, &size); 125 | if(INVALID_SOCKET == conn.s ){ 126 | err("accepting connection: "); 127 | } 128 | return conn.s; 129 | } 130 | 131 | 132 | SOCKET Sock::accept(Sock& conn, int timeoutSec){ 133 | fd_set set; 134 | FD_ZERO(&set); 135 | FD_SET(s, &set); 136 | 137 | struct timeval timeout; 138 | timeout.tv_sec = timeoutSec; 139 | timeout.tv_usec = 0; 140 | 141 | int ret = select( s+1, &set, NULL, NULL, &timeout); 142 | if (ret <= 0){ 143 | return INVALID_SOCKET; 144 | } 145 | 146 | socklen_t size = sizeof(conn.ip); 147 | conn.s = ::accept(s, (sockaddr*)&conn.ip, &size); 148 | if(INVALID_SOCKET == conn.s ){ 149 | err("accepting connection: "); 150 | } 151 | return conn.s; 152 | } 153 | 154 | 155 | int Sock::listen(uint16_t port){ 156 | int reuse = 1; // allow binding to a port if previous socket is lingering 157 | if ( 0 > setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&reuse, sizeof(reuse)) ){ 158 | err("in setsockopt(SO_REUSEADDR): "); 159 | return -2; 160 | } 161 | 162 | int size = sizeof(ip); 163 | ip.sin_family=AF_INET; 164 | ip.sin_port=htons(port); 165 | ip.sin_addr.s_addr = INADDR_ANY; 166 | 167 | if ( SOCKET_ERROR==::bind(s, (sockaddr*) &ip, size)){ 168 | err("binding to port "); 169 | return -3; 170 | } 171 | if ( SOCKET_ERROR==::getsockname(s, (sockaddr*) &ip, (socklen_t*)&size)){ 172 | err("getting bound socket information: "); 173 | return -4; 174 | } 175 | 176 | if( SOCKET_ERROR == ::listen(s, SOMAXCONN) ){ 177 | err("putting the socket into listening mode: "); 178 | return -5; 179 | } 180 | 181 | return 0; 182 | } 183 | 184 | 185 | Sock::Sock() { 186 | initNetwork(); 187 | memset(&ip,0,sizeof(ip)); 188 | s = socket(AF_INET, SOCK_STREAM, 0); 189 | if(s==INVALID_SOCKET){ 190 | err("creating socket: "); 191 | } 192 | } 193 | 194 | 195 | Sock::Sock(SOCKET socket): s(socket) { 196 | socklen_t size = sizeof(ip); 197 | if( SOCKET_ERROR == getpeername(socket, (sockaddr*)&ip, &size) ){ 198 | memset( &ip, 0, sizeof(ip) ); // if getpeername() fails, just clear ip 199 | } 200 | } 201 | 202 | 203 | Sock::~Sock(){ 204 | if(s != INVALID_SOCKET){ 205 | closeSock(); 206 | } 207 | } 208 | 209 | 210 | int Sock::closeSock(void){ 211 | shutdown(s, SHUT_RDWR); 212 | // if( shutdown(s, SHUT_RDWR) ){ 213 | // err("shutting down socket: "); 214 | // } 215 | if(::close(s)){ 216 | err("closing socket: "); 217 | } 218 | s=INVALID_SOCKET; // Connect checks it 219 | return 0; 220 | } 221 | 222 | 223 | int Sock::write(const void* buff, size_t size){ 224 | return send(s, (char*) buff, size, MSG_NOSIGNAL); 225 | } 226 | 227 | int Sock::read(void* buff, size_t size){ 228 | return recv(s, (char*) buff, size, 0); // return recv(s, buff, size, MSG_DONTWAIT); 229 | } 230 | 231 | 232 | int Sock::readLine(void* buff, size_t maxSize){ 233 | char* curr = (char*)buff; 234 | while( maxSize > (size_t) (curr-(char*)buff) ){ 235 | if( read(curr,1) <= 0 ){ 236 | if(curr == buff){ return -1; } 237 | break; 238 | } 239 | if( *curr==0 ) { break; } 240 | if( *curr=='\n'){ break; } 241 | if( *curr!='\r' ){ ++curr; } // skip \r only 242 | } 243 | *curr = 0; 244 | return (int) (curr-(char*)buff); 245 | } 246 | 247 | uint32_t Sock::read32(bool& error){ 248 | uint32_t data; 249 | int size = read(&data, sizeof(data)); 250 | error = (size != sizeof(data) ); 251 | return ntohl(data); 252 | } 253 | 254 | uint16_t Sock::read16(bool& error){ 255 | uint16_t data; 256 | int size = read(&data, sizeof(data) ); 257 | error = (size != sizeof(data) ); 258 | return ntohs(data); 259 | } 260 | 261 | int Sock::write32(uint32_t data){ 262 | data = htonl(data); 263 | return write( &data, sizeof(data) ); 264 | } 265 | 266 | int Sock::write16(uint16_t data){ 267 | data = htons(data); 268 | return write( &data, sizeof(data) ); 269 | } 270 | 271 | 272 | int Sock::writeString(const string& str){ 273 | constexpr static const int MAX_STR_LEN = 255; 274 | unsigned char iclen = (unsigned char) str.length(); 275 | if(str.length() > MAX_STR_LEN){ 276 | iclen = MAX_STR_LEN; 277 | err("WARNING: writeString() is truncating a string."); 278 | } 279 | if( 1 != write( &iclen, 1) ){ return -1; } 280 | return 1 + write( str.c_str(), iclen); 281 | } 282 | 283 | 284 | int Sock::readString(string& str){ 285 | unsigned char size; // since size is an unsigned char it can not be illegal 286 | int rdsize = read( &size, sizeof(size) ); 287 | if( 1!=rdsize ){ return -1; } // ERROR 288 | str.resize(size+1); // +1 for null char 289 | rdsize = read( &str[0], size); 290 | if( rdsize!=size ){ return -2; } // ERROR 291 | return size; 292 | } 293 | 294 | 295 | int Sock::readString(void* buff, size_t buffSize){ // make sure buff is at least 256 char long 296 | unsigned char size; // since size is an unsigned char it can not be illegal 297 | int rdsize = read( &size, sizeof(size) ); 298 | if( 1!=rdsize ){ return -1; } // ERROR 299 | int original = size; 300 | size = (unsigned char) (size < buffSize ? size : buffSize-1); 301 | rdsize = read( buff, size); 302 | if( rdsize!=size ){ return -2; } // ERROR 303 | ((char*)buff)[size] = 0; // null terminate the string 304 | 305 | if(original> size){ // if buffer is too small, read the rest from stream and discard 306 | err("WARNING: readString() buffer is too small."); 307 | char localBuff[256]; 308 | rdsize = read( localBuff, original-size); 309 | if( rdsize!=size ){ return -3; } // ERROR 310 | } 311 | return original; // return the number of bytes read from socket 312 | } 313 | 314 | 315 | // https://en.wikipedia.org/wiki/Private_network 316 | // is it a routable or a non-routable IP ? 317 | bool Sock::isRoutable(uint32_t ip){ // static 318 | unsigned char* ipc = (unsigned char*) &ip; 319 | if( 10 == ipc[0] ){ return false; } 320 | if( 192 == ipc[0] && 168 == ipc[1] ){ return false; } 321 | if( 172 == ipc[0] && ipc[1] >= 16 && ipc[1] <=31 ){ return false; } 322 | if( 127 == ipc[0] && ipc[1] == 0 && ipc[2] == 0 && ipc[3] == 1 ){ return false; } // localhost 323 | if( 169 == ipc[0] && ipc[1] == 254 ){ return false; } // APIPA 324 | // if( 224 <= ipc[0] && ipc[0] <= 239 ){ return false; } // multicast RFC 5771 325 | return true; 326 | } 327 | 328 | 329 | uint32_t Sock::stringToIP(const char* addr){ // static 330 | hostent* ipent=gethostbyname(addr); 331 | if(!ipent){ 332 | err ("Can't gethostbyname(): "); 333 | return 0; 334 | } 335 | uint32_t ip = *(uint32_t*) ipent->h_addr; // h_addr is a macro for h_addr_list[0] 336 | if(ip == INADDR_NONE){ 337 | err("gethostbyname() returned INADDR_NONE: "); 338 | return 0; 339 | } 340 | return ip; 341 | } 342 | 343 | 344 | // turn ip address into a string. ip has to be in network byte order 345 | string Sock::ipToString(uint32_t ip){ // static 346 | #ifdef _WIN32 347 | unsigned char* ipc = (unsigned char*) &ip; 348 | stringstream ss; 349 | ss << (int)ipc[0] << "." << (int)ipc[1] << "." << (int)ipc[2] << "." << (int)ipc[3]; 350 | return ss.str(); 351 | #else 352 | char buff[INET_ADDRSTRLEN]; 353 | if( NULL == inet_ntop(AF_INET, &ip, buff, sizeof(buff) ) ){ 354 | err("Error converting IP to string: "); 355 | return ""; 356 | } 357 | return string(buff); 358 | #endif 359 | } 360 | -------------------------------------------------------------------------------- /sock.h: -------------------------------------------------------------------------------- 1 | #ifndef SOCK_H_INCLUDED 2 | #define SOCK_H_INCLUDED 3 | 4 | #include 5 | #ifdef _WIN32 6 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 // gethostbyname() is deprecated in windows. TODO: use getaddrinfo()? 7 | #include 8 | // typedef SOCKADDR sockaddr; // needed on old windows 9 | // typedef SOCKADDR_IN sockaddr_in; 10 | #else // posix 11 | #include // sockaddr_in 12 | #define INVALID_SOCKET (-1) 13 | typedef int SOCKET; 14 | #endif 15 | 16 | // typedef uint32_t IPADDR; // but sockaddr_in::sin_addr.s_addr is defined as "unsigned long" 17 | // typedef uint16_t IPPORT; 18 | 19 | bool initNetwork(); // calls WSAStartup() on windows or ignores SIGPIPE on unix 20 | 21 | class Sock { // TCP socket 22 | SOCKET s; 23 | sockaddr_in ip; // ip.sin_port can be used to find the server port if listen(ANY_PORT) was used 24 | public: 25 | Sock(); 26 | Sock(SOCKET socket); // wrap an accepted connected socket in the Sock class 27 | ~Sock(); // calls closeSock() 28 | 29 | int connect(const char * ip, uint16_t port); // connect to remote server 30 | int connect(uint32_t ip, uint16_t port); // ip has to be in the network byte order??? 31 | int setRWtimeout(int seconds); // set read() and write() timeouts 32 | int closeSock(void); 33 | 34 | int read(void* buffer, size_t size); 35 | // read a line ending in \r\n from the socket 36 | int readLine(void* buffer, size_t size); 37 | // read a string encoded as 1 byte for length + characters (no null termination) 38 | int readString(void* buff, size_t size); 39 | int readString(std::string& str); 40 | uint16_t read16(bool& error); 41 | uint32_t read32(bool& error); 42 | 43 | int write32(uint32_t data); 44 | int write16(uint16_t data); 45 | int write(const void* buffer, size_t size); 46 | int writeString(const std::string& str); // encoded as 1 byte for length + data 47 | 48 | // server - start listening for a connection. Use ANY_PORT if not binding to a specific port 49 | constexpr const static uint16_t ANY_PORT = 0; 50 | int listen(uint16_t port); 51 | // accept an incoming connection on a listening server socket and return a client socket 52 | SOCKET accept(); 53 | SOCKET accept(Sock& connection); // recommended way of accepting connection 54 | SOCKET accept(Sock& conn, int timeoutSec); // if you want it to timeout (seconds) 55 | 56 | // bound server port even if ANY_PORT was used in listen() or remote port after accept() or connect() 57 | uint16_t getPort() const { return ntohs(ip.sin_port); } 58 | // remote IP after connect() or after object was returned by accept() 59 | uint32_t getIP() const { return ip.sin_addr.s_addr; } 60 | SOCKET getSocketFD() const { return s; } 61 | int setNoDelay(bool state); // set socket option TCP_NODELAY 62 | int setCork(bool state); // set socket option TCP_CORK 63 | 64 | static uint32_t stringToIP(const char* addr); 65 | static std::string ipToString(uint32_t ip); 66 | static bool isRoutable(uint32_t ip); 67 | }; 68 | 69 | #endif // SOCK_H_INCLUDED 70 | -------------------------------------------------------------------------------- /svc.h: -------------------------------------------------------------------------------- 1 | #ifndef INCLUDED_SVC_H 2 | #define INCLUDED_SVC_H 3 | // interface functions for registering (creating) a service 4 | // implementation is OS dependent 5 | // linux implementation is in svclin.cpp 6 | // windows implementation is in svcwin.cpp 7 | 8 | 9 | int registerService( void (*run) () ); // make this program run as a service 10 | bool keepRunning(); // if this returns false, exit all service threads 11 | 12 | 13 | #endif // INCLUDED_SVC_H -------------------------------------------------------------------------------- /svclin.cpp: -------------------------------------------------------------------------------- 1 | #ifndef _WIN32 2 | // This is a linux service implementation (see svcwin.cpp for windows version) 3 | #include "svc.h" 4 | #include "log.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | static bool keepServicesRunning = true; 11 | 12 | 13 | bool keepRunning(){ return keepServicesRunning; } 14 | 15 | 16 | void handleSigterm(int sigNum){ 17 | if(SIGTERM == sigNum){ 18 | keepServicesRunning = false; 19 | log() << "Received SIGTERM. Asking threads to exit." << std::endl; 20 | } else { 21 | logErr() << "Received unexpected signal " << sigNum << std::endl; 22 | } 23 | } 24 | 25 | 26 | int registerService( void (*run) () ){ 27 | signal(SIGTERM, &handleSigterm); 28 | 29 | if( daemon(1, 0) ){ // Do not change CWD. Close stdin/stdout/stderr 30 | int err = errno; 31 | logErr() << "daemon(true, false) failed with error code " << err << std::endl; 32 | return -1; 33 | } 34 | run(); 35 | return 0; 36 | } 37 | 38 | 39 | #endif // #ifndef _WIN32 40 | -------------------------------------------------------------------------------- /svcwin.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | // This is a windows service implementation (see svclin.cpp for linux version) 3 | #include "svc.h" 4 | #include "log.h" 5 | #include 6 | #include 7 | #include 8 | using namespace std; 9 | 10 | 11 | static SERVICE_STATUS serviceStatus; 12 | static SERVICE_STATUS_HANDLE serviceStatusHandle = 0; 13 | static char serviceName[] = "OutNet"; 14 | static bool keepServicesRunning = true; 15 | void (*serviceRun) () = nullptr; 16 | 17 | 18 | bool keepRunning(){ return keepServicesRunning; } 19 | 20 | 21 | void status(DWORD status){ 22 | serviceStatus.dwCurrentState = status; 23 | SetServiceStatus(serviceStatusHandle, & serviceStatus); 24 | } 25 | 26 | 27 | DWORD ServiceHandler( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext){ 28 | switch(dwControl){ 29 | case SERVICE_CONTROL_STOP: // fall through 30 | case SERVICE_CONTROL_SHUTDOWN: 31 | keepServicesRunning = false; 32 | status(SERVICE_STOP_PENDING); 33 | Sleep(3); // this_thread::sleep_for(seconds(3)); 34 | status(SERVICE_STOPPED); 35 | break; 36 | 37 | default: 38 | SetServiceStatus(serviceStatusHandle, &serviceStatus); 39 | break; 40 | } 41 | return NO_ERROR; 42 | } 43 | 44 | 45 | void WINAPI ServiceMain(DWORD argc, LPTSTR* argv){ 46 | char path[MAX_PATH+1]; // +1 for null termination 47 | DWORD ret = GetModuleFileName(NULL, path, sizeof(path) ); 48 | if(ret > 0){ 49 | char* slash = strrchr(path, '\\'); 50 | if(slash){ 51 | *slash = 0; // need the path to the exe 52 | SetCurrentDirectory(path); 53 | log() << "CWD=" << path << endl; 54 | } 55 | } else { 56 | logErr() << "GetModuleFileName() failed. Unable to change CWD." << endl; 57 | } 58 | 59 | serviceStatusHandle = RegisterServiceCtrlHandlerEx(serviceName, &ServiceHandler, NULL); 60 | status(SERVICE_RUNNING); 61 | serviceRun(); 62 | status(SERVICE_STOPPED); 63 | } 64 | 65 | 66 | int registerService( void (*run)() ){ 67 | serviceRun = run; 68 | 69 | serviceStatus = { 70 | SERVICE_WIN32_OWN_PROCESS, 71 | SERVICE_STOPPED, 72 | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN, 73 | NO_ERROR, 74 | 0, 75 | 0, 76 | 0, 77 | }; 78 | 79 | SERVICE_TABLE_ENTRY table[] = { {serviceName, &ServiceMain }, { NULL, NULL } }; 80 | if ( StartServiceCtrlDispatcher(table) ){ 81 | return 0; 82 | } 83 | 84 | DWORD err = GetLastError(); 85 | if (ERROR_SERVICE_ALREADY_RUNNING == err) { 86 | return -1; // caller needs to exit 87 | } 88 | if(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT == err){ 89 | logErr() << "This program is a service and should not be started from command line." << endl; 90 | logErr() << "Use any of the following commands to register, run, stop or delete the serivce:" << endl; 91 | logErr() << "sc create 'OutNet' binPath= c:\\path\\outnet.exe" << endl; 92 | logErr() << "sc [start/stop/delete] 'OutNet'" << endl; 93 | return -2; // program running as console app 94 | } 95 | if(ERROR_INVALID_DATA == err){ 96 | logErr() << "BUG in SERIVCE_TABLE_ENTRY" << endl; 97 | return -3; 98 | } 99 | return -4; // what happened ??? 100 | } 101 | 102 | 103 | #endif // #ifdef _WIN32 -------------------------------------------------------------------------------- /upnp/upnpnat.cpp: -------------------------------------------------------------------------------- 1 | // https://www.codeproject.com/Articles/27237/Easy-Port-Forwarding-and-Managing-Router-with-UPnP 2 | #ifdef _WIN32 3 | #define _CRT_SECURE_NO_WARNINGS // sprintf: This function may be unsafe 4 | #define _WINSOCK_DEPRECATED_NO_WARNINGS 1 // gethostbyname(), inet_addr() are deprecated in windows 5 | #include 6 | #include // socklen_t 7 | #else 8 | #include 9 | #include // inet_addr() 10 | #include // sockaddr_in 11 | #include // close(), usleep() 12 | #include 13 | #define ioctlsocket(s,o,v) ioctl(s,o,v) 14 | #define closesocket(s) close(s) 15 | #define Sleep(t) usleep((t)*1000) 16 | #define SOCKET_ERROR (-1) 17 | #endif 18 | 19 | #include // memset() 20 | #include 21 | #include 22 | 23 | #include "upnpnat.h" 24 | #include "xmlParser.h" 25 | #include "../log.h" 26 | 27 | #define MAX_BUFF_SIZE 102400 28 | 29 | static bool parseUrl(const char* url, std::string& host, unsigned short* port, std::string& path) 30 | { 31 | std::string str_url=url; 32 | 33 | std::string::size_type pos1,pos2,pos3; 34 | pos1=str_url.find("://"); 35 | if(pos1==std::string::npos) 36 | { 37 | return false; 38 | } 39 | pos1=pos1+3; 40 | 41 | pos2=str_url.find(":",pos1); 42 | if(pos2==std::string::npos) 43 | { 44 | *port=80; 45 | pos3=str_url.find("/",pos1); 46 | if(pos3==std::string::npos) 47 | { 48 | return false; 49 | } 50 | 51 | host=str_url.substr(pos1,pos3-pos1); 52 | } 53 | else 54 | { 55 | host=str_url.substr(pos1,pos2-pos1); 56 | pos3=str_url.find("/",pos1); 57 | if(pos3==std::string::npos) 58 | { 59 | return false; 60 | } 61 | 62 | std::string str_port=str_url.substr(pos2+1,pos3-pos2-1); 63 | *port=(unsigned short)atoi(str_port.c_str()); 64 | } 65 | 66 | if(pos3+1>=str_url.size()) 67 | { 68 | path="/"; 69 | } 70 | else 71 | { 72 | path=str_url.substr(pos3,str_url.size()); 73 | } 74 | 75 | return true; 76 | } 77 | 78 | 79 | // "ST: urn:schemas-upnp-org:device:InternetGatewayDevice:1, urn:schemas-upnp-org:device:InternetGatewayDevice:2\r\n" 80 | 81 | /****************************************************************** 82 | ** Discovery Defines * 83 | *******************************************************************/ 84 | #define HTTPMU_HOST_ADDRESS "239.255.255.250" 85 | #define HTTPMU_HOST_PORT 1900 86 | #define SEARCH_REQUEST_STRING "M-SEARCH * HTTP/1.1\r\n" \ 87 | "ST:upnp:rootdevice\r\n" \ 88 | "MX: 2\r\n" \ 89 | "MAN:\"ssdp:discover\"\r\n" \ 90 | "HOST: 239.255.255.250:1900\r\n" \ 91 | "USER-AGENT: upnpnat\r\n" \ 92 | "\r\n" 93 | #define HTTP_OK "200 OK" 94 | #define DEFAULT_HTTP_PORT 80 95 | 96 | 97 | /****************************************************************** 98 | ** Device and Service Defines * 99 | *******************************************************************/ 100 | 101 | #define DEVICE_TYPE_1 "urn:schemas-upnp-org:device:InternetGatewayDevice:1" 102 | #define DEVICE_TYPE_2 "urn:schemas-upnp-org:device:WANDevice:1" 103 | #define DEVICE_TYPE_3 "urn:schemas-upnp-org:device:WANConnectionDevice:1" 104 | 105 | #define SERVICE_WANIP "urn:schemas-upnp-org:service:WANIPConnection:1" 106 | #define SERVICE_WANPPP "urn:schemas-upnp-org:service:WANPPPConnection:1" 107 | 108 | 109 | /****************************************************************** 110 | ** Action Defines * 111 | *******************************************************************/ 112 | #define HTTP_HEADER_ACTION "POST %s HTTP/1.1\r\n" \ 113 | "HOST: %s:%u\r\n" \ 114 | "SOAPACTION:\"%s#%s\"\r\n" \ 115 | "CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"\ 116 | "Content-Length: %lu \r\n\r\n" 117 | 118 | #define SOAP_ACTION "\r\n" \ 119 | "\r\n" \ 123 | "\r\n" \ 124 | "\r\n%s" \ 125 | "\r\n" \ 126 | "\r\n" \ 127 | "\r\n" 128 | 129 | #define PORT_MAPPING_LEASE_TIME "0" // "63072000" // two years 130 | 131 | #define ADD_PORT_MAPPING_PARAMS "\r\n" \ 132 | "%u\r\n" \ 133 | "%s\r\n" \ 134 | "%u\r\n" \ 135 | "%s\r\n" \ 136 | "1\r\n" \ 137 | "%s\r\n" \ 138 | "" \ 139 | PORT_MAPPING_LEASE_TIME \ 140 | "\r\n" 141 | 142 | #define ACTION_ADD "AddPortMapping" 143 | //********************************************************************************* 144 | 145 | 146 | bool UPNPNAT::initNetwork() 147 | { 148 | #ifdef _WIN32 // linux does not need initialization 149 | WORD wVersionRequested = MAKEWORD (2, 2); 150 | WSADATA wsaData; 151 | int err = WSAStartup (wVersionRequested, &wsaData); 152 | if(err != 0){ 153 | logErr() << "ERROR: WSAStartup() failed" << std::endl; 154 | return false; 155 | } 156 | #else 157 | signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE signal which can occur in write() or send() 158 | #endif 159 | return true; 160 | } 161 | 162 | 163 | bool UPNPNAT::tcp_connect(int& sock, const char * host, unsigned short int port) 164 | { 165 | sock=socket(AF_INET,SOCK_STREAM,0); 166 | 167 | // set recv() timeout //1 TODO: get seconds from LocalData::timeoutUPNP 168 | #ifdef _WIN32 169 | DWORD tv = 5*1000; // milliseconds // fucking MSFT 170 | #else 171 | timeval tv = {5,0}; // 5 seconds // tv.tv_sec = 5; tv.tv_usec = 0; 172 | #endif 173 | if ( setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv, sizeof(tv) ) ) { // rcve() timeout 174 | closesocket(sock); 175 | last_error = "Error setting read timeout on socket"; 176 | return false; 177 | } 178 | 179 | struct sockaddr_in r_address; 180 | r_address.sin_family = AF_INET; 181 | r_address.sin_port=htons(port); 182 | r_address.sin_addr.s_addr=inet_addr(host); 183 | 184 | int ret = connect(sock, (const struct sockaddr *)&r_address, sizeof(struct sockaddr_in) ); 185 | if(ret!=0){ 186 | closesocket(sock); 187 | char temp[100]; 188 | sprintf(temp, "Failed to connect to %s:%i\n", host, port); 189 | last_error=temp; 190 | return false; 191 | } 192 | 193 | return true; 194 | } 195 | 196 | 197 | bool UPNPNAT::discovery(int retry) 198 | { 199 | int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 200 | 201 | struct sockaddr_in r_address; 202 | r_address.sin_family=AF_INET; 203 | r_address.sin_port=htons(HTTPMU_HOST_PORT); 204 | r_address.sin_addr.s_addr=inet_addr(HTTPMU_HOST_ADDRESS); 205 | 206 | bool bOptVal = true; 207 | int bOptLen = sizeof(bool); 208 | int ret=setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOptVal, bOptLen); 209 | 210 | u_long val = 1; 211 | ioctlsocket (udp_socket, FIONBIO, &val); // non block 212 | 213 | std::string send_buff=SEARCH_REQUEST_STRING; 214 | std::string recv_buff; 215 | char buff[MAX_BUFF_SIZE+1]; //buff should be enough big 216 | 217 | for(int i=1; i<=retry; ++i) 218 | { 219 | if(i>1){ Sleep(10*1000); } // starting as a daemon. wait for the network to come up. 220 | ret=sendto(udp_socket, send_buff.c_str(), send_buff.size(), 0, (struct sockaddr*)&r_address, sizeof(struct sockaddr_in)); 221 | Sleep(1000); 222 | 223 | ret=recvfrom(udp_socket,buff,MAX_BUFF_SIZE,0,NULL,NULL); 224 | if(ret==SOCKET_ERROR){ 225 | continue; 226 | } 227 | buff[ret] = 0; // null terminate the buffer strings 228 | 229 | recv_buff=buff; 230 | size_t pos=recv_buff.find(HTTP_OK); 231 | if(pos==std::string::npos){ 232 | continue; //invalid response 233 | } 234 | 235 | std::string::size_type begin=recv_buff.find("http://"); 236 | if(begin==std::string::npos){ 237 | continue; //invalid response 238 | } 239 | 240 | std::string::size_type end=recv_buff.find("\r",begin); 241 | if(end==std::string::npos){ 242 | continue; //invalid response 243 | } 244 | 245 | describe_url.assign(recv_buff,begin,end-begin); 246 | 247 | if(!get_description()){ 248 | continue; 249 | } 250 | if(!parse_description()){ 251 | continue; 252 | } 253 | 254 | log() << "Got data from Describe URL: " << describe_url.c_str() << std::endl; 255 | log() << "Control URL: " << this->control_url.c_str() << std::endl; 256 | log() << "Service Type: " << this->service_type.c_str() << std::endl; 257 | // log() << "Base URL: " << this->base_url.c_str() << std::endl; 258 | // log() << "Service URL: " << this->service_describe_url.c_str() << std::endl; 259 | // log() << "Description Info: " << this->description_info.c_str() << std::endl; 260 | 261 | closesocket(udp_socket); 262 | return true ; 263 | } 264 | 265 | last_error="Fail to find an UPNP NAT.\n"; 266 | closesocket(udp_socket); 267 | return false; //no router finded 268 | } 269 | 270 | 271 | bool UPNPNAT::get_description() 272 | { // uses: describe_url, sets: description_info 273 | std::string host,path; 274 | unsigned short int port; 275 | int ret=parseUrl(describe_url.c_str(),host,&port,path); 276 | if(!ret) 277 | { 278 | last_error="Failed to parseURl: "+describe_url; 279 | return false; 280 | } 281 | 282 | int sock; 283 | ret = tcp_connect(sock, host.c_str(),port); 284 | if(!ret){ 285 | return false; // last_error is set in tcp_connect() 286 | } 287 | 288 | char request[200]; 289 | sprintf (request,"GET %s HTTP/1.1\r\nHost: %s:%d\r\n\r\n",path.c_str(),host.c_str(),port); 290 | ret=send(sock, request, strlen(request), 0); 291 | 292 | //get description xml file 293 | char buff[MAX_BUFF_SIZE+1]; 294 | int maxSize = MAX_BUFF_SIZE; 295 | char* buffPtr = buff; 296 | while ( (ret=recv(sock, buffPtr, maxSize, 0)) > 0 ) 297 | { 298 | buffPtr+=ret; 299 | maxSize-=ret; 300 | } 301 | *buffPtr = 0; 302 | closesocket(sock); 303 | 304 | char* xml = strstr(buff,"service_type=serviceType; 439 | 440 | XMLNode controlURL_node=service_node.getChildNode("controlURL"); 441 | control_url=controlURL_node.getText(); 442 | 443 | //make the complete control_url; 444 | if(control_url.find("http://")==std::string::npos&&control_url.find("HTTP://")==std::string::npos) 445 | control_url=base_url+control_url; 446 | if(service_describe_url.find("http://")==std::string::npos&&service_describe_url.find("HTTP://")==std::string::npos) 447 | service_describe_url=base_url+service_describe_url; 448 | 449 | return true; 450 | } 451 | 452 | 453 | bool UPNPNAT::add_port_mapping(const char * _description, const char * _destination_ip, unsigned short int _port_ex, unsigned short int _port_in, const char * _protocol) 454 | { 455 | int ret; 456 | 457 | std::string host,path; 458 | unsigned short int port; 459 | ret=parseUrl(control_url.c_str(),host,&port,path); 460 | if(!ret) 461 | { 462 | last_error="Fail to parseUrl: "+control_url+"\n"; 463 | return false; 464 | } 465 | 466 | int sock; 467 | ret=tcp_connect(sock, host.c_str(),port); 468 | if(!ret){ 469 | return false; // last_error is set in tcp_connect() 470 | } 471 | 472 | char buff[MAX_BUFF_SIZE+1]; 473 | sprintf(buff,ADD_PORT_MAPPING_PARAMS,_port_ex,_protocol,_port_in,_destination_ip,_description); 474 | std::string action_params=buff; 475 | 476 | sprintf(buff,SOAP_ACTION,ACTION_ADD,service_type.c_str(),action_params.c_str(),ACTION_ADD); 477 | std::string soap_message=buff; 478 | 479 | long unsigned int ss = soap_message.size(); 480 | sprintf(buff,HTTP_HEADER_ACTION,path.c_str(),host.c_str(),port,service_type.c_str(),ACTION_ADD, ss); 481 | std::string action_message=buff; 482 | 483 | std::string http_request=action_message+soap_message; 484 | 485 | //printf("\nSENDING:\n"); 486 | //printf(http_request.c_str()); 487 | 488 | //send request 489 | ret=send(sock, http_request.c_str(), http_request.size(),0); 490 | 491 | //wait for response 492 | std::string response; 493 | while ( (ret=recv(sock,buff,MAX_BUFF_SIZE,0)) > 0 ) 494 | { 495 | buff[ret] = 0; 496 | response+=buff; 497 | } 498 | closesocket(sock); 499 | 500 | //printf("\nRESPONSE:\n"); 501 | //printf(response.c_str()); 502 | 503 | if(response.find(HTTP_OK)!=std::string::npos){ 504 | return true; 505 | } 506 | 507 | char temp[100]; 508 | sprintf(temp,"Failed to add port mapping (%s/%s)\n",_description,_protocol); 509 | last_error=temp; 510 | last_error += response; 511 | return false; 512 | } 513 | 514 | 515 | bool UPNPNAT::getExternalIP(std::string& IpOut, uint32_t& localIP){ 516 | #define EXTERNAL_IP_ACTION "GetExternalIPAddress" 517 | #define EXTERNAL_IP_PARAMS " " // "\r\n" 518 | 519 | int ret; 520 | 521 | std::string host,path; 522 | unsigned short int port; 523 | ret=parseUrl(control_url.c_str(),host,&port,path); 524 | if(!ret) 525 | { 526 | last_error="Fail to parseUrl: "+control_url; 527 | return false; 528 | } 529 | 530 | int sock; 531 | ret=tcp_connect(sock, host.c_str(),port); 532 | if(!ret){ 533 | return false; // last_error is set in tcp_connect() 534 | } 535 | 536 | // get local IP 537 | localIP = 0; 538 | sockaddr_in addr; 539 | socklen_t addrSize = sizeof(addr); 540 | if( 0==getsockname(sock, (sockaddr*) &addr, &addrSize ) ){ 541 | localIP = addr.sin_addr.s_addr; // in network byte order 542 | } 543 | 544 | char buff[MAX_BUFF_SIZE+1]; 545 | sprintf(buff, SOAP_ACTION, EXTERNAL_IP_ACTION, service_type.c_str(), "", EXTERNAL_IP_ACTION); 546 | std::string soap_message=buff; 547 | 548 | long unsigned int ss = soap_message.size(); 549 | sprintf(buff,HTTP_HEADER_ACTION,path.c_str(),host.c_str(),port,service_type.c_str(),EXTERNAL_IP_ACTION, ss); 550 | std::string action_message=buff; 551 | 552 | std::string http_request=action_message+soap_message; 553 | 554 | //printf("\nSENDING:\n"); 555 | //printf(http_request.c_str()); 556 | 557 | //send request 558 | ret=send(sock, http_request.c_str(), http_request.size(),0); 559 | 560 | //wait for response 561 | std::string response; 562 | while ( (ret=recv(sock,buff,MAX_BUFF_SIZE,0)) > 0 ) 563 | { 564 | buff[ret] = 0; 565 | response+=buff; 566 | } 567 | closesocket(sock); 568 | 569 | //printf("\nRESPONSE:\n"); 570 | //printf(response.c_str()); 571 | 572 | if(response.find(HTTP_OK)!=std::string::npos){ 573 | long unsigned int index = response.find(" 5 | 6 | #ifdef _MSC_VER // visual studio only 7 | #pragma warning(disable: 4251) 8 | #endif 9 | 10 | class UPNPNAT 11 | { 12 | public: 13 | bool initNetwork(); 14 | bool discovery(int retry = 3); // find router 15 | std::string& get_last_error(){ return last_error; } 16 | 17 | // description: port mapping name 18 | // destination_ip: internal ip address 19 | // port_ex: external listening port 20 | // destination_port: internal port 21 | // protocol: TCP or UDP 22 | bool add_port_mapping(const char * description, const char * destination_ip, unsigned short int port_ex, unsigned short int destination_port, const char * protocol); 23 | bool getExternalIP(std::string& externalIPout, uint32_t& localIPout); 24 | private: 25 | bool get_description(); 26 | bool parse_description(); 27 | bool parse_mapping_info(); 28 | bool tcp_connect(int& sock, const char * addr,unsigned short int port); 29 | std::string service_type; 30 | std::string describe_url; 31 | std::string control_url; 32 | std::string base_url; 33 | std::string service_describe_url; 34 | std::string description_info; 35 | std::string last_error; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | #include "log.h" 3 | #include 4 | #include 5 | #include // std::hex, std::dec 6 | #include 7 | #include 8 | using namespace std; 9 | using namespace std::filesystem; // directory_iterator() 10 | 11 | 12 | void turnBitsOff(uint32_t& mask, uint32_t bits){ 13 | mask = mask & (0xFFFFFFFF^bits); // TODO: return a new mask instead of taking a reference??? 14 | } 15 | 16 | 17 | // parse a string into tokens 18 | bool tokenize( char*& buffer, const char* bufferEnd, char*& token, const string& separators ){ 19 | while(buffer != bufferEnd){ // skip leading separators 20 | char c = *buffer; 21 | if( 0==c || '\r'==c || '\n'==c ) { // end of line - no more tokens 22 | return false; 23 | } 24 | if( string::npos != separators.find(c) ) { // skip separators 25 | ++buffer; 26 | } else { 27 | break; 28 | } 29 | } 30 | 31 | token = buffer; 32 | 33 | while(buffer != bufferEnd){ 34 | char c = *buffer; 35 | if( string::npos != separators.find(c) || '\r'==c || '\n'==c ) { // end of token 36 | *buffer=0; // separate strings 37 | ++buffer; 38 | return true; 39 | } 40 | ++buffer; // skip to the end of token 41 | } 42 | 43 | return buffer>token; // at last one char in the token 44 | } 45 | 46 | 47 | // toLower() is a complete hack as it modifies the string through iterators 48 | string& toLower(string& s){ // convert string to lower case in place 49 | std::transform(begin(s), end(s), begin(s), [](char c){ return std::tolower(c); } ); 50 | return s; 51 | } 52 | 53 | 54 | string& toUpper(string& s){ // convert string to lower case in place 55 | std::transform(begin(s), end(s), begin(s), [](char c){ return std::toupper(c); } ); 56 | return s; 57 | } 58 | 59 | 60 | string& ltrim(string& s){ // trim white spaces on the left 61 | auto ends = find_if(begin(s), end(s), [](char ch){ return !isspace(ch); } ); 62 | s.erase(begin(s), ends ); 63 | return s; 64 | } 65 | 66 | 67 | string& rtrim(string& s){ // trim white spaces on the right 68 | auto start = find_if(rbegin(s), rend(s), [](char ch){ return !isspace(ch); } ); 69 | s.erase( start.base(), end(s) ); 70 | return s; 71 | } 72 | 73 | 74 | void parseLines(istream& stream, vector& lines){ 75 | string line; 76 | while( getline(stream, line) ){ 77 | auto comment = find( begin(line), end(line), '#' ); 78 | if( comment != end(line) ){ 79 | line.erase(comment, end(line)); // line = line.substr(0,comment-begin(line)); 80 | } 81 | rtrim(ltrim(line)); 82 | if(line.length() > 0){ 83 | lines.push_back( move(line) ); 84 | } 85 | } 86 | } 87 | 88 | 89 | int parseFilesIntoLines(const string& extension, vector& lines){ 90 | // ~path() destructor crashes on some systems // path cwd = current_path(); 91 | int fCount = 0; 92 | for(auto& p: directory_iterator(".") ){ 93 | if(p.path().extension() != extension){ continue; } 94 | // log() << p.path() << " "; 95 | ifstream listf (p.path()); 96 | if( !listf ){ continue; } 97 | parseLines(listf, lines); 98 | ++fCount; 99 | } 100 | return fCount; 101 | } 102 | 103 | 104 | // Determine if str is a key=value pair and parse it into key and value parameters. 105 | // Return true if str is a key=value pair. 106 | bool keyValue(const string& str, string& key, string& value){ 107 | auto eq = str.find('='); 108 | if(string::npos == eq){ return false; } 109 | key = str.substr(0,eq); 110 | rtrim(key); 111 | value = str.substr(eq+1); 112 | ltrim(value); 113 | return true; 114 | } 115 | 116 | 117 | // print buffer using hex digits 4 + space 118 | void printHex(ostream& os, const unsigned char* buff, int len){ 119 | for(int i = 0; i< len; ++i){ 120 | os << std::setw(2) << std::setfill('0') << std::hex << (int) buff[i] << ( 1==i%2 ? " ": ""); 121 | } 122 | os << std::setw(0) << std::setfill(' ') << std::dec; // reset back to decimal 123 | } 124 | 125 | 126 | void printAscii(ostream& os, const unsigned char* buff, int len){ 127 | for(int i = 0; i< len; ++i){ 128 | unsigned char val = buff[i]; 129 | val = ( val >= 32 && val< 127 ) ? val : '.'; 130 | os << val; 131 | } 132 | os << endl; 133 | } 134 | 135 | 136 | int writeString(ofstream& file, const string& str){ 137 | unsigned char size = (unsigned char) str.length(); 138 | file.write( (char*) &size, sizeof(size)); 139 | file.write(str.c_str(), size); 140 | return size+1; 141 | } 142 | 143 | 144 | string readString(ifstream& file){ 145 | unsigned char size; // since size is an unsigned char it can not be illegal 146 | file.read( (char*) &size, sizeof(size) ); 147 | if( !file ){ return ""; } // ERROR 148 | string str((int)size,' '); 149 | file.read( &str[0], size); 150 | if( !file ){ return ""; } // ERROR 151 | return str; 152 | } 153 | 154 | 155 | #include 156 | using namespace std::chrono; 157 | int32_t timeMinutes(){ 158 | static auto epoch = system_clock::from_time_t(0); 159 | return duration_cast(system_clock::now() - epoch).count(); 160 | } 161 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H_INCLUDED 2 | #define UTILS_H_INCLUDED 3 | #include 4 | #include 5 | // collection of utility functions 6 | // It is sad that there are no standard versions of these functions in std:: 7 | // Probably why python is more popular :( 8 | 9 | void turnBitsOff(uint32_t& mask, uint32_t bits); // turn bits off in a mask 10 | 11 | int32_t timeMinutes(); // get time in minutes since Jan 1, 1970 12 | 13 | // break string into tokens 14 | bool tokenize( char*& buffer, const char* bufferEnd, char*& token, const std::string& separators ); 15 | 16 | std::string& toLower(std::string& s); // convert string to lower case in place 17 | 18 | std::string& toUpper(std::string& s); // convert string to upper case in place 19 | 20 | std::string& ltrim(std::string& s); // trim white spaces on the left in place 21 | 22 | std::string& rtrim(std::string& s); // trim white spaces on the right in place 23 | 24 | // read lines from a stream and put them in a vector 25 | void parseLines(std::istream& stream, std::vector& lines); 26 | 27 | // run parseLines() on all files with specific extension 28 | int parseFilesIntoLines(const std::string& extension, std::vector& lines); 29 | 30 | // Determine if str is a key=value pair and parse it into key and value parameters. 31 | // Return true if str is a key=value pair. 32 | bool keyValue(const std::string& str, std::string& key, std::string& value); 33 | 34 | void printHex(std::ostream& os, const unsigned char* buff, int len); // print buffer using hex digits (4 + space) 35 | void printAscii(std::ostream& os, const unsigned char* buff, int len); // ascii printable chars or "." 36 | 37 | int writeString(std::ofstream& file, const std::string& str); // write string to file. format: size+string 38 | std::string readString(std::ifstream& file); // read string from file written by writeString() 39 | 40 | 41 | #endif // UTILS_H_INCLUDED 42 | --------------------------------------------------------------------------------