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 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'node/Defaults.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()) { } -- cgit v1.2.3 From bf0da9f2f778aeb3eebe200a8cdeecbc6e1f9253 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 10 Dec 2013 15:30:53 -0800 Subject: Rest of software updater, ready to test... --- main.cpp | 17 +++- node/Constants.hpp | 10 +++ node/Defaults.cpp | 7 +- node/Defaults.hpp | 5 ++ node/HttpClient.cpp | 12 ++- node/Node.hpp | 17 +++- node/RuntimeEnvironment.hpp | 6 +- node/SoftwareUpdater.cpp | 187 ++++++++++++++++++++++++++++++++++++++++++++ node/SoftwareUpdater.hpp | 110 ++++++++++++++++++++++++++ objects.mk | 1 + 10 files changed, 361 insertions(+), 11 deletions(-) create mode 100644 node/SoftwareUpdater.cpp create mode 100644 node/SoftwareUpdater.hpp (limited to 'node/Defaults.cpp') diff --git a/main.cpp b/main.cpp index 872fd37c..37d82bc8 100644 --- a/main.cpp +++ b/main.cpp @@ -44,6 +44,7 @@ #else #include #include +#include #include #include #include @@ -473,13 +474,21 @@ int main(int argc,char **argv) try { node = new Node(homeDir,port,controlPort); - const char *termReason = (char *)0; switch(node->run()) { - case Node::NODE_UNRECOVERABLE_ERROR: + case Node::NODE_NODE_RESTART_FOR_UPGRADE: { +#ifdef __UNIX_LIKE__ + const char *upgPath = node->reasonForTermination(); + if (upgPath) + execl(upgPath,upgPath,"-s",(char *)0); // -s = (re)start after install/upgrade + exitCode = -1; + fprintf(stderr,"%s: abnormal termination: unable to execute update at %s",argv[0],(upgPath) ? upgPath : "(unknown path)"); +#endif + } break; + case Node::NODE_UNRECOVERABLE_ERROR: { exitCode = -1; - termReason = node->reasonForTermination(); + const char *termReason = node->reasonForTermination(); fprintf(stderr,"%s: abnormal termination: %s\n",argv[0],(termReason) ? termReason : "(unknown reason)"); - break; + } break; default: break; } diff --git a/node/Constants.hpp b/node/Constants.hpp index 3f121cf4..21c8a0ec 100644 --- a/node/Constants.hpp +++ b/node/Constants.hpp @@ -330,4 +330,14 @@ error_no_byte_order_defined; */ #define ZT_RENDEZVOUS_NAT_T_DELAY 500 +/** + * Minimum interval between attempts to do a software update + */ +#define ZT_UPDATE_MIN_INTERVAL 120000 + +/** + * Update HTTP timeout in seconds + */ +#define ZT_UPDATE_HTTP_TIMEOUT 30 + #endif diff --git a/node/Defaults.cpp b/node/Defaults.cpp index cfc901b5..566658fa 100644 --- a/node/Defaults.cpp +++ b/node/Defaults.cpp @@ -122,13 +122,18 @@ static inline std::map< Address,Identity > _mkUpdateAuth() return ua; } +static inline std::string _mkUpdateUrl() +{ +} + Defaults::Defaults() : #ifdef ZT_TRACE_MULTICAST multicastTraceWatcher(ZT_TRACE_MULTICAST), #endif defaultHomePath(_mkDefaultHomePath()), supernodes(_mkSupernodeMap()), - updateAuthorities(_mkUpdateAuth()) + updateAuthorities(_mkUpdateAuth()), + updateLatestNfoURL(_mkUpdateUrl()) { } diff --git a/node/Defaults.hpp b/node/Defaults.hpp index d546d01f..9d6d4bcf 100644 --- a/node/Defaults.hpp +++ b/node/Defaults.hpp @@ -78,6 +78,11 @@ public: * build will not auto-update. */ const std::map< Address,Identity > updateAuthorities; + + /** + * URL to latest .nfo for software updates + */ + const std::string updateLatestNfoURL; }; extern const Defaults ZT_DEFAULTS; diff --git a/node/HttpClient.cpp b/node/HttpClient.cpp index 1d1624db..15c01c44 100644 --- a/node/HttpClient.cpp +++ b/node/HttpClient.cpp @@ -112,6 +112,12 @@ public: return; } + if (!_url.length()) { + _handler(_arg,-1,_url,false,"cannot fetch empty URL"); + delete this; + return; + } + curlArgs[0] = const_cast (curlPath.c_str()); curlArgs[1] = const_cast ("-D"); curlArgs[2] = const_cast ("-"); // append headers before output @@ -171,9 +177,11 @@ public: if (FD_ISSET(curlStdout[0],&readfds)) { int n = (int)::read(curlStdout[0],buf,sizeof(buf)); - if (n > 0) + if (n > 0) { _body.append(buf,n); - else if (n < 0) + // Reset timeout when data is read... + timesOutAt = Utils::now() + ((unsigned long long)_timeout * 1000ULL); + } else if (n < 0) break; if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { ::kill(pid,SIGKILL); diff --git a/node/Node.hpp b/node/Node.hpp index 9d02c008..2736713f 100644 --- a/node/Node.hpp +++ b/node/Node.hpp @@ -97,9 +97,24 @@ public: */ enum ReasonForTermination { + /** + * Node is currently in run() + */ NODE_RUNNING = 0, + + /** + * Node is shutting down for normal reasons, including a signal + */ NODE_NORMAL_TERMINATION = 1, - NODE_RESTART_FOR_RECONFIGURATION = 2, + + /** + * An upgrade is available. Its path is in reasonForTermination(). + */ + NODE_RESTART_FOR_UPGRADE = 2, + + /** + * A serious unrecoverable error has occurred. + */ NODE_UNRECOVERABLE_ERROR = 3 }; diff --git a/node/RuntimeEnvironment.hpp b/node/RuntimeEnvironment.hpp index 48797b14..05e10676 100644 --- a/node/RuntimeEnvironment.hpp +++ b/node/RuntimeEnvironment.hpp @@ -46,7 +46,7 @@ class CMWC4096; class Service; class Node; class Multicaster; -class Updater; +class SoftwareUpdater; /** * Holds global state for an instance of ZeroTier::Node @@ -73,7 +73,7 @@ public: topology((Topology *)0), sysEnv((SysEnv *)0), nc((NodeConfig *)0), - updater((Updater *)0) + updater((SoftwareUpdater *)0) #ifndef __WINDOWS__ ,netconfService((Service *)0) #endif @@ -110,7 +110,7 @@ public: SysEnv *sysEnv; NodeConfig *nc; Node *node; - Updater *updater; // null if auto-updates are disabled + SoftwareUpdater *updater; // null if software updates are not enabled #ifndef __WINDOWS__ Service *netconfService; // null if no netconf service running #endif diff --git a/node/SoftwareUpdater.cpp b/node/SoftwareUpdater.cpp new file mode 100644 index 00000000..4cb2f7e4 --- /dev/null +++ b/node/SoftwareUpdater.cpp @@ -0,0 +1,187 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#include +#include +#include + +#include "../version.h" + +#include "SoftwareUpdater.hpp" +#include "Dictionary.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Thread.hpp" +#include "Node.hpp" + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#endif + +namespace ZeroTier { + +SoftwareUpdater::SoftwareUpdater(const RuntimeEnvironment *renv) : + _r(renv), + _myVersion(packVersion(ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION)), + _lastUpdateAttempt(0), + _status(UPDATE_STATUS_IDLE), + _die(false), + _lock() +{ +} + +SoftwareUpdater::~SoftwareUpdater() +{ + _die = true; + for(;;) { + _lock.lock(); + bool ip = (_status != UPDATE_STATUS_IDLE); + _lock.unlock(); + if (ip) + Thread::sleep(500); + else break; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r; + Mutex::Lock _l(upd->_lock); + + if ((upd->_die)||(upd->_status != UPDATE_STATUS_GETTING_NFO)) { + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (code != 200) { + LOG("unable to check for software updates, response code %d (%s)",code,body.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + try { + Dictionary nfo(body); + const unsigned int vMajor = Utils::strToUInt(nfo.get("vMajor").c_str()); + const unsigned int vMinor = Utils::strToUInt(nfo.get("vMinor").c_str()); + const unsigned int vRevision = Utils::strToUInt(nfo.get("vRevision").c_str()); + const Address signedBy(nfo.get("signedBy")); + const std::string signature(Utils::unhex(nfo.get("ed25519"))); + const std::string &url = nfo.get("url"); + + if (signature.length() != ZT_C25519_SIGNATURE_LEN) { + LOG("software update aborted: .nfo file invalid: bad Ed25519 signature"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if ((url.length() <= 7)||(url.substr(0,7) != "http://")) { + LOG("software update aborted: .nfo file invalid: update URL must begin with http://"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) { + LOG("software update aborted: .nfo file invalid: version on web site <= my version"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) { + LOG("software update aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + upd->_status = UPDATE_STATUS_GETTING_FILE; + upd->_signedBy = signedBy; + upd->_signature = signature; + + HttpClient::GET(url,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionBinary,arg); + } catch ( ... ) { + LOG("software update check failed: .nfo file invalid: fields missing or invalid dictionary format"); + upd->_status = UPDATE_STATUS_IDLE; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *_r = (const RuntimeEnvironment *)upd->_r; + Mutex::Lock _l(upd->_lock); + + std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(upd->_signedBy); + if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) { // sanity check, shouldn't happen + LOG("software update aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + // The all-important authenticity check... :) + if (!updateAuthority->second.verify(body.data(),body.length(),upd->_signature.data(),upd->_signature.length())) { + LOG("software update aborted: update fetched from '%s' failed certificate check against signer %s",url.c_str(),updateAuthority->first.toString().c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + +#ifdef __UNIX_LIKE__ + size_t lastSlash = url.rfind('/'); + if (lastSlash == std::string::npos) { // sanity check, shouldn't happen + LOG("software update aborted: invalid URL"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + std::string updatesDir(_r->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::string updatePath(updatesDir + ZT_PATH_SEPARATOR_S + url.substr(lastSlash + 1)); + mkdir(updatesDir.c_str(),0755); + + int fd = ::open(updatePath.c_str(),O_WRONLY|O_CREAT|O_TRUNC,0755); + if (fd <= 0) { + LOG("software update aborted: unable to open %s for writing",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if ((long)::write(fd,body.data(),body.length()) != (long)body.length()) { + LOG("software update aborted: unable to write to %s",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + ::close(fd); + ::chmod(updatePath.c_str(),0755); + + _r->node->terminate(Node::NODE_RESTART_FOR_UPGRADE,updatePath.c_str()); +#endif + +#ifdef __WINDOWS__ + todo; +#endif +} + +} // namespace ZeroTier diff --git a/node/SoftwareUpdater.hpp b/node/SoftwareUpdater.hpp new file mode 100644 index 00000000..bfcdf395 --- /dev/null +++ b/node/SoftwareUpdater.hpp @@ -0,0 +1,110 @@ +/* + * ZeroTier One - Global Peer to Peer Ethernet + * Copyright (C) 2012-2013 ZeroTier Networks LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * -- + * + * ZeroTier may be used and distributed under the terms of the GPLv3, which + * are available at: http://www.gnu.org/licenses/gpl-3.0.html + * + * If you would like to embed ZeroTier into a commercial application or + * redistribute it in a modified binary form, please contact ZeroTier Networks + * LLC. Start here: http://www.zerotier.com/ + */ + +#ifndef ZT_SOFTWAREUPDATER_HPP +#define ZT_SOFTWAREUPDATER_HPP + +#include + +#include "Constants.hpp" +#include "Mutex.hpp" +#include "Utils.hpp" +#include "HttpClient.hpp" +#include "Defaults.hpp" + +namespace ZeroTier { + +class RuntimeEnvironment; + +/** + * Software updater + */ +class SoftwareUpdater +{ +public: + SoftwareUpdater(const RuntimeEnvironment *renv); + ~SoftwareUpdater(); + + /** + * Called on each version message from a peer + * + * If a peer has a newer version, that causes an update to be started. + * + * @param vmaj Peer's major version + * @param vmin Peer's minor version + * @param rev Peer's revision + */ + inline void sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) + { + const uint64_t tmp = packVersion(vmaj,vmin,rev); + if (tmp > _myVersion) { + Mutex::Lock _l(_lock); + if ((_status == UPDATE_STATUS_IDLE)&&(!_die)&&(ZT_DEFAULTS.updateLatestNfoURL.length())) { + const uint64_t now = Utils::now(); + if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MIN_INTERVAL) { + _lastUpdateAttempt = now; + _status = UPDATE_STATUS_GETTING_NFO; + HttpClient::GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } + } + } + } + + /** + * Pack three-component version into a 64-bit integer + * + * @param vmaj Major version (0..65535) + * @param vmin Minor version (0..65535) + * @param rev Revision (0..65535) + */ + static inline uint64_t packVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev) + throw() + { + return ( ((uint64_t)(vmaj & 0xffff) << 32) | ((uint64_t)(vmin & 0xffff) << 16) | (uint64_t)(rev & 0xffff) ); + } + +private: + static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,bool onDisk,const std::string &body); + static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,bool onDisk,const std::string &body); + + const RuntimeEnvironment *_r; + const uint64_t _myVersion; + volatile uint64_t _lastUpdateAttempt; + volatile enum { + UPDATE_STATUS_IDLE, + UPDATE_STATUS_GETTING_NFO, + UPDATE_STATUS_GETTING_FILE + } _status; + volatile bool _die; + Address _signedBy; + std::string _signature; + Mutex _lock; +}; + +} // namespace ZeroTier + +#endif diff --git a/objects.mk b/objects.mk index 7eb97574..317e9e84 100644 --- a/objects.mk +++ b/objects.mk @@ -21,6 +21,7 @@ OBJS=\ node/Poly1305.o \ node/Salsa20.o \ node/Service.o \ + node/SoftwareUpdater.o \ node/SHA512.o \ node/Switch.o \ node/SysEnv.o \ -- cgit v1.2.3 From d3bcc58074d608639176ce3cd30fa7c5307676b9 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Tue, 10 Dec 2013 16:13:07 -0800 Subject: Fix update URL stuff, fix main build, add update dummy for testing updates on OSX and Linux and such. --- attic/update-dummy/update-dummy.nfo | 6 ++++++ attic/update-dummy/update-dummy.sh | 4 ++++ main.cpp | 2 +- node/Defaults.cpp | 20 +++++++++++++++++++- 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 attic/update-dummy/update-dummy.nfo create mode 100644 attic/update-dummy/update-dummy.sh (limited to 'node/Defaults.cpp') diff --git a/attic/update-dummy/update-dummy.nfo b/attic/update-dummy/update-dummy.nfo new file mode 100644 index 00000000..2aa173e0 --- /dev/null +++ b/attic/update-dummy/update-dummy.nfo @@ -0,0 +1,6 @@ +vMajor=999 +vMinor=999 +vRevision=999 +signedBy=e9bc3707b5 +ed25519=ca7b943ace5451f420f1f599822d7013534a7cb7997096141e6a1aa6398c5f260c19dc5eecb297c922950f26dee7f9db787f8dbf85bc422baf3bff94c1131e086a7fc85c26dbb8c1b0a9cae63acc34998d9e1ce553156ea5638f9c99a50f6e2e +url=http://download.zerotier.com/update/update-dummy.sh diff --git a/attic/update-dummy/update-dummy.sh b/attic/update-dummy/update-dummy.sh new file mode 100644 index 00000000..cafb6f90 --- /dev/null +++ b/attic/update-dummy/update-dummy.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +echo "Dummy updater -- run with opts: $*" +exit 0 diff --git a/main.cpp b/main.cpp index 37d82bc8..4bb47cf0 100644 --- a/main.cpp +++ b/main.cpp @@ -475,7 +475,7 @@ int main(int argc,char **argv) try { node = new Node(homeDir,port,controlPort); switch(node->run()) { - case Node::NODE_NODE_RESTART_FOR_UPGRADE: { + case Node::NODE_RESTART_FOR_UPGRADE: { #ifdef __UNIX_LIKE__ const char *upgPath = node->reasonForTermination(); if (upgPath) diff --git a/node/Defaults.cpp b/node/Defaults.cpp index 566658fa..2588c85f 100644 --- a/node/Defaults.cpp +++ b/node/Defaults.cpp @@ -122,8 +122,26 @@ static inline std::map< Address,Identity > _mkUpdateAuth() return ua; } -static inline std::string _mkUpdateUrl() +static inline const char *_mkUpdateUrl() { +#if defined(__LINUX__) && ( defined(__i386__) || defined(__x86_64) || defined(__x86_64__) || defined(__amd64) || defined(__i386) ) + if (sizeof(void *) == 8) + return "http://download.zerotier.com/update/linux/x64/latest.nfo"; + else return "http://download.zerotier.com/update/linux/x86/latest.nfo"; +#define GOT_UPDATE_URL +#endif + +#ifdef __APPLE__ + // TODO: iOS? + return "http://download.zerotier.com/update/mac/combined/latest.nfo"; +#define GOT_UPDATE_URL +#endif + + // TODO: Windows + +#ifndef GOT_UPDATE_URL + return ""; +#endif } Defaults::Defaults() : -- cgit v1.2.3