From ae138566a94ed407c71dbc7a155c810b2389cc4b Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 1 Nov 2013 12:38:38 -0400 Subject: Updater code, work in progress... --- node/Utils.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'node/Utils.cpp') diff --git a/node/Utils.cpp b/node/Utils.cpp index c565d8c4..66bd27dd 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -265,6 +265,16 @@ uint64_t Utils::getLastModified(const char *path) return (((uint64_t)s.st_mtime) * 1000ULL); } +static int64_t getFileSize(const char *path) +{ + struct stat s; + if (stat(path,&s)) + return -1; + if (S_ISREG(s.st_mode)) + return s.st_size; + return -1; +} + std::string Utils::toRfc1123(uint64_t t64) { struct tm t; -- cgit v1.2.3 From 6c63bfce69f0b0087526879f49d36071ddc4b9d9 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Mon, 4 Nov 2013 17:31:00 -0500 Subject: File transfer work, add identities for validation of updates. --- node/Defaults.cpp | 27 ++++++- node/Defaults.hpp | 6 ++ node/Packet.hpp | 12 ++- node/Updater.cpp | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++--- node/Updater.hpp | 138 ++++++++++++++++++++-------------- node/Utils.cpp | 2 +- 6 files changed, 329 insertions(+), 72 deletions(-) (limited to 'node/Utils.cpp') diff --git a/node/Defaults.cpp b/node/Defaults.cpp index 35a677f2..cfc901b5 100644 --- a/node/Defaults.cpp +++ b/node/Defaults.cpp @@ -98,12 +98,37 @@ static inline std::string _mkDefaultHomePath() #endif } +static inline std::map< Address,Identity > _mkUpdateAuth() +{ + std::map< Address,Identity > ua; + + { // 0001 + Identity id("e9bc3707b5:0:c4cef17bde99eadf9748c4fd11b9b06dc5cd8eb429227811d2c336e6b96a8d329e8abd0a4f45e47fe1bcebf878c004c822d952ff77fc2833af4c74e65985c435"); + ua[id.address()] = id; + } + { // 0002 + Identity id("56520eaf93:0:7d858b47988b34399a9a31136de07b46104d7edb4a98fa1d6da3e583d3a33e48be531532b886f0b12cd16794a66ab9220749ec5112cbe96296b18fe0cc79ca05"); + ua[id.address()] = id; + } + { // 0003 + Identity id("7c195de2e0:0:9f659071c960f9b0f0b96f9f9ecdaa27c7295feed9c79b7db6eedcc11feb705e6dd85c70fa21655204d24c897865b99eb946b753a2bbcf2be5f5e006ae618c54"); + ua[id.address()] = id; + } + { // 0004 + Identity id("415f4cfde7:0:54118e87777b0ea5d922c10b337c4f4bd1db7141845bd54004b3255551a6e356ba6b9e1e85357dbfafc45630b8faa2ebf992f31479e9005f0472685f2d8cbd6e"); + ua[id.address()] = id; + } + + return ua; +} + Defaults::Defaults() : #ifdef ZT_TRACE_MULTICAST multicastTraceWatcher(ZT_TRACE_MULTICAST), #endif defaultHomePath(_mkDefaultHomePath()), - supernodes(_mkSupernodeMap()) + supernodes(_mkSupernodeMap()), + updateAuthorities(_mkUpdateAuth()) { } diff --git a/node/Defaults.hpp b/node/Defaults.hpp index b0eb40e5..dac59ae6 100644 --- a/node/Defaults.hpp +++ b/node/Defaults.hpp @@ -70,6 +70,12 @@ public: /** * Identities permitted to sign software updates + * + * ZTN can keep multiple signing identities and rotate them, keeping some in + * "cold storage" and obsoleting others gradually. + * + * If you don't build with ZT_OFFICIAL_BUILD, this isn't used since your + * build will not auto-update. */ const std::map< Address,Identity > updateAuthorities; }; diff --git a/node/Packet.hpp b/node/Packet.hpp index 05c6f3a4..d476e89e 100644 --- a/node/Packet.hpp +++ b/node/Packet.hpp @@ -619,12 +619,12 @@ public: /* Request information about a shared file (for software updates): * <[1] flags, currently unused and must be 0> - * <[2] 16-bit length of filename> + * <[1] 8-bit length of filename> * <[...] name of file being requested> * * OK response payload (indicates that we have and will share): * <[1] flags, currently unused and must be 0> - * <[2] 16-bit length of filename> + * <[1] 8-bit length of filename> * <[...] name of file being requested> * <[64] full length SHA-512 hash of file contents> * <[4] 32-bit length of file in bytes> @@ -636,6 +636,10 @@ public: * <[2] 16-bit length of filename> * <[...] name of file being requested> * + * This is used for distribution of software updates and in the future may + * be used for anything else that needs to be globally distributed. It + * is not designed for end-user use for other purposes. + * * Support is optional. Nodes should return UNSUPPORTED_OPERATION if * not supported or enabled. */ @@ -657,6 +661,10 @@ public: * <[4] 32-bit index of desired chunk> * <[2] 16-bit length of desired chunk> * + * This is used for distribution of software updates and in the future may + * be used for anything else that needs to be globally distributed. It + * is not designed for end-user use for other purposes. + * * Support is optional. Nodes should return UNSUPPORTED_OPERATION if * not supported or enabled. */ diff --git a/node/Updater.cpp b/node/Updater.cpp index 10ac6096..1eefa7a4 100644 --- a/node/Updater.cpp +++ b/node/Updater.cpp @@ -31,6 +31,8 @@ #include "Defaults.hpp" #include "Utils.hpp" #include "Topology.hpp" +#include "Switch.hpp" +#include "SHA512.hpp" #include "../version.h" @@ -69,8 +71,9 @@ void Updater::refreshShared() if (Utils::readFile(nfoPath.c_str(),buf)) { Dictionary nfo(buf); - _Shared shared; - shared.filename = fullPath; + SharedUpdate shared; + shared.fullPath = fullPath; + shared.filename = u->first; std::string sha512(Utils::unhex(nfo.get("sha512",std::string()))); if (sha512.length() < sizeof(shared.sha512)) { @@ -104,9 +107,7 @@ void Updater::refreshShared() } shared.size = (unsigned long)fs; - Array first16Bytes; - memcpy(first16Bytes.data,sha512.data(),16); - _sharedUpdates[first16Bytes] = shared; + _sharedUpdates.push_back(shared); } else { TRACE("skipped shareable update due to missing companion .nfo: %s",fullPath.c_str()); continue; @@ -127,9 +128,9 @@ void Updater::getUpdateIfThisIsNewer(unsigned int vMajor,unsigned int vMinor,uns } } - std::string updateFilename(generateUpdateFilename()); + std::string updateFilename(generateUpdateFilename(vMajor,vMinor,revision)); if (!updateFilename.length()) { - TRACE("a new update to %u.%u.%u is available, but this platform doesn't support auto updates",vMajor,vMinor,revision); + TRACE("an update to %u.%u.%u is available, but this platform or build doesn't support auto-update",vMajor,vMinor,revision); return; } @@ -138,11 +139,8 @@ void Updater::getUpdateIfThisIsNewer(unsigned int vMajor,unsigned int vMinor,uns TRACE("new update available to %u.%u.%u, looking for %s from %u peers",vMajor,vMinor,revision,updateFilename.c_str(),(unsigned int)peers.size()); - if (!peers.size()) - return; - for(std::vector< SharedPtr >::iterator p(peers.begin());p!=peers.end();++p) { - Packet outp(p->address(),_r->identity.address(),Packet::VERB_FILE_INFO_REQUEST); + Packet outp((*p)->address(),_r->identity.address(),Packet::VERB_FILE_INFO_REQUEST); outp.append((unsigned char)0); outp.append((uint16_t)updateFilename.length()); outp.append(updateFilename.data(),updateFilename.length()); @@ -152,14 +150,167 @@ void Updater::getUpdateIfThisIsNewer(unsigned int vMajor,unsigned int vMinor,uns void Updater::retryIfNeeded() { + Mutex::Lock _l(_lock); + + if (_download) { + uint64_t elapsed = Utils::now() - _download->lastChunkReceivedAt; + if ((elapsed >= ZT_UPDATER_PEER_TIMEOUT)||(!_download->currentlyReceivingFrom)) { + if (_download->peersThatHave.empty()) { + // Search for more sources if we have no more possibilities queued + _download->currentlyReceivingFrom.zero(); + + std::vector< SharedPtr > peers; + _r->topology->eachPeer(Topology::CollectPeersWithActiveDirectPath(peers,Utils::now())); + + for(std::vector< SharedPtr >::iterator p(peers.begin());p!=peers.end();++p) { + Packet outp((*p)->address(),_r->identity.address(),Packet::VERB_FILE_INFO_REQUEST); + outp.append((unsigned char)0); + outp.append((uint16_t)_download->filename.length()); + outp.append(_download->filename.data(),_download->filename.length()); + _r->sw->send(outp,true); + } + } else { + // If that peer isn't answering, try the next queued source + _download->currentlyReceivingFrom = _download->peersThatHave.front(); + _download->peersThatHave.pop_front(); + } + } else if (elapsed >= ZT_UPDATER_RETRY_TIMEOUT) { + // Re-request next chunk we don't have from current source + _requestNextChunk(); + } + } } -void Updater::handleChunk(const void *sha512First16,unsigned long at,const void *chunk,unsigned long len) +void Updater::handleChunk(const Address &from,const void *sha512,unsigned int shalen,unsigned long at,const void *chunk,unsigned long len) { + Mutex::Lock _l(_lock); + + if (!_download) { + TRACE("got chunk from %s while no download is in progress, ignored",from.toString().c_str()); + return; + } + + if (memcmp(_download->sha512,sha512,(shalen > 64) ? 64 : shalen)) { + TRACE("got chunk from %s for wrong download (SHA mismatch), ignored",from.toString().c_str()); + return; + } + + unsigned long whichChunk = at / ZT_UPDATER_CHUNK_SIZE; + + if (at != (ZT_UPDATER_CHUNK_SIZE * whichChunk)) + return; // not at chunk boundary + if (whichChunk >= _download->haveChunks.size()) + return; // overflow + if ((whichChunk == (_download->haveChunks.size() - 1))&&(len != _download->lastChunkSize)) + return; // last chunk, size wrong + else if (len != ZT_UPDATER_CHUNK_SIZE) + return; // chunk size wrong + + for(unsigned long i=0;idata[at + i] = ((const char *)chunk)[i]; + + _download->haveChunks[whichChunk] = true; + _download->lastChunkReceivedAt = Utils::now(); + + _requestNextChunk(); +} + +void Updater::handleAvailable(const Address &from,const char *filename,const void *sha512,unsigned long filesize,const Address &signedBy,const void *signature,unsigned int siglen) +{ + unsigned int vMajor = 0,vMinor = 0,revision = 0; + if (!parseUpdateFilename(filename,vMajor,vMinor,revision)) { + TRACE("rejected offer of %s from %s: could not parse version information",filename,from.toString().c_str()); + return; + } + + if (filesize > ZT_UPDATER_MAX_SUPPORTED_SIZE) { + TRACE("rejected offer of %s from %s: file too large (%u)",filename,from.toString().c_str(),(unsigned int)filesize); + return; + } + + if (vMajor < ZEROTIER_ONE_VERSION_MAJOR) + return; + else if (vMajor == ZEROTIER_ONE_VERSION_MAJOR) { + if (vMinor < ZEROTIER_ONE_VERSION_MINOR) + return; + else if (vMinor == ZEROTIER_ONE_VERSION_MINOR) { + if (revision <= ZEROTIER_ONE_VERSION_REVISION) + return; + } + } + + Mutex::Lock _l(_lock); + + if (_download) { + // If a download is in progress, only accept this as another source if + // it matches the size, hash, and version. Also check if this is a newer + // version and if so replace download with this. + } else { + // If there is no download in progress, create one provided the signature + // for the SHA-512 hash verifies as being from a valid signer. + } +} + +bool Updater::findSharedUpdate(const char *filename,SharedUpdate &update) const +{ + Mutex::Lock _l(_lock); + for(std::list::const_iterator u(_sharedUpdates.begin());u!=_sharedUpdates.end();++u) { + if (u->filename == filename) { + update = *u; + return true; + } + } + return false; +} + +bool Updater::findSharedUpdate(const void *sha512,unsigned int shalen,SharedUpdate &update) const +{ + if (!shalen) + return false; + Mutex::Lock _l(_lock); + for(std::list::const_iterator u(_sharedUpdates.begin());u!=_sharedUpdates.end();++u) { + if (!memcmp(u->sha512,sha512,(shalen > 64) ? 64 : shalen)) { + update = *u; + return true; + } + } + return false; +} + +bool Updater::getSharedChunk(const void *sha512,unsigned int shalen,unsigned long at,void *chunk,unsigned long chunklen) const +{ + if (!chunklen) + return true; + if (!shalen) + return false; + Mutex::Lock _l(_lock); + for(std::list::const_iterator u(_sharedUpdates.begin());u!=_sharedUpdates.end();++u) { + if (!memcmp(u->sha512,sha512,(shalen > 64) ? 64 : shalen)) { + FILE *f = fopen(u->fullPath.c_str(),"rb"); + if (!f) + return false; + if (!fseek(f,(long)at,SEEK_SET)) { + fclose(f); + return false; + } + if (fread(chunk,chunklen,1,f) != 1) { + fclose(f); + return false; + } + fclose(f); + return true; + } + } + return false; } std::string Updater::generateUpdateFilename(unsigned int vMajor,unsigned int vMinor,unsigned int revision) { + // Defining ZT_OFFICIAL_BUILD enables this cascade of macros, which will + // make your build auto-update itself if it's for an officially supported + // architecture. The signing identity for auto-updates is in Defaults. +#ifdef ZT_OFFICIAL_BUILD + // Not supported... yet? Get it first cause it might identify as Linux too. #ifdef __ANDROID__ #define _updSupported 1 @@ -202,6 +353,10 @@ std::string Updater::generateUpdateFilename(unsigned int vMajor,unsigned int vMi #ifndef _updSupported return std::string(); #endif + +#else + return std::string(); +#endif // ZT_OFFICIAL_BUILD } bool Updater::parseUpdateFilename(const char *filename,unsigned int &vMajor,unsigned int &vMinor,unsigned int &revision) @@ -218,5 +373,42 @@ bool Updater::parseUpdateFilename(const char *filename,unsigned int &vMajor,unsi return true; } +void Updater::_requestNextChunk() +{ + // assumes _lock is locked + + if (!_download) + return; + + unsigned long whichChunk = 0; + std::vector::iterator ptr(std::find(_download->haveChunks.begin(),_download->haveChunks.end(),false)); + if (ptr == _download->haveChunks.end()) { + unsigned char digest[64]; + SHA512::hash(digest,_download->data.data(),_download->data.length()); + if (memcmp(digest,_download->sha512,64)) { + LOG("retrying download of %s -- SHA-512 mismatch, file corrupt!",_download->filename.c_str()); + std::fill(_download->haveChunks.begin(),_download->haveChunks.end(),false); + whichChunk = 0; + } else { + LOG("successfully downloaded and authenticated %s, launching update...",_download->filename.c_str()); + delete _download; + _download = (_Download *)0; + return; + } + } else { + whichChunk = std::distance(_download->haveChunks.begin(),ptr); + } + + TRACE("requesting chunk %u/%u of %s from %s",(unsigned int)whichChunk,(unsigned int)_download->haveChunks.size(),_download->filename.c_str()_download->currentlyReceivingFrom.toString().c_str()); + + Packet outp(_download->currentlyReceivingFrom,_r->identity.address(),Packet::VERB_FILE_BLOCK_REQUEST); + outp.append(_download->sha512,16); + outp.append((uint32_t)(whichChunk * ZT_UPDATER_CHUNK_SIZE)); + if (whichChunk == (_download->haveChunks.size() - 1)) + outp.append((uint16_t)_download->lastChunkSize); + else outp.append((uint16_t)ZT_UPDATER_CHUNK_SIZE); + _r->sw->send(outp,true); +} + } // namespace ZeroTier diff --git a/node/Updater.hpp b/node/Updater.hpp index 6a1c266b..1fdfdbee 100644 --- a/node/Updater.hpp +++ b/node/Updater.hpp @@ -38,6 +38,7 @@ #include #include #include +#include #include "Constants.hpp" #include "Packet.hpp" @@ -55,10 +56,10 @@ #define ZT_UPDATER_MAX_SUPPORTED_SIZE (1024 * 1024 * 16) // Retry timeout in ms. -#define ZT_UPDATER_RETRY_TIMEOUT 30000 +#define ZT_UPDATER_RETRY_TIMEOUT 15000 -// After this long, look for a new set of peers that have the download shared. -#define ZT_UPDATER_REPOLL_TIMEOUT 60000 +// After this long, look for a new peer to download from +#define ZT_UPDATER_PEER_TIMEOUT 65000 namespace ZeroTier { @@ -67,10 +68,12 @@ class RuntimeEnvironment; /** * Software update downloader and executer * - * FYI: downloads occur via the protocol rather than out of band via http so + * Downloads occur via the ZT1 protocol rather than out of band via http so * that ZeroTier One can be run in secure jailed environments where it is the - * only protocol permitted over the "real" Internet. This is required for a - * number of potentially popular use cases. + * only protocol permitted over the "real" Internet. This is wanted for a + * number of potentially popular use cases, like private LANs that connect + * nodes in hostile environments or playing attack/defend on the future CTF + * network. * * The protocol is a simple chunk-pulling "trivial FTP" like thing that should * be suitable for core engine software updates. Software updates themselves @@ -84,6 +87,19 @@ class RuntimeEnvironment; class Updater { public: + /** + * Contains information about a shared update available to other peers + */ + struct SharedUpdate + { + std::string fullPath; + std::string filename; + unsigned char sha512[64]; + C25519::Signature sig; + Address signedBy; + unsigned long size; + }; + Updater(const RuntimeEnvironment *renv); ~Updater(); @@ -108,18 +124,72 @@ public: /** * Called periodically from main loop + * + * This retries downloads if they're stalled and performs other cleanup. */ void retryIfNeeded(); /** * Called when a chunk is received * - * @param sha512First16 First 16 bytes of SHA-512 hash + * If the chunk is a final chunk and we now have an update, this may result + * in the commencement of the update process and the shutdown of ZT1. + * + * @param from Originating peer + * @param sha512 Up to 64 bytes of hash to match + * @param shalen Length of sha512[] * @param at Position of chunk * @param chunk Chunk data * @param len Length of chunk */ - void handleChunk(const void *sha512First16,unsigned long at,const void *chunk,unsigned long len); + void handleChunk(const Address &from,const void *sha512,unsigned int shalen,unsigned long at,const void *chunk,unsigned long len); + + /** + * Called when a reply to a search for an update is received + * + * This checks SHA-512 hash signature and version as parsed from filename + * before starting the transfer. + * + * @param from Node that sent reply saying it has the file + * @param filename Name of file (can be parsed for version info) + * @param sha512 64-byte SHA-512 hash of file's contents + * @param filesize Size of file in bytes + * @param signedBy Address of signer of hash + * @param signature Signature (currently must be Ed25519) + * @param siglen Length of signature in bytes + */ + void handleAvailable(const Address &from,const char *filename,const void *sha512,unsigned long filesize,const Address &signedBy,const void *signature,unsigned int siglen); + + /** + * Get data about a shared update if found + * + * @param filename File name + * @param update Empty structure to be filled with update info + * @return True if found (if false, 'update' is unmodified) + */ + bool findSharedUpdate(const char *filename,SharedUpdate &update) const; + + /** + * Get data about a shared update if found + * + * @param sha512 Up to 64 bytes of hash to match + * @param shalen Length of sha512[] + * @param update Empty structure to be filled with update info + * @return True if found (if false, 'update' is unmodified) + */ + bool findSharedUpdate(const void *sha512,unsigned int shalen,SharedUpdate &update) const; + + /** + * Get a chunk of a shared update + * + * @param sha512 Up to 64 bytes of hash to match + * @param shalen Length of sha512[] + * @param at Position in file + * @param chunk Buffer to store data + * @param chunklen Number of bytes to get + * @return True if chunk[] was successfully filled, false if not found or other error + */ + bool getSharedChunk(const void *sha512,unsigned int shalen,unsigned long at,void *chunk,unsigned long chunklen) const; /** * @return Canonical update filename for this platform or empty string if unsupported @@ -135,48 +205,13 @@ public: static bool parseUpdateFilename(const char *filename,unsigned int &vMajor,unsigned int &vMinor,unsigned int &revision); private: + void _requestNextChunk(); + struct _Download { - _Download(const void *s512,const std::string &fn,unsigned long len,unsigned int vMajor,unsigned int vMinor,unsigned int rev) - { - data.resize(len); - haveChunks.resize((len / ZT_UPDATER_CHUNK_SIZE) + 1,false); - filename = fn; - memcpy(sha512,s512,64); - lastChunkSize = len % ZT_UPDATER_CHUNK_SIZE; - versionMajor = vMajor; - versionMinor = vMinor; - revision = rev; - } - - long nextChunk() const - { - std::vector::const_iterator ptr(std::find(haveChunks.begin(),haveChunks.end(),false)); - if (ptr != haveChunks.end()) - return std::distance(haveChunks.begin(),ptr); - else return -1; - } - - bool gotChunk(unsigned long at,const void *chunk,unsigned long len) - { - unsigned long whichChunk = at / ZT_UPDATER_CHUNK_SIZE; - if (at != (ZT_UPDATER_CHUNK_SIZE * whichChunk)) - return false; // not at chunk boundary - if (whichChunk >= haveChunks.size()) - return false; // overflow - if ((whichChunk == (haveChunks.size() - 1))&&(len != lastChunkSize)) - return false; // last chunk, size wrong - else if (len != ZT_UPDATER_CHUNK_SIZE) - return false; // chunk size wrong - for(unsigned long i=0;i haveChunks; - std::vector
peersThatHave; + std::list
peersThatHave; // excluding current std::string filename; unsigned char sha512[64]; Address currentlyReceivingFrom; @@ -185,18 +220,9 @@ private: unsigned int versionMajor,versionMinor,revision; }; - struct _Shared - { - std::string filename; - unsigned char sha512[64]; - C25519::Signature sig; - Address signedBy; - unsigned long size; - }; - const RuntimeEnvironment *_r; _Download *_download; - std::map< Array,_Shared > _sharedUpdates; + std::list _sharedUpdates; // usually not more than 1 or 2 of these Mutex _lock; }; diff --git a/node/Utils.cpp b/node/Utils.cpp index 66bd27dd..661dbb8c 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -265,7 +265,7 @@ uint64_t Utils::getLastModified(const char *path) return (((uint64_t)s.st_mtime) * 1000ULL); } -static int64_t getFileSize(const char *path) +int64_t Utils::getFileSize(const char *path) { struct stat s; if (stat(path,&s)) -- cgit v1.2.3 From 9455b1cc810eff7517f51f50eee828344af3526a Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 6 Nov 2013 10:38:19 -0500 Subject: Comments, change .nfo to .sig for uploads, clean some unused code from Utils. --- node/RuntimeEnvironment.hpp | 25 +++--- node/Updater.cpp | 26 +++--- node/Utils.cpp | 94 +-------------------- node/Utils.hpp | 199 ++------------------------------------------ 4 files changed, 35 insertions(+), 309 deletions(-) (limited to 'node/Utils.cpp') diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 4baaab6b..75b171ff 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -80,23 +80,28 @@ public: { } - // home of saved state, identity, etc. + // Full path to home folder std::string homePath; - // signal() to prematurely interrupt main loop wait to cause loop to run - // again and detect some kind of change, exit, etc. + // Main loop waits on this condition when it delays between runs, so + // signaling this will prematurely wake it. Condition mainLoopWaitCondition; + // This node's identity Identity identity; - // hacky... want to get rid of this flag... + // Indicates that we are shutting down -- this is hacky, want to factor out volatile bool shutdownInProgress; - // Order matters a bit here. These are constructed in this order - // and then deleted in the opposite order on Node exit. The order ensures - // that things that are needed are there before they're needed. + /* + * Order matters a bit here. These are constructed in this order + * and then deleted in the opposite order on Node exit. The order ensures + * that things that are needed are there before they're needed. + * + * These are constant and never null after startup unless indicated. + */ - Logger *log; // may be null + Logger *log; // null if logging is disabled CMWC4096 *prng; Multicaster *mc; Switch *sw; @@ -105,9 +110,9 @@ public: SysEnv *sysEnv; NodeConfig *nc; Node *node; - Updater *updater; // may be null if updates are disabled + Updater *updater; // null if auto-updates are disabled #ifndef __WINDOWS__ - Service *netconfService; // may be null + Service *netconfService; // null if no netconf service running #endif }; diff --git a/node/Updater.cpp b/node/Updater.cpp index 1d6a4faf..22eda925 100644 --- a/node/Updater.cpp +++ b/node/Updater.cpp @@ -61,38 +61,38 @@ void Updater::refreshShared() for(std::map::iterator u(ud.begin());u!=ud.end();++u) { if (u->second) continue; // skip directories - if ((u->first.length() >= 4)&&(!strcasecmp(u->first.substr(u->first.length() - 4).c_str(),".nfo"))) - continue; // skip .nfo companion files + if ((u->first.length() >= 4)&&(!strcasecmp(u->first.substr(u->first.length() - 4).c_str(),".sig"))) + continue; // skip .sig companion files std::string fullPath(updatesPath + ZT_PATH_SEPARATOR_S + u->first); - std::string nfoPath(fullPath + ".nfo"); + std::string sigPath(fullPath + ".sig"); std::string buf; - if (Utils::readFile(nfoPath.c_str(),buf)) { - Dictionary nfo(buf); + if (Utils::readFile(sigPath.c_str(),buf)) { + Dictionary sig(buf); SharedUpdate shared; shared.fullPath = fullPath; shared.filename = u->first; - std::string sha512(Utils::unhex(nfo.get("sha512",std::string()))); + std::string sha512(Utils::unhex(sig.get("sha512",std::string()))); if (sha512.length() < sizeof(shared.sha512)) { - TRACE("skipped shareable update due to missing fields in companion .nfo: %s",fullPath.c_str()); + TRACE("skipped shareable update due to missing fields in companion .sig: %s",fullPath.c_str()); continue; } memcpy(shared.sha512,sha512.data(),sizeof(shared.sha512)); - std::string sig(Utils::unhex(nfo.get("sha512sig_ed25519",std::string()))); - if (sig.length() < shared.sig.size()) { - TRACE("skipped shareable update due to missing fields in companion .nfo: %s",fullPath.c_str()); + std::string signature(Utils::unhex(sig.get("sha512sig_ed25519",std::string()))); + if (signature.length() < shared.sig.size()) { + TRACE("skipped shareable update due to missing fields in companion .sig: %s",fullPath.c_str()); continue; } - memcpy(shared.sig.data,sig.data(),shared.sig.size()); + memcpy(shared.sig.data,signature.data(),shared.sig.size()); // Check signature to guard against updates.d being used as a data // exfiltration mechanism. We will only share properly signed updates, // nothing else. - Address signedBy(nfo.get("signedBy",std::string())); + Address signedBy(sig.get("signedBy",std::string())); std::map< Address,Identity >::const_iterator authority(ZT_DEFAULTS.updateAuthorities.find(signedBy)); if ((authority == ZT_DEFAULTS.updateAuthorities.end())||(!authority->second.verify(shared.sha512,64,shared.sig))) { TRACE("skipped shareable update: not signed by valid authority or signature invalid: %s",fullPath.c_str()); @@ -110,7 +110,7 @@ void Updater::refreshShared() LOG("sharing software update %s to other peers",shared.filename.c_str()); _sharedUpdates.push_back(shared); } else { - TRACE("skipped shareable update due to missing companion .nfo: %s",fullPath.c_str()); + TRACE("skipped shareable update due to missing companion .sig: %s",fullPath.c_str()); continue; } } diff --git a/node/Utils.cpp b/node/Utils.cpp index 661dbb8c..31cb40dd 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -50,9 +50,6 @@ namespace ZeroTier { const char Utils::HEXCHARS[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; -static const char *DAY_NAMES[7] = { "Sun","Mon","Tue","Wed","Thu","Fri","Sat" }; -static const char *MONTH_NAMES[12] = { "Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec" }; - std::map Utils::listDirectory(const char *path) { std::map r; @@ -62,7 +59,8 @@ std::map Utils::listDirectory(const char *path) WIN32_FIND_DATAA ffd; if ((hFind = FindFirstFileA((std::string(path) + "\\*").c_str(),&ffd)) != INVALID_HANDLE_VALUE) { do { - r[std::string(ffd.cFileName)] = ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); + if ((strcmp(ffd.cFileName,"."))&&(strcmp(ffd.cFileName,".."))) + r[std::string(ffd.cFileName)] = ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0); } while (FindNextFileA(hFind,&ffd)); FindClose(hFind); } @@ -275,94 +273,6 @@ int64_t Utils::getFileSize(const char *path) return -1; } -std::string Utils::toRfc1123(uint64_t t64) -{ - struct tm t; - char buf[128]; - time_t utc = (time_t)(t64 / 1000ULL); -#ifdef __WINDOWS__ - gmtime_s(&t,&utc); -#else - gmtime_r(&utc,&t); -#endif - Utils::snprintf(buf,sizeof(buf),"%3s, %02d %3s %4d %02d:%02d:%02d GMT",DAY_NAMES[t.tm_wday],t.tm_mday,MONTH_NAMES[t.tm_mon],t.tm_year + 1900,t.tm_hour,t.tm_min,t.tm_sec); - return std::string(buf); -} - -#ifdef __WINDOWS__ -static int is_leap(unsigned y) { - y += 1900; - return (y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0); -} -static time_t timegm(struct tm *tm) { - static const unsigned ndays[2][12] = { - {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, - {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} - }; - time_t res = 0; - int i; - for (i = 70; i < tm->tm_year; ++i) - res += is_leap(i) ? 366 : 365; - - for (i = 0; i < tm->tm_mon; ++i) - res += ndays[is_leap(tm->tm_year)][i]; - res += tm->tm_mday - 1; - res *= 24; - res += tm->tm_hour; - res *= 60; - res += tm->tm_min; - res *= 60; - res += tm->tm_sec; - return res; -} -#endif - -uint64_t Utils::fromRfc1123(const char *tstr) -{ - struct tm t; - char wdays[128],mons[128]; - - int l = (int)strlen(tstr); - if ((l < 29)||(l > 64)) - return 0; - int assigned = sscanf(tstr,"%3s, %02d %3s %4d %02d:%02d:%02d GMT",wdays,&t.tm_mday,mons,&t.tm_year,&t.tm_hour,&t.tm_min,&t.tm_sec); - if (assigned != 7) - return 0; - - wdays[3] = '\0'; - for(t.tm_wday=0;t.tm_wday<7;++t.tm_wday) { -#ifdef __WINDOWS__ - if (!_stricmp(DAY_NAMES[t.tm_wday],wdays)) - break; -#else - if (!strcasecmp(DAY_NAMES[t.tm_wday],wdays)) - break; -#endif - } - if (t.tm_wday == 7) - return 0; - mons[3] = '\0'; - for(t.tm_mon=0;t.tm_mon<12;++t.tm_mon) { -#ifdef __WINDOWS__ - if (!_stricmp(MONTH_NAMES[t.tm_mday],mons)) - break; -#else - if (!strcasecmp(MONTH_NAMES[t.tm_mday],mons)) - break; -#endif - } - if (t.tm_mon == 12) - return 0; - - t.tm_wday = 0; // ignored by timegm - t.tm_yday = 0; // ignored by timegm - t.tm_isdst = 0; // ignored by timegm - - time_t utc = timegm(&t); - - return ((utc > 0) ? (1000ULL * (uint64_t)utc) : 0ULL); -} - bool Utils::readFile(const char *path,std::string &buf) { char tmp[4096]; diff --git a/node/Utils.hpp b/node/Utils.hpp index 208ff755..4e060748 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -41,9 +41,6 @@ #include "Constants.hpp" -#include "../ext/lz4/lz4.h" -#include "../ext/lz4/lz4hc.h" - #ifdef __WINDOWS__ #include #include @@ -53,11 +50,6 @@ #include #endif -/** - * Maximum compression/decompression block size (do not change) - */ -#define ZT_COMPRESSION_BLOCK_SIZE 16777216 - namespace ZeroTier { /** @@ -108,14 +100,13 @@ public: return (unlink(path) == 0); #endif } - static inline bool rm(const std::string &path) - throw() - { - return rm(path.c_str()); - } + static inline bool rm(const std::string &path) throw() { return rm(path.c_str()); } /** * List a directory's contents + * + * Keys in returned map are filenames only and don't include the leading + * path. Pseudo-paths like . and .. are not returned. * * @param path Path to list * @return Map of entries and whether or not they are also directories (empty on failure) @@ -199,187 +190,6 @@ public: */ static int64_t getFileSize(const char *path); - /** - * @param t64 Time in ms since epoch - * @return RFC1123 date string - */ - static std::string toRfc1123(uint64_t t64); - - /** - * @param tstr Time in RFC1123 string format - * @return Time in ms since epoch - */ - static uint64_t fromRfc1123(const char *tstr); - static inline uint64_t fromRfc1123(const std::string &tstr) { return fromRfc1123(tstr.c_str()); } - - /** - * String append output function object for use with compress/decompress - */ - class StringAppendOutput - { - public: - StringAppendOutput(std::string &s) : _s(s) {} - inline void operator()(const void *data,unsigned int len) { _s.append((const char *)data,len); } - private: - std::string &_s; - }; - - /** - * STDIO FILE append output function object for compress/decompress - * - * Throws std::runtime_error on write error. - */ - class FILEAppendOutput - { - public: - FILEAppendOutput(FILE *f) : _f(f) {} - inline void operator()(const void *data,unsigned int len) - throw(std::runtime_error) - { - if ((int)fwrite(data,1,len,_f) != (int)len) - throw std::runtime_error("write failed"); - } - private: - FILE *_f; - }; - - /** - * Compress data - * - * O must be a function or function object that takes the following - * arguments: (const void *data,unsigned int len) - * - * @param in Input iterator that reads bytes (char, uint8_t, etc.) - * @param out Output iterator that writes bytes - */ - template - static inline void compress(I begin,I end,O out) - { - unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); - char *buf = new char[bufLen * 2]; - char *buf2 = buf + bufLen; - - try { - I inp(begin); - for(;;) { - unsigned int readLen = 0; - while ((readLen < ZT_COMPRESSION_BLOCK_SIZE)&&(inp != end)) { - buf[readLen++] = (char)*inp; - ++inp; - } - if (!readLen) - break; - - uint32_t l = hton((uint32_t)readLen); - out((const void *)&l,4); // original size - - if (readLen < 32) { // don't bother compressing itty bitty blocks - l = 0; // stored - out((const void *)&l,4); - out((const void *)buf,readLen); - continue; - } - - int lz4CompressedLen = LZ4_compressHC(buf,buf2,(int)readLen); - if ((lz4CompressedLen <= 0)||(lz4CompressedLen >= (int)readLen)) { - l = 0; // stored - out((const void *)&l,4); - out((const void *)buf,readLen); - continue; - } - - l = hton((uint32_t)lz4CompressedLen); // lz4 only - out((const void *)&l,4); - out((const void *)buf2,(unsigned int)lz4CompressedLen); - } - - delete [] buf; - } catch ( ... ) { - delete [] buf; - throw; - } - } - - /** - * Decompress data - * - * O must be a function or function object that takes the following - * arguments: (const void *data,unsigned int len) - * - * @param in Input iterator that reads bytes (char, uint8_t, etc.) - * @param out Output iterator that writes bytes - * @return False on decompression error - */ - template - static inline bool decompress(I begin,I end,O out) - { - volatile char i32c[4]; - void *const i32cp = (void *)i32c; - unsigned int bufLen = LZ4_compressBound(ZT_COMPRESSION_BLOCK_SIZE); - char *buf = new char[bufLen * 2]; - char *buf2 = buf + bufLen; - - try { - I inp(begin); - while (inp != end) { - i32c[0] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[1] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[2] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[3] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - unsigned int originalSize = ntoh(*((const uint32_t *)i32cp)); - i32c[0] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[1] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[2] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - i32c[3] = (char)*inp; if (++inp == end) { delete [] buf; return false; } - uint32_t _compressedSize = ntoh(*((const uint32_t *)i32cp)); - unsigned int compressedSize = _compressedSize & 0x7fffffff; - - if (compressedSize) { - if (compressedSize > bufLen) { - delete [] buf; - return false; - } - unsigned int readLen = 0; - while ((readLen < compressedSize)&&(inp != end)) { - buf[readLen++] = (char)*inp; - ++inp; - } - if (readLen != compressedSize) { - delete [] buf; - return false; - } - - if (LZ4_uncompress_unknownOutputSize(buf,buf2,compressedSize,bufLen) != (int)originalSize) { - delete [] buf; - return false; - } else out((const void *)buf2,(unsigned int)originalSize); - } else { // stored - if (originalSize > bufLen) { - delete [] buf; - return false; - } - unsigned int readLen = 0; - while ((readLen < originalSize)&&(inp != end)) { - buf[readLen++] = (char)*inp; - ++inp; - } - if (readLen != originalSize) { - delete [] buf; - return false; - } - - out((const void *)buf,(unsigned int)originalSize); - } - } - - delete [] buf; - return true; - } catch ( ... ) { - delete [] buf; - throw; - } - } - /** * @return Current time in milliseconds since epoch */ @@ -682,6 +492,7 @@ public: return ((*aptr & mask) == (*aptr & mask)); } + // Byte swappers for big/little endian conversion static inline uint8_t hton(uint8_t n) throw() { return n; } static inline int8_t hton(int8_t n) throw() { return n; } static inline uint16_t hton(uint16_t n) throw() { return htons(n); } -- cgit v1.2.3 From b699bdefbd008a5dbfab4308e9b969b2aaa88ce1 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 21 Nov 2013 16:34:27 -0500 Subject: Add shutdownIfUnreadable file feature: shut down if shutdownIfUnreadable in home folder is in fact existent but unreadable (e.g. broken link). This enables nifty shutdown on .app trashing feature for OSX. --- node/Node.cpp | 8 ++++++++ node/Utils.cpp | 12 +++++++++++- node/Utils.hpp | 6 ++---- 3 files changed, 21 insertions(+), 5 deletions(-) (limited to 'node/Utils.cpp') diff --git a/node/Node.cpp b/node/Node.cpp index c88741a6..f2668e4e 100644 --- a/node/Node.cpp +++ b/node/Node.cpp @@ -467,6 +467,7 @@ Node::ReasonForTermination Node::run() // Core I/O loop try { + std::string shutdownIfUnreadablePath(_r->homePath + ZT_PATH_SEPARATOR_S + "shutdownIfUnreadable"); uint64_t lastNetworkAutoconfCheck = Utils::now() - 5000; // check autoconf again after 5s for startup uint64_t lastPingCheck = 0; uint64_t lastClean = Utils::now(); // don't need to do this immediately @@ -476,6 +477,13 @@ Node::ReasonForTermination Node::run() long lastDelayDelta = 0; while (impl->reasonForTermination == NODE_RUNNING) { + if (Utils::fileExists(shutdownIfUnreadablePath.c_str(),false)) { + FILE *tmpf = fopen(shutdownIfUnreadablePath.c_str(),"r"); + if (!tmpf) + return impl->terminateBecause(Node::NODE_NORMAL_TERMINATION,"shutdownIfUnreadable was not readable"); + fclose(tmpf); + } + uint64_t now = Utils::now(); bool resynchronize = false; diff --git a/node/Utils.cpp b/node/Utils.cpp index 31cb40dd..608de593 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -246,7 +246,7 @@ no getSecureRandom() implementation; void Utils::lockDownFile(const char *path,bool isDir) { -#if defined(__APPLE__) || defined(__linux__) || defined(linux) || defined(__LINUX__) || defined(__linux) +#ifdef __UNIX_LIKE__ chmod(path,isDir ? 0700 : 0600); #else #ifdef _WIN32 @@ -263,6 +263,16 @@ uint64_t Utils::getLastModified(const char *path) return (((uint64_t)s.st_mtime) * 1000ULL); } +bool Utils::fileExists(const char *path,bool followLinks) +{ + struct stat s; +#ifdef __UNIX_LIKE__ + if (!followLinks) + return (lstat(path,&s) == 0); +#endif + return (stat(path,&s) == 0); +} + int64_t Utils::getFileSize(const char *path) { struct stat s; diff --git a/node/Utils.hpp b/node/Utils.hpp index 4e060748..2fea8b9b 100644 --- a/node/Utils.hpp +++ b/node/Utils.hpp @@ -177,12 +177,10 @@ public: /** * @param path Path to check + * @param followLinks Follow links (on platforms with that concept) * @return True if file or directory exists at path location */ - static inline bool fileExists(const char *path) - { - return (getLastModified(path) != 0); - } + static bool fileExists(const char *path,bool followLinks = true); /** * @param path Path to file -- cgit v1.2.3 From f7e3c10eca9b77880f99cd2012553b4eef932e57 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Thu, 12 Dec 2013 11:33:41 -0800 Subject: Cleanup in Utils, fix for HttpClient on Linux. --- node/HttpClient.cpp | 19 ++++++++++++++----- node/Utils.cpp | 30 ++++++++++++++++-------------- node/Utils.hpp | 29 ++++++++++++++++++++++++++--- 3 files changed, 56 insertions(+), 22 deletions(-) (limited to 'node/Utils.cpp') diff --git a/node/HttpClient.cpp b/node/HttpClient.cpp index 15c01c44..d4e76018 100644 --- a/node/HttpClient.cpp +++ b/node/HttpClient.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #endif namespace ZeroTier { @@ -68,7 +69,6 @@ const std::map HttpClient::NO_HEADERS; // Paths where "curl" may be found on the system #define NUM_CURL_PATHS 5 static const char *CURL_PATHS[NUM_CURL_PATHS] = { "/usr/bin/curl","/bin/curl","/usr/local/bin/curl","/usr/sbin/curl","/sbin/curl" }; -static const std::string CURL_IN_HOME(ZT_DEFAULTS.defaultHomePath + "/curl"); // Maximum message length #define CURL_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) @@ -102,10 +102,6 @@ public: break; } } - if (!curlPath.length()) { - if (Utils::fileExists(CURL_IN_HOME.c_str())) - curlPath = CURL_IN_HOME; - } if (!curlPath.length()) { _handler(_arg,-1,_url,false,"unable to locate 'curl' binary in /usr/bin, /bin, /usr/local/bin, /usr/sbin, or /sbin"); delete this; @@ -201,6 +197,19 @@ public: } if (waitpid(pid,&exitCode,WNOHANG) > 0) { + for(;;) { + // Drain output... + int n = (int)::read(curlStdout[0],buf,sizeof(buf)); + if (n <= 0) + break; + else { + _body.append(buf,n); + if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { + tooLong = true; + break; + } + } + } pid = 0; break; } diff --git a/node/Utils.cpp b/node/Utils.cpp index 608de593..c0886859 100644 --- a/node/Utils.cpp +++ b/node/Utils.cpp @@ -151,7 +151,6 @@ unsigned int Utils::unhex(const char *hex,void *buf,unsigned int len) } unsigned int Utils::unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len) - throw() { int n = 1; unsigned char c,b = 0; @@ -191,7 +190,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) Mutex::Lock _l(randomLock); - // A Salsa20 instance is used to mangle whatever our base + // A Salsa20/8 instance is used to further mangle whatever our base // random source happens to be. if (!randInitialized) { randInitialized = true; @@ -208,7 +207,7 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) { int fd = ::open("/dev/urandom",O_RDONLY); if (fd < 0) { - fprintf(stderr,"FATAL ERROR: unable to open /dev/urandom: %s"ZT_EOL_S,strerror(errno)); + fprintf(stderr,"FATAL ERROR: unable to open /dev/urandom"ZT_EOL_S); exit(-1); } if ((int)::read(fd,randbuf,sizeof(randbuf)) != (int)sizeof(randbuf)) { @@ -220,17 +219,20 @@ void Utils::getSecureRandom(void *buf,unsigned int bytes) #else #ifdef __WINDOWS__ { - char ktmp[32]; - char ivtmp[8]; - for(int i=0;i<32;++i) ktmp[i] = (char)rand(); - for(int i=0;i<8;++i) ivtmp[i] = (char)rand(); - double now = Utils::nowf(); - memcpy(ktmp,&now,sizeof(now)); - DWORD tmp = GetCurrentProcessId(); - memcpy(ktmp + sizeof(now),&tmp,sizeof(tmp)); - tmp = GetTickCount(); - memcpy(ktmp + sizeof(now) + sizeof(DWORD),&tmp,sizeof(tmp)); - Salsa20 s20tmp(ktmp,256,ivtmp,8); + struct { + double nowf; + DWORD processId; + DWORD tickCount; + uint64_t nowi; + char padding[32]; + } keyMaterial; + keyMaterial.nowf = Utils::nowf(); + keyMaterial.processId = GetCurrentProcessId(); + keyMaterial.tickCount = GetTickCount(); + keyMaterial.nowi = Utils::now(); + for(int i=0;i listDirectory(const char *path); /** + * Convert binary data to hexadecimal + * * @param data Data to convert to hex * @param len Length of data * @return Hexadecimal string @@ -122,6 +126,11 @@ public: static inline std::string hex(const std::string &data) { return hex(data.data(),(unsigned int)data.length()); } /** + * Convert hexadecimal to binary data + * + * This ignores all non-hex characters, just stepping over them and + * continuing. Upper and lower case are supported for letters a-f. + * * @param hex Hexadecimal ASCII code (non-hex chars are ignored) * @return Binary data */ @@ -129,6 +138,11 @@ public: static inline std::string unhex(const std::string &hex) { return unhex(hex.c_str()); } /** + * Convert hexadecimal to binary data + * + * This ignores all non-hex characters, just stepping over them and + * continuing. Upper and lower case are supported for letters a-f. + * * @param hex Hexadecimal ASCII * @param buf Buffer to fill * @param len Length of buffer @@ -138,16 +152,25 @@ public: static inline unsigned int unhex(const std::string &hex,void *buf,unsigned int len) { return unhex(hex.c_str(),buf,len); } /** + * Convert hexadecimal to binary data + * + * This ignores all non-hex characters, just stepping over them and + * continuing. Upper and lower case are supported for letters a-f. + * * @param hex Hexadecimal ASCII * @param hexlen Length of hex ASCII * @param buf Buffer to fill * @param len Length of buffer * @return Number of bytes actually written to buffer */ - static unsigned int unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len) - throw(); + static unsigned int unhex(const char *hex,unsigned int hexlen,void *buf,unsigned int len); /** + * Generate secure random bytes + * + * This will try to use whatever OS sources of entropy are available. It's + * guarded by an internal mutex so it's thread-safe. + * * @param buf Buffer to fill * @param bytes Number of random bytes to generate */ -- cgit v1.2.3