From f7b1437154576ec42734de6c2b2ee4adfb1f4f6d Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Wed, 15 Apr 2015 17:00:26 -0700 Subject: Putting the main binary back together... --- node/NetworkController.hpp | 2 +- one.cpp | 678 +++++++++++++++++++++++++++++++++++++++++++- osdep/HttpClient.cpp | 590 -------------------------------------- osdep/HttpClient.hpp | 110 ------- osdep/OSUtils.hpp | 5 +- osdep/SoftwareUpdater.cpp | 328 --------------------- osdep/SoftwareUpdater.hpp | 186 ------------ updater/HttpClient.cpp | 590 ++++++++++++++++++++++++++++++++++++++ updater/HttpClient.hpp | 110 +++++++ updater/SoftwareUpdater.cpp | 328 +++++++++++++++++++++ updater/SoftwareUpdater.hpp | 186 ++++++++++++ 11 files changed, 1890 insertions(+), 1223 deletions(-) delete mode 100644 osdep/HttpClient.cpp delete mode 100644 osdep/HttpClient.hpp delete mode 100644 osdep/SoftwareUpdater.cpp delete mode 100644 osdep/SoftwareUpdater.hpp create mode 100644 updater/HttpClient.cpp create mode 100644 updater/HttpClient.hpp create mode 100644 updater/SoftwareUpdater.cpp create mode 100644 updater/SoftwareUpdater.hpp diff --git a/node/NetworkController.hpp b/node/NetworkController.hpp index 32b8f053..353c091a 100644 --- a/node/NetworkController.hpp +++ b/node/NetworkController.hpp @@ -41,7 +41,7 @@ namespace ZeroTier { class RuntimeEnvironment; /** - * Interface for network configuration (netconf) master implementations + * Interface for network controller implementations */ class NetworkController { diff --git a/one.cpp b/one.cpp index ad1472fc..b6967216 100644 --- a/one.cpp +++ b/one.cpp @@ -29,18 +29,688 @@ #include #include #include +#include +#include + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#include +#include +#include "windows/ZeroTierOne/ServiceInstaller.h" +#include "windows/ZeroTierOne/ServiceBase.h" +#include "windows/ZeroTierOne/ZeroTierOneService.h" +#else +#include +#include +#include +#include +#include +#include +#endif #include #include +#include "version.h" +#include "include/ZeroTierOne.h" +#include "node/Constants.hpp" +#include "node/Identity.hpp" +#include "node/CertificateOfMembership.hpp" +#include "node/Utils.hpp" +#include "osdep/OSUtils.hpp" #include "service/OneService.hpp" +#ifdef ZT_ENABLE_NETWORK_CONTROLLER +#include "controller/SqliteNetworkController.hpp" +#endif + +#define ZT1_AUTHTOKEN_SECRET_PATH "authtoken.secret" +#define ZT1_PID_PATH "zerotier-one.pid" using namespace ZeroTier; -int main(int argc,char **argv) +static OneService *zt1Service = (OneService *)0; + +/****************************************************************************/ +/* zerotier-cli personality */ +/****************************************************************************/ + +#ifdef __WINDOWS__ +int cli(int argc, _TCHAR* argv[]) +#else +int cli(int argc,char **argv) +#endif +{ +} + +/****************************************************************************/ +/* zerotier-idtool personality */ +/****************************************************************************/ + +static void idtoolPrintHelp(FILE *out,const char *pn) +{ + fprintf(out,"Usage: %s []"ZT_EOL_S""ZT_EOL_S"Commands:"ZT_EOL_S,pn); + fprintf(out," generate [] []"ZT_EOL_S); + fprintf(out," validate "ZT_EOL_S); + fprintf(out," getpublic "ZT_EOL_S); + fprintf(out," sign "ZT_EOL_S); + fprintf(out," verify "ZT_EOL_S); + fprintf(out," mkcom [ ...] (hexadecimal integers)"ZT_EOL_S); +} + +static Identity getIdFromArg(char *arg) { - One *one = One::newInstance("/tmp/foo",12345); - one->run(); - printf("termination reason: %d, message: %s\n",(int)one->reasonForTermination(),one->fatalErrorMessage().c_str()); + Identity id; + if ((strlen(arg) > 32)&&(arg[10] == ':')) { // identity is a literal on the command line + if (id.fromString(arg)) + return id; + } else { // identity is to be read from a file + std::string idser; + if (Utils::readFile(arg,idser)) { + if (id.fromString(idser)) + return id; + } + } + return Identity(); +} + +#ifdef __WINDOWS__ +int idtool(int argc, _TCHAR* argv[]) +#else +int idtool(int argc,char **argv) +#endif +{ + if (argc < 2) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + if (!strcmp(argv[1],"generate")) { + Identity id; + id.generate(); + std::string idser = id.toString(true); + if (argc >= 3) { + if (!Utils::writeFile(argv[2],idser)) { + fprintf(stderr,"Error writing to %s"ZT_EOL_S,argv[2]); + return 1; + } else printf("%s written"ZT_EOL_S,argv[2]); + if (argc >= 4) { + idser = id.toString(false); + if (!Utils::writeFile(argv[3],idser)) { + fprintf(stderr,"Error writing to %s"ZT_EOL_S,argv[3]); + return 1; + } else printf("%s written"ZT_EOL_S,argv[3]); + } + } else printf("%s",idser.c_str()); + } else if (!strcmp(argv[1],"validate")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s"ZT_EOL_S,argv[2]); + return 1; + } + + if (!id.locallyValidate()) { + fprintf(stderr,"%s FAILED validation."ZT_EOL_S,argv[2]); + return 1; + } else printf("%s is a valid identity"ZT_EOL_S,argv[2]); + } else if (!strcmp(argv[1],"getpublic")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s"ZT_EOL_S,argv[2]); + return 1; + } + + printf("%s",id.toString(false).c_str()); + } else if (!strcmp(argv[1],"sign")) { + if (argc < 4) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s"ZT_EOL_S,argv[2]); + return 1; + } + + if (!id.hasPrivate()) { + fprintf(stderr,"%s does not contain a private key (must use private to sign)"ZT_EOL_S,argv[2]); + return 1; + } + + std::string inf; + if (!Utils::readFile(argv[3],inf)) { + fprintf(stderr,"%s is not readable"ZT_EOL_S,argv[3]); + return 1; + } + C25519::Signature signature = id.sign(inf.data(),(unsigned int)inf.length()); + printf("%s",Utils::hex(signature.data,(unsigned int)signature.size()).c_str()); + } else if (!strcmp(argv[1],"verify")) { + if (argc < 4) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if (!id) { + fprintf(stderr,"Identity argument invalid or file unreadable: %s"ZT_EOL_S,argv[2]); + return 1; + } + + std::string inf; + if (!Utils::readFile(argv[3],inf)) { + fprintf(stderr,"%s is not readable"ZT_EOL_S,argv[3]); + return 1; + } + + std::string signature(Utils::unhex(argv[4])); + if ((signature.length() > ZT_ADDRESS_LENGTH)&&(id.verify(inf.data(),(unsigned int)inf.length(),signature.data(),(unsigned int)signature.length()))) { + printf("%s signature valid"ZT_EOL_S,argv[3]); + } else { + fprintf(stderr,"%s signature check FAILED"ZT_EOL_S,argv[3]); + return 1; + } + } else if (!strcmp(argv[1],"mkcom")) { + if (argc < 3) { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + + Identity id = getIdFromArg(argv[2]); + if ((!id)||(!id.hasPrivate())) { + fprintf(stderr,"Identity argument invalid, does not include private key, or file unreadable: %s"ZT_EOL_S,argv[2]); + return 1; + } + + CertificateOfMembership com; + for(int a=3;a params(Utils::split(argv[a],",","","")); + if (params.size() == 3) { + uint64_t qId = Utils::hexStrToU64(params[0].c_str()); + uint64_t qValue = Utils::hexStrToU64(params[1].c_str()); + uint64_t qMaxDelta = Utils::hexStrToU64(params[2].c_str()); + com.setQualifier(qId,qValue,qMaxDelta); + } + } + if (!com.sign(id)) { + fprintf(stderr,"Signature of certificate of membership failed."ZT_EOL_S); + return 1; + } + + printf("%s",com.toString().c_str()); + } else { + idtoolPrintHelp(stdout,argv[0]); + return 1; + } + return 0; } + +/****************************************************************************/ +/* Unix helper functions and signal handlers */ +/****************************************************************************/ + +#ifdef __UNIX_LIKE__ +static void _sighandlerHup(int sig) +{ + Node *n = node; + if (n) + n->resync(); +} +static void _sighandlerQuit(int sig) +{ + Node *n = node; + if (n) + n->terminate(Node::NODE_NORMAL_TERMINATION,"terminated by signal"); + else exit(0); +} +#endif + +/****************************************************************************/ +/* Windows helper functions and signal handlers */ +/****************************************************************************/ + +#ifdef __WINDOWS__ +// Console signal handler routine to allow CTRL+C to work, mostly for testing +static BOOL WINAPI _winConsoleCtrlHandler(DWORD dwCtrlType) +{ + switch(dwCtrlType) { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_SHUTDOWN_EVENT: + Node *n = node; + if (n) + n->terminate(Node::NODE_NORMAL_TERMINATION,"terminated by signal"); + return TRUE; + } + return FALSE; +} + +// Pokes a hole in the Windows firewall (advfirewall) for the running program +static void _winPokeAHole() +{ + char myPath[MAX_PATH]; + DWORD ps = GetModuleFileNameA(NULL,myPath,sizeof(myPath)); + if ((ps > 0)&&(ps < (DWORD)sizeof(myPath))) { + STARTUPINFOA startupInfo; + PROCESS_INFORMATION processInfo; + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall delete rule name=\"ZeroTier One\" program=\"") + myPath + "\"").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=in action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + startupInfo.cb = sizeof(startupInfo); + memset(&startupInfo,0,sizeof(STARTUPINFOA)); + memset(&processInfo,0,sizeof(PROCESS_INFORMATION)); + if (CreateProcessA(NULL,(LPSTR)(std::string("C:\\Windows\\System32\\netsh.exe advfirewall firewall add rule name=\"ZeroTier One\" dir=out action=allow program=\"") + myPath + "\" enable=yes").c_str(),NULL,NULL,FALSE,0,NULL,NULL,&startupInfo,&processInfo)) { + WaitForSingleObject(processInfo.hProcess,INFINITE); + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + } +} + +// Returns true if this is running as the local administrator +static BOOL IsCurrentUserLocalAdministrator(void) +{ + BOOL fReturn = FALSE; + DWORD dwStatus; + DWORD dwAccessMask; + DWORD dwAccessDesired; + DWORD dwACLSize; + DWORD dwStructureSize = sizeof(PRIVILEGE_SET); + PACL pACL = NULL; + PSID psidAdmin = NULL; + + HANDLE hToken = NULL; + HANDLE hImpersonationToken = NULL; + + PRIVILEGE_SET ps; + GENERIC_MAPPING GenericMapping; + + PSECURITY_DESCRIPTOR psdAdmin = NULL; + SID_IDENTIFIER_AUTHORITY SystemSidAuthority = SECURITY_NT_AUTHORITY; + + const DWORD ACCESS_READ = 1; + const DWORD ACCESS_WRITE = 2; + + __try + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_DUPLICATE|TOKEN_QUERY,TRUE,&hToken)) + { + if (GetLastError() != ERROR_NO_TOKEN) + __leave; + if (!OpenProcessToken(GetCurrentProcess(),TOKEN_DUPLICATE|TOKEN_QUERY, &hToken)) + __leave; + } + if (!DuplicateToken (hToken, SecurityImpersonation,&hImpersonationToken)) + __leave; + if (!AllocateAndInitializeSid(&SystemSidAuthority, 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, &psidAdmin)) + __leave; + psdAdmin = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH); + if (psdAdmin == NULL) + __leave; + if (!InitializeSecurityDescriptor(psdAdmin,SECURITY_DESCRIPTOR_REVISION)) + __leave; + dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psidAdmin) - sizeof(DWORD); + pACL = (PACL)LocalAlloc(LPTR, dwACLSize); + if (pACL == NULL) + __leave; + if (!InitializeAcl(pACL, dwACLSize, ACL_REVISION2)) + __leave; + dwAccessMask= ACCESS_READ | ACCESS_WRITE; + if (!AddAccessAllowedAce(pACL, ACL_REVISION2, dwAccessMask, psidAdmin)) + __leave; + if (!SetSecurityDescriptorDacl(psdAdmin, TRUE, pACL, FALSE)) + __leave; + + SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE); + SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE); + + if (!IsValidSecurityDescriptor(psdAdmin)) + __leave; + dwAccessDesired = ACCESS_READ; + + GenericMapping.GenericRead = ACCESS_READ; + GenericMapping.GenericWrite = ACCESS_WRITE; + GenericMapping.GenericExecute = 0; + GenericMapping.GenericAll = ACCESS_READ | ACCESS_WRITE; + + if (!AccessCheck(psdAdmin, hImpersonationToken, dwAccessDesired, + &GenericMapping, &ps, &dwStructureSize, &dwStatus, + &fReturn)) + { + fReturn = FALSE; + __leave; + } + } + __finally + { + // Clean up. + if (pACL) LocalFree(pACL); + if (psdAdmin) LocalFree(psdAdmin); + if (psidAdmin) FreeSid(psidAdmin); + if (hImpersonationToken) CloseHandle (hImpersonationToken); + if (hToken) CloseHandle (hToken); + } + + return fReturn; +} +#endif // __WINDOWS__ + +/****************************************************************************/ +/* main() and friends */ +/****************************************************************************/ + +static void printHelp(const char *cn,FILE *out) +{ + fprintf(out,"ZeroTier One version %d.%d.%d"ZT_EOL_S"(c)2011-2015 ZeroTier, Inc."ZT_EOL_S,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + fprintf(out,"Licensed under the GNU General Public License v3"ZT_EOL_S""ZT_EOL_S); + +#ifdef ZT_AUTO_UPDATE + fprintf(out,"Auto-update enabled build, will update from URL:"ZT_EOL_S); + fprintf(out," %s"ZT_EOL_S,ZT_DEFAULTS.updateLatestNfoURL.c_str()); + fprintf(out,"Update authentication signing authorities: "ZT_EOL_S); + int no = 0; + for(std::map< Address,Identity >::const_iterator sa(ZT_DEFAULTS.updateAuthorities.begin());sa!=ZT_DEFAULTS.updateAuthorities.end();++sa) { + if (no == 0) + fprintf(out," %s",sa->first.toString().c_str()); + else fprintf(out,", %s",sa->first.toString().c_str()); + if (++no == 6) { + fprintf(out,ZT_EOL_S); + no = 0; + } + } + fprintf(out,ZT_EOL_S""ZT_EOL_S); +#endif // ZT_AUTO_UPDATE + + fprintf(out,"Usage: %s [-switches] [home directory] [-q ]"ZT_EOL_S""ZT_EOL_S,cn); + fprintf(out,"Available switches:"ZT_EOL_S); + fprintf(out," -h - Display this help"ZT_EOL_S); + fprintf(out," -v - Show version"ZT_EOL_S); + fprintf(out," -p - Port for UDP (default: 9993)"ZT_EOL_S); + //fprintf(out," -T - Override root topology, do not authenticate or update"ZT_EOL_S); +#ifdef __UNIX_LIKE__ + fprintf(out," -d - Fork and run as daemon (Unix-ish OSes)"ZT_EOL_S); +#endif // __UNIX_LIKE__ + fprintf(out," -q - Send a query to a running service (zerotier-cli)"ZT_EOL_S); + fprintf(out," -i - Generate and manage identities (zerotier-idtool)"ZT_EOL_S); +#ifdef __WINDOWS__ + fprintf(out," -C - Run from command line instead of as service (Windows)"ZT_EOL_S); + fprintf(out," -I - Install Windows service (Windows)"ZT_EOL_S); + fprintf(out," -R - Uninstall Windows service (Windows)"ZT_EOL_S); + fprintf(out," -D - Load tap driver into system driver store (Windows)"ZT_EOL_S); +#endif // __WINDOWS__ +} + +#ifdef __WINDOWS__ +int _tmain(int argc, _TCHAR* argv[]) +#else +int main(int argc,char **argv) +#endif +{ +#ifdef __UNIX_LIKE__ + signal(SIGHUP,&_sighandlerHup); + signal(SIGPIPE,SIG_IGN); + signal(SIGUSR1,SIG_IGN); + signal(SIGUSR2,SIG_IGN); + signal(SIGALRM,SIG_IGN); + signal(SIGINT,&_sighandlerQuit); + signal(SIGTERM,&_sighandlerQuit); + signal(SIGQUIT,&_sighandlerQuit); + + /* Ensure that there are no inherited file descriptors open from a previous + * incarnation. This is a hack to ensure that GitHub issue #61 or variants + * of it do not return, and should not do anything otherwise bad. */ + { + int mfd = STDIN_FILENO; + if (STDOUT_FILENO > mfd) mfd = STDOUT_FILENO; + if (STDERR_FILENO > mfd) mfd = STDERR_FILENO; + for(int f=mfd+1;f<1024;++f) + ::close(f); + } + + bool runAsDaemon = false; +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2),&wsaData); + +#ifdef ZT_WIN_RUN_IN_CONSOLE + bool winRunFromCommandLine = true; +#else + bool winRunFromCommandLine = false; +#endif +#endif // __WINDOWS__ + + if ((strstr(argv[0],"zerotier-cli"))||(strstr(argv[0],"ZEROTIER-CLI"))) + return cli(argc,argv); + if ((strstr(argv[0],"zerotier-idtool"))||(strstr(argv[0],"ZEROTIER-IDTOOL"))) + return idtool(argc,argv); + + std::string overrideRootTopology; + std::string homeDir; + unsigned int port = ZT1_DEFAULT_PORT; + + for(int i=1;i 0xffff)||(port == 0)) { + printHelp(argv[0],stdout); + return 1; + } + break; + + case 't': // TCP port -- ignore, since we now bind to both UDP and TCP on the same port + break; + +#ifdef __UNIX_LIKE__ + case 'd': // Run in background as daemon + runAsDaemon = true; + break; +#endif // __UNIX_LIKE__ + + case 'T': // Override root topology + if (argv[i][2]) { + if (!OSUtils::readFile(argv[i] + 2,overrideRootTopology)) { + fprintf(stderr,"%s: cannot read root topology from %s"ZT_EOL_S,argv[0],argv[i] + 2); + return 1; + } + } else { + printHelp(argv[0],stdout); + return 1; + } + break; + + case 'v': // Display version + printf("%d.%d.%d"ZT_EOL_S,ZEROTIER_ONE_VERSION_MAJOR,ZEROTIER_ONE_VERSION_MINOR,ZEROTIER_ONE_VERSION_REVISION); + return 0; + + case 'q': // Invoke cli personality + if (argv[i][2]) { + printHelp(argv[0],stdout); + return 0; + } else return cli(argc,argv); + + case 'i': // Invoke idtool personality + if (argv[i][2]) { + printHelp(argv[0],stdout); + return 0; + } else return idtool(argc,argv); + +#ifdef __WINDOWS__ + case 'C': // Run from command line instead of as Windows service + winRunFromCommandLine = true; + break; + + case 'I': { // Install this binary as a Windows service + if (IsCurrentUserLocalAdministrator() != TRUE) { + fprintf(stderr,"%s: must be run as a local administrator."ZT_EOL_S,argv[0]); + return 1; + } + std::string ret(InstallService(ZT_SERVICE_NAME,ZT_SERVICE_DISPLAY_NAME,ZT_SERVICE_START_TYPE,ZT_SERVICE_DEPENDENCIES,ZT_SERVICE_ACCOUNT,ZT_SERVICE_PASSWORD)); + if (ret.length()) { + fprintf(stderr,"%s: unable to install service: %s"ZT_EOL_S,argv[0],ret.c_str()); + return 3; + } + return 0; + } break; + + case 'R': { // Uninstall this binary as Windows service + if (IsCurrentUserLocalAdministrator() != TRUE) { + fprintf(stderr,"%s: must be run as a local administrator."ZT_EOL_S,argv[0]); + return 1; + } + std::string ret(UninstallService(ZT_SERVICE_NAME)); + if (ret.length()) { + fprintf(stderr,"%s: unable to uninstall service: %s"ZT_EOL_S,argv[0],ret.c_str()); + return 3; + } + return 0; + } break; + +#if 0 + case 'D': { // Install Windows driver (since PNPUTIL.EXE seems to be weirdly unreliable) + std::string pathToInf; +#ifdef _WIN64 + pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x64\\zttap200.inf"; +#else + pathToInf = ZT_DEFAULTS.defaultHomePath + "\\tap-windows\\x86\\zttap200.inf"; +#endif + printf("Installing ZeroTier One virtual Ethernet port driver."ZT_EOL_S""ZT_EOL_S"NOTE: If you don't see a confirmation window to allow driver installation,"ZT_EOL_S"check to make sure it didn't appear under the installer."ZT_EOL_S); + BOOL needReboot = FALSE; + if (DiInstallDriverA(NULL,pathToInf.c_str(),DIIRFLAG_FORCE_INF,&needReboot)) { + printf("%s: driver successfully installed from %s"ZT_EOL_S,argv[0],pathToInf.c_str()); + return 0; + } else { + printf("%s: failed installing %s: %d"ZT_EOL_S,argv[0],pathToInf.c_str(),(int)GetLastError()); + return 3; + } + } break; +#endif // __WINDOWS__ +#endif + + case 'h': + case '?': + default: + printHelp(argv[0],stdout); + return 0; + } + } else { + if (homeDir.length()) { + printHelp(argv[0],stdout); + return 0; + } else { + homeDir = argv[i]; + } + } + } + + if (!homeDir.length()) + homeDir = OneService::platformDefaultHomePath(); + + OSUtils::mkdir(homeDir.c_str()); + + std::string authToken; + { + std::string authTokenPath(homeDir + ZT_PATH_SEPARATOR_S + ZT1_AUTHTOKEN_SECRET_PATH); + if (!OSUtils::readFile(authTokenPath.c_str(),authToken)) { + unsigned char foo[24]; + Utils::getSecureRandom(foo,sizeof(foo)); + authToken = ""; + for(unsigned int i=0;i 0) + return 0; // forked + // else p == 0, so we are daemonized + } + + { + // Write .pid file to home folder + std::string pidPath(homeDir + ZT_PATH_SEPARATOR_S + ZT1_PID_PATH); + FILE *pf = fopen(pidPath.c_str(),"w"); + if (pf) { + fprintf(pf,"%ld",(long)getpid()); + fclose(pf); + } + } +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + if (winRunFromCommandLine) { + // Running in "interactive" mode (mostly for debugging) + if (IsCurrentUserLocalAdministrator() != TRUE) { + fprintf(stderr,"%s: must be run as a local administrator."ZT_EOL_S,argv[0]); + return 1; + } + _winPokeAHole(); + SetConsoleCtrlHandler(&_winConsoleCtrlHandler,TRUE); + // continues on to ordinary command line execution code below... + } else { + // Running from service manager + _winPokeAHole(); + ZeroTierOneService zt1Service; + if (CServiceBase::Run(zt1Service) == TRUE) { + return 0; + } else { + fprintf(stderr,"%s: unable to start service (try -h for help)"ZT_EOL_S,argv[0]); + return 1; + } + } +#endif // __WINDOWS__ + +} diff --git a/osdep/HttpClient.cpp b/osdep/HttpClient.cpp deleted file mode 100644 index 1cf78204..00000000 --- a/osdep/HttpClient.cpp +++ /dev/null @@ -1,590 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * 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 "../node/Constants.hpp" - -#include -#include -#include - -#ifdef __WINDOWS__ -#include -#include -#include -#include -#include -#endif // __WINDOWS__ - -#ifdef __UNIX_LIKE__ -#include -#include -#include -#include -#include -#include -#include -#include -#endif // __UNIX_LIKE__ - -#include -#include -#include - -#include "HttpClient.hpp" -#include "Thread.hpp" -#include "OSUtils.hpp" -#include "../node/Utils.hpp" - -namespace ZeroTier { - -#ifdef __UNIX_LIKE__ - -// The *nix implementation calls 'curl' externally rather than linking to it. -// This makes it an optional dependency that can be avoided in tiny systems -// provided you don't want to have automatic software updates... or want to -// do them via another method. - -#ifdef __APPLE__ -// TODO: get proxy configuration -#endif - -// Paths where "curl" may be found on the system -#define NUM_CURL_PATHS 6 -static const char *CURL_PATHS[NUM_CURL_PATHS] = { "/usr/bin/curl","/bin/curl","/usr/local/bin/curl","/usr/sbin/curl","/sbin/curl","/usr/libexec/curl" }; - -// Maximum message length -#define CURL_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) - -// Internal private thread class that performs request, notifies handler, -// and then commits suicide by deleting itself. -class HttpClient_Private_Request -{ -public: - HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : - _url(url), - _headers(headers), - _timeout(timeout), - _handler(handler), - _arg(arg), - _parent(parent), - _pid(0), - _cancelled(false) - { - _myThread = Thread::start(this); - } - - ~HttpClient_Private_Request() - { - Mutex::Lock _l(_parent->_requests_m); - _parent->_requests.erase((HttpClient::Request)this); - } - - void threadMain() - { - char *curlArgs[1024]; - char buf[16384]; - fd_set readfds,writefds,errfds; - struct timeval tv; - - std::string curlPath; - for(int i=0;i(curlPath.c_str()); - curlArgs[1] = const_cast ("-D"); - curlArgs[2] = const_cast ("-"); // append headers before output - int argPtr = 3; - std::vector headerArgs; - for(std::map::const_iterator h(_headers.begin());h!=_headers.end();++h) { - headerArgs.push_back(h->first); - headerArgs.back().append(": "); - headerArgs.back().append(h->second); - } - for(std::vector::iterator h(headerArgs.begin());h!=headerArgs.end();++h) { - if (argPtr >= (1024 - 4)) // leave room for terminating NULL and URL - break; - curlArgs[argPtr++] = const_cast ("-H"); - curlArgs[argPtr++] = const_cast (h->c_str()); - } - curlArgs[argPtr++] = const_cast (_url.c_str()); - curlArgs[argPtr] = (char *)0; - - if (_cancelled) { - delete this; - return; - } - - int curlStdout[2]; - int curlStderr[2]; - ::pipe(curlStdout); - ::pipe(curlStderr); - - _pid = (long)vfork(); - if (_pid < 0) { - // fork() failed - ::close(curlStdout[0]); - ::close(curlStdout[1]); - ::close(curlStderr[0]); - ::close(curlStderr[1]); - _doH(_arg,-1,_url,"unable to fork()"); - delete this; - return; - } else if (_pid > 0) { - // fork() succeeded, in parent process - ::close(curlStdout[1]); - ::close(curlStderr[1]); - fcntl(curlStdout[0],F_SETFL,O_NONBLOCK); - fcntl(curlStderr[0],F_SETFL,O_NONBLOCK); - - int exitCode = -1; - unsigned long long timesOutAt = OSUtils::now() + ((unsigned long long)_timeout * 1000ULL); - bool timedOut = false; - bool tooLong = false; - - while (!_cancelled) { - FD_ZERO(&readfds); - FD_ZERO(&writefds); - FD_ZERO(&errfds); - FD_SET(curlStdout[0],&readfds); - FD_SET(curlStderr[0],&readfds); - FD_SET(curlStdout[0],&errfds); - FD_SET(curlStderr[0],&errfds); - tv.tv_sec = 1; - tv.tv_usec = 0; - select(std::max(curlStdout[0],curlStderr[0])+1,&readfds,&writefds,&errfds,&tv); - - if (FD_ISSET(curlStdout[0],&readfds)) { - int n = (int)::read(curlStdout[0],buf,sizeof(buf)); - if (n > 0) { - _body.append(buf,n); - // Reset timeout when data is read... - timesOutAt = OSUtils::now() + ((unsigned long long)_timeout * 1000ULL); - } else if (n < 0) - break; - if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { - tooLong = true; - break; - } - } - - if (FD_ISSET(curlStderr[0],&readfds)) - ::read(curlStderr[0],buf,sizeof(buf)); - - if (FD_ISSET(curlStdout[0],&errfds)||FD_ISSET(curlStderr[0],&errfds)) - break; - - if (OSUtils::now() >= timesOutAt) { - timedOut = true; - break; - } - - 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; - } - } - - if (_pid > 0) { - ::kill(_pid,SIGKILL); - waitpid(_pid,&exitCode,0); - } - _pid = 0; - - ::close(curlStdout[0]); - ::close(curlStderr[0]); - - if (timedOut) - _doH(_arg,-1,_url,"connection timed out"); - else if (tooLong) - _doH(_arg,-1,_url,"response too long"); - else if (exitCode) - _doH(_arg,-1,_url,"connection failed (curl returned non-zero exit code)"); - else { - unsigned long idx = 0; - - // Grab status line and headers, which will prefix output on - // success and will end with an empty line. - std::vector headers; - headers.push_back(std::string()); - while (idx < _body.length()) { - char c = _body[idx++]; - if (c == '\n') { - if (!headers.back().length()) { - headers.pop_back(); - break; - } else headers.push_back(std::string()); - } else if (c != '\r') - headers.back().push_back(c); - } - if (headers.empty()||(!headers.front().length())) { - _doH(_arg,-1,_url,"HTTP response empty"); - delete this; - return; - } - - // Parse first line -- HTTP status code and response - size_t scPos = headers.front().find(' '); - if (scPos == std::string::npos) { - _doH(_arg,-1,_url,"invalid HTTP response (no status line)"); - delete this; - return; - } - ++scPos; - unsigned int rcode = Utils::strToUInt(headers.front().substr(scPos,3).c_str()); - if ((!rcode)||(rcode > 999)) { - _doH(_arg,-1,_url,"invalid HTTP response (invalid response code)"); - delete this; - return; - } - - // Serve up the resulting data to the handler - if (rcode == 200) - _doH(_arg,rcode,_url,_body.substr(idx)); - else if ((scPos + 4) < headers.front().length()) - _doH(_arg,rcode,_url,headers.front().substr(scPos+4)); - else _doH(_arg,rcode,_url,"(no status message from server)"); - } - - delete this; - return; - } else { - // fork() succeeded, in child process - ::dup2(curlStdout[1],STDOUT_FILENO); - ::close(curlStdout[1]); - ::dup2(curlStderr[1],STDERR_FILENO); - ::close(curlStderr[1]); - ::execv(curlPath.c_str(),curlArgs); - ::exit(-1); // only reached if execv() fails - } - } - - inline void cancel() - { - { - Mutex::Lock _l(_cancelled_m); - _cancelled = true; - if (_pid > 0) - ::kill(_pid,SIGKILL); - } - Thread::join(_myThread); - } - -private: - inline void _doH(void *arg,int code,const std::string &url,const std::string &body) - { - Mutex::Lock _l(_cancelled_m); - try { - if ((!_cancelled)&&(_handler)) - _handler(arg,code,url,body); - } catch ( ... ) {} - } - - const std::string _url; - std::string _body; - std::map _headers; - unsigned int _timeout; - void (*_handler)(void *,int,const std::string &,const std::string &); - void *_arg; - HttpClient *_parent; - long _pid; - volatile bool _cancelled; - Mutex _cancelled_m; - Thread _myThread; -}; - -#endif // __UNIX_LIKE__ - -#ifdef __WINDOWS__ - -#define WIN_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) - -// Internal private thread class that performs request, notifies handler, -// and then commits suicide by deleting itself. -class HttpClient_Private_Request : NonCopyable -{ -public: - HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : - _url(url), - _headers(headers), - _timeout(timeout), - _handler(handler), - _arg(arg), - _parent(parent), - _hRequest((HINTERNET)0) - { - _myThread = Thread::start(this); - } - - ~HttpClient_Private_Request() - { - Mutex::Lock _l(_parent->_requests_m); - _parent->_requests.erase((HttpClient::Request)this); - } - - void threadMain() - { - HINTERNET hSession = (HINTERNET)0; - HINTERNET hConnect = (HINTERNET)0; - HINTERNET hRequest = (HINTERNET)0; - - try { - hSession = WinHttpOpen(L"ZeroTier One HttpClient/1.0 (WinHttp)",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,WINHTTP_NO_PROXY_NAME,WINHTTP_NO_PROXY_BYPASS,0); - if (!hSession) { - _handler(_arg,-1,_url,"WinHttpOpen() failed"); - goto closeAndReturnFromHttp; - } - int timeoutMs = (int)_timeout * 1000; - WinHttpSetTimeouts(hSession,timeoutMs,timeoutMs,timeoutMs,timeoutMs); - - std::wstring_convert< std::codecvt_utf8 > wcconv; - std::wstring wurl(wcconv.from_bytes(_url)); - - URL_COMPONENTS uc; - memset(&uc,0,sizeof(uc)); - uc.dwStructSize = sizeof(uc); - uc.dwSchemeLength = -1; - uc.dwHostNameLength = -1; - uc.dwUrlPathLength = -1; - uc.dwExtraInfoLength = -1; - if (!WinHttpCrackUrl(wurl.c_str(),(DWORD)wurl.length(),0,&uc)) { - _handler(_arg,-1,_url,"unable to parse URL: WinHttpCrackUrl() failed"); - goto closeAndReturnFromHttp; - } - if ((!uc.lpszHostName)||(!uc.lpszUrlPath)||(!uc.lpszScheme)||(uc.dwHostNameLength <= 0)||(uc.dwUrlPathLength <= 0)||(uc.dwSchemeLength <= 0)) { - _handler(_arg,-1,_url,"unable to parse URL: missing scheme, host name, or path"); - goto closeAndReturnFromHttp; - } - std::wstring urlScheme(uc.lpszScheme,uc.dwSchemeLength); - std::wstring urlHostName(uc.lpszHostName,uc.dwHostNameLength); - std::wstring urlPath(uc.lpszUrlPath,uc.dwUrlPathLength); - if ((uc.lpszExtraInfo)&&(uc.dwExtraInfoLength > 0)) - urlPath.append(uc.lpszExtraInfo,uc.dwExtraInfoLength); - - if (urlScheme != L"http") { - _handler(_arg,-1,_url,"only 'http' scheme is supported"); - goto closeAndReturnFromHttp; - } - - hConnect = WinHttpConnect(hSession,urlHostName.c_str(),((uc.nPort > 0) ? uc.nPort : 80),0); - if (!hConnect) { - _handler(_arg,-1,_url,"connection failed"); - goto closeAndReturnFromHttp; - } - - { - Mutex::Lock _rl(_hRequest_m); - _hRequest = WinHttpOpenRequest(hConnect,L"GET",urlPath.c_str(),NULL,WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,0); - if (!_hRequest) { - _handler(_arg,-1,_url,"error sending request (1)"); - goto closeAndReturnFromHttp; - } - if (!WinHttpSendRequest(_hRequest,WINHTTP_NO_ADDITIONAL_HEADERS,0,WINHTTP_NO_REQUEST_DATA,0,0,0)) { - _handler(_arg,-1,_url,"error sending request (2)"); - goto closeAndReturnFromHttp; - } - hRequest = _hRequest; - } - - if (WinHttpReceiveResponse(hRequest,NULL)) { - DWORD dwStatusCode = 0; - DWORD dwTmp = sizeof(dwStatusCode); - WinHttpQueryHeaders(hRequest,WINHTTP_QUERY_STATUS_CODE| WINHTTP_QUERY_FLAG_NUMBER,NULL,&dwStatusCode,&dwTmp,NULL); - - DWORD dwSize; - do { - dwSize = 0; - if (!WinHttpQueryDataAvailable(hRequest,&dwSize)) { - _handler(_arg,-1,_url,"receive error (1)"); - goto closeAndReturnFromHttp; - } - - { - Mutex::Lock _rl(_hRequest_m); - if (!_hRequest) { - _handler(_arg,-1,_url,"request cancelled"); - goto closeAndReturnFromHttp; - } - } - - char *outBuffer = new char[dwSize]; - DWORD dwRead = 0; - if (!WinHttpReadData(hRequest,(LPVOID)outBuffer,dwSize,&dwRead)) { - _handler(_arg,-1,_url,"receive error (2)"); - goto closeAndReturnFromHttp; - } - - { - Mutex::Lock _rl(_hRequest_m); - if (!_hRequest) { - _handler(_arg,-1,_url,"request cancelled"); - goto closeAndReturnFromHttp; - } - - _body.append(outBuffer,dwRead); - delete [] outBuffer; - if (_body.length() > WIN_MAX_MESSAGE_LENGTH) { - _handler(_arg,-1,_url,"result too large"); - goto closeAndReturnFromHttp; - } - } - } while ((dwSize > 0)&&(_hRequest)); - - { - Mutex::Lock _rl(_hRequest_m); - if (!_hRequest) { - _handler(_arg,-1,_url,"request cancelled"); - goto closeAndReturnFromHttp; - } - - _handler(_arg,dwStatusCode,_url,_body); - } - } else { - _handler(_arg,-1,_url,"receive response failed"); - } - } catch ( ... ) { - _handler(_arg,-1,_url,"unexpected exception"); - } - -closeAndReturnFromHttp: - { - Mutex::Lock _rl(_hRequest_m); - if (_hRequest) { - WinHttpCloseHandle(_hRequest); - _hRequest = (HINTERNET)0; - } - } - if (hConnect) - WinHttpCloseHandle(hConnect); - if (hSession) - WinHttpCloseHandle(hSession); - delete this; - return; - } - - inline void cancel() - { - Mutex::Lock _rl(_hRequest_m); - if (_hRequest) { - WinHttpCloseHandle(_hRequest); - _hRequest = (HINTERNET)0; - } - } - - const std::string _url; - std::string _body; - std::map _headers; - unsigned int _timeout; - void (*_handler)(void *,int,const std::string &,const std::string &); - void *_arg; - HttpClient *_parent; - HINTERNET _hRequest; - Mutex _hRequest_m; - Thread _myThread; -}; - -#endif // __WINDOWS__ - -const std::map HttpClient::NO_HEADERS; - -HttpClient::HttpClient() -{ -} - -HttpClient::~HttpClient() -{ - std::set reqs; - { - Mutex::Lock _l(_requests_m); - reqs = _requests; - } - - for(std::set::iterator r(reqs.begin());r!=reqs.end();++r) - this->cancel(*r); - - for(;;) { - _requests_m.lock(); - if (_requests.empty()) { - _requests_m.unlock(); - break; - } else { - _requests_m.unlock(); - Thread::sleep(250); - } - } -} - -void HttpClient::cancel(HttpClient::Request req) -{ - Mutex::Lock _l(_requests_m); - if (_requests.count(req) == 0) - return; - ((HttpClient_Private_Request *)req)->cancel(); -} - -HttpClient::Request HttpClient::_do( - const char *method, - const std::string &url, - const std::map &headers, - unsigned int timeout, - void (*handler)(void *,int,const std::string &,const std::string &), - void *arg) -{ - HttpClient::Request r = (HttpClient::Request)(new HttpClient_Private_Request(this,method,url,headers,timeout,handler,arg)); - Mutex::Lock _l(_requests_m); - _requests.insert(r); - return r; -} - -} // namespace ZeroTier diff --git a/osdep/HttpClient.hpp b/osdep/HttpClient.hpp deleted file mode 100644 index 00400a8c..00000000 --- a/osdep/HttpClient.hpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * 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_HTTPCLIENT_HPP -#define ZT_HTTPCLIENT_HPP - -#include -#include -#include - -#include "../node/Mutex.hpp" - -namespace ZeroTier { - -class HttpClient_Private_Request; - -/** - * HTTP client that does queries in the background - * - * The handler method takes the following arguments: an arbitrary pointer, an - * HTTP response code, the URL queried, whether or not the message body was - * stored on disk, and the message body. - * - * If stored on disk, the body string contains the path and the file must be - * moved or deleted by the receiver when it's done. If an error occurs, the - * response code will be negative and the body will be the error message. - * - * All headers in the returned headers map will have their header names - * converted to lower case, e.g. "content-type". - * - * Currently only the "http" transport is guaranteed to be supported on all - * platforms. - */ -class HttpClient -{ -public: - friend class HttpClient_Private_Request; - typedef void * Request; - - HttpClient(); - ~HttpClient(); - - /** - * Empty map for convenience use - */ - static const std::map NO_HEADERS; - - /** - * Request a URL using the GET method - */ - inline Request GET( - const std::string &url, - const std::map &headers, - unsigned int timeout, - void (*handler)(void *,int,const std::string &,const std::string &), - void *arg) - { - return _do("GET",url,headers,timeout,handler,arg); - } - - /** - * Cancel a request - * - * If the request is not active, this does nothing. This may take some time - * depending on HTTP implementation. It may also not kill instantly, but - * it will prevent the handler function from ever being called and cause the - * request to die silently when complete. - */ - void cancel(Request req); - -private: - Request _do( - const char *method, - const std::string &url, - const std::map &headers, - unsigned int timeout, - void (*handler)(void *,int,const std::string &,const std::string &), - void *arg); - - std::set _requests; - Mutex _requests_m; -}; - -} // namespace ZeroTier - -#endif diff --git a/osdep/OSUtils.hpp b/osdep/OSUtils.hpp index 142f0aed..fe054ba2 100644 --- a/osdep/OSUtils.hpp +++ b/osdep/OSUtils.hpp @@ -219,10 +219,7 @@ public: * @param s Data to write * @return True if entire file was successfully written */ - static inline bool writeFile(const char *path,const std::string &s) - { - return writeFile(path,s.data(),(unsigned int)s.length()); - } + static inline bool writeFile(const char *path,const std::string &s) { return writeFile(path,s.data(),(unsigned int)s.length()); } }; } // namespace ZeroTier diff --git a/osdep/SoftwareUpdater.cpp b/osdep/SoftwareUpdater.cpp deleted file mode 100644 index e3789bcb..00000000 --- a/osdep/SoftwareUpdater.cpp +++ /dev/null @@ -1,328 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * 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 - -#include "../version.h" - -#include "Constants.hpp" -#include "SoftwareUpdater.hpp" -#include "Dictionary.hpp" -#include "C25519.hpp" -#include "Identity.hpp" -#include "Logger.hpp" -#include "RuntimeEnvironment.hpp" -#include "Thread.hpp" -#include "Node.hpp" -#include "Utils.hpp" -#include "HttpClient.hpp" - -#ifdef __UNIX_LIKE__ -#include -#include -#include -#include -#endif - -namespace ZeroTier { - -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; -} - -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/ZeroTierOneInstaller-linux-x64-LATEST.nfo"; - else return "http://download.zerotier.com/ZeroTierOneInstaller-linux-x86-LATEST.nfo"; -#define GOT_UPDATE_URL -#endif - -#ifdef __APPLE__ - return "http://download.zerotier.com/ZeroTierOneInstaller-mac-combined-LATEST.nfo"; -#define GOT_UPDATE_URL -#endif - -#ifdef __WINDOWS__ - return "http://download.zerotier.com/ZeroTierOneInstaller-windows-intel-LATEST.nfo"; -#define GOT_UPDATE_URL -#endif - -#ifndef GOT_UPDATE_URL - return ""; -#endif -} - -SoftwareUpdater::SoftwareUpdater(const RuntimeEnvironment *renv) : - RR(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::cleanOldUpdates() -{ - std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); - std::map dl(Utils::listDirectory(updatesDir.c_str())); - for(std::map::iterator i(dl.begin());i!=dl.end();++i) { - if (!i->second) - Utils::rm((updatesDir + ZT_PATH_SEPARATOR_S + i->first).c_str()); - } -} - -void SoftwareUpdater::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; - RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); - } - } - } -} - -void SoftwareUpdater::checkNow() -{ - Mutex::Lock _l(_lock); - if (_status == UPDATE_STATUS_IDLE) { - _lastUpdateAttempt = Utils::now(); - _status = UPDATE_STATUS_GETTING_NFO; - RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); - } -} - -const char *SoftwareUpdater::parseNfo( - const char *nfoText, - unsigned int &vMajor, - unsigned int &vMinor, - unsigned int &vRevision, - Address &signedBy, - std::string &signature, - std::string &url) -{ - try { - Dictionary nfo(nfoText); - - vMajor = Utils::strToUInt(nfo.get("vMajor").c_str()); - vMinor = Utils::strToUInt(nfo.get("vMinor").c_str()); - vRevision = Utils::strToUInt(nfo.get("vRevision").c_str()); - signedBy = nfo.get("signedBy"); - signature = Utils::unhex(nfo.get("ed25519")); - url = nfo.get("url"); - - if (signature.length() != ZT_C25519_SIGNATURE_LEN) - return "bad ed25519 signature, invalid length"; - if ((url.length() <= 7)||(url.substr(0,7) != "http://")) - return "invalid URL, must begin with http://"; - - return (const char *)0; - } catch ( ... ) { - return "invalid NFO file format or one or more required fields missing"; - } -} - -bool SoftwareUpdater::validateUpdate( - const void *data, - unsigned int len, - const Address &signedBy, - const std::string &signature) -{ - std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(signedBy); - if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) - return false; - return updateAuthority->second.verify(data,len,signature.data(),(unsigned int)signature.length()); -} - -void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body) -{ - SoftwareUpdater *upd = (SoftwareUpdater *)arg; - const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; - Mutex::Lock _l(upd->_lock); - - if ((upd->_die)||(upd->_status != UPDATE_STATUS_GETTING_NFO)) { - upd->_status = UPDATE_STATUS_IDLE; - return; - } - - if (code != 200) { - LOG("software update check failed: server responded with code %d",code); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - - try { - unsigned int vMajor = 0,vMinor = 0,vRevision = 0; - Address signedBy; - std::string signature,url; - - const char *err = parseNfo(body.c_str(),vMajor,vMinor,vRevision,signedBy,signature,url); - - if (err) { - LOG("software update check aborted: .nfo file parse error: %s",err); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - - if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) { - LOG("software update check aborted: .nfo file specifies unknown signing authority"); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - -#ifndef ZT_ALWAYS_UPDATE /* for testing */ - if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) { - TRACE("software update check complete: version on update site is not newer than my version, no update necessary"); - upd->_status = UPDATE_STATUS_IDLE; - return; - } -#endif - - upd->_status = UPDATE_STATUS_GETTING_FILE; - upd->_signedBy = signedBy; - upd->_signature = signature; - - RR->http->GET(url,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionBinary,arg); - } catch ( ... ) { - LOG("software update check failed: .nfo file invalid or missing field(s)"); - upd->_status = UPDATE_STATUS_IDLE; - } -} - -void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body) -{ - SoftwareUpdater *upd = (SoftwareUpdater *)arg; - const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; - Mutex::Lock _l(upd->_lock); - - if (!validateUpdate(body.data(),(unsigned int)body.length(),upd->_signedBy,upd->_signature)) { - LOG("software update failed: update fetched from '%s' failed signature check (image size: %u)",url.c_str(),(unsigned int)body.length()); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - - size_t lastSlash = url.rfind('/'); - if (lastSlash == std::string::npos) { // sanity check, shouldn't happen - LOG("software update failed: invalid URL"); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); - std::string updateFilename(url.substr(lastSlash + 1)); - if ((updateFilename.length() < 3)||(updateFilename.find("..") != std::string::npos)) { - LOG("software update failed: invalid URL: filename contains invalid characters"); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - for(std::string::iterator c(updateFilename.begin());c!=updateFilename.end();++c) { - // Only allow a list of whitelisted characters to make up the filename to prevent any - // path shenanigans, esp on Windows where / is not the path separator. - if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.0123456789",*c)) { - LOG("software update failed: invalid URL: filename contains invalid characters"); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - } - std::string updatePath(updatesDir + ZT_PATH_SEPARATOR_S + updateFilename); -#ifdef __WINDOWS__ - CreateDirectoryA(updatesDir.c_str(),NULL); -#else - mkdir(updatesDir.c_str(),0755); -#endif - - FILE *upf = fopen(updatePath.c_str(),"wb"); - if (!upf) { - LOG("software update failed: unable to open %s for writing",updatePath.c_str()); - upd->_status = UPDATE_STATUS_IDLE; - return; - } - if (fwrite(body.data(),body.length(),1,upf) != 1) { - LOG("software update failed: unable to write to %s",updatePath.c_str()); - upd->_status = UPDATE_STATUS_IDLE; - fclose(upf); - Utils::rm(updatePath); - return; - } - fclose(upf); - -#ifdef __UNIX_LIKE__ - ::chmod(updatePath.c_str(),0755); -#endif - - // We exit with this reason code and the path as the text. It is the - // caller's responsibility (main.c) to pick this up and do the right - // thing. - upd->_status = UPDATE_STATUS_IDLE; - RR->node->terminate(Node::NODE_RESTART_FOR_UPGRADE,updatePath.c_str()); -} - -} // namespace ZeroTier diff --git a/osdep/SoftwareUpdater.hpp b/osdep/SoftwareUpdater.hpp deleted file mode 100644 index 9beaa8ad..00000000 --- a/osdep/SoftwareUpdater.hpp +++ /dev/null @@ -1,186 +0,0 @@ -/* - * ZeroTier One - Network Virtualization Everywhere - * Copyright (C) 2011-2015 ZeroTier, Inc. - * - * 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 - -#include "../node/Constants.hpp" -#include "../node/Mutex.hpp" -#include "../node/Address.hpp" - -#include "HttpClient.hpp" - -/** - * Delay between fetches of the root topology update URL - * - * 86400000 = check once every 24 hours (this doesn't change often) - */ -#define ZT_UPDATE_ROOT_TOPOLOGY_CHECK_INTERVAL 86400000 - -/** - * Minimum interval between attempts to do a software update - */ -#define ZT_UPDATE_MIN_INTERVAL 120000 - -/** - * Maximum interval between checks for new versions - */ -#define ZT_UPDATE_MAX_INTERVAL 7200000 - -/** - * Software update HTTP timeout in seconds - */ -#define ZT_UPDATE_HTTP_TIMEOUT 120 - -namespace ZeroTier { - -/** - * Software updater - */ -class SoftwareUpdater -{ -public: - SoftwareUpdater(); - ~SoftwareUpdater(); - - /** - * Remove old updates in updates.d - */ - void cleanOldUpdates(); - - /** - * 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 - */ - void sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev); - - /** - * Check for updates now regardless of last check time or version - * - * This only starts a check if one is not in progress. Otherwise it does - * nothing. - */ - void checkNow(); - - /** - * Check for updates now if it's been longer than ZT_UPDATE_MAX_INTERVAL - * - * This is called periodically from the main loop. - */ - inline void checkIfMaxIntervalExceeded(uint64_t now) - { - if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MAX_INTERVAL) - checkNow(); - } - - /** - * 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) - * @return Version packed into an easily comparable 64-bit integer - */ - 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) ); - } - - /** - * Parse NFO data from .nfo file on software update site - * - * The first argument is the NFO data, and all the remaining arguments are - * result parameters to be filled with results. If an error is returned the - * results in the parameters should be considered undefined. - * - * @param nfo NFO data - * @param vMajor Result: major version - * @param vMinor Result: minor version - * @param vRevision Result: revision number - * @param signedBy Result: signing identity - * @param signature Result: Ed25519 signature data - * @param url Result: URL of update binary - * @return NULL on success or error message on failure - */ - static const char *parseNfo( - const char *nfoText, - unsigned int &vMajor, - unsigned int &vMinor, - unsigned int &vRevision, - Address &signedBy, - std::string &signature, - std::string &url); - - /** - * Validate an update once downloaded - * - * This obtains the identity corresponding to the address from the compiled-in - * list of valid signing identities. - * - * @param data Update data - * @param len Length of update data - * @param signedBy Signing authority address - * @param signature Signing authority signature - * @return True on validation success, false if rejected - */ - static bool validateUpdate( - const void *data, - unsigned int len, - const Address &signedBy, - const std::string &signature); - -private: - static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body); - static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body); - - HttpClient httpClient; - 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/updater/HttpClient.cpp b/updater/HttpClient.cpp new file mode 100644 index 00000000..1cf78204 --- /dev/null +++ b/updater/HttpClient.cpp @@ -0,0 +1,590 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 "../node/Constants.hpp" + +#include +#include +#include + +#ifdef __WINDOWS__ +#include +#include +#include +#include +#include +#endif // __WINDOWS__ + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#include +#include +#include +#include +#endif // __UNIX_LIKE__ + +#include +#include +#include + +#include "HttpClient.hpp" +#include "Thread.hpp" +#include "OSUtils.hpp" +#include "../node/Utils.hpp" + +namespace ZeroTier { + +#ifdef __UNIX_LIKE__ + +// The *nix implementation calls 'curl' externally rather than linking to it. +// This makes it an optional dependency that can be avoided in tiny systems +// provided you don't want to have automatic software updates... or want to +// do them via another method. + +#ifdef __APPLE__ +// TODO: get proxy configuration +#endif + +// Paths where "curl" may be found on the system +#define NUM_CURL_PATHS 6 +static const char *CURL_PATHS[NUM_CURL_PATHS] = { "/usr/bin/curl","/bin/curl","/usr/local/bin/curl","/usr/sbin/curl","/sbin/curl","/usr/libexec/curl" }; + +// Maximum message length +#define CURL_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) + +// Internal private thread class that performs request, notifies handler, +// and then commits suicide by deleting itself. +class HttpClient_Private_Request +{ +public: + HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : + _url(url), + _headers(headers), + _timeout(timeout), + _handler(handler), + _arg(arg), + _parent(parent), + _pid(0), + _cancelled(false) + { + _myThread = Thread::start(this); + } + + ~HttpClient_Private_Request() + { + Mutex::Lock _l(_parent->_requests_m); + _parent->_requests.erase((HttpClient::Request)this); + } + + void threadMain() + { + char *curlArgs[1024]; + char buf[16384]; + fd_set readfds,writefds,errfds; + struct timeval tv; + + std::string curlPath; + for(int i=0;i(curlPath.c_str()); + curlArgs[1] = const_cast ("-D"); + curlArgs[2] = const_cast ("-"); // append headers before output + int argPtr = 3; + std::vector headerArgs; + for(std::map::const_iterator h(_headers.begin());h!=_headers.end();++h) { + headerArgs.push_back(h->first); + headerArgs.back().append(": "); + headerArgs.back().append(h->second); + } + for(std::vector::iterator h(headerArgs.begin());h!=headerArgs.end();++h) { + if (argPtr >= (1024 - 4)) // leave room for terminating NULL and URL + break; + curlArgs[argPtr++] = const_cast ("-H"); + curlArgs[argPtr++] = const_cast (h->c_str()); + } + curlArgs[argPtr++] = const_cast (_url.c_str()); + curlArgs[argPtr] = (char *)0; + + if (_cancelled) { + delete this; + return; + } + + int curlStdout[2]; + int curlStderr[2]; + ::pipe(curlStdout); + ::pipe(curlStderr); + + _pid = (long)vfork(); + if (_pid < 0) { + // fork() failed + ::close(curlStdout[0]); + ::close(curlStdout[1]); + ::close(curlStderr[0]); + ::close(curlStderr[1]); + _doH(_arg,-1,_url,"unable to fork()"); + delete this; + return; + } else if (_pid > 0) { + // fork() succeeded, in parent process + ::close(curlStdout[1]); + ::close(curlStderr[1]); + fcntl(curlStdout[0],F_SETFL,O_NONBLOCK); + fcntl(curlStderr[0],F_SETFL,O_NONBLOCK); + + int exitCode = -1; + unsigned long long timesOutAt = OSUtils::now() + ((unsigned long long)_timeout * 1000ULL); + bool timedOut = false; + bool tooLong = false; + + while (!_cancelled) { + FD_ZERO(&readfds); + FD_ZERO(&writefds); + FD_ZERO(&errfds); + FD_SET(curlStdout[0],&readfds); + FD_SET(curlStderr[0],&readfds); + FD_SET(curlStdout[0],&errfds); + FD_SET(curlStderr[0],&errfds); + tv.tv_sec = 1; + tv.tv_usec = 0; + select(std::max(curlStdout[0],curlStderr[0])+1,&readfds,&writefds,&errfds,&tv); + + if (FD_ISSET(curlStdout[0],&readfds)) { + int n = (int)::read(curlStdout[0],buf,sizeof(buf)); + if (n > 0) { + _body.append(buf,n); + // Reset timeout when data is read... + timesOutAt = OSUtils::now() + ((unsigned long long)_timeout * 1000ULL); + } else if (n < 0) + break; + if (_body.length() > CURL_MAX_MESSAGE_LENGTH) { + tooLong = true; + break; + } + } + + if (FD_ISSET(curlStderr[0],&readfds)) + ::read(curlStderr[0],buf,sizeof(buf)); + + if (FD_ISSET(curlStdout[0],&errfds)||FD_ISSET(curlStderr[0],&errfds)) + break; + + if (OSUtils::now() >= timesOutAt) { + timedOut = true; + break; + } + + 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; + } + } + + if (_pid > 0) { + ::kill(_pid,SIGKILL); + waitpid(_pid,&exitCode,0); + } + _pid = 0; + + ::close(curlStdout[0]); + ::close(curlStderr[0]); + + if (timedOut) + _doH(_arg,-1,_url,"connection timed out"); + else if (tooLong) + _doH(_arg,-1,_url,"response too long"); + else if (exitCode) + _doH(_arg,-1,_url,"connection failed (curl returned non-zero exit code)"); + else { + unsigned long idx = 0; + + // Grab status line and headers, which will prefix output on + // success and will end with an empty line. + std::vector headers; + headers.push_back(std::string()); + while (idx < _body.length()) { + char c = _body[idx++]; + if (c == '\n') { + if (!headers.back().length()) { + headers.pop_back(); + break; + } else headers.push_back(std::string()); + } else if (c != '\r') + headers.back().push_back(c); + } + if (headers.empty()||(!headers.front().length())) { + _doH(_arg,-1,_url,"HTTP response empty"); + delete this; + return; + } + + // Parse first line -- HTTP status code and response + size_t scPos = headers.front().find(' '); + if (scPos == std::string::npos) { + _doH(_arg,-1,_url,"invalid HTTP response (no status line)"); + delete this; + return; + } + ++scPos; + unsigned int rcode = Utils::strToUInt(headers.front().substr(scPos,3).c_str()); + if ((!rcode)||(rcode > 999)) { + _doH(_arg,-1,_url,"invalid HTTP response (invalid response code)"); + delete this; + return; + } + + // Serve up the resulting data to the handler + if (rcode == 200) + _doH(_arg,rcode,_url,_body.substr(idx)); + else if ((scPos + 4) < headers.front().length()) + _doH(_arg,rcode,_url,headers.front().substr(scPos+4)); + else _doH(_arg,rcode,_url,"(no status message from server)"); + } + + delete this; + return; + } else { + // fork() succeeded, in child process + ::dup2(curlStdout[1],STDOUT_FILENO); + ::close(curlStdout[1]); + ::dup2(curlStderr[1],STDERR_FILENO); + ::close(curlStderr[1]); + ::execv(curlPath.c_str(),curlArgs); + ::exit(-1); // only reached if execv() fails + } + } + + inline void cancel() + { + { + Mutex::Lock _l(_cancelled_m); + _cancelled = true; + if (_pid > 0) + ::kill(_pid,SIGKILL); + } + Thread::join(_myThread); + } + +private: + inline void _doH(void *arg,int code,const std::string &url,const std::string &body) + { + Mutex::Lock _l(_cancelled_m); + try { + if ((!_cancelled)&&(_handler)) + _handler(arg,code,url,body); + } catch ( ... ) {} + } + + const std::string _url; + std::string _body; + std::map _headers; + unsigned int _timeout; + void (*_handler)(void *,int,const std::string &,const std::string &); + void *_arg; + HttpClient *_parent; + long _pid; + volatile bool _cancelled; + Mutex _cancelled_m; + Thread _myThread; +}; + +#endif // __UNIX_LIKE__ + +#ifdef __WINDOWS__ + +#define WIN_MAX_MESSAGE_LENGTH (1024 * 1024 * 64) + +// Internal private thread class that performs request, notifies handler, +// and then commits suicide by deleting itself. +class HttpClient_Private_Request : NonCopyable +{ +public: + HttpClient_Private_Request(HttpClient *parent,const char *method,const std::string &url,const std::map &headers,unsigned int timeout,void (*handler)(void *,int,const std::string &,const std::string &),void *arg) : + _url(url), + _headers(headers), + _timeout(timeout), + _handler(handler), + _arg(arg), + _parent(parent), + _hRequest((HINTERNET)0) + { + _myThread = Thread::start(this); + } + + ~HttpClient_Private_Request() + { + Mutex::Lock _l(_parent->_requests_m); + _parent->_requests.erase((HttpClient::Request)this); + } + + void threadMain() + { + HINTERNET hSession = (HINTERNET)0; + HINTERNET hConnect = (HINTERNET)0; + HINTERNET hRequest = (HINTERNET)0; + + try { + hSession = WinHttpOpen(L"ZeroTier One HttpClient/1.0 (WinHttp)",WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,WINHTTP_NO_PROXY_NAME,WINHTTP_NO_PROXY_BYPASS,0); + if (!hSession) { + _handler(_arg,-1,_url,"WinHttpOpen() failed"); + goto closeAndReturnFromHttp; + } + int timeoutMs = (int)_timeout * 1000; + WinHttpSetTimeouts(hSession,timeoutMs,timeoutMs,timeoutMs,timeoutMs); + + std::wstring_convert< std::codecvt_utf8 > wcconv; + std::wstring wurl(wcconv.from_bytes(_url)); + + URL_COMPONENTS uc; + memset(&uc,0,sizeof(uc)); + uc.dwStructSize = sizeof(uc); + uc.dwSchemeLength = -1; + uc.dwHostNameLength = -1; + uc.dwUrlPathLength = -1; + uc.dwExtraInfoLength = -1; + if (!WinHttpCrackUrl(wurl.c_str(),(DWORD)wurl.length(),0,&uc)) { + _handler(_arg,-1,_url,"unable to parse URL: WinHttpCrackUrl() failed"); + goto closeAndReturnFromHttp; + } + if ((!uc.lpszHostName)||(!uc.lpszUrlPath)||(!uc.lpszScheme)||(uc.dwHostNameLength <= 0)||(uc.dwUrlPathLength <= 0)||(uc.dwSchemeLength <= 0)) { + _handler(_arg,-1,_url,"unable to parse URL: missing scheme, host name, or path"); + goto closeAndReturnFromHttp; + } + std::wstring urlScheme(uc.lpszScheme,uc.dwSchemeLength); + std::wstring urlHostName(uc.lpszHostName,uc.dwHostNameLength); + std::wstring urlPath(uc.lpszUrlPath,uc.dwUrlPathLength); + if ((uc.lpszExtraInfo)&&(uc.dwExtraInfoLength > 0)) + urlPath.append(uc.lpszExtraInfo,uc.dwExtraInfoLength); + + if (urlScheme != L"http") { + _handler(_arg,-1,_url,"only 'http' scheme is supported"); + goto closeAndReturnFromHttp; + } + + hConnect = WinHttpConnect(hSession,urlHostName.c_str(),((uc.nPort > 0) ? uc.nPort : 80),0); + if (!hConnect) { + _handler(_arg,-1,_url,"connection failed"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + _hRequest = WinHttpOpenRequest(hConnect,L"GET",urlPath.c_str(),NULL,WINHTTP_NO_REFERER,WINHTTP_DEFAULT_ACCEPT_TYPES,0); + if (!_hRequest) { + _handler(_arg,-1,_url,"error sending request (1)"); + goto closeAndReturnFromHttp; + } + if (!WinHttpSendRequest(_hRequest,WINHTTP_NO_ADDITIONAL_HEADERS,0,WINHTTP_NO_REQUEST_DATA,0,0,0)) { + _handler(_arg,-1,_url,"error sending request (2)"); + goto closeAndReturnFromHttp; + } + hRequest = _hRequest; + } + + if (WinHttpReceiveResponse(hRequest,NULL)) { + DWORD dwStatusCode = 0; + DWORD dwTmp = sizeof(dwStatusCode); + WinHttpQueryHeaders(hRequest,WINHTTP_QUERY_STATUS_CODE| WINHTTP_QUERY_FLAG_NUMBER,NULL,&dwStatusCode,&dwTmp,NULL); + + DWORD dwSize; + do { + dwSize = 0; + if (!WinHttpQueryDataAvailable(hRequest,&dwSize)) { + _handler(_arg,-1,_url,"receive error (1)"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + } + + char *outBuffer = new char[dwSize]; + DWORD dwRead = 0; + if (!WinHttpReadData(hRequest,(LPVOID)outBuffer,dwSize,&dwRead)) { + _handler(_arg,-1,_url,"receive error (2)"); + goto closeAndReturnFromHttp; + } + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + + _body.append(outBuffer,dwRead); + delete [] outBuffer; + if (_body.length() > WIN_MAX_MESSAGE_LENGTH) { + _handler(_arg,-1,_url,"result too large"); + goto closeAndReturnFromHttp; + } + } + } while ((dwSize > 0)&&(_hRequest)); + + { + Mutex::Lock _rl(_hRequest_m); + if (!_hRequest) { + _handler(_arg,-1,_url,"request cancelled"); + goto closeAndReturnFromHttp; + } + + _handler(_arg,dwStatusCode,_url,_body); + } + } else { + _handler(_arg,-1,_url,"receive response failed"); + } + } catch ( ... ) { + _handler(_arg,-1,_url,"unexpected exception"); + } + +closeAndReturnFromHttp: + { + Mutex::Lock _rl(_hRequest_m); + if (_hRequest) { + WinHttpCloseHandle(_hRequest); + _hRequest = (HINTERNET)0; + } + } + if (hConnect) + WinHttpCloseHandle(hConnect); + if (hSession) + WinHttpCloseHandle(hSession); + delete this; + return; + } + + inline void cancel() + { + Mutex::Lock _rl(_hRequest_m); + if (_hRequest) { + WinHttpCloseHandle(_hRequest); + _hRequest = (HINTERNET)0; + } + } + + const std::string _url; + std::string _body; + std::map _headers; + unsigned int _timeout; + void (*_handler)(void *,int,const std::string &,const std::string &); + void *_arg; + HttpClient *_parent; + HINTERNET _hRequest; + Mutex _hRequest_m; + Thread _myThread; +}; + +#endif // __WINDOWS__ + +const std::map HttpClient::NO_HEADERS; + +HttpClient::HttpClient() +{ +} + +HttpClient::~HttpClient() +{ + std::set reqs; + { + Mutex::Lock _l(_requests_m); + reqs = _requests; + } + + for(std::set::iterator r(reqs.begin());r!=reqs.end();++r) + this->cancel(*r); + + for(;;) { + _requests_m.lock(); + if (_requests.empty()) { + _requests_m.unlock(); + break; + } else { + _requests_m.unlock(); + Thread::sleep(250); + } + } +} + +void HttpClient::cancel(HttpClient::Request req) +{ + Mutex::Lock _l(_requests_m); + if (_requests.count(req) == 0) + return; + ((HttpClient_Private_Request *)req)->cancel(); +} + +HttpClient::Request HttpClient::_do( + const char *method, + const std::string &url, + const std::map &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg) +{ + HttpClient::Request r = (HttpClient::Request)(new HttpClient_Private_Request(this,method,url,headers,timeout,handler,arg)); + Mutex::Lock _l(_requests_m); + _requests.insert(r); + return r; +} + +} // namespace ZeroTier diff --git a/updater/HttpClient.hpp b/updater/HttpClient.hpp new file mode 100644 index 00000000..00400a8c --- /dev/null +++ b/updater/HttpClient.hpp @@ -0,0 +1,110 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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_HTTPCLIENT_HPP +#define ZT_HTTPCLIENT_HPP + +#include +#include +#include + +#include "../node/Mutex.hpp" + +namespace ZeroTier { + +class HttpClient_Private_Request; + +/** + * HTTP client that does queries in the background + * + * The handler method takes the following arguments: an arbitrary pointer, an + * HTTP response code, the URL queried, whether or not the message body was + * stored on disk, and the message body. + * + * If stored on disk, the body string contains the path and the file must be + * moved or deleted by the receiver when it's done. If an error occurs, the + * response code will be negative and the body will be the error message. + * + * All headers in the returned headers map will have their header names + * converted to lower case, e.g. "content-type". + * + * Currently only the "http" transport is guaranteed to be supported on all + * platforms. + */ +class HttpClient +{ +public: + friend class HttpClient_Private_Request; + typedef void * Request; + + HttpClient(); + ~HttpClient(); + + /** + * Empty map for convenience use + */ + static const std::map NO_HEADERS; + + /** + * Request a URL using the GET method + */ + inline Request GET( + const std::string &url, + const std::map &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg) + { + return _do("GET",url,headers,timeout,handler,arg); + } + + /** + * Cancel a request + * + * If the request is not active, this does nothing. This may take some time + * depending on HTTP implementation. It may also not kill instantly, but + * it will prevent the handler function from ever being called and cause the + * request to die silently when complete. + */ + void cancel(Request req); + +private: + Request _do( + const char *method, + const std::string &url, + const std::map &headers, + unsigned int timeout, + void (*handler)(void *,int,const std::string &,const std::string &), + void *arg); + + std::set _requests; + Mutex _requests_m; +}; + +} // namespace ZeroTier + +#endif diff --git a/updater/SoftwareUpdater.cpp b/updater/SoftwareUpdater.cpp new file mode 100644 index 00000000..e3789bcb --- /dev/null +++ b/updater/SoftwareUpdater.cpp @@ -0,0 +1,328 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 + +#include "../version.h" + +#include "Constants.hpp" +#include "SoftwareUpdater.hpp" +#include "Dictionary.hpp" +#include "C25519.hpp" +#include "Identity.hpp" +#include "Logger.hpp" +#include "RuntimeEnvironment.hpp" +#include "Thread.hpp" +#include "Node.hpp" +#include "Utils.hpp" +#include "HttpClient.hpp" + +#ifdef __UNIX_LIKE__ +#include +#include +#include +#include +#endif + +namespace ZeroTier { + +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; +} + +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/ZeroTierOneInstaller-linux-x64-LATEST.nfo"; + else return "http://download.zerotier.com/ZeroTierOneInstaller-linux-x86-LATEST.nfo"; +#define GOT_UPDATE_URL +#endif + +#ifdef __APPLE__ + return "http://download.zerotier.com/ZeroTierOneInstaller-mac-combined-LATEST.nfo"; +#define GOT_UPDATE_URL +#endif + +#ifdef __WINDOWS__ + return "http://download.zerotier.com/ZeroTierOneInstaller-windows-intel-LATEST.nfo"; +#define GOT_UPDATE_URL +#endif + +#ifndef GOT_UPDATE_URL + return ""; +#endif +} + +SoftwareUpdater::SoftwareUpdater(const RuntimeEnvironment *renv) : + RR(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::cleanOldUpdates() +{ + std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::map dl(Utils::listDirectory(updatesDir.c_str())); + for(std::map::iterator i(dl.begin());i!=dl.end();++i) { + if (!i->second) + Utils::rm((updatesDir + ZT_PATH_SEPARATOR_S + i->first).c_str()); + } +} + +void SoftwareUpdater::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; + RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } + } + } +} + +void SoftwareUpdater::checkNow() +{ + Mutex::Lock _l(_lock); + if (_status == UPDATE_STATUS_IDLE) { + _lastUpdateAttempt = Utils::now(); + _status = UPDATE_STATUS_GETTING_NFO; + RR->http->GET(ZT_DEFAULTS.updateLatestNfoURL,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionInfo,this); + } +} + +const char *SoftwareUpdater::parseNfo( + const char *nfoText, + unsigned int &vMajor, + unsigned int &vMinor, + unsigned int &vRevision, + Address &signedBy, + std::string &signature, + std::string &url) +{ + try { + Dictionary nfo(nfoText); + + vMajor = Utils::strToUInt(nfo.get("vMajor").c_str()); + vMinor = Utils::strToUInt(nfo.get("vMinor").c_str()); + vRevision = Utils::strToUInt(nfo.get("vRevision").c_str()); + signedBy = nfo.get("signedBy"); + signature = Utils::unhex(nfo.get("ed25519")); + url = nfo.get("url"); + + if (signature.length() != ZT_C25519_SIGNATURE_LEN) + return "bad ed25519 signature, invalid length"; + if ((url.length() <= 7)||(url.substr(0,7) != "http://")) + return "invalid URL, must begin with http://"; + + return (const char *)0; + } catch ( ... ) { + return "invalid NFO file format or one or more required fields missing"; + } +} + +bool SoftwareUpdater::validateUpdate( + const void *data, + unsigned int len, + const Address &signedBy, + const std::string &signature) +{ + std::map< Address,Identity >::const_iterator updateAuthority = ZT_DEFAULTS.updateAuthorities.find(signedBy); + if (updateAuthority == ZT_DEFAULTS.updateAuthorities.end()) + return false; + return updateAuthority->second.verify(data,len,signature.data(),(unsigned int)signature.length()); +} + +void SoftwareUpdater::_cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; + Mutex::Lock _l(upd->_lock); + + if ((upd->_die)||(upd->_status != UPDATE_STATUS_GETTING_NFO)) { + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (code != 200) { + LOG("software update check failed: server responded with code %d",code); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + try { + unsigned int vMajor = 0,vMinor = 0,vRevision = 0; + Address signedBy; + std::string signature,url; + + const char *err = parseNfo(body.c_str(),vMajor,vMinor,vRevision,signedBy,signature,url); + + if (err) { + LOG("software update check aborted: .nfo file parse error: %s",err); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + if (!ZT_DEFAULTS.updateAuthorities.count(signedBy)) { + LOG("software update check aborted: .nfo file specifies unknown signing authority"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + +#ifndef ZT_ALWAYS_UPDATE /* for testing */ + if (packVersion(vMajor,vMinor,vRevision) <= upd->_myVersion) { + TRACE("software update check complete: version on update site is not newer than my version, no update necessary"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } +#endif + + upd->_status = UPDATE_STATUS_GETTING_FILE; + upd->_signedBy = signedBy; + upd->_signature = signature; + + RR->http->GET(url,HttpClient::NO_HEADERS,ZT_UPDATE_HTTP_TIMEOUT,&_cbHandleGetLatestVersionBinary,arg); + } catch ( ... ) { + LOG("software update check failed: .nfo file invalid or missing field(s)"); + upd->_status = UPDATE_STATUS_IDLE; + } +} + +void SoftwareUpdater::_cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body) +{ + SoftwareUpdater *upd = (SoftwareUpdater *)arg; + const RuntimeEnvironment *RR = (const RuntimeEnvironment *)upd->RR; + Mutex::Lock _l(upd->_lock); + + if (!validateUpdate(body.data(),(unsigned int)body.length(),upd->_signedBy,upd->_signature)) { + LOG("software update failed: update fetched from '%s' failed signature check (image size: %u)",url.c_str(),(unsigned int)body.length()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + + size_t lastSlash = url.rfind('/'); + if (lastSlash == std::string::npos) { // sanity check, shouldn't happen + LOG("software update failed: invalid URL"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + std::string updatesDir(RR->homePath + ZT_PATH_SEPARATOR_S + "updates.d"); + std::string updateFilename(url.substr(lastSlash + 1)); + if ((updateFilename.length() < 3)||(updateFilename.find("..") != std::string::npos)) { + LOG("software update failed: invalid URL: filename contains invalid characters"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + for(std::string::iterator c(updateFilename.begin());c!=updateFilename.end();++c) { + // Only allow a list of whitelisted characters to make up the filename to prevent any + // path shenanigans, esp on Windows where / is not the path separator. + if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.0123456789",*c)) { + LOG("software update failed: invalid URL: filename contains invalid characters"); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + } + std::string updatePath(updatesDir + ZT_PATH_SEPARATOR_S + updateFilename); +#ifdef __WINDOWS__ + CreateDirectoryA(updatesDir.c_str(),NULL); +#else + mkdir(updatesDir.c_str(),0755); +#endif + + FILE *upf = fopen(updatePath.c_str(),"wb"); + if (!upf) { + LOG("software update failed: unable to open %s for writing",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + return; + } + if (fwrite(body.data(),body.length(),1,upf) != 1) { + LOG("software update failed: unable to write to %s",updatePath.c_str()); + upd->_status = UPDATE_STATUS_IDLE; + fclose(upf); + Utils::rm(updatePath); + return; + } + fclose(upf); + +#ifdef __UNIX_LIKE__ + ::chmod(updatePath.c_str(),0755); +#endif + + // We exit with this reason code and the path as the text. It is the + // caller's responsibility (main.c) to pick this up and do the right + // thing. + upd->_status = UPDATE_STATUS_IDLE; + RR->node->terminate(Node::NODE_RESTART_FOR_UPGRADE,updatePath.c_str()); +} + +} // namespace ZeroTier diff --git a/updater/SoftwareUpdater.hpp b/updater/SoftwareUpdater.hpp new file mode 100644 index 00000000..9beaa8ad --- /dev/null +++ b/updater/SoftwareUpdater.hpp @@ -0,0 +1,186 @@ +/* + * ZeroTier One - Network Virtualization Everywhere + * Copyright (C) 2011-2015 ZeroTier, Inc. + * + * 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 + +#include "../node/Constants.hpp" +#include "../node/Mutex.hpp" +#include "../node/Address.hpp" + +#include "HttpClient.hpp" + +/** + * Delay between fetches of the root topology update URL + * + * 86400000 = check once every 24 hours (this doesn't change often) + */ +#define ZT_UPDATE_ROOT_TOPOLOGY_CHECK_INTERVAL 86400000 + +/** + * Minimum interval between attempts to do a software update + */ +#define ZT_UPDATE_MIN_INTERVAL 120000 + +/** + * Maximum interval between checks for new versions + */ +#define ZT_UPDATE_MAX_INTERVAL 7200000 + +/** + * Software update HTTP timeout in seconds + */ +#define ZT_UPDATE_HTTP_TIMEOUT 120 + +namespace ZeroTier { + +/** + * Software updater + */ +class SoftwareUpdater +{ +public: + SoftwareUpdater(); + ~SoftwareUpdater(); + + /** + * Remove old updates in updates.d + */ + void cleanOldUpdates(); + + /** + * 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 + */ + void sawRemoteVersion(unsigned int vmaj,unsigned int vmin,unsigned int rev); + + /** + * Check for updates now regardless of last check time or version + * + * This only starts a check if one is not in progress. Otherwise it does + * nothing. + */ + void checkNow(); + + /** + * Check for updates now if it's been longer than ZT_UPDATE_MAX_INTERVAL + * + * This is called periodically from the main loop. + */ + inline void checkIfMaxIntervalExceeded(uint64_t now) + { + if ((now - _lastUpdateAttempt) >= ZT_UPDATE_MAX_INTERVAL) + checkNow(); + } + + /** + * 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) + * @return Version packed into an easily comparable 64-bit integer + */ + 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) ); + } + + /** + * Parse NFO data from .nfo file on software update site + * + * The first argument is the NFO data, and all the remaining arguments are + * result parameters to be filled with results. If an error is returned the + * results in the parameters should be considered undefined. + * + * @param nfo NFO data + * @param vMajor Result: major version + * @param vMinor Result: minor version + * @param vRevision Result: revision number + * @param signedBy Result: signing identity + * @param signature Result: Ed25519 signature data + * @param url Result: URL of update binary + * @return NULL on success or error message on failure + */ + static const char *parseNfo( + const char *nfoText, + unsigned int &vMajor, + unsigned int &vMinor, + unsigned int &vRevision, + Address &signedBy, + std::string &signature, + std::string &url); + + /** + * Validate an update once downloaded + * + * This obtains the identity corresponding to the address from the compiled-in + * list of valid signing identities. + * + * @param data Update data + * @param len Length of update data + * @param signedBy Signing authority address + * @param signature Signing authority signature + * @return True on validation success, false if rejected + */ + static bool validateUpdate( + const void *data, + unsigned int len, + const Address &signedBy, + const std::string &signature); + +private: + static void _cbHandleGetLatestVersionInfo(void *arg,int code,const std::string &url,const std::string &body); + static void _cbHandleGetLatestVersionBinary(void *arg,int code,const std::string &url,const std::string &body); + + HttpClient httpClient; + 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 -- cgit v1.2.3