2013-07-04 20:56:19 +00:00
/*
* 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 < http : //www.gnu.org/licenses/>.
*
* - -
*
* 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 <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
# include <errno.h>
2013-08-26 21:22:20 +00:00
2013-07-04 20:56:19 +00:00
# include <string>
# include <stdexcept>
2013-08-10 14:27:53 +00:00
# include "node/Constants.hpp"
# ifdef __WINDOWS__
2013-08-26 21:22:20 +00:00
# include <WinSock2.h>
2013-07-04 20:56:19 +00:00
# include <Windows.h>
2013-08-26 21:22:20 +00:00
# include <tchar.h>
# include <wchar.h>
2013-07-04 20:56:19 +00:00
# else
# include <unistd.h>
# include <pwd.h>
2013-12-10 23:30:53 +00:00
# include <fcntl.h>
2013-07-04 20:56:19 +00:00
# include <sys/types.h>
# include <sys/stat.h>
# include <signal.h>
# endif
2013-08-26 21:22:20 +00:00
# include "node/Constants.hpp"
# include "node/Defaults.hpp"
2013-07-04 20:56:19 +00:00
# include "node/Utils.hpp"
2013-08-26 21:22:20 +00:00
# include "node/Node.hpp"
2013-11-08 20:23:48 +00:00
# include "node/Condition.hpp"
2013-12-04 22:44:28 +00:00
# include "node/C25519.hpp"
# include "node/Identity.hpp"
2013-07-04 20:56:19 +00:00
using namespace ZeroTier ;
static Node * node = ( Node * ) 0 ;
static void printHelp ( const char * cn , FILE * out )
{
2013-09-17 18:47:48 +00:00
fprintf ( out , " ZeroTier One version %d.%d.%d " ZT_EOL_S " (c)2012-2013 ZeroTier Networks LLC " ZT_EOL_S , Node : : versionMajor ( ) , Node : : versionMinor ( ) , Node : : versionRevision ( ) ) ;
fprintf ( out , " Licensed under the GNU General Public License v3 " ZT_EOL_S " " ZT_EOL_S ) ;
2013-12-12 15:50:04 +00:00
# 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 ) ;
# else
fprintf ( out , " Auto-updates not enabled on this build. You must update manually. " ZT_EOL_S " " ZT_EOL_S ) ;
# endif
2013-09-17 18:47:48 +00:00
fprintf ( out , " Usage: %s [-switches] [home directory] " ZT_EOL_S " " ZT_EOL_S , cn ) ;
fprintf ( out , " Available switches: " ZT_EOL_S ) ;
2013-12-05 00:29:49 +00:00
fprintf ( out , " -h - Display this help " ZT_EOL_S ) ;
fprintf ( out , " -v - Show version " ZT_EOL_S ) ;
fprintf ( out , " -p<port> - Bind to this port for network I/O " ZT_EOL_S ) ;
fprintf ( out , " -c<port> - Bind to this port for local control packets " ZT_EOL_S ) ;
fprintf ( out , " -q - Send a query to a running service (zerotier-cli) " ZT_EOL_S ) ;
fprintf ( out , " -i - Run idtool command (zerotier-idtool) " ZT_EOL_S ) ;
2013-07-04 20:56:19 +00:00
}
2013-12-04 22:44:28 +00:00
namespace ZeroTierCLI { // ---------------------------------------------------
2013-11-08 20:23:48 +00:00
static void printHelp ( FILE * out , const char * exename )
{
fprintf ( out , " Usage: %s [-switches] <command> " ZT_EOL_S , exename ) ;
fprintf ( out , ZT_EOL_S ) ;
fprintf ( out , " Available switches: " ZT_EOL_S ) ;
2013-12-05 00:29:49 +00:00
fprintf ( out , " -c<port> - Communicate with daemon over this local port " ZT_EOL_S ) ;
fprintf ( out , " -t<token> - Specify token on command line " ZT_EOL_S ) ;
fprintf ( out , " -T<file> - Read token from file " ZT_EOL_S ) ;
2013-11-08 20:23:48 +00:00
fprintf ( out , ZT_EOL_S ) ;
fprintf ( out , " Use the 'help' command to get help from ZeroTier One itself. " ZT_EOL_S ) ;
}
static volatile unsigned int numResults = 0 ;
static Condition doneCondition ;
static void resultHandler ( void * arg , unsigned long id , const char * line )
{
+ + numResults ;
if ( strlen ( line ) )
fprintf ( stdout , " %s " ZT_EOL_S , line ) ;
else doneCondition . signal ( ) ;
}
// Runs instead of rest of main() if process is called zerotier-cli or if
// -q is specified as an option.
# ifdef __WINDOWS__
static int main ( int argc , _TCHAR * argv [ ] )
# else
static int main ( int argc , char * * argv )
# endif
{
if ( argc < = 1 ) {
printHelp ( stdout , argv [ 0 ] ) ;
return - 1 ;
}
std : : string authToken ;
std : : string command ;
bool pastSwitches = false ;
unsigned int controlPort = 0 ;
for ( int i = 1 ; i < argc ; + + i ) {
if ( ( argv [ i ] [ 0 ] = = ' - ' ) & & ( ! pastSwitches ) ) {
if ( strlen ( argv [ i ] ) < = 1 ) {
printHelp ( stdout , argv [ 0 ] ) ;
return - 1 ;
}
switch ( argv [ i ] [ 1 ] ) {
case ' q ' : // does nothing, for invocation without binary path name aliasing
if ( argv [ i ] [ 2 ] ) {
printHelp ( argv [ 0 ] , stderr ) ;
return 0 ;
}
break ;
case ' c ' :
controlPort = Utils : : strToUInt ( argv [ i ] + 2 ) ;
break ;
case ' t ' :
authToken . assign ( argv [ i ] + 2 ) ;
break ;
case ' T ' :
if ( ! Utils : : readFile ( argv [ i ] + 2 , authToken ) ) {
fprintf ( stdout , " FATAL ERROR: unable to read token from '%s' " ZT_EOL_S , argv [ i ] + 2 ) ;
return - 2 ;
}
break ;
2014-01-03 22:03:29 +00:00
case ' h ' :
printHelp ( stdout , argv [ 0 ] ) ;
return 0 ;
2013-11-08 20:23:48 +00:00
default :
return - 1 ;
}
} else {
pastSwitches = true ;
if ( command . length ( ) )
command . push_back ( ' ' ) ;
command . append ( argv [ i ] ) ;
}
}
2013-11-08 20:45:28 +00:00
if ( ! command . length ( ) ) {
printHelp ( stdout , argv [ 0 ] ) ;
return - 1 ;
}
2013-11-08 20:23:48 +00:00
if ( ! authToken . length ( ) ) {
2014-01-03 22:03:29 +00:00
if ( ! Utils : : readFile ( Node : : LocalClient : : authTokenDefaultUserPath ( ) . c_str ( ) , authToken ) ) {
if ( ! Utils : : readFile ( Node : : LocalClient : : authTokenDefaultSystemPath ( ) . c_str ( ) , authToken ) ) {
fprintf ( stdout , " FATAL ERROR: no token specified on command line and could not read '%s' or '%s' " ZT_EOL_S , Node : : LocalClient : : authTokenDefaultSystemPath ( ) . c_str ( ) , Node : : LocalClient : : authTokenDefaultUserPath ( ) . c_str ( ) ) ;
2013-11-08 20:23:48 +00:00
return - 2 ;
}
}
}
if ( ! authToken . length ( ) ) {
fprintf ( stdout , " FATAL ERROR: could not find auth token " ZT_EOL_S ) ;
return - 2 ;
}
Node : : LocalClient client ( authToken . c_str ( ) , controlPort , & resultHandler , ( void * ) 0 ) ;
client . send ( command . c_str ( ) ) ;
doneCondition . wait ( 1000 ) ;
if ( ! numResults ) {
fprintf ( stdout , " ERROR: no results received. Is ZeroTier One running? " ZT_EOL_S ) ;
return - 1 ;
}
return 0 ;
}
2013-12-04 22:44:28 +00:00
} // namespace ZeroTierCLI ---------------------------------------------------
namespace ZeroTierIdTool { // ------------------------------------------------
static void printHelp ( FILE * out , const char * pn )
{
fprintf ( out , " Usage: %s <command> [<args>] " ZT_EOL_S " " ZT_EOL_S " Commands: " ZT_EOL_S , pn ) ;
fprintf ( out , " generate [<identity.secret>] [<identity.public>] " ZT_EOL_S ) ;
fprintf ( out , " validate <identity.secret/public> " ZT_EOL_S ) ;
fprintf ( out , " getpublic <identity.secret> " ZT_EOL_S ) ;
fprintf ( out , " sign <identity.secret> <file> " ZT_EOL_S ) ;
fprintf ( out , " verify <identity.secret/public> <file> <signature> " ZT_EOL_S ) ;
}
static Identity getIdFromArg ( char * arg )
{
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 ( ) ;
}
// Runs instead of rest of main() if process is called zerotier-idtool or if
// -i is specified as an option.
# ifdef __WINDOWS__
static int main ( int argc , _TCHAR * argv [ ] )
# else
static int main ( int argc , char * * argv )
# endif
{
if ( argc < 2 ) {
printHelp ( stderr , 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 ) {
printHelp ( stderr , 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 ) {
printHelp ( stderr , 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 ) {
printHelp ( stderr , 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 ( ) , inf . length ( ) ) ;
printf ( " %s " , Utils : : hex ( signature . data , signature . size ( ) ) . c_str ( ) ) ;
} else if ( ! strcmp ( argv [ 1 ] , " verify " ) ) {
if ( argc < 4 ) {
printHelp ( stderr , 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 ( ) , inf . length ( ) , signature . data ( ) , 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 {
printHelp ( stderr , argv [ 0 ] ) ;
return - 1 ;
}
return 0 ;
}
} // namespace ZeroTierIdTool ------------------------------------------------
2013-11-08 20:23:48 +00:00
2013-08-26 21:22:20 +00:00
# ifdef __UNIX_LIKE__
2013-07-04 20:56:19 +00:00
static void sighandlerQuit ( int sig )
{
Node * n = node ;
if ( n )
2013-08-30 19:02:12 +00:00
n - > terminate ( Node : : NODE_NORMAL_TERMINATION , " terminated by signal " ) ;
2013-07-04 20:56:19 +00:00
else exit ( 0 ) ;
}
# endif
2013-08-27 20:11:39 +00:00
# ifdef __WINDOWS__
static BOOL WINAPI _handlerRoutine ( 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 )
2013-08-30 19:02:12 +00:00
n - > terminate ( Node : : NODE_NORMAL_TERMINATION , " terminated by signal " ) ;
2013-08-27 20:11:39 +00:00
return TRUE ;
}
return FALSE ;
}
# endif
2013-08-26 21:22:20 +00:00
# ifdef __WINDOWS__
int _tmain ( int argc , _TCHAR * argv [ ] )
# else
2013-07-04 20:56:19 +00:00
int main ( int argc , char * * argv )
2013-08-26 21:22:20 +00:00
# endif
2013-07-04 20:56:19 +00:00
{
2013-08-26 21:22:20 +00:00
# ifdef __UNIX_LIKE__
2013-07-17 18:39:34 +00:00
signal ( SIGHUP , SIG_IGN ) ;
2013-07-04 20:56:19 +00:00
signal ( SIGPIPE , SIG_IGN ) ;
2013-07-17 18:39:34 +00:00
signal ( SIGUSR1 , SIG_IGN ) ;
signal ( SIGUSR2 , SIG_IGN ) ;
2013-07-04 20:56:19 +00:00
signal ( SIGALRM , SIG_IGN ) ;
signal ( SIGINT , & sighandlerQuit ) ;
signal ( SIGTERM , & sighandlerQuit ) ;
signal ( SIGQUIT , & sighandlerQuit ) ;
# endif
2013-08-26 21:22:20 +00:00
# ifdef __WINDOWS__
WSADATA wsaData ;
WSAStartup ( MAKEWORD ( 2 , 2 ) , & wsaData ) ;
2013-08-27 20:11:39 +00:00
SetConsoleCtrlHandler ( & _handlerRoutine , TRUE ) ;
2013-08-26 21:22:20 +00:00
# endif
2013-08-10 14:27:53 +00:00
2013-11-08 20:23:48 +00:00
if ( ( strstr ( argv [ 0 ] , " zerotier-cli " ) ) | | ( strstr ( argv [ 0 ] , " ZEROTIER-CLI " ) ) )
return ZeroTierCLI : : main ( argc , argv ) ;
2013-12-04 22:44:28 +00:00
if ( ( strstr ( argv [ 0 ] , " zerotier-idtool " ) ) | | ( strstr ( argv [ 0 ] , " ZEROTIER-IDTOOL " ) ) )
return ZeroTierIdTool : : main ( argc , argv ) ;
2013-11-08 20:23:48 +00:00
2013-07-04 20:56:19 +00:00
const char * homeDir = ( const char * ) 0 ;
2013-09-17 18:47:48 +00:00
unsigned int port = 0 ;
unsigned int controlPort = 0 ;
2013-07-04 20:56:19 +00:00
for ( int i = 1 ; i < argc ; + + i ) {
if ( argv [ i ] [ 0 ] = = ' - ' ) {
switch ( argv [ i ] [ 1 ] ) {
2013-09-17 18:47:48 +00:00
case ' p ' :
port = Utils : : strToUInt ( argv [ i ] + 2 ) ;
if ( port > 65535 ) {
printHelp ( argv [ 0 ] , stderr ) ;
2013-12-11 00:38:45 +00:00
return 1 ;
2013-09-17 18:47:48 +00:00
}
break ;
2013-12-05 00:29:49 +00:00
case ' v ' :
printf ( " %s " ZT_EOL_S , Node : : versionString ( ) ) ;
return 0 ;
2013-09-17 18:47:48 +00:00
case ' c ' :
controlPort = Utils : : strToUInt ( argv [ i ] + 2 ) ;
if ( controlPort > 65535 ) {
printHelp ( argv [ 0 ] , stderr ) ;
2013-12-11 00:38:45 +00:00
return 1 ;
2013-09-17 18:47:48 +00:00
}
break ;
2013-11-08 20:23:48 +00:00
case ' q ' :
if ( argv [ i ] [ 2 ] ) {
printHelp ( argv [ 0 ] , stderr ) ;
return 0 ;
} else return ZeroTierCLI : : main ( argc , argv ) ;
2013-12-04 22:44:28 +00:00
case ' i ' :
if ( argv [ i ] [ 2 ] ) {
printHelp ( argv [ 0 ] , stderr ) ;
return 0 ;
} else return ZeroTierIdTool : : main ( argc , argv ) ;
2013-08-26 21:22:20 +00:00
case ' h ' :
case ' ? ' :
2013-07-04 20:56:19 +00:00
default :
printHelp ( argv [ 0 ] , stderr ) ;
2013-08-30 19:02:12 +00:00
return 0 ;
2013-07-04 20:56:19 +00:00
}
} else {
if ( homeDir ) {
printHelp ( argv [ 0 ] , stderr ) ;
2013-08-30 19:02:12 +00:00
return 0 ;
2013-07-04 20:56:19 +00:00
}
homeDir = argv [ i ] ;
break ;
}
}
2013-08-26 21:22:20 +00:00
if ( ( ! homeDir ) | | ( strlen ( homeDir ) = = 0 ) )
homeDir = ZT_DEFAULTS . defaultHomePath . c_str ( ) ;
# ifdef __UNIX_LIKE__
2013-12-11 00:38:45 +00:00
if ( getuid ( ) ) {
fprintf ( stderr , " %s: must be run as root (uid==0) \n " , argv [ 0 ] ) ;
return 1 ;
}
2013-07-04 20:56:19 +00:00
mkdir ( homeDir , 0755 ) ; // will fail if it already exists
2013-11-06 19:43:47 +00:00
{
char pidpath [ 4096 ] ;
2013-11-08 16:42:11 +00:00
Utils : : snprintf ( pidpath , sizeof ( pidpath ) , " %s/zerotier-one.pid " , homeDir ) ;
2013-11-06 19:43:47 +00:00
FILE * pf = fopen ( pidpath , " w " ) ;
if ( pf ) {
fprintf ( pf , " %ld " , ( long ) getpid ( ) ) ;
fclose ( pf ) ;
}
}
2013-07-04 20:56:19 +00:00
# endif
2013-08-30 19:02:12 +00:00
int exitCode = 0 ;
2013-07-04 20:56:19 +00:00
2013-11-06 19:43:47 +00:00
try {
node = new Node ( homeDir , port , controlPort ) ;
switch ( node - > run ( ) ) {
2013-12-11 00:13:07 +00:00
case Node : : NODE_RESTART_FOR_UPGRADE : {
2013-12-10 23:30:53 +00:00
# ifdef __UNIX_LIKE__
const char * upgPath = node - > reasonForTermination ( ) ;
if ( upgPath )
execl ( upgPath , upgPath , " -s " , ( char * ) 0 ) ; // -s = (re)start after install/upgrade
2013-12-11 00:38:45 +00:00
exitCode = 2 ;
fprintf ( stderr , " %s: abnormal termination: unable to execute update at %s \n " , argv [ 0 ] , ( upgPath ) ? upgPath : " (unknown path) " ) ;
2013-12-10 23:30:53 +00:00
# endif
} break ;
case Node : : NODE_UNRECOVERABLE_ERROR : {
2013-12-11 00:38:45 +00:00
exitCode = 3 ;
2013-12-10 23:30:53 +00:00
const char * termReason = node - > reasonForTermination ( ) ;
2013-11-06 19:43:47 +00:00
fprintf ( stderr , " %s: abnormal termination: %s \n " , argv [ 0 ] , ( termReason ) ? termReason : " (unknown reason) " ) ;
2013-12-10 23:30:53 +00:00
} break ;
2013-11-06 19:43:47 +00:00
default :
break ;
}
delete node ;
node = ( Node * ) 0 ;
} catch ( . . . ) { }
# ifdef __UNIX_LIKE__
{
char pidpath [ 4096 ] ;
2013-11-08 16:42:11 +00:00
Utils : : snprintf ( pidpath , sizeof ( pidpath ) , " %s/zerotier-one.pid " , homeDir ) ;
2013-11-06 19:43:47 +00:00
Utils : : rm ( pidpath ) ;
2013-07-04 20:56:19 +00:00
}
2013-11-06 19:43:47 +00:00
# endif
2013-07-04 20:56:19 +00:00
return exitCode ;
}